Permalink
Fetching contributors…
Cannot retrieve contributors at this time
216 lines (191 sloc) 6.75 KB
// 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
}