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 an
7 : fd_quic_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 fd_quic_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 :
18 : #include "../../../shared/fd_config.h"
19 : #include "../../../../disco/metrics/fd_metrics.h"
20 : #include "../../../../disco/quic/fd_quic_tile.h"
21 : #include "../../../../waltz/quic/log/fd_quic_log_user.h"
22 : #include "../../../../ballet/hex/fd_hex.h"
23 : #include <stdlib.h>
24 :
25 : /* Define global variables */
26 :
27 : fd_quic_ctx_t fd_quic_trace_ctx;
28 : fd_quic_ctx_t const * fd_quic_trace_ctx_remote;
29 : ulong fd_quic_trace_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 : 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 : }
56 :
57 : static char const *
58 0 : dump_val_enum_role( int role ) {
59 0 : switch( role ) {
60 0 : case FD_QUIC_ROLE_CLIENT:
61 0 : return "ROLE_CLIENT";
62 0 : case FD_QUIC_ROLE_SERVER:
63 0 : return "ROLE_SERVER";
64 0 : default:
65 0 : return "ROLE_UNKNOWN";
66 0 : }
67 0 : }
68 :
69 : static char const *
70 0 : dump_val_bool( int value ) {
71 0 : switch( value ) {
72 0 : case 0: return "false";
73 0 : case 1: return "true";
74 0 : default: return "invalid"; /* in case something is assuming a config is in {0,1} */
75 0 : }
76 0 : }
77 :
78 : void
79 0 : dump_quic_config( fd_quic_config_t * config ) {
80 0 : switch( config->role ) {
81 0 : case FD_QUIC_ROLE_CLIENT:
82 0 : FD_LOG_NOTICE(( "CONFIG: role: %d FD_QUIC_ROLE_CLIENT", config->role ));
83 0 : break;
84 0 : case FD_QUIC_ROLE_SERVER:
85 0 : FD_LOG_NOTICE(( "CONFIG: role: %d FD_QUIC_ROLE_SERVER", config->role ));
86 0 : break;
87 0 : default:
88 0 : FD_LOG_NOTICE(( "CONFIG: role: %d UNKNOWN", config->role ));
89 0 : }
90 :
91 0 : #define HEXFMT32 "%02x%02x%02x%02x" "%02x%02x%02x%02x" \
92 0 : "%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 : #define HEXARG32(X) (X)[0], (X)[1], (X)[2], (X)[3], \
96 0 : (X)[4], (X)[5], (X)[6], (X)[7], \
97 0 : (X)[8], (X)[9], (X)[10], (X)[11], \
98 0 : (X)[12], (X)[13], (X)[14], (X)[15], \
99 0 : (X)[16], (X)[17], (X)[18], (X)[19], \
100 0 : (X)[20], (X)[21], (X)[22], (X)[23], \
101 0 : (X)[24], (X)[25], (X)[26], (X)[27], \
102 0 : (X)[28], (X)[29], (X)[30], (X)[31]
103 :
104 0 : #define dump_val_class_enum( NAME, FMT, CLASS, UNIT, VAL ) \
105 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": " FMT " - %s", config->NAME, dump_val_enum_##NAME( config->NAME ) ));
106 0 : #define dump_val_class_bool( NAME, FMT, CLASS, UNIT, VAL ) \
107 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": " FMT " - %s", config->NAME, dump_val_bool( config->NAME ) ));
108 0 : #define dump_val_class_units( NAME, FMT, CLASS, UNIT, VAL ) \
109 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": " FMT " %s", config->NAME, UNIT ));
110 0 : #define dump_val_class_value( NAME, FMT, CLASS, UNIT, VAL ) \
111 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": " FMT, config->NAME ));
112 0 : #define dump_val_class_ptr( NAME, FMT, CLASS, UNIT, VAL ) \
113 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": 0x%lx", (ulong)config->NAME ));
114 0 : #define dump_val_class_hex32( NAME, FMT, CLASS, UNIT, VAL ) \
115 0 : FD_LOG_NOTICE(( "CONFIG: " #NAME ": 0x" HEXFMT32, HEXARG32(config->NAME) ));
116 :
117 0 : #define dump_val( NAME, FMT, CLASS, UNIT, VAL ) \
118 0 : dump_val_class_##CLASS( NAME, FMT, CLASS, UNIT, VAL )
119 :
120 0 : FD_QUIC_CONFIG_LIST( dump_val, x )
121 0 : }
122 :
123 : static char const *
124 0 : peer_cid_str( fd_quic_conn_t const * conn ) {
125 0 : static char buf[FD_QUIC_MAX_CONN_ID_SZ*2];
126 0 : ulong sz = conn->peer_cids[0].sz;
127 0 : uchar const * cid = conn->peer_cids[0].conn_id;
128 0 : sz = fd_ulong_min( sz, FD_QUIC_MAX_CONN_ID_SZ );
129 :
130 0 : fd_hex_encode( buf, cid, sz );
131 :
132 0 : return buf;
133 0 : }
134 :
135 : static void
136 0 : dump_connection( fd_quic_conn_t const * conn ) {
137 0 : (void)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_ticks, "%lu", ( (CONN).idle_timeout_ticks ), __VA_ARGS__ ) \
171 0 : X( last_activity, "%lu", ( (CONN).last_activity ), __VA_ARGS__ ) \
172 0 : X( last_ack, "%lu", ( (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 : static fd_quic_conn_t const *
186 0 : fd_quic_trace_conn_at_idx( fd_quic_t const * quic, ulong idx, ulong quic_raddr ) {
187 0 : fd_quic_state_t const * state = fd_quic_get_state_const( quic );
188 0 : ulong const conn_base_off = state->conn_base - quic_raddr;
189 0 : ulong const local_conn_base = (ulong)quic + conn_base_off;
190 0 : return (fd_quic_conn_t *)( local_conn_base + idx * state->conn_sz );
191 0 : }
192 :
193 : void
194 : quic_trace_cmd_fn( args_t * args,
195 0 : config_t * config ) {
196 0 : fd_topo_t * topo = &config->topo;
197 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_ONLY );
198 0 : fd_topo_fill( topo );
199 :
200 0 : fd_topo_tile_t * quic_tile = NULL;
201 0 : for( ulong tile_idx=0UL; tile_idx < topo->tile_cnt; tile_idx++ ) {
202 0 : if( 0==strcmp( topo->tiles[ tile_idx ].name, "quic" ) ) {
203 0 : quic_tile = &topo->tiles[ tile_idx ];
204 0 : break;
205 0 : }
206 0 : }
207 0 : if( !quic_tile ) FD_LOG_ERR(( "QUIC tile not found in topology" ));
208 0 : if( FD_UNLIKELY( quic_tile->in_cnt!=1UL ) ) { /* FIXME */
209 0 : FD_LOG_ERR(( "Sorry, fd_quic_trace does not support multiple net tiles yet" ));
210 0 : }
211 :
212 : /* Ugly: fd_quic_ctx_t uses non-relocatable object addressing.
213 : We need to rebase pointers. foreign_{...} refer to the original
214 : objects in shared memory, local_{...} refer to translated copies. */
215 :
216 0 : void * quic_tile_base = fd_topo_obj_laddr( topo, quic_tile->tile_obj_id );
217 0 : fd_quic_ctx_t const * foreign_quic_ctx = quic_tile_base;
218 0 : fd_quic_ctx_t * quic_ctx = &fd_quic_trace_ctx;
219 0 : *quic_ctx = *foreign_quic_ctx;
220 0 : fd_quic_trace_ctx_remote = foreign_quic_ctx;
221 :
222 0 : ulong quic_raddr = (ulong)foreign_quic_ctx->quic;
223 0 : ulong ctx_raddr = quic_raddr - fd_ulong_align_up( sizeof(fd_quic_ctx_t), fd_ulong_max( alignof(fd_quic_ctx_t), fd_quic_align() ) );
224 0 : fd_quic_trace_ctx_raddr = ctx_raddr;
225 :
226 0 : FD_LOG_INFO(( "fd_quic_tile state at %p in tile address space", (void *)ctx_raddr ));
227 0 : FD_LOG_INFO(( "fd_quic_tile state at %p in local address space", quic_tile_base ));
228 :
229 0 : quic_ctx->reasm = (void *)( (ulong)quic_tile_base + (ulong)quic_ctx->reasm - ctx_raddr );
230 0 : quic_ctx->stem = (void *)( (ulong)quic_tile_base + (ulong)quic_ctx->stem - ctx_raddr );
231 0 : quic_ctx->quic = (void *)( (ulong)quic_tile_base + (ulong)quic_ctx->quic - ctx_raddr );
232 :
233 : /* find quic_net in topology */
234 0 : ulong link_id = fd_topo_find_link( topo, "quic_net", 0 );
235 :
236 0 : if( link_id == ULONG_MAX ) {
237 0 : FD_LOG_ERR(( "quic_net not found" ));
238 0 : }
239 0 : fd_topo_link_t * quic_net = &topo->links[ link_id ];
240 :
241 0 : fd_quic_trace_ctx_t trace_ctx[1] =
242 0 : {{ .dump = args->quic_trace.dump,
243 0 : .dump_config = args->quic_trace.dump_config,
244 0 : .dump_conns = args->quic_trace.dump_conns }};
245 0 : fd_wksp_t * quic_net_wksp = fd_wksp_containing( quic_net->dcache );
246 :
247 : /* quic_net_wksp is the base address for locating chunks */
248 0 : trace_ctx->net_out_base = (ulong)quic_net_wksp;
249 0 : trace_ctx->net_out = 1;
250 :
251 0 : fd_topo_link_t * net_quic = &topo->links[ quic_tile->in_link_id[ 0 ] ];
252 0 : fd_net_rx_bounds_init( &quic_ctx->net_in_bounds[ 0 ], net_quic->dcache );
253 0 : FD_LOG_INFO(( "net->quic dcache at %p", (void *)net_quic->dcache ));
254 :
255 : /* Join shared memory objects
256 : Mostly nops but verifies object magic numbers to ensure that
257 : derived pointers are correct. */
258 :
259 0 : FD_LOG_INFO(( "Joining fd_quic" ));
260 0 : fd_quic_t * quic = fd_quic_join( quic_ctx->quic );
261 0 : if( !quic ) FD_LOG_ERR(( "Failed to join fd_quic" ));
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 :
287 0 : ulong conn_cnt = quic->limits.conn_cnt;
288 0 : ulong state_unknown = 0;
289 0 : #define COMMA ,
290 0 : #define _(X,Y) [FD_QUIC_CONN_STATE_##X] = 0
291 0 : ulong state_cnt[] = { CONN_STATE_LIST(_,COMMA,Y) };
292 0 : ulong state_cap = sizeof( state_cnt) / sizeof( state_cnt[0] );
293 0 : #undef _
294 :
295 0 : for( ulong j = 0; j < conn_cnt; ++j ) {
296 0 : fd_quic_conn_t const * conn = fd_quic_trace_conn_at_idx( quic, j, quic_raddr );
297 0 : ulong state = conn->state;
298 0 : ulong *state_bucket = state < state_cap ? &state_cnt[state] : &state_unknown;
299 :
300 0 : (*state_bucket)++;
301 :
302 0 : switch( conn->state ) {
303 0 : case FD_QUIC_CONN_STATE_INVALID:
304 : /* indicates the connection is free */
305 0 : break;
306 0 : default:
307 0 : if( trace_ctx->dump_conns ) {
308 0 : dump_connection( conn );
309 0 : }
310 :
311 : /* add connection to the peer_conn_id_map */
312 :
313 : /* when we receive a one-rtt quic packet, we don't know the conn_id
314 : size, so we assume its longer than 8 bytes, and truncate the rest */
315 0 : ulong key;
316 0 : memcpy( &key, conn->peer_cids[0].conn_id, sizeof( key ) );
317 0 : peer_conn_id_map_t * entry = peer_conn_id_map_insert( peer_map, key );
318 0 : if( entry ) {
319 0 : entry->conn_idx = (uint)j;
320 0 : } else {
321 : /* this is a diagnostics tool, so we'll continue here */
322 0 : FD_LOG_WARNING(( "Peer connection id map full. Continuing with partial functionality" ));
323 0 : }
324 0 : }
325 0 : }
326 :
327 0 : #define _FMT(X,Y) "%s=%lu"
328 0 : #define _ARG(X,Y) #X, state_cnt[FD_QUIC_CONN_STATE_##X]
329 0 : FD_LOG_NOTICE(( "Total connections: %lu "
330 0 : CONN_STATE_LIST(_FMT," ",Y), conn_cnt,
331 0 : CONN_STATE_LIST(_ARG,COMMA,Y) ));
332 0 : #undef _FMT
333 0 : #undef _ARG
334 :
335 : /* Locate original fseq objects
336 : These are monitored to ensure the trace RX tile doesn't skip ahead
337 : of the quic tile. */
338 0 : fd_quic_trace_target_fseq = malloc( quic_tile->in_cnt * sizeof(ulong) );
339 0 : for( ulong i=0UL; i<quic_tile->in_cnt; i++ ) {
340 0 : fd_quic_trace_target_fseq[ i ] = quic_tile->in_link_fseq[ i ];
341 0 : }
342 :
343 : /* Locate log buffer */
344 :
345 0 : void * log = (void *)( (ulong)quic + quic->layout.log_off );
346 0 : fd_quic_log_rx_t log_rx[1];
347 0 : FD_LOG_DEBUG(( "Joining quic_log" ));
348 0 : if( FD_UNLIKELY( !fd_quic_log_rx_join( log_rx, log ) ) ) {
349 0 : FD_LOG_ERR(( "fd_quic_log_rx_join failed" ));
350 0 : }
351 0 : fd_quic_trace_log_base = log_rx->base;
352 :
353 : /* Redirect metadata writes to dummy buffers.
354 : Without this hack, stem_run would attempt to write metadata updates
355 : into the target topology which is read-only. */
356 :
357 : /* ... redirect metric updates */
358 0 : ulong * metrics = aligned_alloc( FD_METRICS_ALIGN, FD_METRICS_FOOTPRINT( quic_tile->in_cnt, quic_tile->out_cnt ) );
359 0 : if( !metrics ) FD_LOG_ERR(( "out of memory" ));
360 0 : fd_memset( metrics, 0, FD_METRICS_FOOTPRINT( quic_tile->in_cnt, quic_tile->out_cnt ) );
361 0 : fd_metrics_register( metrics );
362 :
363 0 : fd_quic_trace_link_metrics = fd_metrics_link_in( fd_metrics_base_tl, 0 );
364 :
365 : /* Join net->quic link consumer */
366 :
367 0 : fd_frag_meta_t const * rx_mcache = net_quic->mcache;
368 0 : fd_frag_meta_t const * tx_mcache = quic_net->mcache;
369 :
370 0 : FD_LOG_NOTICE(( "quic-trace starting ..." ));
371 0 : switch( args->quic_trace.event ) {
372 0 : case EVENT_STREAM:
373 0 : fd_quic_trace_rx_tile( trace_ctx, rx_mcache, tx_mcache );
374 0 : break;
375 0 : case EVENT_ERROR:
376 0 : fd_quic_trace_log_tile( log_rx->mcache );
377 0 : break;
378 0 : default:
379 0 : __builtin_unreachable();
380 0 : }
381 :
382 0 : fd_quic_log_rx_leave( log_rx );
383 0 : }
384 :
385 : action_t fd_action_quic_trace = {
386 : .name = "quic-trace",
387 : .args = quic_trace_cmd_args,
388 : .fn = quic_trace_cmd_fn,
389 : .description = "Trace quic tile",
390 : .is_diagnostic = 1
391 : };
|