Skip to content

Instantly share code, notes, and snippets.

@idosch

idosch/README.md Secret

Last active October 16, 2025 07:21
Show Gist options
  • Select an option

  • Save idosch/5013448cdb5e9e060e6bfdc8b433577c to your computer and use it in GitHub Desktop.

Select an option

Save idosch/5013448cdb5e9e060e6bfdc8b433577c to your computer and use it in GitHub Desktop.
ICMP extension structure trimming

The following tc-bpf program can be used to trim the ICMP extension structure from ICMP error messages. Example commands to compile and attach:

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
$ clang -O2 -target bpf -g -c icmp_ext_trim.c -o icmp_ext_trim.o
# tc qdisc add dev dummy1 clsact
# tc filter add dev dummy1 egress bpf obj icmp_ext_trim.o sec tc da
#include "vmlinux.h"
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#define TC_ACT_OK 0
#define TC_ACT_SHOT 2
#define ETH_P_IP 0x0800
#define ETH_P_IPV6 0x86DD
#define IPPROTO_ICMP 1
#define IPPROTO_ICMPV6 58
#define ICMP_DEST_UNREACH 3
#define ICMP_TIME_EXCEEDED 11
#define ICMP_PARAMETERPROB 12
#define ICMPV6_DEST_UNREACH 1
#define ICMPV6_TIME_EXCEED 3
#define IP_CSUM_OFF offsetof(struct iphdr, check)
#define ICMP_CSUM_OFF offsetof(struct icmphdr, checksum)
#define ICMPV6_CSUM_OFF offsetof(struct icmp6hdr, icmp6_cksum)
#define ICMP_PAYLOAD_LEN 128
static __u16 csum_fold(__u32 csum)
{
csum = (csum & 0xffff) + (csum >> 16);
csum = (csum & 0xffff) + (csum >> 16);
return (__u16)~csum;
}
static __sum16 csum_ipv6_magic(const struct in6_addr *saddr,
const struct in6_addr *daddr,
__u32 len, __u8 proto, __wsum csum)
{
__u64 s = csum;
int i;
for (i = 0; i < 4; i++)
s += (__u32)saddr->in6_u.u6_addr32[i];
for (i = 0; i < 4; i++)
s += (__u32)daddr->in6_u.u6_addr32[i];
s += bpf_htons(proto + len);
s = (s & 0xffffffff) + (s >> 32);
s = (s & 0xffffffff) + (s >> 32);
return csum_fold((__u32)s);
}
static int __icmp4_ext_trim(struct __sk_buff *skb, struct bpf_dynptr *p,
__u32 nhoff, __u32 thoff)
{
__u8 icmp_buf[sizeof(struct icmphdr) + ICMP_PAYLOAD_LEN] = {};
__u8 icmph_buf[sizeof(struct icmphdr)] = {};
__u8 iph_buf[sizeof(struct iphdr)] = {};
struct icmphdr *icmph;
struct iphdr *iph;
__u32 new_len;
__s64 csum;
icmph = bpf_dynptr_slice(p, thoff, icmph_buf, sizeof(icmph_buf));
if (!icmph)
return TC_ACT_SHOT;
switch (icmph->type) {
case ICMP_DEST_UNREACH:
case ICMP_TIME_EXCEEDED:
case ICMP_PARAMETERPROB:
break;
default:
return TC_ACT_OK;
}
if (icmph->un.reserved[1] == 0)
return TC_ACT_OK;
if (icmph->un.reserved[1] * 4 != ICMP_PAYLOAD_LEN)
return TC_ACT_SHOT;
/* Remove the ICMP Extension Structure. */
new_len = thoff + sizeof(struct icmphdr) + ICMP_PAYLOAD_LEN;
if (bpf_skb_change_tail(skb, new_len, 0))
return TC_ACT_SHOT;
/* Adjust the IPv4 header. */
iph = bpf_dynptr_slice_rdwr(p, nhoff, iph_buf, sizeof(iph_buf));
if (!iph)
return TC_ACT_SHOT;
iph->tot_len = bpf_htons(new_len - nhoff);
iph->check = 0;
csum = bpf_csum_diff(NULL, 0, (__be32 *)iph, sizeof(struct iphdr), 0);
if (csum < 0)
return TC_ACT_SHOT;
if (bpf_l4_csum_replace(skb, nhoff + IP_CSUM_OFF, 0, csum, 0))
return TC_ACT_SHOT;
/* Recompute the ICMP checksum. */
icmph = bpf_dynptr_slice_rdwr(p, thoff, icmp_buf, sizeof(icmp_buf));
if (!icmph)
return TC_ACT_SHOT;
icmph->un.reserved[1] = 0;
icmph->checksum = 0;
csum = bpf_csum_diff(NULL, 0, (__be32 *)icmph,
sizeof(struct icmphdr) + ICMP_PAYLOAD_LEN, 0);
if (csum < 0)
return TC_ACT_SHOT;
if (bpf_l4_csum_replace(skb, thoff + ICMP_CSUM_OFF, 0, csum, 0))
return TC_ACT_SHOT;
return TC_ACT_OK;
}
static int icmp4_ext_trim(struct __sk_buff *skb, struct bpf_dynptr *p,
__u32 off)
{
__u8 iph_buf[sizeof(struct iphdr)] = {};
struct iphdr *iph;
int ret;
iph = bpf_dynptr_slice(p, off, iph_buf, sizeof(iph_buf));
if (!iph)
return TC_ACT_SHOT;
if (iph->protocol != IPPROTO_ICMP)
return TC_ACT_OK;
if (iph->ihl != 5)
return TC_ACT_SHOT;
return __icmp4_ext_trim(skb, p, off, off + sizeof(struct iphdr));
}
static int __icmp6_ext_trim(struct __sk_buff *skb, struct bpf_dynptr *p,
__u32 nhoff, __u32 thoff)
{
__u8 icmp6_buf[sizeof(struct icmp6hdr) + ICMP_PAYLOAD_LEN] = {};
__u8 icmp6h_buf[sizeof(struct icmp6hdr)] = {};
__u8 ip6h_buf[sizeof(struct ipv6hdr)] = {};
struct icmp6hdr *icmp6h;
struct ipv6hdr *ip6h;
__u32 new_len;
__s64 csum;
icmp6h = bpf_dynptr_slice(p, thoff, icmp6h_buf, sizeof(icmp6h_buf));
if (!icmp6h)
return TC_ACT_SHOT;
switch (icmp6h->icmp6_type) {
case ICMPV6_DEST_UNREACH:
case ICMPV6_TIME_EXCEED:
break;
default:
return TC_ACT_OK;
}
if (icmp6h->icmp6_dataun.un_data8[0] == 0)
return TC_ACT_OK;
if (icmp6h->icmp6_dataun.un_data8[0] * 8 != ICMP_PAYLOAD_LEN)
return TC_ACT_SHOT;
/* Remove the ICMP Extension Structure. */
new_len = thoff + sizeof(struct icmp6hdr) + ICMP_PAYLOAD_LEN;
if (bpf_skb_change_tail(skb, new_len, 0))
return TC_ACT_SHOT;
/* Adjust the IPv6 header. */
ip6h = bpf_dynptr_slice_rdwr(p, nhoff, ip6h_buf, sizeof(ip6h_buf));
if (!ip6h)
return TC_ACT_SHOT;
ip6h->payload_len = bpf_htons(new_len - thoff);
/* Recompute the ICMP checksum. */
icmp6h = bpf_dynptr_slice_rdwr(p, thoff, icmp6_buf, sizeof(icmp6_buf));
if (!icmp6h)
return TC_ACT_SHOT;
icmp6h->icmp6_dataun.un_data8[0] = 0;
icmp6h->icmp6_cksum = 0;
csum = bpf_csum_diff(NULL, 0, (__be32 *)icmp6h,
sizeof(struct icmp6hdr) + ICMP_PAYLOAD_LEN, 0);
if (csum < 0)
return TC_ACT_SHOT;
csum = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, new_len - thoff,
IPPROTO_ICMPV6, csum);
if (bpf_l4_csum_replace(skb, thoff + ICMPV6_CSUM_OFF, 0, csum,
BPF_F_IPV6))
return TC_ACT_SHOT;
return TC_ACT_OK;
}
static int icmp6_ext_trim(struct __sk_buff *skb, struct bpf_dynptr *p,
__u32 off)
{
__u8 ip6h_buf[sizeof(struct ipv6hdr)] = {};
struct ipv6hdr *ip6h;
int ret;
ip6h = bpf_dynptr_slice(p, off, ip6h_buf, sizeof(ip6h_buf));
if (!ip6h)
return TC_ACT_SHOT;
if (ip6h->nexthdr != IPPROTO_ICMPV6)
return TC_ACT_OK;
return __icmp6_ext_trim(skb, p, off, off + sizeof(struct ipv6hdr));
}
SEC("tc")
int icmp_ext_trim(struct __sk_buff *skb)
{
__u32 off = sizeof(struct ethhdr);
struct bpf_dynptr dynptr;
/* Only process locally generated packets. */
if (skb->ingress_ifindex != 0)
return TC_ACT_OK;
if (bpf_dynptr_from_skb(skb, 0, &dynptr))
return TC_ACT_SHOT;
switch (skb->protocol) {
case bpf_htons(ETH_P_IP):
return icmp4_ext_trim(skb, &dynptr, off);
case bpf_htons(ETH_P_IPV6):
return icmp6_ext_trim(skb, &dynptr, off);
default:
return TC_ACT_OK;
}
}
char _license[] SEC("license") = "GPL";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment