3333#include "regex_r.h"
3434// statis_assert()
3535#include <assert.h>
36+ #include <poll.h>
37+ #include <time.h>
3638// TCP_MAX_QUERIES
3739#include "dnsmasq/config.h"
3840
@@ -285,13 +287,101 @@ int create_socket(bool tcp, struct sockaddr_in *dest)
285287// Helper macro to reduce code duplication
286288#define log_resolve_info (host , port , tcp ) { log_info("Tried to resolve PTR \"%s\" on 127.0.0.1#%u (%s)", host, port, tcp ? "TCP" : "UDP"); }
287289
290+ // Return CLOCK_MONOTONIC in milliseconds for resolver receive deadlines
291+ static int64_t resolver_monotonic_msec (void )
292+ {
293+ struct timespec now = { 0 };
294+ if (clock_gettime (CLOCK_MONOTONIC , & now ) != 0 )
295+ return -1 ;
296+
297+ return (int64_t )now .tv_sec * 1000LL + now .tv_nsec / 1000000LL ;
298+ }
299+
300+ // Validate that a UDP DNS response belongs to the active PTR request and
301+ // return a pointer to the first answer record in the received packet.
302+ static bool validate_udp_ptr_response (uint8_t * buf , const size_t response_len ,
303+ const uint16_t request_id , const char * host ,
304+ struct DNS_HEADER * dns , uint8_t * * answer )
305+ {
306+ if (response_len < sizeof (struct DNS_HEADER ))
307+ {
308+ log_debug (DEBUG_RESOLVER ,
309+ "Discarding short UDP DNS reply while resolving PTR \"%s\" (%zu bytes)" ,
310+ host , response_len );
311+ return false;
312+ }
313+
314+ memcpy (dns , buf , sizeof (struct DNS_HEADER ));
315+
316+ if (dns -> id != request_id || dns -> qr != 1 || dns -> opcode != 0 ||
317+ ntohs (dns -> q_count ) != 1 )
318+ {
319+ log_debug (DEBUG_RESOLVER ,
320+ "Discarding unrelated UDP DNS reply while resolving PTR \"%s\" "
321+ "(id %u, expected %u, qr %u, opcode %u, questions %u)" ,
322+ host , (unsigned int )ntohs (dns -> id ),
323+ (unsigned int )ntohs (request_id ), (unsigned int )dns -> qr ,
324+ (unsigned int )dns -> opcode ,
325+ (unsigned int )ntohs (dns -> q_count ));
326+ return false;
327+ }
328+
329+ const unsigned char * bufend = buf + response_len ;
330+ uint8_t * reader = buf + sizeof (struct DNS_HEADER );
331+ uint16_t consumed = 0 ;
332+ unsigned char * question_name = nameFromDNS (reader , buf , bufend , & consumed );
333+ if (question_name == NULL )
334+ {
335+ log_debug (DEBUG_RESOLVER ,
336+ "Discarding malformed UDP DNS question while resolving PTR \"%s\"" ,
337+ host );
338+ return false;
339+ }
340+
341+ if (consumed > (size_t )(bufend - reader ) ||
342+ sizeof (struct QUESTION ) > (size_t )(bufend - reader - consumed ))
343+ {
344+ free (question_name );
345+ log_debug (DEBUG_RESOLVER ,
346+ "Discarding truncated UDP DNS question while resolving PTR \"%s\"" ,
347+ host );
348+ return false;
349+ }
350+
351+ reader += consumed ;
352+ struct QUESTION question = { 0 };
353+ memcpy (& question , reader , sizeof (question ));
354+
355+ const bool matches =
356+ strcasecmp ((const char * )question_name , host ) == 0 &&
357+ ntohs (question .qtype ) == T_PTR &&
358+ ntohs (question .qclass ) == 1 ;
359+
360+ if (!matches )
361+ {
362+ log_debug (DEBUG_RESOLVER ,
363+ "Discarding UDP DNS reply with mismatched question while resolving "
364+ "PTR \"%s\" (received \"%s\", type %u, class %u)" ,
365+ host , (const char * )question_name ,
366+ (unsigned int )ntohs (question .qtype ),
367+ (unsigned int )ntohs (question .qclass ));
368+ free (question_name );
369+ return false;
370+ }
371+
372+ free (question_name );
373+ * answer = reader + sizeof (struct QUESTION );
374+ return true;
375+ }
376+
288377// Perform a name lookup by sending a packet to ourselves
289378static bool ngethostbyname (const int sock , const bool tcp , struct sockaddr_in * dest ,
290379 char hostn [MAXDOMAINLEN ], const char * host , const char * ipaddr , bool * truncated )
291380{
292381 // Initialize request DNS header
293382 struct DNS_HEADER dns = { 0 };
294383 dns .id = (unsigned short ) htons (random ()); // random query ID
384+ const uint16_t request_id = dns .id ;
295385 dns .qr = 0 ; // This is a query
296386 dns .opcode = 0 ; // This is a standard query
297387 dns .aa = 0 ; // Not Authoritative
@@ -314,7 +404,7 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
314404 if (hname == NULL )
315405 {
316406 log_err ("Unable to allocate memory for hname" );
317- return NULL ;
407+ return false ;
318408 }
319409 strncpy (hname , host , hnamelen );
320410 strncat (hname , "." , hnamelen - strlen (hname ));
@@ -342,6 +432,9 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
342432 log_debug (DEBUG_RESOLVER , "Resolving PTR \"%s\" on 127.0.0.1#%u (%s)" ,
343433 host , config .dns .port .v .u16 , tcp ? "TCP" : "UDP" );
344434
435+ ssize_t response_len = sizeof (buf );
436+ uint8_t * reader = NULL ;
437+
345438 // Send the query and receive the answer
346439 if (!tcp )
347440 {
@@ -352,15 +445,92 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
352445 {
353446 log_err ("Cannot send UDP DNS query: %s" , strsockerr (errno ));
354447 log_resolve_info (host , config .dns .port .v .u16 , tcp );
355- return NULL ;
448+ return false ;
356449 }
357450
358- // Receive the answer
359- if (recvfrom (sock , buf , sizeof (buf ), 0 , (struct sockaddr * )dest , & addrlen ) < 0 )
451+ // Use one absolute deadline for this request. Mismatched packets are
452+ // discarded without extending the original two-second timeout.
453+ const int64_t start = resolver_monotonic_msec ();
454+ if (start < 0 )
360455 {
361- log_err ("Cannot receive UDP DNS reply : %s" , strsockerr (errno ));
456+ log_err ("Cannot read resolver monotonic clock : %s" , strerror (errno ));
362457 log_resolve_info (host , config .dns .port .v .u16 , tcp );
363- return NULL ;
458+ return false;
459+ }
460+ const int64_t deadline = start + 2000 ;
461+
462+ while (true)
463+ {
464+ const int64_t now = resolver_monotonic_msec ();
465+ if (now < 0 )
466+ {
467+ log_err ("Cannot read resolver monotonic clock: %s" , strerror (errno ));
468+ log_resolve_info (host , config .dns .port .v .u16 , tcp );
469+ return false;
470+ }
471+
472+ const int64_t remaining = deadline - now ;
473+ if (remaining <= 0 )
474+ {
475+ log_err ("Cannot receive UDP DNS reply: %s" , strsockerr (EAGAIN ));
476+ log_resolve_info (host , config .dns .port .v .u16 , tcp );
477+ return false;
478+ }
479+
480+ struct pollfd pfd = {
481+ .fd = sock ,
482+ .events = POLLIN ,
483+ .revents = 0 ,
484+ };
485+
486+ const int ready = poll (& pfd , 1 , (int )remaining );
487+ if (ready < 0 )
488+ {
489+ if (errno == EINTR )
490+ continue ;
491+
492+ log_err ("Cannot wait for UDP DNS reply: %s" , strsockerr (errno ));
493+ log_resolve_info (host , config .dns .port .v .u16 , tcp );
494+ return false;
495+ }
496+ if (ready == 0 )
497+ {
498+ log_err ("Cannot receive UDP DNS reply: %s" , strsockerr (EAGAIN ));
499+ log_resolve_info (host , config .dns .port .v .u16 , tcp );
500+ return false;
501+ }
502+
503+ struct sockaddr_in source = { 0 };
504+ socklen_t source_len = sizeof (source );
505+ response_len = recvfrom (sock , buf , sizeof (buf ), MSG_DONTWAIT ,
506+ (struct sockaddr * )& source , & source_len );
507+ if (response_len < 0 )
508+ {
509+ if (errno == EAGAIN || errno == EINTR )
510+ continue ;
511+
512+ log_err ("Cannot receive UDP DNS reply: %s" , strsockerr (errno ));
513+ log_resolve_info (host , config .dns .port .v .u16 , tcp );
514+ return false;
515+ }
516+
517+ if (source_len < sizeof (source ) ||
518+ source .sin_family != dest -> sin_family ||
519+ source .sin_addr .s_addr != dest -> sin_addr .s_addr ||
520+ source .sin_port != dest -> sin_port )
521+ {
522+ log_debug (DEBUG_RESOLVER ,
523+ "Discarding UDP DNS reply from unexpected source while "
524+ "resolving PTR \"%s\"" ,
525+ host );
526+ continue ;
527+ }
528+
529+ if (!validate_udp_ptr_response (buf , (size_t )response_len ,
530+ request_id , host , & dns , & reader ))
531+ continue ;
532+
533+ break ;
364534 }
365535 }
366536 else
@@ -378,7 +548,7 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
378548 {
379549 log_err ("Cannot send TCP DNS query: %s" , strsockerr (errno ));
380550 log_resolve_info (host , config .dns .port .v .u16 , tcp );
381- return NULL ;
551+ return false ;
382552 }
383553
384554 // Receive the answer, first the length of the message ...
@@ -387,7 +557,7 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
387557 {
388558 log_err ("Cannot receive TCP DNS reply (1): %s" , strsockerr (errno ));
389559 log_resolve_info (host , config .dns .port .v .u16 , tcp );
390- return NULL ;
560+ return false ;
391561 }
392562 prefix = ntohs (prefix );
393563
@@ -398,22 +568,22 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
398568 {
399569 log_err ("Received TCP DNS reply is too long (%u bytes)" , prefix );
400570 log_resolve_info (host , config .dns .port .v .u16 , tcp );
401- return NULL ;
571+ return false ;
402572 }
403573 bzero (buf , prefix + 1 );
404574 // ... then the message itself
405575 if (recv (sock , buf , sizeof (buf ), 0 ) < 0 )
406576 {
407577 log_err ("Cannot receive TCP DNS reply (2): %s" , strsockerr (errno ));
408578 log_resolve_info (host , config .dns .port .v .u16 , tcp );
409- return NULL ;
579+ return false ;
410580 }
411- }
412581
413- // Parse the reply
414- memcpy (& dns , buf , sizeof (struct DNS_HEADER ));
415- // Move ahead of the dns header and the query field
416- uint8_t * reader = & buf [len ];
582+ // Preserve the existing TCP parser behavior. The PoC hardens the UDP
583+ // path where sequential lookups share one datagram socket.
584+ memcpy (& dns , buf , sizeof (struct DNS_HEADER ));
585+ reader = & buf [len ];
586+ }
417587
418588 // Log the status of the query
419589 log_debug (DEBUG_RESOLVER , "DNS query for PTR \"%s\" returned status %s (%i)" ,
@@ -425,14 +595,14 @@ static bool ngethostbyname(const int sock, const bool tcp, struct sockaddr_in *d
425595 log_debug (DEBUG_RESOLVER , " --> DNS response truncated" );
426596 if (truncated != NULL )
427597 * truncated = true;
428- return NULL ;
598+ return false ;
429599 }
430600
431601 // Start reading answers
432602 uint16_t stop = 0 ;
433603 bool have_name = false;
434604 struct RES_RECORD answers [20 ] = { 0 };
435- const unsigned char * bufend = buf + sizeof (buf );
605+ const unsigned char * bufend = tcp ? buf + sizeof (buf ) : buf + response_len ;
436606 for (uint16_t i = 0 ; i < min (ntohs (dns .ans_count ), ArraySize (answers )); i ++ )
437607 {
438608 // Ensure the read pointer still points within the receive
0 commit comments