Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscellaneous bsdbpf fixes and improvements #1091

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 62 additions & 14 deletions bsdbpf/bsd_bpf_sniffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
// that can be found in the LICENSE file in the root of the source
// tree.

//go:build darwin || dragonfly || freebsd || netbsd || openbsd
// +build darwin dragonfly freebsd netbsd openbsd

package bsdbpf

import (
"errors"
"fmt"
"github.com/google/gopacket/pcap"
"syscall"
"time"
"unsafe"
Expand All @@ -22,7 +24,7 @@ import (
const wordSize = int(unsafe.Sizeof(uintptr(0)))

func bpfWordAlign(x int) int {
return (((x) + (wordSize - 1)) &^ (wordSize - 1))
return ((x) + (wordSize - 1)) &^ (wordSize - 1)
}

// Options is used to configure various properties of the BPF sniffer.
Expand Down Expand Up @@ -58,6 +60,9 @@ type Options struct {
// as provided, to the wire.
// The default is true.
PreserveLinkAddr bool
// SeeSent is set to false if locally generated packets on the interface should not be returned by BPF.
// The default is true.
SeeSent bool
}

var defaultOptions = Options{
Expand All @@ -67,6 +72,7 @@ var defaultOptions = Options{
Promisc: true,
Immediate: true,
PreserveLinkAddr: true,
SeeSent: true,
}

// BPFSniffer is a struct used to track state of a BSD BPF ethernet sniffer
Expand Down Expand Up @@ -97,34 +103,32 @@ func NewBPFSniffer(iface string, options *Options) (*BPFSniffer, error) {
sniffer.options = options
}

if sniffer.options.BPFDeviceName == "" {
sniffer.pickBpfDevice()
}
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
goto err
}
} else {
sniffer.options.ReadBufLen, err = syscall.SetBpfBuflen(sniffer.fd, sniffer.options.ReadBufLen)
if err != nil {
return nil, err
goto err
}
}
sniffer.readBuffer = make([]byte, sniffer.options.ReadBufLen)

err = syscall.SetBpfInterface(sniffer.fd, sniffer.sniffDeviceName)
if err != nil {
return nil, err
goto 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
goto err
}
}

Expand All @@ -134,7 +138,7 @@ func NewBPFSniffer(iface string, options *Options) (*BPFSniffer, error) {
if sniffer.options.Timeout != nil {
err = syscall.SetBpfTimeout(sniffer.fd, sniffer.options.Timeout)
if err != nil {
return nil, err
goto err
}
}

Expand All @@ -143,19 +147,30 @@ func NewBPFSniffer(iface string, options *Options) (*BPFSniffer, error) {
// higher level protocol analyzers will not need this
err = syscall.SetBpfHeadercmpl(sniffer.fd, enable)
if err != nil {
return nil, err
goto err
}
}

if sniffer.options.Promisc {
// forces the interface into promiscuous mode
err = syscall.SetBpfPromisc(sniffer.fd, enable)
if err != nil {
return nil, err
goto err
}
}

if !sniffer.options.SeeSent {
// See sent is set by default, need to turn it off
err = unix.IoctlSetPointerInt(sniffer.fd, syscall.BIOCSSEESENT, 0)
if err != nil {
goto err
}
}

return &sniffer, nil
err:
syscall.Close(sniffer.fd)
return nil, err
}

// Close is used to close the file-descriptor of the BPF device file.
Expand All @@ -165,13 +180,26 @@ func (b *BPFSniffer) Close() error {

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)
if len(b.options.BPFDeviceName) > 0 {
b.fd, err = syscall.Open(b.options.BPFDeviceName, syscall.O_RDWR, 0)
if err == nil {
return
}
} else {
b.options.BPFDeviceName = ""
for i := 0; i < 99; i++ {
name := fmt.Sprintf("/dev/bpf%d", i)
b.fd, err = syscall.Open(name, syscall.O_RDWR, 0)
if err == nil {
b.options.BPFDeviceName = name
return
}

if err == syscall.Errno(syscall.ENOENT) {
// No such file, no needs to iterate further
break
}
}
}
panic("failed to acquire a BPF device for read-write access")
}
Expand Down Expand Up @@ -213,3 +241,23 @@ func (b *BPFSniffer) ReadPacketData() ([]byte, gopacket.CaptureInfo, error) {
func (b *BPFSniffer) GetReadBufLen() int {
return b.options.ReadBufLen
}

func (s *BPFSniffer) SetBPFFilter(bpfInstructions []pcap.BPFInstruction) error {
bpfIns := make([]syscall.BpfInsn, 0, len(bpfInstructions))
for _, ins := range bpfInstructions {
sysIns := syscall.BpfInsn{
Code: ins.Code,
Jt: ins.Jt,
Jf: ins.Jf,
K: ins.K,
}
bpfIns = append(bpfIns, sysIns)
}

return syscall.SetBpf(s.fd, bpfIns)
}

func (h *BPFSniffer) WritePacketData(pkt []byte) error {
_, err := unix.Write(h.fd, pkt)
return err
}