LCOV - code coverage report
Current view: top level - waltz/resolv - fd_adns.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 195 234 83.3 %
Date: 2026-08-16 04:31:21 Functions: 10 10 100.0 %

          Line data    Source code
       1             : #include "fd_adns.h"
       2             : #include "fd_netdb.h"
       3             : #include "fd_lookup.h"
       4             : #include "../../util/log/fd_log.h"
       5             : #include "../../util/io/fd_io.h"
       6             : 
       7             : #include <errno.h>
       8             : #include <unistd.h>
       9             : #include <sys/socket.h>
      10             : #include <netinet/in.h>
      11             : 
      12             : /* Queries advertise EDNS0 with this UDP payload capacity (the DNS
      13             :    flag day 2020 recommendation: large enough that an A answer never
      14             :    truncates, small enough to avoid IP fragmentation). */
      15             : #define ANSWER_MTU (1232UL)
      16             : 
      17             : /* EDNS0 OPT pseudo-RR appended to each query: root name, TYPE=OPT,
      18             :    CLASS=payload capacity, TTL=0 (no extended flags), RDLEN=0. */
      19          48 : #define OPT_RR_SZ (11UL)
      20             : static uchar const opt_rr[ OPT_RR_SZ ] = { 0, 0, 41, (uchar)(ANSWER_MTU>>8), (uchar)(ANSWER_MTU&0xFF), 0, 0, 0, 0, 0, 0 };
      21             : 
      22             : /* Bound receive work per advance so a UDP flood at our ephemeral
      23             :    port cannot starve the caller's run loop. */
      24          51 : #define RECV_BURST_MAX (64UL)
      25             : 
      26             : FD_TL ushort fd_adns_ns_port = 53;
      27             : 
      28          12 : #define NS_MAX (3UL) /* MAXNS */
      29             : 
      30          75 : #define REQ_STATE_IDLE    (0)
      31          18 : #define REQ_STATE_PENDING (1)
      32          21 : #define REQ_STATE_DONE    (2)
      33             : 
      34             : struct fd_adns_req {
      35             :   int              state;
      36             :   uchar            query[ FD_DNS_QUERY_MTU ];
      37             :   int              qlen;
      38             :   int              no_edns;         /* server answered FORMERR/NOTIMP to EDNS0 */
      39             :   long             deadline_nanos;  /* next (re)send */
      40             :   uint             sends_left;
      41             :   fd_adns_result_t result;
      42             : };
      43             : 
      44             : typedef struct fd_adns_req fd_adns_req_t;
      45             : 
      46             : struct fd_adns_private {
      47             :   int   fd;
      48             : 
      49             :   uint  ns[ NS_MAX ];
      50             :   ulong ns_cnt;
      51             :   long  retry_nanos;
      52             :   uint  attempts;
      53             : 
      54             :   ulong           max;
      55             :   ulong           active_cnt;  /* PENDING+DONE */
      56             :   ulong           pending_cnt; /* PENDING only */
      57             :   fd_adns_req_t * reqs;
      58             : 
      59             :   ulong magic;
      60             : };
      61             : 
      62             : FD_FN_CONST ulong
      63         258 : fd_adns_align( void ) {
      64         258 :   return fd_ulong_max( alignof(fd_adns_t), alignof(fd_adns_req_t) );
      65         258 : }
      66             : 
      67             : FD_FN_CONST ulong
      68          42 : fd_adns_footprint( ulong max_reqs ) {
      69          42 :   ulong l = FD_LAYOUT_INIT;
      70          42 :   l = FD_LAYOUT_APPEND( l, alignof(fd_adns_t),     sizeof(fd_adns_t) );
      71          42 :   l = FD_LAYOUT_APPEND( l, alignof(fd_adns_req_t), max_reqs*sizeof(fd_adns_req_t) );
      72          42 :   return FD_LAYOUT_FINI( l, fd_adns_align() );
      73          42 : }
      74             : 
      75             : void *
      76             : fd_adns_new( void * shmem,
      77          12 :              ulong  max_reqs ) {
      78          12 :   if( FD_UNLIKELY( !shmem ) ) {
      79           0 :     FD_LOG_WARNING(( "NULL shmem" ));
      80           0 :     return NULL;
      81           0 :   }
      82             : 
      83          12 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_adns_align() ) ) ) {
      84           0 :     FD_LOG_WARNING(( "unaligned shmem" ));
      85           0 :     return NULL;
      86           0 :   }
      87             : 
      88          12 :   if( FD_UNLIKELY( !max_reqs ) ) {
      89           0 :     FD_LOG_WARNING(( "max_reqs must be at least 1" ));
      90           0 :     return NULL;
      91           0 :   }
      92             : 
      93          12 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
      94          12 :   fd_adns_t * adns = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_adns_t),     sizeof(fd_adns_t) );
      95          12 :   adns->reqs       = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_adns_req_t), max_reqs*sizeof(fd_adns_req_t) );
      96             : 
      97          12 :   adns->max         = max_reqs;
      98          12 :   adns->active_cnt  = 0UL;
      99          12 :   adns->pending_cnt = 0UL;
     100          54 :   for( ulong i=0UL; i<max_reqs; i++ ) adns->reqs[ i ].state = REQ_STATE_IDLE;
     101             : 
     102             :   /* resolv.conf: nameservers plus retry cadence (timeout is the total
     103             :      budget spread over attempts, like the blocking resolver). */
     104          12 :   uint ns[ NS_MAX ];
     105          12 :   uint timeout  = 5U;
     106          12 :   uint attempts = 2U;
     107          12 :   int  ns_cnt = fd_dns_nameservers( ns, NS_MAX, &timeout, &attempts );
     108          12 :   if( FD_UNLIKELY( ns_cnt<=0 ) ) {
     109           0 :     FD_LOG_WARNING(( "no usable IPv4 nameservers in /etc/resolv.conf" ));
     110           0 :     ns_cnt = 0;
     111           0 :   }
     112          12 :   adns->ns_cnt = (ulong)ns_cnt;
     113          24 :   for( ulong i=0UL; i<adns->ns_cnt; i++ ) adns->ns[ i ] = ns[ i ];
     114          12 :   adns->attempts    = fd_uint_max( attempts, 1U );
     115          12 :   adns->retry_nanos = ((long)timeout*1000L*1000L*1000L)/(long)adns->attempts;
     116             : 
     117          12 :   adns->fd = socket( AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0 );
     118          12 :   if( FD_UNLIKELY( -1==adns->fd ) ) FD_LOG_ERR(( "socket() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     119             : 
     120          12 :   FD_COMPILER_MFENCE();
     121          12 :   FD_VOLATILE( adns->magic ) = FD_ADNS_MAGIC;
     122          12 :   FD_COMPILER_MFENCE();
     123             : 
     124          12 :   return (void *)adns;
     125          12 : }
     126             : 
     127             : fd_adns_t *
     128          12 : fd_adns_join( void * shadns ) {
     129          12 :   if( FD_UNLIKELY( !shadns ) ) {
     130           0 :     FD_LOG_WARNING(( "NULL shadns" ));
     131           0 :     return NULL;
     132           0 :   }
     133             : 
     134          12 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shadns, fd_adns_align() ) ) ) {
     135           0 :     FD_LOG_WARNING(( "unaligned shadns" ));
     136           0 :     return NULL;
     137           0 :   }
     138             : 
     139          12 :   fd_adns_t * adns = (fd_adns_t *)shadns;
     140          12 :   if( FD_UNLIKELY( adns->magic!=FD_ADNS_MAGIC ) ) {
     141           0 :     FD_LOG_WARNING(( "bad magic" ));
     142           0 :     return NULL;
     143           0 :   }
     144             : 
     145          12 :   return adns;
     146          12 : }
     147             : 
     148             : void *
     149          12 : fd_adns_leave( fd_adns_t * adns ) {
     150          12 :   return (void *)adns;
     151          12 : }
     152             : 
     153             : void *
     154          12 : fd_adns_delete( void * shadns ) {
     155          12 :   fd_adns_t * adns = (fd_adns_t *)shadns;
     156          12 :   if( FD_UNLIKELY( !adns ) ) return NULL;
     157          12 :   if( FD_UNLIKELY( adns->magic!=FD_ADNS_MAGIC ) ) {
     158           0 :     FD_LOG_WARNING(( "bad magic" ));
     159           0 :     return NULL;
     160           0 :   }
     161             : 
     162          12 :   if( FD_UNLIKELY( -1==close( adns->fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     163             : 
     164          12 :   FD_COMPILER_MFENCE();
     165          12 :   FD_VOLATILE( adns->magic ) = 0UL;
     166          12 :   FD_COMPILER_MFENCE();
     167             : 
     168          12 :   return shadns;
     169          12 : }
     170             : 
     171             : int
     172             : fd_adns_resolve( fd_adns_t *  adns,
     173             :                  char const * name,
     174          30 :                  ulong        req_id ) {
     175          30 :   if( FD_UNLIKELY( adns->active_cnt>=adns->max ) ) return -1;
     176             : 
     177          27 :   fd_adns_req_t * req = NULL;
     178          30 :   for( ulong i=0UL; i<adns->max; i++ ) {
     179          30 :     if( FD_LIKELY( adns->reqs[ i ].state==REQ_STATE_IDLE ) ) { req = &adns->reqs[ i ]; break; }
     180          30 :   }
     181          27 :   FD_TEST( req );
     182             : 
     183          27 :   req->result.req_id   = req_id;
     184          27 :   req->result.err      = 0;
     185          27 :   req->result.addr_cnt = 0UL;
     186          27 :   req->no_edns         = 0;
     187             : 
     188             :   /* Local names (literals, /etc/hosts) complete without network I/O */
     189          27 :   int cnt = fd_dns_ip4_local( name, req->result.addrs, FD_ADNS_ADDR_MAX );
     190          27 :   if( FD_UNLIKELY( cnt ) ) {
     191           9 :     if( FD_LIKELY( cnt>0 ) ) req->result.addr_cnt = (ulong)cnt;
     192           3 :     else                     req->result.err      = cnt;
     193           9 :     req->state = REQ_STATE_DONE;
     194           9 :     adns->active_cnt++;
     195           9 :     return 0;
     196           9 :   }
     197             : 
     198          18 :   req->qlen = fd_dns_ip4_query( name, req->query );
     199          18 :   if( FD_UNLIKELY( req->qlen<0 ) ) {
     200           0 :     req->result.err = FD_EAI_NONAME;
     201           0 :     req->state      = REQ_STATE_DONE;
     202           0 :     adns->active_cnt++;
     203           0 :     return 0;
     204           0 :   }
     205             : 
     206             :   /* Answers are matched by query id; make ids distinct. */
     207          72 :   for( ulong i=0UL; i<adns->max; i++ ) {
     208          54 :     if( FD_UNLIKELY( adns->reqs[ i ].state!=REQ_STATE_PENDING ) ) continue;
     209           3 :     if( FD_UNLIKELY( adns->reqs[ i ].query[ 0 ]==req->query[ 0 ] && adns->reqs[ i ].query[ 1 ]==req->query[ 1 ] ) ) {
     210           0 :       req->query[ 1 ]++;
     211           0 :       i = (ulong)-1L; /* restart scan */
     212           0 :     }
     213           3 :   }
     214             : 
     215          18 :   req->deadline_nanos = 0L; /* due immediately */
     216          18 :   req->sends_left     = adns->attempts;
     217          18 :   req->state          = REQ_STATE_PENDING;
     218          18 :   adns->active_cnt++;
     219          18 :   adns->pending_cnt++;
     220          18 :   return 0;
     221          18 : }
     222             : 
     223             : static void
     224             : drain_answers( fd_adns_t * adns );
     225             : 
     226             : static void
     227             : pending_io( fd_adns_t * adns,
     228          51 :             long        now ) {
     229          51 :   if( FD_LIKELY( !adns->pending_cnt ) ) return;
     230             : 
     231          39 :   drain_answers( adns );
     232             : 
     233             :   /* Send due (re)tries to all nameservers in parallel */
     234         177 :   for( ulong i=0UL; i<adns->max; i++ ) {
     235         138 :     fd_adns_req_t * req = &adns->reqs[ i ];
     236         138 :     if( FD_LIKELY( req->state!=REQ_STATE_PENDING ) ) continue;
     237          45 :     if( FD_LIKELY( req->deadline_nanos>now ) )       continue;
     238             : 
     239          36 :     if( FD_UNLIKELY( !req->sends_left || !adns->ns_cnt ) ) {
     240           9 :       req->result.err = FD_EAI_AGAIN;
     241           9 :       req->state      = REQ_STATE_DONE;
     242           9 :       adns->pending_cnt--;
     243           9 :       continue;
     244           9 :     }
     245             : 
     246             :     /* req->query holds the bare question packet (the part answers
     247             :        echo); the wire packet adds the EDNS0 OPT RR unless the server
     248             :        rejected it */
     249          27 :     uchar pkt[ FD_DNS_QUERY_MTU+OPT_RR_SZ ];
     250          27 :     fd_memcpy( pkt, req->query, (ulong)req->qlen );
     251          27 :     ulong pkt_sz = (ulong)req->qlen;
     252          27 :     if( FD_LIKELY( !req->no_edns ) ) {
     253          24 :       fd_memcpy( pkt+pkt_sz, opt_rr, OPT_RR_SZ );
     254          24 :       pkt[ 11 ] = 1; /* arcount */
     255          24 :       pkt_sz += OPT_RR_SZ;
     256          24 :     }
     257             : 
     258          27 :     int sent_any = 0, all_eagain = 1;
     259          54 :     for( ulong j=0UL; j<adns->ns_cnt; j++ ) {
     260          27 :       struct sockaddr_in ns_addr = {
     261          27 :         .sin_family = AF_INET,
     262          27 :         .sin_port   = fd_ushort_bswap( fd_adns_ns_port ),
     263          27 :         .sin_addr   = { .s_addr = adns->ns[ j ] }
     264          27 :       };
     265          27 :       long sent;
     266          27 :       do sent = sendto( adns->fd, pkt, pkt_sz, MSG_NOSIGNAL, fd_type_pun( &ns_addr ), sizeof(ns_addr) );
     267          27 :       while( FD_UNLIKELY( -1L==sent && errno==EINTR ) ); /* retry before charging the attempt */
     268          27 :       if( FD_UNLIKELY( -1L==sent ) ) {
     269           0 :         if( FD_UNLIKELY( errno!=EAGAIN && errno!=ECONNREFUSED && errno!=ENETUNREACH && errno!=EHOSTUNREACH ) )
     270           0 :           FD_LOG_ERR(( "sendto() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     271           0 :         if( FD_LIKELY( errno!=EAGAIN ) ) all_eagain = 0;
     272          27 :       } else {
     273          27 :         sent_any = 1;
     274          27 :       }
     275          27 :     }
     276             :     /* Local socket backpressure put nothing on the wire: stay due and
     277             :        retry next advance without consuming the attempt */
     278          27 :     if( FD_UNLIKELY( !sent_any && all_eagain && adns->ns_cnt ) ) { req->deadline_nanos = now; continue; }
     279          27 :     req->sends_left--;
     280          27 :     req->deadline_nanos = now+adns->retry_nanos;
     281          27 :   }
     282          39 : }
     283             : 
     284             : static void
     285          39 : drain_answers( fd_adns_t * adns ) {
     286          51 :   for( ulong burst=0UL; burst<RECV_BURST_MAX; burst++ ) {
     287          51 :     uchar answer[ ANSWER_MTU ];
     288          51 :     struct sockaddr_in sa;
     289          51 :     struct iovec iov = { .iov_base = answer, .iov_len = sizeof(answer) };
     290          51 :     struct msghdr mh = {
     291          51 :       .msg_name    = &sa,
     292          51 :       .msg_namelen = sizeof(sa),
     293          51 :       .msg_iov     = &iov,
     294          51 :       .msg_iovlen  = 1
     295          51 :     };
     296          51 :     long rlen = recvmsg( adns->fd, &mh, 0 );
     297          51 :     if( FD_LIKELY( rlen<0L ) ) {
     298          39 :       if( FD_LIKELY( errno==EAGAIN || errno==EWOULDBLOCK ) ) break;
     299           0 :       if( FD_LIKELY( errno==ECONNREFUSED || errno==EINTR ) ) continue;
     300           0 :       FD_LOG_ERR(( "recvmsg() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     301           0 :     }
     302          12 :     if( FD_UNLIKELY( rlen<12L ) ) continue;
     303             : 
     304             :     /* Ignore replies from addresses we didn't send to */
     305          12 :     ulong j;
     306          12 :     for( j=0UL; j<adns->ns_cnt; j++ ) {
     307          12 :       if( FD_LIKELY( sa.sin_family==AF_INET && sa.sin_addr.s_addr==adns->ns[ j ] && sa.sin_port==fd_ushort_bswap( fd_adns_ns_port ) ) ) break;
     308          12 :     }
     309          12 :     if( FD_UNLIKELY( j==adns->ns_cnt ) ) continue;
     310             : 
     311             :     /* Must be a standard-query response */
     312          12 :     if( FD_UNLIKELY( (answer[ 2 ]&0x80)!=0x80 || (answer[ 2 ]&0x78)!=0 ) ) continue;
     313             : 
     314          12 :     fd_adns_req_t * req = NULL;
     315          24 :     for( ulong i=0UL; i<adns->max; i++ ) {
     316          21 :       if( FD_LIKELY( adns->reqs[ i ].state==REQ_STATE_PENDING && adns->reqs[ i ].query[ 0 ]==answer[ 0 ] && adns->reqs[ i ].query[ 1 ]==answer[ 1 ] ) ) {
     317           9 :         req = &adns->reqs[ i ];
     318           9 :         break;
     319           9 :       }
     320          21 :     }
     321          12 :     if( FD_UNLIKELY( !req ) ) continue;
     322           9 :     if( FD_UNLIKELY( rlen<req->qlen ) ) continue;
     323           9 :     if( FD_UNLIKELY( answer[ 4 ]!=req->query[ 4 ] || answer[ 5 ]!=req->query[ 5 ] ) ) continue;
     324           9 :     if( FD_UNLIKELY( memcmp( answer+12, req->query+12, (ulong)req->qlen-12UL ) ) ) continue;
     325             : 
     326             :     /* FORMERR/NOTIMP from an EDNS0-unaware server: resend plain.
     327             :        Guarantee a send remains, else attempts:1 expires the request
     328             :        at the sends_left check instead of retrying. */
     329           6 :     if( FD_UNLIKELY( ((answer[ 3 ]&15)==1 || (answer[ 3 ]&15)==4) && !req->no_edns ) ) {
     330           3 :       req->no_edns        = 1;
     331           3 :       req->sends_left     = fd_uint_max( req->sends_left, 1U );
     332           3 :       req->deadline_nanos = 0L; /* due immediately */
     333           3 :       continue;
     334           3 :     }
     335             : 
     336           3 :     int truncated = !!(answer[ 2 ]&2) | !!(mh.msg_flags&MSG_TRUNC);
     337             : 
     338           3 :     int cnt = fd_dns_ip4_answer( answer, (ulong)rlen, req->result.addrs, FD_ADNS_ADDR_MAX );
     339           3 :     if( FD_UNLIKELY( cnt==FD_EAI_AGAIN && !truncated ) ) continue; /* SERVFAIL: leave pending for retry */
     340             :     /* Truncated and nothing parseable: with EDNS0 advertising 1232B
     341             :        this means a pathological answer; the leading records we need
     342             :        (up to FD_ADNS_ADDR_MAX) essentially always fit, so this is a
     343             :        terminal failure rather than a TCP fallback. */
     344           3 :     if( FD_UNLIKELY( cnt<=0 && truncated ) ) cnt = FD_EAI_FAIL;
     345             : 
     346           3 :     if( FD_LIKELY( cnt>0 ) ) req->result.addr_cnt = (ulong)cnt;
     347           0 :     else                     req->result.err      = cnt;
     348           3 :     req->state = REQ_STATE_DONE;
     349           3 :     adns->pending_cnt--;
     350           3 :   }
     351          39 : }
     352             : 
     353             : int
     354             : fd_adns_advance( fd_adns_t *        adns,
     355             :                  long               now,
     356          54 :                  fd_adns_result_t * result ) {
     357          54 :   if( FD_LIKELY( !adns->active_cnt ) ) return 0;
     358             : 
     359          51 :   pending_io( adns, now );
     360             : 
     361         162 :   for( ulong i=0UL; i<adns->max; i++ ) {
     362         132 :     fd_adns_req_t * req = &adns->reqs[ i ];
     363         132 :     if( FD_LIKELY( req->state!=REQ_STATE_DONE ) ) continue;
     364          21 :     *result    = req->result;
     365          21 :     req->state = REQ_STATE_IDLE;
     366          21 :     adns->active_cnt--;
     367          21 :     return 1;
     368         132 :   }
     369          30 :   return 0;
     370          51 : }

Generated by: LCOV version 1.14