]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: quic: implement a basic "show quic" CLI handler
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 1 Feb 2023 09:18:26 +0000 (10:18 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 9 Feb 2023 17:11:00 +0000 (18:11 +0100)
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
include/haproxy/quic_conn-t.h
include/haproxy/tinfo-t.h
src/quic_conn.c

index 5a15ec06e0edd2a10db4469bdd79453f80ed817e..bf7ba8c4b606590586133d819e5fb1c1574333e8 100644 (file)
@@ -2966,6 +2966,11 @@ show resolvers [<resolvers section id>]
     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 [<backend>]
   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
index 68362343d5e45ad16341ef650599994bf6ab9872..6560bd5f3dd89a39b0f7a90c0238b2ef9edde7ec 100644 (file)
@@ -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 */
index 7bac2c5da4c4e9c5e1bceded37bd5c670015a800..45cfe8a29924dda507ecf3ae51938391ebfd6eaa 100644 (file)
@@ -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 */
index 2eb2ad3b00175fff20c737e8977f132491577465..6b951281c0080452f8e64d126e056d238ec51e21 100644 (file)
@@ -34,6 +34,8 @@
 #include <haproxy/tools.h>
 #include <haproxy/ticks.h>
 
+#include <haproxy/applet-t.h>
+#include <haproxy/cli.h>
 #include <haproxy/connection.h>
 #include <haproxy/fd.h>
 #include <haproxy/freq_ctr.h>
 #include <haproxy/quic_tls.h>
 #include <haproxy/ssl_sock.h>
 #include <haproxy/task.h>
+#include <haproxy/thread.h>
 #include <haproxy/trace.h>
 
+/* 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