1 // Copyright 2012 Google, Inc. All rights reserved.
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
7 // The pcapdump binary implements a tcpdump-like command line tool with gopacket
8 // using pcap as a backend data collection mechanism.
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"
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")
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")
58 var hexdump = flag.Bool("dump", false, "Dump HTTP request/response as hex")
59 var hexdumppkt = flag.Bool("dumppkt", false, "Dump packet as hex")
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")
68 var memprofile = flag.String("memprofile", "", "Write memory profile")
83 biggestChunkPackets int
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
95 type httpReader struct {
104 func (h *httpReader) Read(p []byte) (int, error) {
106 for ok && len(h.data) == 0 {
107 h.data, ok = <-h.bytes
109 if !ok || len(h.data) == 0 {
119 var errorsMap map[string]uint
122 // Too bad for perf that a... is evaluated
123 func Error(t string, s string, a ...interface{}) {
125 nb, _ := errorsMap[t]
126 errorsMap[t] = nb + 1
127 if outputLevel >= 0 {
131 func Info(s string, a ...interface{}) {
132 if outputLevel >= 1 {
136 func Debug(s string, a ...interface{}) {
137 if outputLevel >= 2 {
142 func (h *httpReader) run(wg *sync.WaitGroup) {
144 b := bufio.NewReader(h)
147 req, err := http.ReadRequest(b)
148 if err == io.EOF || err == io.ErrUnexpectedEOF {
150 } else if err != nil {
151 Error("HTTP-request", "HTTP/%s Request error: %s (%v,%+v)\n", h.ident, err, err, err)
154 body, err := ioutil.ReadAll(req.Body)
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))
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())
165 res, err := http.ReadResponse(b, nil)
167 if len(h.parent.urls) == 0 {
168 req = fmt.Sprintf("<no-request-seen>")
170 req, h.parent.urls = h.parent.urls[0], h.parent.urls[1:]
172 if err == io.EOF || err == io.ErrUnexpectedEOF {
174 } else if err != nil {
175 Error("HTTP-response", "HTTP/%s Response error: %s (%v,%+v)\n", h.ident, err, err, err)
178 body, err := ioutil.ReadAll(res.Body)
181 Error("HTTP-response-body", "HTTP/%s: failed to get body(parsed len:%d): %s\n", h.ident, s, err)
184 Info("Body(%d/0x%x)\n%s\n", len(body), len(body), hex.Dump(body))
188 if res.ContentLength > 0 && res.ContentLength != int64(s) {
191 contentType, ok := res.Header["Content-Type"]
193 contentType = []string{http.DetectContentType(body)}
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))
200 base = "incomplete-" + base
202 base = path.Join(*output, base)
204 base = base[:250] + "..."
207 base = path.Join(*output, "noname")
212 _, err := os.Stat(target)
213 //if os.IsNotExist(err) != nil {
217 target = fmt.Sprintf("%s-%d", base, n)
220 f, err := os.Create(target)
222 Error("HTTP-create", "Cannot create %s: %s\n", target, err)
226 r = bytes.NewBuffer(body)
227 if len(encoding) > 0 && (encoding[0] == "gzip" || encoding[0] == "deflate") {
228 r, err = gzip.NewReader(r)
230 Error("HTTP-gunzip", "Failed to gzip decode: %s", err)
234 w, err := io.Copy(f, r)
235 if _, ok := r.(*gzip.Reader); ok {
236 r.(*gzip.Reader).Close()
240 Error("HTTP-save", "%s: failed to save %s (l:%d): %s\n", h.ident, target, w, err)
242 Info("%s: Saved %s (l:%d)\n", h.ident, target, w)
251 * The TCP factory: returns a new Stream
253 type tcpStreamFactory struct {
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,
263 stream := &tcpStream{
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(),
274 stream.client = httpReader{
275 bytes: make(chan []byte),
276 ident: fmt.Sprintf("%s %s", net, transport),
281 stream.server = httpReader{
282 bytes: make(chan []byte),
283 ident: fmt.Sprintf("%s %s", net.Reverse(), transport.Reverse()),
288 go stream.client.run(&factory.wg)
289 go stream.server.run(&factory.wg)
294 func (factory *tcpStreamFactory) WaitGoRoutines() {
299 * The assembler context
301 type Context struct {
302 CaptureInfo gopacket.CaptureInfo
305 func (c *Context) GetCaptureInfo() gopacket.CaptureInfo {
313 /* It's a connection (bidirectional) */
314 type tcpStream struct {
315 tcpstate *reassembly.TCPSimpleFSM
317 optchecker reassembly.TCPOptionCheck
318 net, transport gopacket.Flow
328 func (t *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, acked reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool {
330 if !t.tcpstate.CheckState(tcp, dir) {
331 Error("FSM", "%s: Packet rejected by FSM (state:%s)\n", t.ident, t.tcpstate.String())
335 stats.rejectConnFsm++
342 err := t.optchecker.Accept(tcp, ci, dir, acked, start)
344 Error("OptionChecker", "%s: Packet rejected by OptionChecker: %s\n", t.ident, err)
353 c, err := tcp.ComputeChecksum()
355 Error("ChecksumCompute", "%s: Got error computing checksum: %s\n", t.ident, err)
358 Error("Checksum", "%s: Invalid checksum: 0x%x\n", t.ident, c)
368 func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) {
369 dir, start, end, skip := sg.Info()
370 length, saved := sg.Lengths()
372 sgStats := sg.Stats()
374 stats.missedBytes += skip
376 stats.sz += length - saved
377 stats.pkt += sgStats.Packets
378 if sgStats.Chunks > 1 {
381 stats.outOfOrderPackets += sgStats.QueuedPackets
382 stats.outOfOrderBytes += sgStats.QueuedBytes
383 if length > stats.biggestChunkBytes {
384 stats.biggestChunkBytes = length
386 if sgStats.Packets > stats.biggestChunkPackets {
387 stats.biggestChunkPackets = sgStats.Packets
389 if sgStats.OverlapBytes != 0 && sgStats.OverlapPackets == 0 {
390 fmt.Printf("bytes:%d, pkts:%d\n", sgStats.OverlapBytes, sgStats.OverlapPackets)
391 panic("Invalid overlap")
393 stats.overlapBytes += sgStats.OverlapBytes
394 stats.overlapPackets += sgStats.OverlapPackets
397 if dir == reassembly.TCPDirClientToServer {
398 ident = fmt.Sprintf("%v %v(%s): ", t.net, t.transport, dir)
400 ident = fmt.Sprintf("%v %v(%s): ", t.net.Reverse(), t.transport.Reverse(), dir)
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 {
405 } else if skip != 0 {
406 // Missing bytes in stream: do not even try to parse it
409 data := sg.Fetch(length)
412 var decoded []gopacket.LayerType
419 dnsSize := binary.BigEndian.Uint16(data[:2])
420 missing := int(dnsSize) - len(data[2:])
421 Debug("dnsSize: %d, missing: %d\n", dnsSize, missing)
423 Info("Missing some bytes: %d\n", missing)
427 p := gopacket.NewDecodingLayerParser(layers.LayerTypeDNS, dns)
428 err := p.DecodeLayers(data[2:], &decoded)
430 Error("DNS-parser", "Failed to decode DNS: %v\n", err)
432 Debug("DNS: %s\n", gopacket.LayerDump(dns))
434 if len(data) > 2+int(dnsSize) {
435 sg.KeepFrom(2 + int(dnsSize))
440 Debug("Feeding http with:\n%s", hex.Dump(data))
442 if dir == reassembly.TCPDirClientToServer && !t.reversed {
443 t.client.bytes <- data
445 t.server.bytes <- data
451 func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
452 Debug("%s: Connection closed\n", t.ident)
454 close(t.client.bytes)
455 close(t.server.bytes)
457 // do not remove the connection to allow last ACK
463 var handle *pcap.Handle
472 errorsMap = make(map[string]uint)
474 if handle, err = pcap.OpenOffline(*fname); err != nil {
475 log.Fatal("PCAP OpenOffline error:", err)
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)
483 log.Fatal("could not create: %v", err)
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)
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())
500 if handle, err = inactive.Activate(); err != nil {
501 log.Fatal("PCAP Activate error:", err)
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)
513 var dec gopacket.Decoder
515 decoder_name := *decoder
516 if decoder_name == "" {
517 decoder_name = fmt.Sprintf("%s", handle.LinkType())
519 if dec, ok = gopacket.DecodersByLayerName[decoder_name]; !ok {
520 log.Fatalln("No decoder named", decoder_name)
522 source := gopacket.NewPacketSource(handle, dec)
525 Info("Starting to read packets\n")
529 defragger := ip4defrag.NewIPv4Defragmenter()
531 streamFactory := &tcpStreamFactory{doHTTP: !*nohttp}
532 streamPool := reassembly.NewStreamPool(streamFactory)
533 assembler := reassembly.NewAssembler(streamPool)
535 signalChan := make(chan os.Signal, 1)
536 signal.Notify(signalChan, os.Interrupt)
538 for packet := range source.Packets() {
540 Debug("PACKET #%d\n", count)
541 data := packet.Data()
542 bytes += int64(len(data))
544 Debug("Packet content (%d/0x%x)\n%s\n", len(data), len(data), hex.Dump(data))
547 // defrag the IPv4 packet if required
549 ip4Layer := packet.Layer(layers.LayerTypeIPv4)
553 ip4 := ip4Layer.(*layers.IPv4)
555 newip4, err := defragger.DefragIPv4(ip4)
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.
562 if newip4.Length != l {
564 Debug("Decoding re-assembled packet: %s\n", newip4.NextLayerType())
565 pb, ok := packet.(gopacket.PacketBuilder)
567 panic("Not a PacketBuilder")
569 nextDecoder := newip4.NextLayerType()
570 nextDecoder.Decode(newip4.Payload, pb)
574 tcp := packet.Layer(layers.LayerTypeTCP)
576 tcp := tcp.(*layers.TCP)
578 err := tcp.SetNetworkLayerForChecksum(packet.NetworkLayer())
580 log.Fatalf("Failed to set network layer for checksum: %s\n", err)
584 CaptureInfo: packet.Metadata().CaptureInfo,
586 stats.totalsz += len(tcp.Payload)
587 assembler.AssembleWithContext(packet.NetworkLayer().NetworkFlow(), tcp, &c)
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)
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))
601 fmt.Fprintf(os.Stderr, "\nCaught SIGINT: aborting\n")
611 closed := assembler.FlushAll()
612 Debug("Final flush: %d closed", closed)
613 if outputLevel >= 2 {
617 if *memprofile != "" {
618 f, err := os.Create(*memprofile)
622 pprof.WriteHeapProfile(f)
626 streamFactory.WaitGoRoutines()
627 Debug("%s\n", assembler.Dump())
629 fmt.Printf("IPdefrag:\t\t%d\n", stats.ipdefrag)
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])