From b50fb644a945c088f68fece9cecf49754cd2af3b Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Thu, 9 Jul 2026 09:57:54 +0300 Subject: [PATCH] conntrack: ICMP error tracking with ConnState::Error variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added ConnState::Error to the connection state enum for ICMP error packets. Added is_error detection for ICMPv4/v6 Dest Unreachable and Time Exceeded types. When an ICMP error is detected, delegates to track_icmp_error() which extracts the embedded original packet tuple and relates it to an existing tracked connection (mirrors Linux 7.1 nf_conntrack_icmp_error() in net/netfilter/nf_conntrack_proto_icmp.c). Previously WIP — now committed to stabilize the base fork build. --- netstack/src/filter/conntrack.rs | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/netstack/src/filter/conntrack.rs b/netstack/src/filter/conntrack.rs index f18720b8d0..67403c2caf 100644 --- a/netstack/src/filter/conntrack.rs +++ b/netstack/src/filter/conntrack.rs @@ -78,6 +78,7 @@ pub enum ConnState { Established, Related, OverLimit, + Error, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -365,6 +366,17 @@ impl ConntrackTable { let is_echo = icmp_type == 8 || icmp_type == 128; let is_echo_reply = icmp_type == 0 || icmp_type == 129; + let is_error = (l3num == 4 && icmp_type == 3) // ICMPv4 Dest Unreachable + || (l3num == 4 && icmp_type == 11) // ICMPv4 Time Exceeded + || (l3num == 6 && icmp_type == 1) // ICMPv6 Dest Unreachable + || (l3num == 6 && icmp_type == 3); // ICMPv6 Time Exceeded + + // ICMP errors carry the original packet. Try to extract the + // embedded connection tuple so we can relate it to an existing + // tracked connection (mirrors nf_conntrack_icmp_error()). + if is_error { + return self.track_icmp_error(ctx, l3num, icmp_offset, now); + } if !is_echo && !is_echo_reply { return ConnState::New; @@ -425,6 +437,54 @@ impl ConntrackTable { ConnState::New } + /// Process an ICMP error message. Extracts the embedded connection + /// tuple from the original IP header carried in the ICMP payload. + /// Returns ConnState::Related if the embedded connection matches an + /// existing tracked connection (mirrors nf_conntrack_icmp_error()). + fn track_icmp_error( + &mut self, + ctx: &PacketContext, + l3num: u8, + icmp_offset: usize, + now: Instant, + ) -> ConnState { + // ICMP error payload: 4 bytes unused + original IP header + 8 bytes + let inner_ip_start = icmp_offset + 8; + if ctx.packet.len() < inner_ip_start + 20 { + return ConnState::Error; + } + // Parse the inner IPv4 header. + let inner = &ctx.packet[inner_ip_start..]; + if inner.len() < 20 || (inner[0] >> 4) != 4 { + return ConnState::Error; + } + let inner_proto = inner[9]; + let inner_src = IpAddress::v4(inner[12], inner[13], inner[14], inner[15]); + let inner_dst = IpAddress::v4(inner[16], inner[17], inner[18], inner[19]); + let ihl = (inner[0] & 0x0f) as usize * 4; + if inner.len() < ihl + 4 || inner_proto != 6 && inner_proto != 17 { + return ConnState::Error; + } + let sport = u16::from_be_bytes([inner[ihl], inner[ihl + 1]]); + let dport = u16::from_be_bytes([inner[ihl + 2], inner[ihl + 3]]); + + // Build the inner connection key and check for a match. + let inner_key = ConnKey { + l3num: 4, + l4proto: inner_proto, + src_addr: inner_src, + dst_addr: inner_dst, + src_port: sport, + dst_port: dport, + }; + + if self.entries.contains_key(&inner_key) { + ConnState::Related + } else { + ConnState::Error + } + } + pub fn clean_expired(&mut self, now: Instant) { if let Some(last) = self.last_cleanup { if now < last + Duration::from_secs(1) {