Add support for bpf_loop().

This commit is contained in:
Christian Deacon
2025-03-27 20:36:20 -04:00
parent 6001428130
commit 148385b3e7
4 changed files with 399 additions and 307 deletions

View File

@@ -13,7 +13,7 @@
// The maximum amount of filters allowed.
// Decrease this value if you receive errors related to the BPF program being too large.
#define MAX_FILTERS 60
#define MAX_FILTERS 1000
// Feel free to comment this out if you don't want the `blocked` entry on the stats map to be incremented every single time a packet is dropped from the source IP being on the blocked map.
// Commenting this line out should increase performance when blocking malicious traffic.
@@ -54,3 +54,7 @@
// Enables IPv6.
// If you're not using IPv6, this will speed up performance of the XDP program.
#define ENABLE_IPV6
// If enabled, uses a newer bpf_loop() function when choosing a source port for a new connection.
// This allows for a much higher source port range. However, it requires a more recent kernel.
#define USE_NEW_LOOP

View File

@@ -11,7 +11,7 @@
#include <common/all.h>
#include <xdp/utils/rl.h>
#include <xdp/utils/logging.h>
#include <xdp/utils/rule.h>
#include <xdp/utils/stats.h>
#include <xdp/utils/helpers.h>
@@ -332,316 +332,40 @@ int xdp_prog_main(struct xdp_md *ctx)
#endif
#endif
int action = 0;
u64 block_time = 1;
// Create rule context.
rule_ctx_t rule = {0};
rule.flow_pps = flow_pps;
rule.flow_bps = flow_bps;
rule.ip_pps = ip_pps;
rule.ip_bps = ip_bps;
rule.now = now;
rule.pkt_len = pkt_len;
rule.src_port = src_port;
rule.dst_port = dst_port;
rule.protocol = protocol;
rule.iph = iph;
rule.iph6 = iph6;
rule.tcph = tcph;
rule.udph = udph;
rule.icmph = icmph;
rule.icmph6 = icmp6h;
#ifdef USE_NEW_LOOP
bpf_loop(MAX_FILTERS, process_rule, &rule, 0);
#else
#pragma unroll 30
for (int i = 0; i < MAX_FILTERS; i++)
{
u32 key = i;
filter_t *filter = bpf_map_lookup_elem(&map_filters, &key);
if (!filter || !filter->set)
if (process_rule(i, &rule))
{
break;
}
#ifdef ENABLE_RL_IP
// Check source IP rate limits.
if (filter->do_ip_pps && ip_pps < filter->ip_pps)
{
continue;
}
if (filter->do_ip_bps && ip_bps < filter->ip_bps)
{
continue;
}
#endif
#ifdef ENABLE_RL_FLOW
// Check source flow rate limits.
if (filter->do_flow_pps && flow_pps < filter->flow_pps)
if (rule.matched)
{
continue;
}
if (filter->do_flow_bps && flow_bps < filter->flow_bps)
{
continue;
}
#endif
// Match IP settings.
if (iph)
{
// Source address.
if (filter->ip.src_ip)
{
if (filter->ip.src_cidr == 32 && iph->saddr != filter->ip.src_ip)
{
continue;
}
if (!is_ip_in_range(iph->saddr, filter->ip.src_ip, filter->ip.src_cidr))
{
continue;
}
}
// Destination address.
if (filter->ip.dst_ip)
{
if (filter->ip.dst_cidr == 32 && iph->daddr != filter->ip.dst_ip)
{
continue;
}
if (!is_ip_in_range(iph->daddr, filter->ip.dst_ip, filter->ip.dst_cidr))
{
continue;
}
}
#if defined(ENABLE_IPV6) && defined(ALLOW_SINGLE_IP_V4_V6)
if ((filter->ip.src_ip6[0] != 0 || filter->ip.src_ip6[1] != 0 || filter->ip.src_ip6[2] != 0 || filter->ip.src_ip6[3] != 0) || (filter->ip.dst_ip6[0] != 0 || filter->ip.dst_ip6[1] != 0 || filter->ip.dst_ip6[2] != 0 || filter->ip.dst_ip6[3] != 0))
{
continue;
}
#endif
// TOS.
if (filter->ip.do_tos && filter->ip.tos != iph->tos)
{
continue;
}
// Max TTL.
if (filter->ip.do_max_ttl && filter->ip.max_ttl < iph->ttl)
{
continue;
}
// Min TTL.
if (filter->ip.do_min_ttl && filter->ip.min_ttl > iph->ttl)
{
continue;
}
// Max packet length.
if (filter->ip.do_max_len && filter->ip.max_len < pkt_len)
{
continue;
}
// Min packet length.
if (filter->ip.do_min_len && filter->ip.min_len > pkt_len)
{
continue;
}
}
#ifdef ENABLE_IPV6
else if (iph6)
{
// Source address.
if (filter->ip.src_ip6[0] != 0 && (iph6->saddr.in6_u.u6_addr32[0] != filter->ip.src_ip6[0] || iph6->saddr.in6_u.u6_addr32[1] != filter->ip.src_ip6[1] || iph6->saddr.in6_u.u6_addr32[2] != filter->ip.src_ip6[2] || iph6->saddr.in6_u.u6_addr32[3] != filter->ip.src_ip6[3]))
{
continue;
}
// Destination address.
if (filter->ip.dst_ip6[0] != 0 && (iph6->daddr.in6_u.u6_addr32[0] != filter->ip.dst_ip6[0] || iph6->daddr.in6_u.u6_addr32[1] != filter->ip.dst_ip6[1] || iph6->daddr.in6_u.u6_addr32[2] != filter->ip.dst_ip6[2] || iph6->daddr.in6_u.u6_addr32[3] != filter->ip.dst_ip6[3]))
{
continue;
}
#ifdef ALLOW_SINGLE_IP_V4_V6
if (filter->ip.src_ip != 0 || filter->ip.dst_ip != 0)
{
continue;
}
#endif
// Max TTL length.
if (filter->ip.do_max_ttl && filter->ip.max_ttl < iph6->hop_limit)
{
continue;
}
// Min TTL length.
if (filter->ip.do_min_ttl && filter->ip.min_ttl > iph6->hop_limit)
{
continue;
}
// Max packet length.
if (filter->ip.do_max_len && filter->ip.max_len < pkt_len)
{
continue;
}
// Min packet length.
if (filter->ip.do_min_len && filter->ip.min_len > pkt_len)
{
continue;
}
}
#endif
// Check TCP matches.
if (filter->tcp.enabled)
{
if (!tcph)
{
continue;
}
// Source port checks.
if (filter->tcp.do_sport_min && ntohs(tcph->source) < filter->tcp.sport_min)
{
continue;
}
if (filter->tcp.do_sport_max && ntohs(tcph->source) > filter->tcp.sport_max)
{
continue;
}
// Destination port checks.
if (filter->tcp.do_dport_min && ntohs(tcph->dest) < filter->tcp.dport_min)
{
continue;
}
if (filter->tcp.do_dport_max && ntohs(tcph->dest) > filter->tcp.dport_max)
{
continue;
}
// URG flag.
if (filter->tcp.do_urg && filter->tcp.urg != tcph->urg)
{
continue;
}
// ACK flag.
if (filter->tcp.do_ack && filter->tcp.ack != tcph->ack)
{
continue;
}
// RST flag.
if (filter->tcp.do_rst && filter->tcp.rst != tcph->rst)
{
continue;
}
// PSH flag.
if (filter->tcp.do_psh && filter->tcp.psh != tcph->psh)
{
continue;
}
// SYN flag.
if (filter->tcp.do_syn && filter->tcp.syn != tcph->syn)
{
continue;
}
// FIN flag.
if (filter->tcp.do_fin && filter->tcp.fin != tcph->fin)
{
continue;
}
// ECE flag.
if (filter->tcp.do_ece && filter->tcp.ece != tcph->ece)
{
continue;
}
// CWR flag.
if (filter->tcp.do_cwr && filter->tcp.cwr != tcph->cwr)
{
continue;
}
}
// Check UDP matches.
else if (filter->udp.enabled)
{
if (!udph)
{
continue;
}
// Source port checks.
if (filter->udp.do_sport_min && ntohs(udph->source) < filter->udp.sport_min)
{
continue;
}
if (filter->udp.do_sport_max && ntohs(udph->source) > filter->udp.sport_max)
{
continue;
}
// Destination port checks.
if (filter->udp.do_dport_min && ntohs(udph->dest) < filter->udp.dport_min)
{
continue;
}
if (filter->udp.do_dport_max && ntohs(udph->dest) > filter->udp.dport_max)
{
continue;
}
}
else if (filter->icmp.enabled)
{
if (icmph)
{
// Code.
if (filter->icmp.do_code && filter->icmp.code != icmph->code)
{
continue;
}
// Type.
if (filter->icmp.do_type && filter->icmp.type != icmph->type)
{
continue;
}
}
#ifdef ENABLE_IPV6
else if (icmp6h)
{
// Code.
if (filter->icmp.do_code && filter->icmp.code != icmp6h->icmp6_code)
{
continue;
}
// Type.
if (filter->icmp.do_type && filter->icmp.type != icmp6h->icmp6_type)
{
continue;
}
}
#endif
}
#ifdef ENABLE_FILTER_LOGGING
if (filter->log > 0)
{
log_filter_msg(iph, iph6, src_port, dst_port, protocol, now, ip_pps, ip_bps, flow_pps, flow_bps, pkt_len, i);
}
#endif
// Matched.
action = filter->action;
block_time = filter->block_time;
goto matched;
}
#endif
@@ -652,12 +376,12 @@ int xdp_prog_main(struct xdp_md *ctx)
#ifdef ENABLE_FILTERS
matched:
if (action == 0)
if (rule.action == 0)
{
// Before dropping, update the block map.
if (block_time > 0)
if (rule.block_time > 0)
{
u64 new_time = now + (block_time * NANO_TO_SEC);
u64 new_time = now + (rule.block_time * NANO_TO_SEC);
if (iph)
{

313
src/xdp/utils/rule.c Normal file
View File

@@ -0,0 +1,313 @@
#include <xdp/utils/rule.h>
#ifdef ENABLE_FILTERS
/**
* Processes a filter rule.
*
* @param idx The rule index.
* @param data A pointer to the rule context.
*
* @return 1 to break the loop or 0 to continue.
*/
static __always_inline long process_rule(u32 idx, void* data)
{
rule_ctx_t* ctx = data;
filter_t *filter = bpf_map_lookup_elem(&map_filters, &idx);
if (!filter || !filter->set)
{
return 1;
}
#ifdef ENABLE_RL_IP
// Check source IP rate limits.
if (filter->do_ip_pps && ctx->ip_pps < filter->ip_pps)
{
return 0;
}
if (filter->do_ip_bps && ctx->ip_bps < filter->ip_bps)
{
return 0;
}
#endif
#ifdef ENABLE_RL_FLOW
// Check source flow rate limits.
if (filter->do_flow_pps && ctx->flow_pps < filter->flow_pps)
{
return 0;
}
if (filter->do_flow_bps && ctx->flow_bps < filter->flow_bps)
{
return 0;
}
#endif
// Max packet length.
if (filter->ip.do_max_len && filter->ip.max_len < ctx->pkt_len)
{
return 0;
}
// Min packet length.
if (filter->ip.do_min_len && filter->ip.min_len > ctx->pkt_len)
{
return 0;
}
// Match IP settings.
if (ctx->iph)
{
// Source address.
if (filter->ip.src_ip)
{
if (filter->ip.src_cidr == 32 && ctx->iph->saddr != filter->ip.src_ip)
{
return 0;
}
if (!is_ip_in_range(ctx->iph->saddr, filter->ip.src_ip, filter->ip.src_cidr))
{
return 0;
}
}
// Destination address.
if (filter->ip.dst_ip)
{
if (filter->ip.dst_cidr == 32 && ctx->iph->daddr != filter->ip.dst_ip)
{
return 0;
}
if (!is_ip_in_range(ctx->iph->daddr, filter->ip.dst_ip, filter->ip.dst_cidr))
{
return 0;
}
}
#if defined(ENABLE_IPV6) && defined(ALLOW_SINGLE_IP_V4_V6)
if ((filter->ip.src_ip6[0] != 0 || filter->ip.src_ip6[1] != 0 || filter->ip.src_ip6[2] != 0 || filter->ip.src_ip6[3] != 0) || (filter->ip.dst_ip6[0] != 0 || filter->ip.dst_ip6[1] != 0 || filter->ip.dst_ip6[2] != 0 || filter->ip.dst_ip6[3] != 0))
{
return 0;
}
#endif
// TOS.
if (filter->ip.do_tos && filter->ip.tos != ctx->iph->tos)
{
return 0;
}
// Max TTL.
if (filter->ip.do_max_ttl && filter->ip.max_ttl < ctx->iph->ttl)
{
return 0;
}
// Min TTL.
if (filter->ip.do_min_ttl && filter->ip.min_ttl > ctx->iph->ttl)
{
return 0;
}
}
#ifdef ENABLE_IPV6
else if (ctx->iph6)
{
// Source address.
if (filter->ip.src_ip6[0] != 0 && (ctx->iph6->saddr.in6_u.u6_addr32[0] != filter->ip.src_ip6[0] || ctx->iph6->saddr.in6_u.u6_addr32[1] != filter->ip.src_ip6[1] || ctx->iph6->saddr.in6_u.u6_addr32[2] != filter->ip.src_ip6[2] || ctx->iph6->saddr.in6_u.u6_addr32[3] != filter->ip.src_ip6[3]))
{
return 0;
}
// Destination address.
if (filter->ip.dst_ip6[0] != 0 && (ctx->iph6->daddr.in6_u.u6_addr32[0] != filter->ip.dst_ip6[0] || ctx->iph6->daddr.in6_u.u6_addr32[1] != filter->ip.dst_ip6[1] || ctx->iph6->daddr.in6_u.u6_addr32[2] != filter->ip.dst_ip6[2] || ctx->iph6->daddr.in6_u.u6_addr32[3] != filter->ip.dst_ip6[3]))
{
return 0;
}
#ifdef ALLOW_SINGLE_IP_V4_V6
if (filter->ip.src_ip != 0 || filter->ip.dst_ip != 0)
{
return 0;
}
#endif
// Max TTL length.
if (filter->ip.do_max_ttl && filter->ip.max_ttl < ctx->iph6->hop_limit)
{
return 0;
}
// Min TTL length.
if (filter->ip.do_min_ttl && filter->ip.min_ttl > ctx->iph6->hop_limit)
{
return 0;
}
}
#endif
// Check TCP matches.
if (filter->tcp.enabled)
{
if (!ctx->tcph)
{
return 0;
}
// Source port checks.
if (filter->tcp.do_sport_min && ntohs(ctx->tcph->source) < filter->tcp.sport_min)
{
return 0;
}
if (filter->tcp.do_sport_max && ntohs(ctx->tcph->source) > filter->tcp.sport_max)
{
return 0;
}
// Destination port checks.
if (filter->tcp.do_dport_min && ntohs(ctx->tcph->dest) < filter->tcp.dport_min)
{
return 0;
}
if (filter->tcp.do_dport_max && ntohs(ctx->tcph->dest) > filter->tcp.dport_max)
{
return 0;
}
// URG flag.
if (filter->tcp.do_urg && filter->tcp.urg != ctx->tcph->urg)
{
return 0;
}
// ACK flag.
if (filter->tcp.do_ack && filter->tcp.ack != ctx->tcph->ack)
{
return 0;
}
// RST flag.
if (filter->tcp.do_rst && filter->tcp.rst != ctx->tcph->rst)
{
return 0;
}
// PSH flag.
if (filter->tcp.do_psh && filter->tcp.psh != ctx->tcph->psh)
{
return 0;
}
// SYN flag.
if (filter->tcp.do_syn && filter->tcp.syn != ctx->tcph->syn)
{
return 0;
}
// FIN flag.
if (filter->tcp.do_fin && filter->tcp.fin != ctx->tcph->fin)
{
return 0;
}
// ECE flag.
if (filter->tcp.do_ece && filter->tcp.ece != ctx->tcph->ece)
{
return 0;
}
// CWR flag.
if (filter->tcp.do_cwr && filter->tcp.cwr != ctx->tcph->cwr)
{
return 0;
}
}
// Check UDP matches.
else if (filter->udp.enabled)
{
if (!ctx->udph)
{
return 0;
}
// Source port checks.
if (filter->udp.do_sport_min && ntohs(ctx->udph->source) < filter->udp.sport_min)
{
return 0;
}
if (filter->udp.do_sport_max && ntohs(ctx->udph->source) > filter->udp.sport_max)
{
return 0;
}
// Destination port checks.
if (filter->udp.do_dport_min && ntohs(ctx->udph->dest) < filter->udp.dport_min)
{
return 0;
}
if (filter->udp.do_dport_max && ntohs(ctx->udph->dest) > filter->udp.dport_max)
{
return 0;
}
}
else if (filter->icmp.enabled)
{
if (ctx->icmph)
{
// Code.
if (filter->icmp.do_code && filter->icmp.code != ctx->icmph->code)
{
return 0;
}
// Type.
if (filter->icmp.do_type && filter->icmp.type != ctx->icmph->type)
{
return 0;
}
}
#ifdef ENABLE_IPV6
else if (ctx->icmph6)
{
// Code.
if (filter->icmp.do_code && filter->icmp.code != ctx->icmph6->icmp6_code)
{
return 0;
}
// Type.
if (filter->icmp.do_type && filter->icmp.type != ctx->icmph6->icmp6_type)
{
return 0;
}
}
#endif
else
{
return 0;
}
}
#ifdef ENABLE_FILTER_LOGGING
if (filter->log > 0)
{
log_filter_msg(ctx->iph, ctx->iph6, ctx->src_port, ctx->dst_port, ctx->protocol, ctx->now, ctx->ip_pps, ctx->ip_bps, ctx->flow_pps, ctx->flow_bps, ctx->pkt_len, idx);
}
#endif
// Matched.
ctx->matched = 1;
ctx->action = filter->action;
ctx->block_time = filter->block_time;
return 1;
}
#endif

51
src/xdp/utils/rule.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include <common/all.h>
#include <xdp/utils/logging.h>
#include <xdp/utils/maps.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/icmpv6.h>
struct rule_ctx
{
int matched;
int action;
u64 block_time;
u64 ip_pps;
u64 ip_bps;
u64 flow_pps;
u64 flow_bps;
u64 now;
int pkt_len;
u16 src_port;
u16 dst_port;
u8 protocol;
struct iphdr* iph;
struct ipv6hdr* iph6;
struct tcphdr* tcph;
struct udphdr* udph;
struct icmphdr* icmph;
struct icmp6hdr* icmph6;
} typedef rule_ctx_t;
#ifdef ENABLE_FILTERS
static __always_inline long process_rule(u32 idx, void* data);
#endif
// The source file is included directly below instead of compiled and linked as an object because when linking, there is no guarantee the compiler will inline the function (which is crucial for performance).
// I'd prefer not to include the function logic inside of the header file.
// More Info: https://stackoverflow.com/questions/24289599/always-inline-does-not-work-when-function-is-implemented-in-different-file
#include "rule.c"