Move "tcp" command to a separate file. Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
415 lines
9.6 KiB
C
415 lines
9.6 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 "common.h"
|
|
|
|
#if defined(CONFIG_NET_TCP) && defined(CONFIG_NET_NATIVE_TCP)
|
|
static struct net_context *tcp_ctx;
|
|
static const struct shell *tcp_shell;
|
|
|
|
#define TCP_CONNECT_TIMEOUT K_SECONDS(5) /* ms */
|
|
#define TCP_TIMEOUT K_SECONDS(2) /* ms */
|
|
|
|
static void tcp_connected(struct net_context *context,
|
|
int status,
|
|
void *user_data)
|
|
{
|
|
if (status < 0) {
|
|
PR_SHELL(tcp_shell, "TCP connection failed (%d)\n", status);
|
|
|
|
net_context_put(context);
|
|
|
|
tcp_ctx = NULL;
|
|
} else {
|
|
PR_SHELL(tcp_shell, "TCP connected\n");
|
|
}
|
|
}
|
|
|
|
static void get_my_ipv6_addr(struct net_if *iface,
|
|
struct sockaddr *myaddr)
|
|
{
|
|
#if defined(CONFIG_NET_IPV6)
|
|
const struct in6_addr *my6addr;
|
|
|
|
my6addr = net_if_ipv6_select_src_addr(iface,
|
|
&net_sin6(myaddr)->sin6_addr);
|
|
|
|
memcpy(&net_sin6(myaddr)->sin6_addr, my6addr, sizeof(struct in6_addr));
|
|
|
|
net_sin6(myaddr)->sin6_port = 0U; /* let the IP stack to select */
|
|
#endif
|
|
}
|
|
|
|
static void get_my_ipv4_addr(struct net_if *iface,
|
|
struct sockaddr *myaddr)
|
|
{
|
|
#if defined(CONFIG_NET_NATIVE_IPV4)
|
|
/* Just take the first IPv4 address of an interface. */
|
|
memcpy(&net_sin(myaddr)->sin_addr,
|
|
&iface->config.ip.ipv4->unicast[0].address.in_addr,
|
|
sizeof(struct in_addr));
|
|
|
|
net_sin(myaddr)->sin_port = 0U; /* let the IP stack to select */
|
|
#endif
|
|
}
|
|
|
|
static void print_connect_info(const struct shell *sh,
|
|
int family,
|
|
struct sockaddr *myaddr,
|
|
struct sockaddr *addr)
|
|
{
|
|
switch (family) {
|
|
case AF_INET:
|
|
if (IS_ENABLED(CONFIG_NET_IPV4)) {
|
|
PR("Connecting from %s:%u ",
|
|
net_sprint_ipv4_addr(&net_sin(myaddr)->sin_addr),
|
|
ntohs(net_sin(myaddr)->sin_port));
|
|
PR("to %s:%u\n",
|
|
net_sprint_ipv4_addr(&net_sin(addr)->sin_addr),
|
|
ntohs(net_sin(addr)->sin_port));
|
|
} else {
|
|
PR_INFO("IPv4 not supported\n");
|
|
}
|
|
|
|
break;
|
|
|
|
case AF_INET6:
|
|
if (IS_ENABLED(CONFIG_NET_IPV6)) {
|
|
PR("Connecting from [%s]:%u ",
|
|
net_sprint_ipv6_addr(&net_sin6(myaddr)->sin6_addr),
|
|
ntohs(net_sin6(myaddr)->sin6_port));
|
|
PR("to [%s]:%u\n",
|
|
net_sprint_ipv6_addr(&net_sin6(addr)->sin6_addr),
|
|
ntohs(net_sin6(addr)->sin6_port));
|
|
} else {
|
|
PR_INFO("IPv6 not supported\n");
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
PR_WARNING("Unknown protocol family (%d)\n", family);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void tcp_connect(const struct shell *sh, char *host, uint16_t port,
|
|
struct net_context **ctx)
|
|
{
|
|
struct net_if *iface = net_if_get_default();
|
|
struct sockaddr myaddr;
|
|
struct sockaddr addr;
|
|
struct net_nbr *nbr;
|
|
int addrlen;
|
|
int family;
|
|
int ret;
|
|
|
|
if (IS_ENABLED(CONFIG_NET_IPV6) && !IS_ENABLED(CONFIG_NET_IPV4)) {
|
|
ret = net_addr_pton(AF_INET6, host,
|
|
&net_sin6(&addr)->sin6_addr);
|
|
if (ret < 0) {
|
|
PR_WARNING("Invalid IPv6 address\n");
|
|
return;
|
|
}
|
|
|
|
net_sin6(&addr)->sin6_port = htons(port);
|
|
addrlen = sizeof(struct sockaddr_in6);
|
|
|
|
nbr = net_ipv6_nbr_lookup(NULL, &net_sin6(&addr)->sin6_addr);
|
|
if (nbr) {
|
|
iface = nbr->iface;
|
|
}
|
|
|
|
get_my_ipv6_addr(iface, &myaddr);
|
|
family = addr.sa_family = myaddr.sa_family = AF_INET6;
|
|
|
|
} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
|
|
!IS_ENABLED(CONFIG_NET_IPV6)) {
|
|
ARG_UNUSED(nbr);
|
|
|
|
ret = net_addr_pton(AF_INET, host, &net_sin(&addr)->sin_addr);
|
|
if (ret < 0) {
|
|
PR_WARNING("Invalid IPv4 address\n");
|
|
return;
|
|
}
|
|
|
|
get_my_ipv4_addr(iface, &myaddr);
|
|
net_sin(&addr)->sin_port = htons(port);
|
|
addrlen = sizeof(struct sockaddr_in);
|
|
family = addr.sa_family = myaddr.sa_family = AF_INET;
|
|
} else if (IS_ENABLED(CONFIG_NET_IPV6) &&
|
|
IS_ENABLED(CONFIG_NET_IPV4)) {
|
|
ret = net_addr_pton(AF_INET6, host,
|
|
&net_sin6(&addr)->sin6_addr);
|
|
if (ret < 0) {
|
|
ret = net_addr_pton(AF_INET, host,
|
|
&net_sin(&addr)->sin_addr);
|
|
if (ret < 0) {
|
|
PR_WARNING("Invalid IP address\n");
|
|
return;
|
|
}
|
|
|
|
net_sin(&addr)->sin_port = htons(port);
|
|
addrlen = sizeof(struct sockaddr_in);
|
|
|
|
get_my_ipv4_addr(iface, &myaddr);
|
|
family = addr.sa_family = myaddr.sa_family = AF_INET;
|
|
} else {
|
|
net_sin6(&addr)->sin6_port = htons(port);
|
|
addrlen = sizeof(struct sockaddr_in6);
|
|
|
|
nbr = net_ipv6_nbr_lookup(NULL,
|
|
&net_sin6(&addr)->sin6_addr);
|
|
if (nbr) {
|
|
iface = nbr->iface;
|
|
}
|
|
|
|
get_my_ipv6_addr(iface, &myaddr);
|
|
family = addr.sa_family = myaddr.sa_family = AF_INET6;
|
|
}
|
|
} else {
|
|
PR_WARNING("No IPv6 nor IPv4 is enabled\n");
|
|
return;
|
|
}
|
|
|
|
print_connect_info(sh, family, &myaddr, &addr);
|
|
|
|
ret = net_context_get(family, SOCK_STREAM, IPPROTO_TCP, ctx);
|
|
if (ret < 0) {
|
|
PR_WARNING("Cannot get TCP context (%d)\n", ret);
|
|
return;
|
|
}
|
|
|
|
ret = net_context_bind(*ctx, &myaddr, addrlen);
|
|
if (ret < 0) {
|
|
PR_WARNING("Cannot bind TCP (%d)\n", ret);
|
|
return;
|
|
}
|
|
|
|
/* Note that we cannot put shell as a user_data when connecting
|
|
* because the tcp_connected() will be called much later and
|
|
* all local stack variables are lost at that point.
|
|
*/
|
|
tcp_shell = sh;
|
|
|
|
#if defined(CONFIG_NET_SOCKETS_CONNECT_TIMEOUT)
|
|
#define CONNECT_TIMEOUT K_MSEC(CONFIG_NET_SOCKETS_CONNECT_TIMEOUT)
|
|
#else
|
|
#define CONNECT_TIMEOUT K_SECONDS(3)
|
|
#endif
|
|
|
|
net_context_connect(*ctx, &addr, addrlen, tcp_connected,
|
|
CONNECT_TIMEOUT, NULL);
|
|
}
|
|
|
|
static void tcp_sent_cb(struct net_context *context,
|
|
int status, void *user_data)
|
|
{
|
|
PR_SHELL(tcp_shell, "Message sent\n");
|
|
}
|
|
|
|
static void tcp_recv_cb(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)
|
|
{
|
|
int ret, len;
|
|
|
|
if (pkt == NULL) {
|
|
if (!tcp_ctx || !net_context_is_used(tcp_ctx)) {
|
|
return;
|
|
}
|
|
|
|
ret = net_context_put(tcp_ctx);
|
|
if (ret < 0) {
|
|
PR_SHELL(tcp_shell,
|
|
"Cannot close the connection (%d)\n", ret);
|
|
return;
|
|
}
|
|
|
|
PR_SHELL(tcp_shell, "Connection closed by remote peer.\n");
|
|
tcp_ctx = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
len = net_pkt_remaining_data(pkt);
|
|
|
|
(void)net_context_update_recv_wnd(context, len);
|
|
|
|
PR_SHELL(tcp_shell, "%zu bytes received\n", net_pkt_get_len(pkt));
|
|
|
|
net_pkt_unref(pkt);
|
|
}
|
|
#endif
|
|
|
|
static int cmd_net_tcp_connect(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
#if defined(CONFIG_NET_TCP) && defined(CONFIG_NET_NATIVE_TCP)
|
|
int arg = 0;
|
|
|
|
/* tcp connect <ip> port */
|
|
char *endptr;
|
|
char *ip;
|
|
uint16_t port;
|
|
|
|
/* tcp connect <ip> port */
|
|
if (tcp_ctx && net_context_is_used(tcp_ctx)) {
|
|
PR("Already connected\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (!argv[++arg]) {
|
|
PR_WARNING("Peer IP address missing.\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
ip = argv[arg];
|
|
|
|
if (!argv[++arg]) {
|
|
PR_WARNING("Peer port missing.\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
port = strtol(argv[arg], &endptr, 10);
|
|
if (*endptr != '\0') {
|
|
PR_WARNING("Invalid port %s\n", argv[arg]);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
tcp_connect(sh, ip, port, &tcp_ctx);
|
|
#else
|
|
PR_INFO("Set %s to enable %s support.\n",
|
|
"CONFIG_NET_TCP and CONFIG_NET_NATIVE", "TCP");
|
|
#endif /* CONFIG_NET_NATIVE_TCP */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_net_tcp_send(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
#if defined(CONFIG_NET_TCP) && defined(CONFIG_NET_NATIVE_TCP)
|
|
int arg = 0;
|
|
int ret;
|
|
struct net_shell_user_data user_data;
|
|
|
|
/* tcp send <data> */
|
|
if (!tcp_ctx || !net_context_is_used(tcp_ctx)) {
|
|
PR_WARNING("Not connected\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (!argv[++arg]) {
|
|
PR_WARNING("No data to send.\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
user_data.sh = sh;
|
|
|
|
ret = net_context_send(tcp_ctx, (uint8_t *)argv[arg],
|
|
strlen(argv[arg]), tcp_sent_cb,
|
|
TCP_TIMEOUT, &user_data);
|
|
if (ret < 0) {
|
|
PR_WARNING("Cannot send msg (%d)\n", ret);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
#else
|
|
PR_INFO("Set %s to enable %s support.\n",
|
|
"CONFIG_NET_TCP and CONFIG_NET_NATIVE", "TCP");
|
|
#endif /* CONFIG_NET_NATIVE_TCP */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_net_tcp_recv(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
#if defined(CONFIG_NET_TCP) && defined(CONFIG_NET_NATIVE_TCP)
|
|
int ret;
|
|
struct net_shell_user_data user_data;
|
|
|
|
/* tcp recv */
|
|
if (!tcp_ctx || !net_context_is_used(tcp_ctx)) {
|
|
PR_WARNING("Not connected\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
user_data.sh = sh;
|
|
|
|
ret = net_context_recv(tcp_ctx, tcp_recv_cb, K_NO_WAIT, &user_data);
|
|
if (ret < 0) {
|
|
PR_WARNING("Cannot recv data (%d)\n", ret);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
#else
|
|
PR_INFO("Set %s to enable %s support.\n",
|
|
"CONFIG_NET_TCP and CONFIG_NET_NATIVE", "TCP");
|
|
#endif /* CONFIG_NET_NATIVE_TCP */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_net_tcp_close(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
#if defined(CONFIG_NET_TCP) && defined(CONFIG_NET_NATIVE_TCP)
|
|
int ret;
|
|
|
|
/* tcp close */
|
|
if (!tcp_ctx || !net_context_is_used(tcp_ctx)) {
|
|
PR_WARNING("Not connected\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
ret = net_context_put(tcp_ctx);
|
|
if (ret < 0) {
|
|
PR_WARNING("Cannot close the connection (%d)\n", ret);
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
PR("Connection closed.\n");
|
|
tcp_ctx = NULL;
|
|
#else
|
|
PR_INFO("Set %s to enable %s support.\n",
|
|
"CONFIG_NET_TCP and CONFIG_NET_NATIVE", "TCP");
|
|
#endif /* CONFIG_NET_TCP */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_net_tcp(const struct shell *sh, size_t argc, char *argv[])
|
|
{
|
|
ARG_UNUSED(argc);
|
|
ARG_UNUSED(argv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_tcp,
|
|
SHELL_CMD(connect, NULL,
|
|
"'net tcp connect <address> <port>' connects to TCP peer.",
|
|
cmd_net_tcp_connect),
|
|
SHELL_CMD(send, NULL,
|
|
"'net tcp send <data>' sends data to peer using TCP.",
|
|
cmd_net_tcp_send),
|
|
SHELL_CMD(recv, NULL,
|
|
"'net tcp recv' receives data using TCP.",
|
|
cmd_net_tcp_recv),
|
|
SHELL_CMD(close, NULL,
|
|
"'net tcp close' closes TCP connection.", cmd_net_tcp_close),
|
|
SHELL_SUBCMD_SET_END
|
|
);
|
|
|
|
SHELL_SUBCMD_ADD((net), tcp, &net_cmd_tcp,
|
|
"Connect/send/close TCP connection.",
|
|
cmd_net_tcp, 1, 0);
|