From 148385b3e7bdf2c8d25923e2a33d6546e6a99d44 Mon Sep 17 00:00:00 2001 From: Christian Deacon Date: Thu, 27 Mar 2025 20:36:20 -0400 Subject: [PATCH] Add support for bpf_loop(). --- src/common/config.h | 8 +- src/xdp/prog.c | 334 ++++--------------------------------------- src/xdp/utils/rule.c | 313 ++++++++++++++++++++++++++++++++++++++++ src/xdp/utils/rule.h | 51 +++++++ 4 files changed, 399 insertions(+), 307 deletions(-) create mode 100644 src/xdp/utils/rule.c create mode 100644 src/xdp/utils/rule.h diff --git a/src/common/config.h b/src/common/config.h index e9217b2..28b07ed 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -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. @@ -53,4 +53,8 @@ // Enables IPv6. // If you're not using IPv6, this will speed up performance of the XDP program. -#define ENABLE_IPV6 \ No newline at end of file +#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 \ No newline at end of file diff --git a/src/xdp/prog.c b/src/xdp/prog.c index 6b6c854..d2aaeff 100644 --- a/src/xdp/prog.c +++ b/src/xdp/prog.c @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include @@ -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) - { - 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; - + if (rule.matched) + { 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) { diff --git a/src/xdp/utils/rule.c b/src/xdp/utils/rule.c new file mode 100644 index 0000000..919728c --- /dev/null +++ b/src/xdp/utils/rule.c @@ -0,0 +1,313 @@ +#include + +#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 \ No newline at end of file diff --git a/src/xdp/utils/rule.h b/src/xdp/utils/rule.h new file mode 100644 index 0000000..5d39a52 --- /dev/null +++ b/src/xdp/utils/rule.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +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" \ No newline at end of file