1 // Copyright 2012 Google, Inc. All rights reserved.
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
7 // +build darwin dragonfly freebsd netbsd openbsd
18 "github.com/google/gopacket"
19 "golang.org/x/sys/unix"
22 const wordSize = int(unsafe.Sizeof(uintptr(0)))
24 func bpfWordAlign(x int) int {
25 return (((x) + (wordSize - 1)) &^ (wordSize - 1))
28 // Options is used to configure various properties of the BPF sniffer.
29 // Default values are used when a nil Options pointer is passed to NewBPFSniffer.
31 // BPFDeviceName is name of the bpf device to use for sniffing
32 // the network device. The default value of BPFDeviceName is empty string
33 // which causes the first available BPF device file /dev/bpfX to be used.
35 // ReadBufLen specifies the size of the buffer used to read packets
36 // off the wire such that multiple packets are buffered with each read syscall.
37 // Note that an individual packet larger than the buffer size is necessarily truncated.
38 // A larger buffer should increase performance because fewer read syscalls would be made.
39 // If zero is used, the system's default buffer length will be used which depending on the
40 // system may default to 4096 bytes which is not big enough to accomodate some link layers
41 // such as WLAN (802.11).
42 // ReadBufLen defaults to 32767... however typical BSD manual pages for BPF indicate that
43 // if the requested buffer size cannot be accommodated, the closest allowable size will be
44 // set and returned... hence our GetReadBufLen method.
46 // Timeout is the length of time to wait before timing out on a read request.
47 // Timeout defaults to nil which means no timeout is used.
48 Timeout *syscall.Timeval
49 // Promisc is set to true for promiscuous mode ethernet sniffing.
50 // Promisc defaults to true.
52 // Immediate is set to true to make our read requests return as soon as a packet becomes available.
53 // Otherwise, a read will block until either the kernel buffer becomes full or a timeout occurs.
54 // The default is true.
56 // PreserveLinkAddr is set to false if the link level source address should be filled in automatically
57 // by the interface output routine. Set to true if the link level source address will be written,
58 // as provided, to the wire.
59 // The default is true.
63 var defaultOptions = Options{
69 PreserveLinkAddr: true,
72 // BPFSniffer is a struct used to track state of a BSD BPF ethernet sniffer
73 // such that gopacket's PacketDataSource interface is implemented.
74 type BPFSniffer struct {
76 sniffDeviceName string
83 // NewBPFSniffer is used to create BSD-only BPF ethernet sniffer
84 // iface is the network interface device name that you wish to sniff
85 // options can set to nil in order to utilize default values for everything.
86 // Each field of Options also have a default setting if left unspecified by
87 // the user's custome Options struct.
88 func NewBPFSniffer(iface string, options *Options) (*BPFSniffer, error) {
91 sniffer := BPFSniffer{
92 sniffDeviceName: iface,
95 sniffer.options = &defaultOptions
97 sniffer.options = options
100 if sniffer.options.BPFDeviceName == "" {
101 sniffer.pickBpfDevice()
104 // setup our read buffer
105 if sniffer.options.ReadBufLen == 0 {
106 sniffer.options.ReadBufLen, err = syscall.BpfBuflen(sniffer.fd)
111 sniffer.options.ReadBufLen, err = syscall.SetBpfBuflen(sniffer.fd, sniffer.options.ReadBufLen)
116 sniffer.readBuffer = make([]byte, sniffer.options.ReadBufLen)
118 err = syscall.SetBpfInterface(sniffer.fd, sniffer.sniffDeviceName)
123 if sniffer.options.Immediate {
124 // turn immediate mode on. This makes the snffer non-blocking.
125 err = syscall.SetBpfImmediate(sniffer.fd, enable)
131 // the above call to syscall.SetBpfImmediate needs to be made
132 // before setting a timer otherwise the reads will block for the
133 // entire timer duration even if there are packets to return.
134 if sniffer.options.Timeout != nil {
135 err = syscall.SetBpfTimeout(sniffer.fd, sniffer.options.Timeout)
141 if sniffer.options.PreserveLinkAddr {
142 // preserves the link level source address...
143 // higher level protocol analyzers will not need this
144 err = syscall.SetBpfHeadercmpl(sniffer.fd, enable)
150 if sniffer.options.Promisc {
151 // forces the interface into promiscuous mode
152 err = syscall.SetBpfPromisc(sniffer.fd, enable)
161 // Close is used to close the file-descriptor of the BPF device file.
162 func (b *BPFSniffer) Close() error {
163 return syscall.Close(b.fd)
166 func (b *BPFSniffer) pickBpfDevice() {
168 b.options.BPFDeviceName = ""
169 for i := 0; i < 99; i++ {
170 b.options.BPFDeviceName = fmt.Sprintf("/dev/bpf%d", i)
171 b.fd, err = syscall.Open(b.options.BPFDeviceName, syscall.O_RDWR, 0)
176 panic("failed to acquire a BPF device for read-write access")
179 func (b *BPFSniffer) ReadPacketData() ([]byte, gopacket.CaptureInfo, error) {
181 if b.readBytesConsumed >= b.lastReadLen {
182 b.readBytesConsumed = 0
183 b.readBuffer = make([]byte, b.options.ReadBufLen)
184 b.lastReadLen, err = syscall.Read(b.fd, b.readBuffer)
187 return nil, gopacket.CaptureInfo{}, err
190 hdr := (*unix.BpfHdr)(unsafe.Pointer(&b.readBuffer[b.readBytesConsumed]))
191 frameStart := b.readBytesConsumed + int(hdr.Hdrlen)
192 b.readBytesConsumed += bpfWordAlign(int(hdr.Hdrlen) + int(hdr.Caplen))
194 if frameStart+int(hdr.Caplen) > len(b.readBuffer) {
195 captureInfo := gopacket.CaptureInfo{
196 Timestamp: time.Unix(int64(hdr.Tstamp.Sec), int64(hdr.Tstamp.Usec)*1000),
200 return nil, captureInfo, errors.New("BPF captured frame received with corrupted BpfHdr struct.")
203 rawFrame := b.readBuffer[frameStart : frameStart+int(hdr.Caplen)]
204 captureInfo := gopacket.CaptureInfo{
205 Timestamp: time.Unix(int64(hdr.Tstamp.Sec), int64(hdr.Tstamp.Usec)*1000),
206 CaptureLength: len(rawFrame),
207 Length: len(rawFrame),
209 return rawFrame, captureInfo, nil
212 // GetReadBufLen returns the BPF read buffer length
213 func (b *BPFSniffer) GetReadBufLen() int {
214 return b.options.ReadBufLen