Skip to content

Commit 4a5957a

Browse files
snapshots: add https (#6354)
* snapshots: add https * deps: fix multi-threaded OpenSSL usage Fixes incorrect usage of the OpenSSL 'static' feature which is intended for statically linking glibc. The static feature breaks multi-threaded use of OpenSSL, so it should be disabled. Note that libssl.a still gets built, so OpenSSL itself is still statically linked. --------- Co-authored-by: Richard Patel <[email protected]>
1 parent 14f8bdb commit 4a5957a

31 files changed

+1311
-439
lines changed

book/api/metrics-generated.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,7 @@
900900
| <span class="metrics-name">snapct_&#8203;predicted_&#8203;slot</span> | gauge | The predicted slot from which replay starts after snapshot loading finishes. Might change if snapshot load is aborted and restarted |
901901
| <span class="metrics-name">snapct_&#8203;gossip_&#8203;fresh_&#8203;count</span> | gauge | Number of fresh gossip peers seen when collecting gossip peers. |
902902
| <span class="metrics-name">snapct_&#8203;gossip_&#8203;total_&#8203;count</span> | gauge | Number of total gossip peers seen when collecting gossip peers. |
903+
| <span class="metrics-name">snapct_&#8203;ssl_&#8203;alloc_&#8203;errors</span> | counter | Number of SSL allocation errors encountered. |
903904

904905
</div>
905906

@@ -910,6 +911,7 @@
910911
| Metric | Type | Description |
911912
|--------|------|-------------|
912913
| <span class="metrics-name">snapld_&#8203;state</span> | gauge | State of the tile. 0=IDLE, 1=PROCESSING, 2=FINISHING, 3=ERROR, 4=SHUTDOWN |
914+
| <span class="metrics-name">snapld_&#8203;ssl_&#8203;alloc_&#8203;errors</span> | counter | Number of SSL allocation errors encountered. |
913915

914916
</div>
915917

@@ -1200,6 +1202,5 @@
12001202
| <span class="metrics-name">tower_&#8203;hard_&#8203;forks_&#8203;seen</span> | counter | Number of hard forks we've seen (block ids with multiple candidate bank hashes) |
12011203
| <span class="metrics-name">tower_&#8203;hard_&#8203;forks_&#8203;pruned</span> | counter | Number of hard forks (candidate bank hashes) we've pruned |
12021204
| <span class="metrics-name">tower_&#8203;hard_&#8203;forks_&#8203;active</span> | gauge | Currently active hard forks |
1203-
| <span class="metrics-name">tower_&#8203;hard_&#8203;forks_&#8203;max_&#8203;width</span> | gauge | The max width of hard forks (block id with most candidate bank hashes) we've ever seen |
12041205

12051206
</div>

deps.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,10 +505,10 @@ install_openssl () {
505505

506506
echo "[+] Configuring OpenSSL"
507507
./config \
508-
-static \
509508
-fPIC \
510509
--prefix="$PREFIX" \
511510
--libdir=lib \
511+
threads \
512512
no-engine \
513513
no-static-engine \
514514
no-weak-ssl-ciphers \

src/app/firedancer/config/testnet.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
max_account_records = 200_000_000
1111
[snapshots]
1212
[snapshots.sources]
13-
servers = [ "http://solana-testnet-rpc.jumpisolated.com:8899" ]
13+
servers = [ "http://solana-testnet-rpc.jumpisolated.com:8899", "https://solana-testnet-tls.jumpisolated.com" ]
1414
[snapshots.sources.gossip]
1515
allow_any = false
1616
allow_list = []

src/app/firedancer/topology.c

Lines changed: 101 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ setup_topo_vinyl_cache( fd_topo_t * topo,
185185
return line_obj;
186186
}
187187

188+
/* Resolves a hostname to a single ip address. If multiple ip address
189+
records are returned by getaddrinfo, only the first IPV4 address is
190+
returned via ip_addr. */
188191
static int
189192
resolve_address( char const * address,
190193
uint * ip_addr ) {
@@ -209,51 +212,107 @@ resolve_address( char const * address,
209212
return resolved;
210213
}
211214

215+
/* Resolves a hostname to multiple ip addresses, specified by
216+
ip_addr_cnt. ip_addrs points to an array of fd_ip4_port_t objects.
217+
hints points to an optionally NULL addrinfo hints object. If hints
218+
is NULL, a default hints settings containing the IPV4 address family
219+
hint will be used. */
212220
static int
213-
resolve_peer( char const * peer,
214-
fd_ip4_port_t * ip4_port ) {
221+
resolve_addresses( char const * address,
222+
struct addrinfo const * hints,
223+
fd_ip4_port_t * ip_addrs,
224+
ulong ip_addr_cnt ) {
225+
struct addrinfo default_hints = { .ai_family = AF_INET };
226+
if( FD_UNLIKELY( !hints ) ) {
227+
hints = &default_hints;
228+
}
229+
230+
struct addrinfo * res;
231+
int err = getaddrinfo( address, NULL, hints, &res );
232+
if( FD_UNLIKELY( err ) ) {
233+
FD_LOG_WARNING(( "cannot resolve address \"%s\": %i-%s", address, err, gai_strerror( err ) ));
234+
return 0;
235+
}
236+
237+
int resolved = 0;
238+
for( struct addrinfo * cur=res; cur; cur=cur->ai_next ) {
239+
if( FD_UNLIKELY( (ulong)resolved>=ip_addr_cnt ) ) break;
240+
if( FD_UNLIKELY( cur->ai_addr->sa_family!=AF_INET ) ) continue;
241+
struct sockaddr_in const * addr = (struct sockaddr_in const *)cur->ai_addr;
242+
ip_addrs[ resolved ].addr = addr->sin_addr.s_addr;
243+
resolved++;
244+
}
245+
246+
freeaddrinfo( res );
247+
return resolved;
248+
}
249+
250+
static int
251+
resolve_peer( char const * peer,
252+
struct addrinfo const * addr_resolve_hints,
253+
char const * config_str,
254+
char hostname[ static 256UL ],
255+
fd_ip4_port_t * ip4_port,
256+
ulong ip4_port_cnt,
257+
int * is_https ) {
215258

216259
/* Split host:port */
260+
int https = 0;
217261
char const * host_port = peer;
218262
if( FD_LIKELY( strncmp( peer, "http://", 7UL )==0 ) ) {
263+
if( FD_LIKELY( is_https ) ) *is_https = 0;
219264
host_port += 7UL;
220265
} else if( FD_LIKELY( strncmp( peer, "https://", 8UL )==0 ) ) {
266+
if( FD_LIKELY( is_https ) ) *is_https = 1;
221267
host_port += 8UL;
268+
https = 1;
222269
}
223270

224-
char const * colon = strrchr( host_port, ':' );
225-
if( FD_UNLIKELY( !colon ) ) {
226-
FD_LOG_ERR(( "invalid [gossip.entrypoints] entry \"%s\": no port number", host_port ));
271+
char const * colon = strrchr( host_port, ':' );
272+
char const * host_end = colon;
273+
if( FD_LIKELY( FD_UNLIKELY( !colon && !https ) ) ) {
274+
FD_LOG_ERR(( "invalid [%s] entry \"%s\": no port number", config_str, host_port ));
275+
host_end = colon;
276+
} else if( FD_LIKELY( !colon && https ) ) {
277+
host_end = host_port + strlen( host_port );
227278
}
228279

229-
char fqdn[ 255 ];
230-
ulong fqdn_len = (ulong)( colon-host_port );
231-
if( FD_UNLIKELY( fqdn_len>254 ) ) {
232-
FD_LOG_ERR(( "invalid [gossip.entrypoints] entry \"%s\": hostname too long", host_port ));
280+
ulong fqdn_len = (ulong)( host_end-host_port );
281+
if( FD_UNLIKELY( fqdn_len>255 ) ) {
282+
FD_LOG_ERR(( "invalid [%s] entry \"%s\": hostname too long", config_str, host_port ));
233283
}
234-
fd_memcpy( fqdn, host_port, fqdn_len );
235-
fqdn[ fqdn_len ] = '\0';
284+
fd_memcpy( hostname, host_port, fqdn_len );
285+
hostname[ fqdn_len ] = '\0';
286+
287+
/* Resolve hostname */
288+
int resolved = resolve_addresses( hostname, addr_resolve_hints, ip4_port, ip4_port_cnt );
236289

237290
/* Parse port number */
238291

239-
char const * port_str = colon+1;
240-
char const * endptr = NULL;
241-
ulong port = strtoul( port_str, (char **)&endptr, 10 );
242-
if( FD_UNLIKELY( !endptr || !port || port>USHORT_MAX || *endptr!='\0' ) ) {
243-
FD_LOG_ERR(( "invalid [gossip.entrypoints] entry \"%s\": invalid port number", host_port ));
292+
if( FD_LIKELY( colon ) ) {
293+
char const * port_str = host_end+1;
294+
char const * endptr = NULL;
295+
ulong port = strtoul( port_str, (char **)&endptr, 10 );
296+
if( FD_UNLIKELY( endptr==port_str || !port || port>USHORT_MAX || *endptr!='\0' ) ) {
297+
FD_LOG_ERR(( "invalid [%s] entry \"%s\": invalid port number", config_str, host_port ));
298+
}
299+
for( ulong i=0UL; i<(ulong)resolved; i++ ) ip4_port[ i ].port = fd_ushort_bswap( (ushort)port );
300+
} else if( FD_LIKELY( !colon && https ) ) {
301+
/* use default https port */
302+
for( ulong i=0UL; i<(ulong)resolved; i++ ) ip4_port[ i ].port = fd_ushort_bswap( 443U );
303+
} else {
304+
FD_LOG_ERR(( "invalid [%s] entry \"%s\": no port number", config_str, host_port ));
244305
}
245-
ip4_port->port = (ushort)fd_ushort_bswap( (ushort)port );
246306

247-
/* Resolve hostname */
248-
int resolved = resolve_address( fqdn, &ip4_port->addr );
249307
return resolved;
250308
}
251309

252310
static void
253311
resolve_gossip_entrypoints( config_t * config ) {
254312
ulong entrypoint_cnt = config->gossip.entrypoints_cnt;
255313
for( ulong i=0UL; i<entrypoint_cnt; i++ ) {
256-
if( FD_UNLIKELY( 0==resolve_peer( config->gossip.entrypoints[ i ], &config->gossip.resolved_entrypoints[ i ] ) ) ) {
314+
char hostname[ 256UL ];
315+
if( FD_UNLIKELY( 0==resolve_peer( config->gossip.entrypoints[ i ], NULL, "gossip.entrypoints", hostname, &config->gossip.resolved_entrypoints[ i ], 1, NULL ) ) ) {
257316
FD_LOG_ERR(( "failed to resolve address of [gossip.entrypoints] entry \"%s\"", config->gossip.entrypoints[ i ] ));
258317
}
259318
}
@@ -1080,12 +1139,32 @@ fd_topo_configure_tile( fd_topo_tile_t * tile,
10801139
FD_LOG_ERR(( "[snapshots.sources.gossip.block_list[%lu] invalid (%s)", i, config->firedancer.snapshots.sources.gossip.block_list[ i ] ));
10811140
}
10821141
}
1142+
1143+
ulong resolved_peers_cnt = 0UL;
10831144
for( ulong i=0UL; i<tile->snapct.sources.servers_cnt; i++ ) {
1084-
if( FD_UNLIKELY( !resolve_peer( config->firedancer.snapshots.sources.servers[ i ], &tile->snapct.sources.servers[ i ] ) ) ) {
1145+
fd_ip4_port_t resolved_addrs[ FD_TOPO_MAX_RESOLVED_ADDRS ];
1146+
struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM };
1147+
int num_resolved = resolve_peer( config->firedancer.snapshots.sources.servers[ i ],
1148+
&hints,
1149+
"snapshots.sources.servers",
1150+
tile->snapct.sources.servers[ resolved_peers_cnt ].hostname,
1151+
resolved_addrs,
1152+
FD_TOPO_MAX_RESOLVED_ADDRS,
1153+
&tile->snapct.sources.servers[ resolved_peers_cnt ].is_https );
1154+
if( FD_UNLIKELY( 0==num_resolved ) ) {
10851155
FD_LOG_ERR(( "[snapshots.sources.servers[%lu] invalid (%s)", i, config->firedancer.snapshots.sources.servers[ i ] ));
1156+
} else {
1157+
for( ulong i=0UL; i<(ulong)num_resolved; i++ ) tile->snapct.sources.servers[ resolved_peers_cnt+i ].addr = resolved_addrs[ i ];
1158+
for( ulong i=1UL; i<(ulong)num_resolved; i++ ) {
1159+
tile->snapct.sources.servers[ resolved_peers_cnt+i ].is_https = tile->snapct.sources.servers[ resolved_peers_cnt ].is_https;
1160+
fd_memcpy( tile->snapct.sources.servers[ resolved_peers_cnt+i ].hostname,
1161+
tile->snapct.sources.servers[ resolved_peers_cnt ].hostname,
1162+
sizeof(tile->snapct.sources.servers[ resolved_peers_cnt ].hostname) );
1163+
}
1164+
resolved_peers_cnt += (ulong)num_resolved;
10861165
}
10871166
}
1088-
1167+
tile->snapct.sources.servers_cnt = resolved_peers_cnt;
10891168
} else if( FD_UNLIKELY( !strcmp( tile->name, "snapld" ) ) ) {
10901169

10911170
fd_memcpy( tile->snapld.snapshots_path, config->paths.snapshots, PATH_MAX );

src/disco/bundle/fd_bundle_tile.c

Lines changed: 7 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../keyguard/fd_keyload.h"
66
#include "../plugin/fd_plugin.h"
77
#include "../../waltz/http/fd_url.h"
8+
#include "../../waltz/openssl/fd_openssl_tile.h"
89

910
#include <errno.h>
1011
#include <dirent.h> /* opendir */
@@ -55,6 +56,9 @@ metrics_write( fd_bundle_tile_t * ctx ) {
5556
FD_MCNT_SET( BUNDLE, ERRORS_PROTOBUF, ctx->metrics.decode_fail_cnt );
5657
FD_MCNT_SET( BUNDLE, ERRORS_TRANSPORT, ctx->metrics.transport_fail_cnt );
5758
FD_MCNT_SET( BUNDLE, ERRORS_NO_FEE_INFO, ctx->metrics.missing_builder_info_fail_cnt );
59+
#if FD_HAS_OPENSSL
60+
FD_MCNT_SET( BUNDLE, ERRORS_SSL_ALLOC, fd_ossl_alloc_errors );
61+
#endif
5862

5963
FD_MGAUGE_SET( BUNDLE, RTT_SAMPLE, (ulong)ctx->rtt->latest_rtt );
6064
FD_MGAUGE_SET( BUNDLE, RTT_SMOOTHED, (ulong)ctx->rtt->smoothed_rtt );
@@ -247,65 +251,6 @@ fd_bundle_tile_parse_endpoint( fd_bundle_tile_t * ctx,
247251

248252
#if FD_HAS_OPENSSL
249253

250-
/* OpenSSL allows us to specify custom memory allocation functions,
251-
which we want to point to an fd_alloc_t, but it does not let us use a
252-
context object. Instead we stash it in this thread local, which is
253-
OK because the parent workspace exists for the duration of the SSL
254-
context, and the process only has one thread.
255-
256-
Currently fd_alloc doesn't support realloc, so it's implemented on
257-
top of malloc and free, and then also it doesn't support getting the
258-
size of an allocation from the pointer, which we need for realloc, so
259-
we pad each alloc by 8 bytes and stuff the size into the first 8
260-
bytes. */
261-
static FD_TL fd_alloc_t * fd_quic_ssl_mem_function_ctx = NULL;
262-
263-
static void *
264-
crypto_malloc( ulong num,
265-
char const * file,
266-
int line ) {
267-
(void)file; (void)line;
268-
void * result = fd_alloc_malloc( fd_quic_ssl_mem_function_ctx, 16UL, num + 8UL );
269-
if( FD_UNLIKELY( !result ) ) {
270-
FD_MCNT_INC( BUNDLE, ERRORS_SSL_ALLOC, 1UL );
271-
return NULL;
272-
}
273-
*(ulong *)result = num;
274-
return (uchar *)result + 8UL;
275-
}
276-
277-
static void
278-
crypto_free( void * addr,
279-
char const * file,
280-
int line ) {
281-
(void)file;
282-
(void)line;
283-
284-
if( FD_UNLIKELY( !addr ) ) return;
285-
fd_alloc_free( fd_quic_ssl_mem_function_ctx, (uchar *)addr - 8UL );
286-
}
287-
288-
static void *
289-
crypto_realloc( void * addr,
290-
ulong num,
291-
char const * file,
292-
int line ) {
293-
if( FD_UNLIKELY( !addr ) ) return crypto_malloc( num, file, line );
294-
if( FD_UNLIKELY( !num ) ) {
295-
crypto_free( addr, file, line );
296-
return NULL;
297-
}
298-
299-
void * new = fd_alloc_malloc( fd_quic_ssl_mem_function_ctx, 16UL, num + 8UL );
300-
if( FD_UNLIKELY( !new ) ) return NULL;
301-
302-
ulong old_num = *(ulong *)( (uchar *)addr - 8UL );
303-
fd_memcpy( (uchar*)new + 8, (uchar*)addr, fd_ulong_min( old_num, num ) );
304-
fd_alloc_free( fd_quic_ssl_mem_function_ctx, (uchar *)addr - 8UL );
305-
*(ulong *)new = num;
306-
return (uchar*)new + 8UL;
307-
}
308-
309254
static void
310255
fd_ossl_keylog_callback( SSL const * ssl,
311256
char const * line ) {
@@ -321,58 +266,6 @@ fd_ossl_keylog_callback( SSL const * ssl,
321266
}
322267
}
323268

324-
static void
325-
fd_bundle_tile_load_certs( SSL_CTX * ssl_ctx ) {
326-
X509_STORE * ca_certs = X509_STORE_new();
327-
if( FD_UNLIKELY( !ca_certs ) ) {
328-
FD_LOG_ERR(( "X509_STORE_new failed" ));
329-
}
330-
331-
static char const default_dir[] = "/etc/ssl/certs/";
332-
DIR * dir = opendir( default_dir );
333-
if( FD_UNLIKELY( !dir ) ) {
334-
FD_LOG_ERR(( "opendir(%s) failed (%i-%s)", default_dir, errno, fd_io_strerror( errno ) ));
335-
}
336-
337-
struct dirent * entry;
338-
errno = 0; // clear old value since entry can be NULL when reaching end of directory.
339-
while( (entry = readdir( dir )) ) {
340-
if( !strcmp( entry->d_name, "." ) || !strcmp( entry->d_name, ".." ) ) continue;
341-
342-
char cert_path[ PATH_MAX ];
343-
char * p = fd_cstr_init( cert_path );
344-
p = fd_cstr_append_text( p, default_dir, sizeof(default_dir)-1 );
345-
p = fd_cstr_append_cstr_safe( p, entry->d_name, (ulong)(cert_path+sizeof(cert_path)-1) - (ulong)p );
346-
fd_cstr_fini( p );
347-
348-
if( !X509_STORE_load_locations( ca_certs, cert_path, NULL ) ) {
349-
/* Not all files in /etc/ssl/certs are valid certs, so ignore errors */
350-
continue;
351-
}
352-
errno = 0;
353-
}
354-
355-
if( FD_UNLIKELY( errno && errno!=ENOENT ) ) {
356-
FD_LOG_ERR(( "readdir(%s) failed (%i-%s)", default_dir, errno, fd_io_strerror( errno ) ));
357-
}
358-
359-
STACK_OF(X509) * cert_list = X509_STORE_get1_all_certs( ca_certs );
360-
FD_LOG_INFO(( "Loaded %d CA certs from %s into OpenSSL", sk_X509_num( cert_list ), default_dir ));
361-
if( fd_log_level_logfile()==0 ) {
362-
for( int i=0; i<sk_X509_num( cert_list ); i++ ) {
363-
X509 * cert = sk_X509_value( cert_list, i );
364-
FD_LOG_DEBUG(( "Loaded CA cert \"%s\"", X509_NAME_oneline( X509_get_subject_name( cert ), NULL, 0 ) ));
365-
}
366-
}
367-
sk_X509_pop_free( cert_list, X509_free );
368-
369-
SSL_CTX_set_cert_store( ssl_ctx, ca_certs );
370-
371-
if( FD_UNLIKELY( 0!=closedir( dir ) ) ) {
372-
FD_LOG_ERR(( "closedir(%s) failed (%i-%s)", default_dir, errno, fd_io_strerror( errno ) ));
373-
}
374-
}
375-
376269
static void
377270
fd_bundle_tile_init_openssl( fd_bundle_tile_t * ctx,
378271
void * alloc_mem,
@@ -381,19 +274,8 @@ fd_bundle_tile_init_openssl( fd_bundle_tile_t * ctx,
381274
if( FD_UNLIKELY( !alloc ) ) {
382275
FD_LOG_ERR(( "fd_alloc_new failed" ));
383276
}
384-
ctx->ssl_alloc = alloc;
385-
fd_quic_ssl_mem_function_ctx = alloc;
386-
387-
if( FD_UNLIKELY( !CRYPTO_set_mem_functions( crypto_malloc, crypto_realloc, crypto_free ) ) ) {
388-
FD_LOG_ERR(( "CRYPTO_set_mem_functions failed" ));
389-
}
390-
391-
OPENSSL_init_ssl(
392-
OPENSSL_INIT_LOAD_SSL_STRINGS |
393-
OPENSSL_INIT_LOAD_CRYPTO_STRINGS |
394-
OPENSSL_INIT_NO_LOAD_CONFIG,
395-
NULL
396-
);
277+
ctx->ssl_alloc = alloc;
278+
fd_ossl_tile_init( alloc );
397279

398280
SSL_CTX * ssl_ctx = SSL_CTX_new( TLS_client_method() );
399281
if( FD_UNLIKELY( !ssl_ctx ) ) {
@@ -417,8 +299,7 @@ fd_bundle_tile_init_openssl( fd_bundle_tile_t * ctx,
417299
}
418300

419301
if( tls_cert_verify ) {
420-
fd_bundle_tile_load_certs( ssl_ctx );
421-
SSL_CTX_set_verify( ssl_ctx, SSL_VERIFY_PEER, NULL );
302+
fd_ossl_load_certs( ssl_ctx );
422303
}
423304

424305
if( FD_LIKELY( ctx->keylog_fd >= 0 ) ) {

src/disco/metrics/generated/fd_metrics_snapct.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ const fd_metrics_meta_t FD_METRICS_SNAPCT[FD_METRICS_SNAPCT_TOTAL] = {
1616
DECLARE_METRIC( SNAPCT_PREDICTED_SLOT, GAUGE ),
1717
DECLARE_METRIC( SNAPCT_GOSSIP_FRESH_COUNT, GAUGE ),
1818
DECLARE_METRIC( SNAPCT_GOSSIP_TOTAL_COUNT, GAUGE ),
19+
DECLARE_METRIC( SNAPCT_SSL_ALLOC_ERRORS, COUNTER ),
1920
};

0 commit comments

Comments
 (0)