tests: net: sockets: add tests for SO_REUSEPORT

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 <t.frauenschlaeger@me.com>
This commit is contained in:
Tobias Frauenschläger 2023-09-01 11:24:51 +02:00 committed by Carles Cufí
commit 1de4e73e6d
6 changed files with 1015 additions and 474 deletions

View file

@ -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));
}

View file

@ -1,473 +0,0 @@
/*
* Copyright (c) 2019 Linaro Limited
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <zephyr/ztest_assert.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/net_ip.h>
#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);

View file

@ -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"

File diff suppressed because it is too large Load diff