conntrack: ICMP error tracking with ConnState::Error variant

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.
This commit is contained in:
Red Bear OS
2026-07-09 09:57:54 +03:00
parent d4612ef4c9
commit b50fb644a9
+60
View File
@@ -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) {