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. This reuse 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). The kfunc set the dst of the cloned skb by using the saddr as the daddr and routing it. * Checksums are not computed or verified and IPv4 fragmentation is not checked early (icmp_send will check). * We protect against recursion since the kfunc, by generating an ICMP error message could retrigger the BPF prog that invoked it. Signed-off-by: Mahe Tardy --- net/core/filter.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/net/core/filter.c b/net/core/filter.c index fcfcb72663ca..a6c3b9145c93 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -84,6 +84,10 @@ #include #include #include +#include +#include +#include +#include #include "dev.h" @@ -12423,6 +12427,86 @@ __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_unreach - Send ICMP destination unreachable error + * @skb: Packet that triggered the error + * @code: ICMP unreachable code (0-15 for IPv4, 0-6 for IPv6) + * + * Sends an ICMP destination unreachable message in response to the + * packet. The original packet is cloned before sending the ICMP error, + * so the BPF program can still let the packet pass if desired. + * + * 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 + * -ENOMEM: Memory allocation failed + * -EHOSTUNREACH: Routing lookup failed + * -EBUSY: Recursion detected + * -EPROTONOSUPPORT: Non-IP protocol + */ +__bpf_kfunc int bpf_icmp_send_unreach(struct __sk_buff *__skb, 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 (code < 0 || code > NR_ICMP_UNREACH) + return -EINVAL; + + nskb = skb_clone(skb, GFP_ATOMIC); + if (!nskb) + return -ENOMEM; + + if (!skb_dst(nskb) && ip_route_reply_fetch_dst(nskb) < 0) { + kfree_skb(nskb); + return -EHOSTUNREACH; + } + + *in_progress = true; + icmp_send(nskb, ICMP_DEST_UNREACH, code, 0); + *in_progress = false; + kfree_skb(nskb); + break; +#endif +#if IS_ENABLED(CONFIG_IPV6) + case htons(ETH_P_IPV6): + if (code < 0 || code > ICMPV6_REJECT_ROUTE) + return -EINVAL; + + nskb = skb_clone(skb, GFP_ATOMIC); + if (!nskb) + return -ENOMEM; + + if (!skb_dst(nskb) && ip6_route_reply_fetch_dst(nskb) < 0) { + kfree_skb(nskb); + return -EHOSTUNREACH; + } + + *in_progress = true; + icmpv6_send(nskb, ICMPV6_DEST_UNREACH, 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, @@ -12442,6 +12526,7 @@ int bpf_dynptr_from_skb_rdonly(struct __sk_buff *skb, u64 flags, BTF_KFUNCS_START(bpf_kfunc_check_set_skb) BTF_ID_FLAGS(func, bpf_dynptr_from_skb) +BTF_ID_FLAGS(func, bpf_icmp_send_unreach) BTF_KFUNCS_END(bpf_kfunc_check_set_skb) BTF_KFUNCS_START(bpf_kfunc_check_set_skb_meta) -- 2.34.1