LCOV - code coverage report
Current view: top level - discof/restore/utils - fd_http_resolver.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 397 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 15 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "fd_http_resolver.h"
       3             : #include "fd_ssresolve.h"
       4             : 
       5             : #include "../../../util/log/fd_log.h"
       6             : #include "../../../util/fd_util.h"
       7             : 
       8             : #include <poll.h>
       9             : #include <errno.h>
      10             : #include <unistd.h>
      11             : #include <string.h>
      12             : #include <sys/epoll.h>
      13             : #include <sys/socket.h>
      14             : #include <sys/random.h>
      15             : #include <netinet/in.h>
      16             : #include <netinet/tcp.h>
      17             : 
      18             : #include "../../../waltz/tls/fd_tls.h"
      19             : #include "../../../ballet/ed25519/fd_x25519.h"
      20             : 
      21           0 : #define PEER_STATE_UNRESOLVED (0)
      22           0 : #define PEER_STATE_REFRESHING (1)
      23           0 : #define PEER_STATE_VALID      (2)
      24           0 : #define PEER_STATE_INVALID    (3)
      25             : 
      26           0 : #define PEER_DEADLINE_NANOS_VALID   (5L*1000L*1000L*1000L) /* 5 seconds */
      27           0 : #define PEER_DEADLINE_NANOS_RESOLVE (2L*1000L*1000L*1000L) /* 2 seconds */
      28           0 : #define PEER_DEADLINE_NANOS_INVALID (5L*1000L*1000L*1000L) /* 5 seconds */
      29             : 
      30             : /* FIXME: The fds/fds_len/idx logic is fragile, replace with something
      31             :    that duplicates less state / etc. */
      32             : 
      33             : struct fd_ssresolve_peer {
      34             :   fd_sspeer_key_t key;
      35             :   fd_ip4_port_t addr;
      36             :   int           is_https;
      37             :   ulong         full_slot;
      38             :   ulong         incr_slot;
      39             :   ulong         incr_base_slot; /* the full slot that the incremental is built upon */
      40             :   uchar         full_hash[ FD_HASH_FOOTPRINT ];
      41             :   uchar         incr_hash[ FD_HASH_FOOTPRINT ];
      42             : 
      43             :   fd_ssresolve_t * full_ssresolve;
      44             :   fd_ssresolve_t * inc_ssresolve;
      45             : 
      46             :   struct {
      47             :     ulong next;
      48             :   } pool;
      49             : 
      50             :   struct {
      51             :     ulong next;
      52             :     ulong prev;
      53             :   } deadline;
      54             : 
      55             :   struct {
      56             :     ulong idx;
      57             :   } fd;
      58             : 
      59             :   int  state;
      60             :   long deadline_nanos;
      61             : };
      62             : typedef struct fd_ssresolve_peer fd_ssresolve_peer_t;
      63             : 
      64             : #define POOL_NAME  peer_pool
      65           0 : #define POOL_T     fd_ssresolve_peer_t
      66             : #define POOL_IDX_T ulong
      67           0 : #define POOL_NEXT  pool.next
      68             : #include "../../../util/tmpl/fd_pool.c"
      69             : 
      70             : #define DLIST_NAME  deadline_list
      71             : #define DLIST_ELE_T fd_ssresolve_peer_t
      72           0 : #define DLIST_PREV  deadline.prev
      73           0 : #define DLIST_NEXT  deadline.next
      74             : #include "../../../util/tmpl/fd_dlist.c"
      75             : 
      76             : static inline void
      77           0 : clear_peer_snapshot_data( fd_ssresolve_peer_t * peer ) {
      78           0 :   peer->full_slot      = FD_SSPEER_SLOT_UNKNOWN;
      79           0 :   peer->incr_slot      = FD_SSPEER_SLOT_UNKNOWN;
      80           0 :   peer->incr_base_slot = FD_SSPEER_SLOT_UNKNOWN;
      81           0 :   fd_memset( peer->full_hash, 0, FD_HASH_FOOTPRINT );
      82           0 :   fd_memset( peer->incr_hash, 0, FD_HASH_FOOTPRINT );
      83           0 : }
      84             : 
      85             : struct fd_http_resolver_private {
      86             :   fd_ssresolve_peer_t *            pool;
      87             :   deadline_list_t *                unresolved;
      88             :   deadline_list_t *                resolving;
      89             :   deadline_list_t *                valid;
      90             :   deadline_list_t *                invalid;
      91             : 
      92             :   ulong                            fds_len;
      93             :   struct pollfd *                  fds;
      94             :   ulong *                          fds_idx;
      95             :   int                              epoll_fd;
      96             : 
      97             :   int                              incremental_snapshot_fetch;
      98             : 
      99             :   void *                           cb_arg;
     100             :   fd_http_resolver_on_resolve_fn_t on_resolve_cb;
     101             : 
     102             :   fd_tls_t                         tls;
     103             :   fd_chacha_rng_t                  rng[1];
     104             : 
     105             :   fd_x509_ca_store_t               ca_store;
     106             : 
     107             :   ulong                            magic; /* ==FD_HTTP_RESOLVER_MAGIC */
     108             : };
     109             : 
     110             : FD_FN_CONST ulong
     111           0 : fd_http_resolver_align( void ) {
     112           0 :   return fd_ulong_max( alignof(fd_http_resolver_t), fd_ulong_max( peer_pool_align(), fd_ulong_max( deadline_list_align(), fd_ulong_max( alignof(struct pollfd), alignof(ulong) ) ) ) );
     113           0 : }
     114             : 
     115             : FD_FN_CONST ulong
     116           0 : fd_http_resolver_footprint( ulong peers_cnt ) {
     117           0 :   ulong l;
     118           0 :   l = FD_LAYOUT_INIT;
     119           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_http_resolver_t), sizeof(fd_http_resolver_t) );
     120           0 :   l = FD_LAYOUT_APPEND( l, peer_pool_align(),           peer_pool_footprint( peers_cnt ) );
     121           0 :   l = FD_LAYOUT_APPEND( l, deadline_list_align(),       deadline_list_footprint() );
     122           0 :   l = FD_LAYOUT_APPEND( l, deadline_list_align(),       deadline_list_footprint() );
     123           0 :   l = FD_LAYOUT_APPEND( l, deadline_list_align(),       deadline_list_footprint() );
     124           0 :   l = FD_LAYOUT_APPEND( l, deadline_list_align(),       deadline_list_footprint() );
     125           0 :   l = FD_LAYOUT_APPEND( l, alignof(struct pollfd),      2UL*peers_cnt*sizeof(struct pollfd) );
     126           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),              2UL*peers_cnt*sizeof(ulong) );
     127             : 
     128           0 :   for( ulong i=0UL; i<peers_cnt*2UL; i++ ) {
     129           0 :     l = FD_LAYOUT_APPEND( l, fd_ssresolve_align(), fd_ssresolve_footprint() );
     130           0 :   }
     131           0 :   return FD_LAYOUT_FINI( l, fd_http_resolver_align() );
     132           0 : }
     133             : 
     134             : void *
     135             : fd_http_resolver_new( void *                           shmem,
     136             :                       ulong                            peers_cnt,
     137             :                       int                              incremental_snapshot_fetch,
     138             :                       int                              load_ca_store,
     139             :                       fd_http_resolver_on_resolve_fn_t on_resolve_cb,
     140             :                       void *                           cb_arg,
     141           0 :                       int                              epoll_fd ) {
     142           0 :   if( FD_UNLIKELY( !shmem ) ) {
     143           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     144           0 :     return NULL;
     145           0 :   }
     146             : 
     147           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_http_resolver_align() ) ) ) {
     148           0 :     FD_LOG_WARNING(( "unaligned shmem" ));
     149           0 :     return NULL;
     150           0 :   }
     151             : 
     152           0 :   if( FD_UNLIKELY( peers_cnt<1UL ) ) {
     153           0 :     FD_LOG_WARNING(( "max_peers must be at least 1" ));
     154           0 :     return NULL;
     155           0 :   }
     156             : 
     157           0 :   if( FD_UNLIKELY( epoll_fd==-1 ) ) {
     158           0 :     FD_LOG_WARNING(( "invalid epoll_fd" ));
     159           0 :     return NULL;
     160           0 :   }
     161             : 
     162           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     163           0 :   fd_http_resolver_t * resolver = FD_SCRATCH_ALLOC_APPEND( l, fd_http_resolver_align(), sizeof(fd_http_resolver_t) );
     164           0 :   void * _pool        = FD_SCRATCH_ALLOC_APPEND( l, peer_pool_align(), peer_pool_footprint( peers_cnt ) );
     165           0 :   void * _unresolved  = FD_SCRATCH_ALLOC_APPEND( l, deadline_list_align(), deadline_list_footprint() );
     166           0 :   void * _resolving   = FD_SCRATCH_ALLOC_APPEND( l, deadline_list_align(), deadline_list_footprint() );
     167           0 :   void * _invalid     = FD_SCRATCH_ALLOC_APPEND( l, deadline_list_align(), deadline_list_footprint() );
     168           0 :   void * _valid       = FD_SCRATCH_ALLOC_APPEND( l, deadline_list_align(), deadline_list_footprint() );
     169           0 :   struct pollfd * fds = FD_SCRATCH_ALLOC_APPEND( l, alignof(struct pollfd), 2UL*peers_cnt*sizeof(struct pollfd) );
     170           0 :   ulong * fds_idx     = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), 2UL*peers_cnt*sizeof(ulong) );
     171             : 
     172           0 :   resolver->pool       = peer_pool_join( peer_pool_new( _pool, peers_cnt ) );
     173           0 :   resolver->unresolved = deadline_list_join( deadline_list_new( _unresolved ) );
     174           0 :   resolver->resolving  = deadline_list_join( deadline_list_new( _resolving ) );
     175           0 :   resolver->invalid    = deadline_list_join( deadline_list_new( _invalid ) );
     176           0 :   resolver->valid      = deadline_list_join( deadline_list_new( _valid ) );
     177             : 
     178           0 :   resolver->fds_len     = 0UL;
     179           0 :   resolver->fds         = fds;
     180           0 :   resolver->fds_idx     = fds_idx;
     181             : 
     182           0 :   for( ulong i=0UL; i<peer_pool_max( resolver->pool ); i++ ) {
     183           0 :     void * _full_ssresolve = FD_SCRATCH_ALLOC_APPEND( l, fd_ssresolve_align(), fd_ssresolve_footprint() );
     184           0 :     void * _inc_ssresolve  = FD_SCRATCH_ALLOC_APPEND( l, fd_ssresolve_align(), fd_ssresolve_footprint() );
     185           0 :     resolver->pool[ i ].full_ssresolve = fd_ssresolve_join( fd_ssresolve_new( _full_ssresolve ) );
     186           0 :     resolver->pool[ i ].inc_ssresolve  = fd_ssresolve_join( fd_ssresolve_new( _inc_ssresolve ) );
     187           0 :   }
     188             : 
     189           0 :   resolver->incremental_snapshot_fetch = incremental_snapshot_fetch;
     190           0 :   resolver->cb_arg                     = cb_arg;
     191           0 :   resolver->on_resolve_cb              = on_resolve_cb;
     192             : 
     193           0 :   resolver->epoll_fd = epoll_fd;
     194             : 
     195           0 :   {
     196           0 :     fd_tls_t * tls = &resolver->tls;
     197           0 :     fd_memset( tls, 0, sizeof(fd_tls_t) );
     198             : 
     199             :     /* Seed the CSPRNG that fd_tls draws handshake randomness from */
     200             : 
     201           0 :     uchar rng_key[ FD_CHACHA_KEY_SZ ];
     202           0 :     if( FD_UNLIKELY( !fd_rng_secure( rng_key, sizeof(rng_key) ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
     203           0 :     fd_chacha_rng_init( resolver->rng, rng_key, FD_CHACHA_RNG_ALGO_CHACHA8 );
     204           0 :     fd_memzero_explicit( rng_key, sizeof(rng_key) );
     205           0 :     tls->rng = resolver->rng;
     206             : 
     207             :     /* Generate ephemeral X25519 key */
     208             : 
     209           0 :     if( FD_UNLIKELY( !fd_rng_secure( tls->kex_private_key, 32UL ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
     210           0 :     fd_x25519_public( tls->kex_public_key, tls->kex_private_key );
     211             : 
     212           0 :     static uchar const alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '1' };
     213           0 :     fd_memcpy( tls->alpn, alpn, sizeof(alpn) );
     214           0 :     tls->alpn_sz = sizeof(alpn);
     215             : 
     216           0 :     tls->quic = 0;
     217           0 :   }
     218             : 
     219           0 :   resolver->ca_store.cnt = 0UL;
     220           0 :   if( load_ca_store ) {
     221           0 :     if( FD_UNLIKELY( fd_x509_ca_store_load_system( &resolver->ca_store )<0L ) ) {
     222           0 :       FD_LOG_ERR(( "No CA certificate bundle found, cannot verify HTTPS snapshot "
     223           0 :                    "server certificates.  Install the system CA certificates, or "
     224           0 :                    "use http:// [snapshots.sources.servers] entries." ));
     225           0 :     }
     226           0 :   }
     227             : 
     228           0 :   FD_COMPILER_MFENCE();
     229           0 :   FD_VOLATILE( resolver->magic ) = FD_HTTP_RESOLVER_MAGIC;
     230           0 :   FD_COMPILER_MFENCE();
     231             : 
     232           0 :   return (void *)resolver;
     233           0 : }
     234             : 
     235             : fd_http_resolver_t *
     236           0 : fd_http_resolver_join( void * shresolver ) {
     237           0 :   if( FD_UNLIKELY( !shresolver ) ) {
     238           0 :     FD_LOG_WARNING(( "NULL shresolver" ));
     239           0 :     return NULL;
     240           0 :   }
     241             : 
     242           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shresolver, fd_http_resolver_align() ) ) ) {
     243           0 :     FD_LOG_WARNING(( "misaligned shresolver" ));
     244           0 :     return NULL;
     245           0 :   }
     246             : 
     247           0 :   fd_http_resolver_t * resolver = (fd_http_resolver_t *)shresolver;
     248             : 
     249           0 :   if( FD_UNLIKELY( resolver->magic!=FD_HTTP_RESOLVER_MAGIC ) ) {
     250           0 :     FD_LOG_WARNING(( "bad magic" ));
     251           0 :     return NULL;
     252           0 :   }
     253             : 
     254           0 :   return resolver;
     255           0 : }
     256             : 
     257             : int
     258             : fd_http_resolver_add( fd_http_resolver_t *   resolver,
     259             :                       fd_ip4_port_t          addr,
     260             :                       char const *           hostname,
     261             :                       int                    is_https,
     262           0 :                       fd_sspeer_selector_t * selector  ) {
     263           0 :   if( !peer_pool_free( resolver->pool ) ) {
     264           0 :     FD_LOG_WARNING(( "peer pool exhausted" ));
     265           0 :     return -1;
     266           0 :   }
     267           0 :   fd_ssresolve_peer_t * peer = peer_pool_ele_acquire( resolver->pool );
     268           0 :   memset( &peer->key.url, 0, sizeof(peer->key.url) );
     269           0 :   if( FD_LIKELY( hostname ) ) {
     270           0 :     strncpy( peer->key.url.hostname, hostname, sizeof(peer->key.url.hostname) - 1UL );
     271           0 :     peer->key.url.hostname[ sizeof(peer->key.url.hostname) - 1UL ] = '\0';
     272           0 :   } else {
     273           0 :     peer->key.url.hostname[ 0 ] = '\0';
     274           0 :   }
     275           0 :   peer->key.url.resolved_addr = addr;
     276           0 :   peer->key.is_url            = 1;
     277           0 :   peer->state                 = PEER_STATE_UNRESOLVED;
     278           0 :   peer->addr                  = addr;
     279           0 :   peer->is_https              = is_https;
     280           0 :   peer->fd.idx                = ULONG_MAX;
     281           0 :   clear_peer_snapshot_data( peer );
     282             : 
     283             :   /* Create the selector entry now.  Latency, full/incr slot, and
     284             :      full/incr hash are unknown at this point, so the peer only
     285             :      becomes selectable by best() after on_resolve updates them once
     286             :      resolution succeeds. */
     287           0 :   ulong score = fd_sspeer_selector_add( selector, &peer->key, addr, FD_SSPEER_LATENCY_UNKNOWN,
     288           0 :                                         FD_SSPEER_SLOT_UNKNOWN, FD_SSPEER_SLOT_UNKNOWN, NULL, NULL );
     289           0 :   if( FD_UNLIKELY( score==FD_SSPEER_SCORE_INVALID ) ) {
     290             :     /* If unable to add, then release the element back to the pool. */
     291           0 :     FD_LOG_WARNING(( "failed to add peer to selector (hostname \"%s\" addr=" FD_IP4_ADDR_FMT ":%hu score=%lu)",
     292           0 :                      peer->key.url.hostname[ 0 ] ? peer->key.url.hostname : "(none)",
     293           0 :                      FD_IP4_ADDR_FMT_ARGS( peer->addr.addr ), fd_ushort_bswap( peer->addr.port ), score ));
     294           0 :     peer_pool_ele_release( resolver->pool, peer );
     295           0 :     return -1;
     296           0 :   }
     297             :   /* Add to the unresolved list. */
     298           0 :   deadline_list_ele_push_tail( resolver->unresolved, peer, resolver->pool );
     299           0 :   return 0;
     300           0 : }
     301             : 
     302             : static int
     303             : create_socket( fd_http_resolver_t *  resolver,
     304           0 :                fd_ssresolve_peer_t * peer ) {
     305           0 :   int sockfd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0 );
     306           0 :   if( FD_UNLIKELY( -1==sockfd ) ) FD_LOG_ERR(( "socket failed (%i-%s)", errno, strerror( errno ) ));
     307             : 
     308           0 :   int optval = 1;
     309           0 :   if( FD_UNLIKELY( -1==setsockopt( sockfd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(int) ) ) ) {
     310           0 :     FD_LOG_ERR(( "setsockopt() failed (%d-%s)", errno, fd_io_strerror( errno ) ));
     311           0 :   }
     312             : 
     313           0 :   struct sockaddr_in addr = {
     314           0 :     .sin_family = AF_INET,
     315           0 :     .sin_port   = peer->addr.port,
     316           0 :     .sin_addr   = { .s_addr = peer->addr.addr }
     317           0 :   };
     318             : 
     319           0 :   if( FD_UNLIKELY( -1==connect( sockfd, fd_type_pun( &addr ), sizeof(addr) ) && errno!=EINPROGRESS ) ) {
     320           0 :     if( FD_UNLIKELY( -1==close( sockfd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     321           0 :     return -1;
     322           0 :   }
     323             : 
     324           0 :   struct epoll_event ev = { .events = EPOLLIN|EPOLLOUT, .data.fd = sockfd };
     325           0 :   if( FD_UNLIKELY( -1==epoll_ctl( resolver->epoll_fd, EPOLL_CTL_ADD, sockfd, &ev ) ) ) FD_LOG_ERR(( "epoll_ctl(ADD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     326             : 
     327           0 :   resolver->fds[ resolver->fds_len ] = (struct pollfd){
     328           0 :     .fd      = sockfd,
     329           0 :     .events  = POLLIN|POLLOUT,
     330           0 :     .revents = 0
     331           0 :   };
     332             : 
     333           0 :   return 0;
     334           0 : }
     335             : 
     336             : static int
     337             : peer_connect( fd_http_resolver_t *  resolver,
     338           0 :               fd_ssresolve_peer_t * peer ) {
     339           0 :   int err;
     340           0 :   err = create_socket( resolver, peer ); /* full */
     341           0 :   if( FD_UNLIKELY( err ) ) return err;
     342           0 :   resolver->fds_idx[ resolver->fds_len ] = peer_pool_idx( resolver->pool, peer );
     343           0 :   peer->fd.idx = resolver->fds_len;
     344           0 :   resolver->fds_len++;
     345             : 
     346           0 :   if( FD_UNLIKELY( peer->is_https ) ) {
     347           0 :     fd_ssresolve_init_https( peer->full_ssresolve, peer->addr, resolver->fds[ peer->fd.idx ].fd, 1, peer->key.url.hostname, &resolver->tls,
     348           0 :                              &resolver->ca_store );
     349           0 :   } else {
     350           0 :     fd_ssresolve_init( peer->full_ssresolve, peer->addr, resolver->fds[ peer->fd.idx ].fd, 1, peer->key.url.hostname );
     351           0 :   }
     352             : 
     353           0 :   if( FD_LIKELY( resolver->incremental_snapshot_fetch ) ) {
     354           0 :     err = create_socket( resolver, peer ); /* incremental */
     355           0 :     if( FD_UNLIKELY( err ) ) {
     356             :       /* Undo the full socket setup to avoid leaking the fd and
     357             :          corrupting the fds array (entries must always come in pairs). */
     358           0 :       if( FD_UNLIKELY( -1==epoll_ctl( resolver->epoll_fd, EPOLL_CTL_DEL, resolver->fds[ resolver->fds_len-1UL ].fd, NULL ) ) ) FD_LOG_ERR(( "epoll_ctl(DEL) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     359           0 :       fd_ssresolve_cancel( peer->full_ssresolve );
     360           0 :       resolver->fds_len--;
     361           0 :       peer->fd.idx = ULONG_MAX;
     362           0 :       return err;
     363           0 :     }
     364           0 :     resolver->fds_idx[ resolver->fds_len ] = peer_pool_idx( resolver->pool, peer );
     365           0 :     resolver->fds_len++;
     366           0 :     if( FD_UNLIKELY( peer->is_https ) ) {
     367           0 :       fd_ssresolve_init_https( peer->inc_ssresolve, peer->addr, resolver->fds[ peer->fd.idx+1UL ].fd, 0, peer->key.url.hostname, &resolver->tls,
     368           0 :                                &resolver->ca_store );
     369           0 :     } else {
     370           0 :       fd_ssresolve_init( peer->inc_ssresolve, peer->addr, resolver->fds[ peer->fd.idx+1UL ].fd, 0, peer->key.url.hostname );
     371           0 :     }
     372           0 :   } else {
     373           0 :     resolver->fds[ resolver->fds_len ] = (struct pollfd) {
     374           0 :       .fd      = -1,
     375           0 :       .events  = 0,
     376           0 :       .revents = 0
     377           0 :     };
     378           0 :     resolver->fds_idx[ resolver->fds_len ] = ULONG_MAX;
     379           0 :     resolver->fds_len++;
     380           0 :   }
     381             : 
     382           0 :   return 0;
     383           0 : }
     384             : 
     385             : static inline void
     386             : remove_peer( fd_http_resolver_t * resolver,
     387           0 :              ulong                idx ) {
     388           0 :   FD_TEST( idx<resolver->fds_len );
     389             : 
     390           0 :   fd_ssresolve_peer_t * cur_peer = peer_pool_ele( resolver->pool, resolver->fds_idx[ idx ] );
     391           0 :   for( ulong k=0UL; k<2UL; k++ ) {
     392           0 :     int fd = resolver->fds[ idx+k ].fd;
     393           0 :     if( FD_UNLIKELY( fd==-1 || !resolver->fds[ idx+k ].events ) ) continue; /* never added or already deregistered */
     394           0 :     if( FD_UNLIKELY( -1==epoll_ctl( resolver->epoll_fd, EPOLL_CTL_DEL, fd, NULL ) ) ) FD_LOG_ERR(( "epoll_ctl(DEL) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     395           0 :   }
     396           0 :   fd_ssresolve_cancel( cur_peer->full_ssresolve );
     397           0 :   fd_ssresolve_cancel( cur_peer->inc_ssresolve );
     398             : 
     399           0 :   if( FD_UNLIKELY( resolver->fds_len==2UL ) ) {
     400           0 :     resolver->fds_len = 0UL;
     401           0 :     return;
     402           0 :   }
     403             : 
     404           0 :   resolver->fds[ idx ]     = resolver->fds[ resolver->fds_len-2UL ];
     405           0 :   resolver->fds_idx[ idx ] = resolver->fds_idx[ resolver->fds_len-2UL ];
     406             : 
     407           0 :   resolver->fds[ idx+1UL ]     = resolver->fds[ resolver->fds_len-1UL ];
     408           0 :   resolver->fds_idx[ idx+1UL ] = resolver->fds_idx[ resolver->fds_len-1UL ];
     409             : 
     410           0 :   fd_ssresolve_peer_t * peer = peer_pool_ele( resolver->pool, resolver->fds_idx[ idx ] );
     411           0 :   peer->fd.idx               = idx;
     412             : 
     413           0 :   resolver->fds_len -= 2UL;
     414           0 : }
     415             : 
     416             : static inline void
     417             : unresolve_peer( fd_http_resolver_t *   resolver,
     418             :                 fd_sspeer_selector_t * selector,
     419             :                 fd_ssresolve_peer_t *  peer,
     420           0 :                 long                   now ) {
     421           0 :   FD_TEST( peer->state==PEER_STATE_UNRESOLVED || peer->state==PEER_STATE_REFRESHING );
     422           0 :   fd_sspeer_selector_remove( selector, &peer->key );
     423           0 :   remove_peer( resolver, peer->fd.idx );
     424           0 :   deadline_list_ele_remove( resolver->resolving, peer, resolver->pool );
     425           0 :   peer->state          = PEER_STATE_INVALID;
     426           0 :   peer->deadline_nanos = now + PEER_DEADLINE_NANOS_INVALID;
     427           0 :   deadline_list_ele_push_tail( resolver->invalid, peer, resolver->pool );
     428           0 : }
     429             : 
     430             : /* Only wait for writability while there is something to send,
     431             :    otherwise a connected socket is always ready and the tile spins.  A
     432             :    finished resolve leaves the set entirely (events==0) since the server
     433             :    close would otherwise report HUP until the sibling finishes too. */
     434             : 
     435             : static inline void
     436             : update_events( fd_http_resolver_t * resolver,
     437             :                struct pollfd *      pfd,
     438           0 :                fd_ssresolve_t *     ssresolve ) {
     439           0 :   short events = 0;
     440           0 :   if( FD_LIKELY( !fd_ssresolve_is_done( ssresolve ) ) ) events = (short)(POLLIN | (fd_ssresolve_wants_pollout( ssresolve ) ? POLLOUT : 0));
     441           0 :   if( FD_LIKELY( events==pfd->events ) ) return;
     442           0 :   if( FD_UNLIKELY( !events ) ) {
     443           0 :     if( FD_UNLIKELY( -1==epoll_ctl( resolver->epoll_fd, EPOLL_CTL_DEL, pfd->fd, NULL ) ) ) FD_LOG_ERR(( "epoll_ctl(DEL) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     444           0 :   } else {
     445           0 :     struct epoll_event ev = { .events = EPOLLIN | ((events&POLLOUT) ? EPOLLOUT : 0U), .data.fd = pfd->fd };
     446           0 :     if( FD_UNLIKELY( -1==epoll_ctl( resolver->epoll_fd, EPOLL_CTL_MOD, pfd->fd, &ev ) ) ) FD_LOG_ERR(( "epoll_ctl(MOD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     447           0 :   }
     448           0 :   pfd->events = events;
     449           0 : }
     450             : 
     451             : static inline int
     452             : poll_resolve( fd_http_resolver_t *   resolver,
     453             :               fd_sspeer_selector_t * selector,
     454             :               struct pollfd *        pfd,
     455             :               fd_ssresolve_peer_t *  peer,
     456             :               fd_ssresolve_t *       ssresolve,
     457             :               ulong                  idx,
     458           0 :               long                   now ) {
     459           0 :   FD_TEST( !fd_ssresolve_is_done( ssresolve ) );
     460           0 :   if( FD_LIKELY( pfd->revents & POLLOUT ) ) {
     461           0 :     int res = fd_ssresolve_advance_poll_out( ssresolve );
     462             : 
     463           0 :     if( FD_UNLIKELY( res==FD_SSRESOLVE_ADVANCE_ERROR ) ) {
     464           0 :       unresolve_peer( resolver, selector, peer_pool_ele( resolver->pool, resolver->fds_idx[ idx ] ), now );
     465           0 :       return -1;
     466           0 :     }
     467           0 :   }
     468             : 
     469           0 :   if( FD_LIKELY( pfd->revents & POLLIN ) ) {
     470           0 :     fd_ssresolve_result_t resolve_result;
     471           0 :     int res = fd_ssresolve_advance_poll_in( ssresolve, &resolve_result );
     472             : 
     473           0 :     if( FD_UNLIKELY( res==FD_SSRESOLVE_ADVANCE_ERROR ) ) {
     474           0 :       unresolve_peer( resolver, selector, peer_pool_ele( resolver->pool, resolver->fds_idx[ idx ] ), now );
     475           0 :       return -1;
     476           0 :     } else if( FD_UNLIKELY( res==FD_SSRESOLVE_ADVANCE_AGAIN ) ) {
     477           0 :       update_events( resolver, pfd, ssresolve );
     478           0 :       return -1;
     479           0 :     } else if( FD_UNLIKELY( res==FD_SSRESOLVE_ADVANCE_ABSENT ) ) {
     480             :       /* Nothing to record here, the peer's incr_slot is already FD_SSPEER_SLOT_UNKNOWN. */
     481           0 :     } else if( FD_LIKELY( res==FD_SSRESOLVE_ADVANCE_RESULT ) ) {
     482           0 :       FD_TEST( peer->deadline_nanos>now );
     483             : 
     484           0 :       if( resolve_result.base_slot==ULONG_MAX ) {
     485           0 :         peer->full_slot = resolve_result.slot;
     486           0 :         fd_memcpy( peer->full_hash, resolve_result.hash, FD_HASH_FOOTPRINT );
     487           0 :       } else {
     488           0 :         peer->incr_base_slot = resolve_result.base_slot;
     489           0 :         peer->incr_slot      = resolve_result.slot;
     490           0 :         fd_memcpy( peer->incr_hash, resolve_result.hash, FD_HASH_FOOTPRINT );
     491           0 :       }
     492           0 :     }
     493           0 :   }
     494             : 
     495           0 :   update_events( resolver, pfd, ssresolve );
     496           0 :   return 0;
     497           0 : }
     498             : 
     499             : static inline void
     500             : poll_advance( fd_http_resolver_t *   resolver,
     501             :               fd_sspeer_selector_t * selector,
     502           0 :               long                   now ) {
     503           0 :   if( FD_LIKELY( !resolver->fds_len ) ) return;
     504             : 
     505           0 :   int nfds = fd_syscall_poll( resolver->fds, (uint)resolver->fds_len, 0 );
     506           0 :   if( FD_LIKELY( !nfds ) ) return;
     507           0 :   else if( FD_UNLIKELY( -1==nfds && errno==EINTR ) ) return;
     508           0 :   else if( FD_UNLIKELY( -1==nfds ) ) FD_LOG_ERR(( "poll failed (%i-%s)", errno, strerror( errno ) ));
     509             : 
     510           0 :   for( ulong i=0UL; i<resolver->fds_len; i++) {
     511             : 
     512           0 :     struct pollfd * pfd = &resolver->fds[ i ];
     513           0 :     if( FD_UNLIKELY( pfd->fd==-1 ) ) continue;
     514             : 
     515           0 :     fd_ssresolve_peer_t * peer = peer_pool_ele( resolver->pool, resolver->fds_idx[ i ] );
     516           0 :     int                   full = i&1UL ? 0 : 1; /* even indices are full, odd indices are incremental */
     517           0 :     fd_ssresolve_t * ssresolve = full ? peer->full_ssresolve : peer->inc_ssresolve;
     518             : 
     519             :     /* Process pending I/O before checking for errors.  POLLIN can
     520             :        coexist with POLLHUP when the server sends a response and then
     521             :        closes the connection (common for HTTP HEAD redirects). */
     522           0 :     if( FD_LIKELY( !fd_ssresolve_is_done( ssresolve ) ) ) {
     523           0 :       int res = poll_resolve( resolver, selector, pfd, peer, ssresolve, i, now );
     524           0 :       if( FD_UNLIKELY( res ) ) continue;
     525           0 :     }
     526             : 
     527             :     /* Only react to POLLERR/POLLHUP if the ssresolve hasn't completed
     528             :        yet.  After a redirect is parsed the server often closes the
     529             :        connection, which is harmless. */
     530           0 :     if( FD_UNLIKELY( (pfd->revents & (POLLERR|POLLHUP)) && !fd_ssresolve_is_done( ssresolve ) ) ) {
     531           0 :       unresolve_peer( resolver, selector, peer_pool_ele( resolver->pool, resolver->fds_idx[ i ] ), now );
     532           0 :       continue;
     533           0 :     }
     534             : 
     535             :     /* Once both the full and incremental snapshots are resolved, we can
     536             :        mark the peer valid and remove the peer from the list of peers to
     537             :        ping. */
     538           0 :     if( FD_LIKELY( fd_ssresolve_is_done( peer->full_ssresolve ) &&
     539           0 :                    (!resolver->incremental_snapshot_fetch || fd_ssresolve_is_done( peer->inc_ssresolve ) ) ) ) {
     540           0 :       peer->state                 = PEER_STATE_VALID;
     541           0 :       peer->deadline_nanos        = now + PEER_DEADLINE_NANOS_VALID;
     542             : 
     543           0 :       deadline_list_ele_remove( resolver->resolving, peer, resolver->pool );
     544           0 :       deadline_list_ele_push_tail( resolver->valid, peer, resolver->pool );
     545           0 :       remove_peer( resolver, peer->fd.idx );
     546             : 
     547           0 :       int has_incr = peer->incr_slot!=FD_SSPEER_SLOT_UNKNOWN && peer->incr_base_slot==peer->full_slot;
     548           0 :       resolver->on_resolve_cb( resolver->cb_arg, &peer->key, peer->addr, peer->full_slot,
     549           0 :                                has_incr ? peer->incr_slot : FD_SSPEER_SLOT_UNKNOWN, peer->full_hash,
     550           0 :                                has_incr ? peer->incr_hash : NULL );
     551           0 :     }
     552           0 :   }
     553           0 : }
     554             : 
     555             : long
     556           0 : fd_http_resolver_next_deadline( fd_http_resolver_t const * resolver ) {
     557           0 :   long next = LONG_MAX;
     558           0 :   deadline_list_t const * lists[] = { resolver->resolving, resolver->valid, resolver->invalid };
     559           0 :   for( ulong i=0UL; i<sizeof(lists)/sizeof(lists[0]); i++ ) {
     560           0 :     if( FD_LIKELY( deadline_list_is_empty( lists[ i ], resolver->pool ) ) ) continue;
     561           0 :     next = fd_long_min( next, deadline_list_ele_peek_head_const( lists[ i ], resolver->pool )->deadline_nanos );
     562           0 :   }
     563           0 :   if( !deadline_list_is_empty( resolver->unresolved, resolver->pool ) ) next = 0L; /* connects now */
     564           0 :   return next;
     565           0 : }
     566             : 
     567             : void
     568             : fd_http_resolver_advance( fd_http_resolver_t *   resolver,
     569             :                           long                   now,
     570           0 :                           fd_sspeer_selector_t * selector ) {
     571           0 :   while( !deadline_list_is_empty( resolver->unresolved, resolver->pool ) )  {
     572           0 :     fd_ssresolve_peer_t * peer = deadline_list_ele_pop_head( resolver->unresolved, resolver->pool );
     573             : 
     574           0 :     FD_LOG_INFO(( "resolving " FD_IP4_ADDR_FMT ":%hu", FD_IP4_ADDR_FMT_ARGS( peer->addr.addr ), fd_ushort_bswap( peer->addr.port ) ));
     575             :     /* Clear stale snapshot data so the new resolve cycle starts clean.
     576             :        Without this, a previously-valid peer could carry stale
     577             :        incr_slot/incr_hash through the invalid->unresolved cycle. */
     578           0 :     clear_peer_snapshot_data( peer );
     579             :     /* Peers that were already removed from the selector due to
     580             :        timeout or blacklist handling stay absent while resolving.
     581             :        Re-adding such peers here with unknown slots would bypass
     582             :        blacklist checks and contribute no useful data.  The
     583             :        on_resolve callback re-adds them once resolution succeeds. */
     584           0 :     int result = peer_connect( resolver, peer );
     585           0 :     if( FD_UNLIKELY( -1==result ) ) {
     586           0 :       peer->state          = PEER_STATE_INVALID;
     587           0 :       peer->deadline_nanos = now + PEER_DEADLINE_NANOS_INVALID;
     588           0 :       deadline_list_ele_push_tail( resolver->invalid, peer, resolver->pool );
     589           0 :     } else {
     590           0 :       peer->state          = PEER_STATE_REFRESHING;
     591           0 :       peer->deadline_nanos = now + PEER_DEADLINE_NANOS_RESOLVE;
     592           0 :       deadline_list_ele_push_tail( resolver->resolving, peer, resolver->pool );
     593           0 :     }
     594           0 :   }
     595             : 
     596           0 :   while( !deadline_list_is_empty( resolver->resolving, resolver->pool ) ) {
     597           0 :     fd_ssresolve_peer_t * peer = deadline_list_ele_peek_head( resolver->resolving, resolver->pool );
     598           0 :     if( FD_LIKELY( peer->deadline_nanos>now ) ) break;
     599             : 
     600           0 :     deadline_list_ele_pop_head( resolver->resolving, resolver->pool );
     601           0 :     peer->state          = PEER_STATE_INVALID;
     602           0 :     peer->deadline_nanos = now + PEER_DEADLINE_NANOS_INVALID;
     603           0 :     deadline_list_ele_push_tail( resolver->invalid, peer, resolver->pool );
     604           0 :     remove_peer( resolver, peer->fd.idx );
     605             : 
     606           0 :     fd_sspeer_selector_remove( selector, &peer->key );
     607           0 :   }
     608             : 
     609           0 :   while( !deadline_list_is_empty( resolver->invalid, resolver->pool ) ) {
     610           0 :     fd_ssresolve_peer_t * peer = deadline_list_ele_peek_head( resolver->invalid, resolver->pool );
     611           0 :     if( FD_LIKELY( peer->deadline_nanos>now ) ) break;
     612             : 
     613           0 :     deadline_list_ele_pop_head( resolver->invalid, resolver->pool );
     614             : 
     615           0 :     peer->state          = PEER_STATE_UNRESOLVED;
     616           0 :     peer->deadline_nanos = 0L;
     617           0 :     deadline_list_ele_push_tail( resolver->unresolved, peer, resolver->pool );
     618           0 :   }
     619             : 
     620           0 :   while( !deadline_list_is_empty( resolver->valid, resolver->pool ) )  {
     621           0 :     fd_ssresolve_peer_t * peer = deadline_list_ele_peek_head( resolver->valid, resolver->pool );
     622           0 :     if( FD_LIKELY( peer->deadline_nanos>now ) ) break;
     623             : 
     624           0 :     deadline_list_ele_pop_head( resolver->valid, resolver->pool );
     625             : 
     626             :     /* Clear stale snapshot data before re-resolving so the peer
     627             :        does not carry data from the previous resolve cycle. */
     628           0 :     clear_peer_snapshot_data( peer );
     629           0 :     int result = peer_connect( resolver, peer );
     630           0 :     if( FD_UNLIKELY( -1==result ) ) {
     631           0 :       peer->state = PEER_STATE_INVALID;
     632           0 :       peer->deadline_nanos = now + PEER_DEADLINE_NANOS_INVALID;
     633           0 :       deadline_list_ele_push_tail( resolver->invalid, peer, resolver->pool );
     634           0 :       fd_sspeer_selector_remove( selector, &peer->key );
     635           0 :     } else {
     636           0 :       peer->state = PEER_STATE_REFRESHING;
     637           0 :       peer->deadline_nanos = now + PEER_DEADLINE_NANOS_RESOLVE;
     638           0 :       deadline_list_ele_push_tail( resolver->resolving, peer, resolver->pool );
     639           0 :     }
     640           0 :   }
     641             : 
     642           0 :   poll_advance( resolver, selector, now );
     643           0 : }

Generated by: LCOV version 1.14