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