GPL/HostIsolation/TcFilter/BPFTcFilterTests.cpp (465 lines of code) (raw):

// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause /* * Copyright (C) 2021 Elasticsearch BV * * This software is dual-licensed under the BSD 2-Clause and GPL v2 licenses. * You may choose either one of them if you use this software. */ #include "Kerneldefs.h" #include <bpf/bpf.h> #include <bpf/bpf_endian.h> #include <bpf/libbpf.h> #include <gtest/gtest.h> #include <sched.h> #include <sys/resource.h> #include "TcFilterdefs.h" #define OBJECT_PATH_ENV_VAR "ELASTIC_EBPF_TC_FILTER_OBJ_PATH" #define DEFAULT_OBJECT_PATH "TcFilter.bpf.o" #define CLASSIFIER_SECTION_NAME "classifier" #define MAGIC_BYTES 123 #define __packed __attribute__((__packed__)) struct packet_v4 { struct ethhdr eth; struct iphdr iph; struct tcphdr tcp; } __packed; struct packet_v4_udp { struct ethhdr eth; struct iphdr iph; struct udphdr udp; } __packed; struct packet_v6 { struct ethhdr eth; struct ipv6hdr iph; struct tcphdr tcp; } __packed; class BPFTcFilterTests : public ::testing::Test { protected: struct bpf_object *m_obj = nullptr; int m_prog_fd = -1; virtual void SetUp() override { struct bpf_program *prog = nullptr; char *object_path_env = getenv(OBJECT_PATH_ENV_VAR); int err = 0; m_obj = object_path_env == NULL ? bpf_object__open(DEFAULT_OBJECT_PATH) : bpf_object__open(object_path_env); if (libbpf_get_error(m_obj)) { FAIL() << "Cannot open ELF object to test, you can pass a custom one with the " << OBJECT_PATH_ENV_VAR << " environment variable"; } prog = bpf_object__find_program_by_name(m_obj, CLASSIFIER_SECTION_NAME); ASSERT_FALSE(prog == NULL); bpf_program__set_type(prog, BPF_PROG_TYPE_SCHED_CLS); err = bpf_object__load(m_obj); if (err) { FAIL() << "Could not load the bpf program, please check your permissions"; return; } m_prog_fd = bpf_program__fd(prog); } virtual void TearDown() override { int err = 0; struct bpf_map *allowed_ips_map = bpf_object__find_map_by_name(m_obj, "allowed_IPs"); if (!allowed_ips_map) { FAIL() << "Could not find the allowed_IPs map"; return; } err = bpf_map__unpin(allowed_ips_map, bpf_map__get_pin_path(allowed_ips_map)); if (err != 0) { FAIL() << "Could not unpin the allowed_IPs map"; return; } struct bpf_map *allowed_subnets_map = bpf_object__find_map_by_name(m_obj, "allowed_subnets"); if (!allowed_subnets_map) { FAIL() << "Could not find the allowed_subnets map"; return; } err = bpf_map__unpin(allowed_subnets_map, bpf_map__get_pin_path(allowed_subnets_map)); if (err != 0) { FAIL() << "Could not unpin the allowed_subnets map"; return; } bpf_object__close(m_obj); m_prog_fd = -1; } static void SetUpTestSuite() { struct rlimit rinf; rinf = {RLIM_INFINITY, RLIM_INFINITY}; if (setrlimit(RLIMIT_MEMLOCK, &rinf) == -EPERM) { FAIL() << "setrlimit failed, running the BPFTcFilterTests suite requires root permissions"; } } }; TEST_F(BPFTcFilterTests, TestAllowArpPacket) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_ARP); struct iphdr iph { }; struct tcphdr tcp { }; struct packet_v4 pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.tcp = tcp; struct __sk_buff skb = {}; opts.ctx_in = &skb; opts.ctx_size_in = sizeof(skb); opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); opts.ctx_out = &skb; opts.ctx_size_out = sizeof(skb); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)ALLOW_PACKET); } TEST_F(BPFTcFilterTests, TestDropUnsupportedPackets) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_LOOP); struct iphdr iph { }; struct tcphdr tcp { }; struct packet_v4 pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.tcp = tcp; struct __sk_buff skb = {}; opts.ctx_in = &skb; opts.ctx_size_in = sizeof(skb); opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); opts.ctx_out = &skb; opts.ctx_size_out = sizeof(skb); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)DROP_PACKET); } TEST_F(BPFTcFilterTests, TestDropIPV6Packets) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct ipv6hdr iph { }; iph.version = 6; struct tcphdr tcp { }; struct packet_v6 pkt_v6 { }; pkt_v6.eth = eth; pkt_v6.iph = iph; pkt_v6.tcp = tcp; struct __sk_buff skb = {}; opts.ctx_in = &skb; opts.ctx_size_in = sizeof(skb); opts.data_in = &pkt_v6; opts.data_size_in = sizeof(pkt_v6); opts.ctx_out = &skb; opts.ctx_size_out = sizeof(skb); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)DROP_PACKET); } TEST_F(BPFTcFilterTests, TestDropInvalidHeaderLength) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 10; struct tcphdr tcp { }; struct packet_v4 pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.tcp = tcp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)DROP_PACKET); } TEST_F(BPFTcFilterTests, TestDropFragmentedPacket) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.frag_off |= PCKT_FRAGMENTED; struct tcphdr tcp { }; struct packet_v4 pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.tcp = tcp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)DROP_PACKET); } TEST_F(BPFTcFilterTests, TestAllowUDPPacketDNSPortSource) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_UDP; struct udphdr udp { }; udp.source = __bpf_htons(53); struct packet_v4_udp pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.udp = udp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)ALLOW_PACKET); } TEST_F(BPFTcFilterTests, TestAllowUDPPacketDNSPortDest) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_UDP; struct udphdr udp { }; udp.dest = __bpf_htons(53); struct packet_v4_udp pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.udp = udp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)ALLOW_PACKET); } TEST_F(BPFTcFilterTests, TestAllowUDPPacketDHCPClient) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_UDP; struct udphdr udp { }; udp.source = __bpf_htons(DHCP_SERVER_PORT); udp.dest = __bpf_htons(DHCP_CLIENT_PORT); struct packet_v4_udp pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.udp = udp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)ALLOW_PACKET); } TEST_F(BPFTcFilterTests, TestAllowUDPPacketDHCPServer) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_UDP; struct udphdr udp { }; udp.source = __bpf_htons(DHCP_CLIENT_PORT); udp.dest = __bpf_htons(DHCP_SERVER_PORT); struct packet_v4_udp pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.udp = udp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)ALLOW_PACKET); } TEST_F(BPFTcFilterTests, TestDropUnknownUDPPackets) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_UDP; struct udphdr udp { }; struct packet_v4_udp pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.udp = udp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)DROP_PACKET); } TEST_F(BPFTcFilterTests, TestDropUnknownTCPDestination) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_TCP; struct tcphdr tcp { }; struct packet_v4 pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.tcp = tcp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)DROP_PACKET); } TEST_F(BPFTcFilterTests, TestAllowTCPAddressInAllowedIPs) { int allowed_ips_map_fd; struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_TCP; iph.daddr = __bpf_htonl(0x0A010203); // 10.1.2.3 allowed_ips_map_fd = bpf_object__find_map_fd_by_name(m_obj, "allowed_IPs"); int val = 1; int ret = bpf_map_update_elem(allowed_ips_map_fd, &iph.daddr, &val, BPF_ANY); ASSERT_EQ(ret, 0); struct tcphdr tcp { }; struct packet_v4 pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.tcp = tcp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)ALLOW_PACKET); } TEST_F(BPFTcFilterTests, TestDropUnknownICMPDestination) { struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_ICMP; struct tcphdr tcp { }; struct packet_v4 pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.tcp = tcp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)DROP_PACKET); } TEST_F(BPFTcFilterTests, TestAllowICMPAddressInAllowedIPs) { int allowed_ips_map_fd; struct bpf_test_run_opts opts = {}; opts.sz = sizeof(opts); struct ethhdr eth { }; eth.h_proto = __bpf_htons(ETH_P_IP); struct iphdr iph { }; iph.version = 4; iph.ihl = 5; iph.protocol = IPPROTO_ICMP; iph.daddr = __bpf_htonl(0x0A010203); // 10.1.2.3 allowed_ips_map_fd = bpf_object__find_map_fd_by_name(m_obj, "allowed_IPs"); int val = 1; int ret = bpf_map_update_elem(allowed_ips_map_fd, &iph.daddr, &val, BPF_ANY); ASSERT_EQ(ret, 0); struct tcphdr tcp { }; struct packet_v4 pkt_v4 { }; pkt_v4.eth = eth; pkt_v4.iph = iph; pkt_v4.tcp = tcp; opts.data_in = &pkt_v4; opts.data_size_in = sizeof(pkt_v4); int err = bpf_prog_test_run_opts(m_prog_fd, &opts); EXPECT_EQ(err, 0); EXPECT_EQ(opts.retval, (unsigned int)ALLOW_PACKET); }