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 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
|
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020 Facebook */
#define _GNU_SOURCE
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sched.h>
#include <net/if.h>
#include <linux/compiler.h>
#include <bpf/libbpf.h>
#include "network_helpers.h"
#include "test_progs.h"
#include "test_btf_skc_cls_ingress.skel.h"
#define TEST_NS "skc_cls_ingress"
#define BIT(n) (1 << (n))
#define TEST_MODE_IPV4 BIT(0)
#define TEST_MODE_IPV6 BIT(1)
#define TEST_MODE_DUAL (TEST_MODE_IPV4 | TEST_MODE_IPV6)
#define SERVER_ADDR_IPV4 "127.0.0.1"
#define SERVER_ADDR_IPV6 "::1"
#define SERVER_ADDR_DUAL "::0"
/* RFC791, 576 for minimal IPv4 datagram, minus 40 bytes of TCP header */
#define MIN_IPV4_MSS 536
static struct netns_obj *prepare_netns(struct test_btf_skc_cls_ingress *skel)
{
LIBBPF_OPTS(bpf_tc_hook, qdisc_lo, .attach_point = BPF_TC_INGRESS);
LIBBPF_OPTS(bpf_tc_opts, tc_attach,
.prog_fd = bpf_program__fd(skel->progs.cls_ingress));
struct netns_obj *ns = NULL;
ns = netns_new(TEST_NS, true);
if (!ASSERT_OK_PTR(ns, "create and join netns"))
return ns;
qdisc_lo.ifindex = if_nametoindex("lo");
if (!ASSERT_OK(bpf_tc_hook_create(&qdisc_lo), "qdisc add dev lo clsact"))
goto free_ns;
if (!ASSERT_OK(bpf_tc_attach(&qdisc_lo, &tc_attach),
"filter add dev lo ingress"))
goto free_ns;
/* Ensure 20 bytes options (i.e. in total 40 bytes tcp header) for the
* bpf_tcp_gen_syncookie() helper.
*/
if (write_sysctl("/proc/sys/net/ipv4/tcp_window_scaling", "1") ||
write_sysctl("/proc/sys/net/ipv4/tcp_timestamps", "1") ||
write_sysctl("/proc/sys/net/ipv4/tcp_sack", "1"))
goto free_ns;
return ns;
free_ns:
netns_free(ns);
return NULL;
}
static void reset_test(struct test_btf_skc_cls_ingress *skel)
{
memset(&skel->bss->srv_sa4, 0, sizeof(skel->bss->srv_sa4));
memset(&skel->bss->srv_sa6, 0, sizeof(skel->bss->srv_sa6));
skel->bss->listen_tp_sport = 0;
skel->bss->req_sk_sport = 0;
skel->bss->recv_cookie = 0;
skel->bss->gen_cookie = 0;
skel->bss->linum = 0;
skel->bss->mss = 0;
}
static void print_err_line(struct test_btf_skc_cls_ingress *skel)
{
if (skel->bss->linum)
printf("bpf prog error at line %u\n", skel->bss->linum);
}
static int v6only_true(int fd, void *opts)
{
int mode = true;
return setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &mode, sizeof(mode));
}
static int v6only_false(int fd, void *opts)
{
int mode = false;
return setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &mode, sizeof(mode));
}
static void run_test(struct test_btf_skc_cls_ingress *skel, bool gen_cookies,
int ip_mode)
{
const char *tcp_syncookies = gen_cookies ? "2" : "1";
int listen_fd = -1, cli_fd = -1, srv_fd = -1, err;
struct network_helper_opts opts = { 0 };
struct sockaddr_storage *addr;
struct sockaddr_in6 srv_sa6;
struct sockaddr_in srv_sa4;
socklen_t addr_len;
int sock_family;
char *srv_addr;
int srv_port;
switch (ip_mode) {
case TEST_MODE_IPV4:
sock_family = AF_INET;
srv_addr = SERVER_ADDR_IPV4;
addr = (struct sockaddr_storage *)&srv_sa4;
addr_len = sizeof(srv_sa4);
break;
case TEST_MODE_IPV6:
opts.post_socket_cb = v6only_true;
sock_family = AF_INET6;
srv_addr = SERVER_ADDR_IPV6;
addr = (struct sockaddr_storage *)&srv_sa6;
addr_len = sizeof(srv_sa6);
break;
case TEST_MODE_DUAL:
opts.post_socket_cb = v6only_false;
sock_family = AF_INET6;
srv_addr = SERVER_ADDR_DUAL;
addr = (struct sockaddr_storage *)&srv_sa6;
addr_len = sizeof(srv_sa6);
break;
default:
PRINT_FAIL("Unknown IP mode %d", ip_mode);
return;
}
if (write_sysctl("/proc/sys/net/ipv4/tcp_syncookies", tcp_syncookies))
return;
listen_fd = start_server_str(sock_family, SOCK_STREAM, srv_addr, 0,
&opts);
if (!ASSERT_OK_FD(listen_fd, "start server"))
return;
err = getsockname(listen_fd, (struct sockaddr *)addr, &addr_len);
if (!ASSERT_OK(err, "getsockname(listen_fd)"))
goto done;
switch (ip_mode) {
case TEST_MODE_IPV4:
memcpy(&skel->bss->srv_sa4, &srv_sa4, sizeof(srv_sa4));
srv_port = ntohs(srv_sa4.sin_port);
break;
case TEST_MODE_IPV6:
case TEST_MODE_DUAL:
memcpy(&skel->bss->srv_sa6, &srv_sa6, sizeof(srv_sa6));
srv_port = ntohs(srv_sa6.sin6_port);
break;
default:
goto done;
}
cli_fd = connect_to_fd(listen_fd, 0);
if (!ASSERT_OK_FD(cli_fd, "connect client"))
goto done;
srv_fd = accept(listen_fd, NULL, NULL);
if (!ASSERT_OK_FD(srv_fd, "accept connection"))
goto done;
ASSERT_EQ(skel->bss->listen_tp_sport, srv_port, "listen tp src port");
if (!gen_cookies) {
ASSERT_EQ(skel->bss->req_sk_sport, srv_port,
"request socket source port with syncookies disabled");
ASSERT_EQ(skel->bss->gen_cookie, 0,
"generated syncookie with syncookies disabled");
ASSERT_EQ(skel->bss->recv_cookie, 0,
"received syncookie with syncookies disabled");
} else {
ASSERT_EQ(skel->bss->req_sk_sport, 0,
"request socket source port with syncookies enabled");
ASSERT_NEQ(skel->bss->gen_cookie, 0,
"syncookie properly generated");
ASSERT_EQ(skel->bss->gen_cookie, skel->bss->recv_cookie,
"matching syncookies on client and server");
ASSERT_GT(skel->bss->mss, MIN_IPV4_MSS,
"MSS in cookie min value");
ASSERT_LT(skel->bss->mss, USHRT_MAX,
"MSS in cookie max value");
}
done:
if (listen_fd != -1)
close(listen_fd);
if (cli_fd != -1)
close(cli_fd);
if (srv_fd != -1)
close(srv_fd);
}
static void test_conn_ipv4(struct test_btf_skc_cls_ingress *skel)
{
run_test(skel, false, TEST_MODE_IPV4);
}
static void test_conn_ipv6(struct test_btf_skc_cls_ingress *skel)
{
run_test(skel, false, TEST_MODE_IPV6);
}
static void test_conn_dual(struct test_btf_skc_cls_ingress *skel)
{
run_test(skel, false, TEST_MODE_DUAL);
}
static void test_syncookie_ipv4(struct test_btf_skc_cls_ingress *skel)
{
run_test(skel, true, TEST_MODE_IPV4);
}
static void test_syncookie_ipv6(struct test_btf_skc_cls_ingress *skel)
{
run_test(skel, true, TEST_MODE_IPV6);
}
static void test_syncookie_dual(struct test_btf_skc_cls_ingress *skel)
{
run_test(skel, true, TEST_MODE_DUAL);
}
struct test {
const char *desc;
void (*run)(struct test_btf_skc_cls_ingress *skel);
};
#define DEF_TEST(name) { #name, test_##name }
static struct test tests[] = {
DEF_TEST(conn_ipv4),
DEF_TEST(conn_ipv6),
DEF_TEST(conn_dual),
DEF_TEST(syncookie_ipv4),
DEF_TEST(syncookie_ipv6),
DEF_TEST(syncookie_dual),
};
void test_btf_skc_cls_ingress(void)
{
struct test_btf_skc_cls_ingress *skel;
struct netns_obj *ns;
int i;
skel = test_btf_skc_cls_ingress__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_btf_skc_cls_ingress__open_and_load"))
return;
for (i = 0; i < ARRAY_SIZE(tests); i++) {
if (!test__start_subtest(tests[i].desc))
continue;
ns = prepare_netns(skel);
if (!ns)
break;
tests[i].run(skel);
print_err_line(skel);
reset_test(skel);
netns_free(ns);
}
test_btf_skc_cls_ingress__destroy(skel);
}
|