xref: /linux/tools/testing/selftests/net/udp_splice_checksum.c (revision 1221246d3bd08d8b5228777e1a9b3361ca946e64)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Exercise UDP software checksums over multiple spliced pipe fragments.
4  * All traffic stays on loopback; no privileges or external services are needed.
5  * Exit status: 0 = pass, 1 = failure, 4 = all cases skipped.
6  */
7 #define _GNU_SOURCE
8 
9 #include <arpa/inet.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <poll.h>
13 #include <stdbool.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/socket.h>
18 #include <time.h>
19 #include <unistd.h>
20 
21 #include "kselftest.h"
22 
23 #define TIMEOUT_MS 200
24 
25 struct test_case {
26 	const char *name;
27 	const char *body;
28 	const char *prefix;
29 	size_t fragments[3];
30 	bool ordinary;
31 	bool page_tail;
32 };
33 
34 static const struct test_case cases[] = {
35 	{ "ordinary-control", "abcDEFGH", "", { 3, 5 }, true, false },
36 	{ "uniform-odd-fragments", "BBBBBBBB", "", { 3, 5 }, false, false },
37 	{ "even-fragments", "abcdEFGH", "", { 4, 4 }, false, false },
38 	{ "odd-fragments", "abcDEFGH", "", { 3, 5 }, false, false },
39 	{ "odd-prefix-odd-fragments", "abcDEFGH", "P", { 3, 5 }, false, false },
40 	{ "even-prefix-odd-fragments", "abcDEFGH", "PQ", { 3, 5 }, false, false },
41 	{ "page-tail-odd-fragments", "abcDEFGH", "", { 3, 5 }, false, true },
42 	{ "three-fragments", "abcDEFGhijkl", "", { 3, 4, 5 }, false, false },
43 	{ "odd-prefix-three-fragments",
44 	  "abcDEFGhijkl",
45 	  "P",
46 	  { 3, 4, 5 },
47 	  false,
48 	  false },
49 };
50 
51 static const char *const families[] = { "IPv4", "IPv6", "IPv4-mapped" };
52 
53 static int error(const char *what)
54 {
55 	ksft_perror(what);
56 	return 1;
57 }
58 
59 static bool unavailable(void)
60 {
61 	return errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT ||
62 	       errno == EADDRNOTAVAIL;
63 }
64 
65 static long long now_ms(void)
66 {
67 	struct timespec ts;
68 
69 	if (clock_gettime(CLOCK_MONOTONIC, &ts))
70 		ksft_exit_fail_perror("clock_gettime");
71 	return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
72 }
73 
74 static int receive_one(int fd, const char *expected, size_t length)
75 {
76 	struct pollfd pfd = { .fd = fd, .events = POLLIN };
77 	long long deadline = now_ms() + TIMEOUT_MS;
78 	int remaining, ret;
79 	char received[64];
80 	ssize_t count;
81 
82 	for (;;) {
83 		remaining = deadline - now_ms();
84 		if (remaining < 0)
85 			remaining = 0;
86 		ret = poll(&pfd, 1, remaining);
87 		if (ret < 0) {
88 			if (errno == EINTR)
89 				continue;
90 			return error("poll");
91 		}
92 		if (!ret) {
93 			ksft_print_msg("no datagram within %d ms after send\n",
94 				       TIMEOUT_MS);
95 			return 1;
96 		}
97 		count = recv(fd, received, sizeof(received),
98 			     MSG_DONTWAIT | MSG_TRUNC);
99 		if (count < 0) {
100 			if (errno == EINTR || errno == EAGAIN)
101 				continue;
102 			return error("recv");
103 		}
104 		break;
105 	}
106 	if (count != (ssize_t)length || memcmp(received, expected, length)) {
107 		ksft_print_msg("received %zd bytes; expected %zu matching\n",
108 			       count, length);
109 		return 1;
110 	}
111 	count = recv(fd, received, sizeof(received), MSG_DONTWAIT | MSG_TRUNC);
112 	if (count >= 0) {
113 		ksft_print_msg("unexpected extra datagram (%zd bytes)\n",
114 			       count);
115 		return 1;
116 	}
117 	if (errno != EAGAIN)
118 		return error("extra-datagram check");
119 	return 0;
120 }
121 
122 /* Separate source pipes prevent adjacent small writes merging into one buffer. */
123 static int append_fragment(int dest, const char *data, size_t length)
124 {
125 	int source[2], ret = 1;
126 
127 	if (pipe2(source, O_NONBLOCK | O_CLOEXEC))
128 		return error("source pipe");
129 	if (write(source[1], data, length) != (ssize_t)length) {
130 		error("source write");
131 		goto out;
132 	}
133 	if (splice(source[0], NULL, dest, NULL, length, SPLICE_F_NONBLOCK) !=
134 	    (ssize_t)length) {
135 		error("pipe-to-pipe splice");
136 		goto out;
137 	}
138 	ret = 0;
139 out:
140 	close(source[0]);
141 	close(source[1]);
142 	return ret;
143 }
144 
145 static int pipe_capacity(int fd, unsigned int slots, const char **skip_reason)
146 {
147 	long required = slots * sysconf(_SC_PAGESIZE);
148 	int capacity = fcntl(fd, F_GETPIPE_SZ);
149 
150 	if (capacity < 0)
151 		return error("query pipe capacity");
152 	if (capacity >= required ||
153 	    fcntl(fd, F_SETPIPE_SZ, required) >= required)
154 		return 0;
155 	ksft_print_msg("cannot reserve %u pipe slots: %s\n", slots,
156 		       strerror(errno));
157 	*skip_reason = "required pipe capacity unavailable";
158 	return KSFT_SKIP;
159 }
160 
161 /* Leave three bytes at a page's end, followed by five bytes on the next page. */
162 static int append_page_tail(int dest, const char *body,
163 			    const char **skip_reason)
164 {
165 	long page = sysconf(_SC_PAGESIZE);
166 	int source[2], ret = 1;
167 	char *data;
168 
169 	if (page < 8) {
170 		ksft_print_msg("invalid page size %ld\n", page);
171 		return 1;
172 	}
173 	data = malloc(page + 5);
174 	if (!data)
175 		return error("malloc");
176 	memset(data, 'x', page - 3);
177 	memcpy(data + page - 3, body, 8);
178 	if (pipe2(source, O_NONBLOCK | O_CLOEXEC)) {
179 		error("page-tail source pipe");
180 		goto free_data;
181 	}
182 	ret = pipe_capacity(source[1], 2, skip_reason);
183 	if (ret)
184 		goto out;
185 	ret = 1;
186 	if (write(source[1], data, page + 5) != page + 5) {
187 		error("page-tail write");
188 		goto out;
189 	}
190 	if (read(source[0], data, page - 3) != page - 3) {
191 		error("page-tail discard");
192 		goto out;
193 	}
194 	if (splice(source[0], NULL, dest, NULL, 8, SPLICE_F_NONBLOCK) != 8) {
195 		error("page-tail pipe-to-pipe splice");
196 		goto out;
197 	}
198 	ret = 0;
199 out:
200 	close(source[0]);
201 	close(source[1]);
202 free_data:
203 	free(data);
204 	return ret;
205 }
206 
207 static int run_case(unsigned int family, bool connected,
208 		    const struct test_case *test, const char **skip_reason)
209 {
210 	size_t body_length = strlen(test->body), offset = 0;
211 	int tx_family = family == 0 ? AF_INET : AF_INET6;
212 	int rx_family = family == 1 ? AF_INET6 : AF_INET;
213 	int rx = -1, tx = -1, pipefd[2] = { -1, -1 };
214 	size_t prefix_length = strlen(test->prefix);
215 	struct sockaddr_storage address = { 0 };
216 	socklen_t address_length;
217 	struct sockaddr_in6 *v6;
218 	struct sockaddr_in *v4;
219 	int ret = 1, zero = 0;
220 	char expected[64];
221 	unsigned int i;
222 	ssize_t count;
223 
224 	v4 = (struct sockaddr_in *)&address;
225 	v6 = (struct sockaddr_in6 *)&address;
226 	*skip_reason = "address family unavailable";
227 	memcpy(expected, test->prefix, prefix_length);
228 	memcpy(expected + prefix_length, test->body, body_length);
229 	rx = socket(rx_family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
230 	if (rx < 0) {
231 		ret = unavailable() ? KSFT_SKIP : error("receive socket");
232 		goto out;
233 	}
234 	if (rx_family == AF_INET) {
235 		v4->sin_family = AF_INET;
236 		v4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
237 		address_length = sizeof(*v4);
238 	} else {
239 		v6->sin6_family = AF_INET6;
240 		v6->sin6_addr = in6addr_loopback;
241 		address_length = sizeof(*v6);
242 	}
243 	if (bind(rx, (struct sockaddr *)&address, address_length)) {
244 		ret = unavailable() ? KSFT_SKIP : error("bind loopback");
245 		goto out;
246 	}
247 	if (getsockname(rx, (struct sockaddr *)&address, &address_length)) {
248 		error("getsockname");
249 		goto out;
250 	}
251 	if (family == 2) {
252 		unsigned short port = v4->sin_port;
253 
254 		memset(&address, 0, sizeof(address));
255 		v6->sin6_family = AF_INET6;
256 		v6->sin6_port = port;
257 		inet_pton(AF_INET6, "::ffff:127.0.0.1", &v6->sin6_addr);
258 		address_length = sizeof(*v6);
259 	}
260 	tx = socket(tx_family, SOCK_DGRAM | SOCK_CLOEXEC, 0);
261 	if (tx < 0) {
262 		ret = unavailable() ? KSFT_SKIP : error("send socket");
263 		goto out;
264 	}
265 	if (family == 2 &&
266 	    setsockopt(tx, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero))) {
267 		error("disable IPV6_V6ONLY");
268 		goto out;
269 	}
270 	if (connected &&
271 	    connect(tx, (struct sockaddr *)&address, address_length)) {
272 		error("connect");
273 		goto out;
274 	}
275 	if (test->ordinary) {
276 		count = sendto(tx, expected, prefix_length + body_length, 0,
277 			       (struct sockaddr *)&address, address_length);
278 		if (count != (ssize_t)(prefix_length + body_length)) {
279 			error("ordinary sendto");
280 			goto out;
281 		}
282 	} else {
283 		if (pipe2(pipefd, O_NONBLOCK | O_CLOEXEC)) {
284 			error("assembled pipe");
285 			goto out;
286 		}
287 		ret = pipe_capacity(pipefd[1], test->fragments[2] ? 3 : 2,
288 				    skip_reason);
289 		if (ret)
290 			goto out;
291 		ret = 1;
292 		if (test->page_tail) {
293 			ret = append_page_tail(pipefd[1], test->body,
294 					       skip_reason);
295 			if (ret)
296 				goto out;
297 			ret = 1;
298 		} else {
299 			for (i = 0; i < ARRAY_SIZE(test->fragments); i++) {
300 				if (!test->fragments[i])
301 					break;
302 				if (append_fragment(pipefd[1],
303 						    test->body + offset,
304 						    test->fragments[i]))
305 					goto out;
306 				offset += test->fragments[i];
307 			}
308 		}
309 		/* Even an empty prefix establishes the destination and UDP cork. */
310 		count = sendto(tx, test->prefix, prefix_length, MSG_MORE,
311 			       (struct sockaddr *)&address, address_length);
312 		if (count != (ssize_t)prefix_length) {
313 			error("prime sendto(MSG_MORE)");
314 			goto out;
315 		}
316 		count = splice(pipefd[0], NULL, tx, NULL, body_length,
317 			       SPLICE_F_NONBLOCK | SPLICE_F_MORE);
318 		if (count != (ssize_t)body_length) {
319 			ksft_print_msg("socket splice: %zd, expected %zu: %s\n",
320 				       count, body_length,
321 				       count < 0 ? strerror(errno) :
322 						   "short transfer");
323 			goto out;
324 		}
325 		if (sendto(tx, "", 0, 0, (struct sockaddr *)&address,
326 			   address_length)) {
327 			error("commit sendto");
328 			goto out;
329 		}
330 	}
331 	ret = receive_one(rx, expected, prefix_length + body_length);
332 out:
333 	if (pipefd[0] >= 0) {
334 		close(pipefd[0]);
335 		close(pipefd[1]);
336 	}
337 	if (tx >= 0)
338 		close(tx);
339 	if (rx >= 0)
340 		close(rx);
341 	return ret;
342 }
343 
344 int main(void)
345 {
346 	unsigned int family, connected, i;
347 	const char *connection, *skip;
348 	int ret;
349 
350 	ksft_print_header();
351 	ksft_set_plan(ARRAY_SIZE(families) * 2 * ARRAY_SIZE(cases));
352 	for (family = 0; family < ARRAY_SIZE(families); family++) {
353 		for (connected = 0; connected < 2; connected++) {
354 			connection = connected ? "connected" : "unconnected";
355 			for (i = 0; i < ARRAY_SIZE(cases); i++) {
356 				ret = run_case(family, connected, &cases[i],
357 					       &skip);
358 				if (ret == KSFT_SKIP)
359 					ksft_test_result_skip("%s %s %s: %s\n",
360 							      families[family],
361 							      connection,
362 							      cases[i].name,
363 							      skip);
364 				else
365 					ksft_test_result(!ret, "%s %s %s\n",
366 							 families[family],
367 							 connection,
368 							 cases[i].name);
369 			}
370 		}
371 	}
372 	if (!ksft_get_pass_cnt() && !ksft_get_fail_cnt()) {
373 		/* ksft_exit_skip() would add a result beyond the plan. */
374 		ksft_print_cnts();
375 		return KSFT_SKIP;
376 	}
377 	ksft_finished();
378 }
379