aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/google/gopacket/bsdbpf/bsd_bpf_sniffer.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/google/gopacket/bsdbpf/bsd_bpf_sniffer.go')
-rw-r--r--vendor/github.com/google/gopacket/bsdbpf/bsd_bpf_sniffer.go215
1 files changed, 215 insertions, 0 deletions
diff --git a/vendor/github.com/google/gopacket/bsdbpf/bsd_bpf_sniffer.go b/vendor/github.com/google/gopacket/bsdbpf/bsd_bpf_sniffer.go
new file mode 100644
index 0000000..3e1da0b
--- /dev/null
+++ b/vendor/github.com/google/gopacket/bsdbpf/bsd_bpf_sniffer.go
@@ -0,0 +1,215 @@
+// Copyright 2012 Google, Inc. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree.
+
+// +build darwin dragonfly freebsd netbsd openbsd
+
+package bsdbpf
+
+import (
+ "errors"
+ "fmt"
+ "syscall"
+ "time"
+ "unsafe"
+
+ "github.com/google/gopacket"
+ "golang.org/x/sys/unix"
+)
+
+const wordSize = int(unsafe.Sizeof(uintptr(0)))
+
+func bpfWordAlign(x int) int {
+ return (((x) + (wordSize - 1)) &^ (wordSize - 1))
+}
+
+// Options is used to configure various properties of the BPF sniffer.
+// Default values are used when a nil Options pointer is passed to NewBPFSniffer.
+type Options struct {
+ // BPFDeviceName is name of the bpf device to use for sniffing
+ // the network device. The default value of BPFDeviceName is empty string
+ // which causes the first available BPF device file /dev/bpfX to be used.
+ BPFDeviceName string
+ // ReadBufLen specifies the size of the buffer used to read packets
+ // off the wire such that multiple packets are buffered with each read syscall.
+ // Note that an individual packet larger than the buffer size is necessarily truncated.
+ // A larger buffer should increase performance because fewer read syscalls would be made.
+ // If zero is used, the system's default buffer length will be used which depending on the
+ // system may default to 4096 bytes which is not big enough to accomodate some link layers
+ // such as WLAN (802.11).
+ // ReadBufLen defaults to 32767... however typical BSD manual pages for BPF indicate that
+ // if the requested buffer size cannot be accommodated, the closest allowable size will be
+ // set and returned... hence our GetReadBufLen method.
+ ReadBufLen int
+ // Timeout is the length of time to wait before timing out on a read request.
+ // Timeout defaults to nil which means no timeout is used.
+ Timeout *syscall.Timeval
+ // Promisc is set to true for promiscuous mode ethernet sniffing.
+ // Promisc defaults to true.
+ Promisc bool
+ // Immediate is set to true to make our read requests return as soon as a packet becomes available.
+ // Otherwise, a read will block until either the kernel buffer becomes full or a timeout occurs.
+ // The default is true.
+ Immediate bool
+ // PreserveLinkAddr is set to false if the link level source address should be filled in automatically
+ // by the interface output routine. Set to true if the link level source address will be written,
+ // as provided, to the wire.
+ // The default is true.
+ PreserveLinkAddr bool
+}
+
+var defaultOptions = Options{
+ BPFDeviceName: "",
+ ReadBufLen: 32767,
+ Timeout: nil,
+ Promisc: true,
+ Immediate: true,
+ PreserveLinkAddr: true,
+}
+
+// BPFSniffer is a struct used to track state of a BSD BPF ethernet sniffer
+// such that gopacket's PacketDataSource interface is implemented.
+type BPFSniffer struct {
+ options *Options
+ sniffDeviceName string
+ fd int
+ readBuffer []byte
+ lastReadLen int
+ readBytesConsumed int
+}
+
+// NewBPFSniffer is used to create BSD-only BPF ethernet sniffer
+// iface is the network interface device name that you wish to sniff
+// options can set to nil in order to utilize default values for everything.
+// Each field of Options also have a default setting if left unspecified by
+// the user's custome Options struct.
+func NewBPFSniffer(iface string, options *Options) (*BPFSniffer, error) {
+ var err error
+ enable := 1
+ sniffer := BPFSniffer{
+ sniffDeviceName: iface,
+ }
+ if options == nil {
+ sniffer.options = &defaultOptions
+ } else {
+ sniffer.options = options
+ }
+
+ if sniffer.options.BPFDeviceName == "" {
+ sniffer.pickBpfDevice()
+ }
+
+ // setup our read buffer
+ if sniffer.options.ReadBufLen == 0 {
+ sniffer.options.ReadBufLen, err = syscall.BpfBuflen(sniffer.fd)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ sniffer.options.ReadBufLen, err = syscall.SetBpfBuflen(sniffer.fd, sniffer.options.ReadBufLen)
+ if err != nil {
+ return nil, err
+ }
+ }
+ sniffer.readBuffer = make([]byte, sniffer.options.ReadBufLen)
+
+ err = syscall.SetBpfInterface(sniffer.fd, sniffer.sniffDeviceName)
+ if err != nil {
+ return nil, err
+ }
+
+ if sniffer.options.Immediate {
+ // turn immediate mode on. This makes the snffer non-blocking.
+ err = syscall.SetBpfImmediate(sniffer.fd, enable)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // the above call to syscall.SetBpfImmediate needs to be made
+ // before setting a timer otherwise the reads will block for the
+ // entire timer duration even if there are packets to return.
+ if sniffer.options.Timeout != nil {
+ err = syscall.SetBpfTimeout(sniffer.fd, sniffer.options.Timeout)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if sniffer.options.PreserveLinkAddr {
+ // preserves the link level source address...
+ // higher level protocol analyzers will not need this
+ err = syscall.SetBpfHeadercmpl(sniffer.fd, enable)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if sniffer.options.Promisc {
+ // forces the interface into promiscuous mode
+ err = syscall.SetBpfPromisc(sniffer.fd, enable)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &sniffer, nil
+}
+
+// Close is used to close the file-descriptor of the BPF device file.
+func (b *BPFSniffer) Close() error {
+ return syscall.Close(b.fd)
+}
+
+func (b *BPFSniffer) pickBpfDevice() {
+ var err error
+ b.options.BPFDeviceName = ""
+ for i := 0; i < 99; i++ {
+ b.options.BPFDeviceName = fmt.Sprintf("/dev/bpf%d", i)
+ b.fd, err = syscall.Open(b.options.BPFDeviceName, syscall.O_RDWR, 0)
+ if err == nil {
+ return
+ }
+ }
+ panic("failed to acquire a BPF device for read-write access")
+}
+
+func (b *BPFSniffer) ReadPacketData() ([]byte, gopacket.CaptureInfo, error) {
+ var err error
+ if b.readBytesConsumed >= b.lastReadLen {
+ b.readBytesConsumed = 0
+ b.readBuffer = make([]byte, b.options.ReadBufLen)
+ b.lastReadLen, err = syscall.Read(b.fd, b.readBuffer)
+ if err != nil {
+ b.lastReadLen = 0
+ return nil, gopacket.CaptureInfo{}, err
+ }
+ }
+ hdr := (*unix.BpfHdr)(unsafe.Pointer(&b.readBuffer[b.readBytesConsumed]))
+ frameStart := b.readBytesConsumed + int(hdr.Hdrlen)
+ b.readBytesConsumed += bpfWordAlign(int(hdr.Hdrlen) + int(hdr.Caplen))
+
+ if frameStart+int(hdr.Caplen) > len(b.readBuffer) {
+ captureInfo := gopacket.CaptureInfo{
+ Timestamp: time.Unix(int64(hdr.Tstamp.Sec), int64(hdr.Tstamp.Usec)*1000),
+ CaptureLength: 0,
+ Length: 0,
+ }
+ return nil, captureInfo, errors.New("BPF captured frame received with corrupted BpfHdr struct.")
+ }
+
+ rawFrame := b.readBuffer[frameStart : frameStart+int(hdr.Caplen)]
+ captureInfo := gopacket.CaptureInfo{
+ Timestamp: time.Unix(int64(hdr.Tstamp.Sec), int64(hdr.Tstamp.Usec)*1000),
+ CaptureLength: len(rawFrame),
+ Length: len(rawFrame),
+ }
+ return rawFrame, captureInfo, nil
+}
+
+// GetReadBufLen returns the BPF read buffer length
+func (b *BPFSniffer) GetReadBufLen() int {
+ return b.options.ReadBufLen
+}