From d51320ebfff6c70c355fd1b2268748d28a3ef946 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Thu, 9 Jul 2026 01:33:26 +0300 Subject: [PATCH] conntrack: fin_from_orig tracking + TCP state in format output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ConnEntry gains fin_from_orig: bool field to track which direction sent the first FIN. This enables correct TimeWait transition: FinWait→TimeWait now only fires when the OPPOSITE direction sends FIN (not when the same side retransmits). Per Linux nf_conntrack_proto_tcp.c: - fin_from_orig=true means orig sent first FIN - TimeWait transition requires reply FIN next - fin_from_orig=false means reply sent first FIN - TimeWait transition requires orig FIN next TCP state now included in format() output: Established tcp=Established src=10.0.0.1 dst=10.0.0.2 sport=80 dport=1234 ... New tcp=SynSent src=... Established tcp=TimeWait src=... All 31 tests pass. --- netstack/src/filter/conntrack.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/netstack/src/filter/conntrack.rs b/netstack/src/filter/conntrack.rs index 438b93d955..f18720b8d0 100644 --- a/netstack/src/filter/conntrack.rs +++ b/netstack/src/filter/conntrack.rs @@ -150,6 +150,7 @@ struct ConnEntry { reply_key: ConnKey, state: ConnState, tcp_state: TcpTracking, + fin_from_orig: bool, timeout: Instant, orig_packets: u64, orig_bytes: u64, @@ -194,10 +195,12 @@ fn advance_entry_state(entry: &mut ConnEntry, is_orig: bool, ctx: &PacketContext } TcpTracking::Established if res_fin => { entry.tcp_state = TcpTracking::FinWait; + entry.fin_from_orig = true; entry.timeout = now + Duration::from_secs(120); return true; } - TcpTracking::FinWait if res_fin => { + TcpTracking::FinWait if res_fin && !entry.fin_from_orig => { + // Second FIN from reply direction (orig FIN was first) entry.tcp_state = TcpTracking::TimeWait; entry.timeout = now + Duration::from_secs(120); return true; @@ -219,10 +222,12 @@ fn advance_entry_state(entry: &mut ConnEntry, is_orig: bool, ctx: &PacketContext } TcpTracking::Established if res_fin => { entry.tcp_state = TcpTracking::FinWait; + entry.fin_from_orig = false; entry.timeout = now + Duration::from_secs(120); return true; } - TcpTracking::FinWait if res_fin => { + TcpTracking::FinWait if res_fin && entry.fin_from_orig => { + // Second FIN from orig direction (reply FIN was first) entry.tcp_state = TcpTracking::TimeWait; entry.timeout = now + Duration::from_secs(120); return true; @@ -327,6 +332,7 @@ impl ConntrackTable { reply_key, state, tcp_state, + fin_from_orig: false, timeout: now + timeout, orig_packets: 1, orig_bytes: ctx.packet.len() as u64, @@ -390,6 +396,7 @@ impl ConntrackTable { }, state: ConnState::New, tcp_state: TcpTracking::None, + fin_from_orig: false, timeout: now + Duration::from_secs(30), orig_packets: 1, orig_bytes: ctx.packet.len() as u64, @@ -518,9 +525,15 @@ impl ConntrackTable { 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() { + let tcp_info = if entry.key.l4proto == 6 { + alloc::format!(" tcp={:?}", entry.tcp_state) + } else { + alloc::string::String::new() + }; out.push_str(&alloc::format!( - " {:?} src={} dst={} sport={} dport={} orig_pkts={} orig_bytes={} reply_pkts={} reply_bytes={}\n", + " {:?}{} src={} dst={} sport={} dport={} orig_pkts={} orig_bytes={} reply_pkts={} reply_bytes={}\n", entry.state, + tcp_info, entry.key.src_addr, entry.key.dst_addr, entry.key.src_port,