summaryrefslogtreecommitdiffstats
path: root/ebpf/xdp_lb.c
diff options
context:
space:
mode:
Diffstat (limited to 'ebpf/xdp_lb.c')
-rw-r--r--ebpf/xdp_lb.c271
1 files changed, 271 insertions, 0 deletions
diff --git a/ebpf/xdp_lb.c b/ebpf/xdp_lb.c
new file mode 100644
index 0000000..a88724e
--- /dev/null
+++ b/ebpf/xdp_lb.c
@@ -0,0 +1,271 @@
+/* Copyright (C) 2019 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * 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
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#define KBUILD_MODNAME "foo"
+#include <stddef.h>
+#include <linux/bpf.h>
+
+#include <linux/in.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/if_vlan.h>
+/* Workaround to avoid the need of 32bit headers */
+#define _LINUX_IF_H
+#define IFNAMSIZ 16
+#include <linux/if_tunnel.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include "bpf_helpers.h"
+
+#include "hash_func01.h"
+
+#define LINUX_VERSION_CODE 263682
+
+/* Hashing initval */
+#define INITVAL 15485863
+
+/* Increase CPUMAP_MAX_CPUS if ever you have more than 128 CPUs */
+#define CPUMAP_MAX_CPUS 128
+
+struct vlan_hdr {
+ __u16 h_vlan_TCI;
+ __u16 h_vlan_encapsulated_proto;
+};
+
+/* Special map type that can XDP_REDIRECT frames to another CPU */
+struct bpf_map_def SEC("maps") cpu_map = {
+ .type = BPF_MAP_TYPE_CPUMAP,
+ .key_size = sizeof(__u32),
+ .value_size = sizeof(__u32),
+ .max_entries = CPUMAP_MAX_CPUS,
+};
+
+struct bpf_map_def SEC("maps") cpus_available = {
+ .type = BPF_MAP_TYPE_ARRAY,
+ .key_size = sizeof(__u32),
+ .value_size = sizeof(__u32),
+ .max_entries = CPUMAP_MAX_CPUS,
+};
+
+struct bpf_map_def SEC("maps") cpus_count = {
+ .type = BPF_MAP_TYPE_ARRAY,
+ .key_size = sizeof(__u32),
+ .value_size = sizeof(__u32),
+ .max_entries = 1,
+};
+
+static int __always_inline hash_ipv4(void *data, void *data_end)
+{
+ struct iphdr *iph = data;
+ if ((void *)(iph + 1) > data_end)
+ return XDP_PASS;
+
+ __u32 key0 = 0;
+ __u32 cpu_dest;
+ __u32 *cpu_max = bpf_map_lookup_elem(&cpus_count, &key0);
+ __u32 *cpu_selected;
+ __u32 cpu_hash;
+
+ /* IP-pairs hit same CPU */
+ cpu_hash = iph->saddr + iph->daddr;
+ cpu_hash = SuperFastHash((char *)&cpu_hash, 4, INITVAL);
+
+ if (cpu_max && *cpu_max) {
+ cpu_dest = cpu_hash % *cpu_max;
+ cpu_selected = bpf_map_lookup_elem(&cpus_available, &cpu_dest);
+ if (!cpu_selected)
+ return XDP_ABORTED;
+ cpu_dest = *cpu_selected;
+ return bpf_redirect_map(&cpu_map, cpu_dest, 0);
+ } else {
+ return XDP_PASS;
+ }
+}
+
+static int __always_inline hash_ipv6(void *data, void *data_end)
+{
+ struct ipv6hdr *ip6h = data;
+ if ((void *)(ip6h + 1) > data_end)
+ return XDP_PASS;
+
+ __u32 key0 = 0;
+ __u32 cpu_dest;
+ __u32 *cpu_max = bpf_map_lookup_elem(&cpus_count, &key0);
+ __u32 *cpu_selected;
+ __u32 cpu_hash;
+
+ /* IP-pairs hit same CPU */
+ cpu_hash = ip6h->saddr.s6_addr32[0] + ip6h->daddr.s6_addr32[0];
+ cpu_hash += ip6h->saddr.s6_addr32[1] + ip6h->daddr.s6_addr32[1];
+ cpu_hash += ip6h->saddr.s6_addr32[2] + ip6h->daddr.s6_addr32[2];
+ cpu_hash += ip6h->saddr.s6_addr32[3] + ip6h->daddr.s6_addr32[3];
+ cpu_hash = SuperFastHash((char *)&cpu_hash, 4, INITVAL);
+
+ if (cpu_max && *cpu_max) {
+ cpu_dest = cpu_hash % *cpu_max;
+ cpu_selected = bpf_map_lookup_elem(&cpus_available, &cpu_dest);
+ if (!cpu_selected)
+ return XDP_ABORTED;
+ cpu_dest = *cpu_selected;
+ return bpf_redirect_map(&cpu_map, cpu_dest, 0);
+ } else {
+ return XDP_PASS;
+ }
+
+ return XDP_PASS;
+}
+
+static int __always_inline filter_gre(struct xdp_md *ctx, void *data, __u64 nh_off, void *data_end)
+{
+ struct iphdr *iph = data + nh_off;
+ __u16 proto;
+ struct gre_hdr {
+ __be16 flags;
+ __be16 proto;
+ };
+
+ nh_off += sizeof(struct iphdr);
+ struct gre_hdr *grhdr = (struct gre_hdr *)(iph + 1);
+
+ if ((void *)(grhdr + 1) > data_end)
+ return XDP_PASS;
+
+ if (grhdr->flags & (GRE_VERSION|GRE_ROUTING))
+ return XDP_PASS;
+
+ nh_off += 4;
+ proto = grhdr->proto;
+ if (grhdr->flags & GRE_CSUM)
+ nh_off += 4;
+ if (grhdr->flags & GRE_KEY)
+ nh_off += 4;
+ if (grhdr->flags & GRE_SEQ)
+ nh_off += 4;
+
+ /* Update offset to skip ERSPAN header if we have one */
+ if (proto == __constant_htons(ETH_P_ERSPAN)) {
+ nh_off += 8;
+ }
+
+ if (data + nh_off > data_end)
+ return XDP_PASS;
+ if (bpf_xdp_adjust_head(ctx, 0 + nh_off))
+ return XDP_PASS;
+
+ data = (void *)(long)ctx->data;
+ data_end = (void *)(long)ctx->data_end;
+
+ /* we have now data starting at Ethernet header */
+ struct ethhdr *eth = data;
+ proto = eth->h_proto;
+ /* we want to hash on IP so we need to get to ip hdr */
+ nh_off = sizeof(*eth);
+
+ if (data + nh_off > data_end)
+ return XDP_PASS;
+
+ /* we need to increase offset and update protocol
+ * in the case we have VLANs */
+ if (proto == __constant_htons(ETH_P_8021Q)) {
+ struct vlan_hdr *vhdr = (struct vlan_hdr *)(data + nh_off);
+ if ((void *)(vhdr + 1) > data_end)
+ return XDP_PASS;
+ proto = vhdr->h_vlan_encapsulated_proto;
+ nh_off += sizeof(struct vlan_hdr);
+ }
+
+ if (data + nh_off > data_end)
+ return XDP_PASS;
+ /* proto should now be IP style */
+ if (proto == __constant_htons(ETH_P_IP)) {
+ return hash_ipv4(data + nh_off, data_end);
+ } else if (proto == __constant_htons(ETH_P_IPV6)) {
+ return hash_ipv6(data + nh_off, data_end);
+ } else
+ return XDP_PASS;
+}
+
+static int __always_inline filter_ipv4(struct xdp_md *ctx, void *data, __u64 nh_off, void *data_end)
+{
+ struct iphdr *iph = data + nh_off;
+ if ((void *)(iph + 1) > data_end)
+ return XDP_PASS;
+
+ if (iph->protocol == IPPROTO_GRE) {
+ return filter_gre(ctx, data, nh_off, data_end);
+ }
+ return hash_ipv4(data + nh_off, data_end);
+}
+
+static int __always_inline filter_ipv6(struct xdp_md *ctx, void *data, __u64 nh_off, void *data_end)
+{
+ struct ipv6hdr *ip6h = data + nh_off;
+ return hash_ipv6((void *)ip6h, data_end);
+}
+
+int SEC("xdp") xdp_loadfilter(struct xdp_md *ctx)
+{
+ void *data_end = (void *)(long)ctx->data_end;
+ void *data = (void *)(long)ctx->data;
+ struct ethhdr *eth = data;
+ __u16 h_proto;
+ __u64 nh_off;
+
+ nh_off = sizeof(*eth);
+ if (data + nh_off > data_end)
+ return XDP_PASS;
+
+ h_proto = eth->h_proto;
+
+#if 0
+ if (h_proto != __constant_htons(ETH_P_IP)) {
+ char fmt[] = "Current proto: %u\n";
+ bpf_trace_printk(fmt, sizeof(fmt), h_proto);
+ }
+#endif
+ if (h_proto == __constant_htons(ETH_P_8021Q) || h_proto == __constant_htons(ETH_P_8021AD)) {
+ struct vlan_hdr *vhdr;
+
+ vhdr = data + nh_off;
+ nh_off += sizeof(struct vlan_hdr);
+ if (data + nh_off > data_end)
+ return XDP_PASS;
+ h_proto = vhdr->h_vlan_encapsulated_proto;
+ }
+ if (h_proto == __constant_htons(ETH_P_8021Q) || h_proto == __constant_htons(ETH_P_8021AD)) {
+ struct vlan_hdr *vhdr;
+
+ vhdr = data + nh_off;
+ nh_off += sizeof(struct vlan_hdr);
+ if (data + nh_off > data_end)
+ return XDP_PASS;
+ h_proto = vhdr->h_vlan_encapsulated_proto;
+ }
+
+ if (h_proto == __constant_htons(ETH_P_IP))
+ return filter_ipv4(ctx, data, nh_off, data_end);
+ else if (h_proto == __constant_htons(ETH_P_IPV6))
+ return filter_ipv6(ctx, data, nh_off, data_end);
+
+ return XDP_PASS;
+}
+
+char __license[] SEC("license") = "GPL";
+
+__u32 __version SEC("version") = LINUX_VERSION_CODE;