From 102d00b51662e3d9fc386aa27d106039820f3f7d Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Thu, 9 Jul 2026 01:28:08 +0300 Subject: [PATCH] =?UTF-8?q?conntrack:=20fix=20SynRecv=E2=86=92Established?= =?UTF-8?q?=20transition=20direction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TCP three-way handshake completion (initiator's ACK after SYN-ACK) arrives on the ORIGINAL direction, not the reply direction. Previously the SynRecv→Established transition was gated on is_orig==false, which meant it would never fire after the initial SYN-ACK reply. Connections would stay in SynRecv state forever with ConnState::New. Fix: move SynRecv→Established from reply to orig direction, per Linux nf_conntrack_proto_tcp.c (TCP_CONNTRACK_SYN_RECV fires on IP_CT_DIR_ORIGINAL packets). Reply direction now only handles: - SYN-ACK from responder (SynSent→SynRecv) - FIN from responder (Established→FinWait) - Second FIN from responder (FinWait→TimeWait) - TimeWait timeout extension Orig direction now handles: - ACK from initiator (SynRecv→Established) - FIN from initiator (Established→FinWait) - Second FIN from initiator (FinWait→TimeWait) RST still closes from either direction. All 31 tests pass. --- netstack/src/filter/conntrack.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/netstack/src/filter/conntrack.rs b/netstack/src/filter/conntrack.rs index 16acd67f82..438b93d955 100644 --- a/netstack/src/filter/conntrack.rs +++ b/netstack/src/filter/conntrack.rs @@ -184,8 +184,14 @@ fn advance_entry_state(entry: &mut ConnEntry, is_orig: bool, ctx: &PacketContext let res_fin = is_fin(flags); if is_orig { - // Original direction: track FIN as connection teardown. + // Original direction: handshake completion and FIN teardown. match entry.tcp_state { + TcpTracking::SynRecv => { + entry.tcp_state = TcpTracking::Established; + entry.state = ConnState::Established; + entry.timeout = now + Duration::from_secs(432000); + return true; + } TcpTracking::Established if res_fin => { entry.tcp_state = TcpTracking::FinWait; entry.timeout = now + Duration::from_secs(120); @@ -199,7 +205,7 @@ fn advance_entry_state(entry: &mut ConnEntry, is_orig: bool, ctx: &PacketContext _ => {} } } else { - // Reply direction: handshake establishment and close. + // Reply direction: handshake initiation and close. match entry.tcp_state { TcpTracking::None if (flags & 0x12) == 0x12 => { entry.tcp_state = TcpTracking::SynRecv; @@ -211,12 +217,6 @@ fn advance_entry_state(entry: &mut ConnEntry, is_orig: bool, ctx: &PacketContext entry.timeout = now + Duration::from_secs(60); return true; } - TcpTracking::SynRecv => { - entry.tcp_state = TcpTracking::Established; - entry.state = ConnState::Established; - entry.timeout = now + Duration::from_secs(432000); - return true; - } TcpTracking::Established if res_fin => { entry.tcp_state = TcpTracking::FinWait; entry.timeout = now + Duration::from_secs(120);