github.com/gopacket/gopacket@v1.1.0/examples/statsassembly/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 sample code for using the gopacket TCP assembler raw, 8 // without the help of the tcpreader library. It watches TCP streams and 9 // reports statistics on completed streams. 10 // 11 // It also uses gopacket.DecodingLayerParser instead of the normal 12 // gopacket.PacketSource, to highlight the methods, pros, and cons of this 13 // approach. 14 package main 15 16 import ( 17 "flag" 18 "log" 19 "time" 20 21 "github.com/gopacket/gopacket" 22 "github.com/gopacket/gopacket/examples/util" 23 "github.com/gopacket/gopacket/layers" 24 "github.com/gopacket/gopacket/pcap" 25 "github.com/gopacket/gopacket/tcpassembly" 26 ) 27 28 var iface = flag.String("i", "eth0", "Interface to get packets from") 29 var snaplen = flag.Int("s", 65536, "SnapLen for pcap packet capture") 30 var filter = flag.String("f", "tcp", "BPF filter for pcap") 31 var logAllPackets = flag.Bool("v", false, "Log whenever we see a packet") 32 var bufferedPerConnection = flag.Int("connection_max_buffer", 0, ` 33 Max packets to buffer for a single connection before skipping over a gap in data 34 and continuing to stream the connection after the buffer. If zero or less, this 35 is infinite.`) 36 var bufferedTotal = flag.Int("total_max_buffer", 0, ` 37 Max packets to buffer total before skipping over gaps in connections and 38 continuing to stream connection data. If zero or less, this is infinite`) 39 var flushAfter = flag.String("flush_after", "2m", ` 40 Connections which have buffered packets (they've gotten packets out of order and 41 are waiting for old packets to fill the gaps) are flushed after they're this old 42 (their oldest gap is skipped). Any string parsed by time.ParseDuration is 43 acceptable here`) 44 var packetCount = flag.Int("c", -1, ` 45 Quit after processing this many packets, flushing all currently buffered 46 connections. If negative, this is infinite`) 47 48 // simpleStreamFactory implements tcpassembly.StreamFactory 49 type statsStreamFactory struct{} 50 51 // statsStream will handle the actual decoding of stats requests. 52 type statsStream struct { 53 net, transport gopacket.Flow 54 bytes, packets, outOfOrder, skipped int64 55 start, end time.Time 56 sawStart, sawEnd bool 57 } 58 59 // New creates a new stream. It's called whenever the assembler sees a stream 60 // it isn't currently following. 61 func (factory *statsStreamFactory) New(net, transport gopacket.Flow) tcpassembly.Stream { 62 log.Printf("new stream %v:%v started", net, transport) 63 s := &statsStream{ 64 net: net, 65 transport: transport, 66 start: time.Now(), 67 } 68 s.end = s.start 69 // ReaderStream implements tcpassembly.Stream, so we can return a pointer to it. 70 return s 71 } 72 73 // Reassembled is called whenever new packet data is available for reading. 74 // Reassembly objects contain stream data IN ORDER. 75 func (s *statsStream) Reassembled(reassemblies []tcpassembly.Reassembly) { 76 for _, reassembly := range reassemblies { 77 if reassembly.Seen.Before(s.end) { 78 s.outOfOrder++ 79 } else { 80 s.end = reassembly.Seen 81 } 82 s.bytes += int64(len(reassembly.Bytes)) 83 s.packets += 1 84 if reassembly.Skip > 0 { 85 s.skipped += int64(reassembly.Skip) 86 } 87 s.sawStart = s.sawStart || reassembly.Start 88 s.sawEnd = s.sawEnd || reassembly.End 89 } 90 } 91 92 // ReassemblyComplete is called when the TCP assembler believes a stream has 93 // finished. 94 func (s *statsStream) ReassemblyComplete() { 95 diffSecs := float64(s.end.Sub(s.start)) / float64(time.Second) 96 log.Printf("Reassembly of stream %v:%v complete - start:%v end:%v bytes:%v packets:%v ooo:%v bps:%v pps:%v skipped:%v", 97 s.net, s.transport, s.start, s.end, s.bytes, s.packets, s.outOfOrder, 98 float64(s.bytes)/diffSecs, float64(s.packets)/diffSecs, s.skipped) 99 } 100 101 func main() { 102 defer util.Run()() 103 104 flushDuration, err := time.ParseDuration(*flushAfter) 105 if err != nil { 106 log.Fatal("invalid flush duration: ", *flushAfter) 107 } 108 109 log.Printf("starting capture on interface %q", *iface) 110 // Set up pcap packet capture 111 handle, err := pcap.OpenLive(*iface, int32(*snaplen), true, flushDuration/2) 112 if err != nil { 113 log.Fatal("error opening pcap handle: ", err) 114 } 115 if err := handle.SetBPFFilter(*filter); err != nil { 116 log.Fatal("error setting BPF filter: ", err) 117 } 118 119 // Set up assembly 120 streamFactory := &statsStreamFactory{} 121 streamPool := tcpassembly.NewStreamPool(streamFactory) 122 assembler := tcpassembly.NewAssembler(streamPool) 123 assembler.MaxBufferedPagesPerConnection = *bufferedPerConnection 124 assembler.MaxBufferedPagesTotal = *bufferedTotal 125 126 log.Println("reading in packets") 127 128 // We use a DecodingLayerParser here instead of a simpler PacketSource. 129 // This approach should be measurably faster, but is also more rigid. 130 // PacketSource will handle any known type of packet safely and easily, 131 // but DecodingLayerParser will only handle those packet types we 132 // specifically pass in. This trade-off can be quite useful, though, in 133 // high-throughput situations. 134 var eth layers.Ethernet 135 var dot1q layers.Dot1Q 136 var ip4 layers.IPv4 137 var ip6 layers.IPv6 138 var ip6extensions layers.IPv6ExtensionSkipper 139 var tcp layers.TCP 140 var payload gopacket.Payload 141 parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, 142 ð, &dot1q, &ip4, &ip6, &ip6extensions, &tcp, &payload) 143 decoded := make([]gopacket.LayerType, 0, 4) 144 145 nextFlush := time.Now().Add(flushDuration / 2) 146 147 var byteCount int64 148 start := time.Now() 149 150 loop: 151 for ; *packetCount != 0; *packetCount-- { 152 // Check to see if we should flush the streams we have 153 // that haven't seen any new data in a while. Note we set a 154 // timeout on our PCAP handle, so this should happen even if we 155 // never see packet data. 156 if time.Now().After(nextFlush) { 157 stats, _ := handle.Stats() 158 log.Printf("flushing all streams that haven't seen packets in the last 2 minutes, pcap stats: %+v", stats) 159 assembler.FlushOlderThan(time.Now().Add(flushDuration)) 160 nextFlush = time.Now().Add(flushDuration / 2) 161 } 162 163 // To speed things up, we're also using the ZeroCopy method for 164 // reading packet data. This method is faster than the normal 165 // ReadPacketData, but the returned bytes in 'data' are 166 // invalidated by any subsequent ZeroCopyReadPacketData call. 167 // Note that tcpassembly is entirely compatible with this packet 168 // reading method. This is another trade-off which might be 169 // appropriate for high-throughput sniffing: it avoids a packet 170 // copy, but its cost is much more careful handling of the 171 // resulting byte slice. 172 data, ci, err := handle.ZeroCopyReadPacketData() 173 174 if err != nil { 175 log.Printf("error getting packet: %v", err) 176 continue 177 } 178 err = parser.DecodeLayers(data, &decoded) 179 if err != nil { 180 log.Printf("error decoding packet: %v", err) 181 continue 182 } 183 if *logAllPackets { 184 log.Printf("decoded the following layers: %v", decoded) 185 } 186 byteCount += int64(len(data)) 187 // Find either the IPv4 or IPv6 address to use as our network 188 // layer. 189 foundNetLayer := false 190 var netFlow gopacket.Flow 191 for _, typ := range decoded { 192 switch typ { 193 case layers.LayerTypeIPv4: 194 netFlow = ip4.NetworkFlow() 195 foundNetLayer = true 196 case layers.LayerTypeIPv6: 197 netFlow = ip6.NetworkFlow() 198 foundNetLayer = true 199 case layers.LayerTypeTCP: 200 if foundNetLayer { 201 assembler.AssembleWithTimestamp(netFlow, &tcp, ci.Timestamp) 202 } else { 203 log.Println("could not find IPv4 or IPv6 layer, inoring") 204 } 205 continue loop 206 } 207 } 208 log.Println("could not find TCP layer") 209 } 210 assembler.FlushAll() 211 log.Printf("processed %d bytes in %v", byteCount, time.Since(start)) 212 }