@@ -826,14 +826,27 @@ static void dohd_reply(int fd, short __attribute__((unused)) revents,
826826 req = (struct req_slot * )arg ;
827827 cd = req -> owner ;
828828
829- /* Cancel timeout timer - we got a response */
829+ /* Cancel timeout timer - we got a response (or error) */
830830 if (req -> timeout_timer ) {
831831 evquick_deltimer (req -> timeout_timer );
832832 req -> timeout_timer = NULL ;
833833 }
834834
835835 len = recv (fd , buff , bufsz , 0 );
836+ if (len <= 0 ) {
837+ /* recv() error or connection closed */
838+ if (len < 0 && (errno == EAGAIN || errno == EWOULDBLOCK )) {
839+ /* Socket timeout (SO_RCVTIMEO) - not a real error, just no data yet.
840+ * This shouldn't happen with epoll (event only fires when data ready),
841+ * but handle gracefully. */
842+ dohprint (DOH_WARN , "DNS socket recv timeout (fd %d)" , fd );
843+ }
844+ /* Don't log for normal close (len == 0) or transient errors */
845+ goto destroy ;
846+ }
836847 if (len < 16 ) {
848+ /* DNS response too short to be valid */
849+ dohprint (DOH_WARN , "DNS response too short (%d bytes)" , len );
837850 goto destroy ;
838851 }
839852
@@ -1220,10 +1233,12 @@ static void usage(const char *name)
12201233
12211234 fprintf (stderr , "%s, DNSoverHTTPS minimalist daemon.\n" , name );
12221235 fprintf (stderr , "License: AGPL\n" );
1223- fprintf (stderr , "Usage: %s -c cert -k key [-p port] [-d dnsserver] [-F] [-u user] [-V] [-v] [-h]\n" , name );
1236+ fprintf (stderr , "Usage: %s -c cert -k key [-p port] [-d dnsserver] [-4|-6] [- F] [-u user] [-V] [-v] [-h]\n" , name );
12241237 fprintf (stderr , "\t'cert' and 'key': certificate and its private key.\n" );
12251238 fprintf (stderr , "\t'user' : login name (when running as root) to switch to (dropping permissions)\n" );
12261239 fprintf (stderr , "\tDefault values: port=8053 dnsserver=\"::1\"\n" );
1240+ fprintf (stderr , "\tUse '-4' to force IPv4 only\n" );
1241+ fprintf (stderr , "\tUse '-6' to force IPv6 only (default: dual-stack)\n" );
12271242 fprintf (stderr , "\tUse '-h' for help\n" );
12281243 fprintf (stderr , "\tUse '-V' to show version\n" );
12291244 fprintf (stderr , "\tUse '-v' for verbose mode\n" );
@@ -1237,7 +1252,9 @@ int main(int argc, char *argv[])
12371252 char * cert = NULL , * key = NULL ;
12381253 char * user = NULL ;
12391254 uint16_t port = DOH_PORT ;
1240- struct sockaddr_in6 serv_addr ;
1255+ int ip_version = 0 ; /* 0 = dual-stack (default), 4 = IPv4 only, 6 = IPv6 only */
1256+ struct sockaddr_in6 serv_addr6 ;
1257+ struct sockaddr_in serv_addr4 ;
12411258 int option_idx ;
12421259 int c ;
12431260 int foreground = 0 ;
@@ -1253,10 +1270,12 @@ int main(int argc, char *argv[])
12531270 {"user" , 1 , 0 , 'u' },
12541271 {"verbose" , 0 , 0 , 'v' },
12551272 {"do-not-fork" , 0 , 0 , 'F' },
1273+ {"ipv4" , 0 , 0 , '4' },
1274+ {"ipv6" , 0 , 0 , '6' },
12561275 {NULL , 0 , 0 , '\0' }
12571276 };
12581277 while (1 ) {
1259- c = getopt_long (argc , argv , "hvVc :k:p:d:u:F" , long_options , & option_idx );
1278+ c = getopt_long (argc , argv , "46hvVc :k:p:d:u:F" , long_options , & option_idx );
12601279 if (c < 0 )
12611280 break ;
12621281 switch (c ) {
@@ -1324,6 +1343,12 @@ int main(int argc, char *argv[])
13241343 case 'F' :
13251344 foreground = 1 ;
13261345 break ;
1346+ case '4' :
1347+ ip_version = 4 ;
1348+ break ;
1349+ case '6' :
1350+ ip_version = 6 ;
1351+ break ;
13271352 default :
13281353 fprintf (stderr , "Unrecognized option '%c'\n\n\n" ,c );
13291354 usage (argv [0 ]);
@@ -1388,25 +1413,56 @@ int main(int argc, char *argv[])
13881413 }
13891414 dohprint (DOH_NOTICE , "Using %u DNS servers" , n_resolvers );
13901415
1391- /* Create listening socket */
1392- lfd = socket (AF_INET6 , SOCK_STREAM , 0 );
1393- setsockopt (lfd , SOL_SOCKET , SO_REUSEADDR , (char * ) & yes , sizeof (int ));
1394- setsockopt (lfd , SOL_SOCKET , SO_REUSEPORT , (char * ) & yes , sizeof (int ));
1395- if (lfd < 0 ) {
1396- dohprint (DOH_ERR , "ERROR: failed to create DoH socket\n" );
1397- return -1 ;
1398- }
1399- dohprint (DOH_DEBUG , "Main HTTPS socket created." );
1416+ /* Create listening socket based on IP version preference */
1417+ if (ip_version == 4 ) {
1418+ /* IPv4 only */
1419+ lfd = socket (AF_INET , SOCK_STREAM , 0 );
1420+ if (lfd < 0 ) {
1421+ dohprint (DOH_ERR , "ERROR: failed to create IPv4 DoH socket\n" );
1422+ return -1 ;
1423+ }
1424+ setsockopt (lfd , SOL_SOCKET , SO_REUSEADDR , (char * ) & yes , sizeof (int ));
1425+ setsockopt (lfd , SOL_SOCKET , SO_REUSEPORT , (char * ) & yes , sizeof (int ));
14001426
1401- /* Fill in the server address */
1402- memset ( & serv_addr , 0 , sizeof ( serv_addr )) ;
1403- serv_addr . sin6_family = AF_INET6 ; /* using IPv4 */
1404- serv_addr . sin6_port = htons (port ); /* on DEFAULT_PORT */
1427+ memset ( & serv_addr4 , 0 , sizeof ( serv_addr4 ));
1428+ serv_addr4 . sin_family = AF_INET ;
1429+ serv_addr4 . sin_addr . s_addr = INADDR_ANY ;
1430+ serv_addr4 . sin_port = htons (port );
14051431
1406- /* Bind the server socket to our port */
1407- if (bind (lfd , (struct sockaddr * )& serv_addr , sizeof (serv_addr )) == -1 ) {
1408- fprintf (stderr , "ERROR: failed to bind\n" );
1409- return -1 ;
1432+ if (bind (lfd , (struct sockaddr * )& serv_addr4 , sizeof (serv_addr4 )) == -1 ) {
1433+ dohprint (DOH_ERR , "ERROR: failed to bind IPv4 socket\n" );
1434+ return -1 ;
1435+ }
1436+ dohprint (DOH_NOTICE , "Listening on IPv4 only, port %u" , port );
1437+ } else {
1438+ /* IPv6 (with or without dual-stack) */
1439+ lfd = socket (AF_INET6 , SOCK_STREAM , 0 );
1440+ if (lfd < 0 ) {
1441+ dohprint (DOH_ERR , "ERROR: failed to create IPv6 DoH socket\n" );
1442+ return -1 ;
1443+ }
1444+ setsockopt (lfd , SOL_SOCKET , SO_REUSEADDR , (char * ) & yes , sizeof (int ));
1445+ setsockopt (lfd , SOL_SOCKET , SO_REUSEPORT , (char * ) & yes , sizeof (int ));
1446+
1447+ if (ip_version == 6 ) {
1448+ /* IPv6 only - disable dual-stack */
1449+ int ipv6only = 1 ;
1450+ setsockopt (lfd , IPPROTO_IPV6 , IPV6_V6ONLY , & ipv6only , sizeof (ipv6only ));
1451+ dohprint (DOH_NOTICE , "Listening on IPv6 only, port %u" , port );
1452+ } else {
1453+ /* Dual-stack (default) */
1454+ dohprint (DOH_NOTICE , "Listening on IPv4+IPv6 (dual-stack), port %u" , port );
1455+ }
1456+
1457+ memset (& serv_addr6 , 0 , sizeof (serv_addr6 ));
1458+ serv_addr6 .sin6_family = AF_INET6 ;
1459+ serv_addr6 .sin6_addr = in6addr_any ;
1460+ serv_addr6 .sin6_port = htons (port );
1461+
1462+ if (bind (lfd , (struct sockaddr * )& serv_addr6 , sizeof (serv_addr6 )) == -1 ) {
1463+ dohprint (DOH_ERR , "ERROR: failed to bind IPv6 socket\n" );
1464+ return -1 ;
1465+ }
14101466 }
14111467 dohprint (DOH_DEBUG , "Main HTTPS socket is bound." );
14121468
0 commit comments