xref: /linux/tools/testing/selftests/net/ipv6_fragmentation.c (revision aeb8d48ea92e6fd50dd4f973fb8778db86fbcc9f)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Author: Brett A C Sheffield <bacs@librecast.net>
4  *
5  * Kernel selftest for the IPv6 fragmentation regression which affected stable
6  * kernels:
7  *
8  *   https://lore.kernel.org/stable/aElivdUXqd1OqgMY@karahi.gladserv.com
9  *
10  * Commit: a18dfa9925b9 ("ipv6: save dontfrag in cork") was backported to stable
11  * without some prerequisite commits.
12  *
13  * This caused a regression when sending IPv6 UDP packets by preventing
14  * fragmentation and instead returning -1 (EMSGSIZE).
15  *
16  * This selftest demonstrates the issue by sending an IPv6 UDP packet to
17  * localhost (::1) on the loopback interface from the autoconfigured link-local
18  * address.
19  *
20  * sendmsg(2) returns bytes sent correctly on a working kernel, and returns -1
21  * (EMSGSIZE) when the regression is present.
22  *
23  * The regression was not present in the mainline kernel, but add this test to
24  * catch similar breakage in future.
25  */
26 
27 #define _GNU_SOURCE
28 
29 #include <error.h>
30 #include <net/if.h>
31 #include <netinet/in.h>
32 #include <sched.h>
33 #include <stdio.h>
34 #include <sys/ioctl.h>
35 #include <sys/socket.h>
36 #include <unistd.h>
37 #include "../kselftest.h"
38 
39 #define MTU 1500
40 #define LARGER_THAN_MTU 8192
41 
42 static void setup(void)
43 {
44 	struct ifreq ifr = {
45 		.ifr_name = "lo"
46 	};
47 	int ctl;
48 
49 	/* we need to set MTU, so do this in a namespace to play nicely */
50 	if (unshare(CLONE_NEWNET) == -1)
51 		error(KSFT_FAIL, errno, "unshare");
52 
53 	ctl = socket(AF_LOCAL, SOCK_STREAM, 0);
54 	if (ctl == -1)
55 		error(KSFT_FAIL, errno, "socket");
56 
57 	/* ensure MTU is smaller than what we plan to send */
58 	ifr.ifr_mtu = MTU;
59 	if (ioctl(ctl, SIOCSIFMTU, &ifr) == -1)
60 		error(KSFT_FAIL, errno, "ioctl: set MTU");
61 
62 	/* bring up interface */
63 	if (ioctl(ctl, SIOCGIFFLAGS, &ifr) == -1)
64 		error(KSFT_FAIL, errno, "ioctl SIOCGIFFLAGS");
65 	ifr.ifr_flags = ifr.ifr_flags | IFF_UP;
66 	if (ioctl(ctl, SIOCSIFFLAGS, &ifr) == -1)
67 		error(KSFT_FAIL, errno, "ioctl: bring interface up");
68 
69 	if (close(ctl) == -1)
70 		error(KSFT_FAIL, errno, "close");
71 }
72 
73 int main(void)
74 {
75 	struct in6_addr addr = {
76 		.s6_addr[15] = 0x01,  /* ::1 */
77 	};
78 	struct sockaddr_in6 sa = {
79 		.sin6_family = AF_INET6,
80 		.sin6_addr = addr,
81 		.sin6_port = htons(9) /* port 9/udp (DISCARD) */
82 	};
83 	static char buf[LARGER_THAN_MTU] = {0};
84 	struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
85 	struct msghdr msg = {
86 		.msg_iov = &iov,
87 		.msg_iovlen = 1,
88 		.msg_name = (struct sockaddr *)&sa,
89 		.msg_namelen = sizeof(sa),
90 	};
91 	ssize_t rc;
92 	int s;
93 
94 	printf("Testing IPv6 fragmentation\n");
95 	setup();
96 	s = socket(AF_INET6, SOCK_DGRAM, 0);
97 send_again:
98 	rc = sendmsg(s, &msg, 0);
99 	if (rc == -1) {
100 		/* if interface wasn't ready, try again */
101 		if (errno == EADDRNOTAVAIL) {
102 			usleep(1000);
103 			goto send_again;
104 		}
105 		error(KSFT_FAIL, errno, "sendmsg");
106 	} else if (rc != LARGER_THAN_MTU) {
107 		error(KSFT_FAIL, errno, "sendmsg returned %zi, expected %i",
108 				rc, LARGER_THAN_MTU);
109 	}
110 	printf("[PASS] sendmsg() returned %zi\n", rc);
111 	if (close(s) == -1)
112 		error(KSFT_FAIL, errno, "close");
113 	return KSFT_PASS;
114 }
115