LCOV - code coverage report
Current view: top level - discof/restore/utils - fd_sshttp.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 558 0.0 %
Date: 2026-06-29 05:51:35 Functions: 0 21 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "fd_sshttp_private.h"
       3             : #include "fd_ssarchive.h"
       4             : 
       5             : #include "../../../waltz/http/picohttpparser.h"
       6             : #include "../../../waltz/openssl/fd_openssl_tile.h"
       7             : #include "../../../waltz/openssl/fd_openssl.h"
       8             : #include "../../../util/log/fd_log.h"
       9             : #include "../../../waltz/http/fd_http.h"
      10             : 
      11             : #include <unistd.h>
      12             : #include <errno.h>
      13             : #include <poll.h>
      14             : #include <stdlib.h>
      15             : 
      16             : #include <sys/socket.h>
      17             : #include <netinet/in.h>
      18             : 
      19             : _Bool fd_sshttp_fuzz = 0;
      20             : 
      21             : FD_FN_CONST ulong
      22           0 : fd_sshttp_align( void ) {
      23           0 :   return alignof(fd_sshttp_t);
      24           0 : }
      25             : 
      26             : FD_FN_CONST ulong
      27           0 : fd_sshttp_footprint( void ) {
      28           0 :   ulong l;
      29           0 :   l = FD_LAYOUT_INIT;
      30           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
      31           0 :   return FD_LAYOUT_FINI( l, fd_sshttp_align() );
      32           0 : }
      33             : 
      34             : void *
      35           0 : fd_sshttp_new( void * shmem ) {
      36           0 :   if( FD_UNLIKELY( !shmem ) ) {
      37           0 :     FD_LOG_WARNING(( "NULL shmem" ));
      38           0 :     return NULL;
      39           0 :   }
      40             : 
      41           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_sshttp_align() ) ) ) {
      42           0 :     FD_LOG_WARNING(( "unaligned shmem" ));
      43           0 :     return NULL;
      44           0 :   }
      45             : 
      46           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
      47           0 :   fd_sshttp_t * sshttp = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
      48             : 
      49           0 :   sshttp->state = FD_SSHTTP_STATE_INIT;
      50           0 :   sshttp->sockfd = -1;
      51           0 :   sshttp->content_len = 0UL;
      52           0 :   fd_cstr_fini( sshttp->snapshot_name );
      53             : 
      54           0 : #if FD_HAS_OPENSSL
      55           0 :   sshttp->ssl     = NULL;
      56           0 :   sshttp->ssl_ctx = NULL;
      57             : 
      58           0 :   if( !fd_sshttp_fuzz ) {
      59           0 :     SSL_CTX * ssl_ctx = SSL_CTX_new( TLS_client_method() );
      60           0 :     if( FD_UNLIKELY( !ssl_ctx ) ) {
      61           0 :       FD_LOG_ERR(( "SSL_CTX_new failed" ));
      62           0 :     }
      63             : 
      64           0 :     if( FD_UNLIKELY( !SSL_CTX_set_min_proto_version( ssl_ctx, TLS1_3_VERSION ) ) ) {
      65           0 :       FD_LOG_ERR(( "SSL_CTX_set_min_proto_version(ssl_ctx,TLS1_3_VERSION) failed" ));
      66           0 :     }
      67             : 
      68             :     /* transfering ownership of ssl_ctx by assignment */
      69           0 :     sshttp->ssl_ctx = ssl_ctx;
      70             : 
      71           0 :     fd_ossl_load_certs( sshttp->ssl_ctx );
      72           0 :   }
      73           0 : #endif
      74             : 
      75           0 :   FD_COMPILER_MFENCE();
      76           0 :   sshttp->magic = FD_SSHTTP_MAGIC;
      77           0 :   FD_COMPILER_MFENCE();
      78             : 
      79           0 :   return (void *)sshttp;
      80           0 : }
      81             : 
      82             : fd_sshttp_t *
      83           0 : fd_sshttp_join( void * shhttp ) {
      84           0 :   if( FD_UNLIKELY( !shhttp ) ) {
      85           0 :     FD_LOG_WARNING(( "NULL shhttp" ));
      86           0 :     return NULL;
      87           0 :   }
      88             : 
      89           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shhttp, fd_sshttp_align() ) ) ) {
      90           0 :     FD_LOG_WARNING(( "misaligned shhttp" ));
      91           0 :     return NULL;
      92           0 :   }
      93             : 
      94           0 :   fd_sshttp_t * sshttp = (fd_sshttp_t *)shhttp;
      95             : 
      96           0 :   if( FD_UNLIKELY( sshttp->magic!=FD_SSHTTP_MAGIC ) ) {
      97           0 :     FD_LOG_WARNING(( "bad magic" ));
      98           0 :     return NULL;
      99           0 :   }
     100             : 
     101           0 :   return sshttp;
     102           0 : }
     103             : 
     104             : #if FD_HAS_OPENSSL
     105             : static int
     106           0 : http_init_ssl( fd_sshttp_t * http ) {
     107           0 :   FD_TEST( http->hostname );
     108           0 :   FD_TEST( http->ssl_ctx );
     109             : 
     110           0 :   http->ssl = SSL_new( http->ssl_ctx );
     111           0 :   if( FD_UNLIKELY( !http->ssl ) ) {
     112           0 :     FD_LOG_WARNING(( "SSL_new failed for %s", http->hostname ));
     113           0 :     return -1;
     114           0 :   }
     115             : 
     116           0 :   static uchar const alpn_protos[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '1' };
     117           0 :   int alpn_res = SSL_set_alpn_protos( http->ssl, alpn_protos, sizeof(alpn_protos) );
     118           0 :   if( FD_UNLIKELY( alpn_res!=0 ) ) {
     119           0 :     FD_LOG_WARNING(( "SSL_set_alpn_protos failed (%d) for %s", alpn_res, http->hostname ));
     120           0 :     SSL_free( http->ssl ); http->ssl = NULL;
     121           0 :     return -1;
     122           0 :   }
     123             : 
     124             :   /* set SNI and hostname verification */
     125           0 :   long sni_res = SSL_set_tlsext_host_name( http->ssl, http->hostname );
     126           0 :   if( FD_UNLIKELY( !sni_res ) ) {
     127           0 :     FD_LOG_WARNING(( "SSL_set_tlsext_host_name failed (%ld) for %s", sni_res, http->hostname ));
     128           0 :     SSL_free( http->ssl ); http->ssl = NULL;
     129           0 :     return -1;
     130           0 :   }
     131           0 :   int set1_host_res = SSL_set1_host( http->ssl, http->hostname );
     132           0 :   if( FD_UNLIKELY( !set1_host_res ) ) {
     133           0 :     FD_LOG_WARNING(( "SSL_set1_host failed (%d) for %s", set1_host_res, http->hostname ));
     134           0 :     SSL_free( http->ssl ); http->ssl = NULL;
     135           0 :     return -1;
     136           0 :   }
     137           0 :   return 0;
     138           0 : }
     139             : #endif
     140             : 
     141             : int
     142             : fd_sshttp_init( fd_sshttp_t * http,
     143             :                 fd_ip4_port_t addr,
     144             :                 char const *  hostname,
     145             :                 int           is_https,
     146             :                 char const *  path,
     147             :                 ulong         path_len,
     148             :                 ulong         hops,
     149           0 :                 long          now ) {
     150           0 :   FD_TEST( http->state==FD_SSHTTP_STATE_INIT );
     151             : 
     152           0 :   http->hostname = hostname;
     153           0 :   http->is_https = is_https;
     154             : 
     155           0 :   if( FD_LIKELY( is_https ) ) {
     156           0 : #if FD_HAS_OPENSSL
     157           0 :     if( FD_UNLIKELY( http_init_ssl( http ) ) ) return -1;
     158             : #else
     159             :   FD_LOG_ERR(( "cannot make HTTPS connection without OpenSSL" ));
     160             : #endif
     161           0 :   }
     162             : 
     163           0 :   if( hops!=ULONG_MAX ) http->hops = hops;
     164           0 :   http->request_sent = 0UL;
     165           0 :   int fmt_ok;
     166           0 :   if( FD_LIKELY( is_https ) ) {
     167           0 :     fmt_ok = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
     168           0 :       "GET %.*s HTTP/1.1\r\n"
     169           0 :       "User-Agent: Firedancer\r\n"
     170           0 :       "Accept: */*\r\n"
     171           0 :       "Accept-Encoding: identity\r\n"
     172           0 :       "Host: %s\r\n\r\n",
     173           0 :       (int)path_len, path, hostname );
     174           0 :   } else {
     175           0 :     fmt_ok = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
     176           0 :       "GET %.*s HTTP/1.1\r\n"
     177           0 :       "User-Agent: Firedancer\r\n"
     178           0 :       "Accept: */*\r\n"
     179           0 :       "Accept-Encoding: identity\r\n"
     180           0 :       "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
     181           0 :       (int)path_len, path, FD_IP4_ADDR_FMT_ARGS( addr.addr ) );
     182           0 :   }
     183           0 :   if( FD_UNLIKELY( !fmt_ok ) ) {
     184           0 :     FD_LOG_WARNING(( "HTTP request too long for %.*s", (int)path_len, path ));
     185           0 : #if FD_HAS_OPENSSL
     186           0 :     if( FD_LIKELY( http->ssl ) ) { SSL_free( http->ssl ); http->ssl = NULL; }
     187           0 : #endif
     188           0 :     return -1;
     189           0 :   }
     190             : 
     191           0 :   http->response_len = 0UL;
     192           0 :   http->content_len  = 0UL;
     193           0 :   http->content_read = 0UL;
     194           0 :   http->empty_recvs  = 0UL;
     195             : 
     196           0 :   http->addr   = addr;
     197           0 :   http->sockfd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0 );
     198           0 :   if( FD_UNLIKELY( -1==http->sockfd ) ) {
     199           0 :     FD_LOG_WARNING(( "socket() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     200           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     201           0 : #if FD_HAS_OPENSSL
     202           0 :     if( FD_LIKELY( http->ssl ) ) { SSL_free( http->ssl ); http->ssl = NULL; }
     203           0 : #endif
     204           0 :     return -1;
     205           0 :   }
     206             : 
     207           0 :   struct sockaddr_in addr_in = {
     208           0 :     .sin_family = AF_INET,
     209           0 :     .sin_port   = addr.port,
     210           0 :     .sin_addr   = { .s_addr = addr.addr }
     211           0 :   };
     212             : 
     213           0 :   if( FD_LIKELY( -1==connect( http->sockfd, fd_type_pun_const( &addr_in ), sizeof(addr_in) ) ) ) {
     214           0 :     if( FD_UNLIKELY( errno!=EINPROGRESS ) ) {
     215           0 :       FD_LOG_WARNING(( "connect() failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     216           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     217           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 ),
     218           0 :                                                                   FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     219           0 :       http->sockfd = -1;
     220           0 : #if FD_HAS_OPENSSL
     221           0 :       if( FD_LIKELY( http->ssl ) ) { SSL_free( http->ssl ); http->ssl = NULL; }
     222           0 : #endif
     223           0 :       return -1;
     224           0 :     }
     225           0 :   }
     226             : 
     227           0 :   if( FD_LIKELY( is_https ) ) {
     228           0 : #if FD_HAS_OPENSSL
     229           0 :     if( FD_UNLIKELY( !fd_openssl_ssl_set_fd( http->ssl, http->sockfd ) ) ) {
     230           0 :       FD_LOG_WARNING(( "fd_openssl_ssl_set_fd failed for " FD_IP4_ADDR_FMT ":%hu",
     231           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     232           0 :       if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) {
     233           0 :         FD_LOG_ERR(( "close() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     234           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     235           0 :       }
     236           0 :       http->sockfd = -1;
     237           0 :       SSL_free( http->ssl ); http->ssl = NULL;
     238           0 :       return -1;
     239           0 :     }
     240           0 : #endif
     241           0 :     http->state    = FD_SSHTTP_STATE_CONNECT;
     242           0 :     http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
     243           0 :   } else {
     244           0 :     http->state    = FD_SSHTTP_STATE_REQ;
     245           0 :     http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
     246           0 :   }
     247             : 
     248           0 :   return 0;
     249           0 : }
     250             : 
     251             : #if FD_HAS_OPENSSL
     252             : static int
     253             : http_connect_ssl( fd_sshttp_t * http,
     254           0 :                   long          now ) {
     255           0 :   if( FD_UNLIKELY( now>http->deadline ) ) {
     256           0 :     FD_LOG_WARNING(( "deadline exceeded during connect to " FD_IP4_ADDR_FMT ":%hu",
     257           0 :                       FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     258           0 :     fd_sshttp_cancel( http );
     259           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     260           0 :   }
     261             : 
     262           0 :   FD_TEST( http->ssl );
     263           0 :   int ssl_err = SSL_connect( http->ssl );
     264           0 :   if( FD_UNLIKELY( ssl_err!=1 ) ) {
     265           0 :     int ssl_err_code = SSL_get_error( http->ssl, ssl_err );
     266           0 :     if( FD_UNLIKELY( ssl_err_code!=SSL_ERROR_WANT_READ && ssl_err_code!=SSL_ERROR_WANT_WRITE ) ) {
     267           0 :       FD_LOG_WARNING(( "SSL_connect failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", ssl_err_code, fd_openssl_ssl_strerror( ssl_err_code ),
     268           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     269           0 :       fd_sshttp_cancel( http );
     270           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     271           0 :     }
     272             :     /* in progress */
     273           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     274           0 :   }
     275             : 
     276           0 :   http->state    = FD_SSHTTP_STATE_REQ;
     277           0 :   http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
     278           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     279           0 : }
     280             : 
     281             : static int
     282             : http_shutdown_ssl( fd_sshttp_t * http,
     283           0 :                    long          now ) {
     284           0 :   if( FD_UNLIKELY( now>http->deadline ) ) {
     285           0 :     FD_LOG_WARNING(( "deadline exceeded during shutdown for " FD_IP4_ADDR_FMT ":%hu",
     286           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     287           0 :     fd_sshttp_cancel( http );
     288           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     289           0 :   }
     290             : 
     291           0 :   int res = SSL_shutdown( http->ssl );
     292           0 :   if( FD_LIKELY( res<=0 ) ) {
     293           0 :     int ssl_err_code = SSL_get_error( http->ssl, res );
     294           0 :     if( FD_UNLIKELY( ssl_err_code!=SSL_ERROR_WANT_READ && ssl_err_code!=SSL_ERROR_WANT_WRITE && res!=0 ) ) {
     295           0 :       FD_LOG_WARNING(( "SSL_shutdown failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", ssl_err_code, fd_openssl_ssl_strerror( ssl_err_code ),
     296           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     297           0 :       fd_sshttp_cancel( http );
     298           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     299           0 :     }
     300             : 
     301           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     302           0 :   }
     303             : 
     304           0 :   http->state = http->next_state;
     305           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     306           0 : }
     307             : 
     308             : static long
     309             : http_recv_ssl( fd_sshttp_t * http,
     310             :                void *        buf,
     311           0 :                ulong         bufsz ) {
     312           0 :   int read_res = SSL_read( http->ssl, buf, (int)bufsz );
     313           0 :   if( FD_UNLIKELY( read_res<=0 ) ) {
     314           0 :     int ssl_err = SSL_get_error( http->ssl, read_res );
     315             : 
     316           0 :     if( FD_UNLIKELY( ssl_err!=SSL_ERROR_WANT_READ && ssl_err!=SSL_ERROR_WANT_WRITE ) ) {
     317           0 :       FD_LOG_WARNING(( "SSL_read failed (%d-%s) from " FD_IP4_ADDR_FMT ":%hu", ssl_err, fd_openssl_ssl_strerror( ssl_err ),
     318           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     319           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     320           0 :     }
     321             : 
     322           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     323           0 :   }
     324             : 
     325           0 :   return (long)read_res;
     326           0 : }
     327             : 
     328             : static long
     329             : http_send_ssl( fd_sshttp_t * http,
     330             :                void *        buf,
     331           0 :                ulong         bufsz ) {
     332           0 :   int write_res = SSL_write( http->ssl, buf, (int)bufsz );
     333           0 :   if( FD_UNLIKELY( write_res<=0 ) ) {
     334           0 :     int ssl_err = SSL_get_error( http->ssl, write_res );
     335             : 
     336           0 :     if( FD_UNLIKELY( ssl_err!=SSL_ERROR_WANT_READ && ssl_err!=SSL_ERROR_WANT_WRITE ) ) {
     337           0 :       FD_LOG_WARNING(( "SSL_write failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", ssl_err, fd_openssl_ssl_strerror( ssl_err ),
     338           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     339           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     340           0 :     }
     341             : 
     342           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     343           0 :   }
     344             : 
     345           0 :   return (long)write_res;
     346           0 : }
     347             : 
     348             : static int
     349             : setup_redirect( fd_sshttp_t * http,
     350           0 :               long          now ) {
     351           0 :   fd_sshttp_cancel( http );
     352           0 :   if( FD_UNLIKELY( fd_sshttp_init( http, http->addr, http->hostname, http->is_https, http->location, http->location_len, ULONG_MAX, now ) ) ) {
     353           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     354           0 :   }
     355           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     356           0 : }
     357             : 
     358             : #endif
     359             : 
     360             : void
     361           0 : fd_sshttp_cancel( fd_sshttp_t * http ) {
     362           0 :   if( FD_LIKELY( http->state!=FD_SSHTTP_STATE_INIT && -1!=http->sockfd ) ) {
     363           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 ),
     364           0 :                                                                 FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     365           0 :     http->sockfd = -1;
     366           0 :   }
     367           0 :   http->state = FD_SSHTTP_STATE_INIT;
     368             : 
     369           0 : #if FD_HAS_OPENSSL
     370           0 :   if( FD_LIKELY( http->ssl ) ) {
     371           0 :     SSL_free( http->ssl );
     372           0 :     http->ssl = NULL;
     373           0 :   }
     374           0 : #endif
     375           0 : }
     376             : 
     377             : static long
     378             : http_send( fd_sshttp_t * http,
     379             :            void *        buf,
     380           0 :            ulong         bufsz ) {
     381           0 : #if FD_HAS_OPENSSL
     382           0 :   if( FD_LIKELY( http->is_https ) ) return http_send_ssl( http, buf, bufsz );
     383           0 : #endif
     384             : 
     385           0 :   long sent = sendto( http->sockfd, buf, bufsz, MSG_NOSIGNAL, NULL, 0 );
     386           0 :   if( FD_UNLIKELY( -1==sent && errno==EAGAIN ) ) return FD_SSHTTP_ADVANCE_AGAIN;
     387           0 :   else if( FD_UNLIKELY( -1==sent ) ) {
     388           0 :     FD_LOG_WARNING(( "sendto() failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     389           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     390           0 :     fd_sshttp_cancel( http );
     391           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     392           0 :   }
     393             : 
     394           0 :   return sent;
     395           0 : }
     396             : 
     397             : static long
     398             : http_recv( fd_sshttp_t * http,
     399             :            void *        buf,
     400           0 :            ulong         bufsz ) {
     401           0 : #if FD_HAS_OPENSSL
     402           0 :   if( FD_LIKELY( http->is_https ) ) return http_recv_ssl( http, buf, bufsz );
     403           0 : #endif
     404             : 
     405           0 :   long read = recvfrom( http->sockfd, buf, bufsz, 0, NULL, NULL );
     406           0 :   if( FD_UNLIKELY( -1==read && errno==EAGAIN ) ) {
     407           0 :     if( FD_UNLIKELY( ++http->empty_recvs>8UL && !fd_sshttp_fuzz ) ) {
     408             :       /* If we have gone several iterations without having any data to
     409             :          read, sleep the thread for up to one millisecond, or until
     410             :          the socket is readable again, whichever comes first. */
     411           0 :       struct pollfd pfd = {
     412           0 :         .fd = http->sockfd,
     413           0 :         .events = POLLIN,
     414           0 :       };
     415           0 :       if( FD_UNLIKELY( -1==fd_syscall_poll( &pfd, 1 /*fds*/, 1 /*ms*/ ) ) ) {
     416           0 :         if( FD_UNLIKELY( errno!=EINTR ) ) {
     417           0 :           FD_LOG_WARNING(( "fd_syscall_poll() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     418           0 :                            FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     419           0 :           fd_sshttp_cancel( http );
     420           0 :           return FD_SSHTTP_ADVANCE_ERROR;
     421           0 :         }
     422           0 :       }
     423           0 :     }
     424           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     425           0 :   } else if( FD_UNLIKELY( -1==read ) ) {
     426           0 :     FD_LOG_WARNING(( "recvfrom() failed (%d-%s) from " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     427           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     428           0 :     fd_sshttp_cancel( http );
     429           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     430           0 :   }
     431           0 :   http->empty_recvs = 0UL;
     432             : 
     433           0 :   return read;
     434           0 : }
     435             : 
     436             : static int
     437             : send_request( fd_sshttp_t * http,
     438           0 :               long          now ) {
     439           0 :   if( FD_UNLIKELY( now>http->deadline ) ) {
     440           0 :     FD_LOG_WARNING(( "timeout sending request to " FD_IP4_ADDR_FMT ":%hu",
     441           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     442           0 :     fd_sshttp_cancel( http );
     443           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     444           0 :   }
     445             : 
     446           0 :   long sent = http_send( http, http->request+http->request_sent, http->request_len-http->request_sent );
     447           0 :   if( FD_UNLIKELY( sent<=0 ) ) return (int)sent;
     448             : 
     449           0 :   http->request_sent += (ulong)sent;
     450           0 :   if( FD_UNLIKELY( http->request_sent==http->request_len ) ) {
     451           0 :     http->state        = FD_SSHTTP_STATE_RESP;
     452           0 :     http->response_len = 0UL;
     453           0 :     http->deadline     = now + FD_SSHTTP_DEADLINE_NANOS;
     454           0 :   }
     455             : 
     456           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     457           0 : }
     458             : 
     459             : static int
     460             : follow_redirect( fd_sshttp_t *        http,
     461             :                   struct phr_header * headers,
     462             :                   ulong               header_cnt,
     463           0 :                   long                now ) {
     464           0 :   if( FD_UNLIKELY( !http->hops ) ) {
     465           0 :     FD_LOG_WARNING(( "too many redirects (remaining %lu) from " FD_IP4_ADDR_FMT ":%hu", http->hops,
     466           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     467           0 :     fd_sshttp_cancel( http );
     468           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     469           0 :   }
     470             :   /* The check above guarantees hops>0. */
     471           0 :   http->hops--;
     472             : 
     473           0 :   ulong        location_len = 0UL;
     474           0 :   char const * location     = NULL;
     475             : 
     476           0 :   for( ulong i=0UL; i<header_cnt; i++ ) {
     477           0 :     if( FD_UNLIKELY( headers[ i ].name_len == 8 && !strncasecmp( headers[ i ].name, "location", headers[ i ].name_len ) ) ) {
     478           0 :       if( FD_UNLIKELY( !headers [ i ].value_len || headers[ i ].value[ 0 ]!='/' ) ) {
     479           0 :         FD_LOG_WARNING(( "invalid location header `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)headers[ i ].value_len, headers[ i ].value,
     480           0 :                          FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     481           0 :         fd_sshttp_cancel( http );
     482           0 :         return FD_SSHTTP_ADVANCE_ERROR;
     483           0 :       }
     484             : 
     485           0 :       location_len = headers[ i ].value_len;
     486           0 :       location     = headers[ i ].value;
     487             : 
     488           0 :       if( FD_UNLIKELY( location_len>=PATH_MAX-1UL ) ) {
     489           0 :         FD_LOG_WARNING(( "location header too long `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)location_len, location,
     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             : 
     495           0 :       char snapshot_name[ PATH_MAX ];
     496           0 :       fd_memcpy( snapshot_name, location+1UL, location_len-1UL );
     497           0 :       snapshot_name[ location_len-1UL ] = '\0';
     498             : 
     499           0 :       int is_zstd;
     500           0 :       ulong full_entry_slot, incremental_entry_slot;
     501           0 :       uchar decoded_hash[ FD_HASH_FOOTPRINT ];
     502           0 :       int err = fd_ssarchive_parse_filename( snapshot_name, &full_entry_slot, &incremental_entry_slot, decoded_hash, &is_zstd );
     503             : 
     504           0 :       if( FD_UNLIKELY( err || !is_zstd ) ) {
     505           0 :         FD_LOG_WARNING(( "unrecognized snapshot file `%s` in redirect location header from " FD_IP4_ADDR_FMT ":%hu", snapshot_name,
     506           0 :                          FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     507           0 :         fd_sshttp_cancel( http );
     508           0 :         return FD_SSHTTP_ADVANCE_ERROR;
     509           0 :       }
     510             : 
     511           0 :       char encoded_hash[ FD_BASE58_ENCODED_32_SZ ];
     512           0 :       fd_base58_encode_32( decoded_hash, NULL, encoded_hash );
     513             : 
     514           0 :       if( FD_LIKELY( incremental_entry_slot!=ULONG_MAX ) ) {
     515           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 ) );
     516           0 :       } else {
     517           0 :         FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "snapshot-%lu-%s.tar.zst", full_entry_slot, encoded_hash ) );
     518           0 :       }
     519           0 :       break;
     520           0 :     }
     521           0 :   }
     522             : 
     523           0 :   if( FD_UNLIKELY( !location_len ) ) {
     524           0 :     FD_LOG_WARNING(( "no location header in redirect response from " FD_IP4_ADDR_FMT ":%hu",
     525           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     526           0 :     fd_sshttp_cancel( http );
     527           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     528           0 :   }
     529             : 
     530             :   /* Pre-validate that the redirect request will fit in the request
     531             :      buffer.  The request is rebuilt from scratch by fd_sshttp_init
     532             :      during the redirect, but the format must match so that a path
     533             :      accepted here will not overflow in fd_sshttp_init. */
     534           0 :   int pre_check;
     535           0 :   if( FD_LIKELY( http->is_https ) ) {
     536           0 :     pre_check = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
     537           0 :       "GET %.*s HTTP/1.1\r\n"
     538           0 :       "User-Agent: Firedancer\r\n"
     539           0 :       "Accept: */*\r\n"
     540           0 :       "Accept-Encoding: identity\r\n"
     541           0 :       "Host: %s\r\n\r\n",
     542           0 :       (int)location_len, location, http->hostname );
     543           0 :   } else {
     544           0 :     pre_check = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
     545           0 :       "GET %.*s HTTP/1.1\r\n"
     546           0 :       "User-Agent: Firedancer\r\n"
     547           0 :       "Accept: */*\r\n"
     548           0 :       "Accept-Encoding: identity\r\n"
     549           0 :       "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
     550           0 :       (int)location_len, location, FD_IP4_ADDR_FMT_ARGS( http->addr.addr ) );
     551           0 :   }
     552           0 :   if( FD_UNLIKELY( !pre_check ) ) {
     553           0 :     FD_LOG_WARNING(( "redirect request too long `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)location_len, location,
     554           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     555           0 :     fd_sshttp_cancel( http );
     556           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     557           0 :   }
     558             : 
     559           0 :   FD_LOG_NOTICE(( "following redirect to %s://" FD_IP4_ADDR_FMT ":%hu%.*s", http->is_https ? "https" : "http",
     560           0 :                   FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ),
     561           0 :                   (int)location_len, location ));
     562             : 
     563           0 :   if( FD_UNLIKELY( http->is_https ) ) {
     564           0 :     http->next_state   = FD_SSHTTP_STATE_REDIRECT;
     565           0 :     http->state        = FD_SSHTTP_STATE_SHUTTING_DOWN;
     566           0 :     http->location_len = location_len;
     567           0 :     FD_TEST( location_len<PATH_MAX-1UL );
     568           0 :     fd_memcpy( http->location, location, location_len );
     569           0 :     http->location[ location_len ] = '\0';
     570           0 :   } else {
     571           0 :     if( FD_LIKELY( !fd_sshttp_fuzz ) ) {
     572           0 :       fd_sshttp_cancel( http );
     573           0 :       if( FD_UNLIKELY( fd_sshttp_init( http, http->addr, http->hostname, http->is_https, location, location_len, ULONG_MAX, now ) ) ) {
     574           0 :         return FD_SSHTTP_ADVANCE_ERROR;
     575           0 :       }
     576           0 :     } else {
     577           0 :       http->state = FD_SSHTTP_STATE_RESP;
     578           0 :       http->response_len = 0UL;
     579           0 :     }
     580           0 :   }
     581             : 
     582           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     583           0 : }
     584             : 
     585             : static int
     586             : read_response( fd_sshttp_t * http,
     587             :                ulong *       data_len,
     588             :                uchar *       data,
     589           0 :                long          now ) {
     590           0 :   if( FD_UNLIKELY( now>http->deadline ) ) {
     591           0 :     FD_LOG_WARNING(( "timeout reading response from " FD_IP4_ADDR_FMT ":%hu",
     592           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     593           0 :     fd_sshttp_cancel( http );
     594           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     595           0 :   }
     596             : 
     597           0 :   long read = http_recv( http, http->response+http->response_len, sizeof(http->response)-http->response_len );
     598           0 :   if( FD_UNLIKELY( read<=0 ) ) return (int)read;
     599             : 
     600           0 :   http->response_len += (ulong)read;
     601             : 
     602           0 :   int               minor_version;
     603           0 :   int               status;
     604           0 :   const char *      message;
     605           0 :   ulong             message_len;
     606           0 :   struct phr_header headers[ 128UL ];
     607           0 :   ulong             header_cnt = 128UL;
     608           0 :   int parsed = phr_parse_response( http->response,
     609           0 :                                     http->response_len,
     610           0 :                                     &minor_version,
     611           0 :                                     &status,
     612           0 :                                     &message,
     613           0 :                                     &message_len,
     614           0 :                                     headers,
     615           0 :                                     &header_cnt,
     616           0 :                                     http->response_len - (ulong)read );
     617           0 :   if( FD_UNLIKELY( parsed==-1 ) ) {
     618           0 :     FD_LOG_WARNING(( "malformed response headers 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 :   } else if( parsed==-2 ) {
     623           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     624           0 :   }
     625             : 
     626           0 :   int is_redirect = (status==301) | (status==302) | (status==303) | (status==307) | (status==308);
     627           0 :   if( FD_UNLIKELY( is_redirect ) ) {
     628           0 :     return follow_redirect( http, headers, header_cnt, now );
     629           0 :   }
     630             : 
     631           0 :   if( FD_UNLIKELY( status!=200 ) ) {
     632           0 :     FD_LOG_WARNING(( "unexpected response status %d %.*s from " FD_IP4_ADDR_FMT ":%hu", status, (int)message_len, message,
     633           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     634           0 :     fd_sshttp_cancel( http );
     635           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     636           0 :   }
     637             : 
     638           0 :   http->content_read = 0UL;
     639           0 :   http->content_len = ULONG_MAX;
     640           0 :   for( ulong i=0UL; i<header_cnt; i++ ) {
     641           0 :     if( FD_LIKELY( headers[i].name_len!=14UL ) ) continue;
     642           0 :     if( FD_LIKELY( strncasecmp( headers[i].name, "content-length", 14UL ) ) ) continue;
     643             : 
     644           0 :     ulong val = 0UL;
     645           0 :     if( FD_UNLIKELY( fd_http_parse_content_len( headers[i].value, (ulong)headers[i].value_len, &val ) || val==0UL ) ) {
     646           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 ) ));
     647           0 :       fd_sshttp_cancel( http );
     648           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     649           0 :     }
     650           0 :     http->content_len = val;
     651           0 :     break;
     652           0 :   }
     653             : 
     654           0 :   if( FD_UNLIKELY( http->content_len==ULONG_MAX ) ) {
     655           0 :     FD_LOG_WARNING(( "no content-length header in response from " FD_IP4_ADDR_FMT ":%hu",
     656           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     657           0 :     fd_sshttp_cancel( http );
     658           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     659           0 :   }
     660             : 
     661           0 :   http->state = FD_SSHTTP_STATE_DL;
     662           0 :   if( FD_UNLIKELY( (ulong)parsed<http->response_len ) ) {
     663           0 :     ulong need_len = fd_ulong_min( http->response_len - (ulong)parsed, http->content_len );
     664           0 :     if( FD_UNLIKELY( *data_len<need_len ) ) {
     665           0 :       FD_LOG_WARNING(( "data buffer too small (data_len=%lu required=%lu response_len=%lu parsed=%lu)",
     666           0 :                        *data_len, need_len, http->response_len, (ulong)parsed ));
     667           0 :       fd_sshttp_cancel( http );
     668           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     669           0 :     }
     670           0 :     *data_len = need_len;
     671           0 :     fd_memcpy( data, http->response+parsed, *data_len );
     672           0 :     http->content_read += *data_len;
     673           0 :     return FD_SSHTTP_ADVANCE_DATA;
     674           0 :   } else {
     675           0 :     FD_TEST( http->response_len==(ulong)parsed );
     676           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     677           0 :   }
     678           0 : }
     679             : 
     680             : static int
     681             : read_body( fd_sshttp_t * http,
     682             :            ulong *       data_len,
     683             :            uchar *       data,
     684           0 :            long          now ) {
     685           0 :   if( FD_UNLIKELY( http->content_read>=http->content_len ) ) {
     686           0 :     if( FD_UNLIKELY( http->is_https ) ) {
     687           0 :       http->next_state = FD_SSHTTP_STATE_DONE;
     688           0 :       http->state = FD_SSHTTP_STATE_SHUTTING_DOWN;
     689           0 :       http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
     690           0 :       return FD_SSHTTP_ADVANCE_AGAIN;
     691           0 :     } else {
     692           0 :       fd_sshttp_cancel( http );
     693           0 :       http->state = FD_SSHTTP_STATE_INIT;
     694           0 :       return FD_SSHTTP_ADVANCE_DONE;
     695           0 :     }
     696           0 :   }
     697             : 
     698           0 :   FD_TEST( http->content_read<http->content_len );
     699           0 :   long read = http_recv( http, data, fd_ulong_min( *data_len, http->content_len-http->content_read ) );
     700           0 :   if( FD_UNLIKELY( read<=0 ) ) return (int)read;
     701             : 
     702           0 :   *data_len = (ulong)read;
     703           0 :   http->content_read += (ulong)read;
     704             : 
     705           0 :   return FD_SSHTTP_ADVANCE_DATA;
     706           0 : }
     707             : 
     708             : char const *
     709           0 : fd_sshttp_snapshot_name( fd_sshttp_t const * http ) {
     710           0 :   return http->snapshot_name;
     711           0 : }
     712             : 
     713             : ulong
     714           0 : fd_sshttp_content_len( fd_sshttp_t const * http ) {
     715           0 :   return http->content_len;
     716           0 : }
     717             : 
     718             : int
     719             : fd_sshttp_advance( fd_sshttp_t * http,
     720             :                    ulong *       data_len,
     721             :                    uchar *       data,
     722             :                    int *         downloading,
     723           0 :                    long          now ) {
     724           0 :   *downloading = 0;
     725           0 :   switch( http->state ) {
     726           0 :     case FD_SSHTTP_STATE_INIT:          return FD_SSHTTP_ADVANCE_AGAIN;
     727           0 : #if FD_HAS_OPENSSL
     728           0 :     case FD_SSHTTP_STATE_CONNECT:       return http_connect_ssl( http, now );
     729           0 :     case FD_SSHTTP_STATE_SHUTTING_DOWN: return http_shutdown_ssl( http, now );
     730           0 :     case FD_SSHTTP_STATE_REDIRECT:      return setup_redirect( http, now );
     731           0 : #endif
     732           0 :     case FD_SSHTTP_STATE_REQ:           return send_request( http, now );
     733           0 :     case FD_SSHTTP_STATE_RESP:          return read_response( http, data_len, data, now );
     734           0 :     case FD_SSHTTP_STATE_DL:            *downloading = 1; return read_body( http, data_len, data, now );
     735           0 :     case FD_SSHTTP_STATE_DONE:
     736           0 :       fd_sshttp_cancel( http );
     737           0 :       http->state = FD_SSHTTP_STATE_INIT;
     738           0 :       return FD_SSHTTP_ADVANCE_DONE;
     739           0 :     default:                            return FD_SSHTTP_ADVANCE_ERROR;
     740           0 :   }
     741           0 : }

Generated by: LCOV version 1.14