Line data Source code
1 : /* This directory provides the 'fddev quic-trace' subcommand.
2 :
3 : The goal of quic-trace is to tap QUIC traffic on a live system, which
4 : requires encryption keys and other annoying connection state.
5 :
6 : quic-trace does this by tapping into the shared memory segments of a
7 : target tile running on the same host. It does so strictly read-only
8 : to minimize impact to a production system.
9 :
10 : This file (fd_quic_trace_main.c) provides the glue code required to
11 : join remote target tile objects.
12 :
13 : fd_quic_trace_rx_tile.c provides a fd_tango consumer for incoming
14 : QUIC packets. */
15 :
16 : #include "fd_quic_trace.h"
17 : #include "../../../shared/fd_bootinfo.h"
18 :
19 : #include "../../../../disco/metrics/fd_metrics.h"
20 : #include "../../../../disco/quic/fd_quic_tile.h"
21 : #include "../../../../discof/txsend/fd_txsend_tile.h"
22 : #include "../../../../waltz/quic/log/fd_quic_log_user.h"
23 : #include "../../../../ballet/hex/fd_hex.h"
24 : #include <stdlib.h>
25 :
26 : /* Define global variables */
27 :
28 : void const * fd_quic_trace_tile_ctx_remote;
29 : ulong fd_quic_trace_tile_ctx_raddr;
30 : ulong ** fd_quic_trace_target_fseq;
31 : ulong volatile * fd_quic_trace_link_metrics;
32 : void const * fd_quic_trace_log_base;
33 : peer_conn_id_map_t _fd_quic_trace_peer_map[1UL << PEER_MAP_LG_SLOT_CNT];
34 : peer_conn_id_map_t * fd_quic_trace_peer_map;
35 :
36 0 : #define EVENT_STREAM 0
37 0 : #define EVENT_ERROR 1
38 :
39 : static void
40 : quic_trace_cmd_args( int * pargc,
41 : char *** pargv,
42 0 : args_t * args ) {
43 0 : char const * event = fd_env_strip_cmdline_cstr( pargc, pargv, "--event", NULL, "stream" );
44 0 : if( 0==strcmp( event, "stream" ) ) {
45 0 : args->quic_trace.event = EVENT_STREAM;
46 0 : } else if( 0==strcmp( event, "error" ) ) {
47 0 : args->quic_trace.event = EVENT_ERROR;
48 0 : } else {
49 0 : FD_LOG_ERR(( "Unsupported QUIC event type \"%s\"", event ));
50 0 : }
51 :
52 0 : args->quic_trace.dump = fd_env_strip_cmdline_contains( pargc, pargv, "--dump" );
53 0 : args->quic_trace.dump_config = fd_env_strip_cmdline_contains( pargc, pargv, "--dump-config" );
54 0 : args->quic_trace.dump_conns = fd_env_strip_cmdline_contains( pargc, pargv, "--dump-conns" );
55 0 : args->quic_trace.trace_send = fd_env_strip_cmdline_contains( pargc, pargv, "--send-tile" );
56 0 : }
57 :
58 : static char const *
59 0 : dump_val_enum_role( int role ) {
60 0 : switch( role ) {
61 0 : case FD_QUIC_ROLE_CLIENT:
62 0 : return "ROLE_CLIENT";
63 0 : case FD_QUIC_ROLE_SERVER:
64 0 : return "ROLE_SERVER";
65 0 : default:
66 0 : return "ROLE_UNKNOWN";
67 0 : }
68 0 : }
69 :
70 : static char const *
71 0 : dump_val_bool( int value ) {
72 0 : switch( value ) {
73 0 : case 0: return "false";
74 0 : case 1: return "true";
75 0 : default: return "invalid"; /* in case something is assuming a config is in {0,1} */
76 0 : }
77 0 : }
78 :
79 : void
80 0 : dump_quic_config( fd_quic_config_t * config ) {
81 0 : switch( config->role ) {
82 0 : case FD_QUIC_ROLE_CLIENT:
83 0 : FD_LOG_NOTICE(( "CONFIG: role: %d FD_QUIC_ROLE_CLIENT", config->role ));
84 0 : break;
85 0 : case FD_QUIC_ROLE_SERVER:
86 0 : FD_LOG_NOTICE(( "CONFIG: role: %d FD_QUIC_ROLE_SERVER", config->role ));
87 0 : break;
88 0 : default:
89 0 : FD_LOG_NOTICE(( "CONFIG: role: %d UNKNOWN", config->role ));
90 0 : }
91 :
92 0 : #define HEXFMT32 "%02x%02x%02x%02x" "%02x%02x%02x%02x" \
93 0 : "%02x%02x%02x%02x" "%02x%02x%02x%02x" \
94 0 : "%02x%02x%02x%02x" "%02x%02x%02x%02x" \
95 0 : "%02x%02x%02x%02x" "%02x%02x%02x%02x"
96 0 : #define HEXARG32(X) (X)[0], (X)[1], (X)[2], (X)[3], \
97 0 : (X)[4], (X)[5], (X)[6], (X)[7], \
98 0 : (X)[8], (X)[9], (X)[10], (X)[11], \
99 0 : (X)[12], (X)[13], (X)[14], (X)[15], \
100 0 : (X)[16], (X)[17], (X)[18], (X)[19], \
101 0 : (X)[20], (X)[21], (X)[22], (X)[23], \
102 0 : (X)[24], (X)[25], (X)[26], (X)[27], \
103 0 : (X)[28], (X)[29], (X)[30], (X)[31]
104 :
105 0 : #define dump_val_class_enum( NAME, FMT, CLASS, UNIT, VAL ) \
106 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": " FMT " - %s", config->NAME, dump_val_enum_##NAME( config->NAME ) ));
107 0 : #define dump_val_class_bool( NAME, FMT, CLASS, UNIT, VAL ) \
108 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": " FMT " - %s", config->NAME, dump_val_bool( config->NAME ) ));
109 0 : #define dump_val_class_units( NAME, FMT, CLASS, UNIT, VAL ) \
110 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": " FMT " %s", config->NAME, UNIT ));
111 0 : #define dump_val_class_value( NAME, FMT, CLASS, UNIT, VAL ) \
112 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": " FMT, config->NAME ));
113 0 : #define dump_val_class_ptr( NAME, FMT, CLASS, UNIT, VAL ) \
114 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": 0x%lx", (ulong)config->NAME ));
115 0 : #define dump_val_class_hex32( NAME, FMT, CLASS, UNIT, VAL ) \
116 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": 0x" HEXFMT32, HEXARG32(config->NAME) ));
117 :
118 0 : #define dump_val( NAME, FMT, CLASS, UNIT, VAL ) \
119 0 : dump_val_class_##CLASS( NAME, FMT, CLASS, UNIT, VAL )
120 :
121 0 : FD_QUIC_CONFIG_LIST( dump_val, x )
122 0 : }
123 :
124 : static char const *
125 0 : peer_cid_str( fd_quic_conn_t const * conn ) {
126 0 : static char buf[FD_QUIC_MAX_CONN_ID_SZ*2];
127 0 : ulong sz = conn->peer_cids[0].sz;
128 0 : uchar const * cid = conn->peer_cids[0].conn_id;
129 0 : sz = fd_ulong_min( sz, FD_QUIC_MAX_CONN_ID_SZ );
130 :
131 0 : fd_hex_encode( buf, cid, sz );
132 :
133 0 : return buf;
134 0 : }
135 :
136 : static void
137 0 : dump_connection( fd_quic_conn_t const * conn ) {
138 :
139 0 : #define CONN_MEMB_LIST(X,CONN,...) \
140 0 : X( conn_idx, "%u", ( (CONN).conn_idx ), __VA_ARGS__ ) \
141 0 : X( state, "%u", ( (CONN).state ), __VA_ARGS__ ) \
142 0 : X( reason, "%u", ( (CONN).reason ), __VA_ARGS__ ) \
143 0 : X( app_reason, "%u", ( (CONN).app_reason ), __VA_ARGS__ ) \
144 0 : X( tx_ptr, "%p", ( ((void*)(CONN).tx_ptr) ), __VA_ARGS__ ) \
145 0 : X( unacked_sz, "%lu", ( (CONN).unacked_sz ), __VA_ARGS__ ) \
146 0 : X( flags, "%x", ( (CONN).flags ), __VA_ARGS__ ) \
147 0 : X( conn_gen, "%u", ( (CONN).conn_gen ), __VA_ARGS__ ) \
148 0 : X( server, "%d", ( (CONN).server ), __VA_ARGS__ ) \
149 0 : X( established, "%d", ( (CONN).established ), __VA_ARGS__ ) \
150 0 : X( transport_params_set, "%d", ( (CONN).transport_params_set ), __VA_ARGS__ ) \
151 0 : X( called_conn_new, "%d", ( (CONN).called_conn_new ), __VA_ARGS__ ) \
152 0 : X( visited, "%d", ( (CONN).visited ), __VA_ARGS__ ) \
153 0 : X( key_phase, "%d", ( (CONN).key_phase ), __VA_ARGS__ ) \
154 0 : X( key_update, "%d", ( (CONN).key_update ), __VA_ARGS__ ) \
155 0 : X( our_conn_id, "%016lx", ( (CONN).our_conn_id ), __VA_ARGS__ ) \
156 0 : X( peer[0].ip_addr, "%08x", ( (uint)(CONN).peer[0].ip_addr ), __VA_ARGS__ ) \
157 0 : X( peer[0].udp_port, "%u", ( (uint)(CONN).peer[0].udp_port ), __VA_ARGS__ ) \
158 0 : X( handshake_complete, "%d", ( (CONN).handshake_complete ), __VA_ARGS__ ) \
159 0 : X( handshake_done_send, "%d", ( (CONN).handshake_done_send ), __VA_ARGS__ ) \
160 0 : X( handshake_done_ackd, "%d", ( (CONN).handshake_done_ackd ), __VA_ARGS__ ) \
161 0 : X( exp_pkt_number[0], "%lu", ( (CONN).exp_pkt_number[0] ), __VA_ARGS__ ) \
162 0 : X( exp_pkt_number[1], "%lu", ( (CONN).exp_pkt_number[1] ), __VA_ARGS__ ) \
163 0 : X( exp_pkt_number[2], "%lu", ( (CONN).exp_pkt_number[2] ), __VA_ARGS__ ) \
164 0 : X( pkt_number[0], "%lu", ( (CONN).pkt_number[0] ), __VA_ARGS__ ) \
165 0 : X( pkt_number[1], "%lu", ( (CONN).pkt_number[1] ), __VA_ARGS__ ) \
166 0 : X( pkt_number[2], "%lu", ( (CONN).pkt_number[2] ), __VA_ARGS__ ) \
167 0 : X( last_pkt_number[0], "%lu", ( (CONN).last_pkt_number[0] ), __VA_ARGS__ ) \
168 0 : X( last_pkt_number[1], "%lu", ( (CONN).last_pkt_number[1] ), __VA_ARGS__ ) \
169 0 : X( last_pkt_number[2], "%lu", ( (CONN).last_pkt_number[2] ), __VA_ARGS__ ) \
170 0 : X( idle_timeout_ns, "%ld", ( (CONN).idle_timeout_ns ), __VA_ARGS__ ) \
171 0 : X( last_activity, "%ld", ( (CONN).last_activity ), __VA_ARGS__ ) \
172 0 : X( last_ack, "%ld", ( (CONN).last_ack ), __VA_ARGS__ ) \
173 0 : X( used_pkt_meta, "%lu", ( (CONN).used_pkt_meta ), __VA_ARGS__ ) \
174 0 : X( peer_cid, "%s", ( peer_cid_str(&(CONN)) ), __VA_ARGS__ )
175 :
176 0 : #define UNPACK(...) __VA_ARGS__
177 0 : #define CONN_MEMB_FMT(NAME,FMT,ARGS,...) " " #NAME "=" FMT
178 0 : #define CONN_MEMB_ARGS(NAME,FMT,ARGS,...) , UNPACK ARGS
179 0 : FD_LOG_NOTICE(( "CONN: "
180 0 : CONN_MEMB_LIST(CONN_MEMB_FMT,*conn,_)
181 0 : CONN_MEMB_LIST(CONN_MEMB_ARGS,*conn,_)
182 0 : ));
183 0 : }
184 :
185 : void
186 : quic_trace_cmd_fn( args_t * args,
187 0 : config_t * config ) {
188 0 : fd_topo_t * topo = &config->topo;
189 0 : fd_bootinfo_adopt( config );
190 0 : fd_bootinfo_check_layout( config );
191 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_ONLY, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
192 0 : fd_topo_fill( topo );
193 :
194 0 : int trace_send = args->quic_trace.trace_send;
195 :
196 0 : char const * tile_names[] = {"quic", "txsend"};
197 0 : fd_topo_tile_t * target_tile = NULL;
198 0 : for( ulong tile_idx=0UL; tile_idx<topo->tile_cnt; tile_idx++ ) {
199 0 : if( 0==strcmp( topo->tiles[tile_idx].name, tile_names[trace_send] ) ) {
200 0 : target_tile = &topo->tiles[tile_idx];
201 0 : break;
202 0 : }
203 0 : }
204 0 : if( !target_tile ) FD_LOG_ERR(( "%s tile not found in topology", tile_names[trace_send] ));
205 :
206 0 : ulong const target_in_cnt = target_tile->in_cnt;
207 0 : if( FD_UNLIKELY( !trace_send && target_in_cnt != 1UL ) ) { /* FIXME */
208 0 : FD_LOG_ERR(( "Sorry, fd_quic_trace does not support multiple net tiles yet" ));
209 0 : }
210 :
211 : /* Ugly: fd_quic_ctx_t uses non-relocatable object addressing.
212 : We need to rebase pointers. _remote{...} is local pointer to original
213 : objects in shared memory, _raddr is the remote address of the original
214 : object. */
215 :
216 0 : fd_quic_trace_tile_ctx_remote = fd_topo_obj_laddr( topo, target_tile->tile_obj_id );
217 0 : ulong quic_raddr = (ulong)tile_member( fd_quic_trace_tile_ctx_remote, quic, trace_send );
218 0 : ulong tile_align = fd_ulong_if( trace_send, alignof(fd_txsend_tile_t), alignof(fd_quic_ctx_t) );
219 0 : ulong tile_ctx_sz = fd_ulong_if( trace_send, sizeof(fd_txsend_tile_t), sizeof(fd_quic_ctx_t) );
220 0 : fd_quic_trace_tile_ctx_raddr = quic_raddr - fd_ulong_align_up( tile_ctx_sz, fd_ulong_max( tile_align, fd_quic_align() ) );
221 :
222 0 : FD_LOG_INFO(("quic_raddr %p", (void *)quic_raddr));
223 0 : FD_LOG_INFO((
224 0 : "%s tile state at %p in tile address space and %p in local address space",
225 0 : tile_names[trace_send], (void *)fd_quic_trace_tile_ctx_raddr, fd_quic_trace_tile_ctx_remote));
226 :
227 : /* target_net link tracking */
228 0 : char out_link_name[16];
229 0 : FD_TEST( fd_cstr_printf_check( out_link_name, sizeof(out_link_name), NULL, "%s_net", tile_names[trace_send] ) );
230 0 : ulong link_id = fd_topo_find_link( topo, out_link_name, 0 );
231 0 : if( FD_UNLIKELY( link_id == ULONG_MAX ) ) FD_LOG_ERR(("%s not found", out_link_name));
232 0 : fd_topo_link_t * target_net = &topo->links[link_id];
233 :
234 : /* net_target link tracking */
235 0 : FD_TEST( fd_cstr_printf_check( out_link_name, sizeof(out_link_name), NULL, "net_%s", tile_names[trace_send] ) );
236 0 : link_id = fd_topo_find_link( topo, out_link_name, 0 );
237 0 : if( FD_UNLIKELY( link_id == ULONG_MAX ) ) FD_LOG_ERR(("%s not found", out_link_name));
238 :
239 0 : fd_topo_link_t * net_target = &topo->links[link_id];
240 0 : fd_net_rx_bounds_t net_in_bounds;
241 0 : fd_net_rx_bounds_init(&net_in_bounds, net_target->dcache);
242 0 : FD_LOG_INFO(("net->%s dcache at %p", tile_names[trace_send], (void *)net_target->dcache));
243 :
244 : /* Join shared memory objects
245 : Mostly nops but verifies object magic numbers to ensure that
246 : derived pointers are correct. */
247 :
248 0 : FD_LOG_INFO(( "Joining fd_quic in %s tile", tile_names[trace_send] ));
249 0 : fd_quic_t * quic_remote = fd_type_pun( translate_ptr( (void*)quic_raddr ) );
250 0 : fd_quic_t * quic = fd_quic_join( quic_remote );
251 0 : if( !quic ) FD_LOG_ERR( ("Failed to join fd_quic in %s tile", tile_names[trace_send]));
252 :
253 : /* build ctx */
254 0 : fd_quic_trace_ctx_t trace_ctx[1] = {
255 0 : {.dump = args->quic_trace.dump,
256 0 : .dump_config = args->quic_trace.dump_config,
257 0 : .dump_conns = args->quic_trace.dump_conns,
258 0 : .trace_send = args->quic_trace.trace_send,
259 0 : .net_out_base = (ulong)fd_wksp_containing(target_net->dcache),
260 0 : .quic = quic,
261 0 : .net_in_bounds = {net_in_bounds} } };
262 :
263 : /* dump config */
264 0 : if( trace_ctx->dump_config ) {
265 0 : dump_quic_config( &quic->config );
266 0 : }
267 :
268 : /* initialize peer conn_id map */
269 0 : void * shmap = peer_conn_id_map_new( _fd_quic_trace_peer_map );
270 0 : peer_conn_id_map_t * peer_map = peer_conn_id_map_join( shmap );
271 :
272 : /* set the global */
273 0 : fd_quic_trace_peer_map = peer_map;
274 :
275 : /* iterate connections - dump and/or insert */
276 :
277 0 : #define CONN_STATE_LIST(X,SEP,...) \
278 0 : X( INVALID , __VA_ARGS__ ) SEP \
279 0 : X( HANDSHAKE , __VA_ARGS__ ) SEP \
280 0 : X( HANDSHAKE_COMPLETE , __VA_ARGS__ ) SEP \
281 0 : X( ACTIVE , __VA_ARGS__ ) SEP \
282 0 : X( PEER_CLOSE , __VA_ARGS__ ) SEP \
283 0 : X( ABORT , __VA_ARGS__ ) SEP \
284 0 : X( CLOSE_PENDING , __VA_ARGS__ ) SEP \
285 0 : X( DEAD , __VA_ARGS__ )
286 0 : ulong conn_cnt = quic->limits.conn_cnt;
287 0 : ulong state_unknown = 0;
288 0 : #define COMMA ,
289 0 : #define _(X,Y) [FD_QUIC_CONN_STATE_##X] = 0
290 0 : ulong state_cnt[] = { CONN_STATE_LIST(_,COMMA,Y) };
291 0 : ulong state_cap = sizeof( state_cnt) / sizeof( state_cnt[0] );
292 0 : #undef _
293 :
294 0 : for( ulong j=0UL; j<conn_cnt; ++j ) {
295 0 : fd_quic_conn_t const * conn = fd_quic_trace_conn_at_idx( quic, j );
296 0 : ulong state = conn->state;
297 0 : ulong * state_bucket = state < state_cap ? &state_cnt[state] : &state_unknown;
298 :
299 0 : (*state_bucket)++;
300 :
301 0 : switch( conn->state ) {
302 0 : case FD_QUIC_CONN_STATE_INVALID:
303 : /* indicates the connection is free */
304 0 : break;
305 0 : default:
306 0 : if( trace_ctx->dump_conns ) {
307 0 : dump_connection( conn );
308 0 : }
309 :
310 : /* add connection to the peer_conn_id_map */
311 :
312 : /* when we receive a one-rtt quic packet, we don't know the conn_id
313 : size, so we assume its longer than 8 bytes, and truncate the rest */
314 0 : ulong key;
315 0 : memcpy( &key, conn->peer_cids[0].conn_id, sizeof( key ) );
316 0 : peer_conn_id_map_t * entry = peer_conn_id_map_insert( peer_map, key );
317 0 : if( entry ) {
318 0 : entry->conn_idx = (uint)j;
319 0 : } else {
320 : /* this is a diagnostics tool, so we'll continue here */
321 0 : FD_LOG_WARNING(( "Peer connection id map full. Continuing with partial functionality" ));
322 0 : }
323 0 : }
324 0 : }
325 :
326 0 : #define _FMT(X,Y) "%s=%lu"
327 0 : #define _ARG(X,Y) #X, state_cnt[FD_QUIC_CONN_STATE_##X]
328 0 : FD_LOG_NOTICE(( "Total connections: %lu "
329 0 : CONN_STATE_LIST(_FMT," ",Y), conn_cnt,
330 0 : CONN_STATE_LIST(_ARG,COMMA,Y) ));
331 0 : #undef _FMT
332 0 : #undef _ARG
333 :
334 : /* Locate original fseq objects
335 : These are monitored to ensure the trace RX tile doesn't skip ahead
336 : of the target tile. */
337 0 : fd_quic_trace_target_fseq = malloc( target_in_cnt * sizeof(ulong) );
338 0 : for( ulong i=0UL; i<target_in_cnt; i++ ) {
339 0 : fd_quic_trace_target_fseq[i] = target_tile->in_link_fseq[i];
340 0 : }
341 :
342 : /* Locate log buffer */
343 :
344 0 : void * log = (void *)((ulong)quic_remote + quic->layout.log_off);
345 0 : fd_quic_log_rx_t log_rx[1];
346 0 : FD_LOG_DEBUG(( "Joining %s log", tile_names[trace_send] ));
347 0 : if( FD_UNLIKELY( !fd_quic_log_rx_join( log_rx, log ) ) ) {
348 0 : FD_LOG_ERR(( "fd_quic_log_rx_join failed" ));
349 0 : }
350 0 : fd_quic_trace_log_base = log_rx->base;
351 :
352 : /* Redirect metadata writes to dummy buffers.
353 : Without this hack, stem_run would attempt to write metadata updates
354 : into the target topology which is read-only. */
355 :
356 : /* ... redirect metric updates */
357 0 : ulong * metrics = aligned_alloc( FD_METRICS_ALIGN, FD_METRICS_FOOTPRINT( target_in_cnt ) );
358 0 : if( !metrics ) FD_LOG_ERR(( "out of memory" ));
359 0 : fd_memset( metrics, 0, FD_METRICS_FOOTPRINT( target_in_cnt ) );
360 0 : fd_metrics_register( metrics );
361 :
362 0 : fd_quic_trace_link_metrics = fd_metrics_link_in( fd_metrics_base_tl, 0 );
363 :
364 : /* Join net->target link consumer */
365 :
366 0 : fd_frag_meta_t const *rx_mcache = net_target->mcache;
367 0 : fd_frag_meta_t const *tx_mcache = target_net->mcache;
368 :
369 0 : trace_ctx->quic = quic;
370 :
371 0 : FD_LOG_NOTICE(( "quic-trace on %s tile starting ...", tile_names[trace_send] ));
372 0 : switch( args->quic_trace.event ) {
373 0 : case EVENT_STREAM:
374 0 : fd_quic_trace_rx_tile( trace_ctx, rx_mcache, tx_mcache );
375 0 : break;
376 0 : case EVENT_ERROR:
377 0 : fd_quic_trace_log_tile( trace_ctx, log_rx->mcache );
378 0 : break;
379 0 : default:
380 0 : FD_LOG_CRIT(( "unexpected event type: %d", args->quic_trace.event ));
381 0 : }
382 :
383 0 : fd_quic_log_rx_leave( log_rx );
384 0 : }
385 :
386 : action_t fd_action_quic_trace = {
387 : .name = "quic-trace",
388 : .args = quic_trace_cmd_args,
389 : .fn = quic_trace_cmd_fn,
390 : .description = "Trace quic tile",
391 : .is_diagnostic = 1
392 : };
|