Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_sshttp_private.h"
3 : #include "fd_ssarchive.h"
4 :
5 : #include "../../../third_party/picohttpparser/picohttpparser.h"
6 : #include "../../../util/log/fd_log.h"
7 : #include "../../../util/fd_util.h"
8 : #include "../../../waltz/http/fd_http.h"
9 : #include "../../../ballet/ed25519/fd_x25519.h"
10 :
11 : FD_STATIC_ASSERT( FD_HASH_FOOTPRINT==32UL, resolved_hash_sz );
12 :
13 : #include <unistd.h>
14 : #include <errno.h>
15 : #include <poll.h>
16 : #include <stdlib.h>
17 :
18 : #include <sys/socket.h>
19 : #include <sys/random.h>
20 : #include <netinet/in.h>
21 :
22 : _Bool fd_sshttp_fuzz = 0;
23 :
24 : static void
25 12 : fd_sshttp_tls_init( fd_tls_t * tls, fd_sshttp_t * http ) {
26 12 : fd_memset( tls, 0, sizeof(fd_tls_t) );
27 :
28 : /* Seed the CSPRNG that fd_tls draws handshake randomness from */
29 :
30 12 : uchar rng_key[ FD_CHACHA_KEY_SZ ];
31 12 : if( FD_UNLIKELY( !fd_rng_secure( rng_key, sizeof(rng_key) ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
32 12 : fd_chacha_rng_init( http->rng, rng_key, FD_CHACHA_RNG_ALGO_CHACHA8 );
33 12 : fd_memzero_explicit( rng_key, sizeof(rng_key) );
34 12 : tls->rng = http->rng;
35 :
36 12 : static uchar const alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '1' };
37 12 : fd_memcpy( tls->alpn, alpn, sizeof(alpn) );
38 12 : tls->alpn_sz = sizeof(alpn);
39 :
40 12 : tls->quic = 0;
41 :
42 12 : tls->ca_store = &http->ca_store;
43 12 : }
44 :
45 : static int
46 0 : http_init_tls( fd_sshttp_t * http ) {
47 0 : ulong hostname_len = strlen( http->hostname );
48 0 : if( FD_UNLIKELY( hostname_len >= sizeof(http->tls.server_name) ) ) {
49 0 : FD_LOG_WARNING(( "hostname too long for SNI: %s", http->hostname ));
50 0 : return -1;
51 0 : }
52 0 : fd_memcpy( http->tls.server_name, http->hostname, hostname_len );
53 0 : http->tls.server_name[ hostname_len ] = '\0';
54 0 : http->tls.server_name_len = (ushort)hostname_len;
55 :
56 : /* Generate a fresh ephemeral X25519 key for this handshake */
57 :
58 0 : if( FD_UNLIKELY( !fd_rng_secure( http->tls.kex_private_key, 32UL ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
59 0 : fd_x25519_public( http->tls.kex_public_key, http->tls.kex_private_key );
60 :
61 0 : fd_tlsrec_conn_init( &http->tls_conn, &http->tls, 0 );
62 0 : fd_tlsrec_sock_init( http->tls_sock );
63 :
64 0 : return 0;
65 0 : }
66 :
67 : /* io_backoff sleeps for up to a millisecond, or until the socket is
68 : ready for events, once several iterations in a row have moved no
69 : bytes. Returns -1 if the poll failed fatally. */
70 :
71 : static int
72 : io_backoff( fd_sshttp_t * http,
73 0 : short events ) {
74 0 : if( FD_LIKELY( ++http->empty_recvs<=8UL || fd_sshttp_fuzz ) ) return 0;
75 :
76 0 : struct pollfd pfd = {
77 0 : .fd = http->sockfd,
78 0 : .events = events,
79 0 : };
80 0 : if( FD_UNLIKELY( -1==fd_syscall_poll( &pfd, 1 /*fds*/, 1 /*ms*/ ) && errno!=EINTR ) ) {
81 0 : FD_LOG_WARNING(( "fd_syscall_poll() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
82 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
83 0 : return -1;
84 0 : }
85 0 : return 0;
86 0 : }
87 :
88 : static int
89 : http_connect_tls( fd_sshttp_t * http,
90 0 : long now ) {
91 0 : if( FD_UNLIKELY( now > http->deadline ) ) {
92 0 : FD_LOG_WARNING(( "TLS handshake timeout" ));
93 0 : fd_sshttp_cancel( http );
94 0 : return FD_SSHTTP_ADVANCE_ERROR;
95 0 : }
96 :
97 : /* Flush any buffered outgoing data (e.g., ClientHello from a
98 : previous call where the TCP connect was still in progress). */
99 :
100 0 : int flush = fd_tlsrec_sock_flush( http->tls_sock, http->sockfd );
101 0 : if( flush<0 ) { fd_sshttp_cancel( http ); return FD_SSHTTP_ADVANCE_ERROR; }
102 0 : if( flush>0 ) {
103 0 : if( FD_UNLIKELY( -1==io_backoff( http, POLLOUT ) ) ) { fd_sshttp_cancel( http ); return FD_SSHTTP_ADVANCE_ERROR; }
104 0 : return FD_SSHTTP_ADVANCE_AGAIN;
105 0 : }
106 :
107 : /* Drive the TLS handshake */
108 :
109 0 : ulong tcp_rx_sz;
110 0 : int err = fd_tlsrec_sock_rx( http->tls_sock, &http->tls_conn, http->sockfd, &tcp_rx_sz );
111 0 : if( FD_UNLIKELY( err ) ) {
112 0 : if( err==FD_TLSREC_SOCK_ERR_EOF ) {
113 0 : FD_LOG_WARNING(( "peer " FD_IP4_ADDR_FMT ":%hu closed the connection during TLS handshake",
114 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
115 0 : } else {
116 0 : FD_LOG_WARNING(( "TLS handshake failed (%d-%s)", err, fd_tlsrec_sock_strerror( err ) ));
117 0 : }
118 0 : fd_sshttp_cancel( http );
119 0 : return FD_SSHTTP_ADVANCE_ERROR;
120 0 : }
121 0 : if( tcp_rx_sz ) http->empty_recvs = 0UL;
122 0 : else if( FD_UNLIKELY( -1==io_backoff( http, POLLIN ) ) ) { fd_sshttp_cancel( http ); return FD_SSHTTP_ADVANCE_ERROR; }
123 :
124 : /* Transition to request state once handshake completes */
125 :
126 0 : if( fd_tlsrec_conn_is_ready( &http->tls_conn ) ) {
127 0 : http->state = FD_SSHTTP_STATE_REQ;
128 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
129 0 : return FD_SSHTTP_ADVANCE_AGAIN;
130 0 : }
131 :
132 0 : if( fd_tlsrec_conn_is_failed( &http->tls_conn ) ) {
133 0 : FD_LOG_WARNING(( "TLS handshake failed" ));
134 0 : fd_sshttp_cancel( http );
135 0 : return FD_SSHTTP_ADVANCE_ERROR;
136 0 : }
137 :
138 0 : return FD_SSHTTP_ADVANCE_AGAIN;
139 0 : }
140 :
141 : static long
142 : http_send_tls( fd_sshttp_t * http,
143 : void * buf,
144 0 : ulong bufsz ) {
145 0 : int flush = fd_tlsrec_sock_flush( http->tls_sock, http->sockfd );
146 0 : if( flush<0 ) return FD_SSHTTP_ADVANCE_ERROR;
147 0 : if( flush>0 ) {
148 0 : if( FD_UNLIKELY( -1==io_backoff( http, POLLOUT ) ) ) return FD_SSHTTP_ADVANCE_ERROR;
149 0 : return FD_SSHTTP_ADVANCE_AGAIN;
150 0 : }
151 :
152 0 : ulong consumed;
153 0 : if( FD_UNLIKELY( fd_tlsrec_sock_tx( http->tls_sock, &http->tls_conn, http->sockfd, buf, bufsz, &consumed ) ) )
154 0 : return FD_SSHTTP_ADVANCE_ERROR;
155 0 : return (long)consumed;
156 0 : }
157 :
158 : static long
159 : http_recv_tls( fd_sshttp_t * http,
160 : void * buf,
161 0 : ulong bufsz ) {
162 :
163 : /* Drain any buffered plaintext from a previous call */
164 :
165 0 : ulong n = fd_tlsrec_sock_rx_pop( http->tls_sock, buf, bufsz );
166 0 : if( n ) {
167 0 : http->empty_recvs = 0UL;
168 0 : return (long)n;
169 0 : }
170 :
171 : /* Drain pending ciphertext before generating any new records */
172 :
173 0 : int flush = fd_tlsrec_sock_flush( http->tls_sock, http->sockfd );
174 0 : if( flush<0 ) return FD_SSHTTP_ADVANCE_ERROR;
175 0 : if( flush>0 ) {
176 0 : if( FD_UNLIKELY( -1==io_backoff( http, POLLOUT ) ) ) return FD_SSHTTP_ADVANCE_ERROR;
177 0 : return FD_SSHTTP_ADVANCE_AGAIN;
178 0 : }
179 :
180 : /* Read and decrypt */
181 :
182 0 : ulong tcp_rx_sz;
183 0 : int err = fd_tlsrec_sock_rx( http->tls_sock, &http->tls_conn, http->sockfd, &tcp_rx_sz );
184 0 : if( FD_UNLIKELY( err ) ) {
185 0 : if( err==FD_TLSREC_SOCK_ERR_EOF ) {
186 0 : FD_LOG_WARNING(( "peer " FD_IP4_ADDR_FMT ":%hu closed the connection mid-response",
187 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
188 0 : } else if( err==FD_TLSREC_SOCK_ERR_RECV ) {
189 0 : FD_LOG_WARNING(( "recv() failed (%d-%s) from " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
190 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
191 0 : }
192 0 : return FD_SSHTTP_ADVANCE_ERROR;
193 0 : }
194 0 : if( !tcp_rx_sz ) {
195 0 : if( FD_UNLIKELY( -1==io_backoff( http, POLLIN ) ) ) return FD_SSHTTP_ADVANCE_ERROR;
196 0 : return FD_SSHTTP_ADVANCE_AGAIN;
197 0 : }
198 :
199 0 : http->empty_recvs = 0UL; /* socket made progress */
200 :
201 0 : n = fd_tlsrec_sock_rx_pop( http->tls_sock, buf, bufsz );
202 0 : if( !n ) return FD_SSHTTP_ADVANCE_AGAIN;
203 0 : return (long)n;
204 0 : }
205 :
206 : static int
207 : setup_redirect_tls( fd_sshttp_t * http,
208 0 : long now ) {
209 0 : fd_sshttp_cancel( http );
210 0 : if( FD_UNLIKELY( fd_sshttp_init( http, http->addr, http->hostname, http->is_https, http->location, http->location_len, ULONG_MAX, now ) ) ) {
211 0 : return FD_SSHTTP_ADVANCE_ERROR;
212 0 : }
213 0 : return FD_SSHTTP_ADVANCE_AGAIN;
214 0 : }
215 :
216 : void
217 0 : fd_sshttp_load_ca_store( fd_sshttp_t * sshttp ) {
218 0 : if( FD_UNLIKELY( fd_x509_ca_store_load_system( &sshttp->ca_store )<0L ) ) {
219 0 : FD_LOG_WARNING(( "No CA certificate bundle found, HTTPS snapshot sources will be refused" ));
220 0 : return;
221 0 : }
222 0 : sshttp->ca_store_loaded = 1;
223 0 : }
224 :
225 : FD_FN_CONST ulong
226 24 : fd_sshttp_align( void ) {
227 24 : return alignof(fd_sshttp_t);
228 24 : }
229 :
230 : FD_FN_CONST ulong
231 0 : fd_sshttp_footprint( void ) {
232 0 : ulong l;
233 0 : l = FD_LAYOUT_INIT;
234 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
235 0 : return FD_LAYOUT_FINI( l, fd_sshttp_align() );
236 0 : }
237 :
238 : void *
239 12 : fd_sshttp_new( void * shmem ) {
240 12 : if( FD_UNLIKELY( !shmem ) ) {
241 0 : FD_LOG_WARNING(( "NULL shmem" ));
242 0 : return NULL;
243 0 : }
244 :
245 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_sshttp_align() ) ) ) {
246 0 : FD_LOG_WARNING(( "unaligned shmem" ));
247 0 : return NULL;
248 0 : }
249 :
250 12 : FD_SCRATCH_ALLOC_INIT( l, shmem );
251 12 : fd_sshttp_t * sshttp = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
252 :
253 12 : sshttp->state = FD_SSHTTP_STATE_INIT;
254 12 : sshttp->sockfd = -1;
255 12 : sshttp->content_len = 0UL;
256 12 : fd_cstr_fini( sshttp->snapshot_name );
257 12 : sshttp->resolved_slot = 0UL;
258 12 : fd_memset( sshttp->resolved_hash, 0, FD_HASH_FOOTPRINT );
259 :
260 : /* Initialize native TLS config */
261 12 : fd_tlsrec_sock_init( sshttp->tls_sock );
262 12 : fd_sshttp_tls_init( &sshttp->tls, sshttp );
263 :
264 : /* CA trust store — loaded via fd_sshttp_load_ca_store() during
265 : privileged_init, before the seccomp sandbox locks down. */
266 12 : sshttp->ca_store_loaded = 0;
267 :
268 12 : FD_COMPILER_MFENCE();
269 12 : sshttp->magic = FD_SSHTTP_MAGIC;
270 12 : FD_COMPILER_MFENCE();
271 :
272 12 : return (void *)sshttp;
273 12 : }
274 :
275 : fd_sshttp_t *
276 12 : fd_sshttp_join( void * shhttp ) {
277 12 : if( FD_UNLIKELY( !shhttp ) ) {
278 0 : FD_LOG_WARNING(( "NULL shhttp" ));
279 0 : return NULL;
280 0 : }
281 :
282 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shhttp, fd_sshttp_align() ) ) ) {
283 0 : FD_LOG_WARNING(( "misaligned shhttp" ));
284 0 : return NULL;
285 0 : }
286 :
287 12 : fd_sshttp_t * sshttp = (fd_sshttp_t *)shhttp;
288 :
289 12 : if( FD_UNLIKELY( sshttp->magic!=FD_SSHTTP_MAGIC ) ) {
290 0 : FD_LOG_WARNING(( "bad magic" ));
291 0 : return NULL;
292 0 : }
293 :
294 12 : return sshttp;
295 12 : }
296 :
297 : /* http_init_ssl removed — replaced by http_init_tls (native fd_tls) */
298 :
299 : int
300 : fd_sshttp_init( fd_sshttp_t * http,
301 : fd_ip4_port_t addr,
302 : char const * hostname,
303 : int is_https,
304 : char const * path,
305 : ulong path_len,
306 : ulong hops,
307 0 : long now ) {
308 0 : FD_TEST( http->state==FD_SSHTTP_STATE_INIT );
309 :
310 0 : http->hostname = hostname;
311 0 : http->is_https = is_https;
312 :
313 0 : if( FD_LIKELY( is_https ) ) {
314 0 : if( FD_UNLIKELY( !http->ca_store_loaded ) ) {
315 0 : FD_LOG_WARNING(( "Refusing HTTPS snapshot download from %s: no CA trust store loaded", hostname ));
316 0 : return -1;
317 0 : }
318 0 : if( FD_UNLIKELY( http_init_tls( http ) ) ) return -1;
319 0 : }
320 :
321 0 : if( hops!=ULONG_MAX ) {
322 0 : http->hops = hops;
323 0 : fd_cstr_fini( http->snapshot_name );
324 0 : http->resolved_slot = 0UL;
325 0 : fd_memset( http->resolved_hash, 0, FD_HASH_FOOTPRINT );
326 0 : }
327 0 : http->request_sent = 0UL;
328 0 : int fmt_ok;
329 0 : if( FD_LIKELY( is_https ) ) {
330 0 : fmt_ok = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
331 0 : "GET %.*s HTTP/1.1\r\n"
332 0 : "User-Agent: Firedancer\r\n"
333 0 : "Accept: */*\r\n"
334 0 : "Accept-Encoding: identity\r\n"
335 0 : "Host: %s\r\n\r\n",
336 0 : (int)path_len, path, hostname );
337 0 : } else {
338 0 : fmt_ok = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
339 0 : "GET %.*s HTTP/1.1\r\n"
340 0 : "User-Agent: Firedancer\r\n"
341 0 : "Accept: */*\r\n"
342 0 : "Accept-Encoding: identity\r\n"
343 0 : "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
344 0 : (int)path_len, path, FD_IP4_ADDR_FMT_ARGS( addr.addr ) );
345 0 : }
346 0 : if( FD_UNLIKELY( !fmt_ok ) ) {
347 0 : FD_LOG_WARNING(( "HTTP request too long for %.*s", (int)path_len, path ));
348 0 : return -1;
349 0 : }
350 :
351 0 : http->response_len = 0UL;
352 0 : http->content_len = 0UL;
353 0 : http->content_read = 0UL;
354 0 : http->empty_recvs = 0UL;
355 :
356 0 : http->addr = addr;
357 0 : http->sockfd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0 );
358 0 : if( FD_UNLIKELY( -1==http->sockfd ) ) {
359 0 : FD_LOG_WARNING(( "socket() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
360 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
361 0 : return -1;
362 0 : }
363 :
364 0 : struct sockaddr_in addr_in = {
365 0 : .sin_family = AF_INET,
366 0 : .sin_port = addr.port,
367 0 : .sin_addr = { .s_addr = addr.addr }
368 0 : };
369 :
370 0 : if( FD_LIKELY( -1==connect( http->sockfd, fd_type_pun_const( &addr_in ), sizeof(addr_in) ) ) ) {
371 0 : if( FD_UNLIKELY( errno!=EINPROGRESS ) ) {
372 0 : FD_LOG_WARNING(( "connect() failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
373 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
374 0 : if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
375 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
376 0 : http->sockfd = -1;
377 0 : return -1;
378 0 : }
379 0 : }
380 :
381 0 : if( FD_LIKELY( is_https ) ) {
382 0 : http->state = FD_SSHTTP_STATE_CONNECT;
383 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
384 0 : } else {
385 0 : http->state = FD_SSHTTP_STATE_REQ;
386 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
387 0 : }
388 :
389 0 : return 0;
390 0 : }
391 :
392 : void
393 24 : fd_sshttp_cancel( fd_sshttp_t * http ) {
394 24 : if( FD_LIKELY( http->state!=FD_SSHTTP_STATE_INIT && -1!=http->sockfd ) ) {
395 12 : if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
396 12 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
397 12 : http->sockfd = -1;
398 12 : }
399 24 : http->state = FD_SSHTTP_STATE_INIT;
400 :
401 : /* Clean up native TLS state */
402 24 : fd_tlsrec_sock_init( http->tls_sock );
403 24 : }
404 :
405 : static long
406 : http_send( fd_sshttp_t * http,
407 : void * buf,
408 0 : ulong bufsz ) {
409 0 : if( FD_LIKELY( http->is_https ) )
410 0 : return http_send_tls( http, buf, bufsz );
411 :
412 0 : long sent = sendto( http->sockfd, buf, bufsz, MSG_NOSIGNAL, NULL, 0 );
413 0 : if( FD_UNLIKELY( -1==sent && errno==EAGAIN ) ) {
414 0 : if( FD_UNLIKELY( -1==io_backoff( http, POLLOUT ) ) ) {
415 0 : fd_sshttp_cancel( http );
416 0 : return FD_SSHTTP_ADVANCE_ERROR;
417 0 : }
418 0 : return FD_SSHTTP_ADVANCE_AGAIN;
419 0 : } else if( FD_UNLIKELY( -1==sent ) ) {
420 0 : FD_LOG_WARNING(( "sendto() failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
421 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
422 0 : fd_sshttp_cancel( http );
423 0 : return FD_SSHTTP_ADVANCE_ERROR;
424 0 : }
425 0 : http->empty_recvs = 0UL;
426 :
427 0 : return sent;
428 0 : }
429 :
430 : static long
431 : http_recv( fd_sshttp_t * http,
432 : void * buf,
433 18 : ulong bufsz ) {
434 18 : if( FD_LIKELY( http->is_https ) )
435 0 : return http_recv_tls( http, buf, bufsz );
436 :
437 18 : long read = recvfrom( http->sockfd, buf, bufsz, 0, NULL, NULL );
438 18 : if( FD_UNLIKELY( -1==read && errno==EAGAIN ) ) {
439 0 : if( FD_UNLIKELY( -1==io_backoff( http, POLLIN ) ) ) {
440 0 : fd_sshttp_cancel( http );
441 0 : return FD_SSHTTP_ADVANCE_ERROR;
442 0 : }
443 0 : return FD_SSHTTP_ADVANCE_AGAIN;
444 18 : } else if( FD_UNLIKELY( -1==read ) ) {
445 0 : FD_LOG_WARNING(( "recv() failed (%d-%s) from " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
446 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
447 0 : fd_sshttp_cancel( http );
448 0 : return FD_SSHTTP_ADVANCE_ERROR;
449 18 : } else if( FD_UNLIKELY( !read ) ) {
450 9 : FD_LOG_WARNING(( "peer " FD_IP4_ADDR_FMT ":%hu closed the connection mid-response",
451 9 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
452 9 : fd_sshttp_cancel( http );
453 9 : return FD_SSHTTP_ADVANCE_ERROR;
454 9 : }
455 9 : http->empty_recvs = 0UL;
456 :
457 9 : return read;
458 18 : }
459 :
460 : static int
461 : send_request( fd_sshttp_t * http,
462 0 : long now ) {
463 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
464 0 : FD_LOG_WARNING(( "timeout sending request to " FD_IP4_ADDR_FMT ":%hu",
465 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
466 0 : fd_sshttp_cancel( http );
467 0 : return FD_SSHTTP_ADVANCE_ERROR;
468 0 : }
469 :
470 0 : long sent = http_send( http, http->request+http->request_sent, http->request_len-http->request_sent );
471 0 : if( FD_UNLIKELY( sent<=0 ) ) return (int)sent;
472 :
473 0 : http->request_sent += (ulong)sent;
474 0 : if( FD_UNLIKELY( http->request_sent==http->request_len ) ) {
475 0 : http->state = FD_SSHTTP_STATE_RESP;
476 0 : http->response_len = 0UL;
477 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
478 0 : }
479 :
480 0 : return FD_SSHTTP_ADVANCE_AGAIN;
481 0 : }
482 :
483 : static int
484 : follow_redirect( fd_sshttp_t * http,
485 : struct phr_header * headers,
486 : ulong header_cnt,
487 0 : long now ) {
488 0 : if( FD_UNLIKELY( !http->hops ) ) {
489 0 : FD_LOG_WARNING(( "too many redirects (remaining %lu) from " FD_IP4_ADDR_FMT ":%hu", http->hops,
490 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
491 0 : fd_sshttp_cancel( http );
492 0 : return FD_SSHTTP_ADVANCE_ERROR;
493 0 : }
494 : /* The check above guarantees hops>0. */
495 0 : http->hops--;
496 :
497 0 : ulong location_len = 0UL;
498 0 : char const * location = NULL;
499 :
500 0 : for( ulong i=0UL; i<header_cnt; i++ ) {
501 0 : if( FD_UNLIKELY( headers[ i ].name_len == 8 && !strncasecmp( headers[ i ].name, "location", headers[ i ].name_len ) ) ) {
502 0 : if( FD_UNLIKELY( !headers [ i ].value_len || headers[ i ].value[ 0 ]!='/' ) ) {
503 0 : FD_LOG_WARNING(( "invalid location header `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)headers[ i ].value_len, headers[ i ].value,
504 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
505 0 : fd_sshttp_cancel( http );
506 0 : return FD_SSHTTP_ADVANCE_ERROR;
507 0 : }
508 :
509 0 : location_len = headers[ i ].value_len;
510 0 : location = headers[ i ].value;
511 :
512 0 : if( FD_UNLIKELY( location_len>=PATH_MAX-1UL ) ) {
513 0 : FD_LOG_WARNING(( "location header too long `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)location_len, location,
514 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
515 0 : fd_sshttp_cancel( http );
516 0 : return FD_SSHTTP_ADVANCE_ERROR;
517 0 : }
518 :
519 0 : char snapshot_name[ PATH_MAX ];
520 0 : fd_memcpy( snapshot_name, location+1UL, location_len-1UL );
521 0 : snapshot_name[ location_len-1UL ] = '\0';
522 :
523 0 : int is_zstd;
524 0 : ulong full_entry_slot, incremental_entry_slot;
525 0 : uchar decoded_hash[ FD_HASH_FOOTPRINT ];
526 0 : int err = fd_ssarchive_parse_filename( snapshot_name, &full_entry_slot, &incremental_entry_slot, decoded_hash, &is_zstd );
527 :
528 0 : if( FD_UNLIKELY( err || !is_zstd ) ) {
529 0 : FD_LOG_WARNING(( "unrecognized snapshot file `%s` in redirect location header from " FD_IP4_ADDR_FMT ":%hu", snapshot_name,
530 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
531 0 : fd_sshttp_cancel( http );
532 0 : return FD_SSHTTP_ADVANCE_ERROR;
533 0 : }
534 :
535 0 : http->resolved_slot = (incremental_entry_slot!=ULONG_MAX)
536 0 : ? incremental_entry_slot : full_entry_slot;
537 0 : fd_memcpy( http->resolved_hash, decoded_hash, FD_HASH_FOOTPRINT );
538 :
539 0 : char encoded_hash[ FD_BASE58_ENCODED_32_SZ ];
540 0 : fd_base58_encode_32( decoded_hash, NULL, encoded_hash );
541 :
542 0 : if( FD_LIKELY( incremental_entry_slot!=ULONG_MAX ) ) {
543 0 : FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "incremental-snapshot-%lu-%lu-%s.tar.zst", full_entry_slot, incremental_entry_slot, encoded_hash ) );
544 0 : } else {
545 0 : FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "snapshot-%lu-%s.tar.zst", full_entry_slot, encoded_hash ) );
546 0 : }
547 0 : break;
548 0 : }
549 0 : }
550 :
551 0 : if( FD_UNLIKELY( !location_len ) ) {
552 0 : FD_LOG_WARNING(( "no location header in redirect response from " FD_IP4_ADDR_FMT ":%hu",
553 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
554 0 : fd_sshttp_cancel( http );
555 0 : return FD_SSHTTP_ADVANCE_ERROR;
556 0 : }
557 :
558 : /* Pre-validate that the redirect request will fit in the request
559 : buffer. The request is rebuilt from scratch by fd_sshttp_init
560 : during the redirect, but the format must match so that a path
561 : accepted here will not overflow in fd_sshttp_init. */
562 0 : int pre_check;
563 0 : if( FD_LIKELY( http->is_https ) ) {
564 0 : pre_check = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
565 0 : "GET %.*s HTTP/1.1\r\n"
566 0 : "User-Agent: Firedancer\r\n"
567 0 : "Accept: */*\r\n"
568 0 : "Accept-Encoding: identity\r\n"
569 0 : "Host: %s\r\n\r\n",
570 0 : (int)location_len, location, http->hostname );
571 0 : } else {
572 0 : pre_check = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
573 0 : "GET %.*s HTTP/1.1\r\n"
574 0 : "User-Agent: Firedancer\r\n"
575 0 : "Accept: */*\r\n"
576 0 : "Accept-Encoding: identity\r\n"
577 0 : "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
578 0 : (int)location_len, location, FD_IP4_ADDR_FMT_ARGS( http->addr.addr ) );
579 0 : }
580 0 : if( FD_UNLIKELY( !pre_check ) ) {
581 0 : FD_LOG_WARNING(( "redirect request too long `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)location_len, location,
582 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
583 0 : fd_sshttp_cancel( http );
584 0 : return FD_SSHTTP_ADVANCE_ERROR;
585 0 : }
586 :
587 0 : FD_LOG_INFO(( "following redirect to %s://" FD_IP4_ADDR_FMT ":%hu%.*s", http->is_https ? "https" : "http",
588 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ),
589 0 : (int)location_len, location ));
590 :
591 0 : if( FD_UNLIKELY( http->is_https ) ) {
592 0 : http->state = FD_SSHTTP_STATE_REDIRECT;
593 0 : http->location_len = location_len;
594 0 : FD_TEST( location_len<PATH_MAX-1UL );
595 0 : fd_memcpy( http->location, location, location_len );
596 0 : http->location[ location_len ] = '\0';
597 0 : } else {
598 0 : if( FD_LIKELY( !fd_sshttp_fuzz ) ) {
599 0 : fd_sshttp_cancel( http );
600 0 : if( FD_UNLIKELY( fd_sshttp_init( http, http->addr, http->hostname, http->is_https, location, location_len, ULONG_MAX, now ) ) ) {
601 0 : return FD_SSHTTP_ADVANCE_ERROR;
602 0 : }
603 0 : } else {
604 0 : http->state = FD_SSHTTP_STATE_RESP;
605 0 : http->response_len = 0UL;
606 0 : }
607 0 : }
608 :
609 0 : return FD_SSHTTP_ADVANCE_AGAIN;
610 0 : }
611 :
612 : static int
613 : read_response( fd_sshttp_t * http,
614 : ulong * data_len,
615 : uchar * data,
616 18 : long now ) {
617 18 : if( FD_UNLIKELY( now>http->deadline ) ) {
618 0 : FD_LOG_WARNING(( "timeout reading response from " FD_IP4_ADDR_FMT ":%hu",
619 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
620 0 : fd_sshttp_cancel( http );
621 0 : return FD_SSHTTP_ADVANCE_ERROR;
622 0 : }
623 :
624 18 : if( FD_UNLIKELY( http->response_len>=sizeof(http->response) ) ) {
625 3 : FD_LOG_WARNING(( "response headers too large from " FD_IP4_ADDR_FMT ":%hu",
626 3 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
627 3 : fd_sshttp_cancel( http );
628 3 : return FD_SSHTTP_ADVANCE_ERROR;
629 3 : }
630 :
631 15 : long read = http_recv( http, http->response+http->response_len, sizeof(http->response)-http->response_len );
632 15 : if( FD_UNLIKELY( read<=0 ) ) return (int)read;
633 :
634 9 : http->response_len += (ulong)read;
635 :
636 9 : int minor_version;
637 9 : int status;
638 9 : const char * message;
639 9 : ulong message_len;
640 9 : struct phr_header headers[ 128UL ];
641 9 : ulong header_cnt = 128UL;
642 9 : int parsed = phr_parse_response( http->response,
643 9 : http->response_len,
644 9 : &minor_version,
645 9 : &status,
646 9 : &message,
647 9 : &message_len,
648 9 : headers,
649 9 : &header_cnt,
650 9 : http->response_len - (ulong)read );
651 9 : if( FD_UNLIKELY( parsed==-1 ) ) {
652 0 : FD_LOG_WARNING(( "malformed response headers from " FD_IP4_ADDR_FMT ":%hu",
653 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
654 0 : fd_sshttp_cancel( http );
655 0 : return FD_SSHTTP_ADVANCE_ERROR;
656 9 : } else if( parsed==-2 ) {
657 6 : return FD_SSHTTP_ADVANCE_AGAIN;
658 6 : }
659 :
660 3 : int is_redirect = (status==301) | (status==302) | (status==303) | (status==307) | (status==308);
661 3 : if( FD_UNLIKELY( is_redirect ) ) {
662 0 : return follow_redirect( http, headers, header_cnt, now );
663 0 : }
664 :
665 3 : if( FD_UNLIKELY( status!=200 ) ) {
666 0 : FD_LOG_WARNING(( "unexpected response status %d %.*s from " FD_IP4_ADDR_FMT ":%hu", status, (int)message_len, message,
667 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
668 0 : fd_sshttp_cancel( http );
669 0 : return FD_SSHTTP_ADVANCE_ERROR;
670 0 : }
671 :
672 3 : http->content_read = 0UL;
673 3 : http->content_len = ULONG_MAX;
674 3 : for( ulong i=0UL; i<header_cnt; i++ ) {
675 3 : if( FD_LIKELY( headers[i].name_len!=14UL ) ) continue;
676 3 : if( FD_LIKELY( strncasecmp( headers[i].name, "content-length", 14UL ) ) ) continue;
677 :
678 3 : ulong val = 0UL;
679 3 : if( FD_UNLIKELY( fd_http_parse_content_len( headers[i].value, (ulong)headers[i].value_len, &val ) || val==0UL ) ) {
680 0 : FD_LOG_WARNING(( "invalid content-length in response from " FD_IP4_ADDR_FMT ":%hu", FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
681 0 : fd_sshttp_cancel( http );
682 0 : return FD_SSHTTP_ADVANCE_ERROR;
683 0 : }
684 3 : http->content_len = val;
685 3 : break;
686 3 : }
687 :
688 3 : if( FD_UNLIKELY( http->content_len==ULONG_MAX ) ) {
689 0 : FD_LOG_WARNING(( "no content-length header in response from " FD_IP4_ADDR_FMT ":%hu",
690 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
691 0 : fd_sshttp_cancel( http );
692 0 : return FD_SSHTTP_ADVANCE_ERROR;
693 0 : }
694 :
695 3 : http->state = FD_SSHTTP_STATE_DL;
696 3 : if( FD_UNLIKELY( (ulong)parsed<http->response_len ) ) {
697 : /* Body bytes past the caller's buffer are kept in response, with
698 : response_len repurposed as the residual length, drained by
699 : read_body before it reads the socket again. */
700 3 : ulong leftover = fd_ulong_min( http->response_len - (ulong)parsed, http->content_len );
701 3 : ulong copy_len = fd_ulong_min( leftover, *data_len );
702 3 : fd_memcpy( data, http->response+parsed, copy_len );
703 3 : memmove( http->response, http->response+(ulong)parsed+copy_len, leftover-copy_len );
704 3 : http->response_len = leftover-copy_len;
705 3 : http->content_read += copy_len;
706 3 : *data_len = copy_len;
707 3 : return FD_SSHTTP_ADVANCE_DATA;
708 3 : } else {
709 0 : FD_TEST( http->response_len==(ulong)parsed );
710 0 : http->response_len = 0UL;
711 0 : return FD_SSHTTP_ADVANCE_AGAIN;
712 0 : }
713 3 : }
714 :
715 : static int
716 : read_body( fd_sshttp_t * http,
717 : ulong * data_len,
718 : uchar * data,
719 3 : long now ) {
720 3 : if( FD_UNLIKELY( http->content_read>=http->content_len ) ) {
721 0 : if( FD_UNLIKELY( http->is_https ) ) {
722 0 : http->state = FD_SSHTTP_STATE_DONE;
723 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
724 0 : return FD_SSHTTP_ADVANCE_AGAIN;
725 0 : } else {
726 0 : fd_sshttp_cancel( http );
727 0 : http->state = FD_SSHTTP_STATE_INIT;
728 0 : return FD_SSHTTP_ADVANCE_DONE;
729 0 : }
730 0 : }
731 :
732 3 : FD_TEST( http->content_read<http->content_len );
733 :
734 3 : if( FD_UNLIKELY( http->response_len ) ) { /* residual body bytes from read_response */
735 0 : ulong copy_len = fd_ulong_min( http->response_len, *data_len );
736 0 : fd_memcpy( data, http->response, copy_len );
737 0 : memmove( http->response, http->response+copy_len, http->response_len-copy_len );
738 0 : http->response_len -= copy_len;
739 0 : http->content_read += copy_len;
740 0 : *data_len = copy_len;
741 0 : return FD_SSHTTP_ADVANCE_DATA;
742 0 : }
743 :
744 3 : long read = http_recv( http, data, fd_ulong_min( *data_len, http->content_len-http->content_read ) );
745 3 : if( FD_UNLIKELY( read<=0 ) ) return (int)read;
746 :
747 0 : *data_len = (ulong)read;
748 0 : http->content_read += (ulong)read;
749 :
750 0 : return FD_SSHTTP_ADVANCE_DATA;
751 3 : }
752 :
753 : char const *
754 0 : fd_sshttp_snapshot_name( fd_sshttp_t const * http ) {
755 0 : return http->snapshot_name;
756 0 : }
757 :
758 : ulong
759 0 : fd_sshttp_content_len( fd_sshttp_t const * http ) {
760 0 : return http->content_len;
761 0 : }
762 :
763 : ulong
764 0 : fd_sshttp_resolved_slot( fd_sshttp_t const * http ) {
765 0 : return http->resolved_slot;
766 0 : }
767 :
768 : uchar const *
769 0 : fd_sshttp_resolved_hash( fd_sshttp_t const * http ) {
770 0 : return http->resolved_hash;
771 0 : }
772 :
773 : int
774 : fd_sshttp_advance( fd_sshttp_t * http,
775 : ulong * data_len,
776 : uchar * data,
777 : int * downloading,
778 21 : long now ) {
779 21 : *downloading = 0;
780 21 : switch( http->state ) {
781 0 : case FD_SSHTTP_STATE_INIT: return FD_SSHTTP_ADVANCE_AGAIN;
782 0 : case FD_SSHTTP_STATE_CONNECT:
783 0 : return http_connect_tls( http, now );
784 0 : case FD_SSHTTP_STATE_REDIRECT:
785 0 : return setup_redirect_tls( http, now );
786 0 : case FD_SSHTTP_STATE_REQ: return send_request( http, now );
787 18 : case FD_SSHTTP_STATE_RESP: return read_response( http, data_len, data, now );
788 3 : case FD_SSHTTP_STATE_DL: *downloading = 1; return read_body( http, data_len, data, now );
789 0 : case FD_SSHTTP_STATE_DONE:
790 0 : fd_sshttp_cancel( http );
791 0 : http->state = FD_SSHTTP_STATE_INIT;
792 0 : return FD_SSHTTP_ADVANCE_DONE;
793 0 : default: return FD_SSHTTP_ADVANCE_ERROR;
794 21 : }
795 21 : }
|