mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
7.7 KiB
C
273 lines
7.7 KiB
C
/* 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/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 {
|
|
__uint(type, BPF_MAP_TYPE_CPUMAP);
|
|
__type(key, __u32);
|
|
__type(value, __u32);
|
|
__uint(max_entries, CPUMAP_MAX_CPUS);
|
|
} cpu_map SEC(".maps");
|
|
|
|
struct {
|
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
|
__type(key, __u32);
|
|
__type(value, __u32);
|
|
__uint(max_entries, CPUMAP_MAX_CPUS);
|
|
} cpus_available SEC(".maps");
|
|
|
|
struct {
|
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
|
__type(key, __u32);
|
|
__type(value, __u32);
|
|
__uint(max_entries, 1);
|
|
} cpus_count SEC(".maps");
|
|
|
|
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;
|