// Copyright 2012 Google, Inc. All rights reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file in the root of the source // tree. // The pcapdump binary implements a tcpdump-like command line tool with gopacket // using pcap as a backend data collection mechanism. package main import ( "bufio" "bytes" "compress/gzip" "encoding/binary" "encoding/hex" "flag" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "os" "os/signal" "path" "runtime/pprof" "strings" "sync" "time" "github.com/google/gopacket" "github.com/google/gopacket/examples/util" "github.com/google/gopacket/ip4defrag" "github.com/google/gopacket/layers" // pulls in all layers decoders "github.com/google/gopacket/pcap" "github.com/google/gopacket/reassembly" ) var maxcount = flag.Int("c", -1, "Only grab this many packets, then exit") var decoder = flag.String("decoder", "", "Name of the decoder to use (default: guess from capture)") var statsevery = flag.Int("stats", 1000, "Output statistics every N packets") var lazy = flag.Bool("lazy", false, "If true, do lazy decoding") var nodefrag = flag.Bool("nodefrag", false, "If true, do not do IPv4 defrag") var checksum = flag.Bool("checksum", false, "Check TCP checksum") var nooptcheck = flag.Bool("nooptcheck", false, "Do not check TCP options (useful to ignore MSS on captures with TSO)") var ignorefsmerr = flag.Bool("ignorefsmerr", false, "Ignore TCP FSM errors") var allowmissinginit = flag.Bool("allowmissinginit", false, "Support streams without SYN/SYN+ACK/ACK sequence") var verbose = flag.Bool("verbose", false, "Be verbose") var debug = flag.Bool("debug", false, "Display debug information") var quiet = flag.Bool("quiet", false, "Be quiet regarding errors") // http var nohttp = flag.Bool("nohttp", false, "Disable HTTP parsing") var output = flag.String("output", "", "Path to create file for HTTP 200 OK responses") var writeincomplete = flag.Bool("writeincomplete", false, "Write incomplete response") var hexdump = flag.Bool("dump", false, "Dump HTTP request/response as hex") var hexdumppkt = flag.Bool("dumppkt", false, "Dump packet as hex") // capture var iface = flag.String("i", "eth0", "Interface to read packets from") var fname = flag.String("r", "", "Filename to read from, overrides -i") var snaplen = flag.Int("s", 65536, "Snap length (number of bytes max to read per packet") var tstype = flag.String("timestamp_type", "", "Type of timestamps to use") var promisc = flag.Bool("promisc", true, "Set promiscuous mode") var memprofile = flag.String("memprofile", "", "Write memory profile") var stats struct { ipdefrag int missedBytes int pkt int sz int totalsz int rejectFsm int rejectOpt int rejectConnFsm int reassembled int outOfOrderBytes int outOfOrderPackets int biggestChunkBytes int biggestChunkPackets int overlapBytes int overlapPackets int } const closeTimeout time.Duration = time.Hour * 24 // Closing inactive: TODO: from CLI const timeout time.Duration = time.Minute * 5 // Pending bytes: TODO: from CLI /* * HTTP part */ type httpReader struct { ident string isClient bool bytes chan []byte data []byte hexdump bool parent *tcpStream } func (h *httpReader) Read(p []byte) (int, error) { ok := true for ok && len(h.data) == 0 { h.data, ok = <-h.bytes } if !ok || len(h.data) == 0 { return 0, io.EOF } l := copy(p, h.data) h.data = h.data[l:] return l, nil } var outputLevel int var errorsMap map[string]uint var errors uint // Too bad for perf that a... is evaluated func Error(t string, s string, a ...interface{}) { errors++ nb, _ := errorsMap[t] errorsMap[t] = nb + 1 if outputLevel >= 0 { fmt.Printf(s, a...) } } func Info(s string, a ...interface{}) { if outputLevel >= 1 { fmt.Printf(s, a...) } } func Debug(s string, a ...interface{}) { if outputLevel >= 2 { fmt.Printf(s, a...) } } func (h *httpReader) run(wg *sync.WaitGroup) { defer wg.Done() b := bufio.NewReader(h) for true { if h.isClient { req, err := http.ReadRequest(b) if err == io.EOF || err == io.ErrUnexpectedEOF { break } else if err != nil { Error("HTTP-request", "HTTP/%s Request error: %s (%v,%+v)\n", h.ident, err, err, err) continue } body, err := ioutil.ReadAll(req.Body) s := len(body) if err != nil { Error("HTTP-request-body", "Got body err: %s\n", err) } else if h.hexdump { Info("Body(%d/0x%x)\n%s\n", len(body), len(body), hex.Dump(body)) } req.Body.Close() Info("HTTP/%s Request: %s %s (body:%d)\n", h.ident, req.Method, req.URL, s) h.parent.urls = append(h.parent.urls, req.URL.String()) } else { res, err := http.ReadResponse(b, nil) var req string if len(h.parent.urls) == 0 { req = fmt.Sprintf("") } else { req, h.parent.urls = h.parent.urls[0], h.parent.urls[1:] } if err == io.EOF || err == io.ErrUnexpectedEOF { break } else if err != nil { Error("HTTP-response", "HTTP/%s Response error: %s (%v,%+v)\n", h.ident, err, err, err) continue } body, err := ioutil.ReadAll(res.Body) s := len(body) if err != nil { Error("HTTP-response-body", "HTTP/%s: failed to get body(parsed len:%d): %s\n", h.ident, s, err) } if h.hexdump { Info("Body(%d/0x%x)\n%s\n", len(body), len(body), hex.Dump(body)) } res.Body.Close() sym := "," if res.ContentLength > 0 && res.ContentLength != int64(s) { sym = "!=" } contentType, ok := res.Header["Content-Type"] if !ok { contentType = []string{http.DetectContentType(body)} } encoding := res.Header["Content-Encoding"] Info("HTTP/%s Response: %s URL:%s (%d%s%d%s) -> %s\n", h.ident, res.Status, req, res.ContentLength, sym, s, contentType, encoding) if (err == nil || *writeincomplete) && *output != "" { base := url.QueryEscape(path.Base(req)) if err != nil { base = "incomplete-" + base } base = path.Join(*output, base) if len(base) > 250 { base = base[:250] + "..." } if base == *output { base = path.Join(*output, "noname") } target := base n := 0 for true { _, err := os.Stat(target) //if os.IsNotExist(err) != nil { if err != nil { break } target = fmt.Sprintf("%s-%d", base, n) n++ } f, err := os.Create(target) if err != nil { Error("HTTP-create", "Cannot create %s: %s\n", target, err) continue } var r io.Reader r = bytes.NewBuffer(body) if len(encoding) > 0 && (encoding[0] == "gzip" || encoding[0] == "deflate") { r, err = gzip.NewReader(r) if err != nil { Error("HTTP-gunzip", "Failed to gzip decode: %s", err) } } if err == nil { w, err := io.Copy(f, r) if _, ok := r.(*gzip.Reader); ok { r.(*gzip.Reader).Close() } f.Close() if err != nil { Error("HTTP-save", "%s: failed to save %s (l:%d): %s\n", h.ident, target, w, err) } else { Info("%s: Saved %s (l:%d)\n", h.ident, target, w) } } } } } } /* * The TCP factory: returns a new Stream */ type tcpStreamFactory struct { wg sync.WaitGroup doHTTP bool } func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream { Debug("* NEW: %s %s\n", net, transport) fsmOptions := reassembly.TCPSimpleFSMOptions{ SupportMissingEstablishment: *allowmissinginit, } stream := &tcpStream{ net: net, transport: transport, isDNS: tcp.SrcPort == 53 || tcp.DstPort == 53, isHTTP: (tcp.SrcPort == 80 || tcp.DstPort == 80) && factory.doHTTP, reversed: tcp.SrcPort == 80, tcpstate: reassembly.NewTCPSimpleFSM(fsmOptions), ident: fmt.Sprintf("%s:%s", net, transport), optchecker: reassembly.NewTCPOptionCheck(), } if stream.isHTTP { stream.client = httpReader{ bytes: make(chan []byte), ident: fmt.Sprintf("%s %s", net, transport), hexdump: *hexdump, parent: stream, isClient: true, } stream.server = httpReader{ bytes: make(chan []byte), ident: fmt.Sprintf("%s %s", net.Reverse(), transport.Reverse()), hexdump: *hexdump, parent: stream, } factory.wg.Add(2) go stream.client.run(&factory.wg) go stream.server.run(&factory.wg) } return stream } func (factory *tcpStreamFactory) WaitGoRoutines() { factory.wg.Wait() } /* * The assembler context */ type Context struct { CaptureInfo gopacket.CaptureInfo } func (c *Context) GetCaptureInfo() gopacket.CaptureInfo { return c.CaptureInfo } /* * TCP stream */ /* It's a connection (bidirectional) */ type tcpStream struct { tcpstate *reassembly.TCPSimpleFSM fsmerr bool optchecker reassembly.TCPOptionCheck net, transport gopacket.Flow isDNS bool isHTTP bool reversed bool client httpReader server httpReader urls []string ident string } func (t *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, acked reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool { // FSM if !t.tcpstate.CheckState(tcp, dir) { Error("FSM", "%s: Packet rejected by FSM (state:%s)\n", t.ident, t.tcpstate.String()) stats.rejectFsm++ if !t.fsmerr { t.fsmerr = true stats.rejectConnFsm++ } if !*ignorefsmerr { return false } } // Options err := t.optchecker.Accept(tcp, ci, dir, acked, start) if err != nil { Error("OptionChecker", "%s: Packet rejected by OptionChecker: %s\n", t.ident, err) stats.rejectOpt++ if !*nooptcheck { return false } } // Checksum accept := true if *checksum { c, err := tcp.ComputeChecksum() if err != nil { Error("ChecksumCompute", "%s: Got error computing checksum: %s\n", t.ident, err) accept = false } else if c != 0x0 { Error("Checksum", "%s: Invalid checksum: 0x%x\n", t.ident, c) accept = false } } if !accept { stats.rejectOpt++ } return accept } func (t *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) { dir, start, end, skip := sg.Info() length, saved := sg.Lengths() // update stats sgStats := sg.Stats() if skip > 0 { stats.missedBytes += skip } stats.sz += length - saved stats.pkt += sgStats.Packets if sgStats.Chunks > 1 { stats.reassembled++ } stats.outOfOrderPackets += sgStats.QueuedPackets stats.outOfOrderBytes += sgStats.QueuedBytes if length > stats.biggestChunkBytes { stats.biggestChunkBytes = length } if sgStats.Packets > stats.biggestChunkPackets { stats.biggestChunkPackets = sgStats.Packets } if sgStats.OverlapBytes != 0 && sgStats.OverlapPackets == 0 { fmt.Printf("bytes:%d, pkts:%d\n", sgStats.OverlapBytes, sgStats.OverlapPackets) panic("Invalid overlap") } stats.overlapBytes += sgStats.OverlapBytes stats.overlapPackets += sgStats.OverlapPackets var ident string if dir == reassembly.TCPDirClientToServer { ident = fmt.Sprintf("%v %v(%s): ", t.net, t.transport, dir) } else { ident = fmt.Sprintf("%v %v(%s): ", t.net.Reverse(), t.transport.Reverse(), dir) } 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) if skip == -1 && *allowmissinginit { // this is allowed } else if skip != 0 { // Missing bytes in stream: do not even try to parse it return } data := sg.Fetch(length) if t.isDNS { dns := &layers.DNS{} var decoded []gopacket.LayerType if len(data) < 2 { if len(data) > 0 { sg.KeepFrom(0) } return } dnsSize := binary.BigEndian.Uint16(data[:2]) missing := int(dnsSize) - len(data[2:]) Debug("dnsSize: %d, missing: %d\n", dnsSize, missing) if missing > 0 { Info("Missing some bytes: %d\n", missing) sg.KeepFrom(0) return } p := gopacket.NewDecodingLayerParser(layers.LayerTypeDNS, dns) err := p.DecodeLayers(data[2:], &decoded) if err != nil { Error("DNS-parser", "Failed to decode DNS: %v\n", err) } else { Debug("DNS: %s\n", gopacket.LayerDump(dns)) } if len(data) > 2+int(dnsSize) { sg.KeepFrom(2 + int(dnsSize)) } } else if t.isHTTP { if length > 0 { if *hexdump { Debug("Feeding http with:\n%s", hex.Dump(data)) } if dir == reassembly.TCPDirClientToServer && !t.reversed { t.client.bytes <- data } else { t.server.bytes <- data } } } } func (t *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool { Debug("%s: Connection closed\n", t.ident) if t.isHTTP { close(t.client.bytes) close(t.server.bytes) } // do not remove the connection to allow last ACK return false } func main() { defer util.Run()() var handle *pcap.Handle var err error if *debug { outputLevel = 2 } else if *verbose { outputLevel = 1 } else if *quiet { outputLevel = -1 } errorsMap = make(map[string]uint) if *fname != "" { if handle, err = pcap.OpenOffline(*fname); err != nil { log.Fatal("PCAP OpenOffline error:", err) } } else { // This is a little complicated because we want to allow all possible options // for creating the packet capture handle... instead of all this you can // just call pcap.OpenLive if you want a simple handle. inactive, err := pcap.NewInactiveHandle(*iface) if err != nil { log.Fatal("could not create: %v", err) } defer inactive.CleanUp() if err = inactive.SetSnapLen(*snaplen); err != nil { log.Fatal("could not set snap length: %v", err) } else if err = inactive.SetPromisc(*promisc); err != nil { log.Fatal("could not set promisc mode: %v", err) } else if err = inactive.SetTimeout(time.Second); err != nil { log.Fatal("could not set timeout: %v", err) } if *tstype != "" { if t, err := pcap.TimestampSourceFromString(*tstype); err != nil { log.Fatalf("Supported timestamp types: %v", inactive.SupportedTimestamps()) } else if err := inactive.SetTimestampSource(t); err != nil { log.Fatalf("Supported timestamp types: %v", inactive.SupportedTimestamps()) } } if handle, err = inactive.Activate(); err != nil { log.Fatal("PCAP Activate error:", err) } defer handle.Close() } if len(flag.Args()) > 0 { bpffilter := strings.Join(flag.Args(), " ") Info("Using BPF filter %q\n", bpffilter) if err = handle.SetBPFFilter(bpffilter); err != nil { log.Fatal("BPF filter error:", err) } } var dec gopacket.Decoder var ok bool decoder_name := *decoder if decoder_name == "" { decoder_name = fmt.Sprintf("%s", handle.LinkType()) } if dec, ok = gopacket.DecodersByLayerName[decoder_name]; !ok { log.Fatalln("No decoder named", decoder_name) } source := gopacket.NewPacketSource(handle, dec) source.Lazy = *lazy source.NoCopy = true Info("Starting to read packets\n") count := 0 bytes := int64(0) start := time.Now() defragger := ip4defrag.NewIPv4Defragmenter() streamFactory := &tcpStreamFactory{doHTTP: !*nohttp} streamPool := reassembly.NewStreamPool(streamFactory) assembler := reassembly.NewAssembler(streamPool) signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, os.Interrupt) for packet := range source.Packets() { count++ Debug("PACKET #%d\n", count) data := packet.Data() bytes += int64(len(data)) if *hexdumppkt { Debug("Packet content (%d/0x%x)\n%s\n", len(data), len(data), hex.Dump(data)) } // defrag the IPv4 packet if required if !*nodefrag { ip4Layer := packet.Layer(layers.LayerTypeIPv4) if ip4Layer == nil { continue } ip4 := ip4Layer.(*layers.IPv4) l := ip4.Length newip4, err := defragger.DefragIPv4(ip4) if err != nil { log.Fatalln("Error while de-fragmenting", err) } else if newip4 == nil { Debug("Fragment...\n") continue // packet fragment, we don't have whole packet yet. } if newip4.Length != l { stats.ipdefrag++ Debug("Decoding re-assembled packet: %s\n", newip4.NextLayerType()) pb, ok := packet.(gopacket.PacketBuilder) if !ok { panic("Not a PacketBuilder") } nextDecoder := newip4.NextLayerType() nextDecoder.Decode(newip4.Payload, pb) } } tcp := packet.Layer(layers.LayerTypeTCP) if tcp != nil { tcp := tcp.(*layers.TCP) if *checksum { err := tcp.SetNetworkLayerForChecksum(packet.NetworkLayer()) if err != nil { log.Fatalf("Failed to set network layer for checksum: %s\n", err) } } c := Context{ CaptureInfo: packet.Metadata().CaptureInfo, } stats.totalsz += len(tcp.Payload) assembler.AssembleWithContext(packet.NetworkLayer().NetworkFlow(), tcp, &c) } if count%*statsevery == 0 { ref := packet.Metadata().CaptureInfo.Timestamp flushed, closed := assembler.FlushWithOptions(reassembly.FlushOptions{T: ref.Add(-timeout), TC: ref.Add(-closeTimeout)}) Debug("Forced flush: %d flushed, %d closed (%s)", flushed, closed, ref) } done := *maxcount > 0 && count >= *maxcount if count%*statsevery == 0 || done { fmt.Fprintf(os.Stderr, "Processed %v packets (%v bytes) in %v (errors: %v, type:%v)\n", count, bytes, time.Since(start), errors, len(errorsMap)) } select { case <-signalChan: fmt.Fprintf(os.Stderr, "\nCaught SIGINT: aborting\n") done = true default: // NOP: continue } if done { break } } closed := assembler.FlushAll() Debug("Final flush: %d closed", closed) if outputLevel >= 2 { streamPool.Dump() } if *memprofile != "" { f, err := os.Create(*memprofile) if err != nil { log.Fatal(err) } pprof.WriteHeapProfile(f) f.Close() } streamFactory.WaitGoRoutines() Debug("%s\n", assembler.Dump()) if !*nodefrag { fmt.Printf("IPdefrag:\t\t%d\n", stats.ipdefrag) } fmt.Printf("TCP stats:\n") fmt.Printf(" missed bytes:\t\t%d\n", stats.missedBytes) fmt.Printf(" total packets:\t\t%d\n", stats.pkt) fmt.Printf(" rejected FSM:\t\t%d\n", stats.rejectFsm) fmt.Printf(" rejected Options:\t%d\n", stats.rejectOpt) fmt.Printf(" reassembled bytes:\t%d\n", stats.sz) fmt.Printf(" total TCP bytes:\t%d\n", stats.totalsz) fmt.Printf(" conn rejected FSM:\t%d\n", stats.rejectConnFsm) fmt.Printf(" reassembled chunks:\t%d\n", stats.reassembled) fmt.Printf(" out-of-order packets:\t%d\n", stats.outOfOrderPackets) fmt.Printf(" out-of-order bytes:\t%d\n", stats.outOfOrderBytes) fmt.Printf(" biggest-chunk packets:\t%d\n", stats.biggestChunkPackets) fmt.Printf(" biggest-chunk bytes:\t%d\n", stats.biggestChunkBytes) fmt.Printf(" overlap packets:\t%d\n", stats.overlapPackets) fmt.Printf(" overlap bytes:\t\t%d\n", stats.overlapBytes) fmt.Printf("Errors: %d\n", errors) for e, _ := range errorsMap { fmt.Printf(" %s:\t\t%d\n", e, errorsMap[e]) } }