github.com/gopacket/gopacket@v1.1.0/examples/reassemblydump/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  // The pcapdump binary implements a tcpdump-like command line tool with gopacket
     8  // using pcap as a backend data collection mechanism.
     9  package main
    10  
    11  import (
    12  	"bufio"
    13  	"bytes"
    14  	"compress/gzip"
    15  	"encoding/binary"
    16  	"encoding/hex"
    17  	"flag"
    18  	"fmt"
    19  	"io"
    20  	"io/ioutil"
    21  	"log"
    22  	"net/http"
    23  	"net/url"
    24  	"os"
    25  	"os/signal"
    26  	"path"
    27  	"runtime/pprof"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/gopacket/gopacket"
    33  	"github.com/gopacket/gopacket/examples/util"
    34  	"github.com/gopacket/gopacket/ip4defrag"
    35  	"github.com/gopacket/gopacket/layers" // pulls in all layers decoders
    36  	"github.com/gopacket/gopacket/pcap"
    37  	"github.com/gopacket/gopacket/reassembly"
    38  )
    39  
    40  var maxcount = flag.Int("c", -1, "Only grab this many packets, then exit")
    41  var decoder = flag.String("decoder", "", "Name of the decoder to use (default: guess from capture)")
    42  var statsevery = flag.Int("stats", 1000, "Output statistics every N packets")
    43  var lazy = flag.Bool("lazy", false, "If true, do lazy decoding")
    44  var nodefrag = flag.Bool("nodefrag", false, "If true, do not do IPv4 defrag")
    45  var checksum = flag.Bool("checksum", false, "Check TCP checksum")
    46  var nooptcheck = flag.Bool("nooptcheck", false, "Do not check TCP options (useful to ignore MSS on captures with TSO)")
    47  var ignorefsmerr = flag.Bool("ignorefsmerr", false, "Ignore TCP FSM errors")
    48  var allowmissinginit = flag.Bool("allowmissinginit", false, "Support streams without SYN/SYN+ACK/ACK sequence")
    49  var verbose = flag.Bool("verbose", false, "Be verbose")
    50  var debug = flag.Bool("debug", false, "Display debug information")
    51  var quiet = flag.Bool("quiet", false, "Be quiet regarding errors")
    52  
    53  // http
    54  var nohttp = flag.Bool("nohttp", false, "Disable HTTP parsing")
    55  var output = flag.String("output", "", "Path to create file for HTTP 200 OK responses")
    56  var writeincomplete = flag.Bool("writeincomplete", false, "Write incomplete response")
    57  
    58  var hexdump = flag.Bool("dump", false, "Dump HTTP request/response as hex")
    59  var hexdumppkt = flag.Bool("dumppkt", false, "Dump packet as hex")
    60  
    61  // capture
    62  var iface = flag.String("i", "eth0", "Interface to read packets from")
    63  var fname = flag.String("r", "", "Filename to read from, overrides -i")
    64  var snaplen = flag.Int("s", 65536, "Snap length (number of bytes max to read per packet")
    65  var tstype = flag.String("timestamp_type", "", "Type of timestamps to use")
    66  var promisc = flag.Bool("promisc", true, "Set promiscuous mode")
    67  
    68  var memprofile = flag.String("memprofile", "", "Write memory profile")
    69  
    70  var stats struct {
    71  	ipdefrag            int
    72  	missedBytes         int
    73  	pkt                 int
    74  	sz                  int
    75  	totalsz             int
    76  	rejectFsm           int
    77  	rejectOpt           int
    78  	rejectConnFsm       int
    79  	reassembled         int
    80  	outOfOrderBytes     int
    81  	outOfOrderPackets   int
    82  	biggestChunkBytes   int
    83  	biggestChunkPackets int
    84  	overlapBytes        int
    85  	overlapPackets      int
    86  }
    87  
    88  const closeTimeout time.Duration = time.Hour * 24 // Closing inactive: TODO: from CLI
    89  const timeout time.Duration = time.Minute * 5     // Pending bytes: TODO: from CLI
    90  
    91  /*
    92   * HTTP part
    93   */
    94  
    95  type httpReader struct {
    96  	ident    string
    97  	isClient bool
    98  	bytes    chan []byte
    99  	data     []byte
   100  	hexdump  bool
   101  	parent   *tcpStream
   102  }
   103  
   104  func (h *httpReader) Read(p []byte) (int, error) {
   105  	ok := true
   106  	for ok && len(h.data) == 0 {
   107  		h.data, ok = <-h.bytes
   108  	}
   109  	if !ok || len(h.data) == 0 {
   110  		return 0, io.EOF
   111  	}
   112  
   113  	l := copy(p, h.data)
   114  	h.data = h.data[l:]
   115  	return l, nil
   116  }
   117  
   118  var outputLevel int
   119  var errorsMap map[string]uint
   120  var errorsMapMutex sync.Mutex
   121  var errors uint
   122  
   123  // Too bad for perf that a... is evaluated
   124  func Error(t string, s string, a ...interface{}) {
   125  	errorsMapMutex.Lock()
   126  	errors++
   127  	nb, _ := errorsMap[t]
   128  	errorsMap[t] = nb + 1
   129  	errorsMapMutex.Unlock()
   130  	if outputLevel >= 0 {
   131  		fmt.Printf(s, a...)
   132  	}
   133  }
   134  func Info(s string, a ...interface{}) {
   135  	if outputLevel >= 1 {
   136  		fmt.Printf(s, a...)
   137  	}
   138  }
   139  func Debug(s string, a ...interface{}) {
   140  	if outputLevel >= 2 {
   141  		fmt.Printf(s, a...)
   142  	}
   143  }
   144  
   145  func (h *httpReader) run(wg *sync.WaitGroup) {
   146  	defer wg.Done()
   147  	b := bufio.NewReader(h)
   148  	for true {
   149  		if h.isClient {
   150  			req, err := http.ReadRequest(b)
   151  			if err == io.EOF || err == io.ErrUnexpectedEOF {
   152  				break
   153  			} else if err != nil {
   154  				Error("HTTP-request", "HTTP/%s Request error: %s (%v,%+v)\n", h.ident, err, err, err)
   155  				continue
   156  			}
   157  			body, err := ioutil.ReadAll(req.Body)
   158  			s := len(body)
   159  			if err != nil {
   160  				Error("HTTP-request-body", "Got body err: %s\n", err)
   161  			} else if h.hexdump {
   162  				Info("Body(%d/0x%x)\n%s\n", len(body), len(body), hex.Dump(body))
   163  			}
   164  			req.Body.Close()
   165  			Info("HTTP/%s Request: %s %s (body:%d)\n", h.ident, req.Method, req.URL, s)
   166  			h.parent.Lock()
   167  			h.parent.urls = append(h.parent.urls, req.URL.String())
   168  			h.parent.Unlock()
   169  		} else {
   170  			res, err := http.ReadResponse(b, nil)
   171  			var req string
   172  			h.parent.Lock()
   173  			if len(h.parent.urls) == 0 {
   174  				req = fmt.Sprintf("<no-request-seen>")
   175  			} else {
   176  				req, h.parent.urls = h.parent.urls[0], h.parent.urls[1:]
   177  			}
   178  			h.parent.Unlock()
   179  			if err == io.EOF || err == io.ErrUnexpectedEOF {
   180  				break
   181  			} else if err != nil {
   182  				Error("HTTP-response", "HTTP/%s Response error: %s (%v,%+v)\n", h.ident, err, err, err)
   183  				continue
   184  			}
   185  			body, err := ioutil.ReadAll(res.Body)
   186  			s := len(body)
   187  			if err != nil {
   188  				Error("HTTP-response-body", "HTTP/%s: failed to get body(parsed len:%d): %s\n", h.ident, s, err)
   189  			}
   190  			if h.hexdump {
   191  				Info("Body(%d/0x%x)\n%s\n", len(body), len(body), hex.Dump(body))
   192  			}
   193  			res.Body.Close()
   194  			sym := ","
   195  			if res.ContentLength > 0 && res.ContentLength != int64(s) {
   196  				sym = "!="
   197  			}
   198  			contentType, ok := res.Header["Content-Type"]
   199  			if !ok {
   200  				contentType = []string{http.DetectContentType(body)}
   201  			}
   202  			encoding := res.Header["Content-Encoding"]
   203  			Info("HTTP/%s Response: %s URL:%s (%d%s%d%s) -> %s\n", h.ident, res.Status, req, res.ContentLength, sym, s, contentType, encoding)
   204  			if (err == nil || *writeincomplete) && *output != "" {
   205  				base := url.QueryEscape(path.Base(req))
   206  				if err != nil {
   207  					base = "incomplete-" + base
   208  				}
   209  				base = path.Join(*output, base)
   210  				if len(base) > 250 {
   211  					base = base[:250] + "..."
   212  				}
   213  				if base == *output {
   214  					base = path.Join(*output, "noname")
   215  				}
   216  				target := base
   217  				n := 0
   218  				for true {
   219  					_, err := os.Stat(target)
   220  					//if os.IsNotExist(err) != nil {
   221  					if err != nil {
   222  						break
   223  					}
   224  					target = fmt.Sprintf("%s-%d", base, n)
   225  					n++
   226  				}
   227  				f, err := os.Create(target)
   228  				if err != nil {
   229  					Error("HTTP-create", "Cannot create %s: %s\n", target, err)
   230  					continue
   231  				}
   232  				var r io.Reader
   233  				r = bytes.NewBuffer(body)
   234  				if len(encoding) > 0 && (encoding[0] == "gzip" || encoding[0] == "deflate") {
   235  					r, err = gzip.NewReader(r)
   236  					if err != nil {
   237  						Error("HTTP-gunzip", "Failed to gzip decode: %s", err)
   238  					}
   239  				}
   240  				if err == nil {
   241  					w, err := io.Copy(f, r)
   242  					if _, ok := r.(*gzip.Reader); ok {
   243  						r.(*gzip.Reader).Close()
   244  					}
   245  					f.Close()
   246  					if err != nil {
   247  						Error("HTTP-save", "%s: failed to save %s (l:%d): %s\n", h.ident, target, w, err)
   248  					} else {
   249  						Info("%s: Saved %s (l:%d)\n", h.ident, target, w)
   250  					}
   251  				}
   252  			}
   253  		}
   254  	}
   255  }
   256  
   257  /*
   258   * The TCP factory: returns a new Stream
   259   */
   260  type tcpStreamFactory struct {
   261  	wg     sync.WaitGroup
   262  	doHTTP bool
   263  }
   264  
   265  func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream {
   266  	Debug("* NEW: %s %s\n", net, transport)
   267  	fsmOptions := reassembly.TCPSimpleFSMOptions{
   268  		SupportMissingEstablishment: *allowmissinginit,
   269  	}
   270  	stream := &tcpStream{
   271  		net:        net,
   272  		transport:  transport,
   273  		isDNS:      tcp.SrcPort == 53 || tcp.DstPort == 53,
   274  		isHTTP:     (tcp.SrcPort == 80 || tcp.DstPort == 80) && factory.doHTTP,
   275  		reversed:   tcp.SrcPort == 80,
   276  		tcpstate:   reassembly.NewTCPSimpleFSM(fsmOptions),
   277  		ident:      fmt.Sprintf("%s:%s", net, transport),
   278  		optchecker: reassembly.NewTCPOptionCheck(),
   279  	}
   280  	if stream.isHTTP {
   281  		stream.client = httpReader{
   282  			bytes:    make(chan []byte),
   283  			ident:    fmt.Sprintf("%s %s", net, transport),
   284  			hexdump:  *hexdump,
   285  			parent:   stream,
   286  			isClient: true,
   287  		}
   288  		stream.server = httpReader{
   289  			bytes:   make(chan []byte),
   290  			ident:   fmt.Sprintf("%s %s", net.Reverse(), transport.Reverse()),
   291  			hexdump: *hexdump,
   292  			parent:  stream,
   293  		}
   294  		factory.wg.Add(2)
   295  		go stream.client.run(&factory.wg)
   296  		go stream.server.run(&factory.wg)
   297  	}
   298  	return stream
   299  }
   300  
   301  func (factory *tcpStreamFactory) WaitGoRoutines() {
   302  	factory.wg.Wait()
   303  }
   304  
   305  /*
   306   * The assembler context
   307   */
   308  type Context struct {
   309  	CaptureInfo gopacket.CaptureInfo
   310  }
   311  
   312  func (c *Context) GetCaptureInfo() gopacket.CaptureInfo {
   313  	return c.CaptureInfo
   314  }
   315  
   316  /*
   317   * TCP stream
   318   */
   319  
   320  /* It's a connection (bidirectional) */
   321  type tcpStream struct {
   322  	tcpstate       *reassembly.TCPSimpleFSM
   323  	fsmerr         bool
   324  	optchecker     reassembly.TCPOptionCheck
   325  	net, transport gopacket.Flow
   326  	isDNS          bool
   327  	isHTTP         bool
   328  	reversed       bool
   329  	client         httpReader
   330  	server         httpReader
   331  	urls           []string
   332  	ident          string
   333  	sync.Mutex
   334  }
   335  
   336  func (t *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, nextSeq reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool {
   337  	// FSM
   338  	if !t.tcpstate.CheckState(tcp, dir) {
   339  		Error("FSM", "%s: Packet rejected by FSM (state:%s)\n", t.ident, t.tcpstate.String())
   340  		stats.rejectFsm++
   341  		if !t.fsmerr {
   342  			t.fsmerr = true
   343  			stats.rejectConnFsm++
   344  		}
   345  		if !*ignorefsmerr {
   346  			return false
   347  		}
   348  	}
   349  	// Options
   350  	err := t.optchecker.Accept(tcp, ci, dir, nextSeq, start)
   351  	if err != nil {
   352  		Error("OptionChecker", "%s: Packet rejected by OptionChecker: %s\n", t.ident, err)
   353  		stats.rejectOpt++
   354  		if !*nooptcheck {
   355  			return false
   356  		}
   357  	}
   358  	// Checksum
   359  	accept := true
   360  	if *checksum {
   361  		c, err := tcp.ComputeChecksum()
   362  		if err != nil {
   363  			Error("ChecksumCompute", "%s: Got error computing checksum: %s\n", t.ident, err)
   364  			accept = false
   365  		} else if c != 0x0 {
   366  			Error("Checksum", "%s: Invalid checksum: 0x%x\n", t.ident, c)
   367  			accept = false
   368  		}
   369  	}
   370  	if !accept {
   371  		stats.rejectOpt++
   372  	}
   373  	return accept
   374  }
   375  
   376  func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) {
   377  	dir, start, end, skip := sg.Info()
   378  	length, saved := sg.Lengths()
   379  	// update stats
   380  	sgStats := sg.Stats()
   381  	if skip > 0 {
   382  		stats.missedBytes += skip
   383  	}
   384  	stats.sz += length - saved
   385  	stats.pkt += sgStats.Packets
   386  	if sgStats.Chunks > 1 {
   387  		stats.reassembled++
   388  	}
   389  	stats.outOfOrderPackets += sgStats.QueuedPackets
   390  	stats.outOfOrderBytes += sgStats.QueuedBytes
   391  	if length > stats.biggestChunkBytes {
   392  		stats.biggestChunkBytes = length
   393  	}
   394  	if sgStats.Packets > stats.biggestChunkPackets {
   395  		stats.biggestChunkPackets = sgStats.Packets
   396  	}
   397  	if sgStats.OverlapBytes != 0 && sgStats.OverlapPackets == 0 {
   398  		fmt.Printf("bytes:%d, pkts:%d\n", sgStats.OverlapBytes, sgStats.OverlapPackets)
   399  		panic("Invalid overlap")
   400  	}
   401  	stats.overlapBytes += sgStats.OverlapBytes
   402  	stats.overlapPackets += sgStats.OverlapPackets
   403  
   404  	var ident string
   405  	if dir == reassembly.TCPDirClientToServer {
   406  		ident = fmt.Sprintf("%v %v(%s): ", t.net, t.transport, dir)
   407  	} else {
   408  		ident = fmt.Sprintf("%v %v(%s): ", t.net.Reverse(), t.transport.Reverse(), dir)
   409  	}
   410  	Debug("%s: SG reassembled packet with %d bytes (start:%v,end:%v,skip:%d,saved:%d,nb:%d,%d,overlap:%d,%d)\n", ident, length, start, end, skip, saved, sgStats.Packets, sgStats.Chunks, sgStats.OverlapBytes, sgStats.OverlapPackets)
   411  	if skip == -1 && *allowmissinginit {
   412  		// this is allowed
   413  	} else if skip != 0 {
   414  		// Missing bytes in stream: do not even try to parse it
   415  		return
   416  	}
   417  	data := sg.Fetch(length)
   418  	if t.isDNS {
   419  		dns := &layers.DNS{}
   420  		var decoded []gopacket.LayerType
   421  		if len(data) < 2 {
   422  			if len(data) > 0 {
   423  				sg.KeepFrom(0)
   424  			}
   425  			return
   426  		}
   427  		dnsSize := binary.BigEndian.Uint16(data[:2])
   428  		missing := int(dnsSize) - len(data[2:])
   429  		Debug("dnsSize: %d, missing: %d\n", dnsSize, missing)
   430  		if missing > 0 {
   431  			Info("Missing some bytes: %d\n", missing)
   432  			sg.KeepFrom(0)
   433  			return
   434  		}
   435  		p := gopacket.NewDecodingLayerParser(layers.LayerTypeDNS, dns)
   436  		err := p.DecodeLayers(data[2:], &decoded)
   437  		if err != nil {
   438  			Error("DNS-parser", "Failed to decode DNS: %v\n", err)
   439  		} else {
   440  			Debug("DNS: %s\n", gopacket.LayerDump(dns))
   441  		}
   442  		if len(data) > 2+int(dnsSize) {
   443  			sg.KeepFrom(2 + int(dnsSize))
   444  		}
   445  	} else if t.isHTTP {
   446  		if length > 0 {
   447  			if *hexdump {
   448  				Debug("Feeding http with:\n%s", hex.Dump(data))
   449  			}
   450  			if dir == reassembly.TCPDirClientToServer && !t.reversed {
   451  				t.client.bytes <- data
   452  			} else {
   453  				t.server.bytes <- data
   454  			}
   455  		}
   456  	}
   457  }
   458  
   459  func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
   460  	Debug("%s: Connection closed\n", t.ident)
   461  	if t.isHTTP {
   462  		close(t.client.bytes)
   463  		close(t.server.bytes)
   464  	}
   465  	// do not remove the connection to allow last ACK
   466  	return false
   467  }
   468  
   469  func main() {
   470  	defer util.Run()()
   471  	var handle *pcap.Handle
   472  	var err error
   473  	if *debug {
   474  		outputLevel = 2
   475  	} else if *verbose {
   476  		outputLevel = 1
   477  	} else if *quiet {
   478  		outputLevel = -1
   479  	}
   480  	errorsMap = make(map[string]uint)
   481  	if *fname != "" {
   482  		if handle, err = pcap.OpenOffline(*fname); err != nil {
   483  			log.Fatal("PCAP OpenOffline error:", err)
   484  		}
   485  	} else {
   486  		// This is a little complicated because we want to allow all possible options
   487  		// for creating the packet capture handle... instead of all this you can
   488  		// just call pcap.OpenLive if you want a simple handle.
   489  		inactive, err := pcap.NewInactiveHandle(*iface)
   490  		if err != nil {
   491  			log.Fatalf("could not create: %v", err)
   492  		}
   493  		defer inactive.CleanUp()
   494  		if err = inactive.SetSnapLen(*snaplen); err != nil {
   495  			log.Fatalf("could not set snap length: %v", err)
   496  		} else if err = inactive.SetPromisc(*promisc); err != nil {
   497  			log.Fatalf("could not set promisc mode: %v", err)
   498  		} else if err = inactive.SetTimeout(time.Second); err != nil {
   499  			log.Fatalf("could not set timeout: %v", err)
   500  		}
   501  		if *tstype != "" {
   502  			if t, err := pcap.TimestampSourceFromString(*tstype); err != nil {
   503  				log.Fatalf("Supported timestamp types: %v", inactive.SupportedTimestamps())
   504  			} else if err := inactive.SetTimestampSource(t); err != nil {
   505  				log.Fatalf("Supported timestamp types: %v", inactive.SupportedTimestamps())
   506  			}
   507  		}
   508  		if handle, err = inactive.Activate(); err != nil {
   509  			log.Fatal("PCAP Activate error:", err)
   510  		}
   511  		defer handle.Close()
   512  	}
   513  	if len(flag.Args()) > 0 {
   514  		bpffilter := strings.Join(flag.Args(), " ")
   515  		Info("Using BPF filter %q\n", bpffilter)
   516  		if err = handle.SetBPFFilter(bpffilter); err != nil {
   517  			log.Fatal("BPF filter error:", err)
   518  		}
   519  	}
   520  
   521  	var dec gopacket.Decoder
   522  	var ok bool
   523  	decoder_name := *decoder
   524  	if decoder_name == "" {
   525  		decoder_name = fmt.Sprintf("%s", handle.LinkType())
   526  	}
   527  	if dec, ok = gopacket.DecodersByLayerName[decoder_name]; !ok {
   528  		log.Fatalln("No decoder named", decoder_name)
   529  	}
   530  	source := gopacket.NewPacketSource(handle, dec)
   531  	source.Lazy = *lazy
   532  	source.NoCopy = true
   533  	Info("Starting to read packets\n")
   534  	count := 0
   535  	bytes := int64(0)
   536  	start := time.Now()
   537  	defragger := ip4defrag.NewIPv4Defragmenter()
   538  
   539  	streamFactory := &tcpStreamFactory{doHTTP: !*nohttp}
   540  	streamPool := reassembly.NewStreamPool(streamFactory)
   541  	assembler := reassembly.NewAssembler(streamPool)
   542  
   543  	signalChan := make(chan os.Signal, 1)
   544  	signal.Notify(signalChan, os.Interrupt)
   545  
   546  	for packet := range source.Packets() {
   547  		count++
   548  		Debug("PACKET #%d\n", count)
   549  		data := packet.Data()
   550  		bytes += int64(len(data))
   551  		if *hexdumppkt {
   552  			Debug("Packet content (%d/0x%x)\n%s\n", len(data), len(data), hex.Dump(data))
   553  		}
   554  
   555  		// defrag the IPv4 packet if required
   556  		if !*nodefrag {
   557  			ip4Layer := packet.Layer(layers.LayerTypeIPv4)
   558  			if ip4Layer == nil {
   559  				continue
   560  			}
   561  			ip4 := ip4Layer.(*layers.IPv4)
   562  			l := ip4.Length
   563  			newip4, err := defragger.DefragIPv4(ip4)
   564  			if err != nil {
   565  				log.Fatalln("Error while de-fragmenting", err)
   566  			} else if newip4 == nil {
   567  				Debug("Fragment...\n")
   568  				continue // packet fragment, we don't have whole packet yet.
   569  			}
   570  			if newip4.Length != l {
   571  				stats.ipdefrag++
   572  				Debug("Decoding re-assembled packet: %s\n", newip4.NextLayerType())
   573  				pb, ok := packet.(gopacket.PacketBuilder)
   574  				if !ok {
   575  					panic("Not a PacketBuilder")
   576  				}
   577  				nextDecoder := newip4.NextLayerType()
   578  				nextDecoder.Decode(newip4.Payload, pb)
   579  			}
   580  		}
   581  
   582  		tcp := packet.Layer(layers.LayerTypeTCP)
   583  		if tcp != nil {
   584  			tcp := tcp.(*layers.TCP)
   585  			if *checksum {
   586  				err := tcp.SetNetworkLayerForChecksum(packet.NetworkLayer())
   587  				if err != nil {
   588  					log.Fatalf("Failed to set network layer for checksum: %s\n", err)
   589  				}
   590  			}
   591  			c := Context{
   592  				CaptureInfo: packet.Metadata().CaptureInfo,
   593  			}
   594  			stats.totalsz += len(tcp.Payload)
   595  			assembler.AssembleWithContext(packet.NetworkLayer().NetworkFlow(), tcp, &c)
   596  		}
   597  		if count%*statsevery == 0 {
   598  			ref := packet.Metadata().CaptureInfo.Timestamp
   599  			flushed, closed := assembler.FlushWithOptions(reassembly.FlushOptions{T: ref.Add(-timeout), TC: ref.Add(-closeTimeout)})
   600  			Debug("Forced flush: %d flushed, %d closed (%s)", flushed, closed, ref)
   601  		}
   602  
   603  		done := *maxcount > 0 && count >= *maxcount
   604  		if count%*statsevery == 0 || done {
   605  			errorsMapMutex.Lock()
   606  			errorMapLen := len(errorsMap)
   607  			errorsMapMutex.Unlock()
   608  			fmt.Fprintf(os.Stderr, "Processed %v packets (%v bytes) in %v (errors: %v, errTypes:%v)\n", count, bytes, time.Since(start), errors, errorMapLen)
   609  		}
   610  		select {
   611  		case <-signalChan:
   612  			fmt.Fprintf(os.Stderr, "\nCaught SIGINT: aborting\n")
   613  			done = true
   614  		default:
   615  			// NOP: continue
   616  		}
   617  		if done {
   618  			break
   619  		}
   620  	}
   621  
   622  	closed := assembler.FlushAll()
   623  	Debug("Final flush: %d closed", closed)
   624  	if outputLevel >= 2 {
   625  		streamPool.Dump()
   626  	}
   627  
   628  	if *memprofile != "" {
   629  		f, err := os.Create(*memprofile)
   630  		if err != nil {
   631  			log.Fatal(err)
   632  		}
   633  		pprof.WriteHeapProfile(f)
   634  		f.Close()
   635  	}
   636  
   637  	streamFactory.WaitGoRoutines()
   638  	Debug("%s\n", assembler.Dump())
   639  	if !*nodefrag {
   640  		fmt.Printf("IPdefrag:\t\t%d\n", stats.ipdefrag)
   641  	}
   642  	fmt.Printf("TCP stats:\n")
   643  	fmt.Printf(" missed bytes:\t\t%d\n", stats.missedBytes)
   644  	fmt.Printf(" total packets:\t\t%d\n", stats.pkt)
   645  	fmt.Printf(" rejected FSM:\t\t%d\n", stats.rejectFsm)
   646  	fmt.Printf(" rejected Options:\t%d\n", stats.rejectOpt)
   647  	fmt.Printf(" reassembled bytes:\t%d\n", stats.sz)
   648  	fmt.Printf(" total TCP bytes:\t%d\n", stats.totalsz)
   649  	fmt.Printf(" conn rejected FSM:\t%d\n", stats.rejectConnFsm)
   650  	fmt.Printf(" reassembled chunks:\t%d\n", stats.reassembled)
   651  	fmt.Printf(" out-of-order packets:\t%d\n", stats.outOfOrderPackets)
   652  	fmt.Printf(" out-of-order bytes:\t%d\n", stats.outOfOrderBytes)
   653  	fmt.Printf(" biggest-chunk packets:\t%d\n", stats.biggestChunkPackets)
   654  	fmt.Printf(" biggest-chunk bytes:\t%d\n", stats.biggestChunkBytes)
   655  	fmt.Printf(" overlap packets:\t%d\n", stats.overlapPackets)
   656  	fmt.Printf(" overlap bytes:\t\t%d\n", stats.overlapBytes)
   657  	fmt.Printf("Errors: %d\n", errors)
   658  	for e, _ := range errorsMap {
   659  		fmt.Printf(" %s:\t\t%d\n", e, errorsMap[e])
   660  	}
   661  }