// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Topology:
* ---------
* NS0 namespace | NS1 namespace | NS2 namespace
* | |
* +---------------+ | +---------------+ |
* | ipsec0 |---------| ipsec0 | |
* | 192.168.1.100 | | | 192.168.1.200 | |
* | if_id: bpf | | +---------------+ |
* +---------------+ | |
* | | | +---------------+
* | | | | ipsec0 |
* \------------------------------------------| 192.168.1.200 |
* | | +---------------+
* | |
* | | (overlay network)
* ------------------------------------------------------
* | | (underlay network)
* +--------------+ | +--------------+ |
* | veth01 |----------| veth10 | |
* | 172.16.1.100 | | | 172.16.1.200 | |
* ---------------+ | +--------------+ |
* | |
* +--------------+ | | +--------------+
* | veth02 |-----------------------------------| veth20 |
* | 172.16.2.100 | | | | 172.16.2.200 |
* +--------------+ | | +--------------+
*
*
* Test Packet flow
* -----------
* The tests perform 'ping 192.168.1.200' from the NS0 namespace:
* 1) request is routed to NS0 ipsec0
* 2) NS0 ipsec0 tc egress BPF program is triggered and sets the if_id based
* on the requested value. This makes the ipsec0 device in external mode
* select the destination tunnel
* 3) ping reaches the other namespace (NS1 or NS2 based on which if_id was
* used) and response is sent
* 4) response is received on NS0 ipsec0, tc ingress program is triggered and
* records the response if_id
* 5) requested if_id is compared with received if_id
*/
#include <net/if.h>
#include <linux/rtnetlink.h>
#include <linux/if_link.h>
#include "test_progs.h"
#include "network_helpers.h"
#include "xfrm_info.skel.h"
#define NS0 "xfrm_test_ns0"
#define NS1 "xfrm_test_ns1"
#define NS2 "xfrm_test_ns2"
#define IF_ID_0_TO_1 1
#define IF_ID_0_TO_2 2
#define IF_ID_1 3
#define IF_ID_2 4
#define IP4_ADDR_VETH01 "172.16.1.100"
#define IP4_ADDR_VETH10 "172.16.1.200"
#define IP4_ADDR_VETH02 "172.16.2.100"
#define IP4_ADDR_VETH20 "172.16.2.200"
#define ESP_DUMMY_PARAMS \
"proto esp aead 'rfc4106(gcm(aes))' " \
"0xe4d8f4b4da1df18a3510b3781496daa82488b713 128 mode tunnel "
static int attach_tc_prog(struct bpf_tc_hook *hook, int igr_fd, int egr_fd)
{
LIBBPF_OPTS(bpf_tc_opts, opts1, .handle = 1, .priority = 1,
.prog_fd = igr_fd);
LIBBPF_OPTS(bpf_tc_opts, opts2, .handle = 1, .priority = 1,
.prog_fd = egr_fd);
int ret;
ret = bpf_tc_hook_create(hook);
if (!ASSERT_OK(ret, "create tc hook"))
return ret;
if (igr_fd >= 0) {
hook->attach_point = BPF_TC_INGRESS;
ret = bpf_tc_attach(hook, &opts1);
if (!ASSERT_OK(ret, "bpf_tc_attach")) {
bpf_tc_hook_destroy(hook);
return ret;
}
}
if (egr_fd >= 0) {
hook->attach_point = BPF_TC_EGRESS;
ret = bpf_tc_attach(hook, &opts2);
if (!ASSERT_OK(ret, "bpf_tc_attach")) {
bpf_tc_hook_destroy(hook);
return ret;
}
}
return 0;
}
static void cleanup(void)
{
SYS_NOFAIL("test -f /var/run/netns/" NS0 " && ip netns delete " NS0);
SYS_NOFAIL("test -f /var/run/netns/" NS1 " && ip netns delete " NS1);
SYS_NOFAIL("test -f /var/run/netns/" NS2 " && ip netns delete " NS2);
}
static int config_underlay(void)
{
SYS(fail, "ip netns add " NS0);
SYS(fail, "ip netns add " NS1);
SYS(fail, "ip netns add " NS2);
/* NS0 <-> NS1 [veth01 <-> veth10] */
SYS(fail, "ip link add veth01 netns " NS0 " type veth peer name veth10 netns " NS1);
SYS(fail, "ip -net " NS0 " addr add " IP4_ADDR_VETH01 "/24 dev veth01");
SYS(fail, "ip -net " NS0 " link set dev veth01 up");
SYS(fail, "ip -net " NS1 " addr add " IP4_ADDR_VETH10 "/24 dev veth10");
SYS(fail, "ip -net " NS1 " link set dev veth10 up");
/* NS0 <-> NS2 [veth02 <-> veth20] */
SYS(fail, "ip link add veth02 netns " NS0 " type veth peer name veth20 netns " NS2);
SYS(fail, "ip -net " NS0 " addr add " IP4_ADDR_VETH02 "/24 dev veth02");
SYS(fail, "ip -net " NS0 " link set dev veth02 up");
SYS(fail, "ip -net " NS2 " addr add " IP4_ADDR_VETH20 "/24 dev veth20");
SYS(fail, "ip -net " NS2 " link set dev veth20 up");
return 0;
fail:
return -1;
}
static int setup_xfrm_tunnel_ns(const char *ns, const char *ipv4_local,
const char *ipv4_remote, int if_id)
{
/* State: local -> remote */
SYS(fail, "ip -net %s xfrm state add src %s dst %s spi 1 "
ESP_DUMMY_PARAMS "if_id %d", ns, ipv4_local, ipv4_remote, if_id);
/* State: local <- remote */
SYS(fail, "ip -net %s xfrm state add src %s dst %s spi 1 "
ESP_DUMMY_PARAMS "if_id %d", ns, ipv4_remote, ipv4_local, if_id);
/* Policy: local -> remote */
SYS(fail, "ip -net %s xfrm policy add dir out src 0.0.0.0/0 dst 0.0.0.0/0 "
"if_id %d tmpl src %s dst %s proto esp mode tunnel if_id %d", ns,
if_id, ipv4_local, ipv4_remote, if_id);
/* Policy: local <- remote */
SYS(fail, "ip -net %s xfrm policy add dir in src 0.0.0.0/0 dst 0.0.0.0/0 "
"if_id %d tmpl src %s dst %s proto esp mode tunnel if_id %d", ns,
if_id, ipv4_remote, ipv4_local, if_id);
return 0;
fail:
return -1;
}
static int setup_xfrm_tunnel(const char *ns_a, const char *ns_b,
const char *ipv4_a, const char *ipv4_b,
int if_id_a, int if_id_b)
{
return setup_xfrm_tunnel_ns(ns_a, ipv4_a, ipv4_b, if_id_a) ||
setup_xfrm_tunnel_ns(ns_b, ipv4_b, ipv4_a, if_id_b);
}
static struct rtattr *rtattr_add(struct nlmsghdr *nh, unsigned short type,
unsigned short len)
{
struct rtattr *rta =
(struct rtattr *)((uint8_t *)nh + RTA_ALIGN(nh->nlmsg_len));
rta->rta_type = type;
rta->rta_len = RTA_LENGTH(len);
nh->nlmsg_len = RTA_ALIGN(nh->nlmsg_len) + RTA_ALIGN(rta->rta_len);
return rta;
}
static struct rtattr *rtattr_add_str(struct nlmsghdr *nh, unsigned short type,
const char *s)
{
struct rtattr *rta = rtattr_add(nh, type, strlen(s));
memcpy(RTA_DATA(rta), s, strlen(s));
return rta;
}
static struct rtattr *rtattr_begin(struct nlmsghdr *nh, unsigned short type)
{
return rtattr_add(nh, type, 0);
}
static void rtattr_end(struct nlmsghdr *nh, struct rtattr *attr)
{
uint8_t *end = (uint8_t *)nh + nh->nlmsg_len;
attr->rta_len = end - (uint8_t *)attr;
}
static int setup_xfrmi_external_dev(const char *ns)
{
struct {
struct nlmsghdr nh;
struct ifinfomsg info;
unsigned char data[128];
} req;
struct rtattr *link_info, *info_data;
struct nstoken *nstoken;
int ret = -1, sock = -1;
struct nlmsghdr *nh;
memset(&req, 0, sizeof(req));
nh = &req.nh;
nh->nlmsg_len = NLMSG_LENGTH(sizeof(req.info));
nh->nlmsg_type = RTM_NEWLINK;
nh->nlmsg_flags |= NLM_F_CREATE | NLM_F_REQUEST;
rtattr_add_str(nh, IFLA_IFNAME, "ipsec0");
link_info = rtattr_begin(nh, IFLA_LINKINFO);
rtattr_add_str(nh, IFLA_INFO_KIND, "xfrm");
info_data = rtattr_begin(nh, IFLA_INFO_DATA);
rtattr_add(nh, IFLA_XFRM_COLLECT_METADATA, 0);
rtattr_end(nh, info_data);
rtattr_end(nh, link_info);
nstoken = open_netns(ns);
if (!ASSERT_OK_PTR(nstoken, "setns"))
goto done;
sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
if (!ASSERT_GE(sock, 0, "netlink socket"))
goto done;
ret = send(sock, nh, nh->nlmsg_len, 0);
if (!ASSERT_EQ(ret, nh->nlmsg_len, "netlink send length"))
goto done;
ret = 0;
done:
if (sock != -1)
close(sock);
if (nstoken)
close_netns(nstoken);
return ret;
}
static int config_overlay(void)
{
if (setup_xfrm_tunnel(NS0, NS1, IP4_ADDR_VETH01, IP4_ADDR_VETH10,
IF_ID_0_TO_1, IF_ID_1))
goto fail;
if (setup_xfrm_tunnel(NS0, NS2, IP4_ADDR_VETH02, IP4_ADDR_VETH20,
IF_ID_0_TO_2, IF_ID_2))
goto fail;
/* Older iproute2 doesn't support this option */
if (!ASSERT_OK(setup_xfrmi_external_dev(NS0), "xfrmi"))
goto fail;
SYS(fail, "ip -net " NS0 " addr add 192.168.1.100/24 dev ipsec0");
SYS(fail, "ip -net " NS0 " link set dev ipsec0 up");
SYS(fail, "ip -net " NS1 " link add ipsec0 type xfrm if_id %d", IF_ID_1);
SYS(fail, "ip -net " NS1 " addr add 192.168.1.200/24 dev ipsec0");
SYS(fail, "ip -net " NS1 " link set dev ipsec0 up");
SYS(fail, "ip -net " NS2 " link add ipsec0 type xfrm if_id %d", IF_ID_2);
SYS(fail, "ip -net " NS2 " addr add 192.168.1.200/24 dev ipsec0");
SYS(fail, "ip -net " NS2 " link set dev ipsec0 up");
return 0;
fail:
return -1;
}
static int test_xfrm_ping(struct xfrm_info *skel, u32 if_id)
{
skel->bss->req_if_id = if_id;
SYS(fail, "ping -i 0.01 -c 3 -w 10 -q 192.168.1.200 > /dev/null");
if (!ASSERT_EQ(skel->bss->resp_if_id, if_id, "if_id"))
goto fail;
return 0;
fail:
return -1;
}
static void _test_xfrm_info(void)
{
LIBBPF_OPTS(bpf_tc_hook, tc_hook, .attach_point = BPF_TC_INGRESS);
int get_xfrm_info_prog_fd, set_xfrm_info_prog_fd;
struct nstoken *nstoken = NULL;
struct xfrm_info *skel;
int ifindex;
/* load and attach bpf progs to ipsec dev tc hook point */
skel = xfrm_info__open_and_load();
if (!ASSERT_OK_PTR(skel, "xfrm_info__open_and_load"))
goto done;
nstoken = open_netns(NS0);
if (!ASSERT_OK_PTR(nstoken, "setns " NS0))
goto done;
ifindex = if_nametoindex("ipsec0");
if (!ASSERT_NEQ(ifindex, 0, "ipsec0 ifindex"))
goto done;
tc_hook.ifindex = ifindex;
set_xfrm_info_prog_fd = bpf_program__fd(skel->progs.set_xfrm_info);
get_xfrm_info_prog_fd = bpf_program__fd(skel->progs.get_xfrm_info);
if (!ASSERT_GE(set_xfrm_info_prog_fd, 0, "bpf_program__fd"))
goto done;
if (!ASSERT_GE(get_xfrm_info_prog_fd, 0, "bpf_program__fd"))
goto done;
if (attach_tc_prog(&tc_hook, get_xfrm_info_prog_fd,
set_xfrm_info_prog_fd))
goto done;
/* perform test */
if (!ASSERT_EQ(test_xfrm_ping(skel, IF_ID_0_TO_1), 0, "ping " NS1))
goto done;
if (!ASSERT_EQ(test_xfrm_ping(skel, IF_ID_0_TO_2), 0, "ping " NS2))
goto done;
done:
if (nstoken)
close_netns(nstoken);
xfrm_info__destroy(skel);
}
void test_xfrm_info(void)
{
cleanup();
if (!ASSERT_OK(config_underlay(), "config_underlay"))
goto done;
if (!ASSERT_OK(config_overlay(), "config_overlay"))
goto done;
if (test__start_subtest("xfrm_info"))
_test_xfrm_info();
done:
cleanup();
}