/
pcap.go
274 lines (224 loc) · 8.03 KB
/
pcap.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/*
Package pcap allows reading / parsing pcap files (compressed and uncompressed) and implement the
capture.Source interface. Consequently, an instance can act as network capture source, allowing
to replay network traffic previously captured elsewhere. In addition, the pcap package acts as main
facilitaty for testing (since it allows to mimic a live network traffic capture without the need
for privileged access and / or actual network interfaces).
*/
package pcap
import (
"bufio"
"compress/gzip"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"unsafe"
"github.com/fako1024/slimcap/capture"
"github.com/fako1024/slimcap/link"
)
// Source denotes a pcap file capture source
type Source struct {
reader *bufio.Reader
gzipReader *gzip.Reader
header Header
buf []byte
ipLayerOffset byte
link *link.Link
nPackets uint64
swapEndianess bool
packetAddCallbackFn func(payload []byte, totalLen uint32, pktType, ipLayerOffset byte)
}
// NewSource instantiates a new pcap file capture source based on any io.Reader
func NewSource(iface string, r io.Reader) (*Source, error) {
if r == nil {
return nil, errors.New("nil io.Reader provided")
}
obj := Source{
reader: bufio.NewReader(r),
buf: make([]byte, HeaderSize),
}
// Check if the source is compressed
if err := obj.checkCompression(); err != nil {
return nil, err
}
// Parse the main header
if err := obj.read(obj.buf); err != nil {
return nil, err
}
// If required, swap endianess as defined here:
// https://wiki.wireshark.org/Development/LibpcapFileFormat
obj.header = *(*Header)(unsafe.Pointer(&obj.buf[0])) // #nosec G103
if obj.header.MagicNumber == MagicSwappedEndianess {
obj.header = obj.header.SwapEndianess()
obj.swapEndianess = true
}
// After swapping, the header magic must be valid
if obj.header.MagicNumber != MagicNativeEndianess {
return nil, fmt.Errorf("invalid pcap header magic: %x", obj.header.MagicNumber)
}
// Populate (fake) link information
obj.link = &link.Link{
Interface: link.Interface{
Name: iface,
Type: link.Type(obj.header.Network),
},
}
obj.ipLayerOffset = obj.link.Type.IPHeaderOffset()
return &obj, nil
}
// NewSourceFromFile instantiates a new pcap file capture source based on a file name
func NewSourceFromFile(path string) (*Source, error) {
f, err := os.Open(filepath.Clean(path))
if err != nil {
return nil, err
}
return NewSource(filepath.Base(path), f)
}
// NewPacket creates an empty "buffer" packet to be used as destination for the NextPacket() / NextPayload() /
// NextIPPacket() methods (the latter two by calling .Payload() / .IPLayer() on the created buffer). It ensures
// that a valid packet of appropriate structure / length is created
func (s *Source) NewPacket() capture.Packet {
p := make(capture.Packet, int(s.header.Snaplen)+capture.PacketHdrOffset)
return p
}
// NextPacket receives the next packet from the source and returns it. The operation is blocking. In
// case a non-nil "buffer" Packet is provided it will be populated with the data (and returned). The
// buffer packet can be reused. Otherwise a new Packet is allocated.
func (s *Source) NextPacket(pBuf capture.Packet) (capture.Packet, error) {
pktHeader, err := s.nextPacket()
if err != nil {
return nil, err
}
return capture.NewIPPacket(pBuf, s.buf, capture.PacketUnknown, int(pktHeader.OriginalLen), s.ipLayerOffset), nil
}
// NextPayload receives the raw payload of the next packet from the source and returns it. The operation is blocking.
// In case a non-nil "buffer" byte slice / payload is provided it will be populated with the data (and returned).
// The buffer can be reused. Otherwise a new byte slice / payload is allocated.
func (s *Source) NextPayload(pBuf []byte) ([]byte, capture.PacketType, uint32, error) {
pktHeader, err := s.nextPacket()
if err != nil {
return nil, capture.PacketUnknown, 0, err
}
if pBuf != nil {
copy(pBuf, s.buf)
return pBuf, capture.PacketUnknown, uint32(pktHeader.OriginalLen), nil
}
return s.buf, capture.PacketUnknown, uint32(pktHeader.OriginalLen), nil
}
// NextIPPacket receives the IP layer of the next packet from the source and returns it. The operation is blocking.
// In case a non-nil "buffer" IPLayer is provided it will be populated with the data (and returned).
// The buffer can be reused. Otherwise a new IPLayer is allocated.
func (s *Source) NextIPPacket(pBuf capture.IPLayer) (capture.IPLayer, capture.PacketType, uint32, error) {
pktHeader, err := s.nextPacket()
if err != nil {
return nil, capture.PacketUnknown, 0, err
}
if pBuf != nil {
copy(pBuf, s.buf[s.ipLayerOffset:])
return pBuf, capture.PacketUnknown, uint32(pktHeader.OriginalLen), nil
}
return s.buf[s.ipLayerOffset:], capture.PacketUnknown, uint32(pktHeader.OriginalLen), nil
}
// NextPacketFn executes the provided function on the next packet received on the source. If possible, the
// operation should provide a zero-copy way of interaction with the payload / metadata. All operations on the data
// must be completed prior to any subsequent call to any Next*() method.
func (s *Source) NextPacketFn(fn func(payload []byte, totalLen uint32, pktType capture.PacketType, ipLayerOffset byte) error) error {
pktHeader, err := s.nextPacket()
if err != nil {
return err
}
return fn(s.buf, uint32(pktHeader.OriginalLen), capture.PacketUnknown, s.ipLayerOffset)
}
// Stats returns (and clears) the packet counters of the underlying source
func (s *Source) Stats() (capture.Stats, error) {
stats := capture.Stats{
PacketsReceived: s.nPackets,
}
s.nPackets = 0
return stats, nil
}
// Link returns the underlying link
func (s *Source) Link() *link.Link {
return s.link
}
// Unblock ensures that a potentially ongoing blocking poll operation is released (returning an ErrCaptureUnblock from
// any potentially ongoing call to Next*() that might currently be blocked)
func (s *Source) Unblock() error {
return nil
}
// Close stops / closes the capture source
func (s *Source) Close() error {
if s.gzipReader != nil {
return s.gzipReader.Close()
}
s.buf = nil
return nil
}
// PacketAddCallbackFn provides an optional callback function that is called when a packet is added
// to the mock source (e.g. to build a reference for comparison)
func (s *Source) PacketAddCallbackFn(fn func(payload []byte, totalLen uint32, pktType, ipLayerOffset byte)) *Source {
s.packetAddCallbackFn = fn
return s
}
////////////////////////////////////////////////////////////////////////
func (s *Source) checkCompression() error {
// Check if the first two bytes match a gzip file magic in either
// endianess
magicBytes, err := s.reader.Peek(2)
if err != nil {
return err
}
if (magicBytes[0] == 0x1f && magicBytes[1] == 0x8b) ||
(magicBytes[0] == 0x8b && magicBytes[1] == 0x1f) {
// Attempt to open a new gzip decompressor
s.gzipReader, err = gzip.NewReader(s.reader)
if err != nil {
return err
}
// Wrap a new bufio.Reader around the gzip decompressor
s.reader = bufio.NewReader(s.gzipReader)
}
return nil
}
func (s *Source) nextPacket() (pktHeader PacketHeader, err error) {
pktHeader, err = s.nextPacketHeader()
if err != nil {
return
}
if s.swapEndianess {
pktHeader = pktHeader.SwapEndianess()
}
if err = s.nextPacketData(int(pktHeader.CaptureLen)); err == nil {
s.nPackets++
}
// If a callback function was provided, execute it
if s.packetAddCallbackFn != nil {
s.packetAddCallbackFn(s.buf, uint32(pktHeader.OriginalLen), capture.PacketUnknown, s.ipLayerOffset)
}
return
}
func (s *Source) nextPacketHeader() (PacketHeader, error) {
if err := s.read(s.buf[:PacketHeaderSize]); err != nil {
return PacketHeader{}, err
}
return *(*PacketHeader)(unsafe.Pointer(&s.buf[0])), nil // #nosec G103
}
func (s *Source) nextPacketData(snapLen int) error {
if cap(s.buf) < snapLen {
s.buf = make([]byte, snapLen)
}
s.buf = s.buf[:snapLen]
return s.read(s.buf)
}
func (s *Source) read(buf []byte) error {
n, err := io.ReadAtLeast(s.reader, buf, len(buf))
if err != nil {
return err
}
if n != len(buf) {
return fmt.Errorf("unexpected number of bytes read, want %d, have %d", len(buf), n)
}
return nil
}