* [PATCH bpf-next v5 1/4] bpf: add bpf_icmp_send kfunc
2026-05-15 19:47 [PATCH bpf-next v5 0/4] bpf: add icmp_send kfunc Mahe Tardy
@ 2026-05-15 19:47 ` Mahe Tardy
2026-05-15 20:23 ` sashiko-bot
2026-05-15 19:47 ` [PATCH bpf-next v5 2/4] selftests/bpf: add bpf_icmp_send kfunc tests Mahe Tardy
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Mahe Tardy @ 2026-05-15 19:47 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, Mahe Tardy
This is needed in the context of Tetragon to provide improved feedback
(in contrast to just dropping packets) to east-west traffic when blocked
by policies using cgroup_skb programs. We also extend this kfunc to tc
program as a convenience.
This reuses concepts from netfilter reject target codepath with the
differences that:
* Packets are cloned since the BPF user can still let the packet pass
(SK_PASS from the cgroup_skb progs for example) and the current skb
need to stay untouched (cgroup_skb hooks only allow read-only skb
payload).
* Packet should be already routed, checksums are not computed or
verified and IPv4 fragmentation is not checked early because
cgroup_skb and tc programs are called later in the network stack
compared to what netfilter could do.
* We protect against recursion since the kfunc, by generating an ICMP
error message, could retrigger the BPF prog that invoked it.
* Additionally, we ensure the IP headers are linearized before
processing with pskb_network_may_pull(), and zero out the SKB control
block after cloning to prevent icmp_send()/icmpv6_send() from
misinterpreting garbage data as IP options. This should be mostly for
tc ingress progs.
Only ICMP_DEST_UNREACH and ICMPV6_DEST_UNREACH are currently supported.
The interface accepts a type parameter to facilitate future extension to
other ICMP control message types.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
net/core/filter.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 103 insertions(+)
diff --git a/net/core/filter.c b/net/core/filter.c
index 9590877b0714..a1fe20b45f29 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -84,6 +84,8 @@
#include <linux/un.h>
#include <net/xdp_sock_drv.h>
#include <net/inet_dscp.h>
+#include <linux/icmpv6.h>
+#include <net/icmp.h>
#include "dev.h"
@@ -12464,6 +12466,95 @@ __bpf_kfunc int bpf_xdp_pull_data(struct xdp_md *x, u32 len)
return 0;
}
+static DEFINE_PER_CPU(bool, bpf_icmp_send_in_progress);
+
+/**
+ * bpf_icmp_send - Send an ICMP control message
+ * @skb: Packet that triggered the control message
+ * @type: ICMP type (only ICMP_DEST_UNREACH/ICMPV6_DEST_UNREACH supported)
+ * @code: ICMP code (0-15 for IPv4, 0-6 for IPv6)
+ *
+ * Sends an ICMP control message in response to the packet. The original packet
+ * is cloned before sending the ICMP message, so the BPF program can still let
+ * the packet pass if desired.
+ *
+ * Currently only ICMP_DEST_UNREACH (IPv4) and ICMPV6_DEST_UNREACH (IPv6) are
+ * supported.
+ *
+ * Recursion protection: If called from a context that would trigger recursion
+ * (e.g., root cgroup processing its own ICMP packets), returns -EBUSY on
+ * re-entry.
+ *
+ * Return: 0 on success, negative error code on failure:
+ * -EINVAL: Invalid code parameter
+ * -EBADMSG: Packet too short or malformed
+ * -ENOMEM: Memory allocation failed
+ * -EBUSY: Recursion detected
+ * -EPROTONOSUPPORT: Non-IP protocol
+ * -EOPNOTSUPP: Unsupported ICMP type
+ */
+__bpf_kfunc int bpf_icmp_send(struct __sk_buff *__skb, int type, int code)
+{
+ struct sk_buff *skb = (struct sk_buff *)__skb;
+ struct sk_buff *nskb;
+ bool *in_progress;
+
+ in_progress = this_cpu_ptr(&bpf_icmp_send_in_progress);
+ if (*in_progress)
+ return -EBUSY;
+
+ switch (skb->protocol) {
+#if IS_ENABLED(CONFIG_INET)
+ case htons(ETH_P_IP):
+ if (type != ICMP_DEST_UNREACH)
+ return -EOPNOTSUPP;
+ if (code < 0 || code > NR_ICMP_UNREACH)
+ return -EINVAL;
+
+ if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
+ return -EBADMSG;
+
+ nskb = skb_clone(skb, GFP_ATOMIC);
+ if (!nskb)
+ return -ENOMEM;
+
+ memset(IPCB(nskb), 0, sizeof(struct inet_skb_parm));
+
+ *in_progress = true;
+ icmp_send(nskb, type, code, 0);
+ *in_progress = false;
+ kfree_skb(nskb);
+ break;
+#endif
+#if IS_ENABLED(CONFIG_IPV6)
+ case htons(ETH_P_IPV6):
+ if (type != ICMPV6_DEST_UNREACH)
+ return -EOPNOTSUPP;
+ if (code < 0 || code > ICMPV6_REJECT_ROUTE)
+ return -EINVAL;
+
+ if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
+ return -EBADMSG;
+
+ nskb = skb_clone(skb, GFP_ATOMIC);
+ if (!nskb)
+ return -ENOMEM;
+
+ memset(IP6CB(nskb), 0, sizeof(struct inet6_skb_parm));
+
+ *in_progress = true;
+ icmpv6_send(nskb, type, code, 0);
+ *in_progress = false;
+ kfree_skb(nskb);
+ break;
+#endif
+ default:
+ return -EPROTONOSUPPORT;
+ }
+
+ return 0;
+}
+
__bpf_kfunc_end_defs();
int bpf_dynptr_from_skb_rdonly(struct __sk_buff *skb, u64 flags,
@@ -12506,6 +12597,10 @@ BTF_KFUNCS_START(bpf_kfunc_check_set_sock_ops)
BTF_ID_FLAGS(func, bpf_sock_ops_enable_tx_tstamp)
BTF_KFUNCS_END(bpf_kfunc_check_set_sock_ops)
+BTF_KFUNCS_START(bpf_kfunc_check_set_icmp_send)
+BTF_ID_FLAGS(func, bpf_icmp_send)
+BTF_KFUNCS_END(bpf_kfunc_check_set_icmp_send)
+
static const struct btf_kfunc_id_set bpf_kfunc_set_skb = {
.owner = THIS_MODULE,
.set = &bpf_kfunc_check_set_skb,
@@ -12536,6 +12631,11 @@ static const struct btf_kfunc_id_set bpf_kfunc_set_sock_ops = {
.set = &bpf_kfunc_check_set_sock_ops,
};
+static const struct btf_kfunc_id_set bpf_kfunc_set_icmp_send = {
+ .owner = THIS_MODULE,
+ .set = &bpf_kfunc_check_set_icmp_send,
+};
+
static int __init bpf_kfunc_init(void)
{
int ret;
@@ -12557,6 +12657,9 @@ static int __init bpf_kfunc_init(void)
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
&bpf_kfunc_set_sock_addr);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_kfunc_set_tcp_reqsk);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SKB, &bpf_kfunc_set_icmp_send);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_kfunc_set_icmp_send);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_ACT, &bpf_kfunc_set_icmp_send);
return ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SOCK_OPS, &bpf_kfunc_set_sock_ops);
}
late_initcall(bpf_kfunc_init);
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH bpf-next v5 1/4] bpf: add bpf_icmp_send kfunc
2026-05-15 19:47 ` [PATCH bpf-next v5 1/4] bpf: add bpf_icmp_send kfunc Mahe Tardy
@ 2026-05-15 20:23 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-05-15 20:23 UTC (permalink / raw)
To: Mahe Tardy; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] Calling pskb_network_may_pull() on the original skb before cloning modifies the SKB layout, causing BPF packet pointer Use-After-Free and panicking on shared SKBs.
- [High] IPv4 ICMP generation fails silently in TC ingress due to a missing routing table entry.
- [Medium] Unsafe use of a per-CPU recursion protection flag without disabling preemption or bottom-halves causes spurious failures.
--
commit a8e749b3f92e491c40d461bbeec717cff3d92cd2
Author: Mahe Tardy <mahe.tardy@gmail.com>
One line subject: bpf: add bpf_icmp_send kfunc
This commit adds the bpf_icmp_send kfunc, allowing cgroup_skb and tc
programs to generate ICMP error messages instead of silently dropping
packets. It clones the skb to preserve the original buffer, checks bounds
with pskb_network_may_pull, and relies on icmp_send or icmpv6_send to
transmit the error message.
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 9590877b0714f..a1fe20b45f29a 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
[ ... ]
> @@ -12464,6 +12466,95 @@ __bpf_kfunc int bpf_xdp_pull_data(struct xdp_md *x, u32 len)
> return 0;
> }
>
> +static DEFINE_PER_CPU(bool, bpf_icmp_send_in_progress);
> +
> +/**
> + * bpf_icmp_send - Send an ICMP control message
> + * @skb: Packet that triggered the control message
> + * @type: ICMP type (only ICMP_DEST_UNREACH/ICMPV6_DEST_UNREACH supported)
[ ... ]
> +__bpf_kfunc int bpf_icmp_send(struct __sk_buff *__skb, int type, int code)
> +{
> + struct sk_buff *skb = (struct sk_buff *)__skb;
> + struct sk_buff *nskb;
> + bool *in_progress;
> +
> + in_progress = this_cpu_ptr(&bpf_icmp_send_in_progress);
> + if (*in_progress)
> + return -EBUSY;
Since cgroup_skb egress programs execute in process context, they can be
interrupted by network RX softirqs on the same CPU.
If a task sets *in_progress to true and is immediately preempted by a softirq
that runs a TC ingress program calling bpf_icmp_send(), will it read the same
per-CPU flag and spuriously fail with -EBUSY?
Does this critical section need to run with bottom-halves disabled?
> +
> + switch (skb->protocol) {
> +#if IS_ENABLED(CONFIG_INET)
> + case htons(ETH_P_IP):
> + if (type != ICMP_DEST_UNREACH)
> + return -EOPNOTSUPP;
> + if (code < 0 || code > NR_ICMP_UNREACH)
> + return -EINVAL;
> +
> + if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
> + return -EBADMSG;
Does calling pskb_network_may_pull() on the original skb before cloning modify
the packet layout? If the headers span into paged fragments, this could
trigger reallocation of the linear data buffer via pskb_expand_head().
Since this kfunc lacks flags like KF_INVALIDATE_PKT_PTRS, could the BPF
verifier allow programs to retain and dereference stale direct packet
pointers?
Additionally, if the original skb is shared (which is common in tc and
cgroup_skb), would pskb_expand_head() hit a BUG_ON(skb_shared(skb)) and panic
the kernel? Should the packet be cloned first before pulling headers?
> +
> + nskb = skb_clone(skb, GFP_ATOMIC);
> + if (!nskb)
> + return -ENOMEM;
> +
> + memset(IPCB(nskb), 0, sizeof(struct inet_skb_parm));
> +
> + *in_progress = true;
> + icmp_send(nskb, type, code, 0);
The commit message notes this is "mostly for tc ingress progs". Since TC
ingress happens before IP routing, skb_rtable(nskb) will be NULL here.
Looking at __icmp_send(), it checks for a routing entry and aborts early if
missing:
net/ipv4/icmp.c:__icmp_send() {
...
rt = skb_rtable(skb_in);
if (!rt)
goto out;
...
}
Will this cause IPv4 ICMP generation to silently fail for TC ingress programs?
> + *in_progress = false;
> + kfree_skb(nskb);
> + break;
> +#endif
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515194746.50920-1-mahe.tardy@gmail.com?part=1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v5 2/4] selftests/bpf: add bpf_icmp_send kfunc tests
2026-05-15 19:47 [PATCH bpf-next v5 0/4] bpf: add icmp_send kfunc Mahe Tardy
2026-05-15 19:47 ` [PATCH bpf-next v5 1/4] bpf: add bpf_icmp_send kfunc Mahe Tardy
@ 2026-05-15 19:47 ` Mahe Tardy
2026-05-15 20:36 ` sashiko-bot
2026-05-15 19:47 ` [PATCH bpf-next v5 3/4] selftests/bpf: add bpf_icmp_send kfunc IPv6 tests Mahe Tardy
2026-05-15 19:47 ` [PATCH bpf-next v5 4/4] selftests/bpf: add bpf_icmp_send recursion test Mahe Tardy
3 siblings, 1 reply; 9+ messages in thread
From: Mahe Tardy @ 2026-05-15 19:47 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, Mahe Tardy
This test opens a server and client, enters a new cgroup, attach a
cgroup_skb program on egress and calls the bpf_icmp_send function from
the client egress so that an ICMP unreach control message is sent back
to the client. It then fetches the message from the error queue to
confirm the correct ICMP unreach code has been sent.
Note that, for the client, we have to connect in non-blocking mode to
let the test execute faster. Otherwise, we need to wait for the TCP
three-way handshake to timeout in the kernel before reading the errno.
Also note that we don't set IP_RECVERR on the socket in
connect_to_fd_nonblock since the error will be transferred anyway in our
test because the connection is rejected at the beginning of the TCP
handshake. See in net/ipv4/tcp_ipv4.c:tcp_v4_err for more details.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../bpf/prog_tests/icmp_send_kfunc.c | 149 ++++++++++++++++++
tools/testing/selftests/bpf/progs/icmp_send.c | 38 +++++
2 files changed, 187 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
create mode 100644 tools/testing/selftests/bpf/progs/icmp_send.c
diff --git a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
new file mode 100644
index 000000000000..b98c0312adad
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <network_helpers.h>
+#include <linux/errqueue.h>
+#include <poll.h>
+#include "icmp_send.skel.h"
+
+#define TIMEOUT_MS 1000
+
+#define ICMP_DEST_UNREACH 3
+
+#define ICMP_FRAG_NEEDED 4
+#define NR_ICMP_UNREACH 15
+
+static int connect_to_fd_nonblock(int server_fd)
+{
+ struct sockaddr_storage addr;
+ socklen_t len = sizeof(addr);
+ int fd, err;
+
+ if (getsockname(server_fd, (struct sockaddr *)&addr, &len))
+ return -1;
+
+ fd = socket(addr.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return -1;
+
+ err = connect(fd, (struct sockaddr *)&addr, len);
+ if (err < 0 && errno != EINPROGRESS) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static void read_icmp_errqueue(int sockfd, int expected_code)
+{
+ ssize_t n;
+ struct sock_extended_err *sock_err;
+ struct cmsghdr *cm;
+ char ctrl_buf[512];
+ struct msghdr msg = {
+ .msg_control = ctrl_buf,
+ .msg_controllen = sizeof(ctrl_buf),
+ };
+ struct pollfd pfd = {
+ .fd = sockfd,
+ .events = POLLERR,
+ };
+
+ if (!ASSERT_GE(poll(&pfd, 1, TIMEOUT_MS), 1, "poll_errqueue"))
+ return;
+
+ n = recvmsg(sockfd, &msg, MSG_ERRQUEUE);
+ if (!ASSERT_GE(n, 0, "recvmsg_errqueue"))
+ return;
+
+ cm = CMSG_FIRSTHDR(&msg);
+ if (!ASSERT_NEQ(cm, NULL, "cm_firsthdr_null"))
+ return;
+
+ for (; cm; cm = CMSG_NXTHDR(&msg, cm)) {
+ if (cm->cmsg_level != IPPROTO_IP ||
+ cm->cmsg_type != IP_RECVERR)
+ 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"))
+ return;
+ if (!ASSERT_EQ(sock_err->ee_type, ICMP_DEST_UNREACH,
+ "sock_err_type_dest_unreach"))
+ return;
+ ASSERT_EQ(sock_err->ee_code, expected_code, "sock_err_code");
+ }
+}
+
+static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel,
+ int code)
+{
+ int srv_fd = -1, client_fd = -1;
+ struct sockaddr_in addr;
+ socklen_t len = sizeof(addr);
+
+ srv_fd = start_server(AF_INET, SOCK_STREAM, "127.0.0.1", 0,
+ TIMEOUT_MS);
+ if (!ASSERT_GE(srv_fd, 0, "start_server"))
+ return;
+
+ if (getsockname(srv_fd, (struct sockaddr *)&addr, &len)) {
+ close(srv_fd);
+ return;
+ }
+ skel->bss->server_port = ntohs(addr.sin_port);
+ skel->bss->unreach_code = code;
+
+ client_fd = connect_to_fd_nonblock(srv_fd);
+ if (!ASSERT_GE(client_fd, 0, "client_connect_nonblock")) {
+ close(srv_fd);
+ return;
+ }
+
+ /* Skip reading ICMP error queue if code is invalid */
+ if (code >= 0 && code <= NR_ICMP_UNREACH)
+ read_icmp_errqueue(client_fd, code);
+
+ close(client_fd);
+ close(srv_fd);
+}
+
+void test_icmp_send_unreach(void)
+{
+ struct icmp_send *skel;
+ int cgroup_fd = -1;
+
+ skel = icmp_send__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ goto cleanup;
+
+ cgroup_fd = test__join_cgroup("/icmp_send_unreach_cgroup");
+ if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
+ goto cleanup;
+
+ skel->links.egress =
+ bpf_program__attach_cgroup(skel->progs.egress, cgroup_fd);
+ if (!ASSERT_OK_PTR(skel->links.egress, "prog_attach_cgroup"))
+ goto cleanup;
+
+ for (int code = 0; code <= NR_ICMP_UNREACH; code++) {
+ /* The TCP stack reacts differently when asking for
+ * fragmentation, let's ignore it for now.
+ */
+ if (code == ICMP_FRAG_NEEDED)
+ continue;
+
+ trigger_prog_read_icmp_errqueue(skel, code);
+ ASSERT_EQ(skel->data->kfunc_ret, 0, "kfunc_ret");
+ }
+
+ /* Test an invalid code */
+ trigger_prog_read_icmp_errqueue(skel, -1);
+ ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
+
+cleanup:
+ icmp_send__destroy(skel);
+ close(cgroup_fd);
+}
diff --git a/tools/testing/selftests/bpf/progs/icmp_send.c b/tools/testing/selftests/bpf/progs/icmp_send.c
new file mode 100644
index 000000000000..6d0be0a9afe1
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/icmp_send.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+/* 127.0.0.1 in host byte order */
+#define SERVER_IP 0x7F000001
+
+#define ICMP_DEST_UNREACH 3
+
+__u16 server_port = 0;
+int unreach_code = 0;
+int kfunc_ret = -1;
+
+SEC("cgroup_skb/egress")
+int egress(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct iphdr *iph;
+ struct tcphdr *tcph;
+
+ iph = data;
+ if ((void *)(iph + 1) > data_end || iph->version != 4 ||
+ 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;
+
+ kfunc_ret = bpf_icmp_send(skb, ICMP_DEST_UNREACH, unreach_code);
+
+ return SK_DROP;
+}
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH bpf-next v5 2/4] selftests/bpf: add bpf_icmp_send kfunc tests
2026-05-15 19:47 ` [PATCH bpf-next v5 2/4] selftests/bpf: add bpf_icmp_send kfunc tests Mahe Tardy
@ 2026-05-15 20:36 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-05-15 20:36 UTC (permalink / raw)
To: Mahe Tardy; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `read_icmp_errqueue` function will silently pass without asserting the error code if an `IP_RECVERR` control message is not found.
--
commit 30b471fdde74d100ff073064731c793e5f178384
Author: Mahe Tardy <mahe.tardy@gmail.com>
selftests/bpf: add bpf_icmp_send kfunc tests
This commit adds a new selftest for the bpf_icmp_send kfunc. It sets up
a client/server connection, attaches an egress cgroup_skb program to
trigger an ICMP unreach message, and verifies the correct ICMP unreach
code is received in the error queue.
> diff --git a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
> new file mode 100644
> index 0000000000000..b98c0312adadc
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
[ ... ]
> +static void read_icmp_errqueue(int sockfd, int expected_code)
> +{
[ ... ]
> + for (; cm; cm = CMSG_NXTHDR(&msg, cm)) {
> + if (cm->cmsg_level != IPPROTO_IP ||
> + cm->cmsg_type != IP_RECVERR)
> + 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"))
> + return;
> + if (!ASSERT_EQ(sock_err->ee_type, ICMP_DEST_UNREACH,
> + "sock_err_type_dest_unreach"))
> + return;
> + ASSERT_EQ(sock_err->ee_code, expected_code, "sock_err_code");
> + }
> +}
Does this function silently pass if no IP_RECVERR control message is found?
Looking at the loop, if it finishes without finding a message where
cm->cmsg_level == IPPROTO_IP and cm->cmsg_type == IP_RECVERR, it exits
normally without executing the assertions.
Should there be a check after the loop or a boolean flag used to ensure the
expected message was actually received, rather than allowing the test to
pass successfully when the message is completely absent?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515194746.50920-1-mahe.tardy@gmail.com?part=2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v5 3/4] selftests/bpf: add bpf_icmp_send kfunc IPv6 tests
2026-05-15 19:47 [PATCH bpf-next v5 0/4] bpf: add icmp_send kfunc Mahe Tardy
2026-05-15 19:47 ` [PATCH bpf-next v5 1/4] bpf: add bpf_icmp_send kfunc Mahe Tardy
2026-05-15 19:47 ` [PATCH bpf-next v5 2/4] selftests/bpf: add bpf_icmp_send kfunc tests Mahe Tardy
@ 2026-05-15 19:47 ` Mahe Tardy
2026-05-15 20:44 ` sashiko-bot
2026-05-15 19:47 ` [PATCH bpf-next v5 4/4] selftests/bpf: add bpf_icmp_send recursion test Mahe Tardy
3 siblings, 1 reply; 9+ messages in thread
From: Mahe Tardy @ 2026-05-15 19:47 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, Mahe Tardy
This test extends the existing 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>
---
.../bpf/prog_tests/icmp_send_kfunc.c | 76 +++++++++++++------
tools/testing/selftests/bpf/progs/icmp_send.c | 48 +++++++++---
2 files changed, 90 insertions(+), 34 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
index b98c0312adad..d9badfc6e620 100644
--- a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
+++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
@@ -8,15 +8,17 @@
#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
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;
@@ -25,6 +27,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);
@@ -34,7 +42,7 @@ 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)
{
ssize_t n;
struct sock_extended_err *sock_err;
@@ -44,6 +52,12 @@ static void read_icmp_errqueue(int sockfd, int expected_code)
.msg_control = ctrl_buf,
.msg_controllen = sizeof(ctrl_buf),
};
+ int expected_level = (af == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
+ int expected_type = (af == AF_INET) ? IP_RECVERR : IPV6_RECVERR;
+ int expected_origin = (af == AF_INET) ? SO_EE_ORIGIN_ICMP :
+ SO_EE_ORIGIN_ICMP6;
+ int expected_ee_type = (af == AF_INET) ? ICMP_DEST_UNREACH :
+ ICMPV6_DEST_UNREACH;
struct pollfd pfd = {
.fd = sockfd,
.events = POLLERR,
@@ -61,16 +75,16 @@ 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");
@@ -78,14 +92,13 @@ static void read_icmp_errqueue(int sockfd, int expected_code)
}
static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel,
- int code)
+ int code, int af, const char *ip)
{
int srv_fd = -1, client_fd = -1;
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
- 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_GE(srv_fd, 0, "start_server"))
return;
@@ -94,6 +107,8 @@ static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel,
return;
}
skel->bss->server_port = ntohs(addr.sin_port);
+ skel->bss->unreach_type = (af == AF_INET) ? ICMP_DEST_UNREACH :
+ ICMPV6_DEST_UNREACH;
skel->bss->unreach_code = code;
client_fd = connect_to_fd_nonblock(srv_fd);
@@ -103,13 +118,33 @@ static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel,
}
/* Skip reading ICMP error queue if code is invalid */
- if (code >= 0 && code <= NR_ICMP_UNREACH)
- read_icmp_errqueue(client_fd, code);
+ if (code >= 0 && ((af == AF_INET && code <= NR_ICMP_UNREACH) ||
+ (af == AF_INET6 && code <= ICMPV6_REJECT_ROUTE)))
+ 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++) {
+ /* The TCP stack reacts differently when asking for
+ * fragmentation, let's ignore it for now.
+ */
+ 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 an invalid code */
+ trigger_prog_read_icmp_errqueue(skel, -1, af, ip);
+ ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
+}
+
void test_icmp_send_unreach(void)
{
struct icmp_send *skel;
@@ -128,20 +163,11 @@ void test_icmp_send_unreach(void)
if (!ASSERT_OK_PTR(skel->links.egress, "prog_attach_cgroup"))
goto cleanup;
- for (int code = 0; code <= NR_ICMP_UNREACH; code++) {
- /* The TCP stack reacts differently when asking for
- * fragmentation, let's ignore it for now.
- */
- if (code == ICMP_FRAG_NEEDED)
- continue;
-
- trigger_prog_read_icmp_errqueue(skel, code);
- ASSERT_EQ(skel->data->kfunc_ret, 0, "kfunc_ret");
- }
+ if (test__start_subtest("ipv4"))
+ run_icmp_test(skel, AF_INET, "127.0.0.1", NR_ICMP_UNREACH);
- /* Test an invalid code */
- trigger_prog_read_icmp_errqueue(skel, -1);
- 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);
diff --git a/tools/testing/selftests/bpf/progs/icmp_send.c b/tools/testing/selftests/bpf/progs/icmp_send.c
index 6d0be0a9afe1..6e1ba539eeb0 100644
--- a/tools/testing/selftests/bpf/progs/icmp_send.c
+++ b/tools/testing/selftests/bpf/progs/icmp_send.c
@@ -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))
+ version = (*((__u8 *)data)) >> 4;
+
+ 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, ICMP_DEST_UNREACH, unreach_code);
+ kfunc_ret = bpf_icmp_send(skb, unreach_type, unreach_code);
return SK_DROP;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH bpf-next v5 3/4] selftests/bpf: add bpf_icmp_send kfunc IPv6 tests
2026-05-15 19:47 ` [PATCH bpf-next v5 3/4] selftests/bpf: add bpf_icmp_send kfunc IPv6 tests Mahe Tardy
@ 2026-05-15 20:44 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-05-15 20:44 UTC (permalink / raw)
To: Mahe Tardy; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Buffer truncation and type confusion when calling getsockname() for IPv6 sockets.
--
commit 3f16e831f177058fc11380fe48e08f94594b83bf
Author: Mahe Tardy <mahe.tardy@gmail.com>
selftests/bpf: add bpf_icmp_send kfunc IPv6 tests
This commit extends the existing bpf_icmp_send kfunc tests with IPv6
support. It updates the test suite to handle IPv6 connections and
ICMPv6 error queues.
> diff --git a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
> index b98c0312adadc..d9badfc6e620c 100644
> --- a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
> +++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
[ ... ]
> @@ -78,14 +92,13 @@ static void read_icmp_errqueue(int sockfd, int expected_code)
> }
>
> static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel,
> - int code)
> + int code, int af, const char *ip)
> {
> int srv_fd = -1, client_fd = -1;
> struct sockaddr_in addr;
> socklen_t len = sizeof(addr);
>
> - 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_GE(srv_fd, 0, "start_server"))
> return;
>
> @@ -94,6 +107,8 @@ static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel,
> return;
> }
> skel->bss->server_port = ntohs(addr.sin_port);
When af is AF_INET6, start_server() creates an IPv6 socket, but addr
is hardcoded as struct sockaddr_in which is 16 bytes.
Does this cause getsockname() to silently truncate the 28-byte
sockaddr_in6 structure because len is initialized to sizeof(addr)?
While reading addr.sin_port happens to work because sin_port and sin6_port
share the same offset, would it be better to use struct sockaddr_storage
here (like in connect_to_fd_nonblock() earlier in the file) and extract
the port conditionally based on the address family to avoid type confusion?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515194746.50920-1-mahe.tardy@gmail.com?part=3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v5 4/4] selftests/bpf: add bpf_icmp_send recursion test
2026-05-15 19:47 [PATCH bpf-next v5 0/4] bpf: add icmp_send kfunc Mahe Tardy
` (2 preceding siblings ...)
2026-05-15 19:47 ` [PATCH bpf-next v5 3/4] selftests/bpf: add bpf_icmp_send kfunc IPv6 tests Mahe Tardy
@ 2026-05-15 19:47 ` Mahe Tardy
2026-05-15 21:00 ` sashiko-bot
3 siblings, 1 reply; 9+ messages in thread
From: Mahe Tardy @ 2026-05-15 19:47 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, Mahe Tardy
This test is similar to test_icmp_send_unreach but checks that, in case
of recursion, meaning that the BPF program calling the kfunc was
re-triggered by the icmp_send done by the kfunc, the kfunc will stop
early and return -EBUSY.
The test attaches to the root cgroup to ensure the ICMP packet generated
by the kfunc re-triggers the BPF program. All traffic is allowed to pass
to avoid disrupting unrelated network activity.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../bpf/prog_tests/icmp_send_kfunc.c | 40 +++++++++++++++++++
tools/testing/selftests/bpf/progs/icmp_send.c | 31 ++++++++++++++
2 files changed, 71 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
index d9badfc6e620..2363b1c7c711 100644
--- a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
+++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include <network_helpers.h>
+#include <cgroup_helpers.h>
#include <linux/errqueue.h>
#include <poll.h>
#include "icmp_send.skel.h"
@@ -10,6 +11,7 @@
#define ICMP_DEST_UNREACH 3
#define ICMPV6_DEST_UNREACH 1
+#define ICMP_HOST_UNREACH 1
#define ICMP_FRAG_NEEDED 4
#define NR_ICMP_UNREACH 15
#define ICMPV6_REJECT_ROUTE 6
@@ -173,3 +175,41 @@ void test_icmp_send_unreach(void)
icmp_send__destroy(skel);
close(cgroup_fd);
}
+
+void test_icmp_send_unreach_recursion(void)
+{
+ struct icmp_send *skel;
+ int cgroup_fd = -1;
+
+ skel = icmp_send__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ goto cleanup;
+
+ if (setup_cgroup_environment()) {
+ fprintf(stderr, "Failed to setup cgroup environment\n");
+ goto cleanup;
+ }
+
+ cgroup_fd = get_root_cgroup();
+ if (!ASSERT_GE(cgroup_fd, 0, "get_root_cgroup"))
+ goto cleanup;
+
+ skel->links.recursion =
+ bpf_program__attach_cgroup(skel->progs.recursion, cgroup_fd);
+ if (!ASSERT_OK_PTR(skel->links.recursion, "prog_attach_cgroup"))
+ goto cleanup;
+
+ trigger_prog_read_icmp_errqueue(skel, ICMP_HOST_UNREACH, AF_INET, "127.0.0.1");
+
+ /* Because there's recursion involved, the first call will return at
+ * index 1 since it will return the second, and the second call will
+ * return at index 0 since it will return the first.
+ */
+ ASSERT_EQ(skel->data->rec_kfunc_rets[0], -EBUSY, "kfunc_rets[0]");
+ ASSERT_EQ(skel->data->rec_kfunc_rets[1], 0, "kfunc_rets[1]");
+
+cleanup:
+ cleanup_cgroup_environment();
+ icmp_send__destroy(skel);
+ close(cgroup_fd);
+}
diff --git a/tools/testing/selftests/bpf/progs/icmp_send.c b/tools/testing/selftests/bpf/progs/icmp_send.c
index 6e1ba539eeb0..a2ef6a02c2cb 100644
--- a/tools/testing/selftests/bpf/progs/icmp_send.c
+++ b/tools/testing/selftests/bpf/progs/icmp_send.c
@@ -13,6 +13,9 @@ int unreach_type = 0;
int unreach_code = 0;
int kfunc_ret = -1;
+unsigned int rec_count = 0;
+int rec_kfunc_rets[] = { -1, -1 };
+
SEC("cgroup_skb/egress")
int egress(struct __sk_buff *skb)
{
@@ -65,4 +68,32 @@ int egress(struct __sk_buff *skb)
return SK_DROP;
}
+SEC("cgroup_skb/egress")
+int recursion(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct iphdr *iph;
+ int ret;
+
+ iph = data;
+ if ((void *)(iph + 1) > data_end || iph->version != 4)
+ return SK_PASS;
+
+ /* This call will provoke a recursion: the ICMP packet generated by the
+ * kfunc will re-trigger this program since we are in the root cgroup in
+ * which the kernel ICMP socket belongs. However when re-entering the
+ * kfunc, it should return EBUSY.
+ */
+ ret = bpf_icmp_send(skb, unreach_type, unreach_code);
+ rec_kfunc_rets[rec_count & 1] = ret;
+ __sync_fetch_and_add(&rec_count, 1);
+
+ /* Because we attach the root cgroup, let all traffic pass to avoid
+ * disrupting unrelated network activity. Otherwise we could just let the
+ * first ICMP error message pass to trigger recursion and drop the rest.
+ */
+ return SK_PASS;
+}
+
char LICENSE[] SEC("license") = "Dual BSD/GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH bpf-next v5 4/4] selftests/bpf: add bpf_icmp_send recursion test
2026-05-15 19:47 ` [PATCH bpf-next v5 4/4] selftests/bpf: add bpf_icmp_send recursion test Mahe Tardy
@ 2026-05-15 21:00 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-05-15 21:00 UTC (permalink / raw)
To: Mahe Tardy; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Missing packet filtering in root cgroup program causes system disruption and state corruption.
- [Medium] Uninitialized `unreach_type` prevents ICMP generation and causes guaranteed test failure.
--
commit 58dd624e2dc9a29c48c701ba76fcd20e713e626f
Author: Mahe Tardy <mahe.tardy@gmail.com>
selftests/bpf: add bpf_icmp_send recursion test
This commit adds a new test to verify that bpf_icmp_send returns -EBUSY
when recursion occurs. It attaches a BPF program to the root cgroup to
intercept the generated ICMP packets.
> diff --git a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
> index d9badfc6e620c..2363b1c7c711c 100644
> --- a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
> +++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
[ ... ]
> @@ -173,3 +175,41 @@ void test_icmp_send_unreach(void)
> icmp_send__destroy(skel);
> close(cgroup_fd);
> }
> +
> +void test_icmp_send_unreach_recursion(void)
> +{
> + struct icmp_send *skel;
> + int cgroup_fd = -1;
> +
> + skel = icmp_send__open_and_load();
> + if (!ASSERT_OK_PTR(skel, "skel_open"))
> + goto cleanup;
> +
> + if (setup_cgroup_environment()) {
> + fprintf(stderr, "Failed to setup cgroup environment\n");
> + goto cleanup;
> + }
> +
> + cgroup_fd = get_root_cgroup();
> + if (!ASSERT_GE(cgroup_fd, 0, "get_root_cgroup"))
> + goto cleanup;
> +
> + skel->links.recursion =
> + bpf_program__attach_cgroup(skel->progs.recursion, cgroup_fd);
> + if (!ASSERT_OK_PTR(skel->links.recursion, "prog_attach_cgroup"))
> + goto cleanup;
> +
> + trigger_prog_read_icmp_errqueue(skel, ICMP_HOST_UNREACH, AF_INET, "127.0.0.1");
Is it necessary to set skel->bss->unreach_type before triggering the program?
In progs/icmp_send.c, unreach_type defaults to 0. Looking at bpf_icmp_send()
in net/core/filter.c, for IPv4 it requires type to be exactly ICMP_DEST_UNREACH (3),
otherwise it returns -EOPNOTSUPP:
net/core/filter.c:bpf_icmp_send() {
...
case htons(ETH_P_IP):
if (type != ICMP_DEST_UNREACH)
return -EOPNOTSUPP;
...
}
If the kfunc returns -EOPNOTSUPP (-95), no ICMP packet is generated, which seems
like it would prevent the recursion this test is designed to verify and cause
the assertions below to fail.
[ ... ]
> diff --git a/tools/testing/selftests/bpf/progs/icmp_send.c b/tools/testing/selftests/bpf/progs/icmp_send.c
> index 6e1ba539eeb0f..a2ef6a02c2cb0 100644
> --- a/tools/testing/selftests/bpf/progs/icmp_send.c
> +++ b/tools/testing/selftests/bpf/progs/icmp_send.c
[ ... ]
> +SEC("cgroup_skb/egress")
> +int recursion(struct __sk_buff *skb)
> +{
> + void *data = (void *)(long)skb->data;
> + void *data_end = (void *)(long)skb->data_end;
> + struct iphdr *iph;
> + int ret;
> +
> + iph = data;
> + if ((void *)(iph + 1) > data_end || iph->version != 4)
> + return SK_PASS;
Will this cause system-wide disruption and state corruption by intercepting all
IPv4 traffic?
Because this program is attached to the global root cgroup, it will intercept
all egress traffic on the host. Without further filtering for specific test
traffic (like protocol, port, or destination IP), any background IPv4 traffic
will trigger the program and call bpf_icmp_send().
This seems like it would generate ICMP Destination Unreachable messages for all
legitimate background connections (e.g., SSH, DNS, or other selftests),
causing an ICMP storm.
Additionally, concurrent background traffic might cause races on rec_kfunc_rets
and rec_count:
> +
> + /* This call will provoke a recursion: the ICMP packet generated by the
> + * kfunc will re-trigger this program since we are in the root cgroup in
> + * which the kernel ICMP socket belongs. However when re-entering the
> + * kfunc, it should return EBUSY.
> + */
> + ret = bpf_icmp_send(skb, unreach_type, unreach_code);
> + rec_kfunc_rets[rec_count & 1] = ret;
> + __sync_fetch_and_add(&rec_count, 1);
> +
> + /* Because we attach the root cgroup, let all traffic pass to avoid
> + * disrupting unrelated network activity. Otherwise we could just let the
> + * first ICMP error message pass to trigger recursion and drop the rest.
> + */
> + return SK_PASS;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260515194746.50920-1-mahe.tardy@gmail.com?part=4
^ permalink raw reply [flat|nested] 9+ messages in thread