Skip to content

Commit 6df9614

Browse files
q2venPaolo Abeni
authored andcommitted
selftest: Add test for SO_INCOMING_CPU.
Some highly optimised applications use SO_INCOMING_CPU to make them efficient, but they didn't test if it's working correctly by getsockopt() to avoid slowing down. As a result, no one noticed it had been broken for years, so it's a good time to add a test to catch future regression. The test does 1) Create $(nproc) TCP listeners associated with each CPU. 2) Create 32 child sockets for each listener by calling sched_setaffinity() for each CPU. 3) Check if accept()ed sockets' sk_incoming_cpu matches listener's one. If we see -EAGAIN, SO_INCOMING_CPU is broken. However, we might not see any error even if broken; the kernel could miraculously distribute all SYN to correct listeners. Not to let that happen, we must increase the number of clients and CPUs to some extent, so the test requires $(nproc) >= 2 and creates 64 sockets at least. Test: $ nproc 96 $ ./so_incoming_cpu Before the previous patch: # Starting 12 tests from 5 test cases. # RUN so_incoming_cpu.before_reuseport.test1 ... # so_incoming_cpu.c:191:test1:Expected cpu (5) == i (0) # test1: Test terminated by assertion # FAIL so_incoming_cpu.before_reuseport.test1 not ok 1 so_incoming_cpu.before_reuseport.test1 ... # FAILED: 0 / 12 tests passed. # Totals: pass:0 fail:12 xfail:0 xpass:0 skip:0 error:0 After: # Starting 12 tests from 5 test cases. # RUN so_incoming_cpu.before_reuseport.test1 ... # so_incoming_cpu.c:199:test1:SO_INCOMING_CPU is very likely to be working correctly with 3072 sockets. # OK so_incoming_cpu.before_reuseport.test1 ok 1 so_incoming_cpu.before_reuseport.test1 ... # PASSED: 12 / 12 tests passed. # Totals: pass:12 fail:0 xfail:0 xpass:0 skip:0 error:0 Signed-off-by: Kuniyuki Iwashima <[email protected]> Signed-off-by: Paolo Abeni <[email protected]>
1 parent b261eda commit 6df9614

File tree

3 files changed

+244
-0
lines changed

3 files changed

+244
-0
lines changed

tools/testing/selftests/net/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ rxtimestamp
2525
sk_bind_sendto_listen
2626
sk_connect_zero_addr
2727
socket
28+
so_incoming_cpu
2829
so_netns_cookie
2930
so_txtime
3031
stress_reuseport_listen

tools/testing/selftests/net/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ TEST_GEN_FILES += bind_bhash
7171
TEST_GEN_PROGS += sk_bind_sendto_listen
7272
TEST_GEN_PROGS += sk_connect_zero_addr
7373
TEST_PROGS += test_ingress_egress_chaining.sh
74+
TEST_GEN_PROGS += so_incoming_cpu
7475

7576
TEST_FILES := settings
7677

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright Amazon.com Inc. or its affiliates. */
3+
#define _GNU_SOURCE
4+
#include <sched.h>
5+
6+
#include <netinet/in.h>
7+
#include <sys/socket.h>
8+
#include <sys/sysinfo.h>
9+
10+
#include "../kselftest_harness.h"
11+
12+
#define CLIENT_PER_SERVER 32 /* More sockets, more reliable */
13+
#define NR_SERVER self->nproc
14+
#define NR_CLIENT (CLIENT_PER_SERVER * NR_SERVER)
15+
16+
FIXTURE(so_incoming_cpu)
17+
{
18+
int nproc;
19+
int *servers;
20+
union {
21+
struct sockaddr addr;
22+
struct sockaddr_in in_addr;
23+
};
24+
socklen_t addrlen;
25+
};
26+
27+
enum when_to_set {
28+
BEFORE_REUSEPORT,
29+
BEFORE_LISTEN,
30+
AFTER_LISTEN,
31+
AFTER_ALL_LISTEN,
32+
};
33+
34+
FIXTURE_VARIANT(so_incoming_cpu)
35+
{
36+
int when_to_set;
37+
};
38+
39+
FIXTURE_VARIANT_ADD(so_incoming_cpu, before_reuseport)
40+
{
41+
.when_to_set = BEFORE_REUSEPORT,
42+
};
43+
44+
FIXTURE_VARIANT_ADD(so_incoming_cpu, before_listen)
45+
{
46+
.when_to_set = BEFORE_LISTEN,
47+
};
48+
49+
FIXTURE_VARIANT_ADD(so_incoming_cpu, after_listen)
50+
{
51+
.when_to_set = AFTER_LISTEN,
52+
};
53+
54+
FIXTURE_VARIANT_ADD(so_incoming_cpu, after_all_listen)
55+
{
56+
.when_to_set = AFTER_ALL_LISTEN,
57+
};
58+
59+
FIXTURE_SETUP(so_incoming_cpu)
60+
{
61+
self->nproc = get_nprocs();
62+
ASSERT_LE(2, self->nproc);
63+
64+
self->servers = malloc(sizeof(int) * NR_SERVER);
65+
ASSERT_NE(self->servers, NULL);
66+
67+
self->in_addr.sin_family = AF_INET;
68+
self->in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
69+
self->in_addr.sin_port = htons(0);
70+
self->addrlen = sizeof(struct sockaddr_in);
71+
}
72+
73+
FIXTURE_TEARDOWN(so_incoming_cpu)
74+
{
75+
int i;
76+
77+
for (i = 0; i < NR_SERVER; i++)
78+
close(self->servers[i]);
79+
80+
free(self->servers);
81+
}
82+
83+
void set_so_incoming_cpu(struct __test_metadata *_metadata, int fd, int cpu)
84+
{
85+
int ret;
86+
87+
ret = setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &cpu, sizeof(int));
88+
ASSERT_EQ(ret, 0);
89+
}
90+
91+
int create_server(struct __test_metadata *_metadata,
92+
FIXTURE_DATA(so_incoming_cpu) *self,
93+
const FIXTURE_VARIANT(so_incoming_cpu) *variant,
94+
int cpu)
95+
{
96+
int fd, ret;
97+
98+
fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
99+
ASSERT_NE(fd, -1);
100+
101+
if (variant->when_to_set == BEFORE_REUSEPORT)
102+
set_so_incoming_cpu(_metadata, fd, cpu);
103+
104+
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));
105+
ASSERT_EQ(ret, 0);
106+
107+
ret = bind(fd, &self->addr, self->addrlen);
108+
ASSERT_EQ(ret, 0);
109+
110+
if (variant->when_to_set == BEFORE_LISTEN)
111+
set_so_incoming_cpu(_metadata, fd, cpu);
112+
113+
/* We don't use CLIENT_PER_SERVER here not to block
114+
* this test at connect() if SO_INCOMING_CPU is broken.
115+
*/
116+
ret = listen(fd, NR_CLIENT);
117+
ASSERT_EQ(ret, 0);
118+
119+
if (variant->when_to_set == AFTER_LISTEN)
120+
set_so_incoming_cpu(_metadata, fd, cpu);
121+
122+
return fd;
123+
}
124+
125+
void create_servers(struct __test_metadata *_metadata,
126+
FIXTURE_DATA(so_incoming_cpu) *self,
127+
const FIXTURE_VARIANT(so_incoming_cpu) *variant)
128+
{
129+
int i, ret;
130+
131+
for (i = 0; i < NR_SERVER; i++) {
132+
self->servers[i] = create_server(_metadata, self, variant, i);
133+
134+
if (i == 0) {
135+
ret = getsockname(self->servers[i], &self->addr, &self->addrlen);
136+
ASSERT_EQ(ret, 0);
137+
}
138+
}
139+
140+
if (variant->when_to_set == AFTER_ALL_LISTEN) {
141+
for (i = 0; i < NR_SERVER; i++)
142+
set_so_incoming_cpu(_metadata, self->servers[i], i);
143+
}
144+
}
145+
146+
void create_clients(struct __test_metadata *_metadata,
147+
FIXTURE_DATA(so_incoming_cpu) *self)
148+
{
149+
cpu_set_t cpu_set;
150+
int i, j, fd, ret;
151+
152+
for (i = 0; i < NR_SERVER; i++) {
153+
CPU_ZERO(&cpu_set);
154+
155+
CPU_SET(i, &cpu_set);
156+
ASSERT_EQ(CPU_COUNT(&cpu_set), 1);
157+
ASSERT_NE(CPU_ISSET(i, &cpu_set), 0);
158+
159+
/* Make sure SYN will be processed on the i-th CPU
160+
* and finally distributed to the i-th listener.
161+
*/
162+
sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
163+
ASSERT_EQ(ret, 0);
164+
165+
for (j = 0; j < CLIENT_PER_SERVER; j++) {
166+
fd = socket(AF_INET, SOCK_STREAM, 0);
167+
ASSERT_NE(fd, -1);
168+
169+
ret = connect(fd, &self->addr, self->addrlen);
170+
ASSERT_EQ(ret, 0);
171+
172+
close(fd);
173+
}
174+
}
175+
}
176+
177+
void verify_incoming_cpu(struct __test_metadata *_metadata,
178+
FIXTURE_DATA(so_incoming_cpu) *self)
179+
{
180+
int i, j, fd, cpu, ret, total = 0;
181+
socklen_t len = sizeof(int);
182+
183+
for (i = 0; i < NR_SERVER; i++) {
184+
for (j = 0; j < CLIENT_PER_SERVER; j++) {
185+
/* If we see -EAGAIN here, SO_INCOMING_CPU is broken */
186+
fd = accept(self->servers[i], &self->addr, &self->addrlen);
187+
ASSERT_NE(fd, -1);
188+
189+
ret = getsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &cpu, &len);
190+
ASSERT_EQ(ret, 0);
191+
ASSERT_EQ(cpu, i);
192+
193+
close(fd);
194+
total++;
195+
}
196+
}
197+
198+
ASSERT_EQ(total, NR_CLIENT);
199+
TH_LOG("SO_INCOMING_CPU is very likely to be "
200+
"working correctly with %d sockets.", total);
201+
}
202+
203+
TEST_F(so_incoming_cpu, test1)
204+
{
205+
create_servers(_metadata, self, variant);
206+
create_clients(_metadata, self);
207+
verify_incoming_cpu(_metadata, self);
208+
}
209+
210+
TEST_F(so_incoming_cpu, test2)
211+
{
212+
int server;
213+
214+
create_servers(_metadata, self, variant);
215+
216+
/* No CPU specified */
217+
server = create_server(_metadata, self, variant, -1);
218+
close(server);
219+
220+
create_clients(_metadata, self);
221+
verify_incoming_cpu(_metadata, self);
222+
}
223+
224+
TEST_F(so_incoming_cpu, test3)
225+
{
226+
int server, client;
227+
228+
create_servers(_metadata, self, variant);
229+
230+
/* No CPU specified */
231+
server = create_server(_metadata, self, variant, -1);
232+
233+
create_clients(_metadata, self);
234+
235+
/* Never receive any requests */
236+
client = accept(server, &self->addr, &self->addrlen);
237+
ASSERT_EQ(client, -1);
238+
239+
verify_incoming_cpu(_metadata, self);
240+
}
241+
242+
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)