github.com/gopacket/gopacket@v1.1.0/examples/bidirectional/main.go (about) 1 // Copyright 2012 Google, Inc. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style license 4 // that can be found in the LICENSE file in the root of the source 5 // tree. 6 7 // This binary provides an example of connecting up bidirectional streams from 8 // the unidirectional streams provided by gopacket/tcpassembly. 9 package main 10 11 import ( 12 "flag" 13 "fmt" 14 "log" 15 "time" 16 17 "github.com/gopacket/gopacket" 18 "github.com/gopacket/gopacket/examples/util" 19 "github.com/gopacket/gopacket/layers" 20 "github.com/gopacket/gopacket/pcap" 21 "github.com/gopacket/gopacket/tcpassembly" 22 ) 23 24 var iface = flag.String("i", "eth0", "Interface to get packets from") 25 var snaplen = flag.Int("s", 16<<10, "SnapLen for pcap packet capture") 26 var filter = flag.String("f", "tcp", "BPF filter for pcap") 27 var logAllPackets = flag.Bool("v", false, "Logs every packet in great detail") 28 29 // key is used to map bidirectional streams to each other. 30 type key struct { 31 net, transport gopacket.Flow 32 } 33 34 // String prints out the key in a human-readable fashion. 35 func (k key) String() string { 36 return fmt.Sprintf("%v:%v", k.net, k.transport) 37 } 38 39 // timeout is the length of time to wait befor flushing connections and 40 // bidirectional stream pairs. 41 const timeout time.Duration = time.Minute * 5 42 43 // myStream implements tcpassembly.Stream 44 type myStream struct { 45 bytes int64 // total bytes seen on this stream. 46 bidi *bidi // maps to my bidirectional twin. 47 done bool // if true, we've seen the last packet we're going to for this stream. 48 } 49 50 // bidi stores each unidirectional side of a bidirectional stream. 51 // 52 // When a new stream comes in, if we don't have an opposite stream, a bidi is 53 // created with 'a' set to the new stream. If we DO have an opposite stream, 54 // 'b' is set to the new stream. 55 type bidi struct { 56 key key // Key of the first stream, mostly for logging. 57 a, b *myStream // the two bidirectional streams. 58 lastPacketSeen time.Time // last time we saw a packet from either stream. 59 } 60 61 // myFactory implements tcpassmebly.StreamFactory 62 type myFactory struct { 63 // bidiMap maps keys to bidirectional stream pairs. 64 bidiMap map[key]*bidi 65 } 66 67 // New handles creating a new tcpassembly.Stream. 68 func (f *myFactory) New(netFlow, tcpFlow gopacket.Flow) tcpassembly.Stream { 69 // Create a new stream. 70 s := &myStream{} 71 72 // Find the bidi bidirectional struct for this stream, creating a new one if 73 // one doesn't already exist in the map. 74 k := key{netFlow, tcpFlow} 75 bd := f.bidiMap[k] 76 if bd == nil { 77 bd = &bidi{a: s, key: k} 78 log.Printf("[%v] created first side of bidirectional stream", bd.key) 79 // Register bidirectional with the reverse key, so the matching stream going 80 // the other direction will find it. 81 f.bidiMap[key{netFlow.Reverse(), tcpFlow.Reverse()}] = bd 82 } else { 83 log.Printf("[%v] found second side of bidirectional stream", bd.key) 84 bd.b = s 85 // Clear out the bidi we're using from the map, just in case. 86 delete(f.bidiMap, k) 87 } 88 s.bidi = bd 89 return s 90 } 91 92 // emptyStream is used to finish bidi that only have one stream, in 93 // collectOldStreams. 94 var emptyStream = &myStream{done: true} 95 96 // collectOldStreams finds any streams that haven't received a packet within 97 // 'timeout', and sets/finishes the 'b' stream inside them. The 'a' stream may 98 // still receive packets after this. 99 func (f *myFactory) collectOldStreams() { 100 cutoff := time.Now().Add(-timeout) 101 for k, bd := range f.bidiMap { 102 if bd.lastPacketSeen.Before(cutoff) { 103 log.Printf("[%v] timing out old stream", bd.key) 104 bd.b = emptyStream // stub out b with an empty stream. 105 delete(f.bidiMap, k) // remove it from our map. 106 bd.maybeFinish() // if b was the last stream we were waiting for, finish up. 107 } 108 } 109 } 110 111 // Reassembled handles reassembled TCP stream data. 112 func (s *myStream) Reassembled(rs []tcpassembly.Reassembly) { 113 for _, r := range rs { 114 // For now, we'll simply count the bytes on each side of the TCP stream. 115 s.bytes += int64(len(r.Bytes)) 116 if r.Skip > 0 { 117 s.bytes += int64(r.Skip) 118 } 119 // Mark that we've received new packet data. 120 // We could just use time.Now, but by using r.Seen we handle the case 121 // where packets are being read from a file and could be very old. 122 if s.bidi.lastPacketSeen.Before(r.Seen) { 123 s.bidi.lastPacketSeen = r.Seen 124 } 125 } 126 } 127 128 // ReassemblyComplete marks this stream as finished. 129 func (s *myStream) ReassemblyComplete() { 130 s.done = true 131 s.bidi.maybeFinish() 132 } 133 134 // maybeFinish will wait until both directions are complete, then print out 135 // stats. 136 func (bd *bidi) maybeFinish() { 137 switch { 138 case bd.a == nil: 139 log.Fatalf("[%v] a should always be non-nil, since it's set when bidis are created", bd.key) 140 case !bd.a.done: 141 log.Printf("[%v] still waiting on first stream", bd.key) 142 case bd.b == nil: 143 log.Printf("[%v] no second stream yet", bd.key) 144 case !bd.b.done: 145 log.Printf("[%v] still waiting on second stream", bd.key) 146 default: 147 log.Printf("[%v] FINISHED, bytes: %d tx, %d rx", bd.key, bd.a.bytes, bd.b.bytes) 148 } 149 } 150 151 func main() { 152 defer util.Run()() 153 log.Printf("starting capture on interface %q", *iface) 154 // Set up pcap packet capture 155 handle, err := pcap.OpenLive(*iface, int32(*snaplen), true, pcap.BlockForever) 156 if err != nil { 157 panic(err) 158 } 159 if err := handle.SetBPFFilter(*filter); err != nil { 160 panic(err) 161 } 162 163 // Set up assembly 164 streamFactory := &myFactory{bidiMap: make(map[key]*bidi)} 165 streamPool := tcpassembly.NewStreamPool(streamFactory) 166 assembler := tcpassembly.NewAssembler(streamPool) 167 // Limit memory usage by auto-flushing connection state if we get over 100K 168 // packets in memory, or over 1000 for a single stream. 169 assembler.MaxBufferedPagesTotal = 100000 170 assembler.MaxBufferedPagesPerConnection = 1000 171 172 log.Println("reading in packets") 173 // Read in packets, pass to assembler. 174 packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) 175 packets := packetSource.Packets() 176 ticker := time.Tick(timeout / 4) 177 for { 178 select { 179 case packet := <-packets: 180 if *logAllPackets { 181 log.Println(packet) 182 } 183 if packet.NetworkLayer() == nil || packet.TransportLayer() == nil || packet.TransportLayer().LayerType() != layers.LayerTypeTCP { 184 log.Println("Unusable packet") 185 continue 186 } 187 tcp := packet.TransportLayer().(*layers.TCP) 188 assembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp) 189 190 case <-ticker: 191 // Every minute, flush connections that haven't seen activity in the past minute. 192 log.Println("---- FLUSHING ----") 193 assembler.FlushOlderThan(time.Now().Add(-timeout)) 194 streamFactory.collectOldStreams() 195 } 196 } 197 }