Implement both IP and flow-based rate limiting.

This commit is contained in:
Christian Deacon
2025-03-23 20:35:08 -04:00
parent 2727740a64
commit 5aa3270f82
15 changed files with 420 additions and 149 deletions

View File

@@ -285,18 +285,41 @@ int xdp_prog_main(struct xdp_md *ctx)
}
}
#ifdef ENABLE_FILTERS
// Update client stats (PPS/BPS).
u64 pps = 0;
u64 bps = 0;
if (iph6)
#ifdef ENABLE_RL_IP
u64 ip_pps = 0;
u64 ip_bps = 0;
#endif
#ifdef ENABLE_RL_FLOW
u64 flow_pps = 0;
u64 flow_bps = 0;
#endif
#if defined(ENABLE_RL_IP) || defined(ENABLE_RL_FLOW)
if (iph)
{
update_ip6_stats(&pps, &bps, &src_ip6, src_port, protocol, pkt_len, now);
#ifdef ENABLE_RL_IP
update_ip_stats(&ip_pps, &ip_bps, iph->saddr, pkt_len, now);
#endif
#ifdef ENABLE_RL_FLOW
update_flow_stats(&flow_pps, &flow_bps, iph->saddr, src_port, protocol, pkt_len, now);
#endif
}
else if (iph)
else if (iph6)
{
update_ip_stats(&pps, &bps, iph->saddr, src_port, protocol, pkt_len, now);
#ifdef ENABLE_RL_IP
update_ip6_stats(&ip_pps, &ip_bps, &src_ip6, pkt_len, now);
#endif
#ifdef ENABLE_RL_FLOW
update_flow6_stats(&flow_pps, &flow_bps, &src_ip6, src_port, protocol, pkt_len, now);
#endif
}
#endif
#endif
int action = 0;
u64 block_time = 1;
@@ -312,6 +335,32 @@ int xdp_prog_main(struct xdp_md *ctx)
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
// Do specific IPv6.
if (iph6)
{
@@ -426,18 +475,6 @@ int xdp_prog_main(struct xdp_md *ctx)
}
}
// PPS.
if (filter->do_pps && pps < filter->pps)
{
continue;
}
// BPS.
if (filter->do_bps && bps < filter->bps)
{
continue;
}
// Do TCP options.
if (filter->tcp.enabled)
{
@@ -584,7 +621,19 @@ int xdp_prog_main(struct xdp_md *ctx)
#ifdef ENABLE_FILTER_LOGGING
if (filter->log > 0)
{
log_filter_msg(iph, iph6, src_port, dst_port, protocol, now, pps, bps, pkt_len, i);
#if defined(ENABLE_RL_IP) || defined(ENABLE_RL_FLOW)
#if defined(ENABLE_RL_IP) && defined(ENABLE_RL_FLOW)
log_filter_msg(iph, iph6, src_port, dst_port, protocol, now, ip_pps, ip_bps, flow_pps, flow_bps, pkt_len, i);
#else
#ifdef ENABLE_RL_IP
log_filter_msg(iph, iph6, src_port, dst_port, protocol, now, ip_pps, ip_bps, 0, 0, pkt_len, i);
#else
log_filter_msg(iph, iph6, src_port, dst_port, protocol, now, 0, 0, flow_pps, flow_bps, pkt_len, i);
#endif
#endif
#else
log_filter_msg(iph, iph6, src_port, dst_port, protocol, now, 0, 0, 0, 0, pkt_len, i);
#endif
}
#endif

View File

@@ -14,14 +14,16 @@
* @param dst_port The destination port.
* @param protocol The protocol.
* @param now The timestamp.
* @param pps The current PPS rate.
* @param bps The current BPS rate.
* @param ip_pps The current IP PPS rate.
* @param ip_bps The current IP BPS rate.
* @param flow_pps The current flow PPS rate.
* @param flow_bps The current flow BPS rate.
* @param pkt_len The full packet length.
* @param filter_id The filter ID that matched.
*
* @return always 0
*/
static __always_inline int log_filter_msg(struct iphdr* iph, struct ipv6hdr* iph6, u16 src_port, u16 dst_port, u8 protocol, u64 now, u64 pps, u64 bps, int pkt_len, int filter_id)
static __always_inline int log_filter_msg(struct iphdr* iph, struct ipv6hdr* iph6, u16 src_port, u16 dst_port, u8 protocol, u64 now, u64 ip_pps, u64 ip_bps, u64 flow_pps, u64 flow_bps, int pkt_len, int filter_id)
{
filter_log_event_t* e = bpf_ringbuf_reserve(&map_filter_log, sizeof(*e), 0);
@@ -45,8 +47,11 @@ static __always_inline int log_filter_msg(struct iphdr* iph, struct ipv6hdr* iph
e->protocol = protocol;
e->pps = pps;
e->bps = bps;
e->ip_pps = ip_pps;
e->ip_bps = ip_bps;
e->flow_pps = flow_pps;
e->flow_bps = flow_bps;
e->length = pkt_len;

View File

@@ -6,7 +6,7 @@
#include <xdp/prog_dispatcher.h>
#if defined(ENABLE_FILTERS) && defined(ENABLE_FILTER_LOGGING)
static __always_inline int log_filter_msg(struct iphdr* iph, struct ipv6hdr* iph6, u16 src_port, u16 dst_port, u8 protocol, u64 now, u64 pps, u64 bps, int pkt_len, int filter_id);
static __always_inline int log_filter_msg(struct iphdr* iph, struct ipv6hdr* iph6, u16 src_port, u16 dst_port, u8 protocol, u64 now, u64 ip_pps, u64 ip_bps, u64 flow_pps, u64 flow_bps, int pkt_len, int filter_id);
#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).

View File

@@ -16,7 +16,7 @@ struct
struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, MAX_TRACK_IPS);
__uint(max_entries, MAX_BLOCK);
__type(key, u32);
__type(value, u64);
} map_block SEC(".maps");
@@ -24,7 +24,7 @@ struct
struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, MAX_TRACK_IPS);
__uint(max_entries, MAX_BLOCK);
__type(key, u128);
__type(value, u64);
} map_block6 SEC(".maps");
@@ -41,29 +41,41 @@ struct
#endif
#ifdef ENABLE_FILTERS
#ifdef ENABLE_RL_IP
struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, MAX_TRACK_IPS);
#ifdef USE_FLOW_RL
__type(key, flow_t);
#else
__uint(max_entries, MAX_RL_IP);
__type(key, u32);
#endif
__type(value, ip_stats_t);
__type(value, cl_stats_t);
} map_ip_stats SEC(".maps");
struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, MAX_TRACK_IPS);
#ifdef USE_FLOW_RL
__type(key, flow6_t);
#else
__uint(max_entries, MAX_RL_IP);
__type(key, u128);
#endif
__type(value, ip_stats_t);
__type(value, cl_stats_t);
} map_ip6_stats SEC(".maps");
#endif
#ifdef ENABLE_RL_FLOW
struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, MAX_RL_FLOW);
__type(key, flow_t);
__type(value, cl_stats_t);
} map_flow_stats SEC(".maps");
struct
{
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, MAX_RL_FLOW);
__type(key, flow6_t);
__type(value, cl_stats_t);
} map_flow6_stats SEC(".maps");
#endif
struct
{

View File

@@ -1,8 +1,116 @@
#include <xdp/utils/rl.h>
#ifdef ENABLE_FILTERS
#ifdef ENABLE_RL_IP
/**
* Updates IPv4 client stats.
* Updates source IPv4 address stats.
*
* @param pps A pointer to the PPS integer.
* @param bps A pointer to the BPS integer.
* @param ip The client's source IP.
* @param pkt_len The total packet length.
* @param now The current time since boot in nanoseconds.alignas
*
* @return always 0
*/
static __always_inline int update_ip_stats(u64 *pps, u64 *bps, u32 ip, u16 pkt_len, u64 now)
{
cl_stats_t* stats = bpf_map_lookup_elem(&map_ip_stats, &ip);
if (stats)
{
// Check for next update.
if (now > stats->next_update)
{
stats->pps = 1;
stats->bps = pkt_len;
stats->next_update = now + NANO_TO_SEC;
}
else
{
// Increment PPS and BPS using built-in functions.
__sync_fetch_and_add(&stats->pps, 1);
__sync_fetch_and_add(&stats->bps, pkt_len);
}
*pps = stats->pps;
*bps = stats->bps;
}
else
{
// Create new entry.
cl_stats_t new = {0};
new.pps = 1;
new.bps = pkt_len;
new.next_update = now + NANO_TO_SEC;
*pps = new.pps;
*bps = new.bps;
bpf_map_update_elem(&map_ip_stats, &ip, &new, BPF_ANY);
}
return 0;
}
/**
* Updates source IPv6 address stats.
*
* @param pps A pointer to the PPS integer.
* @param bps A pointer to the BPS integer.
* @param ip The client's source IP.
* @param pkt_len The total packet length.
* @param now The current time since boot in nanoseconds.alignas
*
* @return always 0
*/
static __always_inline int update_ip6_stats(u64 *pps, u64 *bps, u128 *ip, u16 pkt_len, u64 now)
{
cl_stats_t* stats = bpf_map_lookup_elem(&map_ip6_stats, ip);
if (stats)
{
// Check for next update.
if (now > stats->next_update)
{
stats->pps = 1;
stats->bps = pkt_len;
stats->next_update = now + NANO_TO_SEC;
}
else
{
// Increment PPS and BPS using built-in functions.
__sync_fetch_and_add(&stats->pps, 1);
__sync_fetch_and_add(&stats->bps, pkt_len);
}
*pps = stats->pps;
*bps = stats->bps;
}
else
{
// Create new entry.
cl_stats_t new = {0};
new.pps = 1;
new.bps = pkt_len;
new.next_update = now + NANO_TO_SEC;
*pps = new.pps;
*bps = new.bps;
bpf_map_update_elem(&map_ip6_stats, ip, &new, BPF_ANY);
}
return 0;
}
#endif
#ifdef ENABLE_RL_FLOW
/**
* Updates IPv4 flow stats.
*
* @param pps A pointer to the PPS integer.
* @param bps A pointer to the BPS integer.
@@ -10,46 +118,42 @@
* @param port The client's source port.
* @param protocol The client's protocol.
* @param pkt_len The total packet length.
* @param now The current time since boot in nanoseconds.alignas
* @param now The current time since boot in nanoseconds.
*
* @return void
* @return always 0
*/
static __always_inline void update_ip_stats(u64 *pps, u64 *bps, u32 ip, u16 port, u8 protocol, u16 pkt_len, u64 now)
static __always_inline int update_flow_stats(u64 *pps, u64 *bps, u32 ip, u16 port, u8 protocol, u16 pkt_len, u64 now)
{
#ifdef USE_FLOW_RL
flow_t key = {0};
key.ip = ip;
key.port = port;
key.protocol = protocol;
ip_stats_t *ip_stats = bpf_map_lookup_elem(&map_ip_stats, &key);
#else
ip_stats_t *ip_stats = bpf_map_lookup_elem(&map_ip_stats, &ip);
#endif
cl_stats_t* stats = bpf_map_lookup_elem(&map_flow_stats, &key);
if (ip_stats)
if (stats)
{
// Check for next update.
if (now > ip_stats->next_update)
if (now > stats->next_update)
{
ip_stats->pps = 1;
ip_stats->bps = pkt_len;
ip_stats->next_update = now + NANO_TO_SEC;
stats->pps = 1;
stats->bps = pkt_len;
stats->next_update = now + NANO_TO_SEC;
}
else
{
// Increment PPS and BPS using built-in functions.
__sync_fetch_and_add(&ip_stats->pps, 1);
__sync_fetch_and_add(&ip_stats->bps, pkt_len);
__sync_fetch_and_add(&stats->pps, 1);
__sync_fetch_and_add(&stats->bps, pkt_len);
}
*pps = ip_stats->pps;
*bps = ip_stats->bps;
*pps = stats->pps;
*bps = stats->bps;
}
else
{
// Create new entry.
ip_stats_t new = {0};
cl_stats_t new = {0};
new.pps = 1;
new.bps = pkt_len;
@@ -58,16 +162,14 @@ static __always_inline void update_ip_stats(u64 *pps, u64 *bps, u32 ip, u16 port
*pps = new.pps;
*bps = new.bps;
#ifdef USE_FLOW_RL
bpf_map_update_elem(&map_ip_stats, &key, &new, BPF_ANY);
#else
bpf_map_update_elem(&map_ip_stats, &ip, &new, BPF_ANY);
#endif
bpf_map_update_elem(&map_flow_stats, &key, &new, BPF_ANY);
}
return 0;
}
/**
* Updates IPv6 client stats.
* Updates IPv6 flow stats.
*
* @param pps A pointer to the PPS integer.
* @param bps A pointer to the BPS integer.
@@ -75,46 +177,42 @@ static __always_inline void update_ip_stats(u64 *pps, u64 *bps, u32 ip, u16 port
* @param port The client's source port.
* @param protocol The client's protocol.
* @param pkt_len The total packet length.
* @param now The current time since boot in nanoseconds.alignas
* @param now The current time since boot in nanoseconds.
*
* @return void
* @return always 0
*/
static __always_inline void update_ip6_stats(u64 *pps, u64 *bps, u128 *ip, u16 port, u8 protocol, u16 pkt_len, u64 now)
static __always_inline int update_flow6_stats(u64 *pps, u64 *bps, u128 *ip, u16 port, u8 protocol, u16 pkt_len, u64 now)
{
#ifdef USE_FLOW_RL
flow6_t key = {0};
key.ip = *ip;
key.port = port;
key.protocol = protocol;
ip_stats_t *ip_stats = bpf_map_lookup_elem(&map_ip6_stats, &key);
#else
ip_stats_t *ip_stats = bpf_map_lookup_elem(&map_ip6_stats, ip);
#endif
cl_stats_t* stats = bpf_map_lookup_elem(&map_flow6_stats, &key);
if (ip_stats)
if (stats)
{
// Check for next update.
if (now > ip_stats->next_update)
if (now > stats->next_update)
{
ip_stats->pps = 1;
ip_stats->bps = pkt_len;
ip_stats->next_update = now + NANO_TO_SEC;
stats->pps = 1;
stats->bps = pkt_len;
stats->next_update = now + NANO_TO_SEC;
}
else
{
// Increment PPS and BPS using built-in functions.
__sync_fetch_and_add(&ip_stats->pps, 1);
__sync_fetch_and_add(&ip_stats->bps, pkt_len);
__sync_fetch_and_add(&stats->pps, 1);
__sync_fetch_and_add(&stats->bps, pkt_len);
}
*pps = ip_stats->pps;
*bps = ip_stats->bps;
*pps = stats->pps;
*bps = stats->bps;
}
else
{
// Create new entry.
ip_stats_t new = {0};
cl_stats_t new = {0};
new.pps = 1;
new.bps = pkt_len;
@@ -123,11 +221,10 @@ static __always_inline void update_ip6_stats(u64 *pps, u64 *bps, u128 *ip, u16 p
*pps = new.pps;
*bps = new.bps;
#ifdef USE_FLOW_RL
bpf_map_update_elem(&map_ip6_stats, &key, &new, BPF_ANY);
#else
bpf_map_update_elem(&map_ip6_stats, ip, &new, BPF_ANY);
#endif
bpf_map_update_elem(&map_flow6_stats, &key, &new, BPF_ANY);
}
return 0;
}
#endif
#endif

View File

@@ -7,8 +7,10 @@
#include <xdp/utils/maps.h>
#ifdef ENABLE_FILTERS
static __always_inline void update_ip_stats(u64 *pps, u64 *bps, u32 ip, u16 port, u8 protocol, u16 pkt_len, u64 now);
static __always_inline void update_ip6_stats(u64 *pps, u64 *bps, u128 *ip, u16 port, u8 protocol, u16 pkt_len, u64 now);
static __always_inline int update_ip_stats(u64 *pps, u64 *bps, u32 ip, u16 pkt_len, u64 now);
static __always_inline int update_ip6_stats(u64 *pps, u64 *bps, u128 *ip, u16 pkt_len, u64 now);
static __always_inline int update_flow_stats(u64 *pps, u64 *bps, u32 ip, u16 port, u8 protocol, u16 pkt_len, u64 now);
static __always_inline int update_flow6_stats(u64 *pps, u64 *bps, u128 *ip, u16 port, u8 protocol, u16 pkt_len, u64 now);
#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).