New test using null device to test filtering with BPF.

Signed-off-by: Stephen Hemminger <[email protected]>
---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 307 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 361 insertions(+)
 create mode 100644 app/test/bpf/filter.c

diff --git a/app/test/bpf/filter.c b/app/test/bpf/filter.c
new file mode 100644
index 0000000000..d47233a47a
--- /dev/null
+++ b/app/test/bpf/filter.c
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * BPF TX filter program for testing rte_bpf_eth_tx_elf_load
+ */
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long uint64_t;
+
+/*
+ * Simple TX filter that accepts TCP packets
+ *
+ * BPF TX programs receive pointer to data and should return:
+ *   0 = drop packet
+ *   non-zero = rx/tx packet
+ *
+ * This filter checks:
+ * 1. Packet is IPv4
+ * 2. Protocol is TCP (IPPROTO_TCP = 6)
+ */
+__attribute__((section("filter"), used))
+uint64_t
+test_filter(void *pkt)
+{
+       uint8_t *data = pkt;
+
+       /* Read version and IHL (first byte of IP header) */
+       uint8_t version_ihl = data[14];
+
+       /* Check IPv4 version (upper 4 bits should be 4) */
+       if ((version_ihl >> 4) != 4)
+               return 0;
+
+       /* Protocol field (byte 9 of IP header) must be TCP (6) */
+       uint8_t proto = data[14 + 9];
+       return (proto == 6);
+}
+
+__attribute__((section("drop"), used))
+uint64_t
+test_drop(void *pkt)
+{
+       (void)pkt;
+       return 0;
+}
+
+__attribute__((section("allow"), used))
+uint64_t
+test_allow(void *pkt)
+{
+       (void)pkt;
+       return 1;
+}
diff --git a/app/test/bpf/meson.build b/app/test/bpf/meson.build
index b4f54aa976..19fec05521 100644
--- a/app/test/bpf/meson.build
+++ b/app/test/bpf/meson.build
@@ -32,6 +32,7 @@ cflags += '-DTEST_BPF_ELF_LOAD'
 # BPF sources to compile
 bpf_progs = {
     'load' : 'test_bpf_load',
+    'filter' : 'test_bpf_filter',
 }
 
 foreach bpf_src, bpf_hdr : bpf_progs
diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
index a80f41a543..24d18f9d33 100644
--- a/app/test/test_bpf.c
+++ b/app/test/test_bpf.c
@@ -16,6 +16,12 @@
 #include <rte_byteorder.h>
 #include <rte_errno.h>
 #include <rte_bpf.h>
+#include <rte_ethdev.h>
+#include <rte_bpf_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_tcp.h>
 
 #include "test.h"
 
@@ -3421,6 +3427,291 @@ test_bpf_elf_load(void)
        printf("%s: ELF load test passed\n", __func__);
        return TEST_SUCCESS;
 }
+
+#include "test_bpf_filter.h"
+
+#define BPF_TEST_BURST         128
+#define BPF_TEST_POOLSIZE      256 /* at least 2x burst */
+#define BPF_TEST_PKT_LEN       64 /* Ether + IP + TCP */
+
+static int null_vdev_setup(const char *name, uint16_t *port, struct 
rte_mempool *pool)
+{
+       int ret;
+
+       /* Make a null device */
+       ret = rte_vdev_init(name, NULL);
+       TEST_ASSERT(ret == 0, "rte_vdev_init(%s) failed: %d", name, ret);
+
+       ret = rte_eth_dev_get_port_by_name(name, port);
+       TEST_ASSERT(ret == 0, "failed to get port id for %s: %d", name, ret);
+
+       struct rte_eth_conf conf = { };
+       ret = rte_eth_dev_configure(*port, 1, 1, &conf);
+       TEST_ASSERT(ret == 0, "failed to configure port %u: %d", *port, ret);
+
+       struct rte_eth_txconf txconf = { };
+       ret = rte_eth_tx_queue_setup(*port, 0, BPF_TEST_BURST, SOCKET_ID_ANY, 
&txconf);
+       TEST_ASSERT(ret == 0, "failed to setup tx queue port %u: %d", *port, 
ret);
+
+       struct rte_eth_rxconf rxconf = { };
+       ret = rte_eth_rx_queue_setup(*port, 0, BPF_TEST_BURST, SOCKET_ID_ANY,
+                                    &rxconf, pool);
+       TEST_ASSERT(ret == 0, "failed to setup rx queue port %u: %d", *port, 
ret);
+
+       ret = rte_eth_dev_start(*port);
+       TEST_ASSERT(ret == 0, "failed to start port %u: %d", *port, ret);
+
+       return 0;
+}
+
+static unsigned int
+setup_mbufs(struct rte_mbuf *burst[], unsigned int n)
+{
+       struct rte_ether_hdr eh = {
+               .ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+       };
+       const struct rte_ipv4_hdr iph = {
+               .version_ihl = RTE_IPV4_VHL_DEF,
+               .total_length = rte_cpu_to_be_16(BPF_TEST_PKT_LEN - sizeof(eh)),
+               .time_to_live = IPDEFTTL,
+               .src_addr = rte_cpu_to_be_32(ip_src_addr),
+               .dst_addr = rte_cpu_to_be_32(ip_dst_addr),
+       };
+       unsigned int tcp_count = 0;
+
+       rte_eth_random_addr(eh.dst_addr.addr_bytes);
+
+       for (unsigned int i = 0; i < n; i++) {
+               struct rte_mbuf *mb = burst[i];
+
+               /* Setup Ethernet header */
+               *rte_pktmbuf_mtod(mb, struct rte_ether_hdr *) = eh;
+
+               /* Setup IP header */
+               struct rte_ipv4_hdr *ip
+                       = rte_pktmbuf_mtod_offset(mb, struct rte_ipv4_hdr *, 
sizeof(eh));
+               *ip = iph;
+
+               if (rte_rand() & 1) {
+                       struct rte_udp_hdr *udp
+                               = rte_pktmbuf_mtod_offset(mb, struct 
rte_udp_hdr *,
+                                                         sizeof(eh) + 
sizeof(iph));
+
+                       ip->next_proto_id = IPPROTO_UDP;
+                       *udp = (struct rte_udp_hdr) {
+                               .src_port = rte_cpu_to_be_16(9),        /* 
discard */
+                               .dst_port = rte_cpu_to_be_16(9),        /* 
discard */
+                               .dgram_len = BPF_TEST_PKT_LEN - sizeof(eh) - 
sizeof(iph),
+                       };
+
+               } else {
+                       struct rte_tcp_hdr *tcp
+                               = rte_pktmbuf_mtod_offset(mb, struct 
rte_tcp_hdr *,
+                                                         sizeof(eh) + 
sizeof(iph));
+
+                       ip->next_proto_id = IPPROTO_TCP;
+                       *tcp = (struct rte_tcp_hdr) {
+                               .src_port = rte_cpu_to_be_16(9),        /* 
discard */
+                               .dst_port = rte_cpu_to_be_16(9),        /* 
discard */
+                               .tcp_flags = RTE_TCP_RST_FLAG,
+                       };
+                       ++tcp_count;
+               }
+       }
+
+       return tcp_count;
+}
+
+static int bpf_tx_test(uint16_t port, const char *tmpfile, struct rte_mempool 
*pool,
+                      const char *section, uint32_t flags)
+{
+       const struct rte_bpf_prm prm = {
+               .prog_arg = {
+                       .type = RTE_BPF_ARG_PTR,
+                       .size = sizeof(struct rte_mbuf),
+               },
+       };
+       int ret;
+
+       struct rte_mbuf *pkts[BPF_TEST_BURST] = { };
+       ret = rte_pktmbuf_alloc_bulk(pool, pkts, BPF_TEST_BURST);
+       TEST_ASSERT(ret == 0, "failed to allocate mbufs");
+
+       uint16_t expect = setup_mbufs(pkts, BPF_TEST_BURST);
+
+       /* Try to load BPF TX program from temp file */
+       ret = rte_bpf_eth_tx_elf_load(port, 0, &prm, tmpfile, section, flags);
+       TEST_ASSERT(ret == 0, "failed to load BPF filter from temp file %s: %d",
+                   tmpfile, ret);
+
+       uint16_t sent = rte_eth_tx_burst(port, 0, pkts, BPF_TEST_BURST);
+       TEST_ASSERT_EQUAL(sent, expect, "rte_eth_tx_burst returned: %u expected 
%u",
+                         sent, expect);
+
+       /* The unsent packets should be dropped */
+       rte_pktmbuf_free_bulk(pkts + sent, BPF_TEST_BURST - sent);
+
+       /* Pool should have same number of packets avail */
+       unsigned int avail = rte_mempool_avail_count(pool);
+       TEST_ASSERT_EQUAL(avail, BPF_TEST_POOLSIZE,
+                         "Mempool available %u != %u leaks?", avail, 
BPF_TEST_POOLSIZE);
+
+       rte_bpf_eth_tx_unload(port, 0);
+       return TEST_SUCCESS;
+}
+
+/* Test loading a transmit filter which only allows IPv4 packets */
+static int
+test_bpf_elf_tx_load(void)
+{
+       static const char null_dev[] = "net_null_bpf0";
+       char *tmpfile = NULL;
+       struct rte_mempool *mb_pool = NULL;
+       uint16_t port = UINT16_MAX;
+       int ret;
+
+       printf("%s start\n", __func__);
+
+       /* Make a pool for packets */
+       mb_pool = rte_pktmbuf_pool_create("bpf_tx_test_pool", BPF_TEST_POOLSIZE,
+                                         0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
+                                         SOCKET_ID_ANY);
+
+       ret = null_vdev_setup(null_dev, &port, mb_pool);
+       if (ret != 0)
+               goto fail;
+
+       /* Create temp file from embedded BPF object */
+       tmpfile = create_temp_bpf_file(app_test_bpf_filter_o, 
app_test_bpf_filter_o_len, "tx");
+       if (tmpfile == NULL)
+               goto fail;
+
+       /* Do test with VM */
+       ret = bpf_tx_test(port, tmpfile, mb_pool, "filter", 0);
+       if (ret != 0)
+               goto fail;
+
+       /* Repeat with JIT */
+       ret = bpf_tx_test(port, tmpfile, mb_pool, "filter", RTE_BPF_ETH_F_JIT);
+       if (ret == 0)
+               printf("%s: TX ELF load test passed\n", __func__);
+
+fail:
+       if (tmpfile) {
+               unlink(tmpfile);
+               free(tmpfile);
+       }
+
+       if (port != UINT16_MAX)
+               rte_vdev_uninit(null_dev);
+
+       rte_mempool_free(mb_pool);
+
+       return ret == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+/* Test loading a receive filter */
+static int bpf_rx_test(uint16_t port, const char *tmpfile, struct rte_mempool 
*pool,
+                      const char *section, uint32_t flags, uint16_t expected)
+{
+       struct rte_mbuf *pkts[BPF_TEST_BURST];
+       const struct rte_bpf_prm prm = {
+               .prog_arg = {
+                       .type = RTE_BPF_ARG_PTR,
+                       .size = sizeof(struct rte_mbuf),
+               },
+       };
+       int ret;
+
+       /* Load BPF program to drop all packets */
+       ret = rte_bpf_eth_rx_elf_load(port, 0, &prm, tmpfile, section, flags);
+       TEST_ASSERT(ret == 0, "failed to load BPF filter from temp file %s: %d",
+                   tmpfile, ret);
+
+       uint16_t rcvd = rte_eth_rx_burst(port, 0, pkts, BPF_TEST_BURST);
+       TEST_ASSERT_EQUAL(rcvd, expected,
+                         "rte_eth_rx_burst returned: %u expect: %u", rcvd, 
expected);
+
+       /* Drop the received packets */
+       rte_pktmbuf_free_bulk(pkts, rcvd);
+
+       rte_bpf_eth_rx_unload(port, 0);
+
+       /* Pool should now be full */
+       unsigned int avail = rte_mempool_avail_count(pool);
+       TEST_ASSERT_EQUAL(avail, BPF_TEST_POOLSIZE,
+                         "Mempool available %u != %u leaks?", avail, 
BPF_TEST_POOLSIZE);
+
+       return TEST_SUCCESS;
+}
+
+/* Test loading a receive filters, first with drop all and then with allow all 
packets */
+static int
+test_bpf_elf_rx_load(void)
+{
+       static const char null_dev[] = "net_null_bpf0";
+       struct rte_mempool *pool = NULL;
+       char *tmpfile = NULL;
+       uint16_t port;
+       int ret;
+
+       printf("%s start\n", __func__);
+
+       /* Make a pool for packets */
+       pool = rte_pktmbuf_pool_create("bpf_rx_test_pool", 2 * BPF_TEST_BURST,
+                                         0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
+                                         SOCKET_ID_ANY);
+       TEST_ASSERT(pool != NULL, "failed to create mempool");
+
+       ret = null_vdev_setup(null_dev, &port, pool);
+       if (ret != 0)
+               goto fail;
+
+       /* Create temp file from embedded BPF object */
+       tmpfile = create_temp_bpf_file(app_test_bpf_filter_o, 
app_test_bpf_filter_o_len, "rx");
+       if (tmpfile == NULL)
+               goto fail;
+
+       /* Do test with VM */
+       ret = bpf_rx_test(port, tmpfile, pool, "drop", 0, 0);
+       if (ret != 0)
+               goto fail;
+
+       /* Repeat with JIT */
+       ret = bpf_rx_test(port, tmpfile, pool, "drop", RTE_BPF_ETH_F_JIT, 0);
+       if (ret != 0)
+               goto fail;
+
+       /* Repeat with allow all */
+       ret = bpf_rx_test(port, tmpfile, pool, "allow", 0, BPF_TEST_BURST);
+       if (ret != 0)
+               goto fail;
+
+       /* Repeat with JIT */
+       ret = bpf_rx_test(port, tmpfile, pool, "allow", RTE_BPF_ETH_F_JIT, 
BPF_TEST_BURST);
+       if (ret != 0)
+               goto fail;
+
+       printf("%s: RX ELF load test passed\n", __func__);
+
+       /* The filter should free the mbufs */
+       unsigned int avail = rte_mempool_avail_count(pool);
+       TEST_ASSERT_EQUAL(avail, BPF_TEST_POOLSIZE,
+                         "Mempool available %u != %u leaks?", avail, 
BPF_TEST_POOLSIZE);
+
+fail:
+       if (tmpfile) {
+               unlink(tmpfile);
+               free(tmpfile);
+       }
+
+       if (port != UINT16_MAX)
+               rte_vdev_uninit(null_dev);
+
+       rte_mempool_free(pool);
+
+       return ret == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
 #else
 
 static int
@@ -3430,9 +3721,25 @@ test_bpf_elf_load(void)
        return TEST_SKIPPED;
 }
 
+static int
+test_bpf_elf_tx_load(void)
+{
+       printf("BPF compile not supported, skipping Tx test\n");
+       return TEST_SKIPPED;
+}
+
+static int
+test_bpf_elf_rx_load(void)
+{
+       printf("BPF compile not supported, skipping Tx test\n");
+       return TEST_SKIPPED;
+}
+
 #endif /* !TEST_BPF_ELF_LOAD */
 
 REGISTER_FAST_TEST(bpf_elf_load_autotest, true, true, test_bpf_elf_load);
+REGISTER_FAST_TEST(bpf_eth_tx_elf_load_autotest, true, true, 
test_bpf_elf_tx_load);
+REGISTER_FAST_TEST(bpf_eth_rx_elf_load_autotest, true, true, 
test_bpf_elf_rx_load);
 
 #ifndef RTE_HAS_LIBPCAP
 
-- 
2.51.0

Reply via email to