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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user