| // 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"; |