xref: /linux/tools/testing/selftests/net/so_incoming_cpu.c (revision 5bb36cd12006500fe6b96c17c2fbe870ae89eb96)
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 <fcntl.h>
7 
8 #include <netinet/in.h>
9 #include <sys/socket.h>
10 #include <sys/sysinfo.h>
11 
12 #include "kselftest_harness.h"
13 
14 FIXTURE(so_incoming_cpu)
15 {
16 	int *servers;
17 	union {
18 		struct sockaddr addr;
19 		struct sockaddr_in in_addr;
20 	};
21 	socklen_t addrlen;
22 };
23 
24 enum when_to_set {
25 	BEFORE_REUSEPORT,
26 	BEFORE_LISTEN,
27 	AFTER_LISTEN,
28 	AFTER_ALL_LISTEN,
29 };
30 
31 FIXTURE_VARIANT(so_incoming_cpu)
32 {
33 	int when_to_set;
34 };
35 
36 FIXTURE_VARIANT_ADD(so_incoming_cpu, before_reuseport)
37 {
38 	.when_to_set = BEFORE_REUSEPORT,
39 };
40 
41 FIXTURE_VARIANT_ADD(so_incoming_cpu, before_listen)
42 {
43 	.when_to_set = BEFORE_LISTEN,
44 };
45 
46 FIXTURE_VARIANT_ADD(so_incoming_cpu, after_listen)
47 {
48 	.when_to_set = AFTER_LISTEN,
49 };
50 
51 FIXTURE_VARIANT_ADD(so_incoming_cpu, after_all_listen)
52 {
53 	.when_to_set = AFTER_ALL_LISTEN,
54 };
55 
56 static void write_sysctl(struct __test_metadata *_metadata,
57 			 char *filename, char *string)
58 {
59 	int fd, len, ret;
60 
61 	fd = open(filename, O_WRONLY);
62 	ASSERT_NE(fd, -1);
63 
64 	len = strlen(string);
65 	ret = write(fd, string, len);
66 	ASSERT_EQ(ret, len);
67 }
68 
69 static void setup_netns(struct __test_metadata *_metadata)
70 {
71 	ASSERT_EQ(unshare(CLONE_NEWNET), 0);
72 	ASSERT_EQ(system("ip link set lo up"), 0);
73 
74 	write_sysctl(_metadata, "/proc/sys/net/ipv4/ip_local_port_range", "10000 60001");
75 	write_sysctl(_metadata, "/proc/sys/net/ipv4/tcp_tw_reuse", "0");
76 }
77 
78 #define NR_PORT				(60001 - 10000 - 1)
79 #define NR_CLIENT_PER_SERVER_DEFAULT	32
80 static int nr_client_per_server, nr_server, nr_client;
81 
82 FIXTURE_SETUP(so_incoming_cpu)
83 {
84 	nr_server = get_nprocs();
85 	if (nr_server < 2)
86 		SKIP(return, "requires at least two CPUs");
87 
88 	setup_netns(_metadata);
89 
90 	if (NR_CLIENT_PER_SERVER_DEFAULT * nr_server < NR_PORT)
91 		nr_client_per_server = NR_CLIENT_PER_SERVER_DEFAULT;
92 	else
93 		nr_client_per_server = NR_PORT / nr_server;
94 
95 	nr_client = nr_client_per_server * nr_server;
96 
97 	self->servers = malloc(sizeof(int) * nr_server);
98 	ASSERT_NE(self->servers, NULL);
99 
100 	self->in_addr.sin_family = AF_INET;
101 	self->in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
102 	self->in_addr.sin_port = htons(0);
103 	self->addrlen = sizeof(struct sockaddr_in);
104 }
105 
106 FIXTURE_TEARDOWN(so_incoming_cpu)
107 {
108 	int i;
109 
110 	for (i = 0; i < nr_server; i++)
111 		close(self->servers[i]);
112 
113 	free(self->servers);
114 }
115 
116 void set_so_incoming_cpu(struct __test_metadata *_metadata, int fd, int cpu)
117 {
118 	int ret;
119 
120 	ret = setsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &cpu, sizeof(int));
121 	ASSERT_EQ(ret, 0);
122 }
123 
124 int create_server(struct __test_metadata *_metadata,
125 		  FIXTURE_DATA(so_incoming_cpu) *self,
126 		  const FIXTURE_VARIANT(so_incoming_cpu) *variant,
127 		  int cpu)
128 {
129 	int fd, ret;
130 
131 	fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
132 	ASSERT_NE(fd, -1);
133 
134 	if (variant->when_to_set == BEFORE_REUSEPORT)
135 		set_so_incoming_cpu(_metadata, fd, cpu);
136 
137 	ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));
138 	ASSERT_EQ(ret, 0);
139 
140 	ret = bind(fd, &self->addr, self->addrlen);
141 	ASSERT_EQ(ret, 0);
142 
143 	if (variant->when_to_set == BEFORE_LISTEN)
144 		set_so_incoming_cpu(_metadata, fd, cpu);
145 
146 	/* We don't use nr_client_per_server here not to block
147 	 * this test at connect() if SO_INCOMING_CPU is broken.
148 	 */
149 	ret = listen(fd, nr_client);
150 	ASSERT_EQ(ret, 0);
151 
152 	if (variant->when_to_set == AFTER_LISTEN)
153 		set_so_incoming_cpu(_metadata, fd, cpu);
154 
155 	return fd;
156 }
157 
158 void create_servers(struct __test_metadata *_metadata,
159 		    FIXTURE_DATA(so_incoming_cpu) *self,
160 		    const FIXTURE_VARIANT(so_incoming_cpu) *variant)
161 {
162 	int i, ret;
163 
164 	for (i = 0; i < nr_server; i++) {
165 		self->servers[i] = create_server(_metadata, self, variant, i);
166 
167 		if (i == 0) {
168 			ret = getsockname(self->servers[i], &self->addr, &self->addrlen);
169 			ASSERT_EQ(ret, 0);
170 		}
171 	}
172 
173 	if (variant->when_to_set == AFTER_ALL_LISTEN) {
174 		for (i = 0; i < nr_server; i++)
175 			set_so_incoming_cpu(_metadata, self->servers[i], i);
176 	}
177 }
178 
179 void create_clients(struct __test_metadata *_metadata,
180 		    FIXTURE_DATA(so_incoming_cpu) *self)
181 {
182 	cpu_set_t cpu_set;
183 	int i, j, fd, ret;
184 
185 	for (i = 0; i < nr_server; i++) {
186 		CPU_ZERO(&cpu_set);
187 
188 		CPU_SET(i, &cpu_set);
189 		ASSERT_EQ(CPU_COUNT(&cpu_set), 1);
190 		ASSERT_NE(CPU_ISSET(i, &cpu_set), 0);
191 
192 		/* Make sure SYN will be processed on the i-th CPU
193 		 * and finally distributed to the i-th listener.
194 		 */
195 		ret = sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
196 		ASSERT_EQ(ret, 0);
197 
198 		for (j = 0; j < nr_client_per_server; j++) {
199 			fd  = socket(AF_INET, SOCK_STREAM, 0);
200 			ASSERT_NE(fd, -1);
201 
202 			ret = connect(fd, &self->addr, self->addrlen);
203 			ASSERT_EQ(ret, 0);
204 
205 			close(fd);
206 		}
207 	}
208 }
209 
210 void verify_incoming_cpu(struct __test_metadata *_metadata,
211 			 FIXTURE_DATA(so_incoming_cpu) *self)
212 {
213 	int i, j, fd, cpu, ret, total = 0;
214 	socklen_t len = sizeof(int);
215 
216 	for (i = 0; i < nr_server; i++) {
217 		for (j = 0; j < nr_client_per_server; j++) {
218 			/* If we see -EAGAIN here, SO_INCOMING_CPU is broken */
219 			fd = accept(self->servers[i], &self->addr, &self->addrlen);
220 			ASSERT_NE(fd, -1);
221 
222 			ret = getsockopt(fd, SOL_SOCKET, SO_INCOMING_CPU, &cpu, &len);
223 			ASSERT_EQ(ret, 0);
224 			ASSERT_EQ(cpu, i);
225 
226 			close(fd);
227 			total++;
228 		}
229 	}
230 
231 	ASSERT_EQ(total, nr_client);
232 	TH_LOG("SO_INCOMING_CPU is very likely to be "
233 	       "working correctly with %d sockets.", total);
234 }
235 
236 TEST_F(so_incoming_cpu, test1)
237 {
238 	create_servers(_metadata, self, variant);
239 	create_clients(_metadata, self);
240 	verify_incoming_cpu(_metadata, self);
241 }
242 
243 TEST_F(so_incoming_cpu, test2)
244 {
245 	int server;
246 
247 	create_servers(_metadata, self, variant);
248 
249 	/* No CPU specified */
250 	server = create_server(_metadata, self, variant, -1);
251 	close(server);
252 
253 	create_clients(_metadata, self);
254 	verify_incoming_cpu(_metadata, self);
255 }
256 
257 TEST_F(so_incoming_cpu, test3)
258 {
259 	int server, client;
260 
261 	create_servers(_metadata, self, variant);
262 
263 	/* No CPU specified */
264 	server = create_server(_metadata, self, variant, -1);
265 
266 	create_clients(_metadata, self);
267 
268 	/* Never receive any requests */
269 	client = accept(server, &self->addr, &self->addrlen);
270 	ASSERT_EQ(client, -1);
271 
272 	verify_incoming_cpu(_metadata, self);
273 }
274 
275 TEST_HARNESS_MAIN
276