feat(tcp, udp): implement stream state release after verdict and adjust flush interval logic

This commit is contained in:
2026-09-12 17:20:24 +08:00
parent cc6ad72706
commit 8f8cc1d4d9
4 changed files with 254 additions and 12 deletions
+30 -5
View File
@@ -124,8 +124,8 @@ type tcpStream struct {
logger Logger
ruleset ruleset.Ruleset
activeEntries []*tcpStreamEntry
doneEntries []*tcpStreamEntry
lastVerdict tcpVerdict
released bool // 最终判定已发出,分析器和属性表已经释放
firstSegSeen bool // true once the client's first data segment was measured
synAckWindow int // window of the observed SYN-ACK, -1 if none seen yet
synAckWscale int // its window scale factor, -1 if the option was absent
@@ -139,6 +139,11 @@ type tcpStreamEntry struct {
}
func (s *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, nextSeq reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool {
if s.released {
// 最终判定已发出,后续报文一律沿用;Props 已经释放,不能再碰
ac.(*tcpContext).Verdict = s.lastVerdict
return false
}
if tcp.SYN && tcp.ACK && s.synAckWindow < 0 {
s.synAckWindow, s.synAckWscale = int(tcp.Window), tcpWindowScale(tcp)
if s.synAckWindow <= tcpSynAckWindowMax {
@@ -210,6 +215,10 @@ func (s *tcpStream) recordFirstSegLen(n int) bool {
}
func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) {
if s.released {
// 判定已发出。还会走到这里只可能是超时 flush 补送的乱序页,没有东西可处理
return
}
dir, start, end, skip := sg.Info()
rev := dir == reassembly.TCPDirServerToClient
avail, _ := sg.Lengths()
@@ -229,7 +238,6 @@ func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
updated = updated || up1 || up2
if done {
s.activeEntries = append(s.activeEntries[:i], s.activeEntries[i+1:]...)
s.doneEntries = append(s.doneEntries, entry)
}
}
ctx := ac.(*tcpContext)
@@ -254,15 +262,20 @@ func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass
ctx.Verdict = tcpVerdictAcceptStream
s.logger.TCPStreamAction(s.info, ruleset.ActionAllow, true)
}
if len(s.activeEntries) == 0 {
// 不会再有分析器读数据,也不会再匹配规则:从这一刻起 Accept 只看 lastVerdict
s.release()
}
}
func (s *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
s.closeActiveEntries()
s.release()
return true
}
func (s *tcpStream) closeActiveEntries() {
// Signal close to all active entries & move them to doneEntries
// Signal close to all active entries
updated := false
for _, entry := range s.activeEntries {
update := entry.Stream.Close(false)
@@ -272,10 +285,22 @@ func (s *tcpStream) closeActiveEntries() {
if updated {
s.logger.TCPStreamPropUpdate(s.info, true)
}
s.doneEntries = append(s.doneEntries, s.activeEntries...)
s.activeEntries = nil
}
// release 在最终判定发出后丢掉不会再用到的状态。
//
// 判定之后 Accept 只看 lastVerdict,分析器和属性表都不会再被读。但这条流本身还要在
// 连接池里挂到超时被 flush,而 gopacket 回收连接槽位时并不清掉 stream 指针,
// 要等下一条连接复用那个槽位才会覆盖。不主动放手的话,每条判定完的流会带着
// 全部分析器对象一直活到那时候;节点上的 heap profile 里这部分占到了八成。
func (s *tcpStream) release() {
s.released = true
s.activeEntries = nil
s.info.Props = nil
s.ruleset = nil
}
func (s *tcpStream) feedEntry(entry *tcpStreamEntry, rev, start, end bool, skip int, data []byte) (update *analyzer.PropUpdate, closeUpdate *analyzer.PropUpdate, done bool) {
if !entry.HasLimit {
update, done = entry.Stream.Feed(rev, start, end, skip, data)
@@ -287,7 +312,7 @@ func (s *tcpStream) feedEntry(entry *tcpStreamEntry, rev, start, end bool, skip
update, done = entry.Stream.Feed(rev, start, end, skip, qData)
entry.Quota -= len(qData)
if entry.Quota <= 0 {
// Quota exhausted, signal close & move to doneEntries
// Quota exhausted, signal close & drop the entry
closeUpdate = entry.Stream.Close(true)
done = true
}