-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathraw_bsd.go
361 lines (305 loc) · 8.76 KB
/
raw_bsd.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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// +build darwin dragonfly freebsd netbsd openbsd
package raw
import (
"errors"
"fmt"
"net"
"os"
"runtime"
"sync"
"syscall"
"time"
"unsafe"
"golang.org/x/net/bpf"
)
const (
// bpfDIn tells BPF to pass through only incoming packets, so we do not
// receive the packets we send using BPF.
bpfDIn = 0
// osFreeBSD is the GOOS name for FreeBSD.
osFreeBSD = "freebsd"
)
// bpfLen returns the length of the BPF header prepended to each incoming ethernet
// frame. FreeBSD uses a slightly modified header from other BSD variants.
func bpfLen() int {
// Majority of BSD family systems use the bpf_hdr struct, but FreeBSD
// has replaced this with bpf_xhdr, which is longer.
const (
bpfHeaderLen = 18
bpfXHeaderLen = 26
)
if runtime.GOOS == osFreeBSD {
return bpfXHeaderLen
}
return bpfHeaderLen
}
var (
// Must implement net.PacketConn at compile-time.
_ net.PacketConn = &packetConn{}
)
// packetConn is the Linux-specific implementation of net.PacketConn for this
// package.
type packetConn struct {
proto uint16
ifi *net.Interface
f *os.File
fd int
buflen int
// Timeouts set via Set{Read,}Deadline, guarded by mutex
timeoutMu sync.RWMutex
rtimeout time.Time
}
// listenPacket creates a net.PacketConn which can be used to send and receive
// data at the device driver level.
func listenPacket(ifi *net.Interface, proto uint16, _ *Config) (*packetConn, error) {
// Config is, as of now, unused on BSD.
var f *os.File
var err error
// Try to find an available BPF device
for i := 0; i <= 10; i++ {
bpfPath := fmt.Sprintf("/dev/bpf%d", i)
f, err = os.OpenFile(bpfPath, os.O_RDWR, 0666)
if err == nil {
// Found a usable device
break
}
// Device is busy, try the next one
if perr, ok := err.(*os.PathError); ok {
if perr.Err.(syscall.Errno) == syscall.EBUSY {
continue
}
}
return nil, err
}
if f == nil {
return nil, errors.New("unable to open BPF device")
}
fd := int(f.Fd())
if fd == -1 {
return nil, errors.New("unable to open BPF device")
}
// Configure BPF device to send and receive data
buflen, err := configureBPF(fd, ifi, proto)
if err != nil {
return nil, err
}
return &packetConn{
proto: proto,
ifi: ifi,
f: f,
fd: fd,
buflen: buflen,
}, nil
}
// ReadFrom implements the net.PacketConn.ReadFrom method.
func (p *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
p.timeoutMu.Lock()
deadline := p.rtimeout
p.timeoutMu.Unlock()
buf := make([]byte, p.buflen)
var n int
for {
var timeout time.Duration
if deadline.IsZero() {
timeout = readTimeout
} else {
timeout = deadline.Sub(time.Now())
if timeout > readTimeout {
timeout = readTimeout
}
}
tv, err := newTimeval(timeout)
if err != nil {
return 0, nil, err
}
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(p.fd), syscall.BIOCSRTIMEOUT, uintptr(unsafe.Pointer(tv))); err != 0 {
return 0, nil, syscall.Errno(err)
}
// Attempt to receive on socket
// The read sycall will NOT be interrupted by closing of the socket
n, err = syscall.Read(p.fd, buf)
if err != nil {
return n, nil, err
}
if n > 0 {
break
}
}
// TODO(mdlayher): consider parsing BPF header if it proves useful.
// BPF header length depends on the platform this code is running on
bpfl := bpfLen()
// Retrieve source MAC address of ethernet header
mac := make(net.HardwareAddr, 6)
copy(mac, buf[bpfl+6:bpfl+12])
// Skip past BPF header to retrieve ethernet frame
out := copy(b, buf[bpfl:bpfl+n])
return out, &Addr{
HardwareAddr: mac,
}, nil
}
// WriteTo implements the net.PacketConn.WriteTo method.
func (p *packetConn) WriteTo(b []byte, _ net.Addr) (int, error) {
return syscall.Write(p.fd, b)
}
// Close closes the connection.
func (p *packetConn) Close() error {
return p.f.Close()
}
// LocalAddr returns the local network address.
func (p *packetConn) LocalAddr() net.Addr {
return &Addr{
HardwareAddr: p.ifi.HardwareAddr,
}
}
// SetDeadline implements the net.PacketConn.SetDeadline method.
func (p *packetConn) SetDeadline(t time.Time) error {
return p.SetReadDeadline(t)
}
// SetReadDeadline implements the net.PacketConn.SetReadDeadline method.
func (p *packetConn) SetReadDeadline(t time.Time) error {
p.timeoutMu.Lock()
p.rtimeout = t
p.timeoutMu.Unlock()
return nil
}
// SetWriteDeadline implements the net.PacketConn.SetWriteDeadline method.
func (p *packetConn) SetWriteDeadline(t time.Time) error {
return ErrNotImplemented
}
// SetBPF attaches an assembled BPF program to a raw net.PacketConn.
func (p *packetConn) SetBPF(filter []bpf.RawInstruction) error {
// Base filter filters traffic based on EtherType
base, err := bpf.Assemble(baseFilter(p.proto))
if err != nil {
return err
}
// Append user filter to base filter, translate to raw format,
// and apply to BPF device
return syscall.SetBpf(p.fd, assembleBpfInsn(append(base, filter...)))
}
// SetPromiscuous enables or disables promiscuous mode on the interface, allowing it
// to receive traffic that is not addressed to the interface.
func (p *packetConn) SetPromiscuous(b bool) error {
m := 1
if !b {
m = 0
}
return syscall.SetBpfPromisc(p.fd, m)
}
// configureBPF configures a BPF device with the specified file descriptor to
// use the specified network and interface and protocol.
func configureBPF(fd int, ifi *net.Interface, proto uint16) (int, error) {
// Use specified interface with BPF device
if err := syscall.SetBpfInterface(fd, ifi.Name); err != nil {
return 0, err
}
// Inform BPF to send us its data immediately
if err := syscall.SetBpfImmediate(fd, 1); err != nil {
return 0, err
}
// Check buffer size of BPF device
buflen, err := syscall.BpfBuflen(fd)
if err != nil {
return 0, err
}
// Do not automatically complete source address in ethernet headers
if err := syscall.SetBpfHeadercmpl(fd, 1); err != nil {
return 0, err
}
// Only retrieve incoming traffic using BPF device
if err := setBPFDirection(fd, bpfDIn); err != nil {
return 0, err
}
// Build and apply base BPF filter which checks for correct EtherType
// on incoming packets
prog, err := bpf.Assemble(baseInterfaceFilter(proto, ifi.MTU))
if err != nil {
return 0, err
}
if err := syscall.SetBpf(fd, assembleBpfInsn(prog)); err != nil {
return 0, err
}
// Flush any packets currently in the BPF device's buffer
if err := syscall.FlushBpf(fd); err != nil {
return 0, err
}
return buflen, nil
}
// setBPFDirection enables filtering traffic traveling in a specific direction
// using BPF, so that traffic sent by this package is not captured when reading
// using this package.
func setBPFDirection(fd int, direction int) error {
_, _, err := syscall.Syscall(
syscall.SYS_IOCTL,
uintptr(fd),
// Even though BIOCSDIRECTION is preferred on FreeBSD, BIOCSSEESENT continues
// to work, and is required for other BSD platforms
syscall.BIOCSSEESENT,
uintptr(unsafe.Pointer(&direction)),
)
if err != 0 {
return syscall.Errno(err)
}
return nil
}
// assembleBpfInsn assembles a slice of bpf.RawInstructions to the format required by
// package syscall.
func assembleBpfInsn(filter []bpf.RawInstruction) []syscall.BpfInsn {
// Copy each bpf.RawInstruction into syscall.BpfInsn. If needed,
// the structures have the same memory layout and could probably be
// unsafely cast to each other for speed.
insns := make([]syscall.BpfInsn, 0, len(filter))
for _, ins := range filter {
insns = append(insns, syscall.BpfInsn{
Code: ins.Op,
Jt: ins.Jt,
Jf: ins.Jf,
K: ins.K,
})
}
return insns
}
// baseInterfaceFilter creates a base BPF filter which filters traffic based
// on its EtherType and returns up to "mtu" bytes of data for processing.
func baseInterfaceFilter(proto uint16, mtu int) []bpf.Instruction {
return append(
// Filter traffic based on EtherType
baseFilter(proto),
// Accept the packet bytes up to the interface's MTU
bpf.RetConstant{
Val: uint32(mtu),
},
)
}
// baseFilter creates a base BPF filter which filters traffic based on its
// EtherType. baseFilter can be prepended to other filters to handle common
// filtering tasks.
func baseFilter(proto uint16) []bpf.Instruction {
// Offset | Length | Comment
// -------------------------
// 00 | 06 | Ethernet destination MAC address
// 06 | 06 | Ethernet source MAC address
// 12 | 02 | Ethernet EtherType
const (
etherTypeOffset = 12
etherTypeLength = 2
)
return []bpf.Instruction{
// Load EtherType value from Ethernet header
bpf.LoadAbsolute{
Off: etherTypeOffset,
Size: etherTypeLength,
},
// If EtherType is equal to the protocol we are using, jump to instructions
// added outside of this function.
bpf.JumpIf{
Cond: bpf.JumpEqual,
Val: uint32(proto),
SkipTrue: 1,
},
// EtherType does not match our protocol
bpf.RetConstant{
Val: 0,
},
}
}