blob: 5c4c07545bc9f214b1ca45ddffc0b9edd0cd8f7a [file]
// SPDX-License-Identifier: GPL-2.0
#include <stdbool.h>
#include <stddef.h>
#include <linux/bpf.h>
#include <linux/icmp.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#define ICMP_SAMPLE_LEN (sizeof(struct iphdr) + 8)
#define ICMP_HDRS_LEN (sizeof(struct iphdr) + sizeof(struct icmphdr))
__be16 server_port = 0;
__u16 pmtu = 0;
long change_tail_ret = 1;
long adjust_room_ret = 0;
bool icmp_sent = false;
bool icmp_err = false;
static __always_inline __sum16 csum_fold(__wsum csum)
{
csum = (csum & 0xffff) + (csum >> 16);
csum = (csum & 0xffff) + (csum >> 16);
return (__sum16)~csum;
}
SEC("tc/egress")
int change_tail_icmp(struct __sk_buff *skb)
{
__u8 smac[ETH_ALEN], dmac[ETH_ALEN];
void *data, *data_end;
struct icmphdr *icmp;
struct ethhdr *eth;
struct tcphdr *tcp;
__be32 saddr, daddr;
struct iphdr *ip;
__wsum csum;
if (icmp_sent || icmp_err)
return TCX_PASS;
data = (void *)(long)skb->data;
data_end = (void *)(long)skb->data_end;
eth = data;
if ((void *)(eth + 1) > data_end)
return TCX_PASS;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return TCX_PASS;
ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return TCX_PASS;
if (ip->ihl != 5 || ip->protocol != IPPROTO_TCP)
return TCX_PASS;
tcp = (void *)(ip + 1);
if ((void *)(tcp + 1) > data_end)
return TCX_PASS;
if (tcp->dest != server_port)
return TCX_PASS;
if (bpf_ntohs(ip->tot_len) <= sizeof(*ip) + tcp->doff * 4)
return TCX_PASS;
__builtin_memcpy(smac, eth->h_source, ETH_ALEN);
__builtin_memcpy(dmac, eth->h_dest, ETH_ALEN);
saddr = ip->saddr;
daddr = ip->daddr;
change_tail_ret = bpf_skb_change_tail(skb, ETH_HLEN + ICMP_SAMPLE_LEN, 0);
if (change_tail_ret) {
icmp_err = true;
return TCX_PASS;
}
adjust_room_ret = bpf_skb_adjust_room(skb, ICMP_HDRS_LEN,
BPF_ADJ_ROOM_MAC,
BPF_F_ADJ_ROOM_NO_CSUM_RESET);
if (adjust_room_ret) {
icmp_err = true;
return TCX_DROP;
}
data = (void *)(long)skb->data;
data_end = (void *)(long)skb->data_end;
eth = data;
ip = (void *)(eth + 1);
icmp = (void *)(ip + 1);
if ((void *)icmp + sizeof(*icmp) + ICMP_SAMPLE_LEN > data_end) {
icmp_err = true;
return TCX_DROP;
}
__builtin_memcpy(eth->h_dest, smac, ETH_ALEN);
__builtin_memcpy(eth->h_source, dmac, ETH_ALEN);
__builtin_memset(icmp, 0, sizeof(*icmp));
icmp->type = ICMP_DEST_UNREACH;
icmp->code = ICMP_FRAG_NEEDED;
icmp->un.frag.mtu = bpf_htons(pmtu);
__builtin_memset(ip, 0, sizeof(*ip));
ip->version = 4;
ip->ihl = 5;
ip->ttl = 64;
ip->protocol = IPPROTO_ICMP;
ip->tot_len = bpf_htons(ICMP_HDRS_LEN + ICMP_SAMPLE_LEN);
ip->saddr = daddr;
ip->daddr = saddr;
csum = bpf_csum_diff(NULL, 0, (__be32 *)icmp,
sizeof(*icmp) + ICMP_SAMPLE_LEN, 0);
icmp->checksum = csum_fold(csum);
csum = bpf_csum_diff(NULL, 0, (__be32 *)ip, sizeof(*ip), 0);
ip->check = csum_fold(csum);
icmp_sent = true;
return bpf_redirect(skb->ifindex, BPF_F_INGRESS);
}
char _license[] SEC("license") = "GPL";