1
2
3
4#define _GNU_SOURCE
5
6#include <arpa/inet.h>
7#include <string.h>
8
9#include <linux/pkt_cls.h>
10
11#include <test_progs.h>
12
13#include "progs/test_cls_redirect.h"
14#include "test_cls_redirect.skel.h"
15
16#define ENCAP_IP INADDR_LOOPBACK
17#define ENCAP_PORT (1234)
18
19struct addr_port {
20 in_port_t port;
21 union {
22 struct in_addr in_addr;
23 struct in6_addr in6_addr;
24 };
25};
26
27struct tuple {
28 int family;
29 struct addr_port src;
30 struct addr_port dst;
31};
32
33static int start_server(const struct sockaddr *addr, socklen_t len, int type)
34{
35 int fd = socket(addr->sa_family, type, 0);
36 if (CHECK_FAIL(fd == -1))
37 return -1;
38 if (CHECK_FAIL(bind(fd, addr, len) == -1))
39 goto err;
40 if (type == SOCK_STREAM && CHECK_FAIL(listen(fd, 128) == -1))
41 goto err;
42
43 return fd;
44
45err:
46 close(fd);
47 return -1;
48}
49
50static int connect_to_server(const struct sockaddr *addr, socklen_t len,
51 int type)
52{
53 int fd = socket(addr->sa_family, type, 0);
54 if (CHECK_FAIL(fd == -1))
55 return -1;
56 if (CHECK_FAIL(connect(fd, addr, len)))
57 goto err;
58
59 return fd;
60
61err:
62 close(fd);
63 return -1;
64}
65
66static bool fill_addr_port(const struct sockaddr *sa, struct addr_port *ap)
67{
68 const struct sockaddr_in6 *in6;
69 const struct sockaddr_in *in;
70
71 switch (sa->sa_family) {
72 case AF_INET:
73 in = (const struct sockaddr_in *)sa;
74 ap->in_addr = in->sin_addr;
75 ap->port = in->sin_port;
76 return true;
77
78 case AF_INET6:
79 in6 = (const struct sockaddr_in6 *)sa;
80 ap->in6_addr = in6->sin6_addr;
81 ap->port = in6->sin6_port;
82 return true;
83
84 default:
85 return false;
86 }
87}
88
89static bool set_up_conn(const struct sockaddr *addr, socklen_t len, int type,
90 int *server, int *conn, struct tuple *tuple)
91{
92 struct sockaddr_storage ss;
93 socklen_t slen = sizeof(ss);
94 struct sockaddr *sa = (struct sockaddr *)&ss;
95
96 *server = start_server(addr, len, type);
97 if (*server < 0)
98 return false;
99
100 if (CHECK_FAIL(getsockname(*server, sa, &slen)))
101 goto close_server;
102
103 *conn = connect_to_server(sa, slen, type);
104 if (*conn < 0)
105 goto close_server;
106
107
108
109
110 slen = sizeof(ss);
111 if (CHECK_FAIL(getsockname(*conn, sa, &slen)))
112 goto close_conn;
113
114 if (CHECK_FAIL(!fill_addr_port(sa, &tuple->dst)))
115 goto close_conn;
116
117 slen = sizeof(ss);
118 if (CHECK_FAIL(getpeername(*conn, sa, &slen)))
119 goto close_conn;
120
121 if (CHECK_FAIL(!fill_addr_port(sa, &tuple->src)))
122 goto close_conn;
123
124 tuple->family = ss.ss_family;
125 return true;
126
127close_conn:
128 close(*conn);
129 *conn = -1;
130close_server:
131 close(*server);
132 *server = -1;
133 return false;
134}
135
136static socklen_t prepare_addr(struct sockaddr_storage *addr, int family)
137{
138 struct sockaddr_in *addr4;
139 struct sockaddr_in6 *addr6;
140
141 switch (family) {
142 case AF_INET:
143 addr4 = (struct sockaddr_in *)addr;
144 memset(addr4, 0, sizeof(*addr4));
145 addr4->sin_family = family;
146 addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
147 return sizeof(*addr4);
148 case AF_INET6:
149 addr6 = (struct sockaddr_in6 *)addr;
150 memset(addr6, 0, sizeof(*addr6));
151 addr6->sin6_family = family;
152 addr6->sin6_addr = in6addr_loopback;
153 return sizeof(*addr6);
154 default:
155 fprintf(stderr, "Invalid family %d", family);
156 return 0;
157 }
158}
159
160static bool was_decapsulated(struct bpf_prog_test_run_attr *tattr)
161{
162 return tattr->data_size_out < tattr->data_size_in;
163}
164
165enum type {
166 UDP,
167 TCP,
168 __NR_KIND,
169};
170
171enum hops {
172 NO_HOPS,
173 ONE_HOP,
174};
175
176enum flags {
177 NONE,
178 SYN,
179 ACK,
180};
181
182enum conn {
183 KNOWN_CONN,
184 UNKNOWN_CONN,
185};
186
187enum result {
188 ACCEPT,
189 FORWARD,
190};
191
192struct test_cfg {
193 enum type type;
194 enum result result;
195 enum conn conn;
196 enum hops hops;
197 enum flags flags;
198};
199
200static int test_str(void *buf, size_t len, const struct test_cfg *test,
201 int family)
202{
203 const char *family_str, *type, *conn, *hops, *result, *flags;
204
205 family_str = "IPv4";
206 if (family == AF_INET6)
207 family_str = "IPv6";
208
209 type = "TCP";
210 if (test->type == UDP)
211 type = "UDP";
212
213 conn = "known";
214 if (test->conn == UNKNOWN_CONN)
215 conn = "unknown";
216
217 hops = "no hops";
218 if (test->hops == ONE_HOP)
219 hops = "one hop";
220
221 result = "accept";
222 if (test->result == FORWARD)
223 result = "forward";
224
225 flags = "none";
226 if (test->flags == SYN)
227 flags = "SYN";
228 else if (test->flags == ACK)
229 flags = "ACK";
230
231 return snprintf(buf, len, "%s %s %s %s (%s, flags: %s)", family_str,
232 type, result, conn, hops, flags);
233}
234
235static struct test_cfg tests[] = {
236 { TCP, ACCEPT, UNKNOWN_CONN, NO_HOPS, SYN },
237 { TCP, ACCEPT, UNKNOWN_CONN, NO_HOPS, ACK },
238 { TCP, FORWARD, UNKNOWN_CONN, ONE_HOP, ACK },
239 { TCP, ACCEPT, KNOWN_CONN, ONE_HOP, ACK },
240 { UDP, ACCEPT, UNKNOWN_CONN, NO_HOPS, NONE },
241 { UDP, FORWARD, UNKNOWN_CONN, ONE_HOP, NONE },
242 { UDP, ACCEPT, KNOWN_CONN, ONE_HOP, NONE },
243};
244
245static void encap_init(encap_headers_t *encap, uint8_t hop_count, uint8_t proto)
246{
247 const uint8_t hlen =
248 (sizeof(struct guehdr) / sizeof(uint32_t)) + hop_count;
249 *encap = (encap_headers_t){
250 .eth = { .h_proto = htons(ETH_P_IP) },
251 .ip = {
252 .ihl = 5,
253 .version = 4,
254 .ttl = IPDEFTTL,
255 .protocol = IPPROTO_UDP,
256 .daddr = htonl(ENCAP_IP)
257 },
258 .udp = {
259 .dest = htons(ENCAP_PORT),
260 },
261 .gue = {
262 .hlen = hlen,
263 .proto_ctype = proto
264 },
265 .unigue = {
266 .hop_count = hop_count
267 },
268 };
269}
270
271static size_t build_input(const struct test_cfg *test, void *const buf,
272 const struct tuple *tuple)
273{
274 in_port_t sport = tuple->src.port;
275 encap_headers_t encap;
276 struct iphdr ip;
277 struct ipv6hdr ipv6;
278 struct tcphdr tcp;
279 struct udphdr udp;
280 struct in_addr next_hop;
281 uint8_t *p = buf;
282 int proto;
283
284 proto = IPPROTO_IPIP;
285 if (tuple->family == AF_INET6)
286 proto = IPPROTO_IPV6;
287
288 encap_init(&encap, test->hops == ONE_HOP ? 1 : 0, proto);
289 p = mempcpy(p, &encap, sizeof(encap));
290
291 if (test->hops == ONE_HOP) {
292 next_hop = (struct in_addr){ .s_addr = htonl(0x7f000002) };
293 p = mempcpy(p, &next_hop, sizeof(next_hop));
294 }
295
296 proto = IPPROTO_TCP;
297 if (test->type == UDP)
298 proto = IPPROTO_UDP;
299
300 switch (tuple->family) {
301 case AF_INET:
302 ip = (struct iphdr){
303 .ihl = 5,
304 .version = 4,
305 .ttl = IPDEFTTL,
306 .protocol = proto,
307 .saddr = tuple->src.in_addr.s_addr,
308 .daddr = tuple->dst.in_addr.s_addr,
309 };
310 p = mempcpy(p, &ip, sizeof(ip));
311 break;
312 case AF_INET6:
313 ipv6 = (struct ipv6hdr){
314 .version = 6,
315 .hop_limit = IPDEFTTL,
316 .nexthdr = proto,
317 .saddr = tuple->src.in6_addr,
318 .daddr = tuple->dst.in6_addr,
319 };
320 p = mempcpy(p, &ipv6, sizeof(ipv6));
321 break;
322 default:
323 return 0;
324 }
325
326 if (test->conn == UNKNOWN_CONN)
327 sport--;
328
329 switch (test->type) {
330 case TCP:
331 tcp = (struct tcphdr){
332 .source = sport,
333 .dest = tuple->dst.port,
334 };
335 if (test->flags == SYN)
336 tcp.syn = true;
337 if (test->flags == ACK)
338 tcp.ack = true;
339 p = mempcpy(p, &tcp, sizeof(tcp));
340 break;
341 case UDP:
342 udp = (struct udphdr){
343 .source = sport,
344 .dest = tuple->dst.port,
345 };
346 p = mempcpy(p, &udp, sizeof(udp));
347 break;
348 default:
349 return 0;
350 }
351
352 return (void *)p - buf;
353}
354
355static void close_fds(int *fds, int n)
356{
357 int i;
358
359 for (i = 0; i < n; i++)
360 if (fds[i] > 0)
361 close(fds[i]);
362}
363
364void test_cls_redirect(void)
365{
366 struct test_cls_redirect *skel = NULL;
367 struct bpf_prog_test_run_attr tattr = {};
368 int families[] = { AF_INET, AF_INET6 };
369 struct sockaddr_storage ss;
370 struct sockaddr *addr;
371 socklen_t slen;
372 int i, j, err;
373
374 int servers[__NR_KIND][ARRAY_SIZE(families)] = {};
375 int conns[__NR_KIND][ARRAY_SIZE(families)] = {};
376 struct tuple tuples[__NR_KIND][ARRAY_SIZE(families)];
377
378 skel = test_cls_redirect__open();
379 if (CHECK_FAIL(!skel))
380 return;
381
382 skel->rodata->ENCAPSULATION_IP = htonl(ENCAP_IP);
383 skel->rodata->ENCAPSULATION_PORT = htons(ENCAP_PORT);
384
385 if (CHECK_FAIL(test_cls_redirect__load(skel)))
386 goto cleanup;
387
388 addr = (struct sockaddr *)&ss;
389 for (i = 0; i < ARRAY_SIZE(families); i++) {
390 slen = prepare_addr(&ss, families[i]);
391 if (CHECK_FAIL(!slen))
392 goto cleanup;
393
394 if (CHECK_FAIL(!set_up_conn(addr, slen, SOCK_DGRAM,
395 &servers[UDP][i], &conns[UDP][i],
396 &tuples[UDP][i])))
397 goto cleanup;
398
399 if (CHECK_FAIL(!set_up_conn(addr, slen, SOCK_STREAM,
400 &servers[TCP][i], &conns[TCP][i],
401 &tuples[TCP][i])))
402 goto cleanup;
403 }
404
405 tattr.prog_fd = bpf_program__fd(skel->progs.cls_redirect);
406 for (i = 0; i < ARRAY_SIZE(tests); i++) {
407 struct test_cfg *test = &tests[i];
408
409 for (j = 0; j < ARRAY_SIZE(families); j++) {
410 struct tuple *tuple = &tuples[test->type][j];
411 char input[256];
412 char tmp[256];
413
414 test_str(tmp, sizeof(tmp), test, tuple->family);
415 if (!test__start_subtest(tmp))
416 continue;
417
418 tattr.data_out = tmp;
419 tattr.data_size_out = sizeof(tmp);
420
421 tattr.data_in = input;
422 tattr.data_size_in = build_input(test, input, tuple);
423 if (CHECK_FAIL(!tattr.data_size_in))
424 continue;
425
426 err = bpf_prog_test_run_xattr(&tattr);
427 if (CHECK_FAIL(err))
428 continue;
429
430 if (tattr.retval != TC_ACT_REDIRECT) {
431 PRINT_FAIL("expected TC_ACT_REDIRECT, got %d\n",
432 tattr.retval);
433 continue;
434 }
435
436 switch (test->result) {
437 case ACCEPT:
438 if (CHECK_FAIL(!was_decapsulated(&tattr)))
439 continue;
440 break;
441 case FORWARD:
442 if (CHECK_FAIL(was_decapsulated(&tattr)))
443 continue;
444 break;
445 default:
446 PRINT_FAIL("unknown result %d\n", test->result);
447 continue;
448 }
449 }
450 }
451
452cleanup:
453 test_cls_redirect__destroy(skel);
454 close_fds((int *)servers, sizeof(servers) / sizeof(servers[0][0]));
455 close_fds((int *)conns, sizeof(conns) / sizeof(conns[0][0]));
456}
457