This is xnu-11215.1.10. See this file in:
/*
* Copyright (c) 2017-2024 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
/*
* TODO: change the setup using two back-to-back feths (once ready) so we can
* actually create some connected flows.
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <spawn.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <uuid/uuid.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <darwintest.h>
#include "skywalk_test_driver.h"
#include "skywalk_test_utils.h"
#include "skywalk_test_common.h"
const char * ifname;
struct in_addr our_ip, our_mask, peer_ip, zero_ip;
struct in6_addr zero_ip6;
struct sktc_nexus_handles handles;
uint16_t our_port;
uint16_t peer_port_1 = 8080;
uint16_t peer_port_2 = 8081;
nexus_port_t nx_port = 2;
int sock;
char sa_buf[128];
char flow_buf[128];
void
skt_listener_test_socket_listen(bool expect_success,
struct in_addr *sip, in_port_t *sport)
{
struct sockaddr_in listener_addr;
int error;
sock = socket(AF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
bzero((char *) &listener_addr, sizeof(listener_addr));
listener_addr.sin_family = AF_INET;
listener_addr.sin_addr = *sip;
listener_addr.sin_port = htons(*sport);
error = bind(sock, (struct sockaddr *)&listener_addr,
sizeof(listener_addr));
if (!expect_success) {
SKTC_ASSERT_ERR(error);
return;
}
SKTC_ASSERT_ERR(!error);
socklen_t addrLen = sizeof(listener_addr);
error = getsockname(sock, (struct sockaddr *)&listener_addr, &addrLen);
SKTC_ASSERT_ERR(!error);
if (*sport == 0) {
*sport = ntohs(listener_addr.sin_port);
}
return;
}
void
skt_listener_cleanup(uuid_t flow_id)
{
struct nx_flow_req nfr;
int error;
memset(&nfr, 0, sizeof(nfr));
uuid_copy(nfr.nfr_flow_uuid, flow_id);
error = __os_nexus_flow_del(handles.controller, handles.fsw_nx_uuid,
&nfr);
SKTC_ASSERT_ERR(!error);
}
int
skt_listener_main(int argc, char *argv[])
{
struct sktu_flow *listener, *connection_1, *connection_2, *no_flow;
ifname = FETH0_NAME;
our_mask = sktc_make_in_addr(IN_CLASSC_NET);
our_ip = sktc_feth0_in_addr();
peer_ip = sktc_feth1_in_addr();
zero_ip.s_addr = htonl(INADDR_ANY);
memset(&zero_ip6, 0, sizeof(zero_ip6));
bzero(&handles, sizeof(handles));
strlcpy(handles.netif_ifname, ifname, sizeof(handles.netif_ifname));
handles.netif_addr = our_ip;
handles.netif_mask = our_mask;
sktc_create_flowswitch_no_address(&handles, -1, -1, -1, -1, 0);
/**********************************************************************/
T_LOG("\nScenario 1. Starting with 2-tuple listener flow\n");
/* SUCCESS: test creating a listener flow (let netns picks a ephemeral port for us)*/
listener = sktu_create_nexus_flow(&handles, AF_INET, &zero_ip, &zero_ip, IPPROTO_TCP, 0, 0);
assert(listener);
our_port = ntohs(listener->nfr.nfr_saddr.sin.sin_port);
/* FAILURE: try add duplicate 2-tuple listener flow (with same lport) */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &zero_ip, &zero_ip, IPPROTO_TCP, our_port, 0);
assert(!no_flow);
/* FAILURE: try add duplicate 3-tuple listener flow (with same lport) */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &zero_ip, IPPROTO_TCP, our_port, 0);
assert(!no_flow);
/* test listening on BSD socket (on same port) */
skt_listener_test_socket_listen(false, &zero_ip, &our_port);
inet_ntop(AF_INET, &zero_ip.s_addr, sa_buf, sizeof(sa_buf));
T_LOG("Rej socket bind tcp %s:%d\n", sa_buf, our_port);
skt_listener_test_socket_listen(false, &our_ip, &our_port);
inet_ntop(AF_INET, &our_ip.s_addr, sa_buf, sizeof(sa_buf));
T_LOG("Rej socket bind tcp %s:%d\n", sa_buf, our_port);
/* test connecting a new flow off the listener flow - 1 */
connection_1 = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, peer_port_1);
assert(connection_1);
/* test connecting a new flow off the listener flow - 2 */
connection_2 = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, peer_port_2);
/* test connecting a duplicate connected flow off the listener flow */
/* test connecting a new flow off the listener flow - 2 */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, peer_port_2);
assert(!no_flow);
/* clean up */
sktu_destroy_nexus_flow(listener);
sktu_destroy_nexus_flow(connection_1);
sktu_destroy_nexus_flow(connection_2);
/**********************************************************************/
T_LOG("\nScenario 2. Starting with 3-tuple listener flow\n");
/* try add 3-tuple listener flow (with specified laddr) */
listener = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &zero_ip, IPPROTO_TCP, 0, 0);
assert(listener);
our_port = ntohs(listener->nfr.nfr_saddr.sin.sin_port);
/* try add duplicate 2-tuple listener flow (with same lport) */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &zero_ip, &zero_ip, IPPROTO_TCP, our_port, 0);
assert(!no_flow);
/* test binding on BSD socket (on same port) */
inet_ntop(AF_INET, &zero_ip.s_addr, sa_buf, sizeof(sa_buf));
T_LOG("Rej socket bind tcp %s:%d\n", sa_buf, our_port);
skt_listener_test_socket_listen(false, &zero_ip, &our_port);
inet_ntop(AF_INET, &our_ip.s_addr, sa_buf, sizeof(sa_buf));
T_LOG("Rej socket bind tcp %s:%d\n", sa_buf, our_port);
skt_listener_test_socket_listen(false, &our_ip, &our_port);
/* test connecting a new flow off the listener flow - 1 */
connection_1 = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, peer_port_1);
assert(connection_1);
/* test connecting a new flow off the listener flow - 2 */
connection_2 = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, peer_port_2);
assert(connection_2);
/* test connecting a duplicate connected flow off the listener flow */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, peer_port_2);
assert(!no_flow);
/* clean up */
sktu_destroy_nexus_flow(listener);
sktu_destroy_nexus_flow(connection_1);
sktu_destroy_nexus_flow(connection_2);
/**********************************************************************/
T_LOG("\nScenario 3. Starting with 5-tuple connected flow\n");
/* test connecting a new connected flow */
connection_1 = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, 0, peer_port_1);
assert(connection_1);
our_port = ntohs(connection_1->nfr.nfr_saddr.sin.sin_port);
/* try add conflicting 2-tuple listener flow (with same lport ) */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &zero_ip, &zero_ip, IPPROTO_TCP, our_port, 0);
assert(!no_flow);
/* try add conflicting 3-tuple listener flow (with same lport) */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &zero_ip, IPPROTO_TCP, our_port, 0);
assert(!no_flow);
/* test listening on BSD socket (on same port) */
inet_ntop(AF_INET, &zero_ip.s_addr, sa_buf, sizeof(sa_buf));
T_LOG("Rej socket bind tcp %s:%d\n", sa_buf, our_port);
skt_listener_test_socket_listen(false, &zero_ip, &our_port);
inet_ntop(AF_INET, &our_ip.s_addr, sa_buf, sizeof(sa_buf));
T_LOG("Rej socket bind tcp %s:%d\n", sa_buf, our_port);
skt_listener_test_socket_listen(false, &our_ip, &our_port);
/* clean up */
sktu_destroy_nexus_flow(connection_1);
/**********************************************************************/
T_LOG("\nScenario 4. v4/v6 Compatibility\n");
/* test creating a v6 listener flow */
listener = sktu_create_nexus_flow(&handles, AF_INET6, &zero_ip6, &zero_ip6, IPPROTO_TCP, 0, 0);
assert(listener);
our_port = ntohs(listener->nfr.nfr_saddr.sin.sin_port);
/* test connecting a new v4 flow off the listener flow - 1 */
connection_1 = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, peer_port_1);
assert(connection_1);
/* clean up */
sktu_destroy_nexus_flow(listener);
sktu_destroy_nexus_flow(connection_1);
/**********************************************************************/
T_LOG("\nScenario 5. Starting with BSD socket\n");
/* test listening on BSD socket */
our_port = 0;
inet_ntop(AF_INET, &our_ip.s_addr, sa_buf, sizeof(sa_buf));
skt_listener_test_socket_listen(true, &our_ip, &our_port);
T_LOG("Add socket bind tcp %s:%d\n", sa_buf, our_port);
/* try add conflicting 2-tuple listener flow (with same lport ) */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &zero_ip, &zero_ip, IPPROTO_TCP, our_port, 0);
assert(!no_flow);
/* try add conflicting 3-tuple listener flow (with same lport) */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &zero_ip, IPPROTO_TCP, our_port, 0);
assert(!no_flow);
/* try add connecting a skywalk flow off the BSD listener flow */
no_flow = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, peer_port_1);
assert(!no_flow);
close(sock);
/**********************************************************************/
sktc_cleanup_flowswitch(&handles);
return 0;
}
int
skt_listener_stress_main(int argc, char *argv[])
{
struct sktu_flow *listener, *flows[511];
int i;
ifname = FETH0_NAME;
our_mask = sktc_make_in_addr(IN_CLASSC_NET);
our_ip = sktc_feth0_in_addr();
peer_ip = sktc_feth1_in_addr();
zero_ip.s_addr = htonl(INADDR_ANY);
bzero(&handles, sizeof(handles));
strlcpy(handles.netif_ifname, ifname, sizeof(handles.netif_ifname));
handles.netif_addr = our_ip;
handles.netif_mask = our_mask;
sktc_create_flowswitch_no_address(&handles, -1, -1, -1, -1, 0);
/**********************************************************************/
T_LOG("\nScenario 1. Starting with 2-tuple listener flow\n");
/* test creating a listener flow (let netns picks a ephemeral port for us)*/
listener = sktu_create_nexus_flow(&handles, AF_INET, &zero_ip, &zero_ip, IPPROTO_TCP, 0, 0);
assert(listener);
/* stress connecting new flows off the listener flow */
uint16_t our_port = ntohs(listener->nfr.nfr_saddr.sin.sin_port);
/* count 511 due to (512 flowadv limit 512) - (1 listener flow) */
for (i = 0; i < 511; i++) {
flows[i] = sktu_create_nexus_flow(&handles, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, 10000 + i);
assert(flows[i]);
}
return 0;
}
int
skt_listen_stress_main(int argc, char *argv[])
{
#define LISTEN_MAX INT16_MAX /* might exhaust socache zone due to late cache purge */
for (int i = 0; i < LISTEN_MAX; i++) {
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
in_port_t last_port = 0;
int listener = socket(AF_INET, SOCK_STREAM, 0);
int error = bind(listener, (struct sockaddr *)&addr, sizeof(addr));
if (error == 0) {
error = listen(listener, 5);
if (error == 0) {
socklen_t size = sizeof(addr);
getsockname(listener, (struct sockaddr *)&addr, &size);
last_port = ntohs(addr.sin_port);
} else {
T_LOG("listen failed (last port %d)\n", last_port);
SKTC_ASSERT_ERR(error);
}
} else {
T_LOG("bind failed\n");
SKTC_ASSERT_ERR(error);
}
close(listener);
}
return 0;
#undef LISTEN_MAX
}
int
skt_listener_reuse_main(int argc, char *argv[])
{
ifname = FETH0_NAME;
our_mask = sktc_make_in_addr(IN_CLASSC_NET);
our_ip = sktc_feth0_in_addr();
peer_ip = sktc_feth1_in_addr();
zero_ip = (struct in_addr){.s_addr = htonl(INADDR_ANY)};
bzero(&zero_ip6, sizeof(zero_ip6));
bzero(&handles, sizeof(handles));
strlcpy(handles.netif_ifname, ifname, sizeof(handles.netif_ifname));
handles.netif_addr = our_ip;
handles.netif_mask = our_mask;
sktc_create_flowswitch_no_address(&handles, -1, -1, -1, -1, 0);
struct sktu_flow *listener, *connection, *dup_listener;
// 2 tuple TCP listener
listener = _sktu_create_nexus_flow(&handles, NEXUS_PORT_ANY, AF_INET6, &zero_ip6, &zero_ip6, IPPROTO_TCP, 0, 0, NXFLOWREQF_REUSEPORT);
assert(listener);
our_port = ntohs(listener->nfr.nfr_saddr.sin.sin_port);
// dup listen should fail
dup_listener = _sktu_create_nexus_flow(&handles, NEXUS_PORT_ANY, AF_INET6, &zero_ip6, &zero_ip6, IPPROTO_TCP, our_port, 0, NXFLOWREQF_REUSEPORT);
assert(!dup_listener);
// 5 tuple TCP connection from listener
connection = _sktu_create_nexus_flow(&handles, NEXUS_PORT_ANY, AF_INET, &our_ip, &peer_ip, IPPROTO_TCP, our_port, our_port, NXFLOWREQF_REUSEPORT);
assert(connection);
// kill listener
sktu_destroy_nexus_flow(listener);
// sock listener should fail(skywalk listener port could only be reused by skywalk)
skt_listener_test_socket_listen(false, &zero_ip, &our_port);
T_LOG("socket listener failed as expected\n");
// restart listener
listener = sktu_create_nexus_flow(&handles, AF_INET6, &zero_ip6, &zero_ip6, IPPROTO_TCP, our_port, 0);
assert(listener);
sktu_destroy_nexus_flow(listener);
sktu_destroy_nexus_flow(connection);
sktc_cleanup_flowswitch(&handles);
return 0;
}
void
skt_listener_net_init(void)
{
sktc_ifnet_feth_pair_create(FETH_FLAGS_TXSTART);
}
void
skt_listener_net_fini(void)
{
sktc_ifnet_feth_pair_destroy();
}
struct skywalk_test skt_listener = {
"listener", "test skywalk listener flow creation check",
SK_FEATURE_SKYWALK | SK_FEATURE_NEXUS_FLOWSWITCH | SK_FEATURE_NETNS,
skt_listener_main, { NULL },
skt_listener_net_init, skt_listener_net_fini,
};
struct skywalk_test skt_listen_stress = {
"listen_stress", "stress posix socket listen",
SK_FEATURE_SKYWALK | SK_FEATURE_NETNS,
skt_listen_stress_main, { NULL },
NULL, NULL,
};
struct skywalk_test skt_listener_stress = {
"listener_stress", "stress skywalk listener flows",
SK_FEATURE_SKYWALK | SK_FEATURE_NEXUS_FLOWSWITCH | SK_FEATURE_NETNS,
skt_listener_stress_main, { NULL },
skt_listener_net_init, skt_listener_net_fini,
};
struct skywalk_test skt_listener_reuse = {
"listener_reuse", "test skywalk listener reuse",
SK_FEATURE_SKYWALK | SK_FEATURE_NEXUS_FLOWSWITCH | SK_FEATURE_NETNS,
skt_listener_reuse_main, { NULL },
skt_listener_net_init, skt_listener_net_fini,
};