blob: f8d279947aedf0ec2d61d77a74cd5279d6e88b03 [file]
/*
* Copyright (c) 2026 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(net_shell);
#include <zephyr/kernel.h>
#include <zephyr/net/net_if.h>
#include "net_shell_private.h"
#include <inttypes.h>
#if defined(CONFIG_QUIC_SHELL)
#include <zephyr/net/quic.h>
#include "quic/quic_internal.h"
static void quic_endpoint_cb(struct quic_endpoint *ep, void *user_data)
{
struct net_shell_user_data *data = user_data;
const struct shell *sh = data->sh;
int *count = data->user_data;
/* +7 for []:port */
char addr_local[ADDR_LEN + 7];
char addr_remote[ADDR_LEN + 7] = "";
if ((*count) == 0) {
PR("Endpoints Ref Sock %-6s %-16s\t Remote\n", "Role", "Local");
}
if (IS_ENABLED(CONFIG_NET_IPV6) && ep->local_addr.ss_family == NET_AF_INET6) {
snprintk(addr_local, sizeof(addr_local), "[%s]:%u",
net_sprint_ipv6_addr(
&net_sin6(net_sad(&ep->local_addr))->sin6_addr),
net_ntohs(net_sin6(net_sad(&ep->local_addr))->sin6_port));
snprintk(addr_remote, sizeof(addr_remote), "[%s]:%u",
net_sprint_ipv6_addr(
&net_sin6(net_sad(&ep->remote_addr))->sin6_addr),
net_ntohs(net_sin6(net_sad(&ep->remote_addr))->sin6_port));
} else if (IS_ENABLED(CONFIG_NET_IPV4) && ep->local_addr.ss_family == NET_AF_INET) {
snprintk(addr_local, sizeof(addr_local), "%s:%d",
net_sprint_ipv4_addr(
&net_sin(net_sad(&ep->local_addr))->sin_addr),
net_ntohs(net_sin(net_sad(&ep->local_addr))->sin_port));
/* Check if we need to print the v4-mapping-to-v6 address */
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) &&
net_sin(net_sad(&ep->remote_addr))->sin_family == NET_AF_INET6 &&
net_ipv6_addr_is_v4_mapped(&net_sin6(net_sad(&ep->remote_addr))->sin6_addr)) {
snprintk(addr_remote, sizeof(addr_remote), "[%s]:%d",
net_sprint_ipv6_addr(
&net_sin6(net_sad(&ep->remote_addr))->sin6_addr),
net_ntohs(net_sin6(net_sad(&ep->remote_addr))->sin6_port));
} else {
snprintk(addr_remote, sizeof(addr_remote), "%s:%d",
net_sprint_ipv4_addr(
&net_sin(net_sad(&ep->remote_addr))->sin_addr),
net_ntohs(net_sin(net_sad(&ep->remote_addr))->sin_port));
}
}
PR("[%2d] %d %-2d %-6s %-16s\t %-16s\n",
(*count) + 1, (int)ep->refcount, ep->sock, ep->is_server ? "server" : "client",
addr_local, addr_remote);
(*count)++;
}
static void quic_stream_cb(struct quic_stream *stream, void *user_data)
{
struct net_shell_user_data *data = user_data;
const struct shell *sh = data->sh;
int *count = data->user_data;
char id_str[sizeof("18446744073709551615")];
if ((*count) == 0) {
PR("Streams Ref Ctx Ep Sock %-4s Role\t %-9s\n",
"Dir", "Id");
}
if (stream->id == QUIC_STREAM_ID_UNASSIGNED) {
snprintk(id_str, sizeof(id_str), "<waiting connection>");
} else {
snprintk(id_str, sizeof(id_str), "%" PRIu64, stream->id);
}
PR("[%2d] %d %-2d %-2d %-4d %-4s %s\t %s\n",
(*count) + 1, (int)stream->refcount, stream->conn->id,
stream->ep == NULL ? -1 : stream->ep->sock, stream->sock,
((stream->type & 0x02) == 0) ? "bidi" : "uni",
/* Note that the initiator bit tells the initiator, which
* is the opposite of the role.
*/
((stream->type & 0x01) == 0) ? "server" : "client",
id_str);
(*count)++;
}
static void quic_context_endpoint_cb(struct quic_endpoint *ep, void *user_data)
{
struct net_shell_user_data *data = user_data;
const struct shell *sh = data->sh;
int *count = data->user_data;
/* +7 for []:port */
char addr_local[ADDR_LEN + 7];
char addr_remote[ADDR_LEN + 7] = "";
if ((*count) == 0) {
PR(" Endpoints Ref %-16s\t Remote\n", "Local");
}
if (IS_ENABLED(CONFIG_NET_IPV6) && ep->local_addr.ss_family == NET_AF_INET6) {
snprintk(addr_local, sizeof(addr_local), "[%s]:%u",
net_sprint_ipv6_addr(
&net_sin6(net_sad(&ep->local_addr))->sin6_addr),
net_ntohs(net_sin6(net_sad(&ep->local_addr))->sin6_port));
snprintk(addr_remote, sizeof(addr_remote), "[%s]:%u",
net_sprint_ipv6_addr(
&net_sin6(net_sad(&ep->remote_addr))->sin6_addr),
net_ntohs(net_sin6(net_sad(&ep->remote_addr))->sin6_port));
} else if (IS_ENABLED(CONFIG_NET_IPV4) && ep->local_addr.ss_family == NET_AF_INET) {
snprintk(addr_local, sizeof(addr_local), "%s:%d",
net_sprint_ipv4_addr(
&net_sin(net_sad(&ep->local_addr))->sin_addr),
net_ntohs(net_sin(net_sad(&ep->local_addr))->sin_port));
/* Check if we need to print the v4-mapping-to-v6 address */
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6) &&
net_sin(net_sad(&ep->remote_addr))->sin_family == NET_AF_INET6 &&
net_ipv6_addr_is_v4_mapped(&net_sin6(net_sad(&ep->remote_addr))->sin6_addr)) {
snprintk(addr_remote, sizeof(addr_remote), "[%s]:%d",
net_sprint_ipv6_addr(
&net_sin6(net_sad(&ep->remote_addr))->sin6_addr),
net_ntohs(net_sin6(net_sad(&ep->remote_addr))->sin6_port));
} else {
snprintk(addr_remote, sizeof(addr_remote), "%s:%d",
net_sprint_ipv4_addr(
&net_sin(net_sad(&ep->remote_addr))->sin_addr),
net_ntohs(net_sin(net_sad(&ep->remote_addr))->sin_port));
}
}
PR(" [%2d] %d %-16s\t %-16s\n",
(*count) + 1, (int)ep->refcount, addr_local, addr_remote);
(*count)++;
}
static void quic_context_stream_cb(struct quic_stream *stream, void *user_data)
{
struct net_shell_user_data *data = user_data;
const struct shell *sh = data->sh;
int *count = data->user_data;
char id_str[sizeof("18446744073709551615")]; /* Max uint64_t in decimal */
if ((*count) == 0) {
PR(" Streams Ref Sock %-4s Role\t %-9s\n",
"Dir", "Id");
}
if (stream->id == QUIC_STREAM_ID_UNASSIGNED) {
snprintk(id_str, sizeof(id_str), "<waiting connection>");
} else {
snprintk(id_str, sizeof(id_str), "%" PRIu64, stream->id);
}
PR(" [%2d] %d %-4d %-4s %s\t %s\n",
(*count) + 1, (int)stream->refcount, stream->sock,
((stream->type & 0x02) == 0) ? "bidi" : "uni",
/* Note that the initiator bit tells the initiator, which
* is the opposite of the role.
*/
((stream->type & 0x01) == 0) ? "server" : "client",
id_str);
(*count)++;
}
static void quic_context_cb(struct quic_context *context,
void *user_data)
{
struct net_shell_user_data *data = user_data;
const struct shell *sh = data->sh;
int *count = data->user_data;
int extra_count = 0;
struct net_shell_user_data extra_data = {
.sh = sh,
.user_data = &extra_count,
};
if ((*count) == 0) {
PR("Context Id Sock\n");
}
if ((*count) > 0) {
PR("\n");
}
PR("[%2d] %-2d %-4d\n",
(*count) + 1, context->id, context->sock);
quic_context_endpoint_foreach(context, quic_context_endpoint_cb, &extra_data);
extra_count = 0;
quic_context_stream_foreach(context, quic_context_stream_cb, &extra_data);
(*count)++;
}
#endif /* CONFIG_QUIC_SHELL */
static int cmd_net_quic_streams(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_QUIC_SHELL)
struct net_shell_user_data user_data;
int count = 0;
ARG_UNUSED(argc);
ARG_UNUSED(argv);
user_data.sh = sh;
user_data.user_data = &count;
quic_stream_foreach(quic_stream_cb, &user_data);
if (count == 0) {
PR("No streams\n");
}
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_QUIC_SHELL",
"Quic");
#endif /* CONFIG_QUIC_SHELL */
return 0;
}
static int cmd_net_quic_endpoints(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_QUIC_SHELL)
struct net_shell_user_data user_data;
int count = 0;
ARG_UNUSED(argc);
ARG_UNUSED(argv);
user_data.sh = sh;
user_data.user_data = &count;
quic_endpoint_foreach(quic_endpoint_cb, &user_data);
if (count == 0) {
PR("No endpoints\n");
}
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_QUIC_SHELL",
"Quic");
#endif /* CONFIG_QUIC_SHELL */
return 0;
}
static int cmd_net_quic(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_QUIC_SHELL)
struct net_shell_user_data user_data;
int count = 0;
ARG_UNUSED(argc);
ARG_UNUSED(argv);
user_data.sh = sh;
user_data.user_data = &count;
quic_context_foreach(quic_context_cb, &user_data);
if (count == 0) {
PR("No connections\n");
}
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_QUIC_SHELL",
"Quic");
#endif /* CONFIG_QUIC_SHELL */
return 0;
}
#if defined(CONFIG_NET_STATISTICS_QUIC) && defined(CONFIG_NET_STATISTICS_USER_API)
#include "quic_stats.h"
static uint64_t quic_shell_uptime_ms(void)
{
int64_t uptime_ms = k_uptime_get();
return uptime_ms > 0 ? (uint64_t)uptime_ms : 0U;
}
static bool quic_addr_is_unspecified(const struct net_sockaddr *addr)
{
if (addr == NULL) {
return true;
}
if (addr->sa_family == NET_AF_INET) {
return net_ipv4_is_addr_unspecified(&net_sin(addr)->sin_addr);
}
if (addr->sa_family == NET_AF_INET6) {
return net_ipv6_is_addr_unspecified(&net_sin6(addr)->sin6_addr);
}
return true;
}
static void quic_resolve_local_addr(const struct net_sockaddr_storage *local,
const struct net_sockaddr_storage *remote,
struct net_sockaddr_storage *resolved)
{
if (resolved == NULL) {
return;
}
memset(resolved, 0, sizeof(*resolved));
if (local == NULL) {
return;
}
memcpy(resolved, local, sizeof(*resolved));
if (!quic_addr_is_unspecified(net_sad(local)) || remote == NULL) {
return;
}
if (local->ss_family == NET_AF_INET && remote->ss_family == NET_AF_INET) {
const struct net_in_addr *src = NULL;
(void)net_if_ipv4_select_src_iface_addr(&net_sin(net_sad(remote))->sin_addr, &src);
if (src != NULL) {
net_ipaddr_copy(&net_sin(net_sad(resolved))->sin_addr, src);
}
return;
}
if (local->ss_family == NET_AF_INET6 && remote->ss_family == NET_AF_INET6) {
const struct net_in6_addr *src6 = NULL;
if (net_ipv6_addr_is_v4_mapped(&net_sin6(net_sad(remote))->sin6_addr)) {
struct net_in_addr dst4;
const struct net_in_addr *src4 = NULL;
struct net_in6_addr mapped;
net_ipv6_addr_get_v4_mapped(&net_sin6(net_sad(remote))->sin6_addr, &dst4);
(void)net_if_ipv4_select_src_iface_addr(&dst4, &src4);
if (src4 != NULL) {
net_ipv6_addr_create_v4_mapped(src4, &mapped);
net_ipaddr_copy(&net_sin6(net_sad(resolved))->sin6_addr, &mapped);
}
return;
}
(void)net_if_ipv6_select_src_iface_addr(&net_sin6(net_sad(remote))->sin6_addr,
&src6);
if (src6 != NULL) {
net_ipaddr_copy(&net_sin6(net_sad(resolved))->sin6_addr, src6);
}
}
}
static void quic_format_addr(const struct net_sockaddr_storage *addr, char *buf, size_t len)
{
if (addr == NULL || buf == NULL || len == 0U || addr->ss_family == 0) {
if (buf != NULL && len > 0U) {
snprintk(buf, len, "-");
}
return;
}
if (IS_ENABLED(CONFIG_NET_IPV6) && addr->ss_family == NET_AF_INET6) {
snprintk(buf, len, "[%s]:%u",
net_sprint_ipv6_addr(&net_sin6(net_sad(addr))->sin6_addr),
net_ntohs(net_sin6(net_sad(addr))->sin6_port));
return;
}
if (IS_ENABLED(CONFIG_NET_IPV4) && addr->ss_family == NET_AF_INET) {
snprintk(buf, len, "%s:%u",
net_sprint_ipv4_addr(&net_sin(net_sad(addr))->sin_addr),
net_ntohs(net_sin(net_sad(addr))->sin_port));
return;
}
snprintk(buf, len, "-");
}
static void print_quic_stats_values(const struct net_stats_quic *stats, const struct shell *sh)
{
PR("Handshake init RX : %u\n", stats->handshake_init_rx);
PR("Handshake init TX : %u\n", stats->handshake_init_tx);
PR("Handshake resp RX : %u\n", stats->handshake_resp_rx);
PR("Handshake resp TX : %u\n", stats->handshake_resp_tx);
PR("Peer not found : %u\n", stats->peer_not_found);
PR("Invalid packet : %u\n", stats->invalid_packet);
PR("Invalid key : %u\n", stats->invalid_key);
PR("Invalid packet len : %u\n", stats->invalid_packet_len);
PR("Invalid handshake : %u\n", stats->invalid_handshake);
PR("Decrypt failed : %u\n", stats->decrypt_failed);
PR("Dropped RX : %u\n", stats->drop_rx);
PR("Dropped TX : %u\n", stats->drop_tx);
PR("Allocation failed : %u\n", stats->alloc_failed);
PR("RX data packets : %u\n", stats->valid_rx);
PR("TX data packets : %u\n", stats->valid_tx);
PR("\n");
}
static void print_active_quic_stats(struct quic_context *ctx, const struct shell *sh)
{
char addr_local[ADDR_LEN + 7];
char addr_remote[ADDR_LEN + 7];
struct net_sockaddr_storage local_addr;
uint64_t age_ms = quic_shell_uptime_ms();
quic_resolve_local_addr(ctx->stats_metadata_valid ? &ctx->stats_local_addr : NULL,
ctx->stats_metadata_valid ? &ctx->stats_remote_addr : NULL,
&local_addr);
quic_format_addr(ctx->stats_metadata_valid ? &local_addr : NULL,
addr_local, sizeof(addr_local));
quic_format_addr(ctx->stats_metadata_valid ? &ctx->stats_remote_addr : NULL,
addr_remote, sizeof(addr_remote));
if (age_ms > ctx->stats_started_at_ms) {
age_ms -= ctx->stats_started_at_ms;
} else {
age_ms = 0U;
}
PR("Statistics for Quic context %d (sock %d)\n", ctx->id, ctx->sock);
PR("Role : %s\n", ctx->stats_is_server ? "server" : "client");
PR("Age : %llu ms\n", age_ms);
PR("Local : %s\n", addr_local);
PR("Remote : %s\n", addr_remote);
PR("Error code : %d\n", ctx->error_code);
print_quic_stats_values(&ctx->stats, sh);
}
static void context_stats_cb(struct quic_context *context, void *user_data)
{
struct net_shell_user_data *data = user_data;
const struct shell *sh = data->sh;
int *count = data->user_data;
if (context->is_listening) {
return;
}
print_active_quic_stats(context, sh);
(*count)++;
}
#if defined(CONFIG_QUIC_STATS_HISTORY)
static void print_closed_quic_stats(const struct quic_closed_context_stats *stats,
const struct shell *sh)
{
char addr_local[ADDR_LEN + 7];
char addr_remote[ADDR_LEN + 7];
struct net_sockaddr_storage local_addr;
quic_resolve_local_addr(&stats->local_addr, &stats->remote_addr, &local_addr);
quic_format_addr(&local_addr, addr_local, sizeof(addr_local));
quic_format_addr(&stats->remote_addr, addr_remote, sizeof(addr_remote));
PR("Statistics for closed Quic context %d\n", stats->id);
PR("Role : %s\n", stats->is_server ? "server" : "client");
PR("Lifetime : %llu ms\n", stats->duration_ms);
PR("Local : %s\n", addr_local);
PR("Remote : %s\n", addr_remote);
PR("Error code : %d\n", stats->error_code);
print_quic_stats_values(&stats->stats, sh);
}
static void closed_context_stats_cb(const struct quic_closed_context_stats *stats, void *user_data)
{
struct net_shell_user_data *data = user_data;
const struct shell *sh = data->sh;
int *count = data->user_data;
print_closed_quic_stats(stats, sh);
(*count)++;
}
#endif /* CONFIG_QUIC_STATS_HISTORY */
#endif /* CONFIG_NET_STATISTICS_QUIC && CONFIG_NET_STATISTICS_USER_API */
static int cmd_net_quic_stats(const struct shell *sh, size_t argc, char *argv[])
{
#if defined(CONFIG_NET_STATISTICS_QUIC) && defined(CONFIG_NET_STATISTICS_USER_API)
struct net_shell_user_data user_data;
int active_count = 0;
#if defined(CONFIG_QUIC_STATS_HISTORY)
int closed_count = 0;
#endif
ARG_UNUSED(argc);
ARG_UNUSED(argv);
user_data.sh = sh;
PR("Global Quic statistics\n");
PR("Packets RX : %u\n", quic_stats->packets_rx);
PR("Connections opened : %u\n", quic_stats->connections_opened);
PR("Connections closed : %u\n", quic_stats->connections_closed);
PR("Streams opened : %u\n", quic_stats->streams_opened);
PR("Streams closed : %u\n", quic_stats->streams_closed);
PR("Conn open failed : %u\n", quic_stats->connection_open_failed);
PR("Stream open failed : %u\n", quic_stats->stream_open_failed);
PR("Conn close failed : %u\n", quic_stats->connection_close_failed);
PR("Stream close failed : %u\n", quic_stats->stream_close_failed);
PR("\n");
PR("Active Quic connection statistics\n");
user_data.user_data = &active_count;
quic_context_foreach(context_stats_cb, &user_data);
if (active_count == 0) {
PR("No active connections\n");
}
#if defined(CONFIG_QUIC_STATS_HISTORY)
PR("\nClosed Quic connection statistics\n");
user_data.user_data = &closed_count;
quic_closed_context_stats_foreach(closed_context_stats_cb, &user_data);
if (closed_count == 0) {
PR("No closed connections\n");
}
#endif
#else
PR_INFO("Set %s to enable %s support.\n",
"CONFIG_NET_STATISTICS_QUIC, CONFIG_NET_STATISTICS_USER_API and CONFIG_QUIC",
"Quic statistics");
#endif /* CONFIG_NET_STATISTICS_QUIC */
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_quic,
SHELL_CMD_ARG(endpoints, NULL,
SHELL_HELP("Show information about Quic endpoints", ""),
cmd_net_quic_endpoints, 1, 0),
SHELL_CMD_ARG(streams, NULL,
SHELL_HELP("Show information about Quic streams", ""),
cmd_net_quic_streams, 1, 0),
SHELL_CMD_ARG(stats, NULL,
SHELL_HELP("Show statistics information for Quic connections", ""),
cmd_net_quic_stats, 1, 1),
SHELL_SUBCMD_SET_END
);
SHELL_SUBCMD_ADD((net), quic, &net_cmd_quic,
"Print information about Quic connections.",
cmd_net_quic, 1, 0);