Merge branch 'selftests-bpf-migrate-test_xdp_redirect_multi-sh-to-test_progs'

Bastien Curutchet says:

====================
This patch series continues the work to migrate the *.sh tests into
prog_tests framework.

test_xdp_redirect_multi.sh tests the XDP redirections done through
bpf_redirect_map().

This is already partly covered by test_xdp_veth.c that already tests
map redirections at XDP level. What isn't covered yet by test_xdp_veth is
the use of the broadcast flags (BPF_F_BROADCAST or BPF_F_EXCLUDE_INGRESS)
and XDP egress programs.

Hence, this patch series add test cases to test_xdp_veth.c to get rid of
the test_xdp_redirect_multi.sh:
 - PATCH 1 & 2 Rework test_xdp_veth.c to avoid using the root namespace
 - PATCH 3 and 4 cover the broadcast flags
 - PATCH 5 covers the XDP egress programs

NOTE: While working on this iteration I ran into a memory leak in
net/core/rtnetlink.c that leads to oom-kill when running ./test_progs in
a loop. This leak has been fixed by commit 1438f5d07b ("rtnetlink:
fix netns leak with rtnl_setlink()") in the net tree.
====================

Link: https://patch.msgid.link/20250212-redirect-multi-v5-0-fd0d39fca6e6@bootlin.com
Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
This commit is contained in:
Martin KaFai Lau 2025-02-18 13:56:34 -08:00
commit 0fc6025c95
6 changed files with 493 additions and 517 deletions

View File

@ -100,7 +100,6 @@ TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c)
# Order correspond to 'make run_tests' order
TEST_PROGS := test_kmod.sh \
test_xdp_redirect_multi.sh \
test_tunnel.sh \
test_lwt_seg6local.sh \
test_lirc_mode2.sh \
@ -135,7 +134,6 @@ TEST_GEN_PROGS_EXTENDED = \
veristat \
xdp_features \
xdp_hw_metadata \
xdp_redirect_multi \
xdp_synproxy \
xdping \
xskxceiver

View File

@ -9,7 +9,11 @@
* | veth11 | | veth22 | | veth33 |
* ----|----- -----|---- -----|----
* | | |
* veth1 veth2 veth3
* ----|------------------|----------------|----
* | veth1 veth2 veth3 |
* | |
* | NSO |
* ---------------------------------------------
*
* Test cases:
* - [test_xdp_veth_redirect] : ping veth33 from veth11
@ -24,6 +28,25 @@
* | | | | | |
* | ------------------ ------------------ |
* -----------------------------------------
*
* - [test_xdp_veth_broadcast_redirect]: broadcast from veth11
* - IPv4 ping : BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS
* -> echo request received by all except veth11
* - IPv4 ping : BPF_F_BROADCAST
* -> echo request received by all veth
* - [test_xdp_veth_egress]:
* - all src mac should be the magic mac
*
* veth11 veth22 veth33
* (XDP_PASS) (XDP_PASS) (XDP_PASS)
* | | |
* | | |
* veth1 veth2 veth3
* (XDP_REDIRECT) (XDP_REDIRECT) (XDP_REDIRECT)
* | ^ ^
* | | |
* ----------------------------------------
*
*/
#define _GNU_SOURCE
@ -32,6 +55,7 @@
#include "network_helpers.h"
#include "xdp_dummy.skel.h"
#include "xdp_redirect_map.skel.h"
#include "xdp_redirect_multi_kern.skel.h"
#include "xdp_tx.skel.h"
#include <uapi/linux/if_link.h>
@ -40,6 +64,7 @@
#define IP_MAX_LEN 16
#define IP_SRC "10.1.1.11"
#define IP_DST "10.1.1.33"
#define IP_NEIGH "10.1.1.253"
#define PROG_NAME_MAX_LEN 128
#define NS_NAME_MAX_LEN 32
@ -51,27 +76,35 @@ struct veth_configuration {
char remote_addr[IP_MAX_LEN]; /* IP address of the remote veth */
};
static const struct veth_configuration default_config[VETH_PAIRS_COUNT] = {
struct net_configuration {
char ns0_name[NS_NAME_MAX_LEN];
struct veth_configuration veth_cfg[VETH_PAIRS_COUNT];
};
static const struct net_configuration default_config = {
.ns0_name = "ns0-",
{
.local_veth = "veth1-",
.remote_veth = "veth11",
.next_veth = 1,
.remote_addr = IP_SRC,
.namespace = "ns-veth11-"
},
{
.local_veth = "veth2-",
.remote_veth = "veth22",
.next_veth = 2,
.remote_addr = "",
.namespace = "ns-veth22-"
},
{
.local_veth = "veth3-",
.remote_veth = "veth33",
.next_veth = 0,
.remote_addr = IP_DST,
.namespace = "ns-veth33-"
{
.local_veth = "veth1-",
.remote_veth = "veth11",
.next_veth = 1,
.remote_addr = IP_SRC,
.namespace = "ns-veth11-"
},
{
.local_veth = "veth2-",
.remote_veth = "veth22",
.next_veth = 2,
.remote_addr = "",
.namespace = "ns-veth22-"
},
{
.local_veth = "veth3-",
.remote_veth = "veth33",
.next_veth = 0,
.remote_addr = IP_DST,
.namespace = "ns-veth33-"
}
}
};
@ -83,7 +116,7 @@ struct prog_configuration {
};
static int attach_programs_to_veth_pair(struct bpf_object **objs, size_t nb_obj,
struct veth_configuration *net_config,
struct net_configuration *net_config,
struct prog_configuration *prog, int index)
{
struct bpf_program *local_prog, *remote_prog;
@ -106,7 +139,7 @@ static int attach_programs_to_veth_pair(struct bpf_object **objs, size_t nb_obj,
if (!ASSERT_OK_PTR(remote_prog, "find remote program"))
return -1;
interface = if_nametoindex(net_config[index].local_veth);
interface = if_nametoindex(net_config->veth_cfg[index].local_veth);
if (!ASSERT_NEQ(interface, 0, "non zero interface index"))
return -1;
@ -115,11 +148,11 @@ static int attach_programs_to_veth_pair(struct bpf_object **objs, size_t nb_obj,
if (!ASSERT_OK(ret, "attach xdp program to local veth"))
return -1;
nstoken = open_netns(net_config[index].namespace);
nstoken = open_netns(net_config->veth_cfg[index].namespace);
if (!ASSERT_OK_PTR(nstoken, "switch to remote veth namespace"))
return -1;
interface = if_nametoindex(net_config[index].remote_veth);
interface = if_nametoindex(net_config->veth_cfg[index].remote_veth);
if (!ASSERT_NEQ(interface, 0, "non zero interface index")) {
close_netns(nstoken);
return -1;
@ -136,55 +169,60 @@ static int attach_programs_to_veth_pair(struct bpf_object **objs, size_t nb_obj,
return 0;
}
static int create_network(struct veth_configuration *net_config)
static int create_network(struct net_configuration *net_config)
{
struct nstoken *nstoken = NULL;
int i, err;
memcpy(net_config, default_config, VETH_PAIRS_COUNT * sizeof(struct veth_configuration));
memcpy(net_config, &default_config, sizeof(struct net_configuration));
/* Create unique namespaces */
err = append_tid(net_config->ns0_name, NS_NAME_MAX_LEN);
if (!ASSERT_OK(err, "append TID to ns0 name"))
goto fail;
SYS(fail, "ip netns add %s", net_config->ns0_name);
/* First create and configure all interfaces */
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
err = append_tid(net_config[i].namespace, NS_NAME_MAX_LEN);
err = append_tid(net_config->veth_cfg[i].namespace, NS_NAME_MAX_LEN);
if (!ASSERT_OK(err, "append TID to ns name"))
return -1;
err = append_tid(net_config[i].local_veth, VETH_NAME_MAX_LEN);
if (!ASSERT_OK(err, "append TID to local veth name"))
return -1;
SYS(fail, "ip netns add %s", net_config[i].namespace);
SYS(fail, "ip link add %s type veth peer name %s netns %s",
net_config[i].local_veth, net_config[i].remote_veth, net_config[i].namespace);
SYS(fail, "ip link set dev %s up", net_config[i].local_veth);
if (net_config[i].remote_addr[0])
SYS(fail, "ip -n %s addr add %s/24 dev %s", net_config[i].namespace,
net_config[i].remote_addr, net_config[i].remote_veth);
SYS(fail, "ip -n %s link set dev %s up", net_config[i].namespace,
net_config[i].remote_veth);
goto fail;
SYS(fail, "ip netns add %s", net_config->veth_cfg[i].namespace);
}
/* Create interfaces */
nstoken = open_netns(net_config->ns0_name);
if (!nstoken)
goto fail;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
SYS(fail, "ip link add %s type veth peer name %s netns %s",
net_config->veth_cfg[i].local_veth, net_config->veth_cfg[i].remote_veth,
net_config->veth_cfg[i].namespace);
SYS(fail, "ip link set dev %s up", net_config->veth_cfg[i].local_veth);
if (net_config->veth_cfg[i].remote_addr[0])
SYS(fail, "ip -n %s addr add %s/24 dev %s",
net_config->veth_cfg[i].namespace,
net_config->veth_cfg[i].remote_addr,
net_config->veth_cfg[i].remote_veth);
SYS(fail, "ip -n %s link set dev %s up", net_config->veth_cfg[i].namespace,
net_config->veth_cfg[i].remote_veth);
}
close_netns(nstoken);
return 0;
fail:
close_netns(nstoken);
return -1;
}
static void cleanup_network(struct veth_configuration *net_config)
static void cleanup_network(struct net_configuration *net_config)
{
struct nstoken *nstoken;
int i;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
bpf_xdp_detach(if_nametoindex(net_config[i].local_veth), 0, NULL);
nstoken = open_netns(net_config[i].namespace);
if (nstoken) {
bpf_xdp_detach(if_nametoindex(net_config[i].remote_veth), 0, NULL);
close_netns(nstoken);
}
/* in case the detach failed */
SYS_NOFAIL("ip link del %s", net_config[i].local_veth);
SYS_NOFAIL("ip netns del %s", net_config[i].namespace);
}
SYS_NOFAIL("ip netns del %s", net_config->ns0_name);
for (i = 0; i < VETH_PAIRS_COUNT; i++)
SYS_NOFAIL("ip netns del %s", net_config->veth_cfg[i].namespace);
}
#define VETH_REDIRECT_SKEL_NB 3
@ -210,9 +248,10 @@ static void xdp_veth_redirect(u32 flags)
.remote_flags = flags,
}
};
struct veth_configuration net_config[VETH_PAIRS_COUNT];
struct bpf_object *bpf_objs[VETH_REDIRECT_SKEL_NB];
struct xdp_redirect_map *xdp_redirect_map;
struct net_configuration net_config;
struct nstoken *nstoken = NULL;
struct xdp_dummy *xdp_dummy;
struct xdp_tx *xdp_tx;
int map_fd;
@ -230,7 +269,7 @@ static void xdp_veth_redirect(u32 flags)
if (!ASSERT_OK_PTR(xdp_redirect_map, "xdp_redirect_map__open_and_load"))
goto destroy_xdp_tx;
if (!ASSERT_OK(create_network(net_config), "create network"))
if (!ASSERT_OK(create_network(&net_config), "create network"))
goto destroy_xdp_redirect_map;
/* Then configure the redirect map and attach programs to interfaces */
@ -241,19 +280,24 @@ static void xdp_veth_redirect(u32 flags)
bpf_objs[0] = xdp_dummy->obj;
bpf_objs[1] = xdp_tx->obj;
bpf_objs[2] = xdp_redirect_map->obj;
nstoken = open_netns(net_config.ns0_name);
if (!ASSERT_OK_PTR(nstoken, "open NS0"))
goto destroy_xdp_redirect_map;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
int next_veth = net_config[i].next_veth;
int next_veth = net_config.veth_cfg[i].next_veth;
int interface_id;
int err;
interface_id = if_nametoindex(net_config[next_veth].local_veth);
interface_id = if_nametoindex(net_config.veth_cfg[next_veth].local_veth);
if (!ASSERT_NEQ(interface_id, 0, "non zero interface index"))
goto destroy_xdp_redirect_map;
err = bpf_map_update_elem(map_fd, &i, &interface_id, BPF_ANY);
if (!ASSERT_OK(err, "configure interface redirection through map"))
goto destroy_xdp_redirect_map;
if (attach_programs_to_veth_pair(bpf_objs, VETH_REDIRECT_SKEL_NB,
net_config, ping_config, i))
&net_config, ping_config, i))
goto destroy_xdp_redirect_map;
}
@ -261,16 +305,250 @@ static void xdp_veth_redirect(u32 flags)
* veth33 from veth11
*/
ASSERT_OK(SYS_NOFAIL("ip netns exec %s ping -c 1 -W 1 %s > /dev/null",
net_config[0].namespace, IP_DST), "ping");
net_config.veth_cfg[0].namespace, IP_DST), "ping");
destroy_xdp_redirect_map:
close_netns(nstoken);
xdp_redirect_map__destroy(xdp_redirect_map);
destroy_xdp_tx:
xdp_tx__destroy(xdp_tx);
destroy_xdp_dummy:
xdp_dummy__destroy(xdp_dummy);
cleanup_network(net_config);
cleanup_network(&net_config);
}
#define BROADCAST_REDIRECT_SKEL_NB 2
static void xdp_veth_broadcast_redirect(u32 attach_flags, u64 redirect_flags)
{
struct prog_configuration prog_cfg[VETH_PAIRS_COUNT] = {
{
.local_name = "xdp_redirect_map_multi_prog",
.remote_name = "xdp_count_0",
.local_flags = attach_flags,
.remote_flags = attach_flags,
},
{
.local_name = "xdp_redirect_map_multi_prog",
.remote_name = "xdp_count_1",
.local_flags = attach_flags,
.remote_flags = attach_flags,
},
{
.local_name = "xdp_redirect_map_multi_prog",
.remote_name = "xdp_count_2",
.local_flags = attach_flags,
.remote_flags = attach_flags,
}
};
struct bpf_object *bpf_objs[BROADCAST_REDIRECT_SKEL_NB];
struct xdp_redirect_multi_kern *xdp_redirect_multi_kern;
struct xdp_redirect_map *xdp_redirect_map;
struct bpf_devmap_val devmap_val = {};
struct net_configuration net_config;
struct nstoken *nstoken = NULL;
u16 protocol = ETH_P_IP;
int group_map;
int flags_map;
int cnt_map;
u64 cnt = 0;
int i, err;
xdp_redirect_multi_kern = xdp_redirect_multi_kern__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_multi_kern, "xdp_redirect_multi_kern__open_and_load"))
return;
xdp_redirect_map = xdp_redirect_map__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_map, "xdp_redirect_map__open_and_load"))
goto destroy_xdp_redirect_multi_kern;
if (!ASSERT_OK(create_network(&net_config), "create network"))
goto destroy_xdp_redirect_map;
group_map = bpf_map__fd(xdp_redirect_multi_kern->maps.map_all);
if (!ASSERT_OK_FD(group_map, "open map_all"))
goto destroy_xdp_redirect_map;
flags_map = bpf_map__fd(xdp_redirect_multi_kern->maps.redirect_flags);
if (!ASSERT_OK_FD(group_map, "open map_all"))
goto destroy_xdp_redirect_map;
err = bpf_map_update_elem(flags_map, &protocol, &redirect_flags, BPF_NOEXIST);
if (!ASSERT_OK(err, "init IP count"))
goto destroy_xdp_redirect_map;
cnt_map = bpf_map__fd(xdp_redirect_map->maps.rxcnt);
if (!ASSERT_OK_FD(cnt_map, "open rxcnt map"))
goto destroy_xdp_redirect_map;
bpf_objs[0] = xdp_redirect_multi_kern->obj;
bpf_objs[1] = xdp_redirect_map->obj;
nstoken = open_netns(net_config.ns0_name);
if (!ASSERT_OK_PTR(nstoken, "open NS0"))
goto destroy_xdp_redirect_map;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
int ifindex = if_nametoindex(net_config.veth_cfg[i].local_veth);
if (attach_programs_to_veth_pair(bpf_objs, BROADCAST_REDIRECT_SKEL_NB,
&net_config, prog_cfg, i))
goto destroy_xdp_redirect_map;
SYS(destroy_xdp_redirect_map,
"ip -n %s neigh add %s lladdr 00:00:00:00:00:01 dev %s",
net_config.veth_cfg[i].namespace, IP_NEIGH, net_config.veth_cfg[i].remote_veth);
devmap_val.ifindex = ifindex;
err = bpf_map_update_elem(group_map, &ifindex, &devmap_val, 0);
if (!ASSERT_OK(err, "bpf_map_update_elem"))
goto destroy_xdp_redirect_map;
}
SYS_NOFAIL("ip netns exec %s ping %s -i 0.1 -c 4 -W1 > /dev/null ",
net_config.veth_cfg[0].namespace, IP_NEIGH);
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
err = bpf_map_lookup_elem(cnt_map, &i, &cnt);
if (!ASSERT_OK(err, "get IP cnt"))
goto destroy_xdp_redirect_map;
if (redirect_flags & BPF_F_EXCLUDE_INGRESS)
/* veth11 shouldn't receive the ICMP requests;
* others should
*/
ASSERT_EQ(cnt, i ? 4 : 0, "compare IP cnt");
else
/* All remote veth should receive the ICMP requests */
ASSERT_EQ(cnt, 4, "compare IP cnt");
}
destroy_xdp_redirect_map:
close_netns(nstoken);
xdp_redirect_map__destroy(xdp_redirect_map);
destroy_xdp_redirect_multi_kern:
xdp_redirect_multi_kern__destroy(xdp_redirect_multi_kern);
cleanup_network(&net_config);
}
#define VETH_EGRESS_SKEL_NB 3
static void xdp_veth_egress(u32 flags)
{
struct prog_configuration prog_cfg[VETH_PAIRS_COUNT] = {
{
.local_name = "xdp_redirect_map_all_prog",
.remote_name = "xdp_dummy_prog",
.local_flags = flags,
.remote_flags = flags,
},
{
.local_name = "xdp_redirect_map_all_prog",
.remote_name = "store_mac_1",
.local_flags = flags,
.remote_flags = flags,
},
{
.local_name = "xdp_redirect_map_all_prog",
.remote_name = "store_mac_2",
.local_flags = flags,
.remote_flags = flags,
}
};
const char magic_mac[6] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
struct xdp_redirect_multi_kern *xdp_redirect_multi_kern;
struct bpf_object *bpf_objs[VETH_EGRESS_SKEL_NB];
struct xdp_redirect_map *xdp_redirect_map;
struct bpf_devmap_val devmap_val = {};
struct net_configuration net_config;
int mac_map, egress_map, res_map;
struct nstoken *nstoken = NULL;
struct xdp_dummy *xdp_dummy;
int err;
int i;
xdp_dummy = xdp_dummy__open_and_load();
if (!ASSERT_OK_PTR(xdp_dummy, "xdp_dummy__open_and_load"))
return;
xdp_redirect_multi_kern = xdp_redirect_multi_kern__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_multi_kern, "xdp_redirect_multi_kern__open_and_load"))
goto destroy_xdp_dummy;
xdp_redirect_map = xdp_redirect_map__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_map, "xdp_redirect_map__open_and_load"))
goto destroy_xdp_redirect_multi_kern;
if (!ASSERT_OK(create_network(&net_config), "create network"))
goto destroy_xdp_redirect_map;
mac_map = bpf_map__fd(xdp_redirect_multi_kern->maps.mac_map);
if (!ASSERT_OK_FD(mac_map, "open mac_map"))
goto destroy_xdp_redirect_map;
egress_map = bpf_map__fd(xdp_redirect_multi_kern->maps.map_egress);
if (!ASSERT_OK_FD(egress_map, "open map_egress"))
goto destroy_xdp_redirect_map;
devmap_val.bpf_prog.fd = bpf_program__fd(xdp_redirect_multi_kern->progs.xdp_devmap_prog);
bpf_objs[0] = xdp_dummy->obj;
bpf_objs[1] = xdp_redirect_multi_kern->obj;
bpf_objs[2] = xdp_redirect_map->obj;
nstoken = open_netns(net_config.ns0_name);
if (!ASSERT_OK_PTR(nstoken, "open NS0"))
goto destroy_xdp_redirect_map;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
int ifindex = if_nametoindex(net_config.veth_cfg[i].local_veth);
SYS(destroy_xdp_redirect_map,
"ip -n %s neigh add %s lladdr 00:00:00:00:00:01 dev %s",
net_config.veth_cfg[i].namespace, IP_NEIGH, net_config.veth_cfg[i].remote_veth);
if (attach_programs_to_veth_pair(bpf_objs, VETH_REDIRECT_SKEL_NB,
&net_config, prog_cfg, i))
goto destroy_xdp_redirect_map;
err = bpf_map_update_elem(mac_map, &ifindex, magic_mac, 0);
if (!ASSERT_OK(err, "bpf_map_update_elem"))
goto destroy_xdp_redirect_map;
devmap_val.ifindex = ifindex;
err = bpf_map_update_elem(egress_map, &ifindex, &devmap_val, 0);
if (!ASSERT_OK(err, "bpf_map_update_elem"))
goto destroy_xdp_redirect_map;
}
SYS_NOFAIL("ip netns exec %s ping %s -i 0.1 -c 4 -W1 > /dev/null ",
net_config.veth_cfg[0].namespace, IP_NEIGH);
res_map = bpf_map__fd(xdp_redirect_map->maps.rx_mac);
if (!ASSERT_OK_FD(res_map, "open rx_map"))
goto destroy_xdp_redirect_map;
for (i = 0; i < 2; i++) {
u32 key = i;
u64 res;
err = bpf_map_lookup_elem(res_map, &key, &res);
if (!ASSERT_OK(err, "get MAC res"))
goto destroy_xdp_redirect_map;
ASSERT_STRNEQ((const char *)&res, magic_mac, ETH_ALEN, "compare mac");
}
destroy_xdp_redirect_map:
close_netns(nstoken);
xdp_redirect_map__destroy(xdp_redirect_map);
destroy_xdp_redirect_multi_kern:
xdp_redirect_multi_kern__destroy(xdp_redirect_multi_kern);
destroy_xdp_dummy:
xdp_dummy__destroy(xdp_dummy);
cleanup_network(&net_config);
}
void test_xdp_veth_redirect(void)
@ -284,3 +562,38 @@ void test_xdp_veth_redirect(void)
if (test__start_subtest("SKB_MODE"))
xdp_veth_redirect(XDP_FLAGS_SKB_MODE);
}
void test_xdp_veth_broadcast_redirect(void)
{
if (test__start_subtest("0/BROADCAST"))
xdp_veth_broadcast_redirect(0, BPF_F_BROADCAST);
if (test__start_subtest("0/(BROADCAST | EXCLUDE_INGRESS)"))
xdp_veth_broadcast_redirect(0, BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS);
if (test__start_subtest("DRV_MODE/BROADCAST"))
xdp_veth_broadcast_redirect(XDP_FLAGS_DRV_MODE, BPF_F_BROADCAST);
if (test__start_subtest("DRV_MODE/(BROADCAST | EXCLUDE_INGRESS)"))
xdp_veth_broadcast_redirect(XDP_FLAGS_DRV_MODE,
BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS);
if (test__start_subtest("SKB_MODE/BROADCAST"))
xdp_veth_broadcast_redirect(XDP_FLAGS_SKB_MODE, BPF_F_BROADCAST);
if (test__start_subtest("SKB_MODE/(BROADCAST | EXCLUDE_INGRESS)"))
xdp_veth_broadcast_redirect(XDP_FLAGS_SKB_MODE,
BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS);
}
void test_xdp_veth_egress(void)
{
if (test__start_subtest("0/egress"))
xdp_veth_egress(0);
if (test__start_subtest("DRV_MODE/egress"))
xdp_veth_egress(XDP_FLAGS_DRV_MODE);
if (test__start_subtest("SKB_MODE/egress"))
xdp_veth_egress(XDP_FLAGS_SKB_MODE);
}

View File

@ -1,7 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/if_ether.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
@ -28,4 +31,89 @@ int xdp_redirect_map_2(struct xdp_md *xdp)
return bpf_redirect_map(&tx_port, 2, 0);
}
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 3);
__type(key, __u32);
__type(value, __u64);
} rxcnt SEC(".maps");
static int xdp_count(struct xdp_md *xdp, __u32 key)
{
void *data_end = (void *)(long)xdp->data_end;
void *data = (void *)(long)xdp->data;
struct ethhdr *eth = data;
__u64 *count;
if (data + sizeof(*eth) > data_end)
return XDP_DROP;
if (bpf_htons(eth->h_proto) == ETH_P_IP) {
/* We only count IPv4 packets */
count = bpf_map_lookup_elem(&rxcnt, &key);
if (count)
*count += 1;
}
return XDP_PASS;
}
SEC("xdp")
int xdp_count_0(struct xdp_md *xdp)
{
return xdp_count(xdp, 0);
}
SEC("xdp")
int xdp_count_1(struct xdp_md *xdp)
{
return xdp_count(xdp, 1);
}
SEC("xdp")
int xdp_count_2(struct xdp_md *xdp)
{
return xdp_count(xdp, 2);
}
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 2);
__type(key, __u32);
__type(value, __be64);
} rx_mac SEC(".maps");
static int store_mac(struct xdp_md *xdp, __u32 id)
{
void *data_end = (void *)(long)xdp->data_end;
void *data = (void *)(long)xdp->data;
struct ethhdr *eth = data;
__u32 key = id;
__be64 mac = 0;
if (data + sizeof(*eth) > data_end)
return XDP_DROP;
/* Only store IPv4 MAC to avoid being polluted by IPv6 packets */
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
__builtin_memcpy(&mac, eth->h_source, ETH_ALEN);
bpf_map_update_elem(&rx_mac, &key, &mac, 0);
bpf_printk("%s - %x", __func__, mac);
}
return XDP_PASS;
}
SEC("xdp")
int store_mac_1(struct xdp_md *xdp)
{
return store_mac(xdp, 0);
}
SEC("xdp")
int store_mac_2(struct xdp_md *xdp)
{
return store_mac(xdp, 1);
}
char _license[] SEC("license") = "GPL";

View File

@ -34,6 +34,14 @@ struct {
__uint(max_entries, 128);
} mac_map SEC(".maps");
/* map to store redirect flags for each protocol*/
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u16);
__type(value, __u64);
__uint(max_entries, 16);
} redirect_flags SEC(".maps");
SEC("xdp")
int xdp_redirect_map_multi_prog(struct xdp_md *ctx)
{
@ -41,25 +49,34 @@ int xdp_redirect_map_multi_prog(struct xdp_md *ctx)
void *data = (void *)(long)ctx->data;
int if_index = ctx->ingress_ifindex;
struct ethhdr *eth = data;
__u64 *flags_from_map;
__u16 h_proto;
__u64 nh_off;
__u64 flags;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return XDP_DROP;
h_proto = eth->h_proto;
h_proto = bpf_htons(eth->h_proto);
/* Using IPv4 for (BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS) testing */
if (h_proto == bpf_htons(ETH_P_IP))
return bpf_redirect_map(&map_all, 0,
BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS);
/* Using IPv6 for none flag testing */
else if (h_proto == bpf_htons(ETH_P_IPV6))
return bpf_redirect_map(&map_all, if_index, 0);
/* All others for BPF_F_BROADCAST testing */
else
return bpf_redirect_map(&map_all, 0, BPF_F_BROADCAST);
flags_from_map = bpf_map_lookup_elem(&redirect_flags, &h_proto);
/* Default flags for IPv4 : (BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS) */
if (h_proto == ETH_P_IP) {
flags = flags_from_map ? *flags_from_map : BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS;
return bpf_redirect_map(&map_all, 0, flags);
}
/* Default flags for IPv6 : 0 */
if (h_proto == ETH_P_IPV6) {
flags = flags_from_map ? *flags_from_map : 0;
return bpf_redirect_map(&map_all, if_index, flags);
}
/* Default flags for others BPF_F_BROADCAST : 0 */
else {
flags = flags_from_map ? *flags_from_map : BPF_F_BROADCAST;
return bpf_redirect_map(&map_all, 0, flags);
}
}
/* The following 2 progs are for 2nd devmap prog testing */

View File

@ -1,214 +0,0 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Test topology:
# - - - - - - - - - - - - - - - - - - -
# | veth1 veth2 veth3 | ns0
# - -| - - - - - - | - - - - - - | - -
# --------- --------- ---------
# | veth0 | | veth0 | | veth0 |
# --------- --------- ---------
# ns1 ns2 ns3
#
# Test modules:
# XDP modes: generic, native, native + egress_prog
#
# Test cases:
# ARP: Testing BPF_F_BROADCAST, the ingress interface also should receive
# the redirects.
# ns1 -> gw: ns1, ns2, ns3, should receive the arp request
# IPv4: Testing BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS, the ingress
# interface should not receive the redirects.
# ns1 -> gw: ns1 should not receive, ns2, ns3 should receive redirects.
# IPv6: Testing none flag, all the pkts should be redirected back
# ping test: ns1 -> ns2 (block), echo requests will be redirect back
# egress_prog:
# all src mac should be egress interface's mac
# netns numbers
NUM=3
IFACES=""
DRV_MODE="xdpgeneric xdpdrv xdpegress"
PASS=0
FAIL=0
LOG_DIR=$(mktemp -d)
declare -a NS
NS[0]="ns0-$(mktemp -u XXXXXX)"
NS[1]="ns1-$(mktemp -u XXXXXX)"
NS[2]="ns2-$(mktemp -u XXXXXX)"
NS[3]="ns3-$(mktemp -u XXXXXX)"
test_pass()
{
echo "Pass: $@"
PASS=$((PASS + 1))
}
test_fail()
{
echo "fail: $@"
FAIL=$((FAIL + 1))
}
clean_up()
{
for i in $(seq 0 $NUM); do
ip netns del ${NS[$i]} 2> /dev/null
done
}
# Kselftest framework requirement - SKIP code is 4.
check_env()
{
ip link set dev lo xdpgeneric off &>/dev/null
if [ $? -ne 0 ];then
echo "selftests: [SKIP] Could not run test without the ip xdpgeneric support"
exit 4
fi
which tcpdump &>/dev/null
if [ $? -ne 0 ];then
echo "selftests: [SKIP] Could not run test without tcpdump"
exit 4
fi
}
setup_ns()
{
local mode=$1
IFACES=""
if [ "$mode" = "xdpegress" ]; then
mode="xdpdrv"
fi
ip netns add ${NS[0]}
for i in $(seq $NUM); do
ip netns add ${NS[$i]}
ip -n ${NS[$i]} link add veth0 type veth peer name veth$i netns ${NS[0]}
ip -n ${NS[$i]} link set veth0 up
ip -n ${NS[0]} link set veth$i up
ip -n ${NS[$i]} addr add 192.0.2.$i/24 dev veth0
ip -n ${NS[$i]} addr add 2001:db8::$i/64 dev veth0
# Add a neigh entry for IPv4 ping test
ip -n ${NS[$i]} neigh add 192.0.2.253 lladdr 00:00:00:00:00:01 dev veth0
ip -n ${NS[$i]} link set veth0 $mode obj \
xdp_dummy.bpf.o sec xdp &> /dev/null || \
{ test_fail "Unable to load dummy xdp" && exit 1; }
IFACES="$IFACES veth$i"
veth_mac[$i]=$(ip -n ${NS[0]} link show veth$i | awk '/link\/ether/ {print $2}')
done
}
do_egress_tests()
{
local mode=$1
# mac test
ip netns exec ${NS[2]} tcpdump -e -i veth0 -nn -l -e &> ${LOG_DIR}/mac_ns1-2_${mode}.log &
ip netns exec ${NS[3]} tcpdump -e -i veth0 -nn -l -e &> ${LOG_DIR}/mac_ns1-3_${mode}.log &
sleep 0.5
ip netns exec ${NS[1]} ping 192.0.2.254 -i 0.1 -c 4 &> /dev/null
sleep 0.5
pkill tcpdump
# mac check
grep -q "${veth_mac[2]} > ff:ff:ff:ff:ff:ff" ${LOG_DIR}/mac_ns1-2_${mode}.log && \
test_pass "$mode mac ns1-2" || test_fail "$mode mac ns1-2"
grep -q "${veth_mac[3]} > ff:ff:ff:ff:ff:ff" ${LOG_DIR}/mac_ns1-3_${mode}.log && \
test_pass "$mode mac ns1-3" || test_fail "$mode mac ns1-3"
}
do_ping_tests()
{
local mode=$1
# ping6 test: echo request should be redirect back to itself, not others
ip netns exec ${NS[1]} ip neigh add 2001:db8::2 dev veth0 lladdr 00:00:00:00:00:02
ip netns exec ${NS[1]} tcpdump -i veth0 -nn -l -e &> ${LOG_DIR}/ns1-1_${mode}.log &
ip netns exec ${NS[2]} tcpdump -i veth0 -nn -l -e &> ${LOG_DIR}/ns1-2_${mode}.log &
ip netns exec ${NS[3]} tcpdump -i veth0 -nn -l -e &> ${LOG_DIR}/ns1-3_${mode}.log &
sleep 0.5
# ARP test
ip netns exec ${NS[1]} arping -q -c 2 -I veth0 192.0.2.254
# IPv4 test
ip netns exec ${NS[1]} ping 192.0.2.253 -i 0.1 -c 4 &> /dev/null
# IPv6 test
ip netns exec ${NS[1]} ping6 2001:db8::2 -i 0.1 -c 2 &> /dev/null
sleep 0.5
pkill tcpdump
# All netns should receive the redirect arp requests
[ $(grep -cF "who-has 192.0.2.254" ${LOG_DIR}/ns1-1_${mode}.log) -eq 4 ] && \
test_pass "$mode arp(F_BROADCAST) ns1-1" || \
test_fail "$mode arp(F_BROADCAST) ns1-1"
[ $(grep -cF "who-has 192.0.2.254" ${LOG_DIR}/ns1-2_${mode}.log) -eq 2 ] && \
test_pass "$mode arp(F_BROADCAST) ns1-2" || \
test_fail "$mode arp(F_BROADCAST) ns1-2"
[ $(grep -cF "who-has 192.0.2.254" ${LOG_DIR}/ns1-3_${mode}.log) -eq 2 ] && \
test_pass "$mode arp(F_BROADCAST) ns1-3" || \
test_fail "$mode arp(F_BROADCAST) ns1-3"
# ns1 should not receive the redirect echo request, others should
[ $(grep -c "ICMP echo request" ${LOG_DIR}/ns1-1_${mode}.log) -eq 4 ] && \
test_pass "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-1" || \
test_fail "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-1"
[ $(grep -c "ICMP echo request" ${LOG_DIR}/ns1-2_${mode}.log) -eq 4 ] && \
test_pass "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-2" || \
test_fail "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-2"
[ $(grep -c "ICMP echo request" ${LOG_DIR}/ns1-3_${mode}.log) -eq 4 ] && \
test_pass "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-3" || \
test_fail "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-3"
# ns1 should receive the echo request, ns2 should not
[ $(grep -c "ICMP6, echo request" ${LOG_DIR}/ns1-1_${mode}.log) -eq 4 ] && \
test_pass "$mode IPv6 (no flags) ns1-1" || \
test_fail "$mode IPv6 (no flags) ns1-1"
[ $(grep -c "ICMP6, echo request" ${LOG_DIR}/ns1-2_${mode}.log) -eq 0 ] && \
test_pass "$mode IPv6 (no flags) ns1-2" || \
test_fail "$mode IPv6 (no flags) ns1-2"
}
do_tests()
{
local mode=$1
local drv_p
case ${mode} in
xdpdrv) drv_p="-N";;
xdpegress) drv_p="-X";;
xdpgeneric) drv_p="-S";;
esac
ip netns exec ${NS[0]} ./xdp_redirect_multi $drv_p $IFACES &> ${LOG_DIR}/xdp_redirect_${mode}.log &
xdp_pid=$!
sleep 1
if ! ps -p $xdp_pid > /dev/null; then
test_fail "$mode xdp_redirect_multi start failed"
return 1
fi
if [ "$mode" = "xdpegress" ]; then
do_egress_tests $mode
else
do_ping_tests $mode
fi
kill $xdp_pid
}
check_env
trap clean_up EXIT
for mode in ${DRV_MODE}; do
setup_ns $mode
do_tests $mode
clean_up
done
rm -rf ${LOG_DIR}
echo "Summary: PASS $PASS, FAIL $FAIL"
[ $FAIL -eq 0 ] && exit 0 || exit 1

View File

@ -1,226 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <linux/if_link.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "bpf_util.h"
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#define MAX_IFACE_NUM 32
#define MAX_INDEX_NUM 1024
static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static int ifaces[MAX_IFACE_NUM] = {};
static void int_exit(int sig)
{
__u32 prog_id = 0;
int i;
for (i = 0; ifaces[i] > 0; i++) {
if (bpf_xdp_query_id(ifaces[i], xdp_flags, &prog_id)) {
printf("bpf_xdp_query_id failed\n");
exit(1);
}
if (prog_id)
bpf_xdp_detach(ifaces[i], xdp_flags, NULL);
}
exit(0);
}
static int get_mac_addr(unsigned int ifindex, void *mac_addr)
{
char ifname[IF_NAMESIZE];
struct ifreq ifr;
int fd, ret = -1;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
return ret;
if (!if_indextoname(ifindex, ifname))
goto err_out;
strcpy(ifr.ifr_name, ifname);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0)
goto err_out;
memcpy(mac_addr, ifr.ifr_hwaddr.sa_data, 6 * sizeof(char));
ret = 0;
err_out:
close(fd);
return ret;
}
static void usage(const char *prog)
{
fprintf(stderr,
"usage: %s [OPTS] <IFNAME|IFINDEX> <IFNAME|IFINDEX> ...\n"
"OPTS:\n"
" -S use skb-mode\n"
" -N enforce native mode\n"
" -F force loading prog\n"
" -X load xdp program on egress\n",
prog);
}
int main(int argc, char **argv)
{
int prog_fd, group_all, mac_map;
struct bpf_program *ingress_prog, *egress_prog;
int i, err, ret, opt, egress_prog_fd = 0;
struct bpf_devmap_val devmap_val;
bool attach_egress_prog = false;
unsigned char mac_addr[6];
char ifname[IF_NAMESIZE];
struct bpf_object *obj;
unsigned int ifindex;
char filename[256];
while ((opt = getopt(argc, argv, "SNFX")) != -1) {
switch (opt) {
case 'S':
xdp_flags |= XDP_FLAGS_SKB_MODE;
break;
case 'N':
/* default, set below */
break;
case 'F':
xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST;
break;
case 'X':
attach_egress_prog = true;
break;
default:
usage(basename(argv[0]));
return 1;
}
}
if (!(xdp_flags & XDP_FLAGS_SKB_MODE)) {
xdp_flags |= XDP_FLAGS_DRV_MODE;
} else if (attach_egress_prog) {
printf("Load xdp program on egress with SKB mode not supported yet\n");
goto err_out;
}
if (optind == argc) {
printf("usage: %s <IFNAME|IFINDEX> <IFNAME|IFINDEX> ...\n", argv[0]);
goto err_out;
}
printf("Get interfaces:");
for (i = 0; i < MAX_IFACE_NUM && argv[optind + i]; i++) {
ifaces[i] = if_nametoindex(argv[optind + i]);
if (!ifaces[i])
ifaces[i] = strtoul(argv[optind + i], NULL, 0);
if (!if_indextoname(ifaces[i], ifname)) {
perror("Invalid interface name or i");
goto err_out;
}
if (ifaces[i] > MAX_INDEX_NUM) {
printf(" interface index too large\n");
goto err_out;
}
printf(" %d", ifaces[i]);
}
printf("\n");
snprintf(filename, sizeof(filename), "%s_kern.bpf.o", argv[0]);
obj = bpf_object__open_file(filename, NULL);
err = libbpf_get_error(obj);
if (err)
goto err_out;
err = bpf_object__load(obj);
if (err)
goto err_out;
prog_fd = bpf_program__fd(bpf_object__next_program(obj, NULL));
if (attach_egress_prog)
group_all = bpf_object__find_map_fd_by_name(obj, "map_egress");
else
group_all = bpf_object__find_map_fd_by_name(obj, "map_all");
mac_map = bpf_object__find_map_fd_by_name(obj, "mac_map");
if (group_all < 0 || mac_map < 0) {
printf("bpf_object__find_map_fd_by_name failed\n");
goto err_out;
}
if (attach_egress_prog) {
/* Find ingress/egress prog for 2nd xdp prog */
ingress_prog = bpf_object__find_program_by_name(obj, "xdp_redirect_map_all_prog");
egress_prog = bpf_object__find_program_by_name(obj, "xdp_devmap_prog");
if (!ingress_prog || !egress_prog) {
printf("finding ingress/egress_prog in obj file failed\n");
goto err_out;
}
prog_fd = bpf_program__fd(ingress_prog);
egress_prog_fd = bpf_program__fd(egress_prog);
if (prog_fd < 0 || egress_prog_fd < 0) {
printf("find egress_prog fd failed\n");
goto err_out;
}
}
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
/* Init forward multicast groups and exclude group */
for (i = 0; ifaces[i] > 0; i++) {
ifindex = ifaces[i];
if (attach_egress_prog) {
ret = get_mac_addr(ifindex, mac_addr);
if (ret < 0) {
printf("get interface %d mac failed\n", ifindex);
goto err_out;
}
ret = bpf_map_update_elem(mac_map, &ifindex, mac_addr, 0);
if (ret) {
perror("bpf_update_elem mac_map failed\n");
goto err_out;
}
}
/* Add all the interfaces to group all */
devmap_val.ifindex = ifindex;
devmap_val.bpf_prog.fd = egress_prog_fd;
ret = bpf_map_update_elem(group_all, &ifindex, &devmap_val, 0);
if (ret) {
perror("bpf_map_update_elem");
goto err_out;
}
/* bind prog_fd to each interface */
ret = bpf_xdp_attach(ifindex, prog_fd, xdp_flags, NULL);
if (ret) {
printf("Set xdp fd failed on %d\n", ifindex);
goto err_out;
}
}
/* sleep some time for testing */
sleep(999);
return 0;
err_out:
return 1;
}