From: Cong Wang <cong.wang@xxxxxxxxxxxxx> Similarly to the previous test, we also need a test case to cover positive offsets as well, TC is an excellent hook for this. Cc: John Fastabend <john.fastabend@xxxxxxxxx> Cc: Daniel Borkmann <daniel@xxxxxxxxxxxxx> Cc: Zijian Zhang <zijianzhang@xxxxxxxxxxxxx> Signed-off-by: Cong Wang <cong.wang@xxxxxxxxxxxxx> --- .../selftests/bpf/prog_tests/tc_change_tail.c | 78 ++++++++++++ .../selftests/bpf/progs/test_tc_change_tail.c | 114 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/tc_change_tail.c create mode 100644 tools/testing/selftests/bpf/progs/test_tc_change_tail.c diff --git a/tools/testing/selftests/bpf/prog_tests/tc_change_tail.c b/tools/testing/selftests/bpf/prog_tests/tc_change_tail.c new file mode 100644 index 000000000000..110f54a71a35 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tc_change_tail.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <error.h> +#include <test_progs.h> +#include <linux/pkt_cls.h> + +#include "test_tc_change_tail.skel.h" +#include "socket_helpers.h" + +#define LO_IFINDEX 1 + +void test_tc_change_tail(void) +{ + DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = LO_IFINDEX, + .attach_point = BPF_TC_INGRESS); + DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts); + struct test_tc_change_tail *skel = NULL; + bool hook_created = false; + int ret, fd; + int c1, p1; + char buf[2]; + + skel = test_tc_change_tail__open_and_load(); + if (!ASSERT_OK_PTR(skel, "test_tc_change_tail__open_and_load")) + return; + ret = bpf_tc_hook_create(&hook); + if (ret == 0) + hook_created = true; + ret = ret == -EEXIST ? 0 : ret; + if (!ASSERT_OK(ret, "bpf_tc_hook_create")) + goto destroy; + fd = bpf_program__fd(skel->progs.change_tail); + opts.prog_fd = fd; + opts.handle = 1; + opts.priority = 1; + opts.flags = BPF_TC_F_REPLACE; + ret = bpf_tc_attach(&hook, &opts); + if (!ASSERT_OK(ret, "bpf_tc_attach")) + goto hook_destroy; + + ret = create_pair(AF_INET, SOCK_STREAM, &c1, &p1); + if (!ASSERT_OK(ret, "create_pair")) + goto detach; + + ret = xsend(p1, "Tr", 2, 0); + ASSERT_EQ(ret, 2, "xsend(p1)"); + ret = recv(c1, buf, 2, 0); + ASSERT_EQ(ret, 2, "recv(c1)"); + ASSERT_EQ(skel->data->change_tail_ret, 0, "change_tail_ret"); + + ret = xsend(p1, "G", 1, 0); + ASSERT_EQ(ret, 1, "xsend(p1)"); + ret = recv(c1, buf, 1, 0); + ASSERT_EQ(ret, 1, "recv(c1)"); + ASSERT_EQ(skel->data->change_tail_ret, 0, "change_tail_ret"); + + ret = xsend(p1, "E", 1, 0); + ASSERT_EQ(ret, 1, "xsend(p1)"); + ret = recv(c1, buf, 1, 0); + ASSERT_EQ(ret, 1, "recv(c1)"); + ASSERT_EQ(skel->data->change_tail_ret, -EINVAL, "change_tail_ret"); + + ret = xsend(p1, "Z", 1, 0); + ASSERT_EQ(ret, 1, "xsend(p1)"); + ret = recv(c1, buf, 1, 0); + ASSERT_EQ(ret, 1, "recv(c1)"); + ASSERT_EQ(skel->data->change_tail_ret, -EINVAL, "change_tail_ret"); + + close(c1); + close(p1); +detach: + bpf_tc_detach(&hook, &opts); +hook_destroy: + if (hook_created) + bpf_tc_hook_destroy(&hook); +destroy: + test_tc_change_tail__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/test_tc_change_tail.c b/tools/testing/selftests/bpf/progs/test_tc_change_tail.c new file mode 100644 index 000000000000..735c7325a2ab --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_tc_change_tail.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bpf.h> +#include <bpf/bpf_helpers.h> +#include <linux/if_ether.h> +#include <linux/in.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/pkt_cls.h> + +long change_tail_ret = 1; + +static __always_inline struct iphdr *parse_ip_header(struct __sk_buff *skb, int *ip_proto) +{ + void *data_end = (void *)(long)skb->data_end; + void *data = (void *)(long)skb->data; + struct ethhdr *eth = data; + struct iphdr *iph; + + /* Verify Ethernet header */ + if ((void *)(data + sizeof(*eth)) > data_end) + return NULL; + + /* Skip Ethernet header to get to IP header */ + iph = (void *)(data + sizeof(struct ethhdr)); + + /* Verify IP header */ + if ((void *)(data + sizeof(struct ethhdr) + sizeof(*iph)) > data_end) + return NULL; + + /* Basic IP header validation */ + if (iph->version != 4) /* Only support IPv4 */ + return NULL; + + if (iph->ihl < 5) /* Minimum IP header length */ + return NULL; + + *ip_proto = iph->protocol; + return iph; +} + +static __always_inline struct tcphdr *parse_tcp_header(struct __sk_buff *skb, struct iphdr *iph) +{ + void *data_end = (void *)(long)skb->data_end; + void *hdr = (void *)iph; + struct tcphdr *tcp; + + /* Calculate TCP header position */ + tcp = hdr + (iph->ihl * 4); + hdr = (void *)tcp; + + /* Verify TCP header bounds */ + if ((void *)(hdr + sizeof(*tcp)) > data_end) + return NULL; + + /* Basic TCP validation */ + if (tcp->doff < 5) /* Minimum TCP header length */ + return NULL; + + /* Success */ + return tcp; +} + +SEC("tc") +int change_tail(struct __sk_buff *skb) +{ + int len = skb->len; + struct tcphdr *tcp; + struct iphdr *iph; + void *data_end; + char *payload; + int ip_proto; + + bpf_skb_pull_data(skb, len); + + data_end = (void *)(long)skb->data_end; + iph = parse_ip_header(skb, &ip_proto); + if (!iph) + return TC_ACT_OK; + + if (ip_proto != IPPROTO_TCP) /* Only support TCP packets */ + return TC_ACT_OK; + + tcp = parse_tcp_header(skb, iph); + if (!tcp) + return TC_ACT_OK; + + payload = (char *)tcp + (tcp->doff * 4); + if (payload + 1 > (char *)data_end) + return TC_ACT_OK; + + if (payload[0] == 'T') { + change_tail_ret = bpf_skb_change_tail(skb, len - 1, 0); + /* Change it back to make TCP happy */ + if (change_tail_ret == 0) + bpf_skb_change_tail(skb, len, 0); + return TC_ACT_OK; + } else if (payload[0] == 'G') { + change_tail_ret = bpf_skb_change_tail(skb, len + 1, 0); + /* Change it back to make TCP happy */ + if (change_tail_ret == 0) + bpf_skb_change_tail(skb, len, 0); + return TC_ACT_OK; + } else if (payload[0] == 'E') { + change_tail_ret = bpf_skb_change_tail(skb, 65535, 0); /* Should fail */ + return TC_ACT_OK; + } else if (payload[0] == 'Z') { + change_tail_ret = bpf_skb_change_tail(skb, 0, 0); /* Should fail */ + return TC_ACT_OK; + } + return TC_ACT_SHOT; +} + +char _license[] SEC("license") = "GPL"; -- 2.34.1