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