In test_bpf_ip_check_defrag_ok(), the new helper client_socket() can be used to replace connect_to_fd_opts() with "noconnect" opts, and the strcut member "noconnect" of network_helper_opts can be dropped now, always connect to server in connect_to_fd_opts(). Acked-by: Eduard Zingerman <eddyz87@gmail.com> Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn> Link: https://lore.kernel.org/r/f45760becce51986e4e08283c7df0f933eb0da14.1718932493.git.tanggeliang@kylinos.cn Signed-off-by: Alexei Starovoitov <ast@kernel.org>
283 lines
7.7 KiB
C
283 lines
7.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <test_progs.h>
|
|
#include <net/if.h>
|
|
#include <linux/netfilter.h>
|
|
#include <network_helpers.h>
|
|
#include "ip_check_defrag.skel.h"
|
|
#include "ip_check_defrag_frags.h"
|
|
|
|
/*
|
|
* This selftest spins up a client and an echo server, each in their own
|
|
* network namespace. The client will send a fragmented message to the server.
|
|
* The prog attached to the server will shoot down any fragments. Thus, if
|
|
* the server is able to correctly echo back the message to the client, we will
|
|
* have verified that netfilter is reassembling packets for us.
|
|
*
|
|
* Topology:
|
|
* =========
|
|
* NS0 | NS1
|
|
* |
|
|
* client | server
|
|
* ---------- | ----------
|
|
* | veth0 | --------- | veth1 |
|
|
* ---------- peer ----------
|
|
* |
|
|
* | with bpf
|
|
*/
|
|
|
|
#define NS0 "defrag_ns0"
|
|
#define NS1 "defrag_ns1"
|
|
#define VETH0 "veth0"
|
|
#define VETH1 "veth1"
|
|
#define VETH0_ADDR "172.16.1.100"
|
|
#define VETH0_ADDR6 "fc00::100"
|
|
/* The following constants must stay in sync with `generate_udp_fragments.py` */
|
|
#define VETH1_ADDR "172.16.1.200"
|
|
#define VETH1_ADDR6 "fc00::200"
|
|
#define CLIENT_PORT 48878
|
|
#define SERVER_PORT 48879
|
|
#define MAGIC_MESSAGE "THIS IS THE ORIGINAL MESSAGE, PLEASE REASSEMBLE ME"
|
|
|
|
static int setup_topology(bool ipv6)
|
|
{
|
|
bool up;
|
|
int i;
|
|
|
|
SYS(fail, "ip netns add " NS0);
|
|
SYS(fail, "ip netns add " NS1);
|
|
SYS(fail, "ip link add " VETH0 " netns " NS0 " type veth peer name " VETH1 " netns " NS1);
|
|
if (ipv6) {
|
|
SYS(fail, "ip -6 -net " NS0 " addr add " VETH0_ADDR6 "/64 dev " VETH0 " nodad");
|
|
SYS(fail, "ip -6 -net " NS1 " addr add " VETH1_ADDR6 "/64 dev " VETH1 " nodad");
|
|
} else {
|
|
SYS(fail, "ip -net " NS0 " addr add " VETH0_ADDR "/24 dev " VETH0);
|
|
SYS(fail, "ip -net " NS1 " addr add " VETH1_ADDR "/24 dev " VETH1);
|
|
}
|
|
SYS(fail, "ip -net " NS0 " link set dev " VETH0 " up");
|
|
SYS(fail, "ip -net " NS1 " link set dev " VETH1 " up");
|
|
|
|
/* Wait for up to 5s for links to come up */
|
|
for (i = 0; i < 5; ++i) {
|
|
if (ipv6)
|
|
up = !SYS_NOFAIL("ip netns exec " NS0 " ping -6 -c 1 -W 1 " VETH1_ADDR6);
|
|
else
|
|
up = !SYS_NOFAIL("ip netns exec " NS0 " ping -c 1 -W 1 " VETH1_ADDR);
|
|
|
|
if (up)
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
fail:
|
|
return -1;
|
|
}
|
|
|
|
static void cleanup_topology(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);
|
|
}
|
|
|
|
static int attach(struct ip_check_defrag *skel, bool ipv6)
|
|
{
|
|
LIBBPF_OPTS(bpf_netfilter_opts, opts,
|
|
.pf = ipv6 ? NFPROTO_IPV6 : NFPROTO_IPV4,
|
|
.priority = 42,
|
|
.flags = BPF_F_NETFILTER_IP_DEFRAG);
|
|
struct nstoken *nstoken;
|
|
int err = -1;
|
|
|
|
nstoken = open_netns(NS1);
|
|
if (!ASSERT_OK_PTR(nstoken, "setns"))
|
|
goto out;
|
|
|
|
skel->links.defrag = bpf_program__attach_netfilter(skel->progs.defrag, &opts);
|
|
if (!ASSERT_OK_PTR(skel->links.defrag, "program attach"))
|
|
goto out;
|
|
|
|
err = 0;
|
|
out:
|
|
close_netns(nstoken);
|
|
return err;
|
|
}
|
|
|
|
static int send_frags(int client)
|
|
{
|
|
struct sockaddr_storage saddr;
|
|
struct sockaddr *saddr_p;
|
|
socklen_t saddr_len;
|
|
int err;
|
|
|
|
saddr_p = (struct sockaddr *)&saddr;
|
|
err = make_sockaddr(AF_INET, VETH1_ADDR, SERVER_PORT, &saddr, &saddr_len);
|
|
if (!ASSERT_OK(err, "make_sockaddr"))
|
|
return -1;
|
|
|
|
err = sendto(client, frag_0, sizeof(frag_0), 0, saddr_p, saddr_len);
|
|
if (!ASSERT_GE(err, 0, "sendto frag_0"))
|
|
return -1;
|
|
|
|
err = sendto(client, frag_1, sizeof(frag_1), 0, saddr_p, saddr_len);
|
|
if (!ASSERT_GE(err, 0, "sendto frag_1"))
|
|
return -1;
|
|
|
|
err = sendto(client, frag_2, sizeof(frag_2), 0, saddr_p, saddr_len);
|
|
if (!ASSERT_GE(err, 0, "sendto frag_2"))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int send_frags6(int client)
|
|
{
|
|
struct sockaddr_storage saddr;
|
|
struct sockaddr *saddr_p;
|
|
socklen_t saddr_len;
|
|
int err;
|
|
|
|
saddr_p = (struct sockaddr *)&saddr;
|
|
/* Port needs to be set to 0 for raw ipv6 socket for some reason */
|
|
err = make_sockaddr(AF_INET6, VETH1_ADDR6, 0, &saddr, &saddr_len);
|
|
if (!ASSERT_OK(err, "make_sockaddr"))
|
|
return -1;
|
|
|
|
err = sendto(client, frag6_0, sizeof(frag6_0), 0, saddr_p, saddr_len);
|
|
if (!ASSERT_GE(err, 0, "sendto frag6_0"))
|
|
return -1;
|
|
|
|
err = sendto(client, frag6_1, sizeof(frag6_1), 0, saddr_p, saddr_len);
|
|
if (!ASSERT_GE(err, 0, "sendto frag6_1"))
|
|
return -1;
|
|
|
|
err = sendto(client, frag6_2, sizeof(frag6_2), 0, saddr_p, saddr_len);
|
|
if (!ASSERT_GE(err, 0, "sendto frag6_2"))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void test_bpf_ip_check_defrag_ok(bool ipv6)
|
|
{
|
|
int family = ipv6 ? AF_INET6 : AF_INET;
|
|
struct network_helper_opts rx_opts = {
|
|
.timeout_ms = 1000,
|
|
};
|
|
struct network_helper_opts tx_ops = {
|
|
.timeout_ms = 1000,
|
|
.proto = IPPROTO_RAW,
|
|
};
|
|
struct sockaddr_storage caddr;
|
|
struct ip_check_defrag *skel;
|
|
struct nstoken *nstoken;
|
|
int client_tx_fd = -1;
|
|
int client_rx_fd = -1;
|
|
socklen_t caddr_len;
|
|
int srv_fd = -1;
|
|
char buf[1024];
|
|
int len, err;
|
|
|
|
skel = ip_check_defrag__open_and_load();
|
|
if (!ASSERT_OK_PTR(skel, "skel_open"))
|
|
return;
|
|
|
|
if (!ASSERT_OK(setup_topology(ipv6), "setup_topology"))
|
|
goto out;
|
|
|
|
if (!ASSERT_OK(attach(skel, ipv6), "attach"))
|
|
goto out;
|
|
|
|
/* Start server in ns1 */
|
|
nstoken = open_netns(NS1);
|
|
if (!ASSERT_OK_PTR(nstoken, "setns ns1"))
|
|
goto out;
|
|
srv_fd = start_server(family, SOCK_DGRAM, NULL, SERVER_PORT, 0);
|
|
close_netns(nstoken);
|
|
if (!ASSERT_GE(srv_fd, 0, "start_server"))
|
|
goto out;
|
|
|
|
/* Open tx raw socket in ns0 */
|
|
nstoken = open_netns(NS0);
|
|
if (!ASSERT_OK_PTR(nstoken, "setns ns0"))
|
|
goto out;
|
|
client_tx_fd = client_socket(family, SOCK_RAW, &tx_ops);
|
|
close_netns(nstoken);
|
|
if (!ASSERT_GE(client_tx_fd, 0, "client_socket"))
|
|
goto out;
|
|
|
|
/* Open rx socket in ns0 */
|
|
nstoken = open_netns(NS0);
|
|
if (!ASSERT_OK_PTR(nstoken, "setns ns0"))
|
|
goto out;
|
|
client_rx_fd = client_socket(family, SOCK_DGRAM, &rx_opts);
|
|
close_netns(nstoken);
|
|
if (!ASSERT_GE(client_rx_fd, 0, "client_socket"))
|
|
goto out;
|
|
|
|
/* Bind rx socket to a premeditated port */
|
|
memset(&caddr, 0, sizeof(caddr));
|
|
nstoken = open_netns(NS0);
|
|
if (!ASSERT_OK_PTR(nstoken, "setns ns0"))
|
|
goto out;
|
|
if (ipv6) {
|
|
struct sockaddr_in6 *c = (struct sockaddr_in6 *)&caddr;
|
|
|
|
c->sin6_family = AF_INET6;
|
|
inet_pton(AF_INET6, VETH0_ADDR6, &c->sin6_addr);
|
|
c->sin6_port = htons(CLIENT_PORT);
|
|
err = bind(client_rx_fd, (struct sockaddr *)c, sizeof(*c));
|
|
} else {
|
|
struct sockaddr_in *c = (struct sockaddr_in *)&caddr;
|
|
|
|
c->sin_family = AF_INET;
|
|
inet_pton(AF_INET, VETH0_ADDR, &c->sin_addr);
|
|
c->sin_port = htons(CLIENT_PORT);
|
|
err = bind(client_rx_fd, (struct sockaddr *)c, sizeof(*c));
|
|
}
|
|
close_netns(nstoken);
|
|
if (!ASSERT_OK(err, "bind"))
|
|
goto out;
|
|
|
|
/* Send message in fragments */
|
|
if (ipv6) {
|
|
if (!ASSERT_OK(send_frags6(client_tx_fd), "send_frags6"))
|
|
goto out;
|
|
} else {
|
|
if (!ASSERT_OK(send_frags(client_tx_fd), "send_frags"))
|
|
goto out;
|
|
}
|
|
|
|
if (!ASSERT_EQ(skel->bss->shootdowns, 0, "shootdowns"))
|
|
goto out;
|
|
|
|
/* Receive reassembled msg on server and echo back to client */
|
|
caddr_len = sizeof(caddr);
|
|
len = recvfrom(srv_fd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &caddr_len);
|
|
if (!ASSERT_GE(len, 0, "server recvfrom"))
|
|
goto out;
|
|
len = sendto(srv_fd, buf, len, 0, (struct sockaddr *)&caddr, caddr_len);
|
|
if (!ASSERT_GE(len, 0, "server sendto"))
|
|
goto out;
|
|
|
|
/* Expect reassembed message to be echoed back */
|
|
len = recvfrom(client_rx_fd, buf, sizeof(buf), 0, NULL, NULL);
|
|
if (!ASSERT_EQ(len, sizeof(MAGIC_MESSAGE) - 1, "client short read"))
|
|
goto out;
|
|
|
|
out:
|
|
if (client_rx_fd != -1)
|
|
close(client_rx_fd);
|
|
if (client_tx_fd != -1)
|
|
close(client_tx_fd);
|
|
if (srv_fd != -1)
|
|
close(srv_fd);
|
|
cleanup_topology();
|
|
ip_check_defrag__destroy(skel);
|
|
}
|
|
|
|
void test_bpf_ip_check_defrag(void)
|
|
{
|
|
if (test__start_subtest("v4"))
|
|
test_bpf_ip_check_defrag_ok(false);
|
|
if (test__start_subtest("v6"))
|
|
test_bpf_ip_check_defrag_ok(true);
|
|
}
|