summaryrefslogtreecommitdiffstats
path: root/tools/testing/selftests/net/sk_so_peek_off.c
blob: d87dd8d8d4912e1c757688831f04e5af71351c74 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// SPDX-License-Identifier: GPL-2.0

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "../kselftest.h"

static char *afstr(int af, int proto)
{
	if (proto == IPPROTO_TCP)
		return af == AF_INET ? "TCP/IPv4" : "TCP/IPv6";
	else
		return af == AF_INET ? "UDP/IPv4" : "UDP/IPv6";
}

int sk_peek_offset_probe(sa_family_t af, int proto)
{
	int type = (proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM);
	int optv = 0;
	int ret = 0;
	int s;

	s = socket(af, type, proto);
	if (s < 0) {
		ksft_perror("Temporary TCP socket creation failed");
	} else {
		if (!setsockopt(s, SOL_SOCKET, SO_PEEK_OFF, &optv, sizeof(int)))
			ret = 1;
		else
			printf("%s does not support SO_PEEK_OFF\n", afstr(af, proto));
		close(s);
	}
	return ret;
}

static void sk_peek_offset_set(int s, int offset)
{
	if (setsockopt(s, SOL_SOCKET, SO_PEEK_OFF, &offset, sizeof(offset)))
		ksft_perror("Failed to set SO_PEEK_OFF value\n");
}

static int sk_peek_offset_get(int s)
{
	int offset;
	socklen_t len = sizeof(offset);

	if (getsockopt(s, SOL_SOCKET, SO_PEEK_OFF, &offset, &len))
		ksft_perror("Failed to get SO_PEEK_OFF value\n");
	return offset;
}

static int sk_peek_offset_test(sa_family_t af, int proto)
{
	int type = (proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM);
	union {
		struct sockaddr sa;
		struct sockaddr_in a4;
		struct sockaddr_in6 a6;
	} a;
	int res = 0;
	int s[2] = {0, 0};
	int recv_sock = 0;
	int offset = 0;
	ssize_t len;
	char buf[2];

	memset(&a, 0, sizeof(a));
	a.sa.sa_family = af;

	s[0] = recv_sock = socket(af, type, proto);
	s[1] = socket(af, type, proto);

	if (s[0] < 0 || s[1] < 0) {
		ksft_perror("Temporary socket creation failed\n");
		goto out;
	}
	if (bind(s[0], &a.sa, sizeof(a)) < 0) {
		ksft_perror("Temporary socket bind() failed\n");
		goto out;
	}
	if (getsockname(s[0], &a.sa, &((socklen_t) { sizeof(a) })) < 0) {
		ksft_perror("Temporary socket getsockname() failed\n");
		goto out;
	}
	if (proto == IPPROTO_TCP && listen(s[0], 0) < 0) {
		ksft_perror("Temporary socket listen() failed\n");
		goto out;
	}
	if (connect(s[1], &a.sa, sizeof(a)) < 0) {
		ksft_perror("Temporary socket connect() failed\n");
		goto out;
	}
	if (proto == IPPROTO_TCP) {
		recv_sock = accept(s[0], NULL, NULL);
		if (recv_sock <= 0) {
			ksft_perror("Temporary socket accept() failed\n");
			goto out;
		}
	}

	/* Some basic tests of getting/setting offset */
	offset = sk_peek_offset_get(recv_sock);
	if (offset != -1) {
		ksft_perror("Initial value of socket offset not -1\n");
		goto out;
	}
	sk_peek_offset_set(recv_sock, 0);
	offset = sk_peek_offset_get(recv_sock);
	if (offset != 0) {
		ksft_perror("Failed to set socket offset to 0\n");
		goto out;
	}

	/* Transfer a message */
	if (send(s[1], (char *)("ab"), 2, 0) != 2) {
		ksft_perror("Temporary probe socket send() failed\n");
		goto out;
	}
	/* Read first byte */
	len = recv(recv_sock, buf, 1, MSG_PEEK);
	if (len != 1 || buf[0] != 'a') {
		ksft_perror("Failed to read first byte of message\n");
		goto out;
	}
	offset = sk_peek_offset_get(recv_sock);
	if (offset != 1) {
		ksft_perror("Offset not forwarded correctly at first byte\n");
		goto out;
	}
	/* Try to read beyond last byte */
	len = recv(recv_sock, buf, 2, MSG_PEEK);
	if (len != 1 || buf[0] != 'b') {
		ksft_perror("Failed to read last byte of message\n");
		goto out;
	}
	offset = sk_peek_offset_get(recv_sock);
	if (offset != 2) {
		ksft_perror("Offset not forwarded correctly at last byte\n");
		goto out;
	}
	/* Flush message */
	len = recv(recv_sock, buf, 2, MSG_TRUNC);
	if (len != 2) {
		ksft_perror("Failed to flush message\n");
		goto out;
	}
	offset = sk_peek_offset_get(recv_sock);
	if (offset != 0) {
		ksft_perror("Offset not reverted correctly after flush\n");
		goto out;
	}

	printf("%s with MSG_PEEK_OFF works correctly\n", afstr(af, proto));
	res = 1;
out:
	if (proto == IPPROTO_TCP && recv_sock >= 0)
		close(recv_sock);
	if (s[1] >= 0)
		close(s[1]);
	if (s[0] >= 0)
		close(s[0]);
	return res;
}

static int do_test(int proto)
{
	int res4, res6;

	res4 = sk_peek_offset_probe(AF_INET, proto);
	res6 = sk_peek_offset_probe(AF_INET6, proto);

	if (!res4 && !res6)
		return KSFT_SKIP;

	if (res4)
		res4 = sk_peek_offset_test(AF_INET, proto);

	if (res6)
		res6 = sk_peek_offset_test(AF_INET6, proto);

	if (!res4 || !res6)
		return KSFT_FAIL;

	return KSFT_PASS;
}

int main(void)
{
	int restcp, resudp;

	restcp = do_test(IPPROTO_TCP);
	resudp = do_test(IPPROTO_UDP);
	if (restcp == KSFT_FAIL || resudp == KSFT_FAIL)
		return KSFT_FAIL;

	return KSFT_PASS;
}