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 : }
|