xref: /linux/tools/testing/selftests/net/sk_so_peek_off.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <errno.h>
8 #include <sys/types.h>
9 #include <netinet/in.h>
10 #include <arpa/inet.h>
11 #include "../kselftest.h"
12 
13 static char *afstr(int af, int proto)
14 {
15 	if (proto == IPPROTO_TCP)
16 		return af == AF_INET ? "TCP/IPv4" : "TCP/IPv6";
17 	else
18 		return af == AF_INET ? "UDP/IPv4" : "UDP/IPv6";
19 }
20 
21 int sk_peek_offset_probe(sa_family_t af, int proto)
22 {
23 	int type = (proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM);
24 	int optv = 0;
25 	int ret = 0;
26 	int s;
27 
28 	s = socket(af, type, proto);
29 	if (s < 0) {
30 		ksft_perror("Temporary TCP socket creation failed");
31 	} else {
32 		if (!setsockopt(s, SOL_SOCKET, SO_PEEK_OFF, &optv, sizeof(int)))
33 			ret = 1;
34 		else
35 			printf("%s does not support SO_PEEK_OFF\n", afstr(af, proto));
36 		close(s);
37 	}
38 	return ret;
39 }
40 
41 static void sk_peek_offset_set(int s, int offset)
42 {
43 	if (setsockopt(s, SOL_SOCKET, SO_PEEK_OFF, &offset, sizeof(offset)))
44 		ksft_perror("Failed to set SO_PEEK_OFF value\n");
45 }
46 
47 static int sk_peek_offset_get(int s)
48 {
49 	int offset;
50 	socklen_t len = sizeof(offset);
51 
52 	if (getsockopt(s, SOL_SOCKET, SO_PEEK_OFF, &offset, &len))
53 		ksft_perror("Failed to get SO_PEEK_OFF value\n");
54 	return offset;
55 }
56 
57 static int sk_peek_offset_test(sa_family_t af, int proto)
58 {
59 	int type = (proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM);
60 	union {
61 		struct sockaddr sa;
62 		struct sockaddr_in a4;
63 		struct sockaddr_in6 a6;
64 	} a;
65 	int res = 0;
66 	int s[2] = {0, 0};
67 	int recv_sock = 0;
68 	int offset = 0;
69 	ssize_t len;
70 	char buf[2];
71 
72 	memset(&a, 0, sizeof(a));
73 	a.sa.sa_family = af;
74 
75 	s[0] = recv_sock = socket(af, type, proto);
76 	s[1] = socket(af, type, proto);
77 
78 	if (s[0] < 0 || s[1] < 0) {
79 		ksft_perror("Temporary socket creation failed\n");
80 		goto out;
81 	}
82 	if (bind(s[0], &a.sa, sizeof(a)) < 0) {
83 		ksft_perror("Temporary socket bind() failed\n");
84 		goto out;
85 	}
86 	if (getsockname(s[0], &a.sa, &((socklen_t) { sizeof(a) })) < 0) {
87 		ksft_perror("Temporary socket getsockname() failed\n");
88 		goto out;
89 	}
90 	if (proto == IPPROTO_TCP && listen(s[0], 0) < 0) {
91 		ksft_perror("Temporary socket listen() failed\n");
92 		goto out;
93 	}
94 	if (connect(s[1], &a.sa, sizeof(a)) < 0) {
95 		ksft_perror("Temporary socket connect() failed\n");
96 		goto out;
97 	}
98 	if (proto == IPPROTO_TCP) {
99 		recv_sock = accept(s[0], NULL, NULL);
100 		if (recv_sock <= 0) {
101 			ksft_perror("Temporary socket accept() failed\n");
102 			goto out;
103 		}
104 	}
105 
106 	/* Some basic tests of getting/setting offset */
107 	offset = sk_peek_offset_get(recv_sock);
108 	if (offset != -1) {
109 		ksft_perror("Initial value of socket offset not -1\n");
110 		goto out;
111 	}
112 	sk_peek_offset_set(recv_sock, 0);
113 	offset = sk_peek_offset_get(recv_sock);
114 	if (offset != 0) {
115 		ksft_perror("Failed to set socket offset to 0\n");
116 		goto out;
117 	}
118 
119 	/* Transfer a message */
120 	if (send(s[1], (char *)("ab"), 2, 0) != 2) {
121 		ksft_perror("Temporary probe socket send() failed\n");
122 		goto out;
123 	}
124 	/* Read first byte */
125 	len = recv(recv_sock, buf, 1, MSG_PEEK);
126 	if (len != 1 || buf[0] != 'a') {
127 		ksft_perror("Failed to read first byte of message\n");
128 		goto out;
129 	}
130 	offset = sk_peek_offset_get(recv_sock);
131 	if (offset != 1) {
132 		ksft_perror("Offset not forwarded correctly at first byte\n");
133 		goto out;
134 	}
135 	/* Try to read beyond last byte */
136 	len = recv(recv_sock, buf, 2, MSG_PEEK);
137 	if (len != 1 || buf[0] != 'b') {
138 		ksft_perror("Failed to read last byte of message\n");
139 		goto out;
140 	}
141 	offset = sk_peek_offset_get(recv_sock);
142 	if (offset != 2) {
143 		ksft_perror("Offset not forwarded correctly at last byte\n");
144 		goto out;
145 	}
146 	/* Flush message */
147 	len = recv(recv_sock, buf, 2, MSG_TRUNC);
148 	if (len != 2) {
149 		ksft_perror("Failed to flush message\n");
150 		goto out;
151 	}
152 	offset = sk_peek_offset_get(recv_sock);
153 	if (offset != 0) {
154 		ksft_perror("Offset not reverted correctly after flush\n");
155 		goto out;
156 	}
157 
158 	printf("%s with MSG_PEEK_OFF works correctly\n", afstr(af, proto));
159 	res = 1;
160 out:
161 	if (proto == IPPROTO_TCP && recv_sock >= 0)
162 		close(recv_sock);
163 	if (s[1] >= 0)
164 		close(s[1]);
165 	if (s[0] >= 0)
166 		close(s[0]);
167 	return res;
168 }
169 
170 static int do_test(int proto)
171 {
172 	int res4, res6;
173 
174 	res4 = sk_peek_offset_probe(AF_INET, proto);
175 	res6 = sk_peek_offset_probe(AF_INET6, proto);
176 
177 	if (!res4 && !res6)
178 		return KSFT_SKIP;
179 
180 	if (res4)
181 		res4 = sk_peek_offset_test(AF_INET, proto);
182 
183 	if (res6)
184 		res6 = sk_peek_offset_test(AF_INET6, proto);
185 
186 	if (!res4 || !res6)
187 		return KSFT_FAIL;
188 
189 	return KSFT_PASS;
190 }
191 
192 int main(void)
193 {
194 	int restcp, resudp;
195 
196 	restcp = do_test(IPPROTO_TCP);
197 	resudp = do_test(IPPROTO_UDP);
198 	if (restcp == KSFT_FAIL || resudp == KSFT_FAIL)
199 		return KSFT_FAIL;
200 
201 	return KSFT_PASS;
202 }
203