Commit c77e572d authored by Lorenzo Bianconi's avatar Lorenzo Bianconi Committed by Daniel Borkmann
Browse files

selftests/bpf: Add selftest for bpf_xdp_flow_lookup kfunc



Introduce e2e selftest for bpf_xdp_flow_lookup kfunc through
xdp_flowtable utility.

Signed-off-by: default avatarLorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/b74393fb4539aecbbd5ac7883605f86a95fb0b6b.1719698275.git.lorenzo@kernel.org
parent 391bb659
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -83,6 +83,19 @@ CONFIG_NF_CONNTRACK_MARK=y
CONFIG_NF_CONNTRACK_ZONES=y
CONFIG_NF_DEFRAG_IPV4=y
CONFIG_NF_DEFRAG_IPV6=y
CONFIG_NF_TABLES=y
CONFIG_NF_TABLES_INET=y
CONFIG_NF_TABLES_NETDEV=y
CONFIG_NF_TABLES_IPV4=y
CONFIG_NF_TABLES_IPV6=y
CONFIG_NETFILTER_INGRESS=y
CONFIG_NF_FLOW_TABLE=y
CONFIG_NF_FLOW_TABLE_INET=y
CONFIG_NETFILTER_NETLINK=y
CONFIG_NFT_FLOW_OFFLOAD=y
CONFIG_IP_NF_IPTABLES=y
CONFIG_IP6_NF_IPTABLES=y
CONFIG_IP6_NF_FILTER=y
CONFIG_NF_NAT=y
CONFIG_RC_CORE=y
CONFIG_SECURITY=y
+168 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include <network_helpers.h>
#include <bpf/btf.h>
#include <linux/if_link.h>
#include <linux/udp.h>
#include <net/if.h>
#include <unistd.h>

#include "xdp_flowtable.skel.h"

#define TX_NETNS_NAME	"ns0"
#define RX_NETNS_NAME	"ns1"

#define TX_NAME		"v0"
#define FORWARD_NAME	"v1"
#define RX_NAME		"d0"

#define TX_MAC		"00:00:00:00:00:01"
#define FORWARD_MAC	"00:00:00:00:00:02"
#define RX_MAC		"00:00:00:00:00:03"
#define DST_MAC		"00:00:00:00:00:04"

#define TX_ADDR		"10.0.0.1"
#define FORWARD_ADDR	"10.0.0.2"
#define RX_ADDR		"20.0.0.1"
#define DST_ADDR	"20.0.0.2"

#define PREFIX_LEN	"8"
#define N_PACKETS	10
#define UDP_PORT	12345
#define UDP_PORT_STR	"12345"

static int send_udp_traffic(void)
{
	struct sockaddr_storage addr;
	int i, sock;

	if (make_sockaddr(AF_INET, DST_ADDR, UDP_PORT, &addr, NULL))
		return -EINVAL;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0)
		return sock;

	for (i = 0; i < N_PACKETS; i++) {
		unsigned char buf[] = { 0xaa, 0xbb, 0xcc };
		int n;

		n = sendto(sock, buf, sizeof(buf), MSG_NOSIGNAL | MSG_CONFIRM,
			   (struct sockaddr *)&addr, sizeof(addr));
		if (n != sizeof(buf)) {
			close(sock);
			return -EINVAL;
		}

		usleep(50000); /* 50ms */
	}
	close(sock);

	return 0;
}

void test_xdp_flowtable(void)
{
	struct xdp_flowtable *skel = NULL;
	struct nstoken *tok = NULL;
	int iifindex, stats_fd;
	__u32 value, key = 0;
	struct bpf_link *link;

	if (SYS_NOFAIL("nft -v")) {
		fprintf(stdout, "Missing required nft tool\n");
		test__skip();
		return;
	}

	SYS(out, "ip netns add " TX_NETNS_NAME);
	SYS(out, "ip netns add " RX_NETNS_NAME);

	tok = open_netns(RX_NETNS_NAME);
	if (!ASSERT_OK_PTR(tok, "setns"))
		goto out;

	SYS(out, "sysctl -qw net.ipv4.conf.all.forwarding=1");

	SYS(out, "ip link add " TX_NAME " type veth peer " FORWARD_NAME);
	SYS(out, "ip link set " TX_NAME " netns " TX_NETNS_NAME);
	SYS(out, "ip link set dev " FORWARD_NAME " address " FORWARD_MAC);
	SYS(out,
	    "ip addr add " FORWARD_ADDR "/" PREFIX_LEN " dev " FORWARD_NAME);
	SYS(out, "ip link set dev " FORWARD_NAME " up");

	SYS(out, "ip link add " RX_NAME " type dummy");
	SYS(out, "ip link set dev " RX_NAME " address " RX_MAC);
	SYS(out, "ip addr add " RX_ADDR "/" PREFIX_LEN " dev " RX_NAME);
	SYS(out, "ip link set dev " RX_NAME " up");

	/* configure the flowtable */
	SYS(out, "nft add table ip filter");
	SYS(out,
	    "nft add flowtable ip filter f { hook ingress priority 0\\; "
	    "devices = { " FORWARD_NAME ", " RX_NAME " }\\; }");
	SYS(out,
	    "nft add chain ip filter forward "
	    "{ type filter hook forward priority 0\\; }");
	SYS(out,
	    "nft add rule ip filter forward ip protocol udp th dport "
	    UDP_PORT_STR " flow add @f");

	/* Avoid ARP calls */
	SYS(out,
	    "ip -4 neigh add " DST_ADDR " lladdr " DST_MAC " dev " RX_NAME);

	close_netns(tok);
	tok = open_netns(TX_NETNS_NAME);
	if (!ASSERT_OK_PTR(tok, "setns"))
		goto out;

	SYS(out, "ip addr add " TX_ADDR "/" PREFIX_LEN " dev " TX_NAME);
	SYS(out, "ip link set dev " TX_NAME " address " TX_MAC);
	SYS(out, "ip link set dev " TX_NAME " up");
	SYS(out, "ip route add default via " FORWARD_ADDR);

	close_netns(tok);
	tok = open_netns(RX_NETNS_NAME);
	if (!ASSERT_OK_PTR(tok, "setns"))
		goto out;

	iifindex = if_nametoindex(FORWARD_NAME);
	if (!ASSERT_NEQ(iifindex, 0, "iifindex"))
		goto out;

	skel = xdp_flowtable__open_and_load();
	if (!ASSERT_OK_PTR(skel, "skel"))
		goto out;

	link = bpf_program__attach_xdp(skel->progs.xdp_flowtable_do_lookup,
				       iifindex);
	if (!ASSERT_OK_PTR(link, "prog_attach"))
		goto out;

	close_netns(tok);
	tok = open_netns(TX_NETNS_NAME);
	if (!ASSERT_OK_PTR(tok, "setns"))
		goto out;

	if (!ASSERT_OK(send_udp_traffic(), "send udp"))
		goto out;

	close_netns(tok);
	tok = open_netns(RX_NETNS_NAME);
	if (!ASSERT_OK_PTR(tok, "setns"))
		goto out;

	stats_fd = bpf_map__fd(skel->maps.stats);
	if (!ASSERT_OK(bpf_map_lookup_elem(stats_fd, &key, &value),
		       "bpf_map_update_elem stats"))
		goto out;

	ASSERT_GE(value, N_PACKETS - 2, "bpf_xdp_flow_lookup failed");
out:
	xdp_flowtable__destroy(skel);
	if (tok)
		close_netns(tok);
	SYS_NOFAIL("ip netns del " TX_NETNS_NAME);
	SYS_NOFAIL("ip netns del " RX_NETNS_NAME);
}
+144 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
#define BPF_NO_KFUNC_PROTOTYPES
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

#define ETH_P_IP	0x0800
#define ETH_P_IPV6	0x86dd
#define IP_MF		0x2000	/* "More Fragments" */
#define IP_OFFSET	0x1fff	/* "Fragment Offset" */
#define AF_INET		2
#define AF_INET6	10

struct bpf_flowtable_opts___local {
	s32 error;
};

struct flow_offload_tuple_rhash *
bpf_xdp_flow_lookup(struct xdp_md *, struct bpf_fib_lookup *,
		    struct bpf_flowtable_opts___local *, u32) __ksym;

struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__type(key, __u32);
	__type(value, __u32);
	__uint(max_entries, 1);
} stats SEC(".maps");

static bool xdp_flowtable_offload_check_iphdr(struct iphdr *iph)
{
	/* ip fragmented traffic */
	if (iph->frag_off & bpf_htons(IP_MF | IP_OFFSET))
		return false;

	/* ip options */
	if (iph->ihl * 4 != sizeof(*iph))
		return false;

	if (iph->ttl <= 1)
		return false;

	return true;
}

static bool xdp_flowtable_offload_check_tcp_state(void *ports, void *data_end,
						  u8 proto)
{
	if (proto == IPPROTO_TCP) {
		struct tcphdr *tcph = ports;

		if (tcph + 1 > data_end)
			return false;

		if (tcph->fin || tcph->rst)
			return false;
	}

	return true;
}

SEC("xdp.frags")
int xdp_flowtable_do_lookup(struct xdp_md *ctx)
{
	void *data_end = (void *)(long)ctx->data_end;
	struct bpf_flowtable_opts___local opts = {};
	struct flow_offload_tuple_rhash *tuplehash;
	struct bpf_fib_lookup tuple = {
		.ifindex = ctx->ingress_ifindex,
	};
	void *data = (void *)(long)ctx->data;
	struct ethhdr *eth = data;
	struct flow_ports *ports;
	__u32 *val, key = 0;

	if (eth + 1 > data_end)
		return XDP_DROP;

	switch (eth->h_proto) {
	case bpf_htons(ETH_P_IP): {
		struct iphdr *iph = data + sizeof(*eth);

		ports = (struct flow_ports *)(iph + 1);
		if (ports + 1 > data_end)
			return XDP_PASS;

		/* sanity check on ip header */
		if (!xdp_flowtable_offload_check_iphdr(iph))
			return XDP_PASS;

		if (!xdp_flowtable_offload_check_tcp_state(ports, data_end,
							   iph->protocol))
			return XDP_PASS;

		tuple.family		= AF_INET;
		tuple.tos		= iph->tos;
		tuple.l4_protocol	= iph->protocol;
		tuple.tot_len		= bpf_ntohs(iph->tot_len);
		tuple.ipv4_src		= iph->saddr;
		tuple.ipv4_dst		= iph->daddr;
		tuple.sport		= ports->source;
		tuple.dport		= ports->dest;
		break;
	}
	case bpf_htons(ETH_P_IPV6): {
		struct in6_addr *src = (struct in6_addr *)tuple.ipv6_src;
		struct in6_addr *dst = (struct in6_addr *)tuple.ipv6_dst;
		struct ipv6hdr *ip6h = data + sizeof(*eth);

		ports = (struct flow_ports *)(ip6h + 1);
		if (ports + 1 > data_end)
			return XDP_PASS;

		if (ip6h->hop_limit <= 1)
			return XDP_PASS;

		if (!xdp_flowtable_offload_check_tcp_state(ports, data_end,
							   ip6h->nexthdr))
			return XDP_PASS;

		tuple.family		= AF_INET6;
		tuple.l4_protocol	= ip6h->nexthdr;
		tuple.tot_len		= bpf_ntohs(ip6h->payload_len);
		*src			= ip6h->saddr;
		*dst			= ip6h->daddr;
		tuple.sport		= ports->source;
		tuple.dport		= ports->dest;
		break;
	}
	default:
		return XDP_PASS;
	}

	tuplehash = bpf_xdp_flow_lookup(ctx, &tuple, &opts, sizeof(opts));
	if (!tuplehash)
		return XDP_PASS;

	val = bpf_map_lookup_elem(&stats, &key);
	if (val)
		__sync_add_and_fetch(val, 1);

	return XDP_PASS;
}

char _license[] SEC("license") = "GPL";