| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2023 Isovalent */ |
| #include <stdbool.h> |
| #include <linux/bpf.h> |
| #include <linux/if_ether.h> |
| #include <linux/in.h> |
| #include <linux/ip.h> |
| #include <linux/ipv6.h> |
| #include <linux/tcp.h> |
| #include <linux/udp.h> |
| #include <bpf/bpf_endian.h> |
| #include <bpf/bpf_helpers.h> |
| #include <linux/pkt_cls.h> |
| |
| char LICENSE[] SEC("license") = "GPL"; |
| |
| __u64 sk_cookie_seen; |
| __u64 reuseport_executed; |
| union { |
| struct tcphdr tcp; |
| struct udphdr udp; |
| } headers; |
| |
| const volatile __u16 dest_port; |
| |
| struct { |
| __uint(type, BPF_MAP_TYPE_SOCKMAP); |
| __uint(max_entries, 1); |
| __type(key, __u32); |
| __type(value, __u64); |
| } sk_map SEC(".maps"); |
| |
| SEC("sk_reuseport") |
| int reuse_accept(struct sk_reuseport_md *ctx) |
| { |
| reuseport_executed++; |
| |
| if (ctx->ip_protocol == IPPROTO_TCP) { |
| if (ctx->data + sizeof(headers.tcp) > ctx->data_end) |
| return SK_DROP; |
| |
| if (__builtin_memcmp(&headers.tcp, ctx->data, sizeof(headers.tcp)) != 0) |
| return SK_DROP; |
| } else if (ctx->ip_protocol == IPPROTO_UDP) { |
| if (ctx->data + sizeof(headers.udp) > ctx->data_end) |
| return SK_DROP; |
| |
| if (__builtin_memcmp(&headers.udp, ctx->data, sizeof(headers.udp)) != 0) |
| return SK_DROP; |
| } else { |
| return SK_DROP; |
| } |
| |
| sk_cookie_seen = bpf_get_socket_cookie(ctx->sk); |
| return SK_PASS; |
| } |
| |
| SEC("sk_reuseport") |
| int reuse_drop(struct sk_reuseport_md *ctx) |
| { |
| reuseport_executed++; |
| sk_cookie_seen = 0; |
| return SK_DROP; |
| } |
| |
| static int |
| assign_sk(struct __sk_buff *skb) |
| { |
| int zero = 0, ret = 0; |
| struct bpf_sock *sk; |
| |
| sk = bpf_map_lookup_elem(&sk_map, &zero); |
| if (!sk) |
| return TC_ACT_SHOT; |
| ret = bpf_sk_assign(skb, sk, 0); |
| bpf_sk_release(sk); |
| return ret ? TC_ACT_SHOT : TC_ACT_OK; |
| } |
| |
| static bool |
| maybe_assign_tcp(struct __sk_buff *skb, struct tcphdr *th) |
| { |
| if (th + 1 > (void *)(long)(skb->data_end)) |
| return TC_ACT_SHOT; |
| |
| if (!th->syn || th->ack || th->dest != bpf_htons(dest_port)) |
| return TC_ACT_OK; |
| |
| __builtin_memcpy(&headers.tcp, th, sizeof(headers.tcp)); |
| return assign_sk(skb); |
| } |
| |
| static bool |
| maybe_assign_udp(struct __sk_buff *skb, struct udphdr *uh) |
| { |
| if (uh + 1 > (void *)(long)(skb->data_end)) |
| return TC_ACT_SHOT; |
| |
| if (uh->dest != bpf_htons(dest_port)) |
| return TC_ACT_OK; |
| |
| __builtin_memcpy(&headers.udp, uh, sizeof(headers.udp)); |
| return assign_sk(skb); |
| } |
| |
| SEC("tc") |
| int tc_main(struct __sk_buff *skb) |
| { |
| void *data_end = (void *)(long)skb->data_end; |
| void *data = (void *)(long)skb->data; |
| struct ethhdr *eth; |
| |
| eth = (struct ethhdr *)(data); |
| if (eth + 1 > data_end) |
| return TC_ACT_SHOT; |
| |
| if (eth->h_proto == bpf_htons(ETH_P_IP)) { |
| struct iphdr *iph = (struct iphdr *)(data + sizeof(*eth)); |
| |
| if (iph + 1 > data_end) |
| return TC_ACT_SHOT; |
| |
| if (iph->protocol == IPPROTO_TCP) |
| return maybe_assign_tcp(skb, (struct tcphdr *)(iph + 1)); |
| else if (iph->protocol == IPPROTO_UDP) |
| return maybe_assign_udp(skb, (struct udphdr *)(iph + 1)); |
| else |
| return TC_ACT_SHOT; |
| } else { |
| struct ipv6hdr *ip6h = (struct ipv6hdr *)(data + sizeof(*eth)); |
| |
| if (ip6h + 1 > data_end) |
| return TC_ACT_SHOT; |
| |
| if (ip6h->nexthdr == IPPROTO_TCP) |
| return maybe_assign_tcp(skb, (struct tcphdr *)(ip6h + 1)); |
| else if (ip6h->nexthdr == IPPROTO_UDP) |
| return maybe_assign_udp(skb, (struct udphdr *)(ip6h + 1)); |
| else |
| return TC_ACT_SHOT; |
| } |
| } |