|
#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"; |