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
error(const char * what)53 static int error(const char *what)
54 {
55 ksft_perror(what);
56 return 1;
57 }
58
unavailable(void)59 static bool unavailable(void)
60 {
61 return errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT ||
62 errno == EADDRNOTAVAIL;
63 }
64
now_ms(void)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
receive_one(int fd,const char * expected,size_t length)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. */
append_fragment(int dest,const char * data,size_t length)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
pipe_capacity(int fd,unsigned int slots,const char ** skip_reason)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. */
append_page_tail(int dest,const char * body,const char ** skip_reason)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
run_case(unsigned int family,bool connected,const struct test_case * test,const char ** skip_reason)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
main(void)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