or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bpf.mdcontext-ctxhttp.mdcontext.mddict.mddns-dnsmessage.mdhtml-atom.mdhtml-charset.mdhtml.mdhttp-httpguts.mdhttp-httpproxy.mdhttp2-h2c.mdhttp2-hpack.mdhttp2.mdicmp.mdidna.mdindex.mdipv4.mdipv6.mdnettest.mdnetutil.mdproxy.mdpublicsuffix.mdquic-qlog.mdquic.mdtrace.mdwebdav.mdwebsocket.mdxsrftoken.md
tile.json

bpf.mddocs/

Berkeley Packet Filter (BPF)

Package bpf implements marshaling and unmarshaling of programs for the Berkeley Packet Filter virtual machine, and provides a Go implementation of the virtual machine.

BPF's main use is to specify a packet filter for network taps, so that the kernel doesn't have to expensively copy every packet it sees to userspace. However, it's been repurposed to other areas where running user code in-kernel is needed. For example, Linux's seccomp uses BPF to apply security policies to system calls.

Import

import "golang.org/x/net/bpf"

Virtual Machine Architecture

The BPF VM is an accumulator machine. Its main register, called register A, is an implicit source and destination in all arithmetic and logic operations. The machine also has 16 scratch registers for temporary storage, and an indirection register (register X) for indirect memory access. All registers are 32 bits wide.

Each run of a BPF program is given one packet, which is placed in the VM's read-only "main memory". LoadAbsolute and LoadIndirect instructions can fetch up to 32 bits at a time into register A for examination.

The goal of a BPF program is to produce and return a verdict (uint32), which tells the kernel what to do with the packet. In the context of packet filtering, the returned value is the number of bytes of the packet to forward to userspace, or 0 to ignore the packet.

Program Assembly

// Assemble converts instructions into raw instructions suitable for loading into a BPF virtual machine
func Assemble(insts []Instruction) ([]RawInstruction, error)

// Disassemble attempts to parse raw back into Instructions
func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool)

Virtual Machine

// VM is an emulated BPF virtual machine
type VM struct {
    // Has unexported fields
}

// NewVM returns a new VM using the input BPF program
func NewVM(filter []Instruction) (*VM, error)

// Run runs the VM's BPF program against the input bytes
func (v *VM) Run(in []byte) (int, error)

Instructions

// Instruction is one instruction executed by the BPF virtual machine
type Instruction interface {
    // Assemble assembles the Instruction into a RawInstruction
    Assemble() (RawInstruction, error)
}

// RawInstruction is a raw BPF virtual machine instruction
type RawInstruction struct {
    Op uint16 // Operation to execute
    Jt uint8  // Jump if true count
    Jf uint8  // Jump if false count
    K  uint32 // Constant parameter (meaning depends on Op)
}

func (ri RawInstruction) Assemble() (RawInstruction, error)
func (ri RawInstruction) Disassemble() Instruction

Load Instructions

// LoadAbsolute loads packet[Off:Off+Size] as an integer value into register A
type LoadAbsolute struct {
    Off  uint32
    Size int // 1, 2 or 4
}

func (a LoadAbsolute) Assemble() (RawInstruction, error)
func (a LoadAbsolute) String() string

// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value into register A
type LoadIndirect struct {
    Off  uint32
    Size int // 1, 2 or 4
}

func (a LoadIndirect) Assemble() (RawInstruction, error)
func (a LoadIndirect) String() string

// LoadConstant loads Val into register Dst
type LoadConstant struct {
    Dst Register
    Val uint32
}

func (a LoadConstant) Assemble() (RawInstruction, error)
func (a LoadConstant) String() string

// LoadScratch loads scratch[N] into register Dst
type LoadScratch struct {
    Dst Register
    N   int // 0-15
}

func (a LoadScratch) Assemble() (RawInstruction, error)
func (a LoadScratch) String() string

// LoadMemShift multiplies the first 4 bits of the byte at packet[Off] by 4 and stores in register X
type LoadMemShift struct {
    Off uint32
}

func (a LoadMemShift) Assemble() (RawInstruction, error)
func (a LoadMemShift) String() string

// LoadExtension invokes a linux-specific extension and stores the result in register A
type LoadExtension struct {
    Num Extension
}

func (a LoadExtension) Assemble() (RawInstruction, error)
func (a LoadExtension) String() string

Store Instructions

// StoreScratch stores register Src into scratch[N]
type StoreScratch struct {
    Src Register
    N   int // 0-15
}

func (a StoreScratch) Assemble() (RawInstruction, error)
func (a StoreScratch) String() string

ALU Operations

// ALUOp is an arithmetic or logic operation
type ALUOp uint16

const (
    ALUOpAdd        ALUOp = iota << 4
    ALUOpSub
    ALUOpMul
    ALUOpDiv
    ALUOpOr
    ALUOpAnd
    ALUOpShiftLeft
    ALUOpShiftRight
    ALUOpMod
    ALUOpXor
)

// ALUOpConstant executes A = A <Op> Val
type ALUOpConstant struct {
    Op  ALUOp
    Val uint32
}

func (a ALUOpConstant) Assemble() (RawInstruction, error)
func (a ALUOpConstant) String() string

// ALUOpX executes A = A <Op> X
type ALUOpX struct {
    Op ALUOp
}

func (a ALUOpX) Assemble() (RawInstruction, error)
func (a ALUOpX) String() string

// NegateA executes A = -A
type NegateA struct{}

func (a NegateA) Assemble() (RawInstruction, error)
func (a NegateA) String() string

Jump Instructions

// JumpTest is a comparison operator used in conditional jumps
type JumpTest uint16

const (
    JumpEqual          JumpTest = iota // K == A
    JumpNotEqual                       // K != A
    JumpGreaterThan                    // K > A
    JumpLessThan                       // K < A
    JumpGreaterOrEqual                 // K >= A
    JumpLessOrEqual                    // K <= A
    JumpBitsSet                        // K & A != 0
    JumpBitsNotSet                     // K & A == 0
)

// Jump skips the following Skip instructions in the program
type Jump struct {
    Skip uint32
}

func (a Jump) Assemble() (RawInstruction, error)
func (a Jump) String() string

// JumpIf skips the following Skip instructions if A <Cond> Val is true
type JumpIf struct {
    Cond      JumpTest
    Val       uint32
    SkipTrue  uint8
    SkipFalse uint8
}

func (a JumpIf) Assemble() (RawInstruction, error)
func (a JumpIf) String() string

// JumpIfX skips the following Skip instructions if A <Cond> X is true
type JumpIfX struct {
    Cond      JumpTest
    SkipTrue  uint8
    SkipFalse uint8
}

func (a JumpIfX) Assemble() (RawInstruction, error)
func (a JumpIfX) String() string

Return Instructions

// RetA exits the BPF program, returning the value of register A
type RetA struct{}

func (a RetA) Assemble() (RawInstruction, error)
func (a RetA) String() string

// RetConstant exits the BPF program, returning a constant value
type RetConstant struct {
    Val uint32
}

func (a RetConstant) Assemble() (RawInstruction, error)
func (a RetConstant) String() string

Register Transfer

// Register is a register of the BPF virtual machine
type Register uint16

const (
    RegA Register = iota // Accumulator register (destination of ALU operations)
    RegX                 // Indirection register (used by LoadIndirect operations)
)

// TAX copies the value of register A to register X
type TAX struct{}

func (a TAX) Assemble() (RawInstruction, error)
func (a TAX) String() string

// TXA copies the value of register X to register A
type TXA struct{}

func (a TXA) Assemble() (RawInstruction, error)
func (a TXA) String() string

Extensions

// Extension is a kernel function call for advanced operations
type Extension int

const (
    ExtLen               Extension = 1  // Returns the length of the packet
    ExtProto             Extension = 0  // Returns the packet's L3 protocol type
    ExtType              Extension = 4  // Returns the packet's type (skb->pkt_type)
    ExtPayloadOffset     Extension = 52 // Returns the offset of the packet payload
    ExtInterfaceIndex    Extension = 8  // Returns the index of the interface
    ExtNetlinkAttr       Extension = 12 // Returns the netlink attribute of type X at offset A
    ExtNetlinkAttrNested Extension = 16 // Returns nested netlink attribute of type X at offset A
    ExtMark              Extension = 20 // Returns the packet's mark value
    ExtQueue             Extension = 24 // Returns the packet's assigned hardware queue
    ExtLinkLayerType     Extension = 28 // Returns the packet's hardware address type
    ExtRXHash            Extension = 32 // Returns the packet's receive hash
    ExtCPUID             Extension = 36 // Returns the ID of the CPU processing the packet
    ExtVLANTag           Extension = 44 // Returns the packet's VLAN tag
    ExtVLANTagPresent    Extension = 48 // Returns non-zero if packet has a VLAN tag
    ExtVLANProto         Extension = 60 // Returns VLAN protocol (0x8100, 0x88a8, etc.)
    ExtRand              Extension = 56 // Returns a uniformly random uint32
)

Setter Interface

// Setter is a type which can attach a compiled BPF filter to itself
type Setter interface {
    SetBPF(filter []RawInstruction) error
}

Usage Examples

ARP Packet Filter

// Select all ARP packets
filter, err := bpf.Assemble([]bpf.Instruction{
    // Load "EtherType" field from the ethernet header
    bpf.LoadAbsolute{Off: 12, Size: 2},
    // Skip over the next instruction if EtherType is not ARP
    bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1},
    // Verdict: send up to 4k of the packet to userspace
    bpf.RetConstant{Val: 4096},
    // Verdict: ignore packet
    bpf.RetConstant{Val: 0},
})
if err != nil {
    panic(err)
}

Random Sampling Filter

// Capture a random 1% sample of traffic
filter, err := bpf.Assemble([]bpf.Instruction{
    // Get a 32-bit random number from the Linux kernel
    bpf.LoadExtension{Num: bpf.ExtRand},
    // 1% dice roll?
    bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2 ^ 32 / 100, SkipFalse: 1},
    // Capture
    bpf.RetConstant{Val: 4096},
    // Ignore
    bpf.RetConstant{Val: 0},
})
if err != nil {
    panic(err)
}

Using the VM

// Create a BPF VM with a program
program := []bpf.Instruction{
    bpf.LoadAbsolute{Off: 12, Size: 2},
    bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0x0800, SkipTrue: 1},
    bpf.RetConstant{Val: 0},
    bpf.RetConstant{Val: 4096},
}

vm, err := bpf.NewVM(program)
if err != nil {
    panic(err)
}

// Run the VM against packet data
packetData := []byte{/* ... packet bytes ... */}
verdict, err := vm.Run(packetData)
if err != nil {
    panic(err)
}

if verdict > 0 {
    fmt.Printf("Accept %d bytes\n", verdict)
} else {
    fmt.Println("Reject packet")
}