mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-07 07:37:02 +02:00
A packet is considered as reordered when it is detected as lost because its packet number is above the largest acknowledeged packet number by at least the packet reordering threshold value. Add ->nb_reordered_pkt new quic_loss struct member at the same location that the number of lost packets to count such packets. Should be backported to 2.6.
414 lines
12 KiB
C
414 lines
12 KiB
C
#include <import/eb64tree.h>
|
|
|
|
#include <haproxy/applet-t.h>
|
|
#include <haproxy/cli.h>
|
|
#include <haproxy/list.h>
|
|
#include <haproxy/tools.h>
|
|
#include <haproxy/quic_conn-t.h>
|
|
#include <haproxy/quic_tp.h>
|
|
|
|
/* incremented by each "show quic". */
|
|
unsigned int qc_epoch = 0;
|
|
|
|
enum quic_dump_format {
|
|
QUIC_DUMP_FMT_ONELINE,
|
|
QUIC_DUMP_FMT_FULL,
|
|
};
|
|
|
|
/* 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;
|
|
};
|
|
|
|
#define QC_CLI_FL_SHOW_ALL 0x1 /* show closing/draining connections */
|
|
|
|
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_ONELINE;
|
|
|
|
if (strcmp(args[argc], "oneline") == 0) {
|
|
/* format already used as default value */
|
|
++argc;
|
|
}
|
|
else if (strcmp(args[argc], "full") == 0) {
|
|
ctx->format = QUIC_DUMP_FMT_FULL;
|
|
++argc;
|
|
}
|
|
|
|
while (*args[argc]) {
|
|
if (strcmp(args[argc], "all") == 0)
|
|
ctx->flags |= QC_CLI_FL_SHOW_ALL;
|
|
|
|
++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;
|
|
|
|
ret = chunk_appendf(&trash, "%p[%02u]/%-.12s ", qc, ctx->thr,
|
|
qc->li->bind_conf->frontend->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 "full" format. */
|
|
static void dump_quic_full(struct show_quic_ctx *ctx, struct quic_conn *qc)
|
|
{
|
|
struct quic_pktns *pktns;
|
|
struct eb64_node *node;
|
|
struct qc_stream_desc *stream;
|
|
char bufaddr[INET6_ADDRSTRLEN], bufport[6];
|
|
int expire, i, 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");
|
|
|
|
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 */
|
|
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 */
|
|
pktns = qc->ipktns;
|
|
if (pktns) {
|
|
chunk_appendf(&trash, " [initl] rx.ackrng=%-6zu tx.inflight=%-6zu",
|
|
pktns->rx.arngs.sz, pktns->tx.in_flight);
|
|
}
|
|
|
|
pktns = qc->hpktns;
|
|
if (pktns) {
|
|
chunk_appendf(&trash, " [hndshk] rx.ackrng=%-6zu tx.inflight=%-6zu\n",
|
|
pktns->rx.arngs.sz, pktns->tx.in_flight);
|
|
}
|
|
|
|
pktns = qc->apktns;
|
|
if (pktns) {
|
|
chunk_appendf(&trash, " [01rtt] rx.ackrng=%-6zu tx.inflight=%-6zu\n",
|
|
pktns->rx.arngs.sz, pktns->tx.in_flight);
|
|
}
|
|
|
|
chunk_appendf(&trash, " srtt=%-4u rttvar=%-4u rttmin=%-4u ptoc=%-4u cwnd=%-6llu"
|
|
" mcwnd=%-6llu sentpkts=%-6llu lostpkts=%-6llu\n reorderedpkts=%-6llu",
|
|
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->mcwnd, (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");
|
|
|
|
/* Streams */
|
|
node = eb64_first(&qc->streams_by_id);
|
|
i = 0;
|
|
while (node) {
|
|
stream = eb64_entry(node, struct qc_stream_desc, by_id);
|
|
node = eb64_next(node);
|
|
|
|
chunk_appendf(&trash, " | stream=%-8llu", (unsigned long long)stream->by_id.key);
|
|
chunk_appendf(&trash, " off=%-8llu ack=%-8llu",
|
|
(unsigned long long)stream->buf_offset,
|
|
(unsigned long long)stream->ack_offset);
|
|
|
|
if (!(++i % 3)) {
|
|
chunk_appendf(&trash, "\n");
|
|
i = 0;
|
|
}
|
|
}
|
|
|
|
chunk_appendf(&trash, "\n");
|
|
}
|
|
|
|
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;
|
|
|
|
/* FIXME: Don't watch the other side !*/
|
|
if (unlikely(sc_opposite(sc)->flags & SC_FL_SHUT_DONE)) {
|
|
/* 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;
|
|
|
|
/* Print legend for oneline format. */
|
|
if (ctx->format == 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", 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->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;
|
|
}
|
|
|
|
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 (ctx->format) {
|
|
case QUIC_DUMP_FMT_FULL:
|
|
dump_quic_full(ctx, qc);
|
|
break;
|
|
case QUIC_DUMP_FMT_ONELINE:
|
|
dump_quic_oneline(ctx, qc);
|
|
break;
|
|
}
|
|
|
|
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 [oneline|full] [all] : 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);
|