| /* |
| * 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 net_sockaddr_storage addr = { 0 }; |
| struct net_sockaddr *sa = net_sad(&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), sa); |
| if (ret < 0) { |
| PR_WARNING("Cannot parse address \"%s\"\n", addr_str); |
| return ret; |
| } |
| |
| ret = net_context_get(addr.ss_family, NET_SOCK_DGRAM, NET_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.ss_family == NET_AF_INET6) { |
| net_sin6(sa)->sin6_port = net_htons(port); |
| addrlen = sizeof(struct net_sockaddr_in6); |
| |
| iface = net_if_ipv6_select_src_iface( |
| &net_sin6(sa)->sin6_addr); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && addr.ss_family == NET_AF_INET) { |
| net_sin(sa)->sin_port = net_htons(port); |
| addrlen = sizeof(struct net_sockaddr_in); |
| |
| iface = net_if_ipv4_select_src_iface( |
| &net_sin(sa)->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, sa, 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 net_sockaddr_storage addr = { 0 }; |
| struct net_sockaddr *sa = net_sad(&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), sa); |
| 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.ss_family, NET_SOCK_DGRAM, NET_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.ss_family == NET_AF_INET6) { |
| net_sin6(sa)->sin6_port = net_htons(port); |
| addrlen = sizeof(struct net_sockaddr_in6); |
| |
| iface = net_if_ipv6_select_src_iface( |
| &net_sin6(sa)->sin6_addr); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && addr.ss_family == NET_AF_INET) { |
| net_sin(sa)->sin_port = net_htons(port); |
| addrlen = sizeof(struct net_sockaddr_in); |
| |
| iface = net_if_ipv4_select_src_iface( |
| &net_sin(sa)->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), sa, |
| 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_dplpmtud(const struct shell *sh, size_t argc, char *argv[]) |
| { |
| #if !defined(CONFIG_NET_UDP) || !defined(CONFIG_NET_NATIVE_UDP) || \ |
| !defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| ARG_UNUSED(sh); |
| ARG_UNUSED(argc); |
| ARG_UNUSED(argv); |
| |
| PR_INFO("Set %s to enable %s support.\n", |
| "CONFIG_NET_UDP_OPTIONS_DPLPMTUD", "DPLPMTUD over UDP options"); |
| return -EOPNOTSUPP; |
| #else |
| char *host = NULL; |
| char *endptr = NULL; |
| long port_long; |
| uint16_t port; |
| struct net_if *iface; |
| struct net_sockaddr_storage addr; |
| struct net_sockaddr *sa = net_sad(&addr); |
| int addrlen; |
| int enable = 1; |
| int ret; |
| |
| if (argc < 3) { |
| PR_WARNING("Usage: net udp dplpmtud <host> <port>\n"); |
| return -EINVAL; |
| } |
| |
| host = argv[1]; |
| port_long = strtol(argv[2], &endptr, 0); |
| if (endptr == argv[2] || *endptr != '\0' || |
| port_long <= 0 || port_long > 65535) { |
| PR_WARNING("Invalid port number\n"); |
| return -EINVAL; |
| } |
| |
| port = (uint16_t)port_long; |
| |
| if (udp_ctx != NULL && net_context_is_used(udp_ctx)) { |
| PR_WARNING("Network context already in use, run 'net udp close' first\n"); |
| return -EALREADY; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| |
| ret = net_ipaddr_parse(host, strlen(host), sa); |
| if (ret < 0) { |
| PR_WARNING("Cannot parse address \"%s\"\n", host); |
| return -EINVAL; |
| } |
| |
| ret = net_context_get(addr.ss_family, NET_SOCK_DGRAM, NET_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.ss_family == NET_AF_INET6) { |
| net_sin6(sa)->sin6_port = net_htons(port); |
| addrlen = sizeof(struct net_sockaddr_in6); |
| iface = net_if_ipv6_select_src_iface(&net_sin6(sa)->sin6_addr); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && addr.ss_family == NET_AF_INET) { |
| net_sin(sa)->sin_port = net_htons(port); |
| addrlen = sizeof(struct net_sockaddr_in); |
| iface = net_if_ipv4_select_src_iface(&net_sin(sa)->sin_addr); |
| } else { |
| PR_WARNING("IPv6 and IPv4 are disabled, cannot probe.\n"); |
| ret = -EAFNOSUPPORT; |
| goto release_ctx; |
| } |
| |
| if (iface == NULL) { |
| PR_WARNING("No interface to reach given host\n"); |
| ret = -ENETUNREACH; |
| goto release_ctx; |
| } |
| |
| net_context_set_iface(udp_ctx, iface); |
| |
| /* A connected UDP context gives the prober a fixed destination. */ |
| ret = net_context_connect(udp_ctx, sa, addrlen, NULL, K_NO_WAIT, NULL); |
| if (ret < 0) { |
| PR_WARNING("Cannot connect UDP context (%d)\n", ret); |
| goto release_ctx; |
| } |
| |
| /* Receiving is needed so the prober sees the RES echoes. */ |
| 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; |
| } |
| |
| /* Enabling DPLPMTUD on a connected context makes the prober emit an |
| * initial BASE_PLPMTU probe (a DF-set, REQ-bearing UDP datagram padded |
| * via the options surplus area) right away and keep probing upward on |
| * its timer. |
| */ |
| ret = net_context_set_option(udp_ctx, NET_OPT_UDP_OPT_DPLPMTUD, |
| &enable, sizeof(enable)); |
| if (ret < 0) { |
| PR_WARNING("Cannot enable DPLPMTUD (%d)\n", ret); |
| goto release_ctx; |
| } |
| |
| PR("DPLPMTUD probing started to %s port %u.\n", host, port); |
| PR("Use 'net pmtu' to see the discovered MTU, 'net udp close' to stop.\n"); |
| |
| return 0; |
| |
| release_ctx: |
| (void)net_context_put(udp_ctx); |
| udp_ctx = NULL; |
| |
| return ret; |
| #endif |
| } |
| |
| static int cmd_net_udp_dplpmtud_server(const struct shell *sh, size_t argc, |
| char *argv[]) |
| { |
| #if !defined(CONFIG_NET_UDP) || !defined(CONFIG_NET_NATIVE_UDP) || \ |
| !defined(CONFIG_NET_UDP_OPTIONS_DPLPMTUD) |
| ARG_UNUSED(sh); |
| ARG_UNUSED(argc); |
| ARG_UNUSED(argv); |
| |
| PR_INFO("Set %s to enable %s support.\n", |
| "CONFIG_NET_UDP_OPTIONS_DPLPMTUD", "DPLPMTUD over UDP options"); |
| return -EOPNOTSUPP; |
| #else |
| char *addr_str = NULL; |
| char *endptr = NULL; |
| long port_long; |
| uint16_t port; |
| struct net_if *iface; |
| struct net_sockaddr_storage addr; |
| struct net_sockaddr *sa = net_sad(&addr); |
| int addrlen; |
| int enable = 1; |
| int ret; |
| |
| if (argc < 3) { |
| PR_WARNING("Usage: net udp dplpmtud_server <addr> <port>\n"); |
| return -EINVAL; |
| } |
| |
| addr_str = argv[1]; |
| port_long = strtol(argv[2], &endptr, 0); |
| if (endptr == argv[2] || *endptr != '\0' || |
| port_long <= 0 || port_long > 65535) { |
| PR_WARNING("Invalid port number\n"); |
| return -EINVAL; |
| } |
| |
| port = (uint16_t)port_long; |
| |
| if (udp_ctx != NULL && net_context_is_used(udp_ctx)) { |
| PR_WARNING("Network context already in use, run 'net udp close' first\n"); |
| return -EALREADY; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| |
| ret = net_ipaddr_parse(addr_str, strlen(addr_str), sa); |
| if (ret < 0) { |
| PR_WARNING("Cannot parse address \"%s\"\n", addr_str); |
| return -EINVAL; |
| } |
| |
| ret = net_context_get(addr.ss_family, NET_SOCK_DGRAM, NET_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.ss_family == NET_AF_INET6) { |
| net_sin6(sa)->sin6_port = net_htons(port); |
| addrlen = sizeof(struct net_sockaddr_in6); |
| iface = net_if_ipv6_select_src_iface(&net_sin6(sa)->sin6_addr); |
| } else if (IS_ENABLED(CONFIG_NET_IPV4) && addr.ss_family == NET_AF_INET) { |
| net_sin(sa)->sin_port = net_htons(port); |
| addrlen = sizeof(struct net_sockaddr_in); |
| iface = net_if_ipv4_select_src_iface(&net_sin(sa)->sin_addr); |
| } else { |
| PR_WARNING("IPv6 and IPv4 are disabled, cannot bind.\n"); |
| ret = -EAFNOSUPPORT; |
| goto release_ctx; |
| } |
| |
| if (iface == NULL) { |
| PR_WARNING("No interface for the given address\n"); |
| ret = -ENETUNREACH; |
| goto release_ctx; |
| } |
| |
| net_context_set_iface(udp_ctx, iface); |
| |
| ret = net_context_bind(udp_ctx, sa, addrlen); |
| if (ret < 0) { |
| PR_WARNING("Binding to UDP port failed (%d)\n", ret); |
| goto release_ctx; |
| } |
| |
| /* Receiving is what lets the responder see incoming REQ options. */ |
| 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; |
| } |
| |
| /* Enabling DPLPMTUD on an unconnected (bound) socket turns on the |
| * responder role only: it echoes each received Request (REQ) token |
| * back in a Response (RES) option. No probing is started because there |
| * is no fixed destination. |
| */ |
| ret = net_context_set_option(udp_ctx, NET_OPT_UDP_OPT_DPLPMTUD, |
| &enable, sizeof(enable)); |
| if (ret < 0) { |
| PR_WARNING("Cannot enable DPLPMTUD (%d)\n", ret); |
| goto release_ctx; |
| } |
| |
| PR("DPLPMTUD responder listening on %s port %u.\n", addr_str, port); |
| PR("Use 'net udp close' to stop.\n"); |
| |
| return 0; |
| |
| release_ctx: |
| (void)net_context_put(udp_ctx); |
| udp_ctx = NULL; |
| |
| return ret; |
| #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, |
| SHELL_HELP("Binds to UDP local port", "<addr> <port>"), |
| cmd_net_udp_bind), |
| SHELL_CMD(close, NULL, |
| SHELL_HELP("Closes previously bound port", ""), |
| cmd_net_udp_close), |
| SHELL_CMD(send, NULL, |
| SHELL_HELP("Sends UDP packet to a network host", |
| "<host> <port> <payload>"), |
| cmd_net_udp_send), |
| SHELL_CMD(dplpmtud, NULL, |
| SHELL_HELP("Start DPLPMTUD (RFC 9869) probing to a host " |
| "using UDP options", "<host> <port>"), |
| cmd_net_udp_dplpmtud), |
| SHELL_CMD(dplpmtud_server, NULL, |
| SHELL_HELP("Bind and act as a DPLPMTUD (RFC 9869) responder, " |
| "echoing UDP-options probes", "<addr> <port>"), |
| cmd_net_udp_dplpmtud_server), |
| SHELL_SUBCMD_SET_END |
| ); |
| |
| SHELL_SUBCMD_ADD((net), udp, &net_cmd_udp, |
| "Send/recv UDP packet", |
| cmd_net_udp, 1, 0); |