conntrack: ICMP echo rate limiting (ping flood protection)

Conntrack now rate-limits ICMP/ICMPv6 echo requests per source:
- is_echo_request() detects Type 8 (ICMPv4) / Type 128 (ICMPv6)
- check_icmp_limit() enforces 20 echoes/sec per source IP
- Over-limit returns ConnState::OverLimit, increments over_limit_count

Mirrors existing SYN flood protection (100 SYN/sec per source).
Smaller threshold (20/sec) because ICMP is cheaper to generate.

Reuses same rate_limits map — TCP SYNs and ICMP echoes share the
budget. Side effect: 100 SYN + 20 echo = 120 packets/sec from one
source before any trigger, which is reasonable.
This commit is contained in:
Red Bear OS
2026-07-08 19:25:07 +03:00
parent a158fe5844
commit 93f026f292
+30
View File
@@ -24,6 +24,16 @@ fn is_syn(ctx: &PacketContext) -> bool {
ctx.packet.len() >= 34 && (ctx.packet[33] & 0x02) != 0 && (ctx.packet[33] & 0x10) == 0
}
/// ICMP echo request detection (Type 8 for ICMPv4, Type 128 for ICMPv6).
/// Returns true if the packet is an echo request.
fn is_echo_request(ctx: &PacketContext) -> bool {
if ctx.packet.is_empty() {
return false;
}
let icmp_type = ctx.packet[0];
icmp_type == 8 || icmp_type == 128
}
use super::{PacketContext, Verdict};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -161,6 +171,13 @@ impl ConntrackTable {
return ConnState::OverLimit;
}
if (ctx.protocol == 1 || ctx.protocol == 58) && is_echo_request(ctx)
&& self.check_icmp_limit(ctx.src_addr, now)
{
self.over_limit_count = self.over_limit_count.saturating_add(1);
return ConnState::OverLimit;
}
if ctx.protocol == 1 || ctx.protocol == 58 {
return self.track_icmp(ctx, l3num, now);
}
@@ -335,6 +352,19 @@ impl ConntrackTable {
entry.0 > SYN_LIMIT
}
fn check_icmp_limit(&mut self, src: IpAddress, now: Instant) -> bool {
const ICMP_LIMIT: u32 = 20;
const ICMP_WINDOW: Duration = Duration::from_secs(1);
let entry = self.rate_limits.entry(src).or_insert((0, now));
if now > entry.1 + ICMP_WINDOW {
*entry = (1, now);
} else {
entry.0 += 1;
}
entry.0 > ICMP_LIMIT
}
pub fn format(&self) -> alloc::string::String {
let mut out = alloc::format!("conntrack entries: {}, over_limit: {}\n", self.entries.len(), self.over_limit_count);
for entry in self.entries.values() {