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  }