From 93f026f292d9f78ee08eb9814a44f9a25566c35c Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Wed, 8 Jul 2026 19:25:07 +0300 Subject: [PATCH] conntrack: ICMP echo rate limiting (ping flood protection) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- netstack/src/filter/conntrack.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/netstack/src/filter/conntrack.rs b/netstack/src/filter/conntrack.rs index 0faf3f14a3..d867dca9f0 100644 --- a/netstack/src/filter/conntrack.rs +++ b/netstack/src/filter/conntrack.rs @@ -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() {