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  		&eth, &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  }