summaryrefslogtreecommitdiffstats
path: root/headers/xdp/parsing_helpers.h
diff options
context:
space:
mode:
Diffstat (limited to 'headers/xdp/parsing_helpers.h')
-rw-r--r--headers/xdp/parsing_helpers.h279
1 files changed, 279 insertions, 0 deletions
diff --git a/headers/xdp/parsing_helpers.h b/headers/xdp/parsing_helpers.h
new file mode 100644
index 0000000..2fc7b6a
--- /dev/null
+++ b/headers/xdp/parsing_helpers.h
@@ -0,0 +1,279 @@
+/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */
+/*
+ * This file contains parsing functions that can be used in eXDP programs. The
+ * functions are marked as __always_inline, and fully defined in this header
+ * file to be included in the BPF program.
+ *
+ * Each helper parses a packet header, including doing bounds checking, and
+ * returns the type of its contents if successful, and -1 otherwise.
+ *
+ * For Ethernet and IP headers, the content type is the type of the payload
+ * (h_proto for Ethernet, nexthdr for IPv6), for ICMP it is the ICMP type field.
+ * All return values are in host byte order.
+ */
+
+#ifndef __PARSING_HELPERS_H
+#define __PARSING_HELPERS_H
+
+#include <stddef.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
+#include <linux/udp.h>
+#include <linux/tcp.h>
+#include <linux/in.h>
+#include <bpf/bpf_endian.h>
+
+/* Header cursor to keep track of current parsing position */
+struct hdr_cursor {
+ void *pos;
+};
+
+/*
+ * struct vlan_hdr - vlan header
+ * @h_vlan_TCI: priority and VLAN ID
+ * @h_vlan_encapsulated_proto: packet type ID or len
+ */
+struct vlan_hdr {
+ __be16 h_vlan_TCI;
+ __be16 h_vlan_encapsulated_proto;
+};
+
+/*
+ * Struct icmphdr_common represents the common part of the icmphdr and icmp6hdr
+ * structures.
+ */
+struct icmphdr_common {
+ __u8 type;
+ __u8 code;
+ __sum16 cksum;
+};
+
+/* Allow users of header file to redefine VLAN max depth */
+#ifndef VLAN_MAX_DEPTH
+#define VLAN_MAX_DEPTH 4
+#endif
+
+/* Longest chain of IPv6 extension headers to resolve */
+#ifndef IPV6_EXT_MAX_CHAIN
+#define IPV6_EXT_MAX_CHAIN 6
+#endif
+
+
+static __always_inline int proto_is_vlan(__u16 h_proto)
+{
+ return !!(h_proto == bpf_htons(ETH_P_8021Q) ||
+ h_proto == bpf_htons(ETH_P_8021AD));
+}
+
+/* Notice, parse_ethhdr() will skip VLAN tags, by advancing nh->pos and returns
+ * next header EtherType, BUT the ethhdr pointer supplied still points to the
+ * Ethernet header. Thus, caller can look at eth->h_proto to see if this was a
+ * VLAN tagged packet.
+ */
+static __always_inline int parse_ethhdr(struct hdr_cursor *nh, void *data_end,
+ struct ethhdr **ethhdr)
+{
+ struct ethhdr *eth = nh->pos;
+ struct vlan_hdr *vlh;
+ __u16 h_proto;
+ int i;
+
+ if (eth + 1 > data_end)
+ return -1;
+
+ nh->pos = eth + 1;
+ *ethhdr = eth;
+ vlh = nh->pos;
+ h_proto = eth->h_proto;
+
+ /* Use loop unrolling to avoid the verifier restriction on loops;
+ * support up to VLAN_MAX_DEPTH layers of VLAN encapsulation.
+ */
+ #pragma unroll
+ for (i = 0; i < VLAN_MAX_DEPTH; i++) {
+ if (!proto_is_vlan(h_proto))
+ break;
+
+ if (vlh + 1 > data_end)
+ break;
+
+ h_proto = vlh->h_vlan_encapsulated_proto;
+ vlh++;
+ }
+
+ nh->pos = vlh;
+ return h_proto; /* network-byte-order */
+}
+
+static __always_inline int skip_ip6hdrext(struct hdr_cursor *nh,
+ void *data_end,
+ __u8 next_hdr_type)
+{
+ for (int i = 0; i < IPV6_EXT_MAX_CHAIN; ++i) {
+ struct ipv6_opt_hdr *hdr = nh->pos;
+
+ if (hdr + 1 > data_end)
+ return -1;
+
+ switch (next_hdr_type) {
+ case IPPROTO_HOPOPTS:
+ case IPPROTO_DSTOPTS:
+ case IPPROTO_ROUTING:
+ case IPPROTO_MH:
+ nh->pos = (char *)hdr + (hdr->hdrlen + 1) * 8;
+ next_hdr_type = hdr->nexthdr;
+ break;
+ case IPPROTO_AH:
+ nh->pos = (char *)hdr + (hdr->hdrlen + 2) * 4;
+ next_hdr_type = hdr->nexthdr;
+ break;
+ case IPPROTO_FRAGMENT:
+ nh->pos = (char *)hdr + 8;
+ next_hdr_type = hdr->nexthdr;
+ break;
+ default:
+ /* Found a header that is not an IPv6 extension header */
+ return next_hdr_type;
+ }
+ }
+
+ return -1;
+}
+
+static __always_inline int parse_ip6hdr(struct hdr_cursor *nh,
+ void *data_end,
+ struct ipv6hdr **ip6hdr)
+{
+ struct ipv6hdr *ip6h = nh->pos;
+
+ /* Pointer-arithmetic bounds check; pointer +1 points to after end of
+ * thing being pointed to. We will be using this style in the remainder
+ * of the tutorial.
+ */
+ if (ip6h + 1 > data_end)
+ return -1;
+
+ nh->pos = ip6h + 1;
+ *ip6hdr = ip6h;
+
+ return skip_ip6hdrext(nh, data_end, ip6h->nexthdr);
+}
+
+static __always_inline int parse_iphdr(struct hdr_cursor *nh,
+ void *data_end,
+ struct iphdr **iphdr)
+{
+ struct iphdr *iph = nh->pos;
+ int hdrsize;
+
+ if (iph + 1 > data_end)
+ return -1;
+
+ hdrsize = iph->ihl * 4;
+
+ /* Variable-length IPv4 header, need to use byte-based arithmetic */
+ if (nh->pos + hdrsize > data_end)
+ return -1;
+
+ nh->pos += hdrsize;
+ *iphdr = iph;
+
+ return iph->protocol;
+}
+
+static __always_inline int parse_icmp6hdr(struct hdr_cursor *nh,
+ void *data_end,
+ struct icmp6hdr **icmp6hdr)
+{
+ struct icmp6hdr *icmp6h = nh->pos;
+
+ if (icmp6h + 1 > data_end)
+ return -1;
+
+ nh->pos = icmp6h + 1;
+ *icmp6hdr = icmp6h;
+
+ return icmp6h->icmp6_type;
+}
+
+static __always_inline int parse_icmphdr(struct hdr_cursor *nh,
+ void *data_end,
+ struct icmphdr **icmphdr)
+{
+ struct icmphdr *icmph = nh->pos;
+
+ if (icmph + 1 > data_end)
+ return -1;
+
+ nh->pos = icmph + 1;
+ *icmphdr = icmph;
+
+ return icmph->type;
+}
+
+static __always_inline int parse_icmphdr_common(struct hdr_cursor *nh,
+ void *data_end,
+ struct icmphdr_common **icmphdr)
+{
+ struct icmphdr_common *h = nh->pos;
+
+ if (h + 1 > data_end)
+ return -1;
+
+ nh->pos = h + 1;
+ *icmphdr = h;
+
+ return h->type;
+}
+
+/*
+ * parse_udphdr: parse the udp header and return the length of the udp payload
+ */
+static __always_inline int parse_udphdr(struct hdr_cursor *nh,
+ void *data_end,
+ struct udphdr **udphdr)
+{
+ int len;
+ struct udphdr *h = nh->pos;
+
+ if (h + 1 > data_end)
+ return -1;
+
+ nh->pos = h + 1;
+ *udphdr = h;
+
+ len = bpf_ntohs(h->len) - sizeof(struct udphdr);
+ if (len < 0)
+ return -1;
+
+ return len;
+}
+
+/*
+ * parse_tcphdr: parse and return the length of the tcp header
+ */
+static __always_inline int parse_tcphdr(struct hdr_cursor *nh,
+ void *data_end,
+ struct tcphdr **tcphdr)
+{
+ int len;
+ struct tcphdr *h = nh->pos;
+
+ if (h + 1 > data_end)
+ return -1;
+
+ len = h->doff * 4;
+ if ((void *) h + len > data_end)
+ return -1;
+
+ nh->pos = h + 1;
+ *tcphdr = h;
+
+ return len;
+}
+
+#endif /* __PARSING_HELPERS_H */