From 2eb935ff192400eabee46a8d7f4eef88f6f40e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 1 Sep 2023 10:45:04 +0200 Subject: [PATCH 1/4] net: sockets: add support for SO_REUSEADDR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds support for the SO_REUSEADDR option to be enabled for a socket using setsockopt(). With this option, it is possible to bind multiple sockets to the same local IP address / port combination, when one of the IP address is unspecified (ANY_ADDR). The implementation strictly follows the BSD implementation and tries to follow the Linux implementation as close as possible. However, there is one limitation: for client sockets, the Linux implementation of SO_REUSEADDR behaves exactly like the one for SO_REUSEPORT and enables multiple sockets to have exactly the same specific IP address / port combination. This behavior is not possible with this implementation, as there is no trivial way to identify a socket to be a client socket during the bind() call. For this behavior, one has to use the SO_REUSEPORT option in Zephyr. There is also a new Kconfig to control this feature similar to other socket options: CONFIG_NET_CONTEXT_REUSEADDR. This option is enabled by default if TCP or UDP are enabled. However, it can still be disabled explicitly. Signed-off-by: Tobias Frauenschläger --- include/zephyr/net/net_context.h | 4 + include/zephyr/net/socket.h | 4 +- subsys/net/ip/Kconfig | 7 ++ subsys/net/ip/net_context.c | 193 ++++++++++++++++++++++++++++--- subsys/net/ip/net_private.h | 6 + subsys/net/ip/tcp.c | 20 ++++ subsys/net/lib/sockets/sockets.c | 31 ++++- 7 files changed, 242 insertions(+), 23 deletions(-) diff --git a/include/zephyr/net/net_context.h b/include/zephyr/net/net_context.h index 7ab30dd0cb1c6..362d350ba8b4b 100644 --- a/include/zephyr/net/net_context.h +++ b/include/zephyr/net/net_context.h @@ -322,6 +322,9 @@ __net_socket struct net_context { #endif #if defined(CONFIG_NET_CONTEXT_DSCP_ECN) uint8_t dscp_ecn; +#endif +#if defined(CONFIG_NET_CONTEXT_REUSEADDR) + bool reuseaddr; #endif } options; @@ -1073,6 +1076,7 @@ enum net_context_option { NET_OPT_RCVBUF = 6, NET_OPT_SNDBUF = 7, NET_OPT_DSCP_ECN = 8, + NET_OPT_REUSEADDR = 9, }; /** diff --git a/include/zephyr/net/socket.h b/include/zephyr/net/socket.h index ff7b2cc2281f8..b3e6cfd88a88c 100644 --- a/include/zephyr/net/socket.h +++ b/include/zephyr/net/socket.h @@ -1009,7 +1009,7 @@ struct ifreq { /** sockopt: Recording debugging information (ignored, for compatibility) */ #define SO_DEBUG 1 -/** sockopt: address reuse (ignored, for compatibility) */ +/** sockopt: address reuse */ #define SO_REUSEADDR 2 /** sockopt: Type of the socket */ #define SO_TYPE 3 @@ -1020,7 +1020,7 @@ struct ifreq { /** sockopt: Transmission of broadcast messages is supported (ignored, for compatibility) */ #define SO_BROADCAST 6 -/** sockopt: Size of socket socket send buffer (ignored, for compatibility) */ +/** sockopt: Size of socket send buffer */ #define SO_SNDBUF 7 /** sockopt: Size of socket recv buffer */ #define SO_RCVBUF 8 diff --git a/subsys/net/ip/Kconfig b/subsys/net/ip/Kconfig index d7edb98855362..4d3b4a91ff255 100644 --- a/subsys/net/ip/Kconfig +++ b/subsys/net/ip/Kconfig @@ -683,6 +683,13 @@ config NET_CONTEXT_DSCP_ECN Notification values on net_context. Those values are then used in IPv4/IPv6 header when sending packets over net_context. +config NET_CONTEXT_REUSEADDR + bool "Add REUSEADDR support to net_context" + default y if NET_TCP || NET_UDP + help + Allow to set the SO_REUSEADDR flag on a socket. This enables multiple + sockets to bind to the same local IP address. + config NET_TEST bool "Network Testing" help diff --git a/subsys/net/ip/net_context.c b/subsys/net/ip/net_context.c index 1372398185a31..ff7a142010876 100644 --- a/subsys/net/ip/net_context.c +++ b/subsys/net/ip/net_context.c @@ -60,10 +60,51 @@ static struct net_context contexts[NET_MAX_CONTEXT]; */ static struct k_sem contexts_lock; +bool net_context_is_reuseaddr_set(struct net_context *context) +{ +#if defined(CONFIG_NET_CONTEXT_REUSEADDR) + return context->options.reuseaddr; +#else + return false; +#endif +} + #if defined(CONFIG_NET_UDP) || defined(CONFIG_NET_TCP) +static inline bool is_in_tcp_listen_state(struct net_context *context) +{ +#if defined(CONFIG_NET_TCP) + if (net_context_get_type(context) == SOCK_STREAM && + net_context_get_state(context) == NET_CONTEXT_LISTENING) { + return true; + } + + return false; +#else + return false; +#endif +} + +static inline bool is_in_tcp_time_wait_state(struct net_context *context) +{ +#if defined(CONFIG_NET_TCP) + if (net_context_get_type(context) == SOCK_STREAM) { + const struct tcp *tcp_conn = context->tcp; + + if (net_tcp_get_state(tcp_conn) == TCP_TIME_WAIT) { + return true; + } + } + + return false; +#else + return false; +#endif +} + static int check_used_port(enum net_ip_protocol proto, uint16_t local_port, - const struct sockaddr *local_addr) + const struct sockaddr *local_addr, + bool reuseaddr_set) { int i; @@ -85,12 +126,41 @@ static int check_used_port(enum net_ip_protocol proto, continue; } + if ((net_ipv6_is_addr_unspecified( + net_sin6_ptr(&contexts[i].local)->sin6_addr) || + net_ipv6_is_addr_unspecified( + &net_sin6(local_addr)->sin6_addr))) { + if (reuseaddr_set && + !is_in_tcp_listen_state(&contexts[i]) && + !(net_ipv6_is_addr_unspecified( + net_sin6_ptr(&contexts[i].local)->sin6_addr) && + net_ipv6_is_addr_unspecified( + &net_sin6(local_addr)->sin6_addr))) { + /* In case of REUSEADDR, only one context may be + * bound to the unspecified address, but not both. + * Furthermore, in case the existing context is in + * TCP LISTEN state, we ignore the REUSEADDR option + * (Linux behavior). + */ + continue; + } else { + return -EEXIST; + } + } + if (net_ipv6_addr_cmp( net_sin6_ptr(&contexts[i].local)-> sin6_addr, &((struct sockaddr_in6 *) local_addr)->sin6_addr)) { - return -EEXIST; + if (reuseaddr_set && is_in_tcp_time_wait_state(&contexts[i])) { + /* With REUSEADDR, the existing context must be + * in the TCP TIME_WAIT state. + */ + continue; + } else { + return -EEXIST; + } } } else if (IS_ENABLED(CONFIG_NET_IPV4) && local_addr->sa_family == AF_INET) { @@ -98,12 +168,41 @@ static int check_used_port(enum net_ip_protocol proto, continue; } + if ((net_ipv4_is_addr_unspecified( + net_sin_ptr(&contexts[i].local)->sin_addr) || + net_ipv4_is_addr_unspecified( + &net_sin(local_addr)->sin_addr))) { + if (reuseaddr_set && + !is_in_tcp_listen_state(&contexts[i]) && + !(net_ipv4_is_addr_unspecified( + net_sin_ptr(&contexts[i].local)->sin_addr) && + net_ipv4_is_addr_unspecified( + &net_sin(local_addr)->sin_addr))) { + /* In case of REUSEADDR, only one context may be + * bound to the unspecified address, but not both. + * Furthermore, in case the existing context is in + * TCP LISTEN state, we ignore the REUSEADDR option + * (Linux behavior). + */ + continue; + } else { + return -EEXIST; + } + } + if (net_ipv4_addr_cmp( net_sin_ptr(&contexts[i].local)-> sin_addr, &((struct sockaddr_in *) local_addr)->sin_addr)) { - return -EEXIST; + if (reuseaddr_set && is_in_tcp_time_wait_state(&contexts[i])) { + /* With REUSEADDR, the existing context must be + * in the TCP TIME_WAIT state. + */ + continue; + } else { + return -EEXIST; + } } } } @@ -119,7 +218,7 @@ static uint16_t find_available_port(struct net_context *context, do { local_port = sys_rand32_get() | 0x8000; } while (check_used_port(net_context_get_proto(context), - htons(local_port), addr) == -EEXIST); + htons(local_port), addr, false) == -EEXIST); return htons(local_port); } @@ -132,7 +231,7 @@ bool net_context_port_in_use(enum net_ip_protocol proto, uint16_t local_port, const struct sockaddr *local_addr) { - return check_used_port(proto, htons(local_port), local_addr) != 0; + return check_used_port(proto, htons(local_port), local_addr, false) != 0; } #if defined(CONFIG_NET_CONTEXT_CHECK) @@ -591,15 +690,18 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr, net_sin6_ptr(&context->local)->sin6_family = AF_INET6; net_sin6_ptr(&context->local)->sin6_addr = ptr; if (addr6->sin6_port) { - ret = check_used_port(AF_INET6, addr6->sin6_port, - addr); - if (!ret) { - net_sin6_ptr(&context->local)->sin6_port = - addr6->sin6_port; - } else { + ret = check_used_port(context->proto, + addr6->sin6_port, + addr, + net_context_is_reuseaddr_set(context)); + if (ret != 0) { NET_ERR("Port %d is in use!", ntohs(addr6->sin6_port)); + ret = -EADDRINUSE; goto unlock_ipv6; + } else { + net_sin6_ptr(&context->local)->sin6_port = + addr6->sin6_port; } } else { addr6->sin6_port = @@ -689,15 +791,18 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr, net_sin_ptr(&context->local)->sin_family = AF_INET; net_sin_ptr(&context->local)->sin_addr = ptr; if (addr4->sin_port) { - ret = check_used_port(AF_INET, addr4->sin_port, - addr); - if (!ret) { - net_sin_ptr(&context->local)->sin_port = - addr4->sin_port; - } else { + ret = check_used_port(context->proto, + addr4->sin_port, + addr, + net_context_is_reuseaddr_set(context)); + if (ret != 0) { NET_ERR("Port %d is in use!", ntohs(addr4->sin_port)); + ret = -EADDRINUSE; goto unlock_ipv4; + } else { + net_sin_ptr(&context->local)->sin_port = + addr4->sin_port; } } else { addr4->sin_port = @@ -1309,6 +1414,32 @@ static int get_context_dscp_ecn(struct net_context *context, #endif } +static int get_context_reuseaddr(struct net_context *context, + void *value, size_t *len) +{ +#if defined(CONFIG_NET_CONTEXT_REUSEADDR) + if (!value || !len) { + return -EINVAL; + } + + if (*len != sizeof(int)) { + return -EINVAL; + } + + if (context->options.reuseaddr == true) { + *((int *)value) = (int) true; + } else { + *((int *)value) = (int) false; + } + + *len = sizeof(int); + + return 0; +#else + return -ENOTSUP; +#endif +} + /* If buf is not NULL, then use it. Otherwise read the data to be written * to net_pkt from msghdr. */ @@ -2363,6 +2494,28 @@ static int set_context_dscp_ecn(struct net_context *context, #endif } +static int set_context_reuseaddr(struct net_context *context, + const void *value, size_t len) +{ +#if defined(CONFIG_NET_CONTEXT_REUSEADDR) + bool reuseaddr = false; + + if (len != sizeof(int)) { + return -EINVAL; + } + + if (*((int *) value) != 0) { + reuseaddr = true; + } + + context->options.reuseaddr = reuseaddr; + + return 0; +#else + return -ENOTSUP; +#endif +} + int net_context_set_option(struct net_context *context, enum net_context_option option, const void *value, size_t len) @@ -2402,6 +2555,9 @@ int net_context_set_option(struct net_context *context, case NET_OPT_DSCP_ECN: ret = set_context_dscp_ecn(context, value, len); break; + case NET_OPT_REUSEADDR: + ret = set_context_reuseaddr(context, value, len); + break; } k_mutex_unlock(&context->lock); @@ -2448,6 +2604,9 @@ int net_context_get_option(struct net_context *context, case NET_OPT_DSCP_ECN: ret = get_context_dscp_ecn(context, value, len); break; + case NET_OPT_REUSEADDR: + ret = get_context_reuseaddr(context, value, len); + break; } k_mutex_unlock(&context->lock); diff --git a/subsys/net/ip/net_private.h b/subsys/net/ip/net_private.h index be0466fc98b16..305dd66a98a50 100644 --- a/subsys/net/ip/net_private.h +++ b/subsys/net/ip/net_private.h @@ -58,6 +58,7 @@ extern void net_process_tx_packet(struct net_pkt *pkt); #if defined(CONFIG_NET_NATIVE) || defined(CONFIG_NET_OFFLOAD) extern void net_context_init(void); extern const char *net_context_state(struct net_context *context); +extern bool net_context_is_reuseaddr_set(struct net_context *context); extern void net_pkt_init(void); extern void net_tc_tx_init(void); extern void net_tc_rx_init(void); @@ -71,6 +72,11 @@ static inline const char *net_context_state(struct net_context *context) ARG_UNUSED(context); return NULL; } +static inline bool net_context_is_reuseaddr_set(struct net_context *context) +{ + ARG_UNUSED(context); + return false; +} #endif #if defined(CONFIG_NET_NATIVE) diff --git a/subsys/net/ip/tcp.c b/subsys/net/ip/tcp.c index c90f7b92c2040..9e40d826bc4ab 100644 --- a/subsys/net/ip/tcp.c +++ b/subsys/net/ip/tcp.c @@ -1923,6 +1923,26 @@ static struct tcp *tcp_conn_new(struct net_pkt *pkt) goto err; } + /* The newly created context object for the new TCP client connection needs + * all four parameters of the tuple (local address, local port, remote + * address, remote port) to be properly identified. Remote address and port + * are already copied above from conn->dst. The call to net_context_bind + * with the prepared local_addr further copies the local address. However, + * this call wont copy the local port, as the bind would then fail due to + * an address/port reuse without the REUSEPORT option enables for both + * connections. Therefore, we copy the port after the bind call. + * It is safe to bind to this address/port combination, as the new TCP + * client connection is separated from the local listening connection + * by the specified remote address and remote port. + */ + if (IS_ENABLED(CONFIG_NET_IPV6) && + net_context_get_family(context) == AF_INET6) { + net_sin6_ptr(&context->local)->sin6_port = conn->src.sin6.sin6_port; + } else if (IS_ENABLED(CONFIG_NET_IPV4) && + net_context_get_family(context) == AF_INET) { + net_sin_ptr(&context->local)->sin_port = conn->src.sin.sin_port; + } + if (!(IS_ENABLED(CONFIG_NET_TEST_PROTOCOL) || IS_ENABLED(CONFIG_NET_TEST))) { conn->seq = tcp_init_isn(&local_addr, &context->remote); diff --git a/subsys/net/lib/sockets/sockets.c b/subsys/net/lib/sockets/sockets.c index 801ed99fe60b7..a0f12161b0568 100644 --- a/subsys/net/lib/sockets/sockets.c +++ b/subsys/net/lib/sockets/sockets.c @@ -2012,6 +2012,20 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname, return 0; } break; + + case SO_REUSEADDR: + if (IS_ENABLED(CONFIG_NET_CONTEXT_REUSEADDR)) { + ret = net_context_get_option(ctx, + NET_OPT_REUSEADDR, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } + break; } break; @@ -2148,10 +2162,19 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname, break; case SO_REUSEADDR: - /* Ignore for now. Provided to let port - * existing apps. - */ - return 0; + if (IS_ENABLED(CONFIG_NET_CONTEXT_REUSEADDR)) { + ret = net_context_set_option(ctx, + NET_OPT_REUSEADDR, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } + + break; case SO_PRIORITY: if (IS_ENABLED(CONFIG_NET_CONTEXT_PRIORITY)) { From dcd538967e2fc1ae673e55975df4051986d6ab5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 1 Sep 2023 10:50:57 +0200 Subject: [PATCH 2/4] tests: net: sockets: add tests for SO_REUSEADDR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds unit tests for the SO_REUSEADDR socket option to test the different possible scenarios for the option to work correctly. There is also a fix for the TCP testsuite to make a now failing test pass again due to a TIME_WAIT socket. Signed-off-by: Tobias Frauenschläger --- tests/net/socket/reuseaddr/CMakeLists.txt | 8 + tests/net/socket/reuseaddr/prj.conf | 55 +++ tests/net/socket/reuseaddr/src/main.c | 473 ++++++++++++++++++++++ tests/net/socket/reuseaddr/testcase.yaml | 20 + tests/net/socket/tcp/src/main.c | 8 +- 5 files changed, 561 insertions(+), 3 deletions(-) create mode 100644 tests/net/socket/reuseaddr/CMakeLists.txt create mode 100644 tests/net/socket/reuseaddr/prj.conf create mode 100644 tests/net/socket/reuseaddr/src/main.c create mode 100644 tests/net/socket/reuseaddr/testcase.yaml diff --git a/tests/net/socket/reuseaddr/CMakeLists.txt b/tests/net/socket/reuseaddr/CMakeLists.txt new file mode 100644 index 0000000000000..eaa76e6a46025 --- /dev/null +++ b/tests/net/socket/reuseaddr/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(socket-reuseaddr_reuseport) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/socket/reuseaddr/prj.conf b/tests/net/socket/reuseaddr/prj.conf new file mode 100644 index 0000000000000..c820fefa19933 --- /dev/null +++ b/tests/net/socket/reuseaddr/prj.conf @@ -0,0 +1,55 @@ +# General config +CONFIG_NEWLIB_LIBC=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_TEST=y +CONFIG_NET_DRIVERS=y +CONFIG_NET_LOOPBACK=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y +CONFIG_NET_L2_DUMMY=y +CONFIG_NET_IPV6_DAD=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_IPV6_ND=n +CONFIG_NET_ARP=n + +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=64 + +CONFIG_NET_CONTEXT_REUSEADDR=y + +CONFIG_NET_HOSTNAME_ENABLE=y +CONFIG_NET_HOSTNAME="ztest_hostname" + +CONFIG_POSIX_MAX_FDS=8 +CONFIG_NET_MAX_CONN=10 +CONFIG_NET_MAX_CONTEXTS=10 + +# Network driver config +CONFIG_TEST_RANDOM_GENERATOR=y + +# Reduce the retry count, so the close always finishes within a second +CONFIG_NET_TCP_RETRY_COUNT=3 +CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT=120 + +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ZTEST_STACK_SIZE=4096 + +# User mode requirements +CONFIG_TEST_USERSPACE=y +CONFIG_HEAP_MEM_POOL_SIZE=8192 + +# If you want to debug the tests, you can get logging using these statements +# CONFIG_LOG=y +# CONFIG_LOG_MODE_DEFERRED=y +# CONFIG_NET_LOG=y +# CONFIG_NET_TCP_LOG_LEVEL_DBG=y diff --git a/tests/net/socket/reuseaddr/src/main.c b/tests/net/socket/reuseaddr/src/main.c new file mode 100644 index 0000000000000..1dc41fb72d142 --- /dev/null +++ b/tests/net/socket/reuseaddr/src/main.c @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2019 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include + +#include +#include + +#include "../../socket_helpers.h" + +#define TEST_IPV4_ANY_ADDR "0.0.0.0" +#define TEST_MY_IPV4_ADDR "192.0.2.1" +#define TEST_MY_IPV4_ADDR2 "192.0.2.2" + +#define TEST_IPV6_ANY_ADDR "::" +#define TEST_MY_IPV6_ADDR "2001:db8::1" +#define TEST_MY_IPV6_ADDR2 "2001:db8::2" + +#define LOCAL_PORT 4242 + +#define TCP_TEARDOWN_TIMEOUT K_SECONDS(3) + +#define SHOULD_SUCCEED true +#define SHOULD_FAIL false + +static inline void prepare_sock_tcp(sa_family_t family, const char *addr, uint16_t port, + int *sock, struct sockaddr *sockaddr) +{ + if (family == AF_INET) { + prepare_sock_tcp_v4(addr, + port, + sock, + (struct sockaddr_in *) sockaddr); + } else if (family == AF_INET6) { + prepare_sock_tcp_v6(addr, + port, + sock, + (struct sockaddr_in6 *) sockaddr); + } +} + +static void test_getsocketopt_reuseaddr(int sock, void *optval, socklen_t *optlen) +{ + zassert_equal(getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, optval, optlen), + 0, + "getsocketopt() failed with error %d", errno); +} + +static void test_setsocketopt_reuseaddr(int sock, void *optval, socklen_t optlen) +{ + zassert_equal(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, optval, optlen), + 0, + "setsocketopt() failed with error %d", errno); +} + +static void test_enable_reuseaddr(int sock) +{ + int value = 1; + + test_setsocketopt_reuseaddr(sock, &value, sizeof(value)); +} + +static void test_add_local_ip_address(sa_family_t family, const char *ip) +{ + if (family == AF_INET) { + struct sockaddr_in addr; + + zsock_inet_pton(AF_INET, ip, &addr.sin_addr); + + zassert_not_null(net_if_ipv4_addr_add(net_if_get_default(), + &addr.sin_addr, + NET_ADDR_MANUAL, + 0), + "Cannot add IPv4 address %s", ip); + } else if (family == AF_INET6) { + struct sockaddr_in6 addr; + + zsock_inet_pton(AF_INET6, ip, &addr.sin6_addr); + + zassert_not_null(net_if_ipv6_addr_add(net_if_get_default(), + &addr.sin6_addr, + NET_ADDR_MANUAL, + 0), + "Cannot add IPv6 address %s", ip); + } +} + +static void test_bind_success(int sock, const struct sockaddr * addr, socklen_t addrlen) +{ + zassert_equal(bind(sock, addr, addrlen), + 0, + "bind() failed with error %d", errno); +} + +static void test_bind_fail(int sock, const struct sockaddr * addr, socklen_t addrlen) +{ + zassert_equal(bind(sock, addr, addrlen), + -1, + "bind() succeeded incorrectly"); + + zassert_equal(errno, EADDRINUSE, "bind() returned unexpected errno (%d)", errno); +} + +static void test_listen(int sock) +{ + zassert_equal(listen(sock, 0), + 0, + "listen() failed with error %d", errno); +} + +static void test_connect(int sock, const struct sockaddr * addr, socklen_t addrlen) +{ + zassert_equal(connect(sock, addr, addrlen), + 0, + "connect() failed with error %d", errno); + + if (IS_ENABLED(CONFIG_NET_TC_THREAD_PREEMPTIVE)) { + /* Let the connection proceed */ + k_msleep(50); + } +} + +static int test_accept(int sock, struct sockaddr * addr, socklen_t * addrlen) +{ + int new_sock = accept(sock, addr, addrlen); + + zassert_not_equal(new_sock, -1, "accept() failed with error %d", errno); + + return new_sock; +} + +static void calc_net_context(struct net_context *context, void *user_data) +{ + int *count = user_data; + + (*count)++; +} + +/* Wait until the number of TCP contexts reaches a certain level + * exp_num_contexts : The number of contexts to wait for + * timeout : The time to wait for + */ +int wait_for_n_tcp_contexts(int exp_num_contexts, k_timeout_t timeout) +{ + uint32_t start_time = k_uptime_get_32(); + uint32_t time_diff; + int context_count = 0; + + /* After the client socket closing, the context count should be 1 less */ + net_context_foreach(calc_net_context, &context_count); + + time_diff = k_uptime_get_32() - start_time; + + /* Eventually the client socket should be cleaned up */ + while (context_count != exp_num_contexts) { + context_count = 0; + net_context_foreach(calc_net_context, &context_count); + k_sleep(K_MSEC(50)); + time_diff = k_uptime_get_32() - start_time; + + if (K_MSEC(time_diff).ticks > timeout.ticks) { + return -ETIMEDOUT; + } + } + + return 0; +} + +static void test_context_cleanup(void) +{ + zassert_equal(wait_for_n_tcp_contexts(0, TCP_TEARDOWN_TIMEOUT), + 0, + "Not all TCP contexts properly cleaned up"); +} + + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_enable_disable) +{ + int server_sock = zsock_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + zassert_true(server_sock >= 0, "socket open failed"); + + int value = -1; + size_t value_size = sizeof(int); + + /* Read initial value */ + test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); + zassert_equal(value_size, sizeof(int), "incorrect value size returned by getsocketopt()"); + zassert_equal(value, (int) false, "SO_REUSEADDR incorrectly set (expected false)"); + + /* Enable option */ + value = 1; + test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); + zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = 2; + test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); + zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = 0x100; + test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); + zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = -1; + test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); + zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); + + close(server_sock); + + test_context_cleanup(); +} + + +static void test_reuseaddr_unspecified_specified_common(sa_family_t family, + char const *first_ip, + char const *second_ip, + bool should_succeed) +{ + int server_sock1 = -1; + int server_sock2 = -1; + + struct sockaddr bind_addr1; + struct sockaddr bind_addr2; + + /* Create the sockets */ + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock1, &bind_addr1); + prepare_sock_tcp(family, second_ip, LOCAL_PORT, &server_sock2, &bind_addr2); + + /* Bind the first socket */ + test_bind_success(server_sock1, &bind_addr1, sizeof(bind_addr1)); + + /* Try to bind the second socket, should fail */ + test_bind_fail(server_sock2, &bind_addr2, sizeof(bind_addr2)); + + /* Enable SO_REUSEADDR option for the second socket */ + test_enable_reuseaddr(server_sock2); + + /* Try to bind the second socket again */ + if (should_succeed) { + test_bind_success(server_sock2, &bind_addr2, sizeof(bind_addr2)); + } else { + test_bind_fail(server_sock2, &bind_addr2, sizeof(bind_addr2)); + } + + close(server_sock1); + close(server_sock2); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_first_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_first_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_second_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_IPV4_ANY_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_second_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_IPV6_ANY_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_both_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_IPV4_ANY_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_both_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_IPV6_ANY_ADDR, + SHOULD_FAIL); +} + + +static void test_reuseaddr_tcp_listening_common(sa_family_t family, + char const *first_ip, + char const *second_ip) +{ + int server_sock1 = -1; + int server_sock2 = -1; + + struct sockaddr bind_addr1; + struct sockaddr bind_addr2; + + /* Create the sockets */ + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock1, &bind_addr1); + prepare_sock_tcp(family, second_ip, LOCAL_PORT, &server_sock2, &bind_addr2); + + /* Bind the first socket */ + test_bind_success(server_sock1, &bind_addr1, sizeof(bind_addr1)); + + /* Set the first socket to LISTEN state */ + test_listen(server_sock1); + + /* Enable SO_REUSEADDR option for the second socket */ + test_enable_reuseaddr(server_sock2); + + /* Try to bind the second socket, should fail */ + test_bind_fail(server_sock2, (struct sockaddr *) &bind_addr2, sizeof(bind_addr2)); + + close(server_sock1); + close(server_sock2); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_tcp_unspecified_listening) +{ + test_reuseaddr_tcp_listening_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_tcp_unspecified_listening) +{ + test_reuseaddr_tcp_listening_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_tcp_specified_listening) +{ + test_reuseaddr_tcp_listening_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_IPV4_ANY_ADDR); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_tcp_specified_listening) +{ + test_reuseaddr_tcp_listening_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_IPV6_ANY_ADDR); +} + + +static void test_reuseaddr_tcp_tcp_time_wait_common(sa_family_t family, + char const *first_ip, + char const *second_ip) +{ + int server_sock = -1; + int client_sock = -1; + int accept_sock = -1; + + struct sockaddr bind_addr; + struct sockaddr conn_addr; + + struct sockaddr accept_addr; + socklen_t accept_addrlen = sizeof(accept_addr); + + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock, &bind_addr); + prepare_sock_tcp(family, second_ip, LOCAL_PORT, &client_sock, &conn_addr); + + /* Bind the server socket */ + test_bind_success(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); + + /* Start listening on the server socket */ + test_listen(server_sock); + + /* Connect the client */ + test_connect(client_sock, &conn_addr, sizeof(conn_addr)); + + /* Accept the client */ + accept_sock = test_accept(server_sock, &accept_addr, &accept_addrlen); + + /* Close the server socket */ + close(server_sock); + + /* Close the accepted socket */ + close(accept_sock); + + /* Wait a short time for the accept socket to enter TIME_WAIT state*/ + k_msleep(50); + + /* Recreate the server socket */ + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock, &bind_addr); + + /* Bind the server socket, should fail */ + test_bind_fail(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); + + /* Enable SO_REUSEADDR option for the new server socket */ + test_enable_reuseaddr(server_sock); + + /* Try to bind the new server socket again, should work now */ + test_bind_success(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); + + close(client_sock); + close(server_sock); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_tcp_time_wait_unspecified) +{ + test_reuseaddr_tcp_tcp_time_wait_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_tcp_time_wait_unspecified) +{ + test_reuseaddr_tcp_tcp_time_wait_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_tcp_time_wait_specified) +{ + test_reuseaddr_tcp_tcp_time_wait_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_MY_IPV4_ADDR); +} + +ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_tcp_time_wait_specified) +{ + test_reuseaddr_tcp_tcp_time_wait_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_MY_IPV6_ADDR); +} + + +static void *setup(void) +{ + /* Make sure that both the specified IPv4 and IPv6 addresses are + * added to the network interface. + */ + test_add_local_ip_address(AF_INET, TEST_MY_IPV4_ADDR); + test_add_local_ip_address(AF_INET6, TEST_MY_IPV6_ADDR); + + return NULL; +} + + +ZTEST_SUITE(socket_reuseaddr_reuseport_test_suite, NULL, setup, NULL, NULL, NULL); diff --git a/tests/net/socket/reuseaddr/testcase.yaml b/tests/net/socket/reuseaddr/testcase.yaml new file mode 100644 index 0000000000000..b2d864d934c23 --- /dev/null +++ b/tests/net/socket/reuseaddr/testcase.yaml @@ -0,0 +1,20 @@ +common: + depends_on: netif + filter: TOOLCHAIN_HAS_NEWLIB == 1 + min_ram: 31 + tags: + - net + - socket + # FIXME: This test fails very frequently on mps2_an385 due to the system + # timer stability issues, so keep it disabled until the root cause + # is fixed (GitHub issue zephyrproject-rtos/zephyr#48608). + platform_exclude: mps2_an385 +tests: + net.socket.reuseaddr_reuseport: + extra_configs: + - CONFIG_TEST_USERSPACE=n + net.socket.reuseaddr_reuseport.userspace: + extra_configs: + - CONFIG_TEST_USERSPACE=y + # FIXME: This test fails with an unknown error on qemu_x86 + platform_exclude: qemu_x86 diff --git a/tests/net/socket/tcp/src/main.c b/tests/net/socket/tcp/src/main.c index 38ba5ae81ad32..5095e6512a78f 100644 --- a/tests/net/socket/tcp/src/main.c +++ b/tests/net/socket/tcp/src/main.c @@ -31,21 +31,21 @@ static void test_bind(int sock, struct sockaddr *addr, socklen_t addrlen) { zassert_equal(bind(sock, addr, addrlen), 0, - "bind failed"); + "bind failed with error %d", errno); } static void test_listen(int sock) { zassert_equal(listen(sock, MAX_CONNS), 0, - "listen failed"); + "listen failed with error %d", errno); } static void test_connect(int sock, struct sockaddr *addr, socklen_t addrlen) { zassert_equal(connect(sock, addr, addrlen), 0, - "connect failed"); + "connect failed with error %d", errno); if (IS_ENABLED(CONFIG_NET_TC_THREAD_PREEMPTIVE)) { /* Let the connection proceed */ @@ -554,6 +554,8 @@ ZTEST(net_socket_tcp, test_v4_broken_link) test_close(s_sock); restore_packet_loss_ratio(); + + k_sleep(TCP_TEARDOWN_TIMEOUT); } ZTEST_USER(net_socket_tcp, test_v4_sendto_recvfrom) From 339824f80a47ba43833219e96d6c76af8b49d5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 1 Sep 2023 11:22:13 +0200 Subject: [PATCH 3/4] net: sockets: add support for SO_REUSEPORT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commits adds support for the SO_REUSEPORT socket option. The implementation follows the behavior of BSD and tries to also follow the specific additional features of linux with the following limitations: * SO_REUSEADDR and SO_REUSEPORT are not "the same" for client sockets, as we do not have a trivial way so identify a socket as "client" during binding. To get the Linux behavior, one has to use SO_REUSEPORT with Zephyr * No prevention of "port hijacking" * No support for the load balancing stuff for incoming packets/connections There is also a new Kconfig option to control this feature, which is enabled by default if TCP or UDP is enabled. Signed-off-by: Tobias Frauenschläger --- include/zephyr/net/net_context.h | 4 + include/zephyr/net/socket.h | 2 +- subsys/net/ip/Kconfig | 7 ++ subsys/net/ip/connection.c | 83 +++++++++++---------- subsys/net/ip/net_context.c | 122 +++++++++++++++++++++++++++---- subsys/net/ip/net_private.h | 6 ++ subsys/net/lib/sockets/sockets.c | 29 ++++++++ 7 files changed, 195 insertions(+), 58 deletions(-) diff --git a/include/zephyr/net/net_context.h b/include/zephyr/net/net_context.h index 362d350ba8b4b..78a88aec0fe44 100644 --- a/include/zephyr/net/net_context.h +++ b/include/zephyr/net/net_context.h @@ -325,6 +325,9 @@ __net_socket struct net_context { #endif #if defined(CONFIG_NET_CONTEXT_REUSEADDR) bool reuseaddr; +#endif +#if defined(CONFIG_NET_CONTEXT_REUSEPORT) + bool reuseport; #endif } options; @@ -1077,6 +1080,7 @@ enum net_context_option { NET_OPT_SNDBUF = 7, NET_OPT_DSCP_ECN = 8, NET_OPT_REUSEADDR = 9, + NET_OPT_REUSEPORT = 10, }; /** diff --git a/include/zephyr/net/socket.h b/include/zephyr/net/socket.h index b3e6cfd88a88c..d431453bbb042 100644 --- a/include/zephyr/net/socket.h +++ b/include/zephyr/net/socket.h @@ -1031,7 +1031,7 @@ struct ifreq { #define SO_OOBINLINE 10 /** sockopt: Socket lingers on close (ignored, for compatibility) */ #define SO_LINGER 13 -/** sockopt: Allow multiple sockets to reuse a single port (ignored, for compatibility) */ +/** sockopt: Allow multiple sockets to reuse a single port */ #define SO_REUSEPORT 15 /** sockopt: Receive low watermark (ignored, for compatibility) */ diff --git a/subsys/net/ip/Kconfig b/subsys/net/ip/Kconfig index 4d3b4a91ff255..eb90a0d4c4f19 100644 --- a/subsys/net/ip/Kconfig +++ b/subsys/net/ip/Kconfig @@ -690,6 +690,13 @@ config NET_CONTEXT_REUSEADDR Allow to set the SO_REUSEADDR flag on a socket. This enables multiple sockets to bind to the same local IP address. +config NET_CONTEXT_REUSEPORT + bool "Add REUSEPORT support to net_context" + default y if NET_TCP || NET_UDP + help + Allow to set the SO_REUSEPORT flag on a socket. This enables multiple + sockets to bind to the same local IP address and port combination. + config NET_TEST bool "Network Testing" help diff --git a/subsys/net/ip/connection.c b/subsys/net/ip/connection.c index 225a3fdbaade9..be1366b32cab3 100644 --- a/subsys/net/ip/connection.c +++ b/subsys/net/ip/connection.c @@ -158,7 +158,8 @@ static struct net_conn *conn_find_handler(uint16_t proto, uint8_t family, const struct sockaddr *remote_addr, const struct sockaddr *local_addr, uint16_t remote_port, - uint16_t local_port) + uint16_t local_port, + bool reuseport_set) { struct net_conn *conn; struct net_conn *tmp; @@ -174,67 +175,75 @@ static struct net_conn *conn_find_handler(uint16_t proto, uint8_t family, continue; } - if (remote_addr) { - if (!(conn->flags & NET_CONN_REMOTE_ADDR_SET)) { + if (local_addr) { + if (!(conn->flags & NET_CONN_LOCAL_ADDR_SET)) { continue; } if (IS_ENABLED(CONFIG_NET_IPV6) && - remote_addr->sa_family == AF_INET6 && - remote_addr->sa_family == - conn->remote_addr.sa_family) { + local_addr->sa_family == AF_INET6 && + local_addr->sa_family == + conn->local_addr.sa_family) { if (!net_ipv6_addr_cmp( - &net_sin6(remote_addr)->sin6_addr, - &net_sin6(&conn->remote_addr)-> + &net_sin6(local_addr)->sin6_addr, + &net_sin6(&conn->local_addr)-> sin6_addr)) { continue; } } else if (IS_ENABLED(CONFIG_NET_IPV4) && - remote_addr->sa_family == AF_INET && - remote_addr->sa_family == - conn->remote_addr.sa_family) { + local_addr->sa_family == AF_INET && + local_addr->sa_family == + conn->local_addr.sa_family) { if (!net_ipv4_addr_cmp( - &net_sin(remote_addr)->sin_addr, - &net_sin(&conn->remote_addr)-> + &net_sin(local_addr)->sin_addr, + &net_sin(&conn->local_addr)-> sin_addr)) { continue; } } else { continue; } - } else if (conn->flags & NET_CONN_REMOTE_ADDR_SET) { + } else if (conn->flags & NET_CONN_LOCAL_ADDR_SET) { continue; } - if (local_addr) { - if (!(conn->flags & NET_CONN_LOCAL_ADDR_SET)) { + if (net_sin(&conn->local_addr)->sin_port != + htons(local_port)) { + continue; + } + + if (remote_addr) { + if (!(conn->flags & NET_CONN_REMOTE_ADDR_SET)) { continue; } if (IS_ENABLED(CONFIG_NET_IPV6) && - local_addr->sa_family == AF_INET6 && - local_addr->sa_family == - conn->local_addr.sa_family) { + remote_addr->sa_family == AF_INET6 && + remote_addr->sa_family == + conn->remote_addr.sa_family) { if (!net_ipv6_addr_cmp( - &net_sin6(local_addr)->sin6_addr, - &net_sin6(&conn->local_addr)-> + &net_sin6(remote_addr)->sin6_addr, + &net_sin6(&conn->remote_addr)-> sin6_addr)) { continue; } } else if (IS_ENABLED(CONFIG_NET_IPV4) && - local_addr->sa_family == AF_INET && - local_addr->sa_family == - conn->local_addr.sa_family) { + remote_addr->sa_family == AF_INET && + remote_addr->sa_family == + conn->remote_addr.sa_family) { if (!net_ipv4_addr_cmp( - &net_sin(local_addr)->sin_addr, - &net_sin(&conn->local_addr)-> + &net_sin(remote_addr)->sin_addr, + &net_sin(&conn->remote_addr)-> sin_addr)) { continue; } } else { continue; } - } else if (conn->flags & NET_CONN_LOCAL_ADDR_SET) { + } else if (conn->flags & NET_CONN_REMOTE_ADDR_SET) { + continue; + } else if (reuseport_set && conn->context != NULL && + net_context_is_reuseport_set(conn->context)) { continue; } @@ -243,11 +252,6 @@ static struct net_conn *conn_find_handler(uint16_t proto, uint8_t family, continue; } - if (net_sin(&conn->local_addr)->sin_port != - htons(local_port)) { - continue; - } - k_mutex_unlock(&conn_lock); return conn; } @@ -270,10 +274,13 @@ int net_conn_register(uint16_t proto, uint8_t family, uint8_t flags = 0U; conn = conn_find_handler(proto, family, remote_addr, local_addr, - remote_port, local_port); + remote_port, local_port, + context != NULL ? + net_context_is_reuseport_set(context) : + false); if (conn) { NET_ERR("Identical connection handler %p already found.", conn); - return -EALREADY; + return -EADDRINUSE; } conn = conn_get_unused(); @@ -736,14 +743,6 @@ enum net_verdict net_conn_input(struct net_pkt *pkt, continue; /* wrong local address */ } - /* If we have an existing best_match, and that one - * specifies a remote port, then we've matched to a - * LISTENING connection that we should not override. - */ - if (best_match != NULL && best_match->flags & NET_CONN_REMOTE_PORT_SPEC) { - continue; /* do not override listening connection */ - } - if (best_rank < NET_CONN_RANK(conn->flags)) { struct net_pkt *mcast_pkt; diff --git a/subsys/net/ip/net_context.c b/subsys/net/ip/net_context.c index ff7a142010876..5bd4122efc627 100644 --- a/subsys/net/ip/net_context.c +++ b/subsys/net/ip/net_context.c @@ -69,6 +69,15 @@ bool net_context_is_reuseaddr_set(struct net_context *context) #endif } +bool net_context_is_reuseport_set(struct net_context *context) +{ +#if defined(CONFIG_NET_CONTEXT_REUSEPORT) + return context->options.reuseport; +#else + return false; +#endif +} + #if defined(CONFIG_NET_UDP) || defined(CONFIG_NET_TCP) static inline bool is_in_tcp_listen_state(struct net_context *context) { @@ -104,7 +113,8 @@ static inline bool is_in_tcp_time_wait_state(struct net_context *context) static int check_used_port(enum net_ip_protocol proto, uint16_t local_port, const struct sockaddr *local_addr, - bool reuseaddr_set) + bool reuseaddr_set, + bool reuseport_set) { int i; @@ -130,11 +140,17 @@ static int check_used_port(enum net_ip_protocol proto, net_sin6_ptr(&contexts[i].local)->sin6_addr) || net_ipv6_is_addr_unspecified( &net_sin6(local_addr)->sin6_addr))) { - if (reuseaddr_set && - !is_in_tcp_listen_state(&contexts[i]) && - !(net_ipv6_is_addr_unspecified( + if (reuseport_set && + net_context_is_reuseport_set(&contexts[i])) { + /* When both context have the REUSEPORT set, both + * may be unspecified. + */ + continue; + } else if (reuseaddr_set && + !is_in_tcp_listen_state(&contexts[i]) && + !(net_ipv6_is_addr_unspecified( net_sin6_ptr(&contexts[i].local)->sin6_addr) && - net_ipv6_is_addr_unspecified( + net_ipv6_is_addr_unspecified( &net_sin6(local_addr)->sin6_addr))) { /* In case of REUSEADDR, only one context may be * bound to the unspecified address, but not both. @@ -153,7 +169,14 @@ static int check_used_port(enum net_ip_protocol proto, sin6_addr, &((struct sockaddr_in6 *) local_addr)->sin6_addr)) { - if (reuseaddr_set && is_in_tcp_time_wait_state(&contexts[i])) { + if (reuseport_set && + net_context_is_reuseport_set(&contexts[i])) { + /* When both context have the REUSEPORT set, both + * may be bound to exactly the same address. + */ + continue; + } else if (reuseaddr_set && + is_in_tcp_time_wait_state(&contexts[i])) { /* With REUSEADDR, the existing context must be * in the TCP TIME_WAIT state. */ @@ -172,11 +195,17 @@ static int check_used_port(enum net_ip_protocol proto, net_sin_ptr(&contexts[i].local)->sin_addr) || net_ipv4_is_addr_unspecified( &net_sin(local_addr)->sin_addr))) { - if (reuseaddr_set && - !is_in_tcp_listen_state(&contexts[i]) && - !(net_ipv4_is_addr_unspecified( + if (reuseport_set && + net_context_is_reuseport_set(&contexts[i])) { + /* When both context have the REUSEPORT set, both + * may be unspecified. + */ + continue; + } else if (reuseaddr_set && + !is_in_tcp_listen_state(&contexts[i]) && + !(net_ipv4_is_addr_unspecified( net_sin_ptr(&contexts[i].local)->sin_addr) && - net_ipv4_is_addr_unspecified( + net_ipv4_is_addr_unspecified( &net_sin(local_addr)->sin_addr))) { /* In case of REUSEADDR, only one context may be * bound to the unspecified address, but not both. @@ -195,7 +224,14 @@ static int check_used_port(enum net_ip_protocol proto, sin_addr, &((struct sockaddr_in *) local_addr)->sin_addr)) { - if (reuseaddr_set && is_in_tcp_time_wait_state(&contexts[i])) { + if (reuseport_set && + net_context_is_reuseport_set(&contexts[i])) { + /* When both context have the REUSEPORT set, both + * may be bound to exactly the same address. + */ + continue; + } else if (reuseaddr_set && + is_in_tcp_time_wait_state(&contexts[i])) { /* With REUSEADDR, the existing context must be * in the TCP TIME_WAIT state. */ @@ -218,7 +254,7 @@ static uint16_t find_available_port(struct net_context *context, do { local_port = sys_rand32_get() | 0x8000; } while (check_used_port(net_context_get_proto(context), - htons(local_port), addr, false) == -EEXIST); + htons(local_port), addr, false, false) == -EEXIST); return htons(local_port); } @@ -231,7 +267,7 @@ bool net_context_port_in_use(enum net_ip_protocol proto, uint16_t local_port, const struct sockaddr *local_addr) { - return check_used_port(proto, htons(local_port), local_addr, false) != 0; + return check_used_port(proto, htons(local_port), local_addr, false, false) != 0; } #if defined(CONFIG_NET_CONTEXT_CHECK) @@ -693,7 +729,8 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr, ret = check_used_port(context->proto, addr6->sin6_port, addr, - net_context_is_reuseaddr_set(context)); + net_context_is_reuseaddr_set(context), + net_context_is_reuseport_set(context)); if (ret != 0) { NET_ERR("Port %d is in use!", ntohs(addr6->sin6_port)); @@ -794,7 +831,8 @@ int net_context_bind(struct net_context *context, const struct sockaddr *addr, ret = check_used_port(context->proto, addr4->sin_port, addr, - net_context_is_reuseaddr_set(context)); + net_context_is_reuseaddr_set(context), + net_context_is_reuseport_set(context)); if (ret != 0) { NET_ERR("Port %d is in use!", ntohs(addr4->sin_port)); @@ -1440,6 +1478,32 @@ static int get_context_reuseaddr(struct net_context *context, #endif } +static int get_context_reuseport(struct net_context *context, + void *value, size_t *len) +{ +#if defined(CONFIG_NET_CONTEXT_REUSEPORT) + if (!value || !len) { + return -EINVAL; + } + + if (*len != sizeof(int)) { + return -EINVAL; + } + + if (context->options.reuseport == true) { + *((int *)value) = (int) true; + } else { + *((int *)value) = (int) false; + } + + *len = sizeof(int); + + return 0; +#else + return -ENOTSUP; +#endif +} + /* If buf is not NULL, then use it. Otherwise read the data to be written * to net_pkt from msghdr. */ @@ -2516,6 +2580,28 @@ static int set_context_reuseaddr(struct net_context *context, #endif } +static int set_context_reuseport(struct net_context *context, + const void *value, size_t len) +{ +#if defined(CONFIG_NET_CONTEXT_REUSEPORT) + bool reuseport = false; + + if (len != sizeof(int)) { + return -EINVAL; + } + + if (*((int *) value) != 0) { + reuseport = true; + } + + context->options.reuseport = reuseport; + + return 0; +#else + return -ENOTSUP; +#endif +} + int net_context_set_option(struct net_context *context, enum net_context_option option, const void *value, size_t len) @@ -2558,6 +2644,9 @@ int net_context_set_option(struct net_context *context, case NET_OPT_REUSEADDR: ret = set_context_reuseaddr(context, value, len); break; + case NET_OPT_REUSEPORT: + ret = set_context_reuseport(context, value, len); + break; } k_mutex_unlock(&context->lock); @@ -2607,6 +2696,9 @@ int net_context_get_option(struct net_context *context, case NET_OPT_REUSEADDR: ret = get_context_reuseaddr(context, value, len); break; + case NET_OPT_REUSEPORT: + ret = get_context_reuseport(context, value, len); + break; } k_mutex_unlock(&context->lock); diff --git a/subsys/net/ip/net_private.h b/subsys/net/ip/net_private.h index 305dd66a98a50..0f573ca12571c 100644 --- a/subsys/net/ip/net_private.h +++ b/subsys/net/ip/net_private.h @@ -59,6 +59,7 @@ extern void net_process_tx_packet(struct net_pkt *pkt); extern void net_context_init(void); extern const char *net_context_state(struct net_context *context); extern bool net_context_is_reuseaddr_set(struct net_context *context); +extern bool net_context_is_reuseport_set(struct net_context *context); extern void net_pkt_init(void); extern void net_tc_tx_init(void); extern void net_tc_rx_init(void); @@ -77,6 +78,11 @@ static inline bool net_context_is_reuseaddr_set(struct net_context *context) ARG_UNUSED(context); return false; } +static inline bool net_context_is_reuseport_set(struct net_context *context) +{ + ARG_UNUSED(context); + return false; +} #endif #if defined(CONFIG_NET_NATIVE) diff --git a/subsys/net/lib/sockets/sockets.c b/subsys/net/lib/sockets/sockets.c index a0f12161b0568..2a18d0a9fb2a2 100644 --- a/subsys/net/lib/sockets/sockets.c +++ b/subsys/net/lib/sockets/sockets.c @@ -2026,6 +2026,20 @@ int zsock_getsockopt_ctx(struct net_context *ctx, int level, int optname, return 0; } break; + + case SO_REUSEPORT: + if (IS_ENABLED(CONFIG_NET_CONTEXT_REUSEPORT)) { + ret = net_context_get_option(ctx, + NET_OPT_REUSEPORT, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } + break; } break; @@ -2176,6 +2190,21 @@ int zsock_setsockopt_ctx(struct net_context *ctx, int level, int optname, break; + case SO_REUSEPORT: + if (IS_ENABLED(CONFIG_NET_CONTEXT_REUSEPORT)) { + ret = net_context_set_option(ctx, + NET_OPT_REUSEPORT, + optval, optlen); + if (ret < 0) { + errno = -ret; + return -1; + } + + return 0; + } + + break; + case SO_PRIORITY: if (IS_ENABLED(CONFIG_NET_CONTEXT_PRIORITY)) { ret = net_context_set_option(ctx, From 533cf8b19bc2f5cc84db0efae6664c1a23237d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 1 Sep 2023 11:24:51 +0200 Subject: [PATCH 4/4] tests: net: sockets: add tests for SO_REUSEPORT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds unit tests for the SO_REUSEPORT sockets option. It also fixes some bugs in other tests triggered by the new features. Signed-off-by: Tobias Frauenschläger --- tests/net/socket/misc/src/main.c | 4 +- tests/net/socket/reuseaddr/src/main.c | 473 -------- .../CMakeLists.txt | 0 .../prj.conf | 1 + .../net/socket/reuseaddr_reuseport/src/main.c | 1011 +++++++++++++++++ .../testcase.yaml | 0 6 files changed, 1015 insertions(+), 474 deletions(-) delete mode 100644 tests/net/socket/reuseaddr/src/main.c rename tests/net/socket/{reuseaddr => reuseaddr_reuseport}/CMakeLists.txt (100%) rename tests/net/socket/{reuseaddr => reuseaddr_reuseport}/prj.conf (97%) create mode 100644 tests/net/socket/reuseaddr_reuseport/src/main.c rename tests/net/socket/{reuseaddr => reuseaddr_reuseport}/testcase.yaml (100%) diff --git a/tests/net/socket/misc/src/main.c b/tests/net/socket/misc/src/main.c index c8f31435fd830..384eafe61260b 100644 --- a/tests/net/socket/misc/src/main.c +++ b/tests/net/socket/misc/src/main.c @@ -286,6 +286,8 @@ void test_so_bindtodevice(int sock_c, int sock_s, struct sockaddr *peer_addr, zassert_equal(ret, 0, "close failed, %d", errno); ret = close(sock_s); zassert_equal(ret, 0, "close failed, %d", errno); + + k_sleep(K_MSEC(CONFIG_NET_TCP_TIME_WAIT_DELAY)); } void test_ipv4_so_bindtodevice(void) @@ -423,7 +425,7 @@ void test_getpeername(int family) ret = close(sock_s); zassert_equal(ret, 0, "close failed, %d", errno); - k_sleep(K_MSEC(CONFIG_NET_TCP_TIME_WAIT_DELAY)); + k_sleep(K_MSEC(2 * CONFIG_NET_TCP_TIME_WAIT_DELAY)); } diff --git a/tests/net/socket/reuseaddr/src/main.c b/tests/net/socket/reuseaddr/src/main.c deleted file mode 100644 index 1dc41fb72d142..0000000000000 --- a/tests/net/socket/reuseaddr/src/main.c +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright (c) 2019 Linaro Limited - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); - -#include -#include -#include -#include - -#include -#include - -#include "../../socket_helpers.h" - -#define TEST_IPV4_ANY_ADDR "0.0.0.0" -#define TEST_MY_IPV4_ADDR "192.0.2.1" -#define TEST_MY_IPV4_ADDR2 "192.0.2.2" - -#define TEST_IPV6_ANY_ADDR "::" -#define TEST_MY_IPV6_ADDR "2001:db8::1" -#define TEST_MY_IPV6_ADDR2 "2001:db8::2" - -#define LOCAL_PORT 4242 - -#define TCP_TEARDOWN_TIMEOUT K_SECONDS(3) - -#define SHOULD_SUCCEED true -#define SHOULD_FAIL false - -static inline void prepare_sock_tcp(sa_family_t family, const char *addr, uint16_t port, - int *sock, struct sockaddr *sockaddr) -{ - if (family == AF_INET) { - prepare_sock_tcp_v4(addr, - port, - sock, - (struct sockaddr_in *) sockaddr); - } else if (family == AF_INET6) { - prepare_sock_tcp_v6(addr, - port, - sock, - (struct sockaddr_in6 *) sockaddr); - } -} - -static void test_getsocketopt_reuseaddr(int sock, void *optval, socklen_t *optlen) -{ - zassert_equal(getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, optval, optlen), - 0, - "getsocketopt() failed with error %d", errno); -} - -static void test_setsocketopt_reuseaddr(int sock, void *optval, socklen_t optlen) -{ - zassert_equal(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, optval, optlen), - 0, - "setsocketopt() failed with error %d", errno); -} - -static void test_enable_reuseaddr(int sock) -{ - int value = 1; - - test_setsocketopt_reuseaddr(sock, &value, sizeof(value)); -} - -static void test_add_local_ip_address(sa_family_t family, const char *ip) -{ - if (family == AF_INET) { - struct sockaddr_in addr; - - zsock_inet_pton(AF_INET, ip, &addr.sin_addr); - - zassert_not_null(net_if_ipv4_addr_add(net_if_get_default(), - &addr.sin_addr, - NET_ADDR_MANUAL, - 0), - "Cannot add IPv4 address %s", ip); - } else if (family == AF_INET6) { - struct sockaddr_in6 addr; - - zsock_inet_pton(AF_INET6, ip, &addr.sin6_addr); - - zassert_not_null(net_if_ipv6_addr_add(net_if_get_default(), - &addr.sin6_addr, - NET_ADDR_MANUAL, - 0), - "Cannot add IPv6 address %s", ip); - } -} - -static void test_bind_success(int sock, const struct sockaddr * addr, socklen_t addrlen) -{ - zassert_equal(bind(sock, addr, addrlen), - 0, - "bind() failed with error %d", errno); -} - -static void test_bind_fail(int sock, const struct sockaddr * addr, socklen_t addrlen) -{ - zassert_equal(bind(sock, addr, addrlen), - -1, - "bind() succeeded incorrectly"); - - zassert_equal(errno, EADDRINUSE, "bind() returned unexpected errno (%d)", errno); -} - -static void test_listen(int sock) -{ - zassert_equal(listen(sock, 0), - 0, - "listen() failed with error %d", errno); -} - -static void test_connect(int sock, const struct sockaddr * addr, socklen_t addrlen) -{ - zassert_equal(connect(sock, addr, addrlen), - 0, - "connect() failed with error %d", errno); - - if (IS_ENABLED(CONFIG_NET_TC_THREAD_PREEMPTIVE)) { - /* Let the connection proceed */ - k_msleep(50); - } -} - -static int test_accept(int sock, struct sockaddr * addr, socklen_t * addrlen) -{ - int new_sock = accept(sock, addr, addrlen); - - zassert_not_equal(new_sock, -1, "accept() failed with error %d", errno); - - return new_sock; -} - -static void calc_net_context(struct net_context *context, void *user_data) -{ - int *count = user_data; - - (*count)++; -} - -/* Wait until the number of TCP contexts reaches a certain level - * exp_num_contexts : The number of contexts to wait for - * timeout : The time to wait for - */ -int wait_for_n_tcp_contexts(int exp_num_contexts, k_timeout_t timeout) -{ - uint32_t start_time = k_uptime_get_32(); - uint32_t time_diff; - int context_count = 0; - - /* After the client socket closing, the context count should be 1 less */ - net_context_foreach(calc_net_context, &context_count); - - time_diff = k_uptime_get_32() - start_time; - - /* Eventually the client socket should be cleaned up */ - while (context_count != exp_num_contexts) { - context_count = 0; - net_context_foreach(calc_net_context, &context_count); - k_sleep(K_MSEC(50)); - time_diff = k_uptime_get_32() - start_time; - - if (K_MSEC(time_diff).ticks > timeout.ticks) { - return -ETIMEDOUT; - } - } - - return 0; -} - -static void test_context_cleanup(void) -{ - zassert_equal(wait_for_n_tcp_contexts(0, TCP_TEARDOWN_TIMEOUT), - 0, - "Not all TCP contexts properly cleaned up"); -} - - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_enable_disable) -{ - int server_sock = zsock_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - zassert_true(server_sock >= 0, "socket open failed"); - - int value = -1; - size_t value_size = sizeof(int); - - /* Read initial value */ - test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); - zassert_equal(value_size, sizeof(int), "incorrect value size returned by getsocketopt()"); - zassert_equal(value, (int) false, "SO_REUSEADDR incorrectly set (expected false)"); - - /* Enable option */ - value = 1; - test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); - test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); - zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); - - /* Enable option (with other value as linux takes any int here) */ - value = 2; - test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); - test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); - zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); - - /* Enable option (with other value as linux takes any int here) */ - value = 0x100; - test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); - test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); - zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); - - /* Enable option (with other value as linux takes any int here) */ - value = -1; - test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); - test_getsocketopt_reuseaddr(server_sock, (void *)&value, (socklen_t *)&value_size); - zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); - - close(server_sock); - - test_context_cleanup(); -} - - -static void test_reuseaddr_unspecified_specified_common(sa_family_t family, - char const *first_ip, - char const *second_ip, - bool should_succeed) -{ - int server_sock1 = -1; - int server_sock2 = -1; - - struct sockaddr bind_addr1; - struct sockaddr bind_addr2; - - /* Create the sockets */ - prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock1, &bind_addr1); - prepare_sock_tcp(family, second_ip, LOCAL_PORT, &server_sock2, &bind_addr2); - - /* Bind the first socket */ - test_bind_success(server_sock1, &bind_addr1, sizeof(bind_addr1)); - - /* Try to bind the second socket, should fail */ - test_bind_fail(server_sock2, &bind_addr2, sizeof(bind_addr2)); - - /* Enable SO_REUSEADDR option for the second socket */ - test_enable_reuseaddr(server_sock2); - - /* Try to bind the second socket again */ - if (should_succeed) { - test_bind_success(server_sock2, &bind_addr2, sizeof(bind_addr2)); - } else { - test_bind_fail(server_sock2, &bind_addr2, sizeof(bind_addr2)); - } - - close(server_sock1); - close(server_sock2); - - test_context_cleanup(); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_first_unspecified) -{ - test_reuseaddr_unspecified_specified_common(AF_INET, - TEST_IPV4_ANY_ADDR, - TEST_MY_IPV4_ADDR, - SHOULD_SUCCEED); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_first_unspecified) -{ - test_reuseaddr_unspecified_specified_common(AF_INET6, - TEST_IPV6_ANY_ADDR, - TEST_MY_IPV6_ADDR, - SHOULD_SUCCEED); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_second_unspecified) -{ - test_reuseaddr_unspecified_specified_common(AF_INET, - TEST_MY_IPV4_ADDR, - TEST_IPV4_ANY_ADDR, - SHOULD_SUCCEED); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_second_unspecified) -{ - test_reuseaddr_unspecified_specified_common(AF_INET6, - TEST_MY_IPV6_ADDR, - TEST_IPV6_ANY_ADDR, - SHOULD_SUCCEED); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_both_unspecified) -{ - test_reuseaddr_unspecified_specified_common(AF_INET, - TEST_IPV4_ANY_ADDR, - TEST_IPV4_ANY_ADDR, - SHOULD_FAIL); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_both_unspecified) -{ - test_reuseaddr_unspecified_specified_common(AF_INET6, - TEST_IPV6_ANY_ADDR, - TEST_IPV6_ANY_ADDR, - SHOULD_FAIL); -} - - -static void test_reuseaddr_tcp_listening_common(sa_family_t family, - char const *first_ip, - char const *second_ip) -{ - int server_sock1 = -1; - int server_sock2 = -1; - - struct sockaddr bind_addr1; - struct sockaddr bind_addr2; - - /* Create the sockets */ - prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock1, &bind_addr1); - prepare_sock_tcp(family, second_ip, LOCAL_PORT, &server_sock2, &bind_addr2); - - /* Bind the first socket */ - test_bind_success(server_sock1, &bind_addr1, sizeof(bind_addr1)); - - /* Set the first socket to LISTEN state */ - test_listen(server_sock1); - - /* Enable SO_REUSEADDR option for the second socket */ - test_enable_reuseaddr(server_sock2); - - /* Try to bind the second socket, should fail */ - test_bind_fail(server_sock2, (struct sockaddr *) &bind_addr2, sizeof(bind_addr2)); - - close(server_sock1); - close(server_sock2); - - test_context_cleanup(); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_tcp_unspecified_listening) -{ - test_reuseaddr_tcp_listening_common(AF_INET, - TEST_IPV4_ANY_ADDR, - TEST_MY_IPV4_ADDR); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_tcp_unspecified_listening) -{ - test_reuseaddr_tcp_listening_common(AF_INET6, - TEST_IPV6_ANY_ADDR, - TEST_MY_IPV6_ADDR); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_tcp_specified_listening) -{ - test_reuseaddr_tcp_listening_common(AF_INET, - TEST_MY_IPV4_ADDR, - TEST_IPV4_ANY_ADDR); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_tcp_specified_listening) -{ - test_reuseaddr_tcp_listening_common(AF_INET6, - TEST_MY_IPV6_ADDR, - TEST_IPV6_ANY_ADDR); -} - - -static void test_reuseaddr_tcp_tcp_time_wait_common(sa_family_t family, - char const *first_ip, - char const *second_ip) -{ - int server_sock = -1; - int client_sock = -1; - int accept_sock = -1; - - struct sockaddr bind_addr; - struct sockaddr conn_addr; - - struct sockaddr accept_addr; - socklen_t accept_addrlen = sizeof(accept_addr); - - prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock, &bind_addr); - prepare_sock_tcp(family, second_ip, LOCAL_PORT, &client_sock, &conn_addr); - - /* Bind the server socket */ - test_bind_success(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); - - /* Start listening on the server socket */ - test_listen(server_sock); - - /* Connect the client */ - test_connect(client_sock, &conn_addr, sizeof(conn_addr)); - - /* Accept the client */ - accept_sock = test_accept(server_sock, &accept_addr, &accept_addrlen); - - /* Close the server socket */ - close(server_sock); - - /* Close the accepted socket */ - close(accept_sock); - - /* Wait a short time for the accept socket to enter TIME_WAIT state*/ - k_msleep(50); - - /* Recreate the server socket */ - prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock, &bind_addr); - - /* Bind the server socket, should fail */ - test_bind_fail(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); - - /* Enable SO_REUSEADDR option for the new server socket */ - test_enable_reuseaddr(server_sock); - - /* Try to bind the new server socket again, should work now */ - test_bind_success(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); - - close(client_sock); - close(server_sock); - - test_context_cleanup(); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_tcp_time_wait_unspecified) -{ - test_reuseaddr_tcp_tcp_time_wait_common(AF_INET, - TEST_IPV4_ANY_ADDR, - TEST_MY_IPV4_ADDR); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_tcp_time_wait_unspecified) -{ - test_reuseaddr_tcp_tcp_time_wait_common(AF_INET6, - TEST_IPV6_ANY_ADDR, - TEST_MY_IPV6_ADDR); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv4_tcp_time_wait_specified) -{ - test_reuseaddr_tcp_tcp_time_wait_common(AF_INET, - TEST_MY_IPV4_ADDR, - TEST_MY_IPV4_ADDR); -} - -ZTEST_USER(socket_reuseaddr_reuseport_test_suite, test_reuseaddr_ipv6_tcp_time_wait_specified) -{ - test_reuseaddr_tcp_tcp_time_wait_common(AF_INET6, - TEST_MY_IPV6_ADDR, - TEST_MY_IPV6_ADDR); -} - - -static void *setup(void) -{ - /* Make sure that both the specified IPv4 and IPv6 addresses are - * added to the network interface. - */ - test_add_local_ip_address(AF_INET, TEST_MY_IPV4_ADDR); - test_add_local_ip_address(AF_INET6, TEST_MY_IPV6_ADDR); - - return NULL; -} - - -ZTEST_SUITE(socket_reuseaddr_reuseport_test_suite, NULL, setup, NULL, NULL, NULL); diff --git a/tests/net/socket/reuseaddr/CMakeLists.txt b/tests/net/socket/reuseaddr_reuseport/CMakeLists.txt similarity index 100% rename from tests/net/socket/reuseaddr/CMakeLists.txt rename to tests/net/socket/reuseaddr_reuseport/CMakeLists.txt diff --git a/tests/net/socket/reuseaddr/prj.conf b/tests/net/socket/reuseaddr_reuseport/prj.conf similarity index 97% rename from tests/net/socket/reuseaddr/prj.conf rename to tests/net/socket/reuseaddr_reuseport/prj.conf index c820fefa19933..7ff751c8785cd 100644 --- a/tests/net/socket/reuseaddr/prj.conf +++ b/tests/net/socket/reuseaddr_reuseport/prj.conf @@ -24,6 +24,7 @@ CONFIG_NET_BUF_RX_COUNT=64 CONFIG_NET_BUF_TX_COUNT=64 CONFIG_NET_CONTEXT_REUSEADDR=y +CONFIG_NET_CONTEXT_REUSEPORT=y CONFIG_NET_HOSTNAME_ENABLE=y CONFIG_NET_HOSTNAME="ztest_hostname" diff --git a/tests/net/socket/reuseaddr_reuseport/src/main.c b/tests/net/socket/reuseaddr_reuseport/src/main.c new file mode 100644 index 0000000000000..db047ac9cf4d8 --- /dev/null +++ b/tests/net/socket/reuseaddr_reuseport/src/main.c @@ -0,0 +1,1011 @@ +/* + * Copyright (c) 2019 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); + +#include +#include +#include +#include + +#include +#include + +#include "../../socket_helpers.h" + +#define TEST_IPV4_ANY_ADDR "0.0.0.0" +#define TEST_MY_IPV4_ADDR "192.0.2.1" +#define TEST_MY_IPV4_ADDR2 "192.0.2.2" + +#define TEST_IPV6_ANY_ADDR "::" +#define TEST_MY_IPV6_ADDR "2001:db8::1" +#define TEST_MY_IPV6_ADDR2 "2001:db8::2" + +#define LOCAL_PORT 4242 + +#define TCP_TEARDOWN_TIMEOUT K_SECONDS(3) + +#define SHOULD_SUCCEED true +#define SHOULD_FAIL false + +static void test_add_local_ip_address(sa_family_t family, const char *ip) +{ + if (family == AF_INET) { + struct sockaddr_in addr; + + zsock_inet_pton(AF_INET, ip, &addr.sin_addr); + + zassert_not_null(net_if_ipv4_addr_add(net_if_get_default(), + &addr.sin_addr, + NET_ADDR_MANUAL, + 0), + "Cannot add IPv4 address %s", ip); + } else if (family == AF_INET6) { + struct sockaddr_in6 addr; + + zsock_inet_pton(AF_INET6, ip, &addr.sin6_addr); + + zassert_not_null(net_if_ipv6_addr_add(net_if_get_default(), + &addr.sin6_addr, + NET_ADDR_MANUAL, + 0), + "Cannot add IPv6 address %s", ip); + } +} + +static void *setup(void) +{ + /* Make sure that both the specified IPv4 and IPv6 addresses are + * added to the network interface. + */ + test_add_local_ip_address(AF_INET, TEST_MY_IPV4_ADDR); + test_add_local_ip_address(AF_INET6, TEST_MY_IPV6_ADDR); + + return NULL; +} + +static inline void prepare_sock_tcp(sa_family_t family, const char *ip, uint16_t port, + int *sock, struct sockaddr *addr) +{ + if (family == AF_INET) { + prepare_sock_tcp_v4(ip, + port, + sock, + (struct sockaddr_in *) addr); + } else if (family == AF_INET6) { + prepare_sock_tcp_v6(ip, + port, + sock, + (struct sockaddr_in6 *) addr); + } +} + +static inline void prepare_sock_udp(sa_family_t family, const char *ip, uint16_t port, + int *sock, struct sockaddr *addr) +{ + if (family == AF_INET) { + prepare_sock_udp_v4(ip, + port, + sock, + (struct sockaddr_in *) addr); + } else if (family == AF_INET6) { + prepare_sock_udp_v6(ip, + port, + sock, + (struct sockaddr_in6 *) addr); + } +} + +static void test_getsocketopt_reuseaddr(int sock, void *optval, socklen_t *optlen) +{ + zassert_equal(getsockopt(sock, SOL_SOCKET, SO_REUSEADDR, optval, optlen), + 0, + "getsocketopt() failed with error %d", errno); +} + +static void test_setsocketopt_reuseaddr(int sock, void *optval, socklen_t optlen) +{ + zassert_equal(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, optval, optlen), + 0, + "setsocketopt() failed with error %d", errno); +} + +static void test_enable_reuseaddr(int sock) +{ + int value = 1; + + test_setsocketopt_reuseaddr(sock, &value, sizeof(value)); +} + +static void test_getsocketopt_reuseport(int sock, void *optval, socklen_t *optlen) +{ + zassert_equal(getsockopt(sock, SOL_SOCKET, SO_REUSEPORT, optval, optlen), + 0, + "getsocketopt() failed with error %d", errno); +} + +static void test_setsocketopt_reuseport(int sock, void *optval, socklen_t optlen) +{ + zassert_equal(setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, optval, optlen), + 0, + "setsocketopt() failed with error %d", errno); +} + +static void test_enable_reuseport(int sock) +{ + int value = 1; + + test_setsocketopt_reuseport(sock, &value, sizeof(value)); +} + +static void test_bind_success(int sock, const struct sockaddr *addr, socklen_t addrlen) +{ + zassert_equal(bind(sock, addr, addrlen), + 0, + "bind() failed with error %d", errno); +} + +static void test_bind_fail(int sock, const struct sockaddr *addr, socklen_t addrlen) +{ + zassert_equal(bind(sock, addr, addrlen), + -1, + "bind() succeeded incorrectly"); + + zassert_equal(errno, EADDRINUSE, "bind() returned unexpected errno (%d)", errno); +} + +static void test_listen(int sock) +{ + zassert_equal(listen(sock, 0), + 0, + "listen() failed with error %d", errno); +} + +static void test_connect_success(int sock, const struct sockaddr *addr, socklen_t addrlen) +{ + zassert_equal(connect(sock, addr, addrlen), + 0, + "connect() failed with error %d", errno); + + if (IS_ENABLED(CONFIG_NET_TC_THREAD_PREEMPTIVE)) { + /* Let the connection proceed */ + k_msleep(50); + } +} + +static void test_connect_fail(int sock, const struct sockaddr *addr, socklen_t addrlen) +{ + zassert_equal(connect(sock, addr, addrlen), + -1, + "connect() succeeded incorrectly"); + + zassert_equal(errno, EADDRINUSE, "connect() returned unexpected errno (%d)", errno); +} + +static int test_accept(int sock, struct sockaddr *addr, socklen_t *addrlen) +{ + int new_sock = accept(sock, addr, addrlen); + + zassert_not_equal(new_sock, -1, "accept() failed with error %d", errno); + + return new_sock; +} + +static void test_sendto(int sock, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + zassert_equal(sendto(sock, buf, len, flags, dest_addr, addrlen), + len, + "sendto failed with error %d", errno); +} + +static void test_recvfrom_success(int sock, void *buf, size_t max_len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + zassert_equal(recvfrom(sock, buf, max_len, flags, src_addr, addrlen), + max_len, + "recvfrom failed with error %d", errno); +} + +static void test_recvfrom_fail(int sock, void *buf, size_t max_len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + zassert_equal(recvfrom(sock, buf, max_len, flags, src_addr, addrlen), + -1, + "recvfrom succeeded incorrectly"); + + zassert_equal(errno, EAGAIN, "recvfrom() returned unexpected errno (%d)", errno); +} + +static void test_recv_success(int sock, void *buf, size_t max_len, int flags) +{ + zassert_equal(recv(sock, buf, max_len, flags), + max_len, + "recv failed with error %d", errno); +} + +static void test_recv_fail(int sock, void *buf, size_t max_len, int flags) +{ + zassert_equal(recv(sock, buf, max_len, flags), + -1, + "recvfrom succeeded incorrectly"); + + zassert_equal(errno, EAGAIN, "recvfrom() returned unexpected errno (%d)", errno); +} + +static void calc_net_context(struct net_context *context, void *user_data) +{ + int *count = user_data; + + (*count)++; +} + +/* Wait until the number of TCP contexts reaches a certain level + * exp_num_contexts : The number of contexts to wait for + * timeout : The time to wait for + */ +int wait_for_n_tcp_contexts(int exp_num_contexts, k_timeout_t timeout) +{ + uint32_t start_time = k_uptime_get_32(); + uint32_t time_diff; + int context_count = 0; + + /* After the client socket closing, the context count should be 1 less */ + net_context_foreach(calc_net_context, &context_count); + + time_diff = k_uptime_get_32() - start_time; + + /* Eventually the client socket should be cleaned up */ + while (context_count != exp_num_contexts) { + context_count = 0; + net_context_foreach(calc_net_context, &context_count); + k_sleep(K_MSEC(50)); + time_diff = k_uptime_get_32() - start_time; + + if (K_MSEC(time_diff).ticks > timeout.ticks) { + return -ETIMEDOUT; + } + } + + return 0; +} + +static void test_context_cleanup(void) +{ + zassert_equal(wait_for_n_tcp_contexts(0, TCP_TEARDOWN_TIMEOUT), + 0, + "Not all TCP contexts properly cleaned up"); +} + + +ZTEST_USER(socket_reuseaddr_test_suite, test_enable_disable) +{ + int server_sock = -1; + int value = -1; + socklen_t value_size = sizeof(int); + + server_sock = zsock_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + zassert_true(server_sock >= 0, "socket open failed"); + + /* Read initial value */ + test_getsocketopt_reuseaddr(server_sock, (void *)&value, &value_size); + zassert_equal(value_size, sizeof(int), "incorrect value size returned by getsocketopt()"); + zassert_equal(value, (int) false, "SO_REUSEADDR incorrectly set (expected false)"); + + /* Enable option */ + value = 1; + test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseaddr(server_sock, (void *)&value, &value_size); + zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = 2; + test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseaddr(server_sock, (void *)&value, &value_size); + zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = 0x100; + test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseaddr(server_sock, (void *)&value, &value_size); + zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = -1; + test_setsocketopt_reuseaddr(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseaddr(server_sock, (void *)&value, &value_size); + zassert_equal(value, (int) true, "SO_REUSEADDR not correctly set, returned %d", value); + + close(server_sock); + + test_context_cleanup(); +} + + +static void test_reuseaddr_unspecified_specified_common(sa_family_t family, + char const *first_ip, + char const *second_ip, + bool should_succeed) +{ + int server_sock1 = -1; + int server_sock2 = -1; + + struct sockaddr bind_addr1; + struct sockaddr bind_addr2; + + /* Create the sockets */ + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock1, &bind_addr1); + prepare_sock_tcp(family, second_ip, LOCAL_PORT, &server_sock2, &bind_addr2); + + /* Bind the first socket */ + test_bind_success(server_sock1, &bind_addr1, sizeof(bind_addr1)); + + /* Try to bind the second socket, should fail */ + test_bind_fail(server_sock2, &bind_addr2, sizeof(bind_addr2)); + + /* Enable SO_REUSEADDR option for the second socket */ + test_enable_reuseaddr(server_sock2); + + /* Try to bind the second socket again */ + if (should_succeed) { + test_bind_success(server_sock2, &bind_addr2, sizeof(bind_addr2)); + } else { + test_bind_fail(server_sock2, &bind_addr2, sizeof(bind_addr2)); + } + + close(server_sock1); + close(server_sock2); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv4_first_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv6_first_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv4_second_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_IPV4_ANY_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv6_second_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_IPV6_ANY_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv4_both_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_IPV4_ANY_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv6_both_unspecified) +{ + test_reuseaddr_unspecified_specified_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_IPV6_ANY_ADDR, + SHOULD_FAIL); +} + + +static void test_reuseaddr_tcp_listening_common(sa_family_t family, + char const *first_ip, + char const *second_ip) +{ + int server_sock1 = -1; + int server_sock2 = -1; + + struct sockaddr bind_addr1; + struct sockaddr bind_addr2; + + /* Create the sockets */ + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock1, &bind_addr1); + prepare_sock_tcp(family, second_ip, LOCAL_PORT, &server_sock2, &bind_addr2); + + /* Bind the first socket */ + test_bind_success(server_sock1, &bind_addr1, sizeof(bind_addr1)); + + /* Set the first socket to LISTEN state */ + test_listen(server_sock1); + + /* Enable SO_REUSEADDR option for the second socket */ + test_enable_reuseaddr(server_sock2); + + /* Try to bind the second socket, should fail */ + test_bind_fail(server_sock2, (struct sockaddr *) &bind_addr2, sizeof(bind_addr2)); + + close(server_sock1); + close(server_sock2); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv4_tcp_unspecified_listening) +{ + test_reuseaddr_tcp_listening_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv6_tcp_unspecified_listening) +{ + test_reuseaddr_tcp_listening_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv4_tcp_specified_listening) +{ + test_reuseaddr_tcp_listening_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_IPV4_ANY_ADDR); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv6_tcp_specified_listening) +{ + test_reuseaddr_tcp_listening_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_IPV6_ANY_ADDR); +} + + +static void test_reuseaddr_tcp_tcp_time_wait_common(sa_family_t family, + char const *first_ip, + char const *second_ip) +{ + int server_sock = -1; + int client_sock = -1; + int accept_sock = -1; + + struct sockaddr bind_addr; + struct sockaddr conn_addr; + + struct sockaddr accept_addr; + socklen_t accept_addrlen = sizeof(accept_addr); + + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock, &bind_addr); + prepare_sock_tcp(family, second_ip, LOCAL_PORT, &client_sock, &conn_addr); + + /* Bind the server socket */ + test_bind_success(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); + + /* Start listening on the server socket */ + test_listen(server_sock); + + /* Connect the client */ + test_connect_success(client_sock, &conn_addr, sizeof(conn_addr)); + + /* Accept the client */ + accept_sock = test_accept(server_sock, &accept_addr, &accept_addrlen); + + /* Close the server socket */ + close(server_sock); + + /* Close the accepted socket */ + close(accept_sock); + + /* Wait a short time for the accept socket to enter TIME_WAIT state*/ + k_msleep(50); + + /* Recreate the server socket */ + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock, &bind_addr); + + /* Bind the server socket, should fail */ + test_bind_fail(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); + + /* Enable SO_REUSEADDR option for the new server socket */ + test_enable_reuseaddr(server_sock); + + /* Try to bind the new server socket again, should work now */ + test_bind_success(server_sock, (struct sockaddr *) &bind_addr, sizeof(bind_addr)); + + close(client_sock); + close(server_sock); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv4_tcp_time_wait_unspecified) +{ + test_reuseaddr_tcp_tcp_time_wait_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv6_tcp_time_wait_unspecified) +{ + test_reuseaddr_tcp_tcp_time_wait_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv4_tcp_time_wait_specified) +{ + test_reuseaddr_tcp_tcp_time_wait_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_MY_IPV4_ADDR); +} + +ZTEST_USER(socket_reuseaddr_test_suite, test_ipv6_tcp_time_wait_specified) +{ + test_reuseaddr_tcp_tcp_time_wait_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_MY_IPV6_ADDR); +} + + +ZTEST_SUITE(socket_reuseaddr_test_suite, NULL, setup, NULL, NULL, NULL); + + +ZTEST_USER(socket_reuseport_test_suite, test_enable_disable) +{ + int server_sock = -1; + + int value = -1; + socklen_t value_size = sizeof(int); + + server_sock = zsock_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + zassert_true(server_sock >= 0, "socket open failed"); + + /* Read initial value */ + test_getsocketopt_reuseport(server_sock, (void *)&value, &value_size); + zassert_equal(value_size, sizeof(int), "incorrect value size returned by getsocketopt()"); + zassert_equal(value, (int) false, "SO_REUSEPORT incorrectly set (expected false)"); + + /* Enable option */ + value = 1; + test_setsocketopt_reuseport(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseport(server_sock, (void *)&value, &value_size); + zassert_equal(value, (int) true, "SO_REUSEPORT not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = 2; + test_setsocketopt_reuseport(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseport(server_sock, (void *)&value, &value_size); + zassert_equal(value, (int) true, "SO_REUSEPORT not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = 0x100; + test_setsocketopt_reuseport(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseport(server_sock, (void *)&value, &value_size); + zassert_equal(value, (int) true, "SO_REUSEPORT not correctly set, returned %d", value); + + /* Enable option (with other value as linux takes any int here) */ + value = -1; + test_setsocketopt_reuseport(server_sock, (void *)&value, sizeof(value)); + test_getsocketopt_reuseport(server_sock, (void *)&value, &value_size); + zassert_equal(value, (int) true, "SO_REUSEPORT not correctly set, returned %d", value); + + close(server_sock); + + test_context_cleanup(); +} + + +static void test_reuseport_unspecified_specified_common(sa_family_t family, + char const *first_ip, + char const *second_ip, + bool should_succeed) +{ + int server_sock1 = -1; + int server_sock2 = -1; + + struct sockaddr bind_addr1; + struct sockaddr bind_addr2; + + /* Create the sockets */ + prepare_sock_tcp(family, first_ip, LOCAL_PORT, &server_sock1, &bind_addr1); + prepare_sock_tcp(family, second_ip, LOCAL_PORT, &server_sock2, &bind_addr2); + + /* Depending on the expected result, we enable SO_REUSEPORT for the first socket */ + if (should_succeed) { + test_enable_reuseport(server_sock1); + } + + /* Bind the first socket */ + test_bind_success(server_sock1, &bind_addr1, sizeof(bind_addr1)); + + /* Try to bind the second socket, should fail */ + test_bind_fail(server_sock2, &bind_addr2, sizeof(bind_addr2)); + + /* Enable SO_REUSEPORT option for the second socket */ + test_enable_reuseport(server_sock2); + + /* Try to bind the second socket again */ + if (should_succeed) { + test_bind_success(server_sock2, &bind_addr2, sizeof(bind_addr2)); + } else { + test_bind_fail(server_sock2, &bind_addr2, sizeof(bind_addr2)); + } + + close(server_sock1); + close(server_sock2); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_both_unspecified_bad) +{ + test_reuseport_unspecified_specified_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_IPV4_ANY_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_both_unspecified_bad) +{ + test_reuseport_unspecified_specified_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_IPV6_ANY_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_both_unspecified_good) +{ + test_reuseport_unspecified_specified_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_IPV4_ANY_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_both_unspecified_good) +{ + test_reuseport_unspecified_specified_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_IPV6_ANY_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_both_specified_bad) +{ + test_reuseport_unspecified_specified_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_MY_IPV4_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_both_specified_bad) +{ + test_reuseport_unspecified_specified_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_MY_IPV6_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_both_specified_good) +{ + test_reuseport_unspecified_specified_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_MY_IPV4_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_both_specified_good) +{ + test_reuseport_unspecified_specified_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_MY_IPV6_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_first_unspecified_bad) +{ + test_reuseport_unspecified_specified_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_first_unspecified_bad) +{ + test_reuseport_unspecified_specified_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_first_unspecified_good) +{ + test_reuseport_unspecified_specified_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_first_unspecified_good) +{ + test_reuseport_unspecified_specified_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_second_unspecified_bad) +{ + test_reuseport_unspecified_specified_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_IPV4_ANY_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_second_unspecified_bad) +{ + test_reuseport_unspecified_specified_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_IPV6_ANY_ADDR, + SHOULD_FAIL); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_second_unspecified_good) +{ + test_reuseport_unspecified_specified_common(AF_INET, + TEST_MY_IPV4_ADDR, + TEST_IPV4_ANY_ADDR, + SHOULD_SUCCEED); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_second_unspecified_good) +{ + test_reuseport_unspecified_specified_common(AF_INET6, + TEST_MY_IPV6_ADDR, + TEST_IPV6_ANY_ADDR, + SHOULD_SUCCEED); +} + + +enum sockets_reuseport_enabled { + NONE_SET = 0, + FIRST_SET, + SECOND_SET, + BOTH_SET +}; + +static void test_reuseport_udp_server_client_common(sa_family_t family, + char const *ip, + enum sockets_reuseport_enabled setup) +{ + int server_sock = -1; + int client_sock = -1; + int accept_sock = 1; + + struct sockaddr server_addr; + struct sockaddr client_addr; + + struct sockaddr accept_addr; + socklen_t accept_addr_len = sizeof(accept_addr); + + char tx_buf = 0x55; + char rx_buf; + + /* Create sockets */ + prepare_sock_udp(family, ip, LOCAL_PORT, &server_sock, &server_addr); + prepare_sock_udp(family, ip, 0, &client_sock, &client_addr); + + /* Make sure we can bind to the address:port */ + if (setup == FIRST_SET || setup == BOTH_SET) { + test_enable_reuseport(server_sock); + } + + /* Bind server socket */ + test_bind_success(server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)); + + /* Bind client socket (on a random port) */ + test_bind_success(client_sock, (struct sockaddr *) &client_addr, sizeof(client_addr)); + + /* Send message from client to server */ + test_sendto(client_sock, &tx_buf, sizeof(tx_buf), 0, &server_addr, sizeof(server_addr)); + + /* Give the packet a chance to go through the net stack */ + k_msleep(50); + + /* Receive data from the client */ + rx_buf = 0; + test_recvfrom_success(server_sock, &rx_buf, sizeof(rx_buf), MSG_DONTWAIT, + &accept_addr, &accept_addr_len); + zassert_equal(rx_buf, tx_buf, "wrong data"); + + /* Create a more specific socket to have a direct connection to the new client */ + accept_sock = socket(family, SOCK_DGRAM, IPPROTO_UDP); + zassert_true(accept_sock >= 0, "socket open failed"); + + /* Make sure we can bind to the address:port */ + if (setup == SECOND_SET || setup == BOTH_SET) { + test_enable_reuseport(accept_sock); + } + + /* Try to bind new client socket */ + if (setup == BOTH_SET) { + /* Should succeed */ + test_bind_success(accept_sock, (struct sockaddr *) &server_addr, + sizeof(server_addr)); + } else { + /* Should fail */ + test_bind_fail(accept_sock, (struct sockaddr *) &server_addr, + sizeof(server_addr)); + } + + /* Connect the client to set remote address and remote port */ + test_connect_success(accept_sock, &accept_addr, sizeof(accept_addr)); + + /* Send another message from client to server */ + test_sendto(client_sock, &tx_buf, sizeof(tx_buf), 0, &server_addr, sizeof(server_addr)); + + /* Give the packet a chance to go through the net stack */ + k_msleep(50); + + /* Receive the data */ + if (setup == BOTH_SET) { + /* We should receive data on the new specific socket, not on the general one */ + rx_buf = 0; + test_recvfrom_fail(server_sock, &rx_buf, sizeof(rx_buf), MSG_DONTWAIT, + &accept_addr, &accept_addr_len); + + rx_buf = 0; + test_recv_success(accept_sock, &rx_buf, sizeof(rx_buf), MSG_DONTWAIT); + } else { + /* We should receive data on the general server socket */ + rx_buf = 0; + test_recvfrom_success(server_sock, &rx_buf, sizeof(rx_buf), MSG_DONTWAIT, + &accept_addr, &accept_addr_len); + + rx_buf = 0; + test_recv_fail(accept_sock, &rx_buf, sizeof(rx_buf), MSG_DONTWAIT); + } + + close(accept_sock); + close(client_sock); + close(server_sock); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_udp_bad_both_not_set) +{ + test_reuseport_udp_server_client_common(AF_INET, + TEST_MY_IPV4_ADDR, + NONE_SET); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_udp_bad_both_not_set) +{ + test_reuseport_udp_server_client_common(AF_INET6, + TEST_MY_IPV6_ADDR, + NONE_SET); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_udp_bad_first_not_set) +{ + test_reuseport_udp_server_client_common(AF_INET, + TEST_MY_IPV4_ADDR, + SECOND_SET); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_udp_bad_first_not_set) +{ + test_reuseport_udp_server_client_common(AF_INET6, + TEST_MY_IPV6_ADDR, + SECOND_SET); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_udp_bad_second_not_set) +{ + test_reuseport_udp_server_client_common(AF_INET, + TEST_MY_IPV4_ADDR, + FIRST_SET); +} + + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_udp_bad_second_not_set) +{ + test_reuseport_udp_server_client_common(AF_INET6, + TEST_MY_IPV6_ADDR, + FIRST_SET); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_udp_good) +{ + test_reuseport_udp_server_client_common(AF_INET, + TEST_MY_IPV4_ADDR, + BOTH_SET); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_udp_good) +{ + test_reuseport_udp_server_client_common(AF_INET6, + TEST_MY_IPV6_ADDR, + BOTH_SET); +} + + +static void test_reuseport_tcp_identical_clients_common(sa_family_t family, + char const *server_ip, + char const *client_ip) +{ + int server_sock = -1; + int client_sock1 = -1; + int client_sock2 = -1; + int accept_sock = 1; + + struct sockaddr server_addr; + struct sockaddr client_addr; + struct sockaddr connect_addr; + + struct sockaddr accept_addr; + socklen_t accept_addr_len = sizeof(accept_addr); + + /* Create sockets */ + prepare_sock_tcp(family, server_ip, LOCAL_PORT, &server_sock, &server_addr); + prepare_sock_tcp(family, client_ip, LOCAL_PORT + 1, &client_sock1, &client_addr); + prepare_sock_tcp(family, client_ip, LOCAL_PORT, &client_sock2, &connect_addr); + + /* Enable SO_REUSEPORT option for the two sockets */ + test_enable_reuseport(client_sock1); + test_enable_reuseport(client_sock2); + + /* Bind server socket */ + test_bind_success(server_sock, &server_addr, sizeof(server_addr)); + + /* Start listening on the server socket */ + test_listen(server_sock); + + /* Bind the client sockets */ + test_bind_success(client_sock1, &client_addr, sizeof(client_addr)); + test_bind_success(client_sock2, &client_addr, sizeof(client_addr)); + + /* Connect the first client */ + test_connect_success(client_sock1, &connect_addr, sizeof(connect_addr)); + + /* Accept the first client */ + accept_sock = test_accept(server_sock, &accept_addr, &accept_addr_len); + + /* Connect the second client, should fail */ + test_connect_fail(client_sock2, (struct sockaddr *)&connect_addr, sizeof(connect_addr)); + + close(accept_sock); + close(client_sock1); + close(client_sock2); + close(server_sock); + + test_context_cleanup(); +} + +ZTEST_USER(socket_reuseport_test_suite, test_ipv4_tcp_identical_clients) +{ + test_reuseport_tcp_identical_clients_common(AF_INET, + TEST_IPV4_ANY_ADDR, + TEST_MY_IPV4_ADDR); +} + + +ZTEST_USER(socket_reuseport_test_suite, test_ipv6_tcp_identical_clients) +{ + test_reuseport_tcp_identical_clients_common(AF_INET6, + TEST_IPV6_ANY_ADDR, + TEST_MY_IPV6_ADDR); +} + +ZTEST_SUITE(socket_reuseport_test_suite, NULL, setup, NULL, NULL, NULL); diff --git a/tests/net/socket/reuseaddr/testcase.yaml b/tests/net/socket/reuseaddr_reuseport/testcase.yaml similarity index 100% rename from tests/net/socket/reuseaddr/testcase.yaml rename to tests/net/socket/reuseaddr_reuseport/testcase.yaml