* [PATCH bpf-next v7 1/7] net: move netfilter nf_reject_fill_skb_dst to core ipv4
2026-05-26 15:37 [PATCH bpf-next v7 0/7] bpf: add icmp_send kfunc Mahe Tardy
@ 2026-05-26 15:37 ` Mahe Tardy
2026-05-26 16:20 ` bot+bpf-ci
2026-05-26 15:37 ` [PATCH bpf-next v7 2/7] net: move netfilter nf_reject6_fill_skb_dst to core ipv6 Mahe Tardy
` (5 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 15:37 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
Mahe Tardy
Move and rename nf_reject_fill_skb_dst from
ipv4/netfilter/nf_reject_ipv4 to ip_route_reply_fill_dst in ipv4/route.c
so that it can be reused in the following patches by BPF kfuncs.
Netfilter uses nf_ip_route that is almost a transparent wrapper around
ip_route_output_key so this patch inlines it.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
include/net/route.h | 1 +
net/ipv4/netfilter/nf_reject_ipv4.c | 19 ++-----------------
net/ipv4/route.c | 15 +++++++++++++++
3 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/include/net/route.h b/include/net/route.h
index f90106f383c5..300d292cd9a1 100644
--- a/include/net/route.h
+++ b/include/net/route.h
@@ -173,6 +173,7 @@ struct rtable *ip_route_output_flow(struct net *, struct flowi4 *flp,
const struct sock *sk);
struct dst_entry *ipv4_blackhole_route(struct net *net,
struct dst_entry *dst_orig);
+int ip_route_reply_fill_dst(struct sk_buff *skb);
static inline struct rtable *ip_route_output_key(struct net *net, struct flowi4 *flp)
{
diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c
index fecf6621f679..c1c0724e4d4d 100644
--- a/net/ipv4/netfilter/nf_reject_ipv4.c
+++ b/net/ipv4/netfilter/nf_reject_ipv4.c
@@ -252,21 +252,6 @@ static void nf_reject_ip_tcphdr_put(struct sk_buff *nskb, const struct sk_buff *
nskb->csum_offset = offsetof(struct tcphdr, check);
}
-static int nf_reject_fill_skb_dst(struct sk_buff *skb_in)
-{
- struct dst_entry *dst = NULL;
- struct flowi fl;
-
- memset(&fl, 0, sizeof(struct flowi));
- fl.u.ip4.daddr = ip_hdr(skb_in)->saddr;
- nf_ip_route(dev_net(skb_in->dev), &dst, &fl, false);
- if (!dst)
- return -1;
-
- skb_dst_set(skb_in, dst);
- return 0;
-}
-
/* Send RST reply */
void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb,
int hook)
@@ -279,7 +264,7 @@ void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb,
if (!oth)
return;
- if (!skb_dst(oldskb) && nf_reject_fill_skb_dst(oldskb) < 0)
+ if (!skb_dst(oldskb) && ip_route_reply_fill_dst(oldskb) < 0)
return;
if (skb_rtable(oldskb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
@@ -352,7 +337,7 @@ void nf_send_unreach(struct sk_buff *skb_in, int code, int hook)
if (iph->frag_off & htons(IP_OFFSET))
return;
- if (!skb_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0)
+ if (!skb_dst(skb_in) && ip_route_reply_fill_dst(skb_in) < 0)
return;
if (skb_csum_unnecessary(skb_in) ||
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index bc1296f0ea69..1f031c5ef554 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -2945,6 +2945,21 @@ struct rtable *ip_route_output_flow(struct net *net, struct flowi4 *flp4,
}
EXPORT_SYMBOL_GPL(ip_route_output_flow);
+int ip_route_reply_fill_dst(struct sk_buff *skb)
+{
+ struct rtable *rt;
+ struct flowi4 fl4 = {
+ .daddr = ip_hdr(skb)->saddr
+ };
+
+ rt = ip_route_output_key(dev_net(skb->dev), &fl4);
+ if (IS_ERR(rt))
+ return PTR_ERR(rt);
+ skb_dst_set(skb, &rt->dst);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ip_route_reply_fill_dst);
+
/* called with rcu_read_lock held */
static int rt_fill_info(struct net *net, __be32 dst, __be32 src,
struct rtable *rt, u32 table_id, dscp_t dscp,
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH bpf-next v7 1/7] net: move netfilter nf_reject_fill_skb_dst to core ipv4
2026-05-26 15:37 ` [PATCH bpf-next v7 1/7] net: move netfilter nf_reject_fill_skb_dst to core ipv4 Mahe Tardy
@ 2026-05-26 16:20 ` bot+bpf-ci
0 siblings, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-05-26 16:20 UTC (permalink / raw)
To: mahe.tardy, bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
mahe.tardy, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3337 bytes --]
> diff --git a/include/net/route.h b/include/net/route.h
> index f90106f383c5..300d292cd9a1 100644
> --- a/include/net/route.h
> +++ b/include/net/route.h
> @@ -173,6 +173,7 @@ struct rtable *ip_route_output_flow(struct net *, struct flowi4 *flp,
> const struct sock *sk);
> struct dst_entry *ipv4_blackhole_route(struct net *net,
> struct dst_entry *dst_orig);
> +int ip_route_reply_fill_dst(struct sk_buff *skb);
>
> static inline struct rtable *ip_route_output_key(struct net *net, struct flowi4 *flp)
> {
[ ... ]
> diff --git a/net/ipv4/route.c b/net/ipv4/route.c
> index 3d62d45d84bd..ff79992ce098 100644
> --- a/net/ipv4/route.c
> +++ b/net/ipv4/route.c
> @@ -2945,6 +2945,21 @@ struct rtable *ip_route_output_flow(struct net *net, struct flowi4 *flp4,
> }
> EXPORT_SYMBOL_GPL(ip_route_output_flow);
>
> +int ip_route_reply_fill_dst(struct sk_buff *skb)
> +{
> + struct rtable *rt;
> + struct flowi4 fl4 = {
> + .daddr = ip_hdr(skb)->saddr
> + };
^^^^
Can ip_hdr(skb)->saddr be accessed safely here?
Now that this is exported for BPF kfuncs, it can be reached from early
hooks like TC ingress where the IPv4 header might not be linearized yet.
If a malformed packet contains less than sizeof(struct iphdr) bytes of
linear data, accessing the saddr field could read beyond the available
linear buffer.
Does this function need validation via pskb_may_pull() or similar to
ensure the header is accessible?
Note: This concern was raised by sashiko-bot@kernel.org in v4 review
(https://lore.kernel.org/bpf/20260421111320.D97D9C2BCB0@smtp.kernel.org/)
and repeated in v6. The author responded that 'it should be fine since
it's the caller responsability to make sure the header has been
linearized, and we are calling pskb_network_may_pull' but the function
itself does not validate this, leaving it vulnerable if called
incorrectly.
> +
> + rt = ip_route_output_key(dev_net(skb->dev), &fl4);
^^^^^^^^
Can skb->dev be NULL here?
While safe in its original netfilter context, a BPF_PROG_TYPE_NETFILTER
program attached to the NF_INET_LOCAL_OUT hook will receive locally
generated SKBs before skb->dev is assigned (in __ip_local_out before
ip_output).
If called from such contexts, dev_net(skb->dev) would trigger a kernel
panic.
Does this function need a NULL check on skb->dev?
Note: This concern was raised by sashiko-bot@kernel.org in v4 review
(https://lore.kernel.org/bpf/20260421111320.D97D9C2BCB0@smtp.kernel.org/)
and repeated in v6. The author responded that 'We can't attach such
program as now it's only cgroup_skb and tc, outdated' but the function
is exported via EXPORT_SYMBOL_GPL without any enforcement, making it
callable from contexts where skb->dev might be NULL.
> + if (IS_ERR(rt))
> + return PTR_ERR(rt);
> + skb_dst_set(skb, &rt->dst);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(ip_route_reply_fill_dst);
> +
> /* called with rcu_read_lock held */
> static int rt_fill_info(struct net *net, __be32 dst, __be32 src,
> struct rtable *rt, u32 table_id, dscp_t dscp,
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26459248718
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH bpf-next v7 2/7] net: move netfilter nf_reject6_fill_skb_dst to core ipv6
2026-05-26 15:37 [PATCH bpf-next v7 0/7] bpf: add icmp_send kfunc Mahe Tardy
2026-05-26 15:37 ` [PATCH bpf-next v7 1/7] net: move netfilter nf_reject_fill_skb_dst to core ipv4 Mahe Tardy
@ 2026-05-26 15:37 ` Mahe Tardy
2026-05-26 16:20 ` bot+bpf-ci
2026-05-26 15:37 ` [PATCH bpf-next v7 3/7] bpf: add bpf_icmp_send kfunc Mahe Tardy
` (4 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 15:37 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
Mahe Tardy
Move and rename nf_reject6_fill_skb_dst from
ipv6/netfilter/nf_reject_ipv6 to ip6_route_reply_fill_dst in
ipv6/route.c so that it can be reused in the following patches by BPF
kfuncs.
Netfilter uses nf_ip6_route that is almost a transparent wrapper around
ip6_route_output so this patch inlines it.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
include/net/ip6_route.h | 2 ++
net/ipv6/netfilter/nf_reject_ipv6.c | 17 +----------------
net/ipv6/route.c | 18 ++++++++++++++++++
3 files changed, 21 insertions(+), 16 deletions(-)
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 09ffe0f13ce7..eb5a60d3babe 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -100,6 +100,8 @@ static inline struct dst_entry *ip6_route_output(struct net *net,
return ip6_route_output_flags(net, sk, fl6, 0);
}
+int ip6_route_reply_fill_dst(struct sk_buff *skb);
+
/* Only conditionally release dst if flags indicates
* !RT6_LOOKUP_F_DST_NOREF or dst is in uncached_list.
*/
diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c
index ef5b7e85cffa..7d2f577e72b8 100644
--- a/net/ipv6/netfilter/nf_reject_ipv6.c
+++ b/net/ipv6/netfilter/nf_reject_ipv6.c
@@ -293,21 +293,6 @@ nf_reject_ip6_tcphdr_put(struct sk_buff *nskb,
sizeof(struct tcphdr), 0));
}
-static int nf_reject6_fill_skb_dst(struct sk_buff *skb_in)
-{
- struct dst_entry *dst = NULL;
- struct flowi fl;
-
- memset(&fl, 0, sizeof(struct flowi));
- fl.u.ip6.daddr = ipv6_hdr(skb_in)->saddr;
- nf_ip6_route(dev_net(skb_in->dev), &dst, &fl, false);
- if (!dst)
- return -1;
-
- skb_dst_set(skb_in, dst);
- return 0;
-}
-
void nf_send_reset6(struct net *net, struct sock *sk, struct sk_buff *oldskb,
int hook)
{
@@ -440,7 +425,7 @@ void nf_send_unreach6(struct net *net, struct sk_buff *skb_in,
if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL)
skb_in->dev = net->loopback_dev;
- if (!skb_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0)
+ if (!skb_dst(skb_in) && ip6_route_reply_fill_dst(skb_in) < 0)
return;
icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0);
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index e3d355d1fbd6..37a7627a94de 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -2725,6 +2725,24 @@ struct dst_entry *ip6_route_output_flags(struct net *net,
}
EXPORT_SYMBOL_GPL(ip6_route_output_flags);
+int ip6_route_reply_fill_dst(struct sk_buff *skb)
+{
+ struct dst_entry *result;
+ struct flowi6 fl = {
+ .daddr = ipv6_hdr(skb)->saddr
+ };
+ int err;
+
+ result = ip6_route_output(dev_net(skb->dev), NULL, &fl);
+ err = result->error;
+ if (err)
+ dst_release(result);
+ else
+ skb_dst_set(skb, result);
+ return err;
+}
+EXPORT_SYMBOL_GPL(ip6_route_reply_fill_dst);
+
struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_orig)
{
struct rt6_info *rt, *ort = dst_rt6_info(dst_orig);
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH bpf-next v7 2/7] net: move netfilter nf_reject6_fill_skb_dst to core ipv6
2026-05-26 15:37 ` [PATCH bpf-next v7 2/7] net: move netfilter nf_reject6_fill_skb_dst to core ipv6 Mahe Tardy
@ 2026-05-26 16:20 ` bot+bpf-ci
2026-05-26 22:02 ` Mahe Tardy
0 siblings, 1 reply; 13+ messages in thread
From: bot+bpf-ci @ 2026-05-26 16:20 UTC (permalink / raw)
To: mahe.tardy, bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
mahe.tardy, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3111 bytes --]
> diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
> index 09ffe0f13ce7..eb5a60d3babe 100644
> --- a/include/net/ip6_route.h
> +++ b/include/net/ip6_route.h
> @@ -100,6 +100,8 @@ static inline struct dst_entry *ip6_route_output(struct net *net,
> return ip6_route_output_flags(net, sk, fl6, 0);
> }
>
> +int ip6_route_reply_fill_dst(struct sk_buff *skb);
> +
> /* Only conditionally release dst if flags indicates
> * !RT6_LOOKUP_F_DST_NOREF or dst is in uncached_list.
> */
[ ... ]
> diff --git a/net/ipv6/route.c b/net/ipv6/route.c
> index b106e5fef9cb..e72065aa5f5a 100644
> --- a/net/ipv6/route.c
> +++ b/net/ipv6/route.c
> @@ -2725,6 +2725,24 @@ struct dst_entry *ip6_route_output_flags(struct net *net,
> }
> EXPORT_SYMBOL_GPL(ip6_route_output_flags);
>
> +int ip6_route_reply_fill_dst(struct sk_buff *skb)
> +{
> + struct dst_entry *result;
> + struct flowi6 fl = {
> + .daddr = ipv6_hdr(skb)->saddr
> + };
Could ipv6_hdr(skb)->saddr read out-of-bounds memory if the IPv6 header
spans paged fragments?
Since this function is exported as EXPORT_SYMBOL_GPL for use by BPF kfuncs,
it might be called on packets where the header isn't guaranteed to be in
the linear data area. If there are no linearization checks like
pskb_may_pull() before accessing ipv6_hdr(), this could read out-of-bounds
memory or cause a page fault when processing undersized or fragmented
packets supplied by BPF programs.
The v6 cover letter mentions "call pskb_network_may_pull on the cloned
pkt", but this linearization check is not present in this function.
While the netfilter caller (nf_send_unreach6) may ensure the header is
linearized, the exported function itself doesn't validate this condition.
Reference: https://lore.kernel.org/bpf/1637d7e04ac3c65239ef77709175697bff287250913d1da68e8d4072e0ff34e1@mail.kernel.org/
> + int err;
> +
> + result = ip6_route_output(dev_net(skb->dev), NULL, &fl);
Can skb->dev be NULL in contexts where this function will be used?
Since this function is exported as EXPORT_SYMBOL_GPL for use by BPF
kfuncs, it might be called from program types where the device reference
is not retained, such as socket filters or tc ingress before device
assignment. If skb->dev is NULL in these contexts, dev_net(skb->dev)
would cause a NULL pointer dereference.
While the netfilter caller (nf_send_unreach6) checks and sets skb->dev
to loopback_dev if NULL, the exported function itself does not have this
protection.
Reference: https://lore.kernel.org/bpf/20260421111321.94393C2BCB0@smtp.kernel.org/
> + err = result->error;
> + if (err)
> + dst_release(result);
> + else
> + skb_dst_set(skb, result);
> + return err;
> +}
> +EXPORT_SYMBOL_GPL(ip6_route_reply_fill_dst);
> +
> struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_orig)
> {
> struct rt6_info *rt, *ort = dst_rt6_info(dst_orig);
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26459248718
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH bpf-next v7 2/7] net: move netfilter nf_reject6_fill_skb_dst to core ipv6
2026-05-26 16:20 ` bot+bpf-ci
@ 2026-05-26 22:02 ` Mahe Tardy
0 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 22:02 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, martin.lau, daniel, john.fastabend, ast, andrii,
yonghong.song, jordan, netdev, netfilter-devel, edumazet, kuba,
pabeni, martin.lau, eddyz87, clm, ihor.solodrai
On Tue, May 26, 2026 at 04:20:14PM +0000, bot+bpf-ci@kernel.org wrote:
> > diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
> > index 09ffe0f13ce7..eb5a60d3babe 100644
> > --- a/include/net/ip6_route.h
> > +++ b/include/net/ip6_route.h
> > @@ -100,6 +100,8 @@ static inline struct dst_entry *ip6_route_output(struct net *net,
> > return ip6_route_output_flags(net, sk, fl6, 0);
> > }
> >
> > +int ip6_route_reply_fill_dst(struct sk_buff *skb);
> > +
> > /* Only conditionally release dst if flags indicates
> > * !RT6_LOOKUP_F_DST_NOREF or dst is in uncached_list.
> > */
>
> [ ... ]
>
> > diff --git a/net/ipv6/route.c b/net/ipv6/route.c
> > index b106e5fef9cb..e72065aa5f5a 100644
> > --- a/net/ipv6/route.c
> > +++ b/net/ipv6/route.c
> > @@ -2725,6 +2725,24 @@ struct dst_entry *ip6_route_output_flags(struct net *net,
> > }
> > EXPORT_SYMBOL_GPL(ip6_route_output_flags);
> >
> > +int ip6_route_reply_fill_dst(struct sk_buff *skb)
> > +{
> > + struct dst_entry *result;
> > + struct flowi6 fl = {
> > + .daddr = ipv6_hdr(skb)->saddr
> > + };
>
> Could ipv6_hdr(skb)->saddr read out-of-bounds memory if the IPv6 header
> spans paged fragments?
>
> Since this function is exported as EXPORT_SYMBOL_GPL for use by BPF kfuncs,
> it might be called on packets where the header isn't guaranteed to be in
> the linear data area. If there are no linearization checks like
> pskb_may_pull() before accessing ipv6_hdr(), this could read out-of-bounds
> memory or cause a page fault when processing undersized or fragmented
> packets supplied by BPF programs.
>
> The v6 cover letter mentions "call pskb_network_may_pull on the cloned
> pkt", but this linearization check is not present in this function.
>
> While the netfilter caller (nf_send_unreach6) may ensure the header is
> linearized, the exported function itself doesn't validate this condition.
>
> Reference: https://lore.kernel.org/bpf/1637d7e04ac3c65239ef77709175697bff287250913d1da68e8d4072e0ff34e1@mail.kernel.org/
This is the same remark as on IPv4 on which I already responded, I don't
think that's an actual issue.
>
> > + int err;
> > +
> > + result = ip6_route_output(dev_net(skb->dev), NULL, &fl);
>
> Can skb->dev be NULL in contexts where this function will be used?
>
> Since this function is exported as EXPORT_SYMBOL_GPL for use by BPF
> kfuncs, it might be called from program types where the device reference
> is not retained, such as socket filters or tc ingress before device
> assignment. If skb->dev is NULL in these contexts, dev_net(skb->dev)
> would cause a NULL pointer dereference.
>
> While the netfilter caller (nf_send_unreach6) checks and sets skb->dev
> to loopback_dev if NULL, the exported function itself does not have this
> protection.
>
> Reference: https://lore.kernel.org/bpf/20260421111321.94393C2BCB0@smtp.kernel.org/
Dito.
>
> > + err = result->error;
> > + if (err)
> > + dst_release(result);
> > + else
> > + skb_dst_set(skb, result);
> > + return err;
> > +}
> > +EXPORT_SYMBOL_GPL(ip6_route_reply_fill_dst);
> > +
> > struct dst_entry *ip6_blackhole_route(struct net *net, struct dst_entry *dst_orig)
> > {
> > struct rt6_info *rt, *ort = dst_rt6_info(dst_orig);
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26459248718
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH bpf-next v7 3/7] bpf: add bpf_icmp_send kfunc
2026-05-26 15:37 [PATCH bpf-next v7 0/7] bpf: add icmp_send kfunc Mahe Tardy
2026-05-26 15:37 ` [PATCH bpf-next v7 1/7] net: move netfilter nf_reject_fill_skb_dst to core ipv4 Mahe Tardy
2026-05-26 15:37 ` [PATCH bpf-next v7 2/7] net: move netfilter nf_reject6_fill_skb_dst to core ipv6 Mahe Tardy
@ 2026-05-26 15:37 ` Mahe Tardy
2026-05-26 15:37 ` [PATCH bpf-next v7 4/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb tests Mahe Tardy
` (3 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 15:37 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
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).
* We protect against recursion since the kfunc, by generating an ICMP
error message, could retrigger the BPF prog that invoked it.
For now, we support cgroup_skb and tc program types. For cgroup_skb and
tc egress, almost everything should be good. However for tc ingress:
- packet will not be routed yet: need to set the net device for
icmp_send, thus the call to ip[6]_route_reply_fill_dst.
- fragments could trigger hook: icmp_send will only reply to fragment 0.
- ensure the ip headers is linearized before processing, and zero out
the SKB control block after cloning to prevent icmp_send()/icmpv6_send()
from misinterpreting garbage data as IP options.
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 | 109 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/net/core/filter.c b/net/core/filter.c
index 9590877b0714..6db0bdd71c6f 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,101 @@ __bpf_kfunc int bpf_xdp_pull_data(struct xdp_md *x, u32 len)
return 0;
}
+/**
+ * bpf_icmp_send - Send an ICMP control message
+ * @skb_ctx: 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.
+ *
+ * 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
+ * -EHOSTUNREACH: Routing failed
+ * -EPROTONOSUPPORT: Non-IP protocol
+ * -EOPNOTSUPP: Unsupported ICMP type
+ */
+__bpf_kfunc int bpf_icmp_send(struct __sk_buff *skb_ctx, int type, int code)
+{
+ struct sk_buff *skb = (struct sk_buff *)skb_ctx;
+ struct sk_buff *nskb;
+ struct sock *sk;
+
+ sk = skb_to_full_sk(skb);
+ if (sk && sk->sk_kern_sock &&
+ (sk->sk_protocol == IPPROTO_ICMP || sk->sk_protocol == IPPROTO_ICMPV6))
+ 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;
+
+ nskb = skb_clone(skb, GFP_ATOMIC);
+ if (!nskb)
+ return -ENOMEM;
+
+ if (!pskb_network_may_pull(nskb, sizeof(struct iphdr))) {
+ kfree_skb(nskb);
+ return -EBADMSG;
+ }
+
+ if (!skb_dst(nskb) && ip_route_reply_fill_dst(nskb) < 0) {
+ kfree_skb(nskb);
+ return -EHOSTUNREACH;
+ }
+
+ memset(IPCB(nskb), 0, sizeof(struct inet_skb_parm));
+
+ icmp_send(nskb, type, code, 0);
+ consume_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;
+
+ nskb = skb_clone(skb, GFP_ATOMIC);
+ if (!nskb)
+ return -ENOMEM;
+
+ if (!pskb_network_may_pull(nskb, sizeof(struct ipv6hdr))) {
+ kfree_skb(nskb);
+ return -EBADMSG;
+ }
+
+ if (!skb_dst(nskb) && ip6_route_reply_fill_dst(nskb) < 0) {
+ kfree_skb(nskb);
+ return -EHOSTUNREACH;
+ }
+
+ memset(IP6CB(nskb), 0, sizeof(struct inet6_skb_parm));
+
+ icmpv6_send(nskb, type, code, 0);
+ consume_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 +12603,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 +12637,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 +12663,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] 13+ messages in thread* [PATCH bpf-next v7 4/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb tests
2026-05-26 15:37 [PATCH bpf-next v7 0/7] bpf: add icmp_send kfunc Mahe Tardy
` (2 preceding siblings ...)
2026-05-26 15:37 ` [PATCH bpf-next v7 3/7] bpf: add bpf_icmp_send kfunc Mahe Tardy
@ 2026-05-26 15:37 ` Mahe Tardy
2026-05-26 16:20 ` bot+bpf-ci
2026-05-26 15:37 ` [PATCH bpf-next v7 5/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb IPv6 tests Mahe Tardy
` (2 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 15:37 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
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..0dc6b6ceafb4
--- /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)
+{
+ struct sock_extended_err *sock_err;
+ char ctrl_buf[512];
+ struct msghdr msg = {
+ .msg_control = ctrl_buf,
+ .msg_controllen = sizeof(ctrl_buf),
+ };
+ struct pollfd pfd = {
+ .fd = sockfd,
+ .events = POLLERR,
+ };
+ struct cmsghdr *cm;
+ ssize_t n;
+
+ 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");
+ return;
+ }
+
+ ASSERT_FAIL("no IP_RECVERR/IPV6_RECVERR control message found");
+}
+
+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_OK_FD(client_fd, "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_cgroup(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_OK_FD(cgroup_fd, "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] 13+ messages in thread* Re: [PATCH bpf-next v7 4/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb tests
2026-05-26 15:37 ` [PATCH bpf-next v7 4/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb tests Mahe Tardy
@ 2026-05-26 16:20 ` bot+bpf-ci
2026-05-26 22:05 ` Mahe Tardy
0 siblings, 1 reply; 13+ messages in thread
From: bot+bpf-ci @ 2026-05-26 16:20 UTC (permalink / raw)
To: mahe.tardy, bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
mahe.tardy, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2603 bytes --]
> 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..0dc6b6ceafb4
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
> @@ -0,0 +1,149 @@
[ ... ]
> +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;
Jordan Rife raised a style concern in v6 about using ASSERT_OK_FD
instead of ASSERT_GE for file descriptor checks. While this was addressed
for client_fd and cgroup_fd, the srv_fd check here still uses ASSERT_GE.
Should this be ASSERT_OK_FD for consistency?
https://lore.kernel.org/bpf/q46w47cqezttz2otf76tncxu7sovhywy2dqwcbajigp64uhbtz@eimvxfeeexuz/
[ ... ]
> +void test_icmp_send_unreach_cgroup(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_OK_FD(cgroup_fd, "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);
Jordan Rife raised a concern in v6 about the cleanup path potentially
closing an invalid file descriptor. Should this be guarded with
'if (cgroup_fd != -1)' before calling close(cgroup_fd)?
https://lore.kernel.org/bpf/q46w47cqezttz2otf76tncxu7sovhywy2dqwcbajigp64uhbtz@eimvxfeeexuz/
> +}
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26459248718
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH bpf-next v7 4/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb tests
2026-05-26 16:20 ` bot+bpf-ci
@ 2026-05-26 22:05 ` Mahe Tardy
0 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 22:05 UTC (permalink / raw)
To: bot+bpf-ci
Cc: bpf, martin.lau, daniel, john.fastabend, ast, andrii,
yonghong.song, jordan, netdev, netfilter-devel, edumazet, kuba,
pabeni, martin.lau, eddyz87, clm, ihor.solodrai
On Tue, May 26, 2026 at 04:20:12PM +0000, bot+bpf-ci@kernel.org wrote:
> > 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..0dc6b6ceafb4
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
> > @@ -0,0 +1,149 @@
>
> [ ... ]
>
> > +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;
>
> Jordan Rife raised a style concern in v6 about using ASSERT_OK_FD
> instead of ASSERT_GE for file descriptor checks. While this was addressed
> for client_fd and cgroup_fd, the srv_fd check here still uses ASSERT_GE.
> Should this be ASSERT_OK_FD for consistency?
>
> https://lore.kernel.org/bpf/q46w47cqezttz2otf76tncxu7sovhywy2dqwcbajigp64uhbtz@eimvxfeeexuz/
Indeed, this is a miss, not sure it's worth submitting a new version
just for it, but will be fixed if I need to push another version.
>
> [ ... ]
>
> > +void test_icmp_send_unreach_cgroup(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_OK_FD(cgroup_fd, "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);
>
> Jordan Rife raised a concern in v6 about the cleanup path potentially
> closing an invalid file descriptor. Should this be guarded with
> 'if (cgroup_fd != -1)' before calling close(cgroup_fd)?
>
> https://lore.kernel.org/bpf/q46w47cqezttz2otf76tncxu7sovhywy2dqwcbajigp64uhbtz@eimvxfeeexuz/
Indeed another miss, could be fixed as well if I send a new version, not
sure it's a big deal.
>
> > +}
>
> [ ... ]
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26459248718
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH bpf-next v7 5/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb IPv6 tests
2026-05-26 15:37 [PATCH bpf-next v7 0/7] bpf: add icmp_send kfunc Mahe Tardy
` (3 preceding siblings ...)
2026-05-26 15:37 ` [PATCH bpf-next v7 4/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb tests Mahe Tardy
@ 2026-05-26 15:37 ` Mahe Tardy
2026-05-26 15:37 ` [PATCH bpf-next v7 6/7] selftests/bpf: add bpf_icmp_send kfunc tc tests Mahe Tardy
2026-05-26 15:37 ` [PATCH bpf-next v7 7/7] selftests/bpf: add bpf_icmp_send recursion test Mahe Tardy
6 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 15:37 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
Mahe Tardy
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>
---
.../bpf/prog_tests/icmp_send_kfunc.c | 77 +++++++++++++------
tools/testing/selftests/bpf/progs/icmp_send.c | 48 +++++++++---
2 files changed, 92 insertions(+), 33 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 0dc6b6ceafb4..1d6900d6a8f8 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,8 +42,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 = {
@@ -61,15 +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");
@@ -79,14 +94,15 @@ static void read_icmp_errqueue(int sockfd, int expected_code)
ASSERT_FAIL("no IP_RECVERR/IPV6_RECVERR control message found");
}
-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;
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"))
+ srv_fd = start_server(af, SOCK_STREAM, ip, 0, TIMEOUT_MS);
+ if (!ASSERT_OK_FD(srv_fd, "start_server"))
return;
if (getsockname(srv_fd, (struct sockaddr *)&addr, &len)) {
@@ -94,6 +110,8 @@ static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code)
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 +121,33 @@ static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code)
}
/* 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_cgroup(void)
{
struct icmp_send *skel;
@@ -128,20 +166,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++) {
- /* 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] 13+ messages in thread* [PATCH bpf-next v7 6/7] selftests/bpf: add bpf_icmp_send kfunc tc tests
2026-05-26 15:37 [PATCH bpf-next v7 0/7] bpf: add icmp_send kfunc Mahe Tardy
` (4 preceding siblings ...)
2026-05-26 15:37 ` [PATCH bpf-next v7 5/7] selftests/bpf: add bpf_icmp_send kfunc cgroup_skb IPv6 tests Mahe Tardy
@ 2026-05-26 15:37 ` Mahe Tardy
2026-05-26 15:37 ` [PATCH bpf-next v7 7/7] selftests/bpf: add bpf_icmp_send recursion test Mahe Tardy
6 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 15:37 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
Mahe Tardy
This test is similar to the one with cgroup_skb programs but uses tc
egress instead.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../bpf/prog_tests/icmp_send_kfunc.c | 25 ++++++++
tools/testing/selftests/bpf/progs/icmp_send.c | 60 +++++++++++++++++++
2 files changed, 85 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 1d6900d6a8f8..51f809ea6896 100644
--- a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
+++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
@@ -176,3 +176,28 @@ void test_icmp_send_unreach_cgroup(void)
icmp_send__destroy(skel);
close(cgroup_fd);
}
+
+void test_icmp_send_unreach_tc(void)
+{
+ LIBBPF_OPTS(bpf_tcx_opts, opts);
+ struct icmp_send *skel;
+ struct bpf_link *link = NULL;
+
+ skel = icmp_send__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ goto cleanup;
+
+ link = bpf_program__attach_tcx(skel->progs.tc_egress, 1, &opts);
+ if (!ASSERT_OK_PTR(link, "prog_attach"))
+ goto cleanup;
+
+ if (test__start_subtest("ipv4"))
+ run_icmp_test(skel, AF_INET, "127.0.0.1", NR_ICMP_UNREACH);
+
+ if (test__start_subtest("ipv6"))
+ run_icmp_test(skel, AF_INET6, "::1", ICMPV6_REJECT_ROUTE);
+
+cleanup:
+ bpf_link__destroy(link);
+ 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 6e1ba539eeb0..5fa5467bdb70 100644
--- a/tools/testing/selftests/bpf/progs/icmp_send.c
+++ b/tools/testing/selftests/bpf/progs/icmp_send.c
@@ -2,6 +2,7 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
+#include "bpf_tracing_net.h"
/* 127.0.0.1 in host byte order */
#define SERVER_IP 0x7F000001
@@ -65,4 +66,63 @@ int egress(struct __sk_buff *skb)
return SK_DROP;
}
+SEC("tc/egress")
+int tc_egress(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct ethhdr *eth;
+ struct iphdr *iph;
+ struct ipv6hdr *ip6h;
+ struct tcphdr *tcph;
+
+ eth = data;
+ if ((void *)(eth + 1) > data_end)
+ return TCX_PASS;
+
+ if (eth->h_proto == bpf_htons(ETH_P_IP)) {
+ iph = (void *)(eth + 1);
+ if ((void *)(iph + 1) > data_end)
+ return TCX_PASS;
+
+ if (iph->protocol != IPPROTO_TCP ||
+ iph->daddr != bpf_htonl(SERVER_IP))
+ return TCX_PASS;
+
+ tcph = (void *)iph + iph->ihl * 4;
+ if ((void *)(tcph + 1) > data_end)
+ return TCX_PASS;
+
+ if (tcph->dest != bpf_htons(server_port))
+ return TCX_PASS;
+
+ } else if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
+ ip6h = (void *)(eth + 1);
+ if ((void *)(ip6h + 1) > data_end)
+ return TCX_PASS;
+
+ if (ip6h->nexthdr != IPPROTO_TCP)
+ return TCX_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 TCX_PASS;
+
+ tcph = (void *)(ip6h + 1);
+ if ((void *)(tcph + 1) > data_end)
+ return TCX_PASS;
+
+ if (tcph->dest != bpf_htons(server_port))
+ return TCX_PASS;
+ } else {
+ return TCX_PASS;
+ }
+
+ kfunc_ret = bpf_icmp_send(skb, unreach_type, unreach_code);
+
+ return TCX_DROP;
+}
+
char LICENSE[] SEC("license") = "Dual BSD/GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH bpf-next v7 7/7] selftests/bpf: add bpf_icmp_send recursion test
2026-05-26 15:37 [PATCH bpf-next v7 0/7] bpf: add icmp_send kfunc Mahe Tardy
` (5 preceding siblings ...)
2026-05-26 15:37 ` [PATCH bpf-next v7 6/7] selftests/bpf: add bpf_icmp_send kfunc tc tests Mahe Tardy
@ 2026-05-26 15:37 ` Mahe Tardy
6 siblings, 0 replies; 13+ messages in thread
From: Mahe Tardy @ 2026-05-26 15:37 UTC (permalink / raw)
To: bpf
Cc: martin.lau, daniel, john.fastabend, ast, andrii, yonghong.song,
jordan, netdev, netfilter-devel, edumazet, kuba, pabeni,
Mahe Tardy
This test is similar to test_icmp_send_unreach_cgroup 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. Since it's attached only for
this recursion test, it should not disrupt the whole network.
Signed-off-by: Mahe Tardy <mahe.tardy@gmail.com>
---
.../bpf/prog_tests/icmp_send_kfunc.c | 42 +++++++++++++++++-
tools/testing/selftests/bpf/progs/icmp_send.c | 44 +++++++++++++++++++
2 files changed, 85 insertions(+), 1 deletion(-)
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 51f809ea6896..f48e1d41e3ed 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
@@ -193,7 +195,6 @@ void test_icmp_send_unreach_tc(void)
if (test__start_subtest("ipv4"))
run_icmp_test(skel, AF_INET, "127.0.0.1", NR_ICMP_UNREACH);
-
if (test__start_subtest("ipv6"))
run_icmp_test(skel, AF_INET6, "::1", ICMPV6_REJECT_ROUTE);
@@ -201,3 +202,42 @@ void test_icmp_send_unreach_tc(void)
bpf_link__destroy(link);
icmp_send__destroy(skel);
}
+
+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 5fa5467bdb70..c899fb7b28d2 100644
--- a/tools/testing/selftests/bpf/progs/icmp_send.c
+++ b/tools/testing/selftests/bpf/progs/icmp_send.c
@@ -14,6 +14,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)
{
@@ -125,4 +128,45 @@ int tc_egress(struct __sk_buff *skb)
return TCX_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 tcphdr *tcph;
+ struct iphdr *iph;
+ int ret;
+
+ iph = data;
+ if ((void *)(iph + 1) > data_end || iph->version != 4)
+ return SK_PASS;
+
+ if (iph->daddr != bpf_htonl(SERVER_IP))
+ return SK_PASS;
+
+ if (iph->protocol == IPPROTO_TCP) {
+ tcph = (void *)iph + iph->ihl * 4;
+ if ((void *)(tcph + 1) > data_end ||
+ tcph->dest != bpf_htons(server_port))
+ return SK_PASS;
+ } else if (iph->protocol != IPPROTO_ICMP) {
+ 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);
+
+ /* Let the first ICMP error message pass */
+ if (iph->protocol == IPPROTO_ICMP)
+ return SK_PASS;
+
+ return SK_DROP;
+}
+
char LICENSE[] SEC("license") = "Dual BSD/GPL";
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread