1 /* SPDX-License-Identifier: GPL-2.0 2 * Copyright (c) 2018 Facebook 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of version 2 of the GNU General Public 6 * License as published by the Free Software Foundation. 7 * 8 * This program shows how to use bpf_xdp_adjust_tail() by 9 * generating ICMPv4 "packet to big" (unreachable/ df bit set frag needed 10 * to be more preice in case of v4)" where receiving packets bigger then 11 * 600 bytes. 12 */ 13 #define KBUILD_MODNAME "foo" 14 #include <uapi/linux/bpf.h> 15 #include <linux/in.h> 16 #include <linux/if_ether.h> 17 #include <linux/if_packet.h> 18 #include <linux/if_vlan.h> 19 #include <linux/ip.h> 20 #include <linux/icmp.h> 21 #include "bpf_helpers.h" 22 23 #define DEFAULT_TTL 64 24 #define MAX_PCKT_SIZE 600 25 #define ICMP_TOOBIG_SIZE 98 26 #define ICMP_TOOBIG_PAYLOAD_SIZE 92 27 28 struct bpf_map_def SEC("maps") icmpcnt = { 29 .type = BPF_MAP_TYPE_ARRAY, 30 .key_size = sizeof(__u32), 31 .value_size = sizeof(__u64), 32 .max_entries = 1, 33 }; 34 35 static __always_inline void count_icmp(void) 36 { 37 u64 key = 0; 38 u64 *icmp_count; 39 40 icmp_count = bpf_map_lookup_elem(&icmpcnt, &key); 41 if (icmp_count) 42 *icmp_count += 1; 43 } 44 45 static __always_inline void swap_mac(void *data, struct ethhdr *orig_eth) 46 { 47 struct ethhdr *eth; 48 49 eth = data; 50 memcpy(eth->h_source, orig_eth->h_dest, ETH_ALEN); 51 memcpy(eth->h_dest, orig_eth->h_source, ETH_ALEN); 52 eth->h_proto = orig_eth->h_proto; 53 } 54 55 static __always_inline __u16 csum_fold_helper(__u32 csum) 56 { 57 return ~((csum & 0xffff) + (csum >> 16)); 58 } 59 60 static __always_inline void ipv4_csum(void *data_start, int data_size, 61 __u32 *csum) 62 { 63 *csum = bpf_csum_diff(0, 0, data_start, data_size, *csum); 64 *csum = csum_fold_helper(*csum); 65 } 66 67 static __always_inline int send_icmp4_too_big(struct xdp_md *xdp) 68 { 69 int headroom = (int)sizeof(struct iphdr) + (int)sizeof(struct icmphdr); 70 71 if (bpf_xdp_adjust_head(xdp, 0 - headroom)) 72 return XDP_DROP; 73 void *data = (void *)(long)xdp->data; 74 void *data_end = (void *)(long)xdp->data_end; 75 76 if (data + (ICMP_TOOBIG_SIZE + headroom) > data_end) 77 return XDP_DROP; 78 79 struct iphdr *iph, *orig_iph; 80 struct icmphdr *icmp_hdr; 81 struct ethhdr *orig_eth; 82 __u32 csum = 0; 83 __u64 off = 0; 84 85 orig_eth = data + headroom; 86 swap_mac(data, orig_eth); 87 off += sizeof(struct ethhdr); 88 iph = data + off; 89 off += sizeof(struct iphdr); 90 icmp_hdr = data + off; 91 off += sizeof(struct icmphdr); 92 orig_iph = data + off; 93 icmp_hdr->type = ICMP_DEST_UNREACH; 94 icmp_hdr->code = ICMP_FRAG_NEEDED; 95 icmp_hdr->un.frag.mtu = htons(MAX_PCKT_SIZE-sizeof(struct ethhdr)); 96 icmp_hdr->checksum = 0; 97 ipv4_csum(icmp_hdr, ICMP_TOOBIG_PAYLOAD_SIZE, &csum); 98 icmp_hdr->checksum = csum; 99 iph->ttl = DEFAULT_TTL; 100 iph->daddr = orig_iph->saddr; 101 iph->saddr = orig_iph->daddr; 102 iph->version = 4; 103 iph->ihl = 5; 104 iph->protocol = IPPROTO_ICMP; 105 iph->tos = 0; 106 iph->tot_len = htons( 107 ICMP_TOOBIG_SIZE + headroom - sizeof(struct ethhdr)); 108 iph->check = 0; 109 csum = 0; 110 ipv4_csum(iph, sizeof(struct iphdr), &csum); 111 iph->check = csum; 112 count_icmp(); 113 return XDP_TX; 114 } 115 116 117 static __always_inline int handle_ipv4(struct xdp_md *xdp) 118 { 119 void *data_end = (void *)(long)xdp->data_end; 120 void *data = (void *)(long)xdp->data; 121 int pckt_size = data_end - data; 122 int offset; 123 124 if (pckt_size > MAX_PCKT_SIZE) { 125 offset = pckt_size - ICMP_TOOBIG_SIZE; 126 if (bpf_xdp_adjust_tail(xdp, 0 - offset)) 127 return XDP_PASS; 128 return send_icmp4_too_big(xdp); 129 } 130 return XDP_PASS; 131 } 132 133 SEC("xdp_icmp") 134 int _xdp_icmp(struct xdp_md *xdp) 135 { 136 void *data_end = (void *)(long)xdp->data_end; 137 void *data = (void *)(long)xdp->data; 138 struct ethhdr *eth = data; 139 __u16 h_proto; 140 141 if (eth + 1 > data_end) 142 return XDP_DROP; 143 144 h_proto = eth->h_proto; 145 146 if (h_proto == htons(ETH_P_IP)) 147 return handle_ipv4(xdp); 148 else 149 return XDP_PASS; 150 } 151 152 char _license[] SEC("license") = "GPL"; 153