From 15c74702d594a5160981b0778a0959ab0e64ad0a Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Wed, 1 Feb 2023 10:18:26 +0100 Subject: [PATCH] MINOR: quic: implement a basic "show quic" CLI handler Implement a basic "show quic" CLI handler. This command will be useful to display various information on all the active QUIC frontend connections. This work is heavily inspired by "show sess". Most notably, a global list of quic_conn has been introduced to be able to loop over them. This list is stored per thread in ha_thread_ctx. Also add three CLI handlers for "show quic" in order to allocate and free the command context. The dump handler runs on thread isolation. Each quic_conn is referenced using a back-ref to handle deletion during handler yielding. For the moment, only a list of raw quic_conn pointers is displayed. The handler will be completed over time with more information as needed. This should be backported up to 2.7. --- doc/management.txt | 5 ++ include/haproxy/quic_conn-t.h | 4 + include/haproxy/tinfo-t.h | 1 + src/quic_conn.c | 154 ++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+) diff --git a/doc/management.txt b/doc/management.txt index 5a15ec06e..bf7ba8c4b 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -2966,6 +2966,11 @@ show resolvers [] too_big: too big response outdated: number of response arrived too late (after an other name server) +show quic + Dump information on all active QUIC frontend connections. This command is + restricted and can only be issued on sockets configured for levels "operator" + or "admin". + show servers conn [] Dump the current and idle connections state of the servers belonging to the designated backend (or all backends if none specified). A backend name or diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h index 68362343d..6560bd5f3 100644 --- a/include/haproxy/quic_conn-t.h +++ b/include/haproxy/quic_conn-t.h @@ -733,6 +733,10 @@ struct quic_conn { const struct qcc_app_ops *app_ops; struct quic_counters *prx_counters; + + struct list el_th_ctx; /* list elem in ha_thread_ctx */ + struct list back_refs; /* list head of CLI context currently dumping this connection. */ + unsigned int qc_epoch; /* delimiter for newer instances started after "show quic". */ }; #endif /* USE_QUIC */ diff --git a/include/haproxy/tinfo-t.h b/include/haproxy/tinfo-t.h index 7bac2c5da..45cfe8a29 100644 --- a/include/haproxy/tinfo-t.h +++ b/include/haproxy/tinfo-t.h @@ -130,6 +130,7 @@ struct thread_ctx { struct list pool_lru_head; /* oldest objects in thread-local pool caches */ struct list buffer_wq; /* buffer waiters */ struct list streams; /* list of streams attached to this thread */ + struct list quic_conns; /* list of quic-conns attached to this thread */ ALWAYS_ALIGN(2*sizeof(void*)); struct list tasklets[TL_CLASSES]; /* tasklets (and/or tasks) to run, by class */ diff --git a/src/quic_conn.c b/src/quic_conn.c index 2eb2ad3b0..6b951281c 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -34,6 +34,8 @@ #include #include +#include +#include #include #include #include @@ -58,8 +60,12 @@ #include #include #include +#include #include +/* incremented by each "show quic". */ +static unsigned int qc_epoch = 0; + /* list of supported QUIC versions by this implementation */ const struct quic_version quic_versions[] = { { @@ -4886,6 +4892,8 @@ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, qc_init_fd(qc); + LIST_INIT(&qc->back_refs); + /* Now proceeds to allocation of qc members. */ buf_area = pool_alloc(pool_head_quic_conn_rxbuf); @@ -5024,6 +5032,9 @@ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, if (!qc_new_isecs(qc, ictx,qc->original_version, dcid->data, dcid->len, 1)) goto err; + LIST_APPEND(&th_ctx->quic_conns, &qc->el_th_ctx); + qc->qc_epoch = HA_ATOMIC_LOAD(&qc_epoch); + TRACE_LEAVE(QUIC_EV_CONN_INIT, qc); return qc; @@ -5051,6 +5062,7 @@ void quic_conn_release(struct quic_conn *qc) struct eb64_node *node; struct quic_tls_ctx *app_tls_ctx; struct quic_rx_packet *pkt, *pktback; + struct bref *bref, *back; TRACE_ENTER(QUIC_EV_CONN_CLOSE, qc); @@ -5126,6 +5138,26 @@ void quic_conn_release(struct quic_conn *qc) quic_free_arngs(qc, &qc->pktns[i].rx.arngs); } + /* Detach CLI context watchers currently dumping this connection. + * Reattach them to the next quic_conn instance. + */ + list_for_each_entry_safe(bref, back, &qc->back_refs, users) { + /* Remove watcher from this quic_conn instance. */ + LIST_DEL_INIT(&bref->users); + + /* Attach it to next instance unless it was the last list element. */ + if (qc->el_th_ctx.n != &th_ctx->quic_conns) { + struct quic_conn *next = LIST_NEXT(&qc->el_th_ctx, + struct quic_conn *, + el_th_ctx); + LIST_APPEND(&next->back_refs, &bref->users); + } + bref->ref = qc->el_th_ctx.n; + __ha_barrier_store(); + } + /* Remove quic_conn from global ha_thread_ctx list. */ + LIST_DELETE(&qc->el_th_ctx); + pool_free(pool_head_quic_conn_rxbuf, qc->rx.buf.area); pool_free(pool_head_quic_conn, qc); TRACE_PROTO("QUIC conn. freed", QUIC_EV_CONN_FREED, qc); @@ -7586,6 +7618,128 @@ void qc_notify_close(struct quic_conn *qc) TRACE_LEAVE(QUIC_EV_CONN_CLOSE, qc); } + +/* appctx context used by "show quic" command */ +struct show_quic_ctx { + unsigned int epoch; + struct bref bref; /* back-reference to the quic-conn being dumped */ + unsigned int thr; +}; + +static int cli_parse_show_quic(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct show_quic_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); + + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return 1; + + ctx->epoch = _HA_ATOMIC_FETCH_ADD(&qc_epoch, 1); + ctx->thr = 0; + + LIST_INIT(&ctx->bref.users); + + return 0; +} + +static int cli_io_handler_dump_quic(struct appctx *appctx) +{ + struct show_quic_ctx *ctx = appctx->svcctx; + struct stconn *sc = appctx_sc(appctx); + struct quic_conn *qc; + + thread_isolate(); + + if (ctx->thr >= global.nbthread) + goto done; + + if (unlikely(sc_ic(sc)->flags & CF_SHUTW)) { + /* If we're forced to shut down, we might have to remove our + * reference to the last stream being dumped. + */ + if (!LIST_ISEMPTY(&ctx->bref.users)) + LIST_DEL_INIT(&ctx->bref.users); + goto done; + } + + chunk_reset(&trash); + + if (!LIST_ISEMPTY(&ctx->bref.users)) { + /* Remove show_quic_ctx from previous quic_conn instance. */ + LIST_DEL_INIT(&ctx->bref.users); + } + else if (!ctx->bref.ref) { + /* First invocation. */ + ctx->bref.ref = ha_thread_ctx[ctx->thr].quic_conns.n; + } + + while (1) { + int done = 0; + + if (ctx->bref.ref == &ha_thread_ctx[ctx->thr].quic_conns) { + done = 1; + } + else { + qc = LIST_ELEM(ctx->bref.ref, struct quic_conn *, el_th_ctx); + if ((int)(qc->qc_epoch - ctx->epoch) > 0) + done = 1; + } + + if (done) { + ++ctx->thr; + if (ctx->thr >= global.nbthread) + break; + ctx->bref.ref = ha_thread_ctx[ctx->thr].quic_conns.n; + continue; + } + + chunk_appendf(&trash, "%p", qc); + chunk_appendf(&trash, "\n"); + + if (applet_putchk(appctx, &trash) == -1) { + /* Register show_quic_ctx to quic_conn instance. */ + LIST_APPEND(&qc->back_refs, &ctx->bref.users); + goto full; + } + + ctx->bref.ref = qc->el_th_ctx.n; + } + + done: + thread_release(); + return 1; + + full: + thread_release(); + return 0; +} + +static void cli_release_show_quic(struct appctx *appctx) +{ + struct show_quic_ctx *ctx = appctx->svcctx; + + if (ctx->thr < global.nbthread) { + thread_isolate(); + if (!LIST_ISEMPTY(&ctx->bref.users)) + LIST_DEL_INIT(&ctx->bref.users); + thread_release(); + } +} + +static struct cli_kw_list cli_kws = {{ }, { + { { "show", "quic", NULL }, "show quic : display quic connections status", cli_parse_show_quic, cli_io_handler_dump_quic, cli_release_show_quic }, +}}; + +INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); + +static void init_quic() +{ + int thr; + + for (thr = 0; thr < MAX_THREADS; ++thr) + LIST_INIT(&ha_thread_ctx[thr].quic_conns); +} +INITCALL0(STG_INIT, init_quic); + /* * Local variables: * c-indent-level: 8