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.
9package main
10
11import (
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
40var maxcount = flag.Int("c", -1, "Only grab this many packets, then exit")
41var decoder = flag.String("decoder", "", "Name of the decoder to use (default: guess from capture)")
42var statsevery = flag.Int("stats", 1000, "Output statistics every N packets")
43var lazy = flag.Bool("lazy", false, "If true, do lazy decoding")
44var nodefrag = flag.Bool("nodefrag", false, "If true, do not do IPv4 defrag")
45var checksum = flag.Bool("checksum", false, "Check TCP checksum")
46var nooptcheck = flag.Bool("nooptcheck", false, "Do not check TCP options (useful to ignore MSS on captures with TSO)")
47var ignorefsmerr = flag.Bool("ignorefsmerr", false, "Ignore TCP FSM errors")
48var allowmissinginit = flag.Bool("allowmissinginit", false, "Support streams without SYN/SYN+ACK/ACK sequence")
49var verbose = flag.Bool("verbose", false, "Be verbose")
50var debug = flag.Bool("debug", false, "Display debug information")
51var quiet = flag.Bool("quiet", false, "Be quiet regarding errors")
52
53// http
54var nohttp = flag.Bool("nohttp", false, "Disable HTTP parsing")
55var output = flag.String("output", "", "Path to create file for HTTP 200 OK responses")
56var writeincomplete = flag.Bool("writeincomplete", false, "Write incomplete response")
57
58var hexdump = flag.Bool("dump", false, "Dump HTTP request/response as hex")
59var hexdumppkt = flag.Bool("dumppkt", false, "Dump packet as hex")
60
61// capture
62var iface = flag.String("i", "eth0", "Interface to read packets from")
63var fname = flag.String("r", "", "Filename to read from, overrides -i")
64var snaplen = flag.Int("s", 65536, "Snap length (number of bytes max to read per packet")
65var tstype = flag.String("timestamp_type", "", "Type of timestamps to use")
66var promisc = flag.Bool("promisc", true, "Set promiscuous mode")
67
68var memprofile = flag.String("memprofile", "", "Write memory profile")
69
70var 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
88const closeTimeout time.Duration = time.Hour * 24 // Closing inactive: TODO: from CLI
89const timeout time.Duration = time.Minute * 5     // Pending bytes: TODO: from CLI
90
91/*
92 * HTTP part
93 */
94
95type httpReader struct {
96	ident    string
97	isClient bool
98	bytes    chan []byte
99	data     []byte
100	hexdump  bool
101	parent   *tcpStream
102}
103
104func (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
118var outputLevel int
119var errorsMap map[string]uint
120var errorsMapMutex sync.Mutex
121var errors uint
122
123// Too bad for perf that a... is evaluated
124func 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}
134func Info(s string, a ...interface{}) {
135	if outputLevel >= 1 {
136		fmt.Printf(s, a...)
137	}
138}
139func Debug(s string, a ...interface{}) {
140	if outputLevel >= 2 {
141		fmt.Printf(s, a...)
142	}
143}
144
145func (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 */
260type tcpStreamFactory struct {
261	wg     sync.WaitGroup
262	doHTTP bool
263}
264
265func (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
301func (factory *tcpStreamFactory) WaitGoRoutines() {
302	factory.wg.Wait()
303}
304
305/*
306 * The assembler context
307 */
308type Context struct {
309	CaptureInfo gopacket.CaptureInfo
310}
311
312func (c *Context) GetCaptureInfo() gopacket.CaptureInfo {
313	return c.CaptureInfo
314}
315
316/*
317 * TCP stream
318 */
319
320/* It's a connection (bidirectional) */
321type 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
336func (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
376func (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
459func (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
469func 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}
662