279 lines
7.3 KiB
Go
279 lines
7.3 KiB
Go
package bcap
|
|
|
|
import "time"
|
|
|
|
type trackerTCPState struct {
|
|
firstSeen time.Time
|
|
lastSeen time.Time
|
|
packetCount uint64
|
|
byteCount uint64
|
|
|
|
seq uint32
|
|
ack uint32
|
|
window uint16
|
|
payload int
|
|
finState bool
|
|
synState bool
|
|
isFirst bool
|
|
state uint8
|
|
segments [trackedTCPSegments]tcpSegmentRange
|
|
segmentCount int
|
|
segmentNext int
|
|
}
|
|
|
|
func (t *Tracker) observeTCP(packet Packet, flow FlowRef) (HintSet, error) {
|
|
tcp := packet.Transport.TCP
|
|
if tcp == nil {
|
|
return HintSet{}, NewParseError(ErrTypeTransport, "TCP", "missing tcp facts", nil)
|
|
}
|
|
|
|
forwardKey := flow.Forward.StableString()
|
|
reverseKey := flow.Reverse.StableString()
|
|
if forwardKey == "" {
|
|
forwardKey = flow.Stable
|
|
}
|
|
if reverseKey == "" {
|
|
reverseKey = stableFlowKey(flow.Reverse)
|
|
}
|
|
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
|
|
lastState, exists := t.tcpStates[forwardKey]
|
|
if !exists {
|
|
lastState = trackerTCPState{
|
|
firstSeen: packet.Meta.Timestamp,
|
|
lastSeen: packet.Meta.Timestamp,
|
|
isFirst: true,
|
|
}
|
|
}
|
|
lastReverse := t.tcpStates[reverseKey]
|
|
|
|
payloadLen := tcp.Payload
|
|
seqEnd := tcpSeqAdvanceFacts(tcp.Seq, tcp.SYN, tcp.FIN, payloadLen)
|
|
state := StateUnknown
|
|
hintOpts := tcpHintOptions{}
|
|
connectionClosed := false
|
|
|
|
if tcp.RST {
|
|
state = StateTcpRst
|
|
connectionClosed = true
|
|
delete(t.tcpStates, forwardKey)
|
|
delete(t.tcpStates, reverseKey)
|
|
} else if tcp.SYN && !tcp.ACK {
|
|
state = StateTcpConnect1
|
|
} else if tcp.SYN && tcp.ACK {
|
|
state = StateTcpConnect2
|
|
} else if tcp.ACK && !tcp.FIN {
|
|
state, hintOpts, connectionClosed = classifyTrackerAckNoFIN(packet, tcp, seqEnd, lastState, lastReverse)
|
|
if connectionClosed {
|
|
delete(t.tcpStates, forwardKey)
|
|
delete(t.tcpStates, reverseKey)
|
|
}
|
|
} else if tcp.ACK && tcp.FIN {
|
|
state = classifyTrackerAckFIN(tcp, lastState, lastReverse)
|
|
}
|
|
|
|
if !connectionClosed {
|
|
next := trackerTCPState{
|
|
firstSeen: firstSeenOrNow(lastState.firstSeen, packet.Meta.Timestamp),
|
|
lastSeen: packet.Meta.Timestamp,
|
|
packetCount: lastState.packetCount + 1,
|
|
byteCount: lastState.byteCount + packetWireLength(packet),
|
|
seq: tcp.Seq,
|
|
ack: tcp.Ack,
|
|
window: tcp.Window,
|
|
payload: payloadLen,
|
|
finState: tcp.FIN,
|
|
synState: tcp.SYN,
|
|
isFirst: false,
|
|
state: state,
|
|
segments: lastState.segments,
|
|
segmentCount: lastState.segmentCount,
|
|
segmentNext: lastState.segmentNext,
|
|
}
|
|
next.rememberSegment(tcp.Seq, seqEnd)
|
|
t.tcpStates[forwardKey] = next
|
|
}
|
|
|
|
return newTCPHints(packet, state, hintOpts), nil
|
|
}
|
|
|
|
func (f FlowKey) StableString() string {
|
|
return stableFlowKey(f)
|
|
}
|
|
|
|
func firstSeenOrNow(firstSeen, fallback time.Time) time.Time {
|
|
if firstSeen.IsZero() {
|
|
return fallback
|
|
}
|
|
return firstSeen
|
|
}
|
|
|
|
func packetWireLength(packet Packet) uint64 {
|
|
switch {
|
|
case packet.Meta.Length > 0:
|
|
return uint64(packet.Meta.Length)
|
|
case packet.Meta.CaptureLength > 0:
|
|
return uint64(packet.Meta.CaptureLength)
|
|
case packet.Raw.Packet != nil:
|
|
return uint64(len(packet.Raw.Packet.Data()))
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func tcpSeqAdvanceFacts(seq uint32, syn, fin bool, payloadLen int) uint32 {
|
|
span := payloadLen
|
|
if syn {
|
|
span++
|
|
}
|
|
if fin {
|
|
span++
|
|
}
|
|
return tcpSeqAdd(seq, uint32(span))
|
|
}
|
|
|
|
func classifyTrackerAckNoFIN(
|
|
packet Packet,
|
|
tcp *TCPFacts,
|
|
seqEnd uint32,
|
|
lastState, lastReverse trackerTCPState,
|
|
) (uint8, tcpHintOptions, bool) {
|
|
if isTrackerHandshakeAck(lastState, lastReverse, tcp.Ack) {
|
|
return StateTcpConnect3, tcpHintOptions{}, false
|
|
}
|
|
if tcp.CWR {
|
|
return StateTcpCwr, tcpHintOptions{}, false
|
|
}
|
|
if tcp.ECE {
|
|
return StateTcpEce, tcpHintOptions{}, false
|
|
}
|
|
if state, opts, ok := classifyTrackerTCPKeepalive(packet, tcp, lastState, lastReverse); ok {
|
|
return state, opts, false
|
|
}
|
|
if lastState.hasSeenSegment(tcp.Seq, seqEnd) {
|
|
return StateTcpRetransmit, tcpHintOptions{}, false
|
|
}
|
|
if lastReverse.finState && lastState.finState {
|
|
return StateTcpDisconnect4, tcpHintOptions{}, true
|
|
}
|
|
if lastReverse.finState && tcpSeqAdd(lastReverse.seq, 1) == tcp.Ack {
|
|
return StateTcpDisconnect2, tcpHintOptions{}, false
|
|
}
|
|
return StateTcpAckOk, tcpHintOptions{}, false
|
|
}
|
|
|
|
func classifyTrackerAckFIN(tcp *TCPFacts, lastState, lastReverse trackerTCPState) uint8 {
|
|
if !lastReverse.finState {
|
|
return StateTcpDisconnect1
|
|
}
|
|
if lastReverse.finState && tcpSeqAdd(lastReverse.seq, 1) == tcp.Ack &&
|
|
lastState.ack == tcp.Ack && lastState.seq == tcp.Seq {
|
|
return StateTcpDisconnect3
|
|
}
|
|
return StateTcpDisconnect23
|
|
}
|
|
|
|
func isTrackerHandshakeAck(lastState, lastReverse trackerTCPState, ack uint32) bool {
|
|
if lastReverse.state != StateTcpConnect2 {
|
|
return false
|
|
}
|
|
if tcpSeqAdd(lastReverse.seq, 1) != ack {
|
|
return false
|
|
}
|
|
return lastState.state == StateTcpConnect1 && lastState.synState
|
|
}
|
|
|
|
func classifyTrackerTCPKeepalive(packet Packet, tcp *TCPFacts, lastState, lastReverse trackerTCPState) (uint8, tcpHintOptions, bool) {
|
|
if isTrackerRepeatedKeepaliveProbe(packet, tcp, lastState, lastReverse) {
|
|
return StateTcpKeepalive, tcpHintOptions{}, true
|
|
}
|
|
if lastReverse.matchesKeepaliveResponse(packet, tcp) {
|
|
return StateTcpKeepalive, tcpHintOptions{keepaliveResponse: true}, true
|
|
}
|
|
if tcp.Seq == tcpSeqPrev(lastReverse.ack) &&
|
|
tcp.Seq == tcpSeqPrev(tcpSeqAdd(lastState.seq, uint32(lastState.payload))) {
|
|
return StateTcpKeepalive, tcpHintOptions{}, true
|
|
}
|
|
return StateUnknown, tcpHintOptions{}, false
|
|
}
|
|
|
|
func isTrackerRepeatedKeepaliveProbe(packet Packet, tcp *TCPFacts, lastState, lastReverse trackerTCPState) bool {
|
|
if lastState.isFirst {
|
|
return false
|
|
}
|
|
if tcp == nil || !tcp.ACK || tcp.SYN || tcp.FIN || tcp.RST || tcp.ECE || tcp.CWR {
|
|
return false
|
|
}
|
|
if tcp.Payload != 0 {
|
|
return false
|
|
}
|
|
if lastState.synState || lastState.finState || lastState.payload != 0 {
|
|
return false
|
|
}
|
|
switch lastState.state {
|
|
case StateTcpAckOk, StateTcpKeepalive, StateTcpConnect3, StateTcpDisconnect2:
|
|
default:
|
|
return false
|
|
}
|
|
if tcp.Seq != lastState.seq || tcp.Ack != lastState.ack || tcp.Window != lastState.window {
|
|
return false
|
|
}
|
|
if packet.Meta.Timestamp.Sub(lastState.lastSeen) < repeatedKeepaliveMinInterval {
|
|
return false
|
|
}
|
|
if !lastReverse.lastSeen.IsZero() && lastReverse.lastSeen.After(lastState.lastSeen) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s trackerTCPState) matchesKeepaliveResponse(packet Packet, tcp *TCPFacts) bool {
|
|
if tcp == nil || s.state != StateTcpKeepalive {
|
|
return false
|
|
}
|
|
if !tcp.ACK || tcp.SYN || tcp.FIN || tcp.RST || tcp.ECE || tcp.CWR {
|
|
return false
|
|
}
|
|
if tcp.Payload != 0 {
|
|
return false
|
|
}
|
|
if packet.Meta.Timestamp.Sub(s.lastSeen) > keepaliveResponseMaxDelay {
|
|
return false
|
|
}
|
|
return tcp.Seq == s.ack && tcp.Ack == tcpSeqAdd(s.seq, 1)
|
|
}
|
|
|
|
func (s trackerTCPState) hasSeenSegment(seq, end uint32) bool {
|
|
if !tcpSeqLess(seq, end) {
|
|
return false
|
|
}
|
|
for i := 0; i < s.segmentCount; i++ {
|
|
seg := s.segments[i]
|
|
if !tcpSeqLess(seg.seq, seg.end) {
|
|
continue
|
|
}
|
|
if !tcpSeqLess(seq, seg.seq) && tcpSeqLEQ(end, seg.end) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *trackerTCPState) rememberSegment(seq, end uint32) {
|
|
if !tcpSeqLess(seq, end) {
|
|
return
|
|
}
|
|
if s.hasSeenSegment(seq, end) {
|
|
return
|
|
}
|
|
if s.segmentCount < trackedTCPSegments {
|
|
s.segments[s.segmentCount] = tcpSegmentRange{seq: seq, end: end}
|
|
s.segmentCount++
|
|
return
|
|
}
|
|
s.segments[s.segmentNext] = tcpSegmentRange{seq: seq, end: end}
|
|
s.segmentNext = (s.segmentNext + 1) % trackedTCPSegments
|
|
}
|