mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-06 23:27:04 +02:00
Replace ->li quic_conn pointer to struct listener member by ->target which is an object type enum and adapt the code. Use __objt_(listener|server)() where the object type is known. Typically this is were the code which is specific to one connection type (frontend/backend). Remove <server> parameter passed to qc_new_conn(). It is redundant with the <target> parameter. GSO is not supported at this time for QUIC backend. qc_prep_pkts() is modified to prevent it from building more than an MTU. This has as consequence to prevent qc_send_ppkts() to use GSO. ssl_clienthello.c code is run only by listeners. This is why __objt_listener() is used in place of ->li.
576 lines
17 KiB
C
576 lines
17 KiB
C
#include <import/eb64tree.h>
|
|
|
|
#include <haproxy/applet-t.h>
|
|
#include <haproxy/cli.h>
|
|
#include <haproxy/list.h>
|
|
#include <haproxy/mux_quic.h>
|
|
#include <haproxy/quic_conn.h>
|
|
#include <haproxy/quic_tp.h>
|
|
#include <haproxy/quic_utils.h>
|
|
#include <haproxy/tools.h>
|
|
|
|
/* incremented by each "show quic". */
|
|
unsigned int qc_epoch = 0;
|
|
|
|
enum quic_dump_format {
|
|
QUIC_DUMP_FMT_DEFAULT, /* value used if not explicitly specified. */
|
|
|
|
QUIC_DUMP_FMT_ONELINE,
|
|
QUIC_DUMP_FMT_STREAM,
|
|
QUIC_DUMP_FMT_CUST,
|
|
};
|
|
|
|
#define QUIC_DUMP_FLD_TP 0x0001
|
|
#define QUIC_DUMP_FLD_SOCK 0x0002
|
|
#define QUIC_DUMP_FLD_PKTNS 0x0004
|
|
#define QUIC_DUMP_FLD_CC 0x0008
|
|
#define QUIC_DUMP_FLD_MUX 0x0010
|
|
/* Do not forget to update FLD_MASK when adding a new field. */
|
|
#define QUIC_DUMP_FLD_MASK 0x001f
|
|
|
|
/* 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;
|
|
int flags;
|
|
enum quic_dump_format format;
|
|
void *ptr;
|
|
int fields;
|
|
};
|
|
|
|
#define QC_CLI_FL_SHOW_ALL 0x1 /* show closing/draining connections */
|
|
|
|
/* Returns the output format for show quic. If specified explicitly use it as
|
|
* set. Else format depends if filtering on a single connection instance. If
|
|
* true, full format is preferred else oneline.
|
|
*/
|
|
static enum quic_dump_format cli_show_quic_format(const struct show_quic_ctx *ctx)
|
|
{
|
|
if (ctx->format == QUIC_DUMP_FMT_DEFAULT)
|
|
return ctx->ptr ? QUIC_DUMP_FMT_CUST : QUIC_DUMP_FMT_ONELINE;
|
|
else
|
|
return ctx->format;
|
|
}
|
|
|
|
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));
|
|
int argc = 2;
|
|
|
|
if (!cli_has_level(appctx, ACCESS_LVL_OPER))
|
|
return 1;
|
|
|
|
ctx->epoch = _HA_ATOMIC_FETCH_ADD(&qc_epoch, 1);
|
|
ctx->thr = 0;
|
|
ctx->flags = 0;
|
|
ctx->format = QUIC_DUMP_FMT_DEFAULT;
|
|
ctx->ptr = 0;
|
|
ctx->fields = 0;
|
|
|
|
if (strcmp(args[argc], "oneline") == 0) {
|
|
ctx->format = QUIC_DUMP_FMT_ONELINE;
|
|
++argc;
|
|
}
|
|
else if (strcmp(args[argc], "stream") == 0) {
|
|
ctx->format = QUIC_DUMP_FMT_STREAM;
|
|
ctx->fields = QUIC_DUMP_FLD_MASK;
|
|
++argc;
|
|
}
|
|
else if (strcmp(args[argc], "full") == 0) {
|
|
ctx->format = QUIC_DUMP_FMT_CUST;
|
|
ctx->fields = QUIC_DUMP_FLD_MASK;
|
|
++argc;
|
|
}
|
|
else if (strcmp(args[argc], "help") == 0) {
|
|
chunk_printf(&trash,
|
|
"Usage: show quic [help|<format>] [<filter>]\n"
|
|
"Dumps information about QUIC connections. Available output formats:\n"
|
|
" oneline dump a single, netstat-like line per connection (default)\n"
|
|
" stream dump a list of streams, one per line, sorted by connection\n"
|
|
" full dump all known information about each connection\n"
|
|
" <levels>* only dump certain information, defined by a comma-delimited list\n"
|
|
" of levels among 'tp', 'sock', 'pktns', 'cc', or 'mux'\n"
|
|
" help display this help\n"
|
|
"Available output filters:\n"
|
|
" all dump all connections (the default)\n"
|
|
" <id> dump only the connection matching this identifier (0x...)\n"
|
|
"Without any argument, all connections are dumped using the oneline format.\n");
|
|
return cli_err(appctx, trash.area);
|
|
}
|
|
else if (*args[argc]) {
|
|
struct ist istarg = ist(args[argc]);
|
|
struct ist field = istsplit(&istarg, ',');
|
|
|
|
do {
|
|
if (isteq(field, ist("tp"))) {
|
|
ctx->fields |= QUIC_DUMP_FLD_TP;
|
|
}
|
|
else if (isteq(field, ist("sock"))) {
|
|
ctx->fields |= QUIC_DUMP_FLD_SOCK;
|
|
}
|
|
else if (isteq(field, ist("pktns"))) {
|
|
ctx->fields |= QUIC_DUMP_FLD_PKTNS;
|
|
}
|
|
else if (isteq(field, ist("cc"))) {
|
|
ctx->fields |= QUIC_DUMP_FLD_CC;
|
|
}
|
|
else if (isteq(field, ist("mux"))) {
|
|
ctx->fields |= QUIC_DUMP_FLD_MUX;
|
|
}
|
|
else {
|
|
/* Current argument is comma-separated so it is
|
|
* interpreted as a field list but an unknown
|
|
* field name has been specified.
|
|
*/
|
|
if (istarg.len || ctx->fields) {
|
|
cli_err(appctx, "Invalid field, use 'help' for more options.\n");
|
|
return 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
field = istsplit(&istarg, ',');
|
|
} while (field.len);
|
|
|
|
/* At least one valid field specified, select the associated
|
|
* format. Else parse the current argument as a filter.
|
|
*/
|
|
if (ctx->fields) {
|
|
ctx->format = QUIC_DUMP_FMT_CUST;
|
|
++argc;
|
|
}
|
|
}
|
|
|
|
if (*args[argc]) {
|
|
struct ist istarg = ist(args[argc]);
|
|
|
|
if (istmatchi(istarg, ist("0x"))) {
|
|
char *nptr;
|
|
ctx->ptr = (void *)strtol(args[argc], &nptr, 16);
|
|
if (*nptr) {
|
|
cli_err(appctx, "Invalid quic_conn pointer.\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!ctx->fields)
|
|
ctx->fields = QUIC_DUMP_FLD_MASK;
|
|
|
|
++argc;
|
|
}
|
|
else if (istmatch(istarg, ist("all"))) {
|
|
ctx->flags |= QC_CLI_FL_SHOW_ALL;
|
|
}
|
|
else {
|
|
cli_err(appctx, "Invalid argument, use 'help' for more options.\n");
|
|
return 1;
|
|
}
|
|
|
|
++argc;
|
|
}
|
|
|
|
LIST_INIT(&ctx->bref.users);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Dump for "show quic" with "oneline" format. */
|
|
static void dump_quic_oneline(struct show_quic_ctx *ctx, struct quic_conn *qc)
|
|
{
|
|
char bufaddr[INET6_ADDRSTRLEN], bufport[6];
|
|
int ret;
|
|
unsigned char cid_len;
|
|
struct listener *l = objt_listener(qc->target);
|
|
|
|
ret = chunk_appendf(&trash, "%p[%02u]/%-.12s ", qc, ctx->thr,
|
|
l ? l->bind_conf->frontend->id : __objt_server(qc->target)->id);
|
|
|
|
chunk_appendf(&trash, "%*s", 36 - ret, " "); /* align output */
|
|
|
|
/* State */
|
|
if (qc->flags & QUIC_FL_CONN_CLOSING)
|
|
chunk_appendf(&trash, "CLOSE ");
|
|
else if (qc->flags & QUIC_FL_CONN_DRAINING)
|
|
chunk_appendf(&trash, "DRAIN ");
|
|
else if (qc->state < QUIC_HS_ST_COMPLETE)
|
|
chunk_appendf(&trash, "HDSHK ");
|
|
else
|
|
chunk_appendf(&trash, "ESTAB ");
|
|
|
|
/* Bytes in flight / Lost packets */
|
|
chunk_appendf(&trash, "%9llu %6llu %6llu ",
|
|
(ullong)qc->path->in_flight,
|
|
(ullong)qc->path->ifae_pkts,
|
|
(ullong)qc->path->loss.nb_lost_pkt);
|
|
|
|
/* Socket */
|
|
if (qc->local_addr.ss_family == AF_INET ||
|
|
qc->local_addr.ss_family == AF_INET6) {
|
|
addr_to_str(&qc->local_addr, bufaddr, sizeof(bufaddr));
|
|
port_to_str(&qc->local_addr, bufport, sizeof(bufport));
|
|
chunk_appendf(&trash, "%15s:%-5s ", bufaddr, bufport);
|
|
|
|
addr_to_str(&qc->peer_addr, bufaddr, sizeof(bufaddr));
|
|
port_to_str(&qc->peer_addr, bufport, sizeof(bufport));
|
|
chunk_appendf(&trash, "%15s:%-5s ", bufaddr, bufport);
|
|
|
|
}
|
|
|
|
/* CIDs */
|
|
for (cid_len = 0; cid_len < qc->scid.len; ++cid_len)
|
|
chunk_appendf(&trash, "%02x", qc->scid.data[cid_len]);
|
|
|
|
chunk_appendf(&trash, " ");
|
|
for (cid_len = 0; cid_len < qc->dcid.len; ++cid_len)
|
|
chunk_appendf(&trash, "%02x", qc->dcid.data[cid_len]);
|
|
|
|
chunk_appendf(&trash, "\n");
|
|
}
|
|
|
|
/* Dump for "show quic" with "stream" format. */
|
|
static void dump_quic_stream(struct show_quic_ctx *ctx, struct quic_conn *qc)
|
|
{
|
|
struct eb64_node *node;
|
|
struct qc_stream_desc *sd;
|
|
struct qcs *qcs;
|
|
uint64_t id;
|
|
|
|
node = eb64_first(&qc->streams_by_id);
|
|
while (node) {
|
|
sd = eb_entry(node, struct qc_stream_desc, by_id);
|
|
id = sd->by_id.key;
|
|
qcs = !(sd->flags & QC_SD_FL_RELEASE) ? sd->ctx : NULL;
|
|
|
|
if (quic_stream_is_uni(id)) {
|
|
node = eb64_next(node);
|
|
continue;
|
|
}
|
|
|
|
chunk_appendf(&trash, "%p.%04llu: 0x%02x age=%s", qc, (ullong)id, sd->flags,
|
|
human_time(ns_to_sec(now_ns) - ns_to_sec(sd->origin_ts), 1));
|
|
|
|
bdata_ctr_print(&trash, &sd->data, "tx=[");
|
|
if (qcs) {
|
|
chunk_appendf(&trash, ",%llu]", (ullong)qcs->tx.fc.off_real);
|
|
chunk_appendf(&trash, " rxb=%d(%d)/%llu", qcs->rx.data.bcnt, qcs->rx.data.bmax, (ullong)qcs->rx.offset);
|
|
chunk_appendf(&trash, " qcs[0x%08x,sd=%p]", qcs->flags, qcs->sd);
|
|
}
|
|
else {
|
|
chunk_appendf(&trash, "/-");
|
|
chunk_appendf(&trash, " rxb=-/-");
|
|
chunk_appendf(&trash, " -- released --");
|
|
}
|
|
|
|
chunk_appendf(&trash, "\n");
|
|
node = eb64_next(node);
|
|
}
|
|
}
|
|
|
|
/* Dump for "show quic" with "full" format. */
|
|
static void dump_quic_full(struct show_quic_ctx *ctx, struct quic_conn *qc)
|
|
{
|
|
struct quic_pktns *pktns;
|
|
char bufaddr[INET6_ADDRSTRLEN], bufport[6];
|
|
int expire, addnl;
|
|
unsigned char cid_len;
|
|
|
|
addnl = 0;
|
|
/* CIDs */
|
|
chunk_appendf(&trash, "* %p[%02u]: scid=", qc, ctx->thr);
|
|
for (cid_len = 0; cid_len < qc->scid.len; ++cid_len)
|
|
chunk_appendf(&trash, "%02x", qc->scid.data[cid_len]);
|
|
while (cid_len++ < 20)
|
|
chunk_appendf(&trash, "..");
|
|
|
|
chunk_appendf(&trash, " dcid=");
|
|
for (cid_len = 0; cid_len < qc->dcid.len; ++cid_len)
|
|
chunk_appendf(&trash, "%02x", qc->dcid.data[cid_len]);
|
|
while (cid_len++ < 20)
|
|
chunk_appendf(&trash, "..");
|
|
|
|
chunk_appendf(&trash, "\n");
|
|
|
|
if (ctx->fields & QUIC_DUMP_FLD_TP) {
|
|
chunk_appendf(&trash, " loc. TPs:");
|
|
quic_transport_params_dump(&trash, qc, &qc->rx.params);
|
|
chunk_appendf(&trash, "\n");
|
|
chunk_appendf(&trash, " rem. TPs:");
|
|
quic_transport_params_dump(&trash, qc, &qc->tx.params);
|
|
chunk_appendf(&trash, "\n");
|
|
}
|
|
|
|
/* Connection state */
|
|
if (qc->flags & QUIC_FL_CONN_CLOSING)
|
|
chunk_appendf(&trash, " st=closing ");
|
|
else if (qc->flags & QUIC_FL_CONN_DRAINING)
|
|
chunk_appendf(&trash, " st=draining ");
|
|
else if (qc->state < QUIC_HS_ST_CONFIRMED)
|
|
chunk_appendf(&trash, " st=handshake ");
|
|
else
|
|
chunk_appendf(&trash, " st=opened ");
|
|
|
|
if (qc->mux_state == QC_MUX_NULL)
|
|
chunk_appendf(&trash, "mux=null ");
|
|
else if (qc->mux_state == QC_MUX_READY)
|
|
chunk_appendf(&trash, "mux=ready ");
|
|
else
|
|
chunk_appendf(&trash, "mux=released ");
|
|
|
|
if (qc->idle_timer_task) {
|
|
expire = qc->idle_timer_task->expire;
|
|
chunk_appendf(&trash, "expire=%02ds ",
|
|
TICKS_TO_MS(tick_remain(now_ms, expire)) / 1000);
|
|
}
|
|
|
|
chunk_appendf(&trash, "\n");
|
|
|
|
/* Socket */
|
|
if (ctx->fields & QUIC_DUMP_FLD_SOCK) {
|
|
chunk_appendf(&trash, " fd=%d", qc->fd);
|
|
if (qc->local_addr.ss_family == AF_INET ||
|
|
qc->local_addr.ss_family == AF_INET6) {
|
|
addr_to_str(&qc->local_addr, bufaddr, sizeof(bufaddr));
|
|
port_to_str(&qc->local_addr, bufport, sizeof(bufport));
|
|
chunk_appendf(&trash, " local_addr=%s:%s", bufaddr, bufport);
|
|
|
|
addr_to_str(&qc->peer_addr, bufaddr, sizeof(bufaddr));
|
|
port_to_str(&qc->peer_addr, bufport, sizeof(bufport));
|
|
chunk_appendf(&trash, " foreign_addr=%s:%s", bufaddr, bufport);
|
|
}
|
|
|
|
chunk_appendf(&trash, "\n");
|
|
}
|
|
|
|
/* Packet number spaces information */
|
|
if (ctx->fields & QUIC_DUMP_FLD_PKTNS) {
|
|
pktns = qc->ipktns;
|
|
if (pktns) {
|
|
chunk_appendf(&trash, " [initl] rx.ackrng=%-6zu tx.inflight=%-6zu(%llu%%)\n",
|
|
pktns->rx.arngs.sz, pktns->tx.in_flight,
|
|
(ull)pktns->tx.in_flight * 100 / qc->path->cwnd);
|
|
}
|
|
|
|
pktns = qc->hpktns;
|
|
if (pktns) {
|
|
chunk_appendf(&trash, " [hndshk] rx.ackrng=%-6zu tx.inflight=%-6zu(%llu%%)\n",
|
|
pktns->rx.arngs.sz, pktns->tx.in_flight,
|
|
(ull)pktns->tx.in_flight * 100 / qc->path->cwnd);
|
|
}
|
|
|
|
pktns = qc->apktns;
|
|
if (pktns) {
|
|
chunk_appendf(&trash, " [01rtt] rx.ackrng=%-6zu tx.inflight=%-6zu(%llu%%)\n",
|
|
pktns->rx.arngs.sz, pktns->tx.in_flight,
|
|
(ull)pktns->tx.in_flight * 100 / qc->path->cwnd);
|
|
}
|
|
}
|
|
|
|
if (ctx->fields & QUIC_DUMP_FLD_CC) {
|
|
if (qc->path->cc.algo->state_cli)
|
|
qc->path->cc.algo->state_cli(&trash, qc->path);
|
|
|
|
chunk_appendf(&trash, " srtt=%-4u rttvar=%-4u rttmin=%-4u ptoc=%-4u\n"
|
|
" cwnd=%-6llu cwnd_last_max=%-6llu\n"
|
|
" sentbytes=%-12llu sentbytesgso=%-12llu sentpkts=%-6llu\n"
|
|
" lostpkts=%-6llu reorderedpkts=%-6llu\n",
|
|
qc->path->loss.srtt, qc->path->loss.rtt_var,
|
|
qc->path->loss.rtt_min, qc->path->loss.pto_count, (ullong)qc->path->cwnd,
|
|
(ullong)qc->path->cwnd_last_max, (ullong)qc->cntrs.sent_bytes, (ullong)qc->cntrs.sent_bytes_gso,
|
|
(ullong)qc->cntrs.sent_pkt, (ullong)qc->path->loss.nb_lost_pkt, (ullong)qc->path->loss.nb_reordered_pkt);
|
|
}
|
|
|
|
if (qc->cntrs.dropped_pkt) {
|
|
chunk_appendf(&trash, " droppkts=%-6llu", qc->cntrs.dropped_pkt);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.dropped_pkt_bufoverrun) {
|
|
chunk_appendf(&trash, " dropbuff=%-6llu", qc->cntrs.dropped_pkt_bufoverrun);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.dropped_parsing) {
|
|
chunk_appendf(&trash, " droppars=%-6llu", qc->cntrs.dropped_parsing);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.socket_full) {
|
|
chunk_appendf(&trash, " sockfull=%-6llu", qc->cntrs.socket_full);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.sendto_err) {
|
|
chunk_appendf(&trash, " sendtoerr=%-6llu", qc->cntrs.sendto_err);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.sendto_err_unknown) {
|
|
chunk_appendf(&trash, " sendtounknerr=%-6llu", qc->cntrs.sendto_err);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.conn_migration_done) {
|
|
chunk_appendf(&trash, " migrdone=%-6llu", qc->cntrs.conn_migration_done);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.data_blocked) {
|
|
chunk_appendf(&trash, " datablocked=%-6llu", qc->cntrs.data_blocked);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.stream_data_blocked) {
|
|
chunk_appendf(&trash, " sdatablocked=%-6llu", qc->cntrs.stream_data_blocked);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.streams_blocked_bidi) {
|
|
chunk_appendf(&trash, " sblockebidi=%-6llu", qc->cntrs.streams_blocked_bidi);
|
|
addnl = 1;
|
|
}
|
|
if (qc->cntrs.streams_blocked_uni) {
|
|
chunk_appendf(&trash, " sblockeduni=%-6llu", qc->cntrs.streams_blocked_uni);
|
|
addnl = 1;
|
|
}
|
|
if (addnl)
|
|
chunk_appendf(&trash, "\n");
|
|
|
|
if (ctx->fields & QUIC_DUMP_FLD_MUX && qc->mux_state == QC_MUX_READY)
|
|
qcc_show_quic(qc->qcc);
|
|
|
|
chunk_appendf(&trash, "\n");
|
|
}
|
|
|
|
static int cli_io_handler_dump_quic(struct appctx *appctx)
|
|
{
|
|
struct show_quic_ctx *ctx = appctx->svcctx;
|
|
struct quic_conn *qc;
|
|
|
|
thread_isolate();
|
|
|
|
if (ctx->thr >= global.nbthread)
|
|
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;
|
|
|
|
/* Print legend for oneline format. */
|
|
if (cli_show_quic_format(ctx) == QUIC_DUMP_FMT_ONELINE) {
|
|
chunk_appendf(&trash, "# conn/frontend state "
|
|
"in_flight infl_p lost_p "
|
|
"Local Address Foreign Address "
|
|
"local & remote CIDs\n");
|
|
applet_putchk(appctx, &trash);
|
|
}
|
|
}
|
|
|
|
while (1) {
|
|
int done = 0;
|
|
|
|
if (ctx->bref.ref == &ha_thread_ctx[ctx->thr].quic_conns) {
|
|
/* If closing connections requested through "all" or a
|
|
* specific connection is filtered, move to
|
|
* quic_conns_clo list after browsing quic_conns. Else
|
|
* move directly to the next quic_conns thread.
|
|
*/
|
|
if (ctx->flags & QC_CLI_FL_SHOW_ALL || ctx->ptr) {
|
|
ctx->bref.ref = ha_thread_ctx[ctx->thr].quic_conns_clo.n;
|
|
continue;
|
|
}
|
|
|
|
done = 1;
|
|
}
|
|
else if (ctx->bref.ref == &ha_thread_ctx[ctx->thr].quic_conns_clo) {
|
|
/* Closing list entirely browsed, go to next quic_conns
|
|
* thread.
|
|
*/
|
|
done = 1;
|
|
}
|
|
else {
|
|
/* Retrieve next element of the current list. */
|
|
qc = LIST_ELEM(ctx->bref.ref, struct quic_conn *, el_th_ctx);
|
|
if ((int)(qc->qc_epoch - ctx->epoch) > 0)
|
|
done = 1;
|
|
|
|
/* Skip to next element if filter on a different connection. */
|
|
if (ctx->ptr && ctx->ptr != qc)
|
|
done = 1;
|
|
}
|
|
|
|
if (done) {
|
|
++ctx->thr;
|
|
if (ctx->thr >= global.nbthread)
|
|
break;
|
|
/* Switch to next thread quic_conns list. */
|
|
ctx->bref.ref = ha_thread_ctx[ctx->thr].quic_conns.n;
|
|
continue;
|
|
}
|
|
|
|
switch (cli_show_quic_format(ctx)) {
|
|
case QUIC_DUMP_FMT_CUST:
|
|
dump_quic_full(ctx, qc);
|
|
break;
|
|
case QUIC_DUMP_FMT_ONELINE:
|
|
dump_quic_oneline(ctx, qc);
|
|
break;
|
|
|
|
case QUIC_DUMP_FMT_STREAM:
|
|
dump_quic_stream(ctx, qc);
|
|
break;
|
|
|
|
case QUIC_DUMP_FMT_DEFAULT:
|
|
/* An explicit format must be returned by cli_show_quic_format(). */
|
|
ABORT_NOW();
|
|
}
|
|
|
|
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;
|
|
|
|
/* If filtered connection displayed, show quic can be stopped early. */
|
|
if (ctx->ptr)
|
|
goto done;
|
|
}
|
|
|
|
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 [help|<format>] [<filter>] : 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 cli_quic_init()
|
|
{
|
|
int thr;
|
|
|
|
for (thr = 0; thr < MAX_THREADS; ++thr) {
|
|
LIST_INIT(&ha_thread_ctx[thr].quic_conns);
|
|
LIST_INIT(&ha_thread_ctx[thr].quic_conns_clo);
|
|
}
|
|
}
|
|
INITCALL0(STG_INIT, cli_quic_init);
|