Commit 6888a036 authored by Martin KaFai Lau's avatar Martin KaFai Lau
Browse files

Merge branch 'selftests-bpf-test-sockmap-sockhash-redirection'

Michal Luczaj says:

====================
selftests/bpf: Test sockmap/sockhash redirection

The idea behind this series is to comprehensively test the BPF redirection:

BPF_MAP_TYPE_SOCKMAP,
BPF_MAP_TYPE_SOCKHASH
	x
	sk_msg-to-egress,
	sk_msg-to-ingress,
	sk_skb-to-egress,
	sk_skb-to-ingress
		x
		AF_INET, SOCK_STREAM,
		AF_INET6, SOCK_STREAM,
		AF_INET, SOCK_DGRAM,
		AF_INET6, SOCK_DGRAM,
		AF_UNIX, SOCK_STREAM,
		AF_UNIX, SOCK_DGRAM,
		AF_VSOCK, SOCK_STREAM,
		AF_VSOCK, SOCK_SEQPACKET

New module is introduced, sockmap_redir: all supported and unsupported
redirect combinations are tested for success and failure respectively. Code
is pretty much stolen/adapted from Jakub Sitnicki's sockmap_redir_matrix.c
[1].

Usage:
$ cd tools/testing/selftests/bpf
$ make
$ sudo ./test_progs -t sockmap_redir
...
Summary: 1/576 PASSED, 0 SKIPPED, 0 FAILED

[1]: https://github.com/jsitnicki/sockmap-redir-matrix/blob/main/sockmap_redir_matrix.c

Changes in v3:
- Drop unrelated changes; sockmap_listen, test_sockmap_listen, doc
- Collect tags [Jakub, John]
- Introduce BPF verdict programs especially for sockmap_redir [Jiayuan]
- Link to v2: https://lore.kernel.org/r/20250411-selftests-sockmap-redir-v2-0-5f9b018d6704@rbox.co

Changes in v2:
- Verify that the unsupported redirect combos do fail [Jakub]
- Dedup tests in sockmap_listen
- Cosmetic changes and code reordering
- Link to v1: https://lore.kernel.org/bpf/42939687-20f9-4a45-b7c2-342a0e11a014@rbox.co/
====================

Link: https://patch.msgid.link/20250515-selftests-sockmap-redir-v3-0-a1ea723f7e7e@rbox.co


Signed-off-by: default avatarMartin KaFai Lau <martin.lau@kernel.org>
parents 5ead9499 c04eeeb2
Loading
Loading
Loading
Loading
+79 −5
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
#ifndef __SOCKET_HELPERS__
#define __SOCKET_HELPERS__

#include <sys/un.h>
#include <linux/vm_sockets.h>

/* include/linux/net.h */
@@ -169,6 +170,15 @@ static inline void init_addr_loopback6(struct sockaddr_storage *ss,
	*len = sizeof(*addr6);
}

static inline void init_addr_loopback_unix(struct sockaddr_storage *ss,
					   socklen_t *len)
{
	struct sockaddr_un *addr = memset(ss, 0, sizeof(*ss));

	addr->sun_family = AF_UNIX;
	*len = sizeof(sa_family_t);
}

static inline void init_addr_loopback_vsock(struct sockaddr_storage *ss,
					    socklen_t *len)
{
@@ -190,6 +200,9 @@ static inline void init_addr_loopback(int family, struct sockaddr_storage *ss,
	case AF_INET6:
		init_addr_loopback6(ss, len);
		return;
	case AF_UNIX:
		init_addr_loopback_unix(ss, len);
		return;
	case AF_VSOCK:
		init_addr_loopback_vsock(ss, len);
		return;
@@ -315,21 +328,27 @@ static inline int create_pair(int family, int sotype, int *p0, int *p1)
{
	__close_fd int s, c = -1, p = -1;
	struct sockaddr_storage addr;
	socklen_t len = sizeof(addr);
	socklen_t len;
	int err;

	s = socket_loopback(family, sotype);
	if (s < 0)
		return s;

	err = xgetsockname(s, sockaddr(&addr), &len);
	if (err)
		return err;

	c = xsocket(family, sotype, 0);
	if (c < 0)
		return c;

	init_addr_loopback(family, &addr, &len);
	err = xbind(c, sockaddr(&addr), len);
	if (err)
		return err;

	len = sizeof(addr);
	err = xgetsockname(s, sockaddr(&addr), &len);
	if (err)
		return err;

	err = connect(c, sockaddr(&addr), len);
	if (err) {
		if (errno != EINPROGRESS) {
@@ -391,4 +410,59 @@ static inline int create_socket_pairs(int family, int sotype, int *c0, int *c1,
	return err;
}

static inline const char *socket_kind_to_str(int sock_fd)
{
	socklen_t opt_len;
	int domain, type;

	opt_len = sizeof(domain);
	if (getsockopt(sock_fd, SOL_SOCKET, SO_DOMAIN, &domain, &opt_len))
		FAIL_ERRNO("getsockopt(SO_DOMAIN)");

	opt_len = sizeof(type);
	if (getsockopt(sock_fd, SOL_SOCKET, SO_TYPE, &type, &opt_len))
		FAIL_ERRNO("getsockopt(SO_TYPE)");

	switch (domain) {
	case AF_INET:
		switch (type) {
		case SOCK_STREAM:
			return "tcp4";
		case SOCK_DGRAM:
			return "udp4";
		}
		break;
	case AF_INET6:
		switch (type) {
		case SOCK_STREAM:
			return "tcp6";
		case SOCK_DGRAM:
			return "udp6";
		}
		break;
	case AF_UNIX:
		switch (type) {
		case SOCK_STREAM:
			return "u_str";
		case SOCK_DGRAM:
			return "u_dgr";
		case SOCK_SEQPACKET:
			return "u_seq";
		}
		break;
	case AF_VSOCK:
		switch (type) {
		case SOCK_STREAM:
			return "v_str";
		case SOCK_DGRAM:
			return "v_dgr";
		case SOCK_SEQPACKET:
			return "v_seq";
		}
		break;
	}

	return "???";
}

#endif // __SOCKET_HELPERS__
+11 −14
Original line number Diff line number Diff line
@@ -5,6 +5,9 @@

#define MAX_TEST_NAME 80

#define u32(v) ((u32){(v)})
#define u64(v) ((u64){(v)})

#define __always_unused	__attribute__((__unused__))

#define xbpf_map_delete_elem(fd, key)                                          \
@@ -66,21 +69,15 @@
		__ret;                                                         \
	})

static inline int add_to_sockmap(int sock_mapfd, int fd1, int fd2)
static inline int add_to_sockmap(int mapfd, int fd1, int fd2)
{
	u64 value;
	u32 key;
	int err;

	key = 0;
	value = fd1;
	err = xbpf_map_update_elem(sock_mapfd, &key, &value, BPF_NOEXIST);
	err = xbpf_map_update_elem(mapfd, &u32(0), &u64(fd1), BPF_NOEXIST);
	if (err)
		return err;

	key = 1;
	value = fd2;
	return xbpf_map_update_elem(sock_mapfd, &key, &value, BPF_NOEXIST);
	return xbpf_map_update_elem(mapfd, &u32(1), &u64(fd2), BPF_NOEXIST);
}

#endif // __SOCKMAP_HELPERS__
+0 −457
Original line number Diff line number Diff line
@@ -1366,237 +1366,6 @@ static void test_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
	}
}

static void pairs_redir_to_connected(int cli0, int peer0, int cli1, int peer1,
				     int sock_mapfd, int nop_mapfd,
				     int verd_mapfd, enum redir_mode mode,
				     int send_flags)
{
	const char *log_prefix = redir_mode_str(mode);
	unsigned int pass;
	int err, n;
	u32 key;
	char b;

	zero_verdict_count(verd_mapfd);

	err = add_to_sockmap(sock_mapfd, peer0, peer1);
	if (err)
		return;

	if (nop_mapfd >= 0) {
		err = add_to_sockmap(nop_mapfd, cli0, cli1);
		if (err)
			return;
	}

	/* Last byte is OOB data when send_flags has MSG_OOB bit set */
	n = xsend(cli1, "ab", 2, send_flags);
	if (n >= 0 && n < 2)
		FAIL("%s: incomplete send", log_prefix);
	if (n < 2)
		return;

	key = SK_PASS;
	err = xbpf_map_lookup_elem(verd_mapfd, &key, &pass);
	if (err)
		return;
	if (pass != 1)
		FAIL("%s: want pass count 1, have %d", log_prefix, pass);

	n = recv_timeout(mode == REDIR_INGRESS ? peer0 : cli0, &b, 1, 0, IO_TIMEOUT_SEC);
	if (n < 0)
		FAIL_ERRNO("%s: recv_timeout", log_prefix);
	if (n == 0)
		FAIL("%s: incomplete recv", log_prefix);

	if (send_flags & MSG_OOB) {
		/* Check that we can't read OOB while in sockmap */
		errno = 0;
		n = recv(peer1, &b, 1, MSG_OOB | MSG_DONTWAIT);
		if (n != -1 || errno != EOPNOTSUPP)
			FAIL("%s: recv(MSG_OOB): expected EOPNOTSUPP: retval=%d errno=%d",
			     log_prefix, n, errno);

		/* Remove peer1 from sockmap */
		xbpf_map_delete_elem(sock_mapfd, &(int){ 1 });

		/* Check that OOB was dropped on redirect */
		errno = 0;
		n = recv(peer1, &b, 1, MSG_OOB | MSG_DONTWAIT);
		if (n != -1 || errno != EINVAL)
			FAIL("%s: recv(MSG_OOB): expected EINVAL: retval=%d errno=%d",
			     log_prefix, n, errno);
	}
}

static void unix_redir_to_connected(int sotype, int sock_mapfd,
			       int verd_mapfd, enum redir_mode mode)
{
	int c0, c1, p0, p1;
	int sfd[2];

	if (socketpair(AF_UNIX, sotype | SOCK_NONBLOCK, 0, sfd))
		return;
	c0 = sfd[0], p0 = sfd[1];

	if (socketpair(AF_UNIX, sotype | SOCK_NONBLOCK, 0, sfd))
		goto close0;
	c1 = sfd[0], p1 = sfd[1];

	pairs_redir_to_connected(c0, p0, c1, p1, sock_mapfd, -1, verd_mapfd,
				 mode, NO_FLAGS);

	xclose(c1);
	xclose(p1);
close0:
	xclose(c0);
	xclose(p0);
}

static void unix_skb_redir_to_connected(struct test_sockmap_listen *skel,
					struct bpf_map *inner_map, int sotype)
{
	int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
	int verdict_map = bpf_map__fd(skel->maps.verdict_map);
	int sock_map = bpf_map__fd(inner_map);
	int err;

	err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
	if (err)
		return;

	skel->bss->test_ingress = false;
	unix_redir_to_connected(sotype, sock_map, verdict_map, REDIR_EGRESS);
	skel->bss->test_ingress = true;
	unix_redir_to_connected(sotype, sock_map, verdict_map, REDIR_INGRESS);

	xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
}

static void test_unix_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
			    int sotype)
{
	const char *family_name, *map_name;
	char s[MAX_TEST_NAME];

	family_name = family_str(AF_UNIX);
	map_name = map_type_str(map);
	snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, __func__);
	if (!test__start_subtest(s))
		return;
	unix_skb_redir_to_connected(skel, map, sotype);
}

/* Returns two connected loopback vsock sockets */
static int vsock_socketpair_connectible(int sotype, int *v0, int *v1)
{
	return create_pair(AF_VSOCK, sotype | SOCK_NONBLOCK, v0, v1);
}

static void vsock_unix_redir_connectible(int sock_mapfd, int verd_mapfd,
					 enum redir_mode mode, int sotype)
{
	const char *log_prefix = redir_mode_str(mode);
	char a = 'a', b = 'b';
	int u0, u1, v0, v1;
	int sfd[2];
	unsigned int pass;
	int err, n;
	u32 key;

	zero_verdict_count(verd_mapfd);

	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sfd))
		return;

	u0 = sfd[0];
	u1 = sfd[1];

	err = vsock_socketpair_connectible(sotype, &v0, &v1);
	if (err) {
		FAIL("vsock_socketpair_connectible() failed");
		goto close_uds;
	}

	err = add_to_sockmap(sock_mapfd, u0, v0);
	if (err) {
		FAIL("add_to_sockmap failed");
		goto close_vsock;
	}

	n = write(v1, &a, sizeof(a));
	if (n < 0)
		FAIL_ERRNO("%s: write", log_prefix);
	if (n == 0)
		FAIL("%s: incomplete write", log_prefix);
	if (n < 1)
		goto out;

	n = xrecv_nonblock(mode == REDIR_INGRESS ? u0 : u1, &b, sizeof(b), 0);
	if (n < 0)
		FAIL("%s: recv() err, errno=%d", log_prefix, errno);
	if (n == 0)
		FAIL("%s: incomplete recv", log_prefix);
	if (b != a)
		FAIL("%s: vsock socket map failed, %c != %c", log_prefix, a, b);

	key = SK_PASS;
	err = xbpf_map_lookup_elem(verd_mapfd, &key, &pass);
	if (err)
		goto out;
	if (pass != 1)
		FAIL("%s: want pass count 1, have %d", log_prefix, pass);
out:
	key = 0;
	bpf_map_delete_elem(sock_mapfd, &key);
	key = 1;
	bpf_map_delete_elem(sock_mapfd, &key);

close_vsock:
	close(v0);
	close(v1);

close_uds:
	close(u0);
	close(u1);
}

static void vsock_unix_skb_redir_connectible(struct test_sockmap_listen *skel,
					     struct bpf_map *inner_map,
					     int sotype)
{
	int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
	int verdict_map = bpf_map__fd(skel->maps.verdict_map);
	int sock_map = bpf_map__fd(inner_map);
	int err;

	err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
	if (err)
		return;

	skel->bss->test_ingress = false;
	vsock_unix_redir_connectible(sock_map, verdict_map, REDIR_EGRESS, sotype);
	skel->bss->test_ingress = true;
	vsock_unix_redir_connectible(sock_map, verdict_map, REDIR_INGRESS, sotype);

	xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
}

static void test_vsock_redir(struct test_sockmap_listen *skel, struct bpf_map *map)
{
	const char *family_name, *map_name;
	char s[MAX_TEST_NAME];

	family_name = family_str(AF_VSOCK);
	map_name = map_type_str(map);
	snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, __func__);
	if (!test__start_subtest(s))
		return;

	vsock_unix_skb_redir_connectible(skel, map, SOCK_STREAM);
	vsock_unix_skb_redir_connectible(skel, map, SOCK_SEQPACKET);
}

static void test_reuseport(struct test_sockmap_listen *skel,
			   struct bpf_map *map, int family, int sotype)
{
@@ -1637,224 +1406,6 @@ static void test_reuseport(struct test_sockmap_listen *skel,
	}
}

static int inet_socketpair(int family, int type, int *s, int *c)
{
	return create_pair(family, type | SOCK_NONBLOCK, s, c);
}

static void udp_redir_to_connected(int family, int sock_mapfd, int verd_mapfd,
				   enum redir_mode mode)
{
	int c0, c1, p0, p1;
	int err;

	err = inet_socketpair(family, SOCK_DGRAM, &p0, &c0);
	if (err)
		return;
	err = inet_socketpair(family, SOCK_DGRAM, &p1, &c1);
	if (err)
		goto close_cli0;

	pairs_redir_to_connected(c0, p0, c1, p1, sock_mapfd, -1, verd_mapfd,
				 mode, NO_FLAGS);

	xclose(c1);
	xclose(p1);
close_cli0:
	xclose(c0);
	xclose(p0);
}

static void udp_skb_redir_to_connected(struct test_sockmap_listen *skel,
				       struct bpf_map *inner_map, int family)
{
	int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
	int verdict_map = bpf_map__fd(skel->maps.verdict_map);
	int sock_map = bpf_map__fd(inner_map);
	int err;

	err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
	if (err)
		return;

	skel->bss->test_ingress = false;
	udp_redir_to_connected(family, sock_map, verdict_map, REDIR_EGRESS);
	skel->bss->test_ingress = true;
	udp_redir_to_connected(family, sock_map, verdict_map, REDIR_INGRESS);

	xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
}

static void test_udp_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
			   int family)
{
	const char *family_name, *map_name;
	char s[MAX_TEST_NAME];

	family_name = family_str(family);
	map_name = map_type_str(map);
	snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, __func__);
	if (!test__start_subtest(s))
		return;
	udp_skb_redir_to_connected(skel, map, family);
}

static void inet_unix_redir_to_connected(int family, int type, int sock_mapfd,
					int verd_mapfd, enum redir_mode mode)
{
	int c0, c1, p0, p1;
	int sfd[2];
	int err;

	if (socketpair(AF_UNIX, type | SOCK_NONBLOCK, 0, sfd))
		return;
	c0 = sfd[0], p0 = sfd[1];

	err = inet_socketpair(family, type, &p1, &c1);
	if (err)
		goto close;

	pairs_redir_to_connected(c0, p0, c1, p1, sock_mapfd, -1, verd_mapfd,
				 mode, NO_FLAGS);

	xclose(c1);
	xclose(p1);
close:
	xclose(c0);
	xclose(p0);
}

static void inet_unix_skb_redir_to_connected(struct test_sockmap_listen *skel,
					    struct bpf_map *inner_map, int family)
{
	int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
	int verdict_map = bpf_map__fd(skel->maps.verdict_map);
	int sock_map = bpf_map__fd(inner_map);
	int err;

	err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
	if (err)
		return;

	skel->bss->test_ingress = false;
	inet_unix_redir_to_connected(family, SOCK_DGRAM, sock_map, verdict_map,
				    REDIR_EGRESS);
	inet_unix_redir_to_connected(family, SOCK_STREAM, sock_map, verdict_map,
				    REDIR_EGRESS);
	skel->bss->test_ingress = true;
	inet_unix_redir_to_connected(family, SOCK_DGRAM, sock_map, verdict_map,
				    REDIR_INGRESS);
	inet_unix_redir_to_connected(family, SOCK_STREAM, sock_map, verdict_map,
				    REDIR_INGRESS);

	xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
}

static void unix_inet_redir_to_connected(int family, int type, int sock_mapfd,
					 int nop_mapfd, int verd_mapfd,
					 enum redir_mode mode, int send_flags)
{
	int c0, c1, p0, p1;
	int sfd[2];
	int err;

	err = inet_socketpair(family, type, &p0, &c0);
	if (err)
		return;

	if (socketpair(AF_UNIX, type | SOCK_NONBLOCK, 0, sfd))
		goto close_cli0;
	c1 = sfd[0], p1 = sfd[1];

	pairs_redir_to_connected(c0, p0, c1, p1, sock_mapfd, nop_mapfd,
				 verd_mapfd, mode, send_flags);

	xclose(c1);
	xclose(p1);
close_cli0:
	xclose(c0);
	xclose(p0);
}

static void unix_inet_skb_redir_to_connected(struct test_sockmap_listen *skel,
					    struct bpf_map *inner_map, int family)
{
	int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
	int nop_map = bpf_map__fd(skel->maps.nop_map);
	int verdict_map = bpf_map__fd(skel->maps.verdict_map);
	int sock_map = bpf_map__fd(inner_map);
	int err;

	err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
	if (err)
		return;

	skel->bss->test_ingress = false;
	unix_inet_redir_to_connected(family, SOCK_DGRAM,
				     sock_map, -1, verdict_map,
				     REDIR_EGRESS, NO_FLAGS);
	unix_inet_redir_to_connected(family, SOCK_STREAM,
				     sock_map, -1, verdict_map,
				     REDIR_EGRESS, NO_FLAGS);

	unix_inet_redir_to_connected(family, SOCK_DGRAM,
				     sock_map, nop_map, verdict_map,
				     REDIR_EGRESS, NO_FLAGS);
	unix_inet_redir_to_connected(family, SOCK_STREAM,
				     sock_map, nop_map, verdict_map,
				     REDIR_EGRESS, NO_FLAGS);

	/* MSG_OOB not supported by AF_UNIX SOCK_DGRAM */
	unix_inet_redir_to_connected(family, SOCK_STREAM,
				     sock_map, nop_map, verdict_map,
				     REDIR_EGRESS, MSG_OOB);

	skel->bss->test_ingress = true;
	unix_inet_redir_to_connected(family, SOCK_DGRAM,
				     sock_map, -1, verdict_map,
				     REDIR_INGRESS, NO_FLAGS);
	unix_inet_redir_to_connected(family, SOCK_STREAM,
				     sock_map, -1, verdict_map,
				     REDIR_INGRESS, NO_FLAGS);

	unix_inet_redir_to_connected(family, SOCK_DGRAM,
				     sock_map, nop_map, verdict_map,
				     REDIR_INGRESS, NO_FLAGS);
	unix_inet_redir_to_connected(family, SOCK_STREAM,
				     sock_map, nop_map, verdict_map,
				     REDIR_INGRESS, NO_FLAGS);

	/* MSG_OOB not supported by AF_UNIX SOCK_DGRAM */
	unix_inet_redir_to_connected(family, SOCK_STREAM,
				     sock_map, nop_map, verdict_map,
				     REDIR_INGRESS, MSG_OOB);

	xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
}

static void test_udp_unix_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
				int family)
{
	const char *family_name, *map_name;
	struct netns_obj *netns;
	char s[MAX_TEST_NAME];

	family_name = family_str(family);
	map_name = map_type_str(map);
	snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, __func__);
	if (!test__start_subtest(s))
		return;

	netns = netns_new("sockmap_listen", true);
	if (!ASSERT_OK_PTR(netns, "netns_new"))
		return;

	inet_unix_skb_redir_to_connected(skel, map, family);
	unix_inet_skb_redir_to_connected(skel, map, family);

	netns_free(netns);
}

static void run_tests(struct test_sockmap_listen *skel, struct bpf_map *map,
		      int family)
{
@@ -1863,8 +1414,6 @@ static void run_tests(struct test_sockmap_listen *skel, struct bpf_map *map,
	test_redir(skel, map, family, SOCK_STREAM);
	test_reuseport(skel, map, family, SOCK_STREAM);
	test_reuseport(skel, map, family, SOCK_DGRAM);
	test_udp_redir(skel, map, family);
	test_udp_unix_redir(skel, map, family);
}

void serial_test_sockmap_listen(void)
@@ -1880,16 +1429,10 @@ void serial_test_sockmap_listen(void)
	skel->bss->test_sockmap = true;
	run_tests(skel, skel->maps.sock_map, AF_INET);
	run_tests(skel, skel->maps.sock_map, AF_INET6);
	test_unix_redir(skel, skel->maps.sock_map, SOCK_DGRAM);
	test_unix_redir(skel, skel->maps.sock_map, SOCK_STREAM);
	test_vsock_redir(skel, skel->maps.sock_map);

	skel->bss->test_sockmap = false;
	run_tests(skel, skel->maps.sock_hash, AF_INET);
	run_tests(skel, skel->maps.sock_hash, AF_INET6);
	test_unix_redir(skel, skel->maps.sock_hash, SOCK_DGRAM);
	test_unix_redir(skel, skel->maps.sock_hash, SOCK_STREAM);
	test_vsock_redir(skel, skel->maps.sock_hash);

	test_sockmap_listen__destroy(skel);
}
+465 −0

File added.

Preview size limit exceeded, changes collapsed.

+68 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"

SEC(".maps") struct {
	__uint(type, BPF_MAP_TYPE_SOCKMAP);
	__uint(max_entries, 1);
	__type(key, __u32);
	__type(value, __u64);
} nop_map, sock_map;

SEC(".maps") struct {
	__uint(type, BPF_MAP_TYPE_SOCKHASH);
	__uint(max_entries, 1);
	__type(key, __u32);
	__type(value, __u64);
} nop_hash, sock_hash;

SEC(".maps") struct {
	__uint(type, BPF_MAP_TYPE_ARRAY);
	__uint(max_entries, 2);
	__type(key, int);
	__type(value, unsigned int);
} verdict_map;

/* Set by user space */
int redirect_type;
int redirect_flags;

#define redirect_map(__data)                                                   \
	_Generic((__data),                                                     \
		 struct __sk_buff * : bpf_sk_redirect_map,                     \
		 struct sk_msg_md * : bpf_msg_redirect_map                     \
	)((__data), &sock_map, (__u32){0}, redirect_flags)

#define redirect_hash(__data)                                                  \
	_Generic((__data),                                                     \
		 struct __sk_buff * : bpf_sk_redirect_hash,                    \
		 struct sk_msg_md * : bpf_msg_redirect_hash                    \
	)((__data), &sock_hash, &(__u32){0}, redirect_flags)

#define DEFINE_PROG(__type, __param)                                           \
SEC("sk_" XSTR(__type))                                                        \
int prog_ ## __type ## _verdict(__param data)                                  \
{                                                                              \
	unsigned int *count;                                                   \
	int verdict;                                                           \
									       \
	if (redirect_type == BPF_MAP_TYPE_SOCKMAP)                             \
		verdict = redirect_map(data);                                  \
	else if (redirect_type == BPF_MAP_TYPE_SOCKHASH)                       \
		verdict = redirect_hash(data);                                 \
	else                                                                   \
		verdict = redirect_type - __MAX_BPF_MAP_TYPE;                  \
									       \
	count = bpf_map_lookup_elem(&verdict_map, &verdict);                   \
	if (count)                                                             \
		(*count)++;                                                    \
									       \
	return verdict;                                                        \
}

DEFINE_PROG(skb, struct __sk_buff *);
DEFINE_PROG(msg, struct sk_msg_md *);

char _license[] SEC("license") = "GPL";