mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-18 21:09:38 +00:00
We'd like to benefit from the memory reduction from: https://github.com/google/gopacket/pull/377 I just ran: $ gvt update github.com/google/gopacket Fixes: https://github.com/weaveworks/scope/issues/2905
216 lines
6.7 KiB
Go
216 lines
6.7 KiB
Go
// 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
|
|
}
|