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 pcaplay binary load an offline capture (pcap file) and replay
8// it on the select interface, with an emphasis on packet timing
9package main
10
11import (
12	"flag"
13	"fmt"
14	"io"
15	"log"
16	"os"
17	"strings"
18	"time"
19
20	"github.com/google/gopacket"
21	"github.com/google/gopacket/examples/util"
22	"github.com/google/gopacket/pcap"
23)
24
25var iface = flag.String("i", "eth0", "Interface to write packets to")
26var fname = flag.String("r", "", "Filename to read from")
27var fast = flag.Bool("f", false, "Send each packets as fast as possible")
28
29var lastTS time.Time
30var lastSend time.Time
31
32var start time.Time
33var bytesSent int
34
35func writePacketDelayed(handle *pcap.Handle, buf []byte, ci gopacket.CaptureInfo) {
36	if ci.CaptureLength != ci.Length {
37		// do not write truncated packets
38		return
39	}
40
41	intervalInCapture := ci.Timestamp.Sub(lastTS)
42	elapsedTime := time.Since(lastSend)
43
44	if (intervalInCapture > elapsedTime) && !lastSend.IsZero() {
45		time.Sleep(intervalInCapture - elapsedTime)
46	}
47
48	lastSend = time.Now()
49	writePacket(handle, buf)
50	lastTS = ci.Timestamp
51}
52
53func writePacket(handle *pcap.Handle, buf []byte) error {
54	if err := handle.WritePacketData(buf); err != nil {
55		log.Printf("Failed to send packet: %s\n", err)
56		return err
57	}
58	return nil
59}
60
61func pcapInfo(filename string) (start time.Time, end time.Time, packets int, size int) {
62	handleRead, err := pcap.OpenOffline(*fname)
63	if err != nil {
64		log.Fatal("PCAP OpenOffline error (handle to read packet):", err)
65	}
66
67	var previousTs time.Time
68	var deltaTotal time.Duration
69
70	for {
71		data, ci, err := handleRead.ReadPacketData()
72		if err != nil && err != io.EOF {
73			log.Fatal(err)
74		} else if err == io.EOF {
75			break
76		} else {
77
78			if start.IsZero() {
79				start = ci.Timestamp
80			}
81			end = ci.Timestamp
82			packets++
83			size += len(data)
84
85			if previousTs.IsZero() {
86				previousTs = ci.Timestamp
87			} else {
88				deltaTotal += ci.Timestamp.Sub(previousTs)
89				previousTs = ci.Timestamp
90			}
91		}
92	}
93	sec := int(deltaTotal.Seconds())
94	if sec == 0 {
95		sec = 1
96	}
97	fmt.Printf("Avg packet rate %d/s\n", packets/sec)
98	return start, end, packets, size
99}
100
101func main() {
102	defer util.Run()()
103
104	// Sanity checks
105	if *fname == "" {
106		log.Fatal("Need a input file")
107	}
108
109	// Open PCAP file + handle potential BPF Filter
110	handleRead, err := pcap.OpenOffline(*fname)
111	if err != nil {
112		log.Fatal("PCAP OpenOffline error (handle to read packet):", err)
113	}
114	defer handleRead.Close()
115	if len(flag.Args()) > 0 {
116		bpffilter := strings.Join(flag.Args(), " ")
117		fmt.Fprintf(os.Stderr, "Using BPF filter %q\n", bpffilter)
118		if err = handleRead.SetBPFFilter(bpffilter); err != nil {
119			log.Fatal("BPF filter error:", err)
120		}
121	}
122	// Open up a second pcap handle for packet writes.
123	handleWrite, err := pcap.OpenLive(*iface, 65536, true, pcap.BlockForever)
124	if err != nil {
125		log.Fatal("PCAP OpenLive error (handle to write packet):", err)
126	}
127	defer handleWrite.Close()
128
129	start = time.Now()
130	pkt := 0
131	tsStart, tsEnd, packets, size := pcapInfo(*fname)
132
133	// Loop over packets and write them
134	for {
135		data, ci, err := handleRead.ReadPacketData()
136		switch {
137		case err == io.EOF:
138			fmt.Printf("\nFinished in %s", time.Since(start))
139			return
140		case err != nil:
141			log.Printf("Failed to read packet %d: %s\n", pkt, err)
142		default:
143			if *fast {
144				writePacket(handleWrite, data)
145			} else {
146				writePacketDelayed(handleWrite, data, ci)
147			}
148
149			bytesSent += len(data)
150			duration := time.Since(start)
151			pkt++
152
153			if duration > time.Second {
154				rate := bytesSent / int(duration.Seconds())
155				remainingTime := tsEnd.Sub(tsStart) - duration
156				fmt.Printf("\rrate %d kB/sec - sent %d/%d kB - %d/%d packets - remaining time %s",
157					rate/1000, bytesSent/1000, size/1000,
158					pkt, packets, remainingTime)
159			}
160		}
161	}
162
163}
164