// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2021 Facebook */ #include <stdbool.h> #include <stdint.h> #include <linux/stddef.h> #include <linux/if_ether.h> #include <linux/in.h> #include <linux/in6.h> #include <linux/ip.h> #include <linux/ipv6.h> #include <linux/tcp.h> #include <linux/udp.h> #include <linux/bpf.h> #include <linux/types.h> #include <bpf/bpf_endian.h> #include <bpf/bpf_helpers.h> enum pkt_parse_err { NO_ERR, BAD_IP6_HDR, BAD_IP4GUE_HDR, BAD_IP6GUE_HDR, }; enum pkt_flag { TUNNEL = 0x1, TCP_SYN = 0x2, QUIC_INITIAL_FLAG = 0x4, TCP_ACK = 0x8, TCP_RST = 0x10 }; struct v4_lpm_key { __u32 prefixlen; __u32 src; }; struct v4_lpm_val { struct v4_lpm_key key; __u8 val; }; struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 16); __type(key, struct in6_addr); __type(value, bool); } v6_addr_map SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 16); __type(key, __u32); __type(value, bool); } v4_addr_map SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_LPM_TRIE); __uint(max_entries, 16); __uint(key_size, sizeof(struct v4_lpm_key)); __uint(value_size, sizeof(struct v4_lpm_val)); __uint(map_flags, BPF_F_NO_PREALLOC); } v4_lpm_val_map SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 16); __type(key, int); __type(value, __u8); } tcp_port_map SEC(".maps"); struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 16); __type(key, int); __type(value, __u16); } udp_port_map SEC(".maps"); enum ip_type { V4 = 1, V6 = 2 }; struct fw_match_info { __u8 v4_src_ip_match; __u8 v6_src_ip_match; __u8 v4_src_prefix_match; __u8 v4_dst_prefix_match; __u8 tcp_dp_match; __u16 udp_sp_match; __u16 udp_dp_match; bool is_tcp; bool is_tcp_syn; }; struct pkt_info { enum ip_type type; union { struct iphdr *ipv4; struct ipv6hdr *ipv6; } ip; int sport; int dport; __u16 trans_hdr_offset; __u8 proto; __u8 flags; }; static __always_inline struct ethhdr *parse_ethhdr(void *data, void *data_end) { struct ethhdr *eth = data; if (eth + 1 > data_end) return NULL; return eth; } static __always_inline __u8 filter_ipv6_addr(const struct in6_addr *ipv6addr) { __u8 *leaf; leaf = bpf_map_lookup_elem(&v6_addr_map, ipv6addr); return leaf ? *leaf : 0; } static __always_inline __u8 filter_ipv4_addr(const __u32 ipaddr) { __u8 *leaf; leaf = bpf_map_lookup_elem(&v4_addr_map, &ipaddr); return leaf ? *leaf : 0; } static __always_inline __u8 filter_ipv4_lpm(const __u32 ipaddr) { struct v4_lpm_key v4_key = {}; struct v4_lpm_val *lpm_val; v4_key.src = ipaddr; v4_key.prefixlen = 32; lpm_val = bpf_map_lookup_elem(&v4_lpm_val_map, &v4_key); return lpm_val ? lpm_val->val : 0; } static __always_inline void filter_src_dst_ip(struct pkt_info* info, struct fw_match_info* match_info) { if (info->type == V6) { match_info->v6_src_ip_match = filter_ipv6_addr(&info->ip.ipv6->saddr); } else if (info->type == V4) { match_info->v4_src_ip_match = filter_ipv4_addr(info->ip.ipv4->saddr); match_info->v4_src_prefix_match = filter_ipv4_lpm(info->ip.ipv4->saddr); match_info->v4_dst_prefix_match = filter_ipv4_lpm(info->ip.ipv4->daddr); } } static __always_inline void * get_transport_hdr(__u16 offset, void *data, void *data_end) { if (offset > 255 || data + offset > data_end) return NULL; return data + offset; } static __always_inline bool tcphdr_only_contains_flag(struct tcphdr *tcp, __u32 FLAG) { return (tcp_flag_word(tcp) & (TCP_FLAG_ACK | TCP_FLAG_RST | TCP_FLAG_SYN | TCP_FLAG_FIN)) == FLAG; } static __always_inline void set_tcp_flags(struct pkt_info *info, struct tcphdr *tcp) { if (tcphdr_only_contains_flag(tcp, TCP_FLAG_SYN)) info->flags |= TCP_SYN; else if (tcphdr_only_contains_flag(tcp, TCP_FLAG_ACK)) info->flags |= TCP_ACK; else if (tcphdr_only_contains_flag(tcp, TCP_FLAG_RST)) info->flags |= TCP_RST; } static __always_inline bool parse_tcp(struct pkt_info *info, void *transport_hdr, void *data_end) { struct tcphdr *tcp = transport_hdr; if (tcp + 1 > data_end) return false; info->sport = bpf_ntohs(tcp->source); info->dport = bpf_ntohs(tcp->dest); set_tcp_flags(info, tcp); return true; } static __always_inline bool parse_udp(struct pkt_info *info, void *transport_hdr, void *data_end) { struct udphdr *udp = transport_hdr; if (udp + 1 > data_end) return false; info->sport = bpf_ntohs(udp->source); info->dport = bpf_ntohs(udp->dest); return true; } static __always_inline __u8 filter_tcp_port(int port) { __u8 *leaf = bpf_map_lookup_elem(&tcp_port_map, &port); return leaf ? *leaf : 0; } static __always_inline __u16 filter_udp_port(int port) { __u16 *leaf = bpf_map_lookup_elem(&udp_port_map, &port); return leaf ? *leaf : 0; } static __always_inline bool filter_transport_hdr(void *transport_hdr, void *data_end, struct pkt_info *info, struct fw_match_info *match_info) { if (info->proto == IPPROTO_TCP) { if (!parse_tcp(info, transport_hdr, data_end)) return false; match_info->is_tcp = true; match_info->is_tcp_syn = (info->flags & TCP_SYN) > 0; match_info->tcp_dp_match = filter_tcp_port(info->dport); } else if (info->proto == IPPROTO_UDP) { if (!parse_udp(info, transport_hdr, data_end)) return false; match_info->udp_dp_match = filter_udp_port(info->dport); match_info->udp_sp_match = filter_udp_port(info->sport); } return true; } static __always_inline __u8 parse_gue_v6(struct pkt_info *info, struct ipv6hdr *ip6h, void *data_end) { struct udphdr *udp = (struct udphdr *)(ip6h + 1); void *encap_data = udp + 1; if (udp + 1 > data_end) return BAD_IP6_HDR; if (udp->dest != bpf_htons(6666)) return NO_ERR; info->flags |= TUNNEL; if (encap_data + 1 > data_end) return BAD_IP6GUE_HDR; if (*(__u8 *)encap_data & 0x30) { struct ipv6hdr *inner_ip6h = encap_data; if (inner_ip6h + 1 > data_end) return BAD_IP6GUE_HDR; info->type = V6; info->proto = inner_ip6h->nexthdr; info->ip.ipv6 = inner_ip6h; info->trans_hdr_offset += sizeof(struct ipv6hdr) + sizeof(struct udphdr); } else { struct iphdr *inner_ip4h = encap_data; if (inner_ip4h + 1 > data_end) return BAD_IP6GUE_HDR; info->type = V4; info->proto = inner_ip4h->protocol; info->ip.ipv4 = inner_ip4h; info->trans_hdr_offset += sizeof(struct iphdr) + sizeof(struct udphdr); } return NO_ERR; } static __always_inline __u8 parse_ipv6_gue(struct pkt_info *info, void *data, void *data_end) { struct ipv6hdr *ip6h = data + sizeof(struct ethhdr); if (ip6h + 1 > data_end) return BAD_IP6_HDR; info->proto = ip6h->nexthdr; info->ip.ipv6 = ip6h; info->type = V6; info->trans_hdr_offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr); if (info->proto == IPPROTO_UDP) return parse_gue_v6(info, ip6h, data_end); return NO_ERR; } SEC("xdp") int edgewall(struct xdp_md *ctx) { void *data_end = (void *)(long)(ctx->data_end); void *data = (void *)(long)(ctx->data); struct fw_match_info match_info = {}; struct pkt_info info = {}; void *transport_hdr; struct ethhdr *eth; bool filter_res; __u32 proto; eth = parse_ethhdr(data, data_end); if (!eth) return XDP_DROP; proto = eth->h_proto; if (proto != bpf_htons(ETH_P_IPV6)) return XDP_DROP; if (parse_ipv6_gue(&info, data, data_end)) return XDP_DROP; if (info.proto == IPPROTO_ICMPV6) return XDP_PASS; if (info.proto != IPPROTO_TCP && info.proto != IPPROTO_UDP) return XDP_DROP; filter_src_dst_ip(&info, &match_info); transport_hdr = get_transport_hdr(info.trans_hdr_offset, data, data_end); if (!transport_hdr) return XDP_DROP; filter_res = filter_transport_hdr(transport_hdr, data_end, &info, &match_info); if (!filter_res) return XDP_DROP; if (match_info.is_tcp && !match_info.is_tcp_syn) return XDP_PASS; return XDP_DROP; } char LICENSE[] SEC("license") = "GPL";