bpf, selftests: Extend test_tc_redirect to use modified bpf_redirect_neigh()
authorToke Høiland-Jørgensen <toke@redhat.com>
Tue, 20 Oct 2020 21:25:57 +0000 (23:25 +0200)
committerDaniel Borkmann <daniel@iogearbox.net>
Wed, 21 Oct 2020 23:30:00 +0000 (01:30 +0200)
This updates the test_tc_neigh prog in selftests to use the new syntax of
bpf_redirect_neigh(). To exercise the helper both with and without the
optional parameter, add an additional test_tc_neigh_fib test program, which
does a bpf_fib_lookup() followed by a call to bpf_redirect_neigh() instead
of looking up the ifindex in a map.

Update the test_tc_redirect.sh script to run both versions of the test,
and while we're add it, fix it to work on systems that have a consolidated
dual-stack 'ping' binary instead of separate ping/ping6 versions.

Signed-off-by: Toke Høiland-Jørgensen <toke@redhat.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/160322915724.32199.17530068594636950447.stgit@toke.dk
tools/testing/selftests/bpf/progs/test_tc_neigh.c
tools/testing/selftests/bpf/progs/test_tc_neigh_fib.c [new file with mode: 0644]
tools/testing/selftests/bpf/test_tc_redirect.sh

index fe182616b112a0d7312dd110363096cf490ee751..b985ac4e7a814a94a9a5e54f851152bfe6eacf80 100644 (file)
@@ -1,4 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
+#include <stddef.h>
 #include <stdint.h>
 #include <stdbool.h>
 
@@ -118,7 +119,7 @@ SEC("dst_ingress") int tc_dst(struct __sk_buff *skb)
        if (bpf_skb_store_bytes(skb, 0, &zero, sizeof(zero), 0) < 0)
                return TC_ACT_SHOT;
 
-       return bpf_redirect_neigh(get_dev_ifindex(dev_src), 0);
+       return bpf_redirect_neigh(get_dev_ifindex(dev_src), NULL, 0, 0);
 }
 
 SEC("src_ingress") int tc_src(struct __sk_buff *skb)
@@ -142,7 +143,7 @@ SEC("src_ingress") int tc_src(struct __sk_buff *skb)
        if (bpf_skb_store_bytes(skb, 0, &zero, sizeof(zero), 0) < 0)
                return TC_ACT_SHOT;
 
-       return bpf_redirect_neigh(get_dev_ifindex(dev_dst), 0);
+       return bpf_redirect_neigh(get_dev_ifindex(dev_dst), NULL, 0, 0);
 }
 
 char __license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_tc_neigh_fib.c b/tools/testing/selftests/bpf/progs/test_tc_neigh_fib.c
new file mode 100644 (file)
index 0000000..d82ed34
--- /dev/null
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <linux/bpf.h>
+#include <linux/stddef.h>
+#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#ifndef ctx_ptr
+# define ctx_ptr(field)                (void *)(long)(field)
+#endif
+
+#define AF_INET 2
+#define AF_INET6 10
+
+static __always_inline int fill_fib_params_v4(struct __sk_buff *skb,
+                                             struct bpf_fib_lookup *fib_params)
+{
+       void *data_end = ctx_ptr(skb->data_end);
+       void *data = ctx_ptr(skb->data);
+       struct iphdr *ip4h;
+
+       if (data + sizeof(struct ethhdr) > data_end)
+               return -1;
+
+       ip4h = (struct iphdr *)(data + sizeof(struct ethhdr));
+       if ((void *)(ip4h + 1) > data_end)
+               return -1;
+
+       fib_params->family = AF_INET;
+       fib_params->tos = ip4h->tos;
+       fib_params->l4_protocol = ip4h->protocol;
+       fib_params->sport = 0;
+       fib_params->dport = 0;
+       fib_params->tot_len = bpf_ntohs(ip4h->tot_len);
+       fib_params->ipv4_src = ip4h->saddr;
+       fib_params->ipv4_dst = ip4h->daddr;
+
+       return 0;
+}
+
+static __always_inline int fill_fib_params_v6(struct __sk_buff *skb,
+                                             struct bpf_fib_lookup *fib_params)
+{
+       struct in6_addr *src = (struct in6_addr *)fib_params->ipv6_src;
+       struct in6_addr *dst = (struct in6_addr *)fib_params->ipv6_dst;
+       void *data_end = ctx_ptr(skb->data_end);
+       void *data = ctx_ptr(skb->data);
+       struct ipv6hdr *ip6h;
+
+       if (data + sizeof(struct ethhdr) > data_end)
+               return -1;
+
+       ip6h = (struct ipv6hdr *)(data + sizeof(struct ethhdr));
+       if ((void *)(ip6h + 1) > data_end)
+               return -1;
+
+       fib_params->family = AF_INET6;
+       fib_params->flowinfo = 0;
+       fib_params->l4_protocol = ip6h->nexthdr;
+       fib_params->sport = 0;
+       fib_params->dport = 0;
+       fib_params->tot_len = bpf_ntohs(ip6h->payload_len);
+       *src = ip6h->saddr;
+       *dst = ip6h->daddr;
+
+       return 0;
+}
+
+SEC("chk_egress") int tc_chk(struct __sk_buff *skb)
+{
+       void *data_end = ctx_ptr(skb->data_end);
+       void *data = ctx_ptr(skb->data);
+       __u32 *raw = data;
+
+       if (data + sizeof(struct ethhdr) > data_end)
+               return TC_ACT_SHOT;
+
+       return !raw[0] && !raw[1] && !raw[2] ? TC_ACT_SHOT : TC_ACT_OK;
+}
+
+static __always_inline int tc_redir(struct __sk_buff *skb)
+{
+       struct bpf_fib_lookup fib_params = { .ifindex = skb->ingress_ifindex };
+       __u8 zero[ETH_ALEN * 2];
+       int ret = -1;
+
+       switch (skb->protocol) {
+       case __bpf_constant_htons(ETH_P_IP):
+               ret = fill_fib_params_v4(skb, &fib_params);
+               break;
+       case __bpf_constant_htons(ETH_P_IPV6):
+               ret = fill_fib_params_v6(skb, &fib_params);
+               break;
+       }
+
+       if (ret)
+               return TC_ACT_OK;
+
+       ret = bpf_fib_lookup(skb, &fib_params, sizeof(fib_params), 0);
+       if (ret == BPF_FIB_LKUP_RET_NOT_FWDED || ret < 0)
+               return TC_ACT_OK;
+
+       __builtin_memset(&zero, 0, sizeof(zero));
+       if (bpf_skb_store_bytes(skb, 0, &zero, sizeof(zero), 0) < 0)
+               return TC_ACT_SHOT;
+
+       if (ret == BPF_FIB_LKUP_RET_NO_NEIGH) {
+               struct bpf_redir_neigh nh_params = {};
+
+               nh_params.nh_family = fib_params.family;
+               __builtin_memcpy(&nh_params.ipv6_nh, &fib_params.ipv6_dst,
+                                sizeof(nh_params.ipv6_nh));
+
+               return bpf_redirect_neigh(fib_params.ifindex, &nh_params,
+                                         sizeof(nh_params), 0);
+
+       } else if (ret == BPF_FIB_LKUP_RET_SUCCESS) {
+               void *data_end = ctx_ptr(skb->data_end);
+               struct ethhdr *eth = ctx_ptr(skb->data);
+
+               if (eth + 1 > data_end)
+                       return TC_ACT_SHOT;
+
+               __builtin_memcpy(eth->h_dest, fib_params.dmac, ETH_ALEN);
+               __builtin_memcpy(eth->h_source, fib_params.smac, ETH_ALEN);
+
+               return bpf_redirect(fib_params.ifindex, 0);
+       }
+
+       return TC_ACT_SHOT;
+}
+
+/* these are identical, but keep them separate for compatibility with the
+ * section names expected by test_tc_redirect.sh
+ */
+SEC("dst_ingress") int tc_dst(struct __sk_buff *skb)
+{
+       return tc_redir(skb);
+}
+
+SEC("src_ingress") int tc_src(struct __sk_buff *skb)
+{
+       return tc_redir(skb);
+}
+
+char __license[] SEC("license") = "GPL";
index 6d74825621401849549e1295e7df8eded0c67698..8868aa1ca9021ffd3533c78095bdfa7005ff1a64 100755 (executable)
@@ -24,8 +24,7 @@ command -v timeout >/dev/null 2>&1 || \
        { echo >&2 "timeout is not available"; exit 1; }
 command -v ping >/dev/null 2>&1 || \
        { echo >&2 "ping is not available"; exit 1; }
-command -v ping6 >/dev/null 2>&1 || \
-       { echo >&2 "ping6 is not available"; exit 1; }
+if command -v ping6 >/dev/null 2>&1; then PING6=ping6; else PING6=ping; fi
 command -v perl >/dev/null 2>&1 || \
        { echo >&2 "perl is not available"; exit 1; }
 command -v jq >/dev/null 2>&1 || \
@@ -152,7 +151,7 @@ netns_test_connectivity()
        echo -e "${TEST}: ${GREEN}PASS${NC}"
 
        TEST="ICMPv6 connectivity test"
-       ip netns exec ${NS_SRC} ping6 $PING_ARG ${IP6_DST}
+       ip netns exec ${NS_SRC} $PING6 $PING_ARG ${IP6_DST}
        if [ $? -ne 0 ]; then
                echo -e "${TEST}: ${RED}FAIL${NC}"
                exit 1
@@ -170,6 +169,7 @@ hex_mem_str()
 netns_setup_bpf()
 {
        local obj=$1
+       local use_forwarding=${2:-0}
 
        ip netns exec ${NS_FWD} tc qdisc add dev veth_src_fwd clsact
        ip netns exec ${NS_FWD} tc filter add dev veth_src_fwd ingress bpf da obj $obj sec src_ingress
@@ -179,6 +179,14 @@ netns_setup_bpf()
        ip netns exec ${NS_FWD} tc filter add dev veth_dst_fwd ingress bpf da obj $obj sec dst_ingress
        ip netns exec ${NS_FWD} tc filter add dev veth_dst_fwd egress  bpf da obj $obj sec chk_egress
 
+       if [ "$use_forwarding" -eq "1" ]; then
+               # bpf_fib_lookup() checks if forwarding is enabled
+               ip netns exec ${NS_FWD} sysctl -w net.ipv4.ip_forward=1
+               ip netns exec ${NS_FWD} sysctl -w net.ipv6.conf.veth_dst_fwd.forwarding=1
+               ip netns exec ${NS_FWD} sysctl -w net.ipv6.conf.veth_src_fwd.forwarding=1
+               return 0
+       fi
+
        veth_src=$(ip netns exec ${NS_FWD} cat /sys/class/net/veth_src_fwd/ifindex)
        veth_dst=$(ip netns exec ${NS_FWD} cat /sys/class/net/veth_dst_fwd/ifindex)
 
@@ -200,5 +208,9 @@ netns_setup_bpf test_tc_neigh.o
 netns_test_connectivity
 netns_cleanup
 netns_setup
+netns_setup_bpf test_tc_neigh_fib.o 1
+netns_test_connectivity
+netns_cleanup
+netns_setup
 netns_setup_bpf test_tc_peer.o
 netns_test_connectivity