tools/xdpdump/XdpDumpKern.h (227 lines of code) (raw):

/* Copyright (C) 2018-present, Facebook, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include <string> const std::string kXdpDumpProg = R"***( #include <uapi/linux/bpf.h> #include <uapi/linux/icmp.h> #include <uapi/linux/icmpv6.h> #include <uapi/linux/if_ether.h> #include <uapi/linux/in.h> #include <uapi/linux/ip.h> #include <uapi/linux/ipv6.h> #include <uapi/linux/pkt_cls.h> #include <uapi/linux/tcp.h> #include <uapi/linux/udp.h> // we dont want to do htons for each packet, so this is ETH_P_IPV6 and // ETH_P_IP in be format #define BE_ETH_P_IP 8 #define BE_ETH_P_IPV6 56710 #define MAX_LEN 128 #define IPV4_HDR_LEN_NO_OPT 20 #define IPV4_PLUS_ICMP_HDR 28 #define IPV6_PLUS_ICMP_HDR 48 // cleint's packet metadata struct packet_description { union { __be32 src; __be32 srcv6[4]; }; union { __be32 dst; __be32 dstv6[4]; }; union { __u32 ports; __u16 port16[2]; }; __u8 proto; __u8 flags; }; struct XdpDumpOutput { union { __u32 src; __u32 srcv6[4]; }; union { __u32 dst; __u32 dstv6[4]; }; bool ipv6; __u16 sport; __u16 dport; __u8 proto; __u16 pkt_size; __u16 data_len; }; BPF_TABLE("extern", __u32, __u32, jmp, 16); BPF_PERF_OUTPUT(perf_event_map); __attribute__((__always_inline__)) static inline __u64 calc_offset(bool is_ipv6) { __u64 off = sizeof(struct ethhdr); if (is_ipv6) { off += sizeof(struct ipv6hdr); } else { off += sizeof(struct iphdr); } return off; } __attribute__((__always_inline__)) static inline bool parse_udp(void *data, void *data_end, bool is_ipv6, struct packet_description *pckt) { __u64 off = calc_offset(is_ipv6); struct udphdr *udp; udp = data + off; if ((void *)(udp + 1) > data_end) { return false; } pckt->port16[0] = udp->source; pckt->port16[1] = udp->dest; return true; } __attribute__((__always_inline__)) static inline bool parse_tcp(void *data, void *data_end, bool is_ipv6, struct packet_description *pckt) { __u64 off = calc_offset(is_ipv6); struct tcphdr *tcp; tcp = data + off; if ((void *)(tcp + 1) > data_end) { return false; } pckt->port16[0] = tcp->source; pckt->port16[1] = tcp->dest; return true; } __attribute__((__always_inline__)) static inline void process_packet(void *data, __u64 off, void *data_end, bool is_ipv6, struct xdp_md *xdp) { struct iphdr *iph; struct ipv6hdr *ip6h; struct packet_description pckt = {}; __u64 iph_len; __u8 protocol; struct XdpDumpOutput output = {}; #ifdef CPU_NUMBER if (bpf_get_smp_processor_id() != CPU_NUMBER) { return; } #endif if (is_ipv6) { ip6h = data + off; if ((void *)(ip6h + 1) > data_end) { return; } iph_len = sizeof(struct ipv6hdr); protocol = ip6h->nexthdr; pckt.proto = protocol; off += iph_len; memcpy(pckt.srcv6, ip6h->saddr.s6_addr32, 16); memcpy(pckt.dstv6, ip6h->daddr.s6_addr32, 16); } else { iph = data + off; if ((void *)(iph + 1) > data_end) { return; } protocol = iph->protocol; pckt.proto = protocol; off += IPV4_HDR_LEN_NO_OPT; pckt.src = iph->saddr; pckt.dst = iph->daddr; } protocol = pckt.proto; if (protocol == IPPROTO_TCP) { parse_tcp(data, data_end, is_ipv6, &pckt); } else if (protocol == IPPROTO_UDP) { parse_udp(data, data_end, is_ipv6, &pckt); } #ifdef SRCV6_0 if ((pckt.srcv6[0] != SRCV6_0) || (pckt.srcv6[1] != SRCV6_1) || (pckt.srcv6[2] != SRCV6_2) || (pckt.srcv6[3] != SRCV6_3)) { return; } #endif #ifdef DSTV6_0 if ((pckt.dstv6[0] != DSTV6_0) || (pckt.dstv6[1] != DSTV6_1) || (pckt.dstv6[2] != DSTV6_2) || (pckt.dstv6[3] != DSTV6_3)) { return; } #endif #ifdef SRCV4 if(pckt.src != SRCV4) { return; } #endif #ifdef DSTV4 if(pckt.dst != DSTV4) { return; } #endif #ifdef SPORT if(pckt.port16[0] != SPORT) { return; } #endif #ifdef DPORT if(pckt.port16[1] != DPORT) { return; } #endif #ifdef PROTO if(pckt.proto != PROTO) { return; } #endif #ifdef OFFSET if((data + sizeof(struct ethhdr) + OFFSET + sizeof(__u32)) > data_end) { return; } __u32 pkt_chunk = *(__u32 *)(data + sizeof(struct ethhdr) + OFFSET); pkt_chunk &=(0xFFFFFFFF >> ((4 - O_LEN) * 8)); if ((pkt_chunk & O_PATTERN) != O_PATTERN) { return; } #endif output.ipv6 = is_ipv6; if (is_ipv6) { memcpy(output.srcv6, pckt.srcv6, 16); memcpy(output.dstv6, pckt.dstv6, 16); } else { output.src = pckt.src; output.dst = pckt.dst; } output.sport = pckt.port16[0]; output.dport = pckt.port16[1]; output.proto = pckt.proto; output.pkt_size = data_end - data; __u16 data_len = output.pkt_size < MAX_LEN ? output.pkt_size : MAX_LEN; output.data_len = data_len; perf_event_map.perf_submit_skb(xdp, data_len, &output, sizeof(output)); return; } __attribute__((__always_inline__)) int xdpdump(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; struct ethhdr *eth = data; __u32 eth_proto; __u32 nh_off; nh_off = sizeof(struct ethhdr); if (!(data + nh_off > data_end)) { eth_proto = eth->h_proto; if (eth_proto == BE_ETH_P_IP) { process_packet(data, nh_off, data_end, false, ctx); } else if (eth_proto == BE_ETH_P_IPV6) { process_packet(data, nh_off, data_end, true, ctx); } } #pragma clang loop unroll(full) for (int i = 1; i < 16; i++) { jmp.call((void *)ctx, i); } return XDP_PASS; } )***";