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