mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
selftests/bpf: Add bpf_icmp_send kfunc cgroup_skb IPv6 tests
This test extends the existing cgroup_skb tests with IPv6 support. Note that we need to set IPV6_RECVERR on the socket for IPv6 in connect_to_fd_nonblock otherwise the error will be ignored even if we are in the middle of the TCP handshake. See in net/ipv6/datagram.c:ipv6_icmp_error for more details. Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Reviewed-by: Jordan Rife <jordan@jrife.io> Acked-by: Stanislav Fomichev <sdf@fomichev.me> Link: https://lore.kernel.org/bpf/20260709144900.245904-4-mahe.tardy@gmail.com
This commit is contained in:
parent
39b337a3d9
commit
340a40df94
|
|
@ -8,9 +8,11 @@
|
|||
#define TIMEOUT_MS 1000
|
||||
|
||||
#define ICMP_DEST_UNREACH 3
|
||||
#define ICMPV6_DEST_UNREACH 1
|
||||
|
||||
#define ICMP_FRAG_NEEDED 4
|
||||
#define NR_ICMP_UNREACH 15
|
||||
#define ICMPV6_REJECT_ROUTE 6
|
||||
|
||||
#define KFUNC_RET_UNSET -1
|
||||
|
||||
|
|
@ -18,7 +20,7 @@ static int connect_to_fd_nonblock(int server_fd)
|
|||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
int fd, err;
|
||||
int fd, err, on = 1;
|
||||
|
||||
if (getsockname(server_fd, (struct sockaddr *)&addr, &len))
|
||||
return -1;
|
||||
|
|
@ -27,6 +29,12 @@ static int connect_to_fd_nonblock(int server_fd)
|
|||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
if (addr.ss_family == AF_INET6 &&
|
||||
setsockopt(fd, IPPROTO_IPV6, IPV6_RECVERR, &on, sizeof(on)) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = connect(fd, (struct sockaddr *)&addr, len);
|
||||
if (err < 0 && errno != EINPROGRESS) {
|
||||
close(fd);
|
||||
|
|
@ -36,8 +44,14 @@ static int connect_to_fd_nonblock(int server_fd)
|
|||
return fd;
|
||||
}
|
||||
|
||||
static void read_icmp_errqueue(int sockfd, int expected_code)
|
||||
static void read_icmp_errqueue(int sockfd, int expected_code, int af)
|
||||
{
|
||||
int expected_ee_type = (af == AF_INET) ? ICMP_DEST_UNREACH :
|
||||
ICMPV6_DEST_UNREACH;
|
||||
int expected_origin = (af == AF_INET) ? SO_EE_ORIGIN_ICMP :
|
||||
SO_EE_ORIGIN_ICMP6;
|
||||
int expected_level = (af == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
|
||||
int expected_type = (af == AF_INET) ? IP_RECVERR : IPV6_RECVERR;
|
||||
struct sock_extended_err *sock_err;
|
||||
char ctrl_buf[512];
|
||||
struct msghdr msg = {
|
||||
|
|
@ -63,38 +77,43 @@ static void read_icmp_errqueue(int sockfd, int expected_code)
|
|||
return;
|
||||
|
||||
for (; cm; cm = CMSG_NXTHDR(&msg, cm)) {
|
||||
if (cm->cmsg_level != IPPROTO_IP || cm->cmsg_type != IP_RECVERR)
|
||||
if (cm->cmsg_level != expected_level ||
|
||||
cm->cmsg_type != expected_type)
|
||||
continue;
|
||||
|
||||
sock_err = (struct sock_extended_err *)CMSG_DATA(cm);
|
||||
|
||||
if (!ASSERT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP,
|
||||
"sock_err_origin_icmp"))
|
||||
if (!ASSERT_EQ(sock_err->ee_origin, expected_origin,
|
||||
"sock_err_origin"))
|
||||
return;
|
||||
if (!ASSERT_EQ(sock_err->ee_type, ICMP_DEST_UNREACH,
|
||||
if (!ASSERT_EQ(sock_err->ee_type, expected_ee_type,
|
||||
"sock_err_type_dest_unreach"))
|
||||
return;
|
||||
ASSERT_EQ(sock_err->ee_code, expected_code, "sock_err_code");
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_FAIL("no IP_RECVERR control message found");
|
||||
ASSERT_FAIL("no IP_RECVERR/IPV6_RECVERR control message found");
|
||||
}
|
||||
|
||||
static bool valid_unreach_code(int code)
|
||||
static bool valid_unreach_code(int code, int af)
|
||||
{
|
||||
if (code < 0)
|
||||
return false;
|
||||
|
||||
return code <= NR_ICMP_UNREACH && code != ICMP_FRAG_NEEDED;
|
||||
if (af == AF_INET)
|
||||
return code <= NR_ICMP_UNREACH && code != ICMP_FRAG_NEEDED;
|
||||
|
||||
return code <= ICMPV6_REJECT_ROUTE;
|
||||
}
|
||||
|
||||
static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code)
|
||||
static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code,
|
||||
int af, const char *ip)
|
||||
{
|
||||
int srv_fd = -1, client_fd = -1;
|
||||
int port;
|
||||
|
||||
srv_fd = start_server(AF_INET, SOCK_STREAM, "127.0.0.1", 0, TIMEOUT_MS);
|
||||
srv_fd = start_server(af, SOCK_STREAM, ip, 0, TIMEOUT_MS);
|
||||
if (!ASSERT_OK_FD(srv_fd, "start_server"))
|
||||
return;
|
||||
|
||||
|
|
@ -105,6 +124,8 @@ static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code)
|
|||
}
|
||||
|
||||
skel->bss->server_port = ntohs(port);
|
||||
skel->bss->unreach_type = (af == AF_INET) ? ICMP_DEST_UNREACH :
|
||||
ICMPV6_DEST_UNREACH;
|
||||
skel->bss->unreach_code = code;
|
||||
skel->data->kfunc_ret = KFUNC_RET_UNSET;
|
||||
|
||||
|
|
@ -114,13 +135,37 @@ static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code)
|
|||
return;
|
||||
}
|
||||
|
||||
if (valid_unreach_code(code))
|
||||
read_icmp_errqueue(client_fd, code);
|
||||
if (valid_unreach_code(code, af))
|
||||
read_icmp_errqueue(client_fd, code, af);
|
||||
|
||||
close(client_fd);
|
||||
close(srv_fd);
|
||||
}
|
||||
|
||||
static void run_icmp_test(struct icmp_send *skel, int af, const char *ip,
|
||||
int max_code)
|
||||
{
|
||||
for (int code = 0; code <= max_code; code++) {
|
||||
if (af == AF_INET && code == ICMP_FRAG_NEEDED)
|
||||
continue;
|
||||
|
||||
trigger_prog_read_icmp_errqueue(skel, code, af, ip);
|
||||
ASSERT_EQ(skel->data->kfunc_ret, 0, "kfunc_ret");
|
||||
}
|
||||
|
||||
/* Test invalid codes */
|
||||
trigger_prog_read_icmp_errqueue(skel, -1, af, ip);
|
||||
ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
|
||||
|
||||
trigger_prog_read_icmp_errqueue(skel, max_code + 1, af, ip);
|
||||
ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
|
||||
|
||||
if (af == AF_INET) {
|
||||
trigger_prog_read_icmp_errqueue(skel, ICMP_FRAG_NEEDED, af, ip);
|
||||
ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
|
||||
}
|
||||
}
|
||||
|
||||
void test_icmp_send_unreach_cgroup(void)
|
||||
{
|
||||
struct icmp_send *skel;
|
||||
|
|
@ -139,23 +184,11 @@ void test_icmp_send_unreach_cgroup(void)
|
|||
if (!ASSERT_OK_PTR(skel->links.egress, "prog_attach_cgroup"))
|
||||
goto cleanup;
|
||||
|
||||
for (int code = 0; code <= NR_ICMP_UNREACH; code++) {
|
||||
if (code == ICMP_FRAG_NEEDED)
|
||||
continue;
|
||||
if (test__start_subtest("ipv4"))
|
||||
run_icmp_test(skel, AF_INET, "127.0.0.1", NR_ICMP_UNREACH);
|
||||
|
||||
trigger_prog_read_icmp_errqueue(skel, code);
|
||||
ASSERT_EQ(skel->data->kfunc_ret, 0, "kfunc_ret");
|
||||
}
|
||||
|
||||
/* Test invalid codes */
|
||||
trigger_prog_read_icmp_errqueue(skel, -1);
|
||||
ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
|
||||
|
||||
trigger_prog_read_icmp_errqueue(skel, NR_ICMP_UNREACH + 1);
|
||||
ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
|
||||
|
||||
trigger_prog_read_icmp_errqueue(skel, ICMP_FRAG_NEEDED);
|
||||
ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
|
||||
if (test__start_subtest("ipv6"))
|
||||
run_icmp_test(skel, AF_INET6, "::1", ICMPV6_REJECT_ROUTE);
|
||||
|
||||
cleanup:
|
||||
icmp_send__destroy(skel);
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
|
||||
/* 127.0.0.1 in host byte order */
|
||||
#define SERVER_IP 0x7F000001
|
||||
|
||||
#define ICMP_DEST_UNREACH 3
|
||||
/* ::1 in host byte order (last 32-bit word) */
|
||||
#define SERVER_IP6_LO 0x00000001
|
||||
|
||||
__u16 server_port = 0;
|
||||
int unreach_type = 0;
|
||||
int unreach_code = 0;
|
||||
int kfunc_ret = -1;
|
||||
|
||||
|
|
@ -18,19 +19,48 @@ int egress(struct __sk_buff *skb)
|
|||
void *data = (void *)(long)skb->data;
|
||||
void *data_end = (void *)(long)skb->data_end;
|
||||
struct iphdr *iph;
|
||||
struct ipv6hdr *ip6h;
|
||||
struct tcphdr *tcph;
|
||||
__u8 version;
|
||||
|
||||
iph = data;
|
||||
if ((void *)(iph + 1) > data_end || iph->version != 4 ||
|
||||
iph->protocol != IPPROTO_TCP || iph->daddr != bpf_htonl(SERVER_IP))
|
||||
if (data + 1 > data_end)
|
||||
return SK_PASS;
|
||||
|
||||
tcph = (void *)iph + iph->ihl * 4;
|
||||
if ((void *)(tcph + 1) > data_end ||
|
||||
tcph->dest != bpf_htons(server_port))
|
||||
return SK_PASS;
|
||||
version = (*((__u8 *)data)) >> 4;
|
||||
|
||||
kfunc_ret = bpf_icmp_send(skb, ICMP_DEST_UNREACH, unreach_code);
|
||||
if (version == 4) {
|
||||
iph = data;
|
||||
if ((void *)(iph + 1) > data_end ||
|
||||
iph->protocol != IPPROTO_TCP ||
|
||||
iph->daddr != bpf_htonl(SERVER_IP))
|
||||
return SK_PASS;
|
||||
|
||||
tcph = (void *)iph + iph->ihl * 4;
|
||||
if ((void *)(tcph + 1) > data_end ||
|
||||
tcph->dest != bpf_htons(server_port))
|
||||
return SK_PASS;
|
||||
|
||||
} else if (version == 6) {
|
||||
ip6h = data;
|
||||
if ((void *)(ip6h + 1) > data_end ||
|
||||
ip6h->nexthdr != IPPROTO_TCP)
|
||||
return SK_PASS;
|
||||
|
||||
if (ip6h->daddr.in6_u.u6_addr32[0] != 0 ||
|
||||
ip6h->daddr.in6_u.u6_addr32[1] != 0 ||
|
||||
ip6h->daddr.in6_u.u6_addr32[2] != 0 ||
|
||||
ip6h->daddr.in6_u.u6_addr32[3] != bpf_htonl(SERVER_IP6_LO))
|
||||
return SK_PASS;
|
||||
|
||||
tcph = (void *)(ip6h + 1);
|
||||
if ((void *)(tcph + 1) > data_end ||
|
||||
tcph->dest != bpf_htons(server_port))
|
||||
return SK_PASS;
|
||||
} else {
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
kfunc_ret = bpf_icmp_send(skb, unreach_type, unreach_code);
|
||||
|
||||
return SK_DROP;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user