package tap import ( "encoding/binary" "encoding/hex" "fmt" "sync" "github.com/google/gopacket" "github.com/google/gopacket/layers" // pulls in all layers decoders "github.com/google/gopacket/reassembly" ) /* It's a connection (bidirectional) * Implements gopacket.reassembly.Stream interface (Accept, ReassembledSG, ReassemblyComplete) * ReassembledSG gets called when new reassembled data is ready (i.e. bytes in order, no duplicates, complete) * In our implementation, we pass information from ReassembledSG to the httpReader through a shared channel. */ type tcpStream struct { tcpstate *reassembly.TCPSimpleFSM fsmerr bool optchecker reassembly.TCPOptionCheck net, transport gopacket.Flow isDNS bool isHTTP bool reversed bool client httpReader server httpReader urls []string ident string sync.Mutex } func (t *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, nextSeq reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool { // FSM if !t.tcpstate.CheckState(tcp, dir) { SilentError("FSM-rejection", "%s: Packet rejected by FSM (state:%s)", t.ident, t.tcpstate.String()) stats.rejectFsm++ if !t.fsmerr { t.fsmerr = true stats.rejectConnFsm++ } if !*ignorefsmerr { return false } } // Options err := t.optchecker.Accept(tcp, ci, dir, nextSeq, start) if err != nil { SilentError("OptionChecker-rejection", "%s: Packet rejected by OptionChecker: %s", t.ident, err) stats.rejectOpt++ if !*nooptcheck { return false } } // Checksum accept := true if *checksum { c, err := tcp.ComputeChecksum() if err != nil { SilentError("ChecksumCompute", "%s: Got error computing checksum: %s", t.ident, err) accept = false } else if c != 0x0 { SilentError("Checksum", "%s: Invalid checksum: 0x%x", t.ident, c) accept = false } } if !accept { stats.rejectOpt++ } return accept } func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) { dir, start, end, skip := sg.Info() length, saved := sg.Lengths() // update stats sgStats := sg.Stats() if skip > 0 { stats.missedBytes += skip } stats.sz += length - saved stats.pkt += sgStats.Packets if sgStats.Chunks > 1 { stats.reassembled++ } stats.outOfOrderPackets += sgStats.QueuedPackets stats.outOfOrderBytes += sgStats.QueuedBytes if length > stats.biggestChunkBytes { stats.biggestChunkBytes = length } if sgStats.Packets > stats.biggestChunkPackets { stats.biggestChunkPackets = sgStats.Packets } if sgStats.OverlapBytes != 0 && sgStats.OverlapPackets == 0 { // In the original example this was handled with panic(). // I don't know what this error means or how to handle it properly. SilentError("Invalid-Overlap", "bytes:%d, pkts:%d", sgStats.OverlapBytes, sgStats.OverlapPackets) } stats.overlapBytes += sgStats.OverlapBytes stats.overlapPackets += sgStats.OverlapPackets var ident string if dir == reassembly.TCPDirClientToServer { ident = fmt.Sprintf("%v %v(%s): ", t.net, t.transport, dir) } else { ident = fmt.Sprintf("%v %v(%s): ", t.net.Reverse(), t.transport.Reverse(), dir) } Trace("%s: SG reassembled packet with %d bytes (start:%v,end:%v,skip:%d,saved:%d,nb:%d,%d,overlap:%d,%d)", ident, length, start, end, skip, saved, sgStats.Packets, sgStats.Chunks, sgStats.OverlapBytes, sgStats.OverlapPackets) if skip == -1 && *allowmissinginit { // this is allowed } else if skip != 0 { // Missing bytes in stream: do not even try to parse it return } data := sg.Fetch(length) if t.isDNS { dns := &layers.DNS{} var decoded []gopacket.LayerType if len(data) < 2 { if len(data) > 0 { sg.KeepFrom(0) } return } dnsSize := binary.BigEndian.Uint16(data[:2]) missing := int(dnsSize) - len(data[2:]) Trace("dnsSize: %d, missing: %d", dnsSize, missing) if missing > 0 { Debug("Missing some bytes: %d", missing) sg.KeepFrom(0) return } p := gopacket.NewDecodingLayerParser(layers.LayerTypeDNS, dns) err := p.DecodeLayers(data[2:], &decoded) if err != nil { SilentError("DNS-parser", "Failed to decode DNS: %v", err) } else { Trace("DNS: %s", gopacket.LayerDump(dns)) } if len(data) > 2+int(dnsSize) { sg.KeepFrom(2 + int(dnsSize)) } } else if t.isHTTP { if length > 0 { if *hexdump { Trace("Feeding http with:%s", hex.Dump(data)) } // This is where we pass the reassembled information onwards // This channel is read by an httpReader object if dir == reassembly.TCPDirClientToServer && !t.reversed { t.client.msgQueue <- httpReaderDataMsg{data, ac.GetCaptureInfo().Timestamp} } else { t.server.msgQueue <- httpReaderDataMsg{data, ac.GetCaptureInfo().Timestamp} } } } } func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool { Trace("%s: Connection closed", t.ident) if t.isHTTP { close(t.client.msgQueue) close(t.server.msgQueue) } // do not remove the connection to allow last ACK return false }