1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #define _GNU_SOURCE |
4 | |
5 | #include <stddef.h> |
6 | #include <arpa/inet.h> |
7 | #include <error.h> |
8 | #include <errno.h> |
9 | #include <net/if.h> |
10 | #include <linux/in.h> |
11 | #include <linux/netlink.h> |
12 | #include <linux/rtnetlink.h> |
13 | #include <netinet/if_ether.h> |
14 | #include <netinet/ip.h> |
15 | #include <netinet/ip6.h> |
16 | #include <netinet/udp.h> |
17 | #include <stdbool.h> |
18 | #include <stdlib.h> |
19 | #include <stdio.h> |
20 | #include <string.h> |
21 | #include <sys/ioctl.h> |
22 | #include <sys/socket.h> |
23 | #include <sys/stat.h> |
24 | #include <sys/time.h> |
25 | #include <sys/types.h> |
26 | #include <unistd.h> |
27 | |
28 | #ifndef ETH_MAX_MTU |
29 | #define ETH_MAX_MTU 0xFFFFU |
30 | #endif |
31 | |
32 | #ifndef UDP_SEGMENT |
33 | #define UDP_SEGMENT 103 |
34 | #endif |
35 | |
36 | #ifndef UDP_MAX_SEGMENTS |
37 | #define UDP_MAX_SEGMENTS (1 << 7UL) |
38 | #endif |
39 | |
40 | #define CONST_MTU_TEST 1500 |
41 | |
42 | #define CONST_HDRLEN_V4 (sizeof(struct iphdr) + sizeof(struct udphdr)) |
43 | #define CONST_HDRLEN_V6 (sizeof(struct ip6_hdr) + sizeof(struct udphdr)) |
44 | |
45 | #define CONST_MSS_V4 (CONST_MTU_TEST - CONST_HDRLEN_V4) |
46 | #define CONST_MSS_V6 (CONST_MTU_TEST - CONST_HDRLEN_V6) |
47 | |
48 | #define CONST_MAX_SEGS_V4 (ETH_MAX_MTU / CONST_MSS_V4) |
49 | #define CONST_MAX_SEGS_V6 (ETH_MAX_MTU / CONST_MSS_V6) |
50 | |
51 | static bool cfg_do_ipv4; |
52 | static bool cfg_do_ipv6; |
53 | static bool cfg_do_connected; |
54 | static bool cfg_do_connectionless; |
55 | static bool cfg_do_msgmore; |
56 | static bool cfg_do_setsockopt; |
57 | static int cfg_specific_test_id = -1; |
58 | |
59 | static unsigned short cfg_port = 9000; |
60 | |
61 | static char buf[ETH_MAX_MTU]; |
62 | |
63 | struct testcase { |
64 | int tlen; /* send() buffer size, may exceed mss */ |
65 | bool tfail; /* send() call is expected to fail */ |
66 | int gso_len; /* mss after applying gso */ |
67 | int r_num_mss; /* recv(): number of calls of full mss */ |
68 | int r_len_last; /* recv(): size of last non-mss dgram, if any */ |
69 | }; |
70 | |
71 | const struct in6_addr addr6 = { |
72 | { { 0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, /* fd00::1 */ |
73 | }; |
74 | |
75 | const struct in_addr addr4 = { |
76 | __constant_htonl(0x0a000001), /* 10.0.0.1 */ |
77 | }; |
78 | |
79 | struct testcase testcases_v4[] = { |
80 | { |
81 | /* no GSO: send a single byte */ |
82 | .tlen = 1, |
83 | .r_len_last = 1, |
84 | }, |
85 | { |
86 | /* no GSO: send a single MSS */ |
87 | .tlen = CONST_MSS_V4, |
88 | .r_num_mss = 1, |
89 | }, |
90 | { |
91 | /* no GSO: send a single MSS + 1B: fail */ |
92 | .tlen = CONST_MSS_V4 + 1, |
93 | .tfail = true, |
94 | }, |
95 | { |
96 | /* send a single MSS: will fall back to no GSO */ |
97 | .tlen = CONST_MSS_V4, |
98 | .gso_len = CONST_MSS_V4, |
99 | .r_num_mss = 1, |
100 | }, |
101 | { |
102 | /* send a single MSS + 1B */ |
103 | .tlen = CONST_MSS_V4 + 1, |
104 | .gso_len = CONST_MSS_V4, |
105 | .r_num_mss = 1, |
106 | .r_len_last = 1, |
107 | }, |
108 | { |
109 | /* send exactly 2 MSS */ |
110 | .tlen = CONST_MSS_V4 * 2, |
111 | .gso_len = CONST_MSS_V4, |
112 | .r_num_mss = 2, |
113 | }, |
114 | { |
115 | /* send 2 MSS + 1B */ |
116 | .tlen = (CONST_MSS_V4 * 2) + 1, |
117 | .gso_len = CONST_MSS_V4, |
118 | .r_num_mss = 2, |
119 | .r_len_last = 1, |
120 | }, |
121 | { |
122 | /* send MAX segs */ |
123 | .tlen = (ETH_MAX_MTU / CONST_MSS_V4) * CONST_MSS_V4, |
124 | .gso_len = CONST_MSS_V4, |
125 | .r_num_mss = (ETH_MAX_MTU / CONST_MSS_V4), |
126 | }, |
127 | |
128 | { |
129 | /* send MAX bytes */ |
130 | .tlen = ETH_MAX_MTU - CONST_HDRLEN_V4, |
131 | .gso_len = CONST_MSS_V4, |
132 | .r_num_mss = CONST_MAX_SEGS_V4, |
133 | .r_len_last = ETH_MAX_MTU - CONST_HDRLEN_V4 - |
134 | (CONST_MAX_SEGS_V4 * CONST_MSS_V4), |
135 | }, |
136 | { |
137 | /* send MAX + 1: fail */ |
138 | .tlen = ETH_MAX_MTU - CONST_HDRLEN_V4 + 1, |
139 | .gso_len = CONST_MSS_V4, |
140 | .tfail = true, |
141 | }, |
142 | { |
143 | /* send a single 1B MSS: will fall back to no GSO */ |
144 | .tlen = 1, |
145 | .gso_len = 1, |
146 | .r_num_mss = 1, |
147 | }, |
148 | { |
149 | /* send 2 1B segments */ |
150 | .tlen = 2, |
151 | .gso_len = 1, |
152 | .r_num_mss = 2, |
153 | }, |
154 | { |
155 | /* send 2B + 2B + 1B segments */ |
156 | .tlen = 5, |
157 | .gso_len = 2, |
158 | .r_num_mss = 2, |
159 | .r_len_last = 1, |
160 | }, |
161 | { |
162 | /* send max number of min sized segments */ |
163 | .tlen = UDP_MAX_SEGMENTS, |
164 | .gso_len = 1, |
165 | .r_num_mss = UDP_MAX_SEGMENTS, |
166 | }, |
167 | { |
168 | /* send max number + 1 of min sized segments: fail */ |
169 | .tlen = UDP_MAX_SEGMENTS + 1, |
170 | .gso_len = 1, |
171 | .tfail = true, |
172 | }, |
173 | { |
174 | /* EOL */ |
175 | } |
176 | }; |
177 | |
178 | #ifndef IP6_MAX_MTU |
179 | #define IP6_MAX_MTU (ETH_MAX_MTU + sizeof(struct ip6_hdr)) |
180 | #endif |
181 | |
182 | struct testcase testcases_v6[] = { |
183 | { |
184 | /* no GSO: send a single byte */ |
185 | .tlen = 1, |
186 | .r_len_last = 1, |
187 | }, |
188 | { |
189 | /* no GSO: send a single MSS */ |
190 | .tlen = CONST_MSS_V6, |
191 | .r_num_mss = 1, |
192 | }, |
193 | { |
194 | /* no GSO: send a single MSS + 1B: fail */ |
195 | .tlen = CONST_MSS_V6 + 1, |
196 | .tfail = true, |
197 | }, |
198 | { |
199 | /* send a single MSS: will fall back to no GSO */ |
200 | .tlen = CONST_MSS_V6, |
201 | .gso_len = CONST_MSS_V6, |
202 | .r_num_mss = 1, |
203 | }, |
204 | { |
205 | /* send a single MSS + 1B */ |
206 | .tlen = CONST_MSS_V6 + 1, |
207 | .gso_len = CONST_MSS_V6, |
208 | .r_num_mss = 1, |
209 | .r_len_last = 1, |
210 | }, |
211 | { |
212 | /* send exactly 2 MSS */ |
213 | .tlen = CONST_MSS_V6 * 2, |
214 | .gso_len = CONST_MSS_V6, |
215 | .r_num_mss = 2, |
216 | }, |
217 | { |
218 | /* send 2 MSS + 1B */ |
219 | .tlen = (CONST_MSS_V6 * 2) + 1, |
220 | .gso_len = CONST_MSS_V6, |
221 | .r_num_mss = 2, |
222 | .r_len_last = 1, |
223 | }, |
224 | { |
225 | /* send MAX segs */ |
226 | .tlen = (IP6_MAX_MTU / CONST_MSS_V6) * CONST_MSS_V6, |
227 | .gso_len = CONST_MSS_V6, |
228 | .r_num_mss = (IP6_MAX_MTU / CONST_MSS_V6), |
229 | }, |
230 | |
231 | { |
232 | /* send MAX bytes */ |
233 | .tlen = IP6_MAX_MTU - CONST_HDRLEN_V6, |
234 | .gso_len = CONST_MSS_V6, |
235 | .r_num_mss = CONST_MAX_SEGS_V6, |
236 | .r_len_last = IP6_MAX_MTU - CONST_HDRLEN_V6 - |
237 | (CONST_MAX_SEGS_V6 * CONST_MSS_V6), |
238 | }, |
239 | { |
240 | /* send MAX + 1: fail */ |
241 | .tlen = IP6_MAX_MTU - CONST_HDRLEN_V6 + 1, |
242 | .gso_len = CONST_MSS_V6, |
243 | .tfail = true, |
244 | }, |
245 | { |
246 | /* send a single 1B MSS: will fall back to no GSO */ |
247 | .tlen = 1, |
248 | .gso_len = 1, |
249 | .r_num_mss = 1, |
250 | }, |
251 | { |
252 | /* send 2 1B segments */ |
253 | .tlen = 2, |
254 | .gso_len = 1, |
255 | .r_num_mss = 2, |
256 | }, |
257 | { |
258 | /* send 2B + 2B + 1B segments */ |
259 | .tlen = 5, |
260 | .gso_len = 2, |
261 | .r_num_mss = 2, |
262 | .r_len_last = 1, |
263 | }, |
264 | { |
265 | /* send max number of min sized segments */ |
266 | .tlen = UDP_MAX_SEGMENTS, |
267 | .gso_len = 1, |
268 | .r_num_mss = UDP_MAX_SEGMENTS, |
269 | }, |
270 | { |
271 | /* send max number + 1 of min sized segments: fail */ |
272 | .tlen = UDP_MAX_SEGMENTS + 1, |
273 | .gso_len = 1, |
274 | .tfail = true, |
275 | }, |
276 | { |
277 | /* EOL */ |
278 | } |
279 | }; |
280 | |
281 | static void set_pmtu_discover(int fd, bool is_ipv4) |
282 | { |
283 | int level, name, val; |
284 | |
285 | if (is_ipv4) { |
286 | level = SOL_IP; |
287 | name = IP_MTU_DISCOVER; |
288 | val = IP_PMTUDISC_DO; |
289 | } else { |
290 | level = SOL_IPV6; |
291 | name = IPV6_MTU_DISCOVER; |
292 | val = IPV6_PMTUDISC_DO; |
293 | } |
294 | |
295 | if (setsockopt(fd, level, name, &val, sizeof(val))) |
296 | error(1, errno, "setsockopt path mtu" ); |
297 | } |
298 | |
299 | static unsigned int get_path_mtu(int fd, bool is_ipv4) |
300 | { |
301 | socklen_t vallen; |
302 | unsigned int mtu; |
303 | int ret; |
304 | |
305 | vallen = sizeof(mtu); |
306 | if (is_ipv4) |
307 | ret = getsockopt(fd, SOL_IP, IP_MTU, &mtu, &vallen); |
308 | else |
309 | ret = getsockopt(fd, SOL_IPV6, IPV6_MTU, &mtu, &vallen); |
310 | |
311 | if (ret) |
312 | error(1, errno, "getsockopt mtu" ); |
313 | |
314 | |
315 | fprintf(stderr, "path mtu (read): %u\n" , mtu); |
316 | return mtu; |
317 | } |
318 | |
319 | static bool __send_one(int fd, struct msghdr *msg, int flags) |
320 | { |
321 | int ret; |
322 | |
323 | ret = sendmsg(fd, msg, flags); |
324 | if (ret == -1 && |
325 | (errno == EMSGSIZE || errno == ENOMEM || errno == EINVAL)) |
326 | return false; |
327 | if (ret == -1) |
328 | error(1, errno, "sendmsg" ); |
329 | if (ret != msg->msg_iov->iov_len) |
330 | error(1, 0, "sendto: %d != %llu" , ret, |
331 | (unsigned long long)msg->msg_iov->iov_len); |
332 | if (msg->msg_flags) |
333 | error(1, 0, "sendmsg: return flags 0x%x\n" , msg->msg_flags); |
334 | |
335 | return true; |
336 | } |
337 | |
338 | static bool send_one(int fd, int len, int gso_len, |
339 | struct sockaddr *addr, socklen_t alen) |
340 | { |
341 | char control[CMSG_SPACE(sizeof(uint16_t))] = {0}; |
342 | struct msghdr msg = {0}; |
343 | struct iovec iov = {0}; |
344 | struct cmsghdr *cm; |
345 | |
346 | iov.iov_base = buf; |
347 | iov.iov_len = len; |
348 | |
349 | msg.msg_iov = &iov; |
350 | msg.msg_iovlen = 1; |
351 | |
352 | msg.msg_name = addr; |
353 | msg.msg_namelen = alen; |
354 | |
355 | if (gso_len && !cfg_do_setsockopt) { |
356 | msg.msg_control = control; |
357 | msg.msg_controllen = sizeof(control); |
358 | |
359 | cm = CMSG_FIRSTHDR(&msg); |
360 | cm->cmsg_level = SOL_UDP; |
361 | cm->cmsg_type = UDP_SEGMENT; |
362 | cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); |
363 | *((uint16_t *) CMSG_DATA(cm)) = gso_len; |
364 | } |
365 | |
366 | /* If MSG_MORE, send 1 byte followed by remainder */ |
367 | if (cfg_do_msgmore && len > 1) { |
368 | iov.iov_len = 1; |
369 | if (!__send_one(fd, msg: &msg, MSG_MORE)) |
370 | error(1, 0, "send 1B failed" ); |
371 | |
372 | iov.iov_base++; |
373 | iov.iov_len = len - 1; |
374 | } |
375 | |
376 | return __send_one(fd, msg: &msg, flags: 0); |
377 | } |
378 | |
379 | static int recv_one(int fd, int flags) |
380 | { |
381 | int ret; |
382 | |
383 | ret = recv(fd, buf, sizeof(buf), flags); |
384 | if (ret == -1 && errno == EAGAIN && (flags & MSG_DONTWAIT)) |
385 | return 0; |
386 | if (ret == -1) |
387 | error(1, errno, "recv" ); |
388 | |
389 | return ret; |
390 | } |
391 | |
392 | static void run_one(struct testcase *test, int fdt, int fdr, |
393 | struct sockaddr *addr, socklen_t alen) |
394 | { |
395 | int i, ret, val, mss; |
396 | bool sent; |
397 | |
398 | fprintf(stderr, "ipv%d tx:%d gso:%d %s\n" , |
399 | addr->sa_family == AF_INET ? 4 : 6, |
400 | test->tlen, test->gso_len, |
401 | test->tfail ? "(fail)" : "" ); |
402 | |
403 | val = test->gso_len; |
404 | if (cfg_do_setsockopt) { |
405 | if (setsockopt(fdt, SOL_UDP, UDP_SEGMENT, &val, sizeof(val))) |
406 | error(1, errno, "setsockopt udp segment" ); |
407 | } |
408 | |
409 | sent = send_one(fd: fdt, len: test->tlen, gso_len: test->gso_len, addr, alen); |
410 | if (sent && test->tfail) |
411 | error(1, 0, "send succeeded while expecting failure" ); |
412 | if (!sent && !test->tfail) |
413 | error(1, 0, "send failed while expecting success" ); |
414 | if (!sent) |
415 | return; |
416 | |
417 | if (test->gso_len) |
418 | mss = test->gso_len; |
419 | else |
420 | mss = addr->sa_family == AF_INET ? CONST_MSS_V4 : CONST_MSS_V6; |
421 | |
422 | |
423 | /* Recv all full MSS datagrams */ |
424 | for (i = 0; i < test->r_num_mss; i++) { |
425 | ret = recv_one(fd: fdr, flags: 0); |
426 | if (ret != mss) |
427 | error(1, 0, "recv.%d: %d != %d" , i, ret, mss); |
428 | } |
429 | |
430 | /* Recv the non-full last datagram, if tlen was not a multiple of mss */ |
431 | if (test->r_len_last) { |
432 | ret = recv_one(fd: fdr, flags: 0); |
433 | if (ret != test->r_len_last) |
434 | error(1, 0, "recv.%d: %d != %d (last)" , |
435 | i, ret, test->r_len_last); |
436 | } |
437 | |
438 | /* Verify received all data */ |
439 | ret = recv_one(fd: fdr, MSG_DONTWAIT); |
440 | if (ret) |
441 | error(1, 0, "recv: unexpected datagram" ); |
442 | } |
443 | |
444 | static void run_all(int fdt, int fdr, struct sockaddr *addr, socklen_t alen) |
445 | { |
446 | struct testcase *tests, *test; |
447 | |
448 | tests = addr->sa_family == AF_INET ? testcases_v4 : testcases_v6; |
449 | |
450 | for (test = tests; test->tlen; test++) { |
451 | /* if a specific test is given, then skip all others */ |
452 | if (cfg_specific_test_id == -1 || |
453 | cfg_specific_test_id == test - tests) |
454 | run_one(test, fdt, fdr, addr, alen); |
455 | } |
456 | } |
457 | |
458 | static void run_test(struct sockaddr *addr, socklen_t alen) |
459 | { |
460 | struct timeval tv = { .tv_usec = 100 * 1000 }; |
461 | int fdr, fdt, val; |
462 | |
463 | fdr = socket(addr->sa_family, SOCK_DGRAM, 0); |
464 | if (fdr == -1) |
465 | error(1, errno, "socket r" ); |
466 | |
467 | if (bind(fdr, addr, alen)) |
468 | error(1, errno, "bind" ); |
469 | |
470 | /* Have tests fail quickly instead of hang */ |
471 | if (setsockopt(fdr, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) |
472 | error(1, errno, "setsockopt rcv timeout" ); |
473 | |
474 | fdt = socket(addr->sa_family, SOCK_DGRAM, 0); |
475 | if (fdt == -1) |
476 | error(1, errno, "socket t" ); |
477 | |
478 | /* Do not fragment these datagrams: only succeed if GSO works */ |
479 | set_pmtu_discover(fd: fdt, is_ipv4: addr->sa_family == AF_INET); |
480 | |
481 | if (cfg_do_connectionless) |
482 | run_all(fdt, fdr, addr, alen); |
483 | |
484 | if (cfg_do_connected) { |
485 | if (connect(fdt, addr, alen)) |
486 | error(1, errno, "connect" ); |
487 | |
488 | val = get_path_mtu(fd: fdt, is_ipv4: addr->sa_family == AF_INET); |
489 | if (val != CONST_MTU_TEST) |
490 | error(1, 0, "bad path mtu %u\n" , val); |
491 | |
492 | run_all(fdt, fdr, addr, 0 /* use connected addr */); |
493 | } |
494 | |
495 | if (close(fdt)) |
496 | error(1, errno, "close t" ); |
497 | if (close(fdr)) |
498 | error(1, errno, "close r" ); |
499 | } |
500 | |
501 | static void run_test_v4(void) |
502 | { |
503 | struct sockaddr_in addr = {0}; |
504 | |
505 | addr.sin_family = AF_INET; |
506 | addr.sin_port = htons(cfg_port); |
507 | addr.sin_addr = addr4; |
508 | |
509 | run_test((void *)&addr, sizeof(addr)); |
510 | } |
511 | |
512 | static void run_test_v6(void) |
513 | { |
514 | struct sockaddr_in6 addr = {0}; |
515 | |
516 | addr.sin6_family = AF_INET6; |
517 | addr.sin6_port = htons(cfg_port); |
518 | addr.sin6_addr = addr6; |
519 | |
520 | run_test((void *)&addr, sizeof(addr)); |
521 | } |
522 | |
523 | static void parse_opts(int argc, char **argv) |
524 | { |
525 | int c; |
526 | |
527 | while ((c = getopt(argc, argv, "46cCmst:" )) != -1) { |
528 | switch (c) { |
529 | case '4': |
530 | cfg_do_ipv4 = true; |
531 | break; |
532 | case '6': |
533 | cfg_do_ipv6 = true; |
534 | break; |
535 | case 'c': |
536 | cfg_do_connected = true; |
537 | break; |
538 | case 'C': |
539 | cfg_do_connectionless = true; |
540 | break; |
541 | case 'm': |
542 | cfg_do_msgmore = true; |
543 | break; |
544 | case 's': |
545 | cfg_do_setsockopt = true; |
546 | break; |
547 | case 't': |
548 | cfg_specific_test_id = strtoul(optarg, NULL, 0); |
549 | break; |
550 | default: |
551 | error(1, 0, "%s: parse error" , argv[0]); |
552 | } |
553 | } |
554 | } |
555 | |
556 | int main(int argc, char **argv) |
557 | { |
558 | parse_opts(argc, argv); |
559 | |
560 | if (cfg_do_ipv4) |
561 | run_test_v4(); |
562 | if (cfg_do_ipv6) |
563 | run_test_v6(); |
564 | |
565 | fprintf(stderr, "OK\n" ); |
566 | return 0; |
567 | } |
568 | |