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