Skip to content

Commit 09f538e

Browse files
committed
Treated tombstoned conns as new
1 parent 6981fdc commit 09f538e

File tree

3 files changed

+163
-3
lines changed

3 files changed

+163
-3
lines changed

client/firewall/uspfilter/conntrack/tcp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func (t *TCPTracker) updateIfExists(srcIP, dstIP netip.Addr, srcPort, dstPort ui
169169
conn, exists := t.connections[key]
170170
t.mutex.RUnlock()
171171

172-
if exists {
172+
if exists && !conn.IsTombstone() {
173173
t.updateState(key, conn, flags, direction, size)
174174
return key, uint16(conn.DNATOrigPort.Load()), true
175175
}

client/firewall/uspfilter/conntrack/tcp_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,155 @@ func TestTCPAbnormalSequences(t *testing.T) {
485485
})
486486
}
487487

488+
// TestTCPPortReuseTombstone verifies that a new connection on a port with a
489+
// tombstoned (closed) conntrack entry is properly tracked. A SYN on a
490+
// tombstoned entry must replace it so that the subsequent SYN-ACK is accepted.
491+
func TestTCPPortReuseTombstone(t *testing.T) {
492+
srcIP := netip.MustParseAddr("100.64.0.1")
493+
dstIP := netip.MustParseAddr("100.64.0.2")
494+
srcPort := uint16(12345)
495+
dstPort := uint16(80)
496+
497+
t.Run("Outbound port reuse after graceful close", func(t *testing.T) {
498+
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
499+
defer tracker.Close()
500+
501+
key := ConnKey{SrcIP: srcIP, DstIP: dstIP, SrcPort: srcPort, DstPort: dstPort}
502+
503+
// Establish and gracefully close a connection (server-initiated close)
504+
establishConnection(t, tracker, srcIP, dstIP, srcPort, dstPort)
505+
506+
// Server sends FIN
507+
valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPFin|TCPAck, 0)
508+
require.True(t, valid)
509+
510+
// Client sends FIN-ACK
511+
tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPFin|TCPAck, 0)
512+
513+
// Server sends final ACK
514+
valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPAck, 0)
515+
require.True(t, valid)
516+
517+
// Connection should be tombstoned
518+
conn := tracker.connections[key]
519+
require.NotNil(t, conn, "old connection should still be in map")
520+
require.True(t, conn.IsTombstone(), "old connection should be tombstoned")
521+
522+
// Now reuse the same port for a new connection
523+
tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPSyn, 100)
524+
525+
// The old tombstoned entry should be replaced with a new one
526+
newConn := tracker.connections[key]
527+
require.NotNil(t, newConn, "new connection should exist")
528+
require.False(t, newConn.IsTombstone(), "new connection should not be tombstoned")
529+
require.Equal(t, TCPStateSynSent, newConn.GetState())
530+
531+
// SYN-ACK for the new connection should be valid
532+
valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPSyn|TCPAck, 100)
533+
require.True(t, valid, "SYN-ACK for new connection on reused port should be accepted")
534+
require.Equal(t, TCPStateEstablished, newConn.GetState())
535+
536+
// Data transfer should work
537+
tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck, 100)
538+
valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPPush|TCPAck, 500)
539+
require.True(t, valid, "data should be allowed on new connection")
540+
})
541+
542+
t.Run("Outbound port reuse after RST", func(t *testing.T) {
543+
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
544+
defer tracker.Close()
545+
546+
key := ConnKey{SrcIP: srcIP, DstIP: dstIP, SrcPort: srcPort, DstPort: dstPort}
547+
548+
// Establish and RST a connection
549+
establishConnection(t, tracker, srcIP, dstIP, srcPort, dstPort)
550+
valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPRst|TCPAck, 0)
551+
require.True(t, valid)
552+
553+
conn := tracker.connections[key]
554+
require.True(t, conn.IsTombstone(), "RST connection should be tombstoned")
555+
556+
// Reuse the same port
557+
tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPSyn, 100)
558+
559+
newConn := tracker.connections[key]
560+
require.NotNil(t, newConn)
561+
require.False(t, newConn.IsTombstone())
562+
require.Equal(t, TCPStateSynSent, newConn.GetState())
563+
564+
valid = tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPSyn|TCPAck, 100)
565+
require.True(t, valid, "SYN-ACK should be accepted after RST tombstone")
566+
})
567+
568+
t.Run("Inbound port reuse after close", func(t *testing.T) {
569+
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
570+
defer tracker.Close()
571+
572+
clientIP := srcIP
573+
serverIP := dstIP
574+
clientPort := srcPort
575+
serverPort := dstPort
576+
key := ConnKey{SrcIP: clientIP, DstIP: serverIP, SrcPort: clientPort, DstPort: serverPort}
577+
578+
// Inbound connection: client SYN → server SYN-ACK → client ACK
579+
tracker.TrackInbound(clientIP, serverIP, clientPort, serverPort, TCPSyn, nil, 100, 0)
580+
tracker.TrackOutbound(serverIP, clientIP, serverPort, clientPort, TCPSyn|TCPAck, 100)
581+
tracker.TrackInbound(clientIP, serverIP, clientPort, serverPort, TCPAck, nil, 100, 0)
582+
583+
conn := tracker.connections[key]
584+
require.Equal(t, TCPStateEstablished, conn.GetState())
585+
586+
// Server-initiated close to reach Closed/tombstoned:
587+
// Server FIN (opposite dir) → CloseWait
588+
tracker.TrackOutbound(serverIP, clientIP, serverPort, clientPort, TCPFin|TCPAck, 100)
589+
require.Equal(t, TCPStateCloseWait, conn.GetState())
590+
// Client FIN-ACK (same dir as conn) → LastAck
591+
tracker.TrackInbound(clientIP, serverIP, clientPort, serverPort, TCPFin|TCPAck, nil, 100, 0)
592+
require.Equal(t, TCPStateLastAck, conn.GetState())
593+
// Server final ACK (opposite dir) → Closed → tombstoned
594+
tracker.TrackOutbound(serverIP, clientIP, serverPort, clientPort, TCPAck, 100)
595+
596+
require.True(t, conn.IsTombstone())
597+
598+
// New inbound connection on same ports
599+
tracker.TrackInbound(clientIP, serverIP, clientPort, serverPort, TCPSyn, nil, 100, 0)
600+
601+
newConn := tracker.connections[key]
602+
require.NotNil(t, newConn)
603+
require.False(t, newConn.IsTombstone())
604+
require.Equal(t, TCPStateSynReceived, newConn.GetState())
605+
606+
// Complete handshake: server SYN-ACK, then client ACK
607+
tracker.TrackOutbound(serverIP, clientIP, serverPort, clientPort, TCPSyn|TCPAck, 100)
608+
tracker.TrackInbound(clientIP, serverIP, clientPort, serverPort, TCPAck, nil, 100, 0)
609+
require.Equal(t, TCPStateEstablished, newConn.GetState())
610+
})
611+
612+
t.Run("Late ACK on tombstoned connection is harmless", func(t *testing.T) {
613+
tracker := NewTCPTracker(DefaultTCPTimeout, logger, flowLogger)
614+
defer tracker.Close()
615+
616+
key := ConnKey{SrcIP: srcIP, DstIP: dstIP, SrcPort: srcPort, DstPort: dstPort}
617+
618+
// Establish and close via passive close (server-initiated FIN → Closed → tombstoned)
619+
establishConnection(t, tracker, srcIP, dstIP, srcPort, dstPort)
620+
tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPFin|TCPAck, 0) // CloseWait
621+
tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPFin|TCPAck, 0) // LastAck
622+
tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPAck, 0) // Closed
623+
624+
conn := tracker.connections[key]
625+
require.True(t, conn.IsTombstone())
626+
627+
// Late ACK should be rejected (tombstoned)
628+
valid := tracker.IsValidInbound(dstIP, srcIP, dstPort, srcPort, TCPAck, 0)
629+
require.False(t, valid, "late ACK on tombstoned connection should be rejected")
630+
631+
// Late outbound ACK should not create a new connection (not a SYN)
632+
tracker.TrackOutbound(srcIP, dstIP, srcPort, dstPort, TCPAck, 0)
633+
require.True(t, tracker.connections[key].IsTombstone(), "late outbound ACK should not replace tombstoned entry")
634+
})
635+
}
636+
488637
func TestTCPTimeoutHandling(t *testing.T) {
489638
// Create tracker with a very short timeout for testing
490639
shortTimeout := 100 * time.Millisecond

client/firewall/uspfilter/log/log.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"context"
66
"fmt"
77
"io"
8+
"os"
9+
"strconv"
810
"sync"
911
"sync/atomic"
1012
"time"
@@ -16,9 +18,18 @@ const (
1618
maxBatchSize = 1024 * 16
1719
maxMessageSize = 1024 * 2
1820
defaultFlushInterval = 2 * time.Second
19-
logChannelSize = 1000
21+
defaultLogChanSize = 1000
2022
)
2123

24+
func getLogChannelSize() int {
25+
if v := os.Getenv("NB_USPFILTER_LOG_BUFFER"); v != "" {
26+
if n, err := strconv.Atoi(v); err == nil && n > 0 {
27+
return n
28+
}
29+
}
30+
return defaultLogChanSize
31+
}
32+
2233
type Level uint32
2334

2435
const (
@@ -69,7 +80,7 @@ type Logger struct {
6980
func NewFromLogrus(logrusLogger *log.Logger) *Logger {
7081
l := &Logger{
7182
output: logrusLogger.Out,
72-
msgChannel: make(chan logMessage, logChannelSize),
83+
msgChannel: make(chan logMessage, getLogChannelSize()),
7384
shutdown: make(chan struct{}),
7485
bufPool: sync.Pool{
7586
New: func() any {

0 commit comments

Comments
 (0)