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 		ret = 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
243