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;
}
|