| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2020 Facebook */ |
| |
| #define _GNU_SOURCE |
| #include <sched.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/socket.h> |
| #include <linux/compiler.h> |
| |
| #include "test_progs.h" |
| #include "cgroup_helpers.h" |
| #include "network_helpers.h" |
| #include "test_tcp_hdr_options.h" |
| #include "test_tcp_hdr_options.skel.h" |
| #include "test_misc_tcp_hdr_options.skel.h" |
| |
| #define LO_ADDR6 "::1" |
| #define CG_NAME "/tcpbpf-hdr-opt-test" |
| |
| static struct bpf_test_option exp_passive_estab_in; |
| static struct bpf_test_option exp_active_estab_in; |
| static struct bpf_test_option exp_passive_fin_in; |
| static struct bpf_test_option exp_active_fin_in; |
| static struct hdr_stg exp_passive_hdr_stg; |
| static struct hdr_stg exp_active_hdr_stg = { .active = true, }; |
| |
| static struct test_misc_tcp_hdr_options *misc_skel; |
| static struct test_tcp_hdr_options *skel; |
| static int lport_linum_map_fd; |
| static int hdr_stg_map_fd; |
| static __u32 duration; |
| static int cg_fd; |
| |
| struct sk_fds { |
| int srv_fd; |
| int passive_fd; |
| int active_fd; |
| int passive_lport; |
| int active_lport; |
| }; |
| |
| static int create_netns(void) |
| { |
| if (CHECK(unshare(CLONE_NEWNET), "create netns", |
| "unshare(CLONE_NEWNET): %s (%d)", |
| strerror(errno), errno)) |
| return -1; |
| |
| if (CHECK(system("ip link set dev lo up"), "run ip cmd", |
| "failed to bring lo link up\n")) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int write_sysctl(const char *sysctl, const char *value) |
| { |
| int fd, err, len; |
| |
| fd = open(sysctl, O_WRONLY); |
| if (CHECK(fd == -1, "open sysctl", "open(%s): %s (%d)\n", |
| sysctl, strerror(errno), errno)) |
| return -1; |
| |
| len = strlen(value); |
| err = write(fd, value, len); |
| close(fd); |
| if (CHECK(err != len, "write sysctl", |
| "write(%s, %s): err:%d %s (%d)\n", |
| sysctl, value, err, strerror(errno), errno)) |
| return -1; |
| |
| return 0; |
| } |
| |
| static void print_hdr_stg(const struct hdr_stg *hdr_stg, const char *prefix) |
| { |
| fprintf(stderr, "%s{active:%u, resend_syn:%u, syncookie:%u, fastopen:%u}\n", |
| prefix ? : "", hdr_stg->active, hdr_stg->resend_syn, |
| hdr_stg->syncookie, hdr_stg->fastopen); |
| } |
| |
| static void print_option(const struct bpf_test_option *opt, const char *prefix) |
| { |
| fprintf(stderr, "%s{flags:0x%x, max_delack_ms:%u, rand:0x%x}\n", |
| prefix ? : "", opt->flags, opt->max_delack_ms, opt->rand); |
| } |
| |
| static void sk_fds_close(struct sk_fds *sk_fds) |
| { |
| close(sk_fds->srv_fd); |
| close(sk_fds->passive_fd); |
| close(sk_fds->active_fd); |
| } |
| |
| static int sk_fds_shutdown(struct sk_fds *sk_fds) |
| { |
| int ret, abyte; |
| |
| shutdown(sk_fds->active_fd, SHUT_WR); |
| ret = read(sk_fds->passive_fd, &abyte, sizeof(abyte)); |
| if (CHECK(ret != 0, "read-after-shutdown(passive_fd):", |
| "ret:%d %s (%d)\n", |
| ret, strerror(errno), errno)) |
| return -1; |
| |
| shutdown(sk_fds->passive_fd, SHUT_WR); |
| ret = read(sk_fds->active_fd, &abyte, sizeof(abyte)); |
| if (CHECK(ret != 0, "read-after-shutdown(active_fd):", |
| "ret:%d %s (%d)\n", |
| ret, strerror(errno), errno)) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int sk_fds_connect(struct sk_fds *sk_fds, bool fast_open) |
| { |
| const char fast[] = "FAST!!!"; |
| struct sockaddr_in6 addr6; |
| socklen_t len; |
| |
| sk_fds->srv_fd = start_server(AF_INET6, SOCK_STREAM, LO_ADDR6, 0, 0); |
| if (CHECK(sk_fds->srv_fd == -1, "start_server", "%s (%d)\n", |
| strerror(errno), errno)) |
| goto error; |
| |
| if (fast_open) |
| sk_fds->active_fd = fastopen_connect(sk_fds->srv_fd, fast, |
| sizeof(fast), 0); |
| else |
| sk_fds->active_fd = connect_to_fd(sk_fds->srv_fd, 0); |
| |
| if (CHECK_FAIL(sk_fds->active_fd == -1)) { |
| close(sk_fds->srv_fd); |
| goto error; |
| } |
| |
| len = sizeof(addr6); |
| if (CHECK(getsockname(sk_fds->srv_fd, (struct sockaddr *)&addr6, |
| &len), "getsockname(srv_fd)", "%s (%d)\n", |
| strerror(errno), errno)) |
| goto error_close; |
| sk_fds->passive_lport = ntohs(addr6.sin6_port); |
| |
| len = sizeof(addr6); |
| if (CHECK(getsockname(sk_fds->active_fd, (struct sockaddr *)&addr6, |
| &len), "getsockname(active_fd)", "%s (%d)\n", |
| strerror(errno), errno)) |
| goto error_close; |
| sk_fds->active_lport = ntohs(addr6.sin6_port); |
| |
| sk_fds->passive_fd = accept(sk_fds->srv_fd, NULL, 0); |
| if (CHECK(sk_fds->passive_fd == -1, "accept(srv_fd)", "%s (%d)\n", |
| strerror(errno), errno)) |
| goto error_close; |
| |
| if (fast_open) { |
| char bytes_in[sizeof(fast)]; |
| int ret; |
| |
| ret = read(sk_fds->passive_fd, bytes_in, sizeof(bytes_in)); |
| if (CHECK(ret != sizeof(fast), "read fastopen syn data", |
| "expected=%lu actual=%d\n", sizeof(fast), ret)) { |
| close(sk_fds->passive_fd); |
| goto error_close; |
| } |
| } |
| |
| return 0; |
| |
| error_close: |
| close(sk_fds->active_fd); |
| close(sk_fds->srv_fd); |
| |
| error: |
| memset(sk_fds, -1, sizeof(*sk_fds)); |
| return -1; |
| } |
| |
| static int check_hdr_opt(const struct bpf_test_option *exp, |
| const struct bpf_test_option *act, |
| const char *hdr_desc) |
| { |
| if (CHECK(memcmp(exp, act, sizeof(*exp)), |
| "expected-vs-actual", "unexpected %s\n", hdr_desc)) { |
| print_option(exp, "expected: "); |
| print_option(act, " actual: "); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int check_hdr_stg(const struct hdr_stg *exp, int fd, |
| const char *stg_desc) |
| { |
| struct hdr_stg act; |
| |
| if (CHECK(bpf_map_lookup_elem(hdr_stg_map_fd, &fd, &act), |
| "map_lookup(hdr_stg_map_fd)", "%s %s (%d)\n", |
| stg_desc, strerror(errno), errno)) |
| return -1; |
| |
| if (CHECK(memcmp(exp, &act, sizeof(*exp)), |
| "expected-vs-actual", "unexpected %s\n", stg_desc)) { |
| print_hdr_stg(exp, "expected: "); |
| print_hdr_stg(&act, " actual: "); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int check_error_linum(const struct sk_fds *sk_fds) |
| { |
| unsigned int nr_errors = 0; |
| struct linum_err linum_err; |
| int lport; |
| |
| lport = sk_fds->passive_lport; |
| if (!bpf_map_lookup_elem(lport_linum_map_fd, &lport, &linum_err)) { |
| fprintf(stderr, |
| "bpf prog error out at lport:passive(%d), linum:%u err:%d\n", |
| lport, linum_err.linum, linum_err.err); |
| nr_errors++; |
| } |
| |
| lport = sk_fds->active_lport; |
| if (!bpf_map_lookup_elem(lport_linum_map_fd, &lport, &linum_err)) { |
| fprintf(stderr, |
| "bpf prog error out at lport:active(%d), linum:%u err:%d\n", |
| lport, linum_err.linum, linum_err.err); |
| nr_errors++; |
| } |
| |
| return nr_errors; |
| } |
| |
| static void check_hdr_and_close_fds(struct sk_fds *sk_fds) |
| { |
| const __u32 expected_inherit_cb_flags = |
| BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG | |
| BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG | |
| BPF_SOCK_OPS_STATE_CB_FLAG; |
| |
| if (sk_fds_shutdown(sk_fds)) |
| goto check_linum; |
| |
| if (CHECK(expected_inherit_cb_flags != skel->bss->inherit_cb_flags, |
| "Unexpected inherit_cb_flags", "0x%x != 0x%x\n", |
| skel->bss->inherit_cb_flags, expected_inherit_cb_flags)) |
| goto check_linum; |
| |
| if (check_hdr_stg(&exp_passive_hdr_stg, sk_fds->passive_fd, |
| "passive_hdr_stg")) |
| goto check_linum; |
| |
| if (check_hdr_stg(&exp_active_hdr_stg, sk_fds->active_fd, |
| "active_hdr_stg")) |
| goto check_linum; |
| |
| if (check_hdr_opt(&exp_passive_estab_in, &skel->bss->passive_estab_in, |
| "passive_estab_in")) |
| goto check_linum; |
| |
| if (check_hdr_opt(&exp_active_estab_in, &skel->bss->active_estab_in, |
| "active_estab_in")) |
| goto check_linum; |
| |
| if (check_hdr_opt(&exp_passive_fin_in, &skel->bss->passive_fin_in, |
| "passive_fin_in")) |
| goto check_linum; |
| |
| check_hdr_opt(&exp_active_fin_in, &skel->bss->active_fin_in, |
| "active_fin_in"); |
| |
| check_linum: |
| CHECK_FAIL(check_error_linum(sk_fds)); |
| sk_fds_close(sk_fds); |
| } |
| |
| static void prepare_out(void) |
| { |
| skel->bss->active_syn_out = exp_passive_estab_in; |
| skel->bss->passive_synack_out = exp_active_estab_in; |
| |
| skel->bss->active_fin_out = exp_passive_fin_in; |
| skel->bss->passive_fin_out = exp_active_fin_in; |
| } |
| |
| static void reset_test(void) |
| { |
| size_t optsize = sizeof(struct bpf_test_option); |
| int lport, err; |
| |
| memset(&skel->bss->passive_synack_out, 0, optsize); |
| memset(&skel->bss->passive_fin_out, 0, optsize); |
| |
| memset(&skel->bss->passive_estab_in, 0, optsize); |
| memset(&skel->bss->passive_fin_in, 0, optsize); |
| |
| memset(&skel->bss->active_syn_out, 0, optsize); |
| memset(&skel->bss->active_fin_out, 0, optsize); |
| |
| memset(&skel->bss->active_estab_in, 0, optsize); |
| memset(&skel->bss->active_fin_in, 0, optsize); |
| |
| skel->bss->inherit_cb_flags = 0; |
| |
| skel->data->test_kind = TCPOPT_EXP; |
| skel->data->test_magic = 0xeB9F; |
| |
| memset(&exp_passive_estab_in, 0, optsize); |
| memset(&exp_active_estab_in, 0, optsize); |
| memset(&exp_passive_fin_in, 0, optsize); |
| memset(&exp_active_fin_in, 0, optsize); |
| |
| memset(&exp_passive_hdr_stg, 0, sizeof(exp_passive_hdr_stg)); |
| memset(&exp_active_hdr_stg, 0, sizeof(exp_active_hdr_stg)); |
| exp_active_hdr_stg.active = true; |
| |
| err = bpf_map_get_next_key(lport_linum_map_fd, NULL, &lport); |
| while (!err) { |
| bpf_map_delete_elem(lport_linum_map_fd, &lport); |
| err = bpf_map_get_next_key(lport_linum_map_fd, &lport, &lport); |
| } |
| } |
| |
| static void fastopen_estab(void) |
| { |
| struct bpf_link *link; |
| struct sk_fds sk_fds; |
| |
| hdr_stg_map_fd = bpf_map__fd(skel->maps.hdr_stg_map); |
| lport_linum_map_fd = bpf_map__fd(skel->maps.lport_linum_map); |
| |
| exp_passive_estab_in.flags = OPTION_F_RAND | OPTION_F_MAX_DELACK_MS; |
| exp_passive_estab_in.rand = 0xfa; |
| exp_passive_estab_in.max_delack_ms = 11; |
| |
| exp_active_estab_in.flags = OPTION_F_RAND | OPTION_F_MAX_DELACK_MS; |
| exp_active_estab_in.rand = 0xce; |
| exp_active_estab_in.max_delack_ms = 22; |
| |
| exp_passive_hdr_stg.fastopen = true; |
| |
| prepare_out(); |
| |
| /* Allow fastopen without fastopen cookie */ |
| if (write_sysctl("/proc/sys/net/ipv4/tcp_fastopen", "1543")) |
| return; |
| |
| link = bpf_program__attach_cgroup(skel->progs.estab, cg_fd); |
| if (!ASSERT_OK_PTR(link, "attach_cgroup(estab)")) |
| return; |
| |
| if (sk_fds_connect(&sk_fds, true)) { |
| bpf_link__destroy(link); |
| return; |
| } |
| |
| check_hdr_and_close_fds(&sk_fds); |
| bpf_link__destroy(link); |
| } |
| |
| static void syncookie_estab(void) |
| { |
| struct bpf_link *link; |
| struct sk_fds sk_fds; |
| |
| hdr_stg_map_fd = bpf_map__fd(skel->maps.hdr_stg_map); |
| lport_linum_map_fd = bpf_map__fd(skel->maps.lport_linum_map); |
| |
| exp_passive_estab_in.flags = OPTION_F_RAND | OPTION_F_MAX_DELACK_MS; |
| exp_passive_estab_in.rand = 0xfa; |
| exp_passive_estab_in.max_delack_ms = 11; |
| |
| exp_active_estab_in.flags = OPTION_F_RAND | OPTION_F_MAX_DELACK_MS | |
| OPTION_F_RESEND; |
| exp_active_estab_in.rand = 0xce; |
| exp_active_estab_in.max_delack_ms = 22; |
| |
| exp_passive_hdr_stg.syncookie = true; |
| exp_active_hdr_stg.resend_syn = true, |
| |
| prepare_out(); |
| |
| /* Clear the RESEND to ensure the bpf prog can learn |
| * want_cookie and set the RESEND by itself. |
| */ |
| skel->bss->passive_synack_out.flags &= ~OPTION_F_RESEND; |
| |
| /* Enforce syncookie mode */ |
| if (write_sysctl("/proc/sys/net/ipv4/tcp_syncookies", "2")) |
| return; |
| |
| link = bpf_program__attach_cgroup(skel->progs.estab, cg_fd); |
| if (!ASSERT_OK_PTR(link, "attach_cgroup(estab)")) |
| return; |
| |
| if (sk_fds_connect(&sk_fds, false)) { |
| bpf_link__destroy(link); |
| return; |
| } |
| |
| check_hdr_and_close_fds(&sk_fds); |
| bpf_link__destroy(link); |
| } |
| |
| static void fin(void) |
| { |
| struct bpf_link *link; |
| struct sk_fds sk_fds; |
| |
| hdr_stg_map_fd = bpf_map__fd(skel->maps.hdr_stg_map); |
| lport_linum_map_fd = bpf_map__fd(skel->maps.lport_linum_map); |
| |
| exp_passive_fin_in.flags = OPTION_F_RAND; |
| exp_passive_fin_in.rand = 0xfa; |
| |
| exp_active_fin_in.flags = OPTION_F_RAND; |
| exp_active_fin_in.rand = 0xce; |
| |
| prepare_out(); |
| |
| if (write_sysctl("/proc/sys/net/ipv4/tcp_syncookies", "1")) |
| return; |
| |
| link = bpf_program__attach_cgroup(skel->progs.estab, cg_fd); |
| if (!ASSERT_OK_PTR(link, "attach_cgroup(estab)")) |
| return; |
| |
| if (sk_fds_connect(&sk_fds, false)) { |
| bpf_link__destroy(link); |
| return; |
| } |
| |
| check_hdr_and_close_fds(&sk_fds); |
| bpf_link__destroy(link); |
| } |
| |
| static void __simple_estab(bool exprm) |
| { |
| struct bpf_link *link; |
| struct sk_fds sk_fds; |
| |
| hdr_stg_map_fd = bpf_map__fd(skel->maps.hdr_stg_map); |
| lport_linum_map_fd = bpf_map__fd(skel->maps.lport_linum_map); |
| |
| exp_passive_estab_in.flags = OPTION_F_RAND | OPTION_F_MAX_DELACK_MS; |
| exp_passive_estab_in.rand = 0xfa; |
| exp_passive_estab_in.max_delack_ms = 11; |
| |
| exp_active_estab_in.flags = OPTION_F_RAND | OPTION_F_MAX_DELACK_MS; |
| exp_active_estab_in.rand = 0xce; |
| exp_active_estab_in.max_delack_ms = 22; |
| |
| prepare_out(); |
| |
| if (!exprm) { |
| skel->data->test_kind = 0xB9; |
| skel->data->test_magic = 0; |
| } |
| |
| if (write_sysctl("/proc/sys/net/ipv4/tcp_syncookies", "1")) |
| return; |
| |
| link = bpf_program__attach_cgroup(skel->progs.estab, cg_fd); |
| if (!ASSERT_OK_PTR(link, "attach_cgroup(estab)")) |
| return; |
| |
| if (sk_fds_connect(&sk_fds, false)) { |
| bpf_link__destroy(link); |
| return; |
| } |
| |
| check_hdr_and_close_fds(&sk_fds); |
| bpf_link__destroy(link); |
| } |
| |
| static void no_exprm_estab(void) |
| { |
| __simple_estab(false); |
| } |
| |
| static void simple_estab(void) |
| { |
| __simple_estab(true); |
| } |
| |
| static void misc(void) |
| { |
| const char send_msg[] = "MISC!!!"; |
| char recv_msg[sizeof(send_msg)]; |
| const unsigned int nr_data = 2; |
| struct bpf_link *link; |
| struct sk_fds sk_fds; |
| int i, ret; |
| |
| lport_linum_map_fd = bpf_map__fd(misc_skel->maps.lport_linum_map); |
| |
| if (write_sysctl("/proc/sys/net/ipv4/tcp_syncookies", "1")) |
| return; |
| |
| link = bpf_program__attach_cgroup(misc_skel->progs.misc_estab, cg_fd); |
| if (!ASSERT_OK_PTR(link, "attach_cgroup(misc_estab)")) |
| return; |
| |
| if (sk_fds_connect(&sk_fds, false)) { |
| bpf_link__destroy(link); |
| return; |
| } |
| |
| for (i = 0; i < nr_data; i++) { |
| /* MSG_EOR to ensure skb will not be combined */ |
| ret = send(sk_fds.active_fd, send_msg, sizeof(send_msg), |
| MSG_EOR); |
| if (CHECK(ret != sizeof(send_msg), "send(msg)", "ret:%d\n", |
| ret)) |
| goto check_linum; |
| |
| ret = read(sk_fds.passive_fd, recv_msg, sizeof(recv_msg)); |
| if (CHECK(ret != sizeof(send_msg), "read(msg)", "ret:%d\n", |
| ret)) |
| goto check_linum; |
| } |
| |
| if (sk_fds_shutdown(&sk_fds)) |
| goto check_linum; |
| |
| CHECK(misc_skel->bss->nr_syn != 1, "unexpected nr_syn", |
| "expected (1) != actual (%u)\n", |
| misc_skel->bss->nr_syn); |
| |
| CHECK(misc_skel->bss->nr_data != nr_data, "unexpected nr_data", |
| "expected (%u) != actual (%u)\n", |
| nr_data, misc_skel->bss->nr_data); |
| |
| /* The last ACK may have been delayed, so it is either 1 or 2. */ |
| CHECK(misc_skel->bss->nr_pure_ack != 1 && |
| misc_skel->bss->nr_pure_ack != 2, |
| "unexpected nr_pure_ack", |
| "expected (1 or 2) != actual (%u)\n", |
| misc_skel->bss->nr_pure_ack); |
| |
| CHECK(misc_skel->bss->nr_fin != 1, "unexpected nr_fin", |
| "expected (1) != actual (%u)\n", |
| misc_skel->bss->nr_fin); |
| |
| check_linum: |
| CHECK_FAIL(check_error_linum(&sk_fds)); |
| sk_fds_close(&sk_fds); |
| bpf_link__destroy(link); |
| } |
| |
| struct test { |
| const char *desc; |
| void (*run)(void); |
| }; |
| |
| #define DEF_TEST(name) { #name, name } |
| static struct test tests[] = { |
| DEF_TEST(simple_estab), |
| DEF_TEST(no_exprm_estab), |
| DEF_TEST(syncookie_estab), |
| DEF_TEST(fastopen_estab), |
| DEF_TEST(fin), |
| DEF_TEST(misc), |
| }; |
| |
| void test_tcp_hdr_options(void) |
| { |
| int i; |
| |
| skel = test_tcp_hdr_options__open_and_load(); |
| if (CHECK(!skel, "open and load skel", "failed")) |
| return; |
| |
| misc_skel = test_misc_tcp_hdr_options__open_and_load(); |
| if (CHECK(!misc_skel, "open and load misc test skel", "failed")) |
| goto skel_destroy; |
| |
| cg_fd = test__join_cgroup(CG_NAME); |
| if (CHECK_FAIL(cg_fd < 0)) |
| goto skel_destroy; |
| |
| for (i = 0; i < ARRAY_SIZE(tests); i++) { |
| if (!test__start_subtest(tests[i].desc)) |
| continue; |
| |
| if (create_netns()) |
| break; |
| |
| tests[i].run(); |
| |
| reset_test(); |
| } |
| |
| close(cg_fd); |
| skel_destroy: |
| test_misc_tcp_hdr_options__destroy(misc_skel); |
| test_tcp_hdr_options__destroy(skel); |
| } |