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// This binary provides sample code for using the gopacket TCP assembler and TCP
8// stream reader.  It reads packets off the wire and reconstructs HTTP requests
9// it sees, logging them.
10package main
11
12import (
13	"bufio"
14	"flag"
15	"io"
16	"log"
17	"net/http"
18	"time"
19
20	"github.com/google/gopacket"
21	"github.com/google/gopacket/examples/util"
22	"github.com/google/gopacket/layers"
23	"github.com/google/gopacket/pcap"
24	"github.com/google/gopacket/tcpassembly"
25	"github.com/google/gopacket/tcpassembly/tcpreader"
26)
27
28var iface = flag.String("i", "eth0", "Interface to get packets from")
29var fname = flag.String("r", "", "Filename to read from, overrides -i")
30var snaplen = flag.Int("s", 1600, "SnapLen for pcap packet capture")
31var filter = flag.String("f", "tcp and dst port 80", "BPF filter for pcap")
32var logAllPackets = flag.Bool("v", false, "Logs every packet in great detail")
33
34// Build a simple HTTP request parser using tcpassembly.StreamFactory and tcpassembly.Stream interfaces
35
36// httpStreamFactory implements tcpassembly.StreamFactory
37type httpStreamFactory struct{}
38
39// httpStream will handle the actual decoding of http requests.
40type httpStream struct {
41	net, transport gopacket.Flow
42	r              tcpreader.ReaderStream
43}
44
45func (h *httpStreamFactory) New(net, transport gopacket.Flow) tcpassembly.Stream {
46	hstream := &httpStream{
47		net:       net,
48		transport: transport,
49		r:         tcpreader.NewReaderStream(),
50	}
51	go hstream.run() // Important... we must guarantee that data from the reader stream is read.
52
53	// ReaderStream implements tcpassembly.Stream, so we can return a pointer to it.
54	return &hstream.r
55}
56
57func (h *httpStream) run() {
58	buf := bufio.NewReader(&h.r)
59	for {
60		req, err := http.ReadRequest(buf)
61		if err == io.EOF {
62			// We must read until we see an EOF... very important!
63			return
64		} else if err != nil {
65			log.Println("Error reading stream", h.net, h.transport, ":", err)
66		} else {
67			bodyBytes := tcpreader.DiscardBytesToEOF(req.Body)
68			req.Body.Close()
69			log.Println("Received request from stream", h.net, h.transport, ":", req, "with", bodyBytes, "bytes in request body")
70		}
71	}
72}
73
74func main() {
75	defer util.Run()()
76	var handle *pcap.Handle
77	var err error
78
79	// Set up pcap packet capture
80	if *fname != "" {
81		log.Printf("Reading from pcap dump %q", *fname)
82		handle, err = pcap.OpenOffline(*fname)
83	} else {
84		log.Printf("Starting capture on interface %q", *iface)
85		handle, err = pcap.OpenLive(*iface, int32(*snaplen), true, pcap.BlockForever)
86	}
87	if err != nil {
88		log.Fatal(err)
89	}
90
91	if err := handle.SetBPFFilter(*filter); err != nil {
92		log.Fatal(err)
93	}
94
95	// Set up assembly
96	streamFactory := &httpStreamFactory{}
97	streamPool := tcpassembly.NewStreamPool(streamFactory)
98	assembler := tcpassembly.NewAssembler(streamPool)
99
100	log.Println("reading in packets")
101	// Read in packets, pass to assembler.
102	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
103	packets := packetSource.Packets()
104	ticker := time.Tick(time.Minute)
105	for {
106		select {
107		case packet := <-packets:
108			// A nil packet indicates the end of a pcap file.
109			if packet == nil {
110				return
111			}
112			if *logAllPackets {
113				log.Println(packet)
114			}
115			if packet.NetworkLayer() == nil || packet.TransportLayer() == nil || packet.TransportLayer().LayerType() != layers.LayerTypeTCP {
116				log.Println("Unusable packet")
117				continue
118			}
119			tcp := packet.TransportLayer().(*layers.TCP)
120			assembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp)
121
122		case <-ticker:
123			// Every minute, flush connections that haven't seen activity in the past 2 minutes.
124			assembler.FlushOlderThan(time.Now().Add(time.Minute * -2))
125		}
126	}
127}
128