conntrack: fin_from_orig tracking + TCP state in format output

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.
This commit is contained in:
Red Bear OS
2026-07-09 01:33:26 +03:00
parent 102d00b516
commit d51320ebff
+16 -3
View File
@@ -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,