zephyr/subsys/net/lib/shell/udp.c
Damian Krolik 391290e67d net: shell: udp: allow sending packet when bound
"udp bind" and "udp send" commands use the same net context
variable and they fail early if the context is already used.

This prevents from using "udp send" after "udp bind", which
makes the commands hard to use for testing bidirectional
communication. Make "udp send" reuse the already bound
context if possible, and resort to allocating temporary one
otherwise.

Signed-off-by: Damian Krolik <damian.krolik@nordicsemi.no>
2025-05-27 11:51:11 +02:00

313 lines
7.0 KiB
C

/*
* Copyright (c) 2016 Intel Corporation
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(net_shell);
#include <stdlib.h>
#include "net_shell_private.h"
#if defined(CONFIG_NET_UDP) && defined(CONFIG_NET_NATIVE_UDP)
static struct net_context *udp_ctx;
static const struct shell *udp_shell;
K_SEM_DEFINE(udp_send_wait, 0, 1);
static void udp_rcvd(struct net_context *context, struct net_pkt *pkt,
union net_ip_header *ip_hdr,
union net_proto_header *proto_hdr, int status,
void *user_data)
{
if (pkt) {
size_t len = net_pkt_remaining_data(pkt);
uint8_t byte;
PR_SHELL(udp_shell, "Received UDP packet: ");
for (size_t i = 0; i < len; ++i) {
if (net_pkt_read_u8(pkt, &byte) < 0) {
break;
}
PR_SHELL(udp_shell, "%02x ", byte);
}
PR_SHELL(udp_shell, "\n");
net_pkt_unref(pkt);
}
}
static void udp_sent(struct net_context *context, int status, void *user_data)
{
ARG_UNUSED(context);
ARG_UNUSED(status);
ARG_UNUSED(user_data);
PR_SHELL(udp_shell, "Message sent\n");
k_sem_give(&udp_send_wait);
}
#endif
static int cmd_net_udp_bind(const struct shell *sh, size_t argc, char *argv[])
{
#if !defined(CONFIG_NET_UDP) || !defined(CONFIG_NET_NATIVE_UDP)
ARG_UNUSED(sh);
ARG_UNUSED(argc);
ARG_UNUSED(argv);
return -EOPNOTSUPP;
#else
char *addr_str = NULL;
char *endptr = NULL;
uint16_t port;
int ret;
struct net_if *iface;
struct sockaddr addr;
int addrlen;
if (argc < 3) {
PR_WARNING("Not enough arguments given for udp bind command\n");
return -EINVAL;
}
addr_str = argv[1];
port = strtol(argv[2], &endptr, 0);
if (endptr == argv[2]) {
PR_WARNING("Invalid port number\n");
return -EINVAL;
}
if (udp_ctx != NULL && net_context_is_used(udp_ctx)) {
PR_WARNING("Network context already in use\n");
return -EALREADY;
}
memset(&addr, 0, sizeof(addr));
ret = net_ipaddr_parse(addr_str, strlen(addr_str), &addr);
if (ret < 0) {
PR_WARNING("Cannot parse address \"%s\"\n", addr_str);
return ret;
}
ret = net_context_get(addr.sa_family, SOCK_DGRAM, IPPROTO_UDP,
&udp_ctx);
if (ret < 0) {
PR_WARNING("Cannot get UDP context (%d)\n", ret);
return ret;
}
udp_shell = sh;
if (IS_ENABLED(CONFIG_NET_IPV6) && addr.sa_family == AF_INET6) {
net_sin6(&addr)->sin6_port = htons(port);
addrlen = sizeof(struct sockaddr_in6);
iface = net_if_ipv6_select_src_iface(
&net_sin6(&addr)->sin6_addr);
} else if (IS_ENABLED(CONFIG_NET_IPV4) && addr.sa_family == AF_INET) {
net_sin(&addr)->sin_port = htons(port);
addrlen = sizeof(struct sockaddr_in);
iface = net_if_ipv4_select_src_iface(
&net_sin(&addr)->sin_addr);
} else {
PR_WARNING("IPv6 and IPv4 are disabled, cannot %s.\n", "bind");
goto release_ctx;
}
if (!iface) {
PR_WARNING("No interface to send to given host\n");
goto release_ctx;
}
net_context_set_iface(udp_ctx, iface);
ret = net_context_bind(udp_ctx, &addr, addrlen);
if (ret < 0) {
PR_WARNING("Binding to UDP port failed (%d)\n", ret);
goto release_ctx;
}
ret = net_context_recv(udp_ctx, udp_rcvd, K_NO_WAIT, NULL);
if (ret < 0) {
PR_WARNING("Receiving from UDP port failed (%d)\n", ret);
goto release_ctx;
}
return 0;
release_ctx:
ret = net_context_put(udp_ctx);
if (ret < 0) {
PR_WARNING("Cannot put UDP context (%d)\n", ret);
}
return 0;
#endif
}
static int cmd_net_udp_close(const struct shell *sh, size_t argc, char *argv[])
{
#if !defined(CONFIG_NET_UDP) || !defined(CONFIG_NET_NATIVE_UDP)
ARG_UNUSED(sh);
ARG_UNUSED(argc);
ARG_UNUSED(argv);
return -EOPNOTSUPP;
#else
int ret;
if (!udp_ctx || !net_context_is_used(udp_ctx)) {
PR_WARNING("Network context is not used. Cannot close.\n");
return -EINVAL;
}
ret = net_context_put(udp_ctx);
if (ret < 0) {
PR_WARNING("Cannot close UDP port (%d)\n", ret);
}
return 0;
#endif
}
static int cmd_net_udp_send(const struct shell *sh, size_t argc, char *argv[])
{
#if !defined(CONFIG_NET_UDP) || !defined(CONFIG_NET_NATIVE_UDP)
ARG_UNUSED(sh);
ARG_UNUSED(argc);
ARG_UNUSED(argv);
return -EOPNOTSUPP;
#else
char *host = NULL;
char *endptr = NULL;
uint16_t port;
uint8_t *payload = NULL;
int ret;
bool should_release_ctx = false;
struct net_if *iface;
struct sockaddr addr;
int addrlen;
if (argc < 4) {
PR_WARNING("Not enough arguments given for udp send command\n");
return -EINVAL;
}
host = argv[1];
port = strtol(argv[2], &endptr, 0);
payload = argv[3];
if (endptr == argv[2]) {
PR_WARNING("Invalid port number\n");
return -EINVAL;
}
memset(&addr, 0, sizeof(addr));
ret = net_ipaddr_parse(host, strlen(host), &addr);
if (ret < 0) {
PR_WARNING("Cannot parse address \"%s\"\n", host);
return ret;
}
/* Re-use already bound context if possible, or allocate temporary one. */
if (udp_ctx == NULL || !net_context_is_used(udp_ctx)) {
ret = net_context_get(addr.sa_family, SOCK_DGRAM, IPPROTO_UDP, &udp_ctx);
if (ret < 0) {
PR_WARNING("Cannot get UDP context (%d)\n", ret);
return ret;
}
should_release_ctx = true;
}
udp_shell = sh;
if (IS_ENABLED(CONFIG_NET_IPV6) && addr.sa_family == AF_INET6) {
net_sin6(&addr)->sin6_port = htons(port);
addrlen = sizeof(struct sockaddr_in6);
iface = net_if_ipv6_select_src_iface(
&net_sin6(&addr)->sin6_addr);
} else if (IS_ENABLED(CONFIG_NET_IPV4) && addr.sa_family == AF_INET) {
net_sin(&addr)->sin_port = htons(port);
addrlen = sizeof(struct sockaddr_in);
iface = net_if_ipv4_select_src_iface(
&net_sin(&addr)->sin_addr);
} else {
PR_WARNING("IPv6 and IPv4 are disabled, cannot %s.\n", "send");
goto release_ctx;
}
if (!iface) {
PR_WARNING("No interface to send to given host\n");
goto release_ctx;
}
net_context_set_iface(udp_ctx, iface);
ret = net_context_recv(udp_ctx, udp_rcvd, K_NO_WAIT, NULL);
if (ret < 0) {
PR_WARNING("Setting rcv callback failed (%d)\n", ret);
goto release_ctx;
}
ret = net_context_sendto(udp_ctx, payload, strlen(payload), &addr,
addrlen, udp_sent, K_FOREVER, NULL);
if (ret < 0) {
PR_WARNING("Sending packet failed (%d)\n", ret);
goto release_ctx;
}
ret = k_sem_take(&udp_send_wait, K_SECONDS(2));
if (ret == -EAGAIN) {
PR_WARNING("UDP packet sending failed\n");
}
release_ctx:
if (should_release_ctx) {
ret = net_context_put(udp_ctx);
if (ret < 0) {
PR_WARNING("Cannot put UDP context (%d)\n", ret);
}
}
return 0;
#endif
}
static int cmd_net_udp(const struct shell *sh, size_t argc, char *argv[])
{
ARG_UNUSED(sh);
ARG_UNUSED(argc);
ARG_UNUSED(argv);
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_udp,
SHELL_CMD(bind, NULL,
"'net udp bind <addr> <port>' binds to UDP local port.",
cmd_net_udp_bind),
SHELL_CMD(close, NULL,
"'net udp close' closes previously bound port.",
cmd_net_udp_close),
SHELL_CMD(send, NULL,
"'net udp send <host> <port> <payload>' "
"sends UDP packet to a network host.",
cmd_net_udp_send),
SHELL_SUBCMD_SET_END
);
SHELL_SUBCMD_ADD((net), udp, &net_cmd_udp,
"Send/recv UDP packet",
cmd_net_udp, 1, 0);