git » go-net » commit 1961d9d

bpf: add Go implementation of virtual machine

author Matt Layher
2016-06-15 20:37:29 UTC
committer Mikio Hara
2016-06-18 00:31:17 UTC
parent d7bf3545bb0dacf009c535b3d3fbf53ac0a339ab

bpf: add Go implementation of virtual machine

Fixes golang/go#16055.

Change-Id: I80437e2895b0f2bf23e090dec29bd20c2900db9a
Reviewed-on: https://go-review.googlesource.com/24136
Reviewed-by: David Anderson <danderson@google.com>
Run-TryBot: Mikio Hara <mikioh.mikioh@gmail.com>
Reviewed-by: Mikio Hara <mikioh.mikioh@gmail.com>

bpf/doc.go +2 -1
bpf/vm.go +134 -0
bpf/vm_aluop_test.go +512 -0
bpf/vm_bpf_test.go +192 -0
bpf/vm_instructions.go +165 -0
bpf/vm_jump_test.go +380 -0
bpf/vm_load_test.go +246 -0
bpf/vm_ret_test.go +115 -0
bpf/vm_scratch_test.go +247 -0
bpf/vm_test.go +144 -0

diff --git a/bpf/doc.go b/bpf/doc.go
index bf2564b..ae62feb 100644
--- a/bpf/doc.go
+++ b/bpf/doc.go
@@ -5,7 +5,8 @@
 /*
 
 Package bpf implements marshaling and unmarshaling of programs for the
-Berkeley Packet Filter virtual machine.
+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
diff --git a/bpf/vm.go b/bpf/vm.go
new file mode 100644
index 0000000..957c95d
--- /dev/null
+++ b/bpf/vm.go
@@ -0,0 +1,134 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf
+
+import (
+	"errors"
+	"fmt"
+)
+
+// A VM is an emulated BPF virtual machine.
+type VM struct {
+	filter []Instruction
+}
+
+// NewVM returns a new VM using the input BPF program.
+func NewVM(filter []Instruction) (*VM, error) {
+	if len(filter) == 0 {
+		return nil, errors.New("one or more Instructions must be specified")
+	}
+
+	for i, ins := range filter {
+		check := len(filter) - (i + 1)
+		switch ins := ins.(type) {
+		// Check for out-of-bounds jumps in instructions
+		case Jump:
+			if check <= int(ins.Skip) {
+				return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
+			}
+		case JumpIf:
+			if check <= int(ins.SkipTrue) {
+				return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
+			}
+			if check <= int(ins.SkipFalse) {
+				return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
+			}
+		// Check for division or modulus by zero
+		case ALUOpConstant:
+			if ins.Val != 0 {
+				break
+			}
+
+			switch ins.Op {
+			case ALUOpDiv, ALUOpMod:
+				return nil, errors.New("cannot divide by zero using ALUOpConstant")
+			}
+		}
+	}
+
+	// Make sure last instruction is a return instruction
+	switch filter[len(filter)-1].(type) {
+	case RetA, RetConstant:
+	default:
+		return nil, errors.New("BPF program must end with RetA or RetConstant")
+	}
+
+	// Though our VM works using disassembled instructions, we
+	// attempt to assemble the input filter anyway to ensure it is compatible
+	// with an operating system VM.
+	_, err := Assemble(filter)
+
+	return &VM{
+		filter: filter,
+	}, err
+}
+
+// Run runs the VM's BPF program against the input bytes.
+// Run returns the number of bytes accepted by the BPF program, and any errors
+// which occurred while processing the program.
+func (v *VM) Run(in []byte) (int, error) {
+	var (
+		// Registers of the virtual machine
+		regA       uint32
+		regX       uint32
+		regScratch [16]uint32
+
+		// OK is true if the program should continue processing the next
+		// instruction, or false if not, causing the loop to break
+		ok = true
+	)
+
+	// TODO(mdlayher): implement:
+	// - NegateA:
+	//   - would require a change from uint32 registers to int32
+	//     registers
+	// - Extension:
+	//   - implement extensions that do not depend on kernel-specific
+	//     functionality, such as 'rand'
+
+	// TODO(mdlayher): add interop tests that check signedness of ALU
+	// operations against kernel implementation, and make sure Go
+	// implementation matches behavior
+
+	for i := 0; i < len(v.filter) && ok; i++ {
+		ins := v.filter[i]
+
+		switch ins := ins.(type) {
+		case ALUOpConstant:
+			regA = aluOpConstant(ins, regA)
+		case ALUOpX:
+			regA, ok = aluOpX(ins, regA, regX)
+		case Jump:
+			i += int(ins.Skip)
+		case JumpIf:
+			jump := jumpIf(ins, regA)
+			i += jump
+		case LoadAbsolute:
+			regA, ok = loadAbsolute(ins, in)
+		case LoadConstant:
+			regA, regX = loadConstant(ins, regA, regX)
+		case LoadIndirect:
+			regA, ok = loadIndirect(ins, in, regX)
+		case LoadMemShift:
+			regX, ok = loadMemShift(ins, in)
+		case LoadScratch:
+			regA, regX = loadScratch(ins, regScratch, regA, regX)
+		case RetA:
+			return int(regA), nil
+		case RetConstant:
+			return int(ins.Val), nil
+		case StoreScratch:
+			regScratch = storeScratch(ins, regScratch, regA, regX)
+		case TAX:
+			regX = regA
+		case TXA:
+			regA = regX
+		default:
+			return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
+		}
+	}
+
+	return 0, nil
+}
diff --git a/bpf/vm_aluop_test.go b/bpf/vm_aluop_test.go
new file mode 100644
index 0000000..1667824
--- /dev/null
+++ b/bpf/vm_aluop_test.go
@@ -0,0 +1,512 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf_test
+
+import (
+	"testing"
+
+	"golang.org/x/net/bpf"
+)
+
+func TestVMALUOpAdd(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpAdd,
+			Val: 3,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		8, 2, 3,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 3, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpSub(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.TAX{},
+		bpf.ALUOpX{
+			Op: bpf.ALUOpSub,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		1, 2, 3,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 0, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpMul(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpMul,
+			Val: 2,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		6, 2, 3, 4,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 4, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpDiv(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpDiv,
+			Val: 2,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		20, 2, 3, 4,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 2, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpDivByZeroALUOpConstant(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpDiv,
+			Val: 0,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "cannot divide by zero using ALUOpConstant" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMALUOpDivByZeroALUOpX(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		// Load byte 0 into X
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.TAX{},
+		// Load byte 1 into A
+		bpf.LoadAbsolute{
+			Off:  9,
+			Size: 1,
+		},
+		// Attempt to perform 1/0
+		bpf.ALUOpX{
+			Op: bpf.ALUOpDiv,
+		},
+		// Return 4 bytes if program does not terminate
+		bpf.LoadConstant{
+			Val: 12,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1, 3, 4,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 0, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpOr(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 2,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpOr,
+			Val: 0x01,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0x00, 0x10, 0x03, 0x04,
+		0x05, 0x06, 0x07, 0x08,
+		0x09, 0xff,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 9, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpAnd(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 2,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpAnd,
+			Val: 0x0019,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0xaa, 0x09,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpShiftLeft(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpShiftLeft,
+			Val: 0x01,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      0x02,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 9,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0x01, 0xaa,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpShiftRight(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpShiftRight,
+			Val: 0x01,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      0x04,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 9,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0x08, 0xff, 0xff,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpMod(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpMod,
+			Val: 20,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		30, 0, 0,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 2, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpModByZeroALUOpConstant(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpMod,
+			Val: 0,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "cannot divide by zero using ALUOpConstant" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMALUOpModByZeroALUOpX(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		// Load byte 0 into X
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.TAX{},
+		// Load byte 1 into A
+		bpf.LoadAbsolute{
+			Off:  9,
+			Size: 1,
+		},
+		// Attempt to perform 1%0
+		bpf.ALUOpX{
+			Op: bpf.ALUOpMod,
+		},
+		// Return 4 bytes if program does not terminate
+		bpf.LoadConstant{
+			Val: 12,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1, 3, 4,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 0, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpXor(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpXor,
+			Val: 0x0a,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      0x01,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 9,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0x0b, 0x00, 0x00, 0x00,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMALUOpUnknown(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.ALUOpConstant{
+			Op:  bpf.ALUOpAdd,
+			Val: 1,
+		},
+		// Verify that an unknown operation is a no-op
+		bpf.ALUOpConstant{
+			Op: 100,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      0x02,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 9,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		1,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
diff --git a/bpf/vm_bpf_test.go b/bpf/vm_bpf_test.go
new file mode 100644
index 0000000..4263623
--- /dev/null
+++ b/bpf/vm_bpf_test.go
@@ -0,0 +1,192 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf_test
+
+import (
+	"net"
+	"runtime"
+	"testing"
+	"time"
+
+	"golang.org/x/net/bpf"
+	"golang.org/x/net/ipv4"
+)
+
+// A virtualMachine is a BPF virtual machine which can process an
+// input packet against a BPF program and render a verdict.
+type virtualMachine interface {
+	Run(in []byte) (int, error)
+}
+
+// canUseOSVM indicates if the OS BPF VM is available on this platform.
+func canUseOSVM() bool {
+	// OS BPF VM can only be used on platforms where x/net/ipv4 supports
+	// attaching a BPF program to a socket.
+	switch runtime.GOOS {
+	case "linux":
+		return true
+	}
+
+	return false
+}
+
+// All BPF tests against both the Go VM and OS VM are assumed to
+// be used with a UDP socket.  As a result, the entire contents
+// of a UDP datagram is sent through the BPF program, but only
+// the body after the UDP header will ever be returned in output.
+
+// testVM sets up a Go BPF VM, and if available, a native OS BPF VM
+// for integration testing.
+func testVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func(), error) {
+	goVM, err := bpf.NewVM(filter)
+	if err != nil {
+		// Some tests expect an error, so this error must be returned
+		// instead of fatally exiting the test
+		return nil, nil, err
+	}
+
+	mvm := &multiVirtualMachine{
+		goVM: goVM,
+
+		t: t,
+	}
+
+	// If available, add the OS VM for tests which verify that both the Go
+	// VM and OS VM have exactly the same output for the same input program
+	// and packet.
+	done := func() {}
+	if canUseOSVM() {
+		osVM, osVMDone := testOSVM(t, filter)
+		done = func() { osVMDone() }
+		mvm.osVM = osVM
+	}
+
+	return mvm, done, nil
+}
+
+// udpHeaderLen is the length of a UDP header.
+const udpHeaderLen = 8
+
+// A multiVirtualMachine is a virtualMachine which can call out to both the Go VM
+// and the native OS VM, if the OS VM is available.
+type multiVirtualMachine struct {
+	goVM virtualMachine
+	osVM virtualMachine
+
+	t *testing.T
+}
+
+func (mvm *multiVirtualMachine) Run(in []byte) (int, error) {
+	if len(in) < udpHeaderLen {
+		mvm.t.Fatalf("input must be at least length of UDP header (%d), got: %d",
+			udpHeaderLen, len(in))
+	}
+
+	// All tests have a UDP header as part of input, because the OS VM
+	// packets always will.  For the Go VM, this output is trimmed before
+	// being sent back to tests.
+	goOut, goErr := mvm.goVM.Run(in)
+	if goOut >= udpHeaderLen {
+		goOut -= udpHeaderLen
+	}
+
+	// If Go output is larger than the size of the packet, packet filtering
+	// interop tests must trim the output bytes to the length of the packet.
+	// The BPF VM should not do this on its own, as other uses of it do
+	// not trim the output byte count.
+	trim := len(in) - udpHeaderLen
+	if goOut > trim {
+		goOut = trim
+	}
+
+	// When the OS VM is not available, process using the Go VM alone
+	if mvm.osVM == nil {
+		return goOut, goErr
+	}
+
+	// The OS VM will apply its own UDP header, so remove the pseudo header
+	// that the Go VM needs.
+	osOut, err := mvm.osVM.Run(in[udpHeaderLen:])
+	if err != nil {
+		mvm.t.Fatalf("error while running OS VM: %v", err)
+	}
+
+	// Verify both VMs return same number of bytes
+	var mismatch bool
+	if goOut != osOut {
+		mismatch = true
+		mvm.t.Logf("output byte count does not match:\n- go: %v\n- os: %v", goOut, osOut)
+	}
+
+	if mismatch {
+		mvm.t.Fatal("Go BPF and OS BPF packet outputs do not match")
+	}
+
+	return goOut, goErr
+}
+
+// An osVirtualMachine is a virtualMachine which uses the OS's BPF VM for
+// processing BPF programs.
+type osVirtualMachine struct {
+	l net.PacketConn
+	s net.Conn
+}
+
+// testOSVM creates a virtualMachine which uses the OS's BPF VM by injecting
+// packets into a UDP listener with a BPF program attached to it.
+func testOSVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func()) {
+	l, err := net.ListenPacket("udp4", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("failed to open OS VM UDP listener: %v", err)
+	}
+
+	prog, err := bpf.Assemble(filter)
+	if err != nil {
+		t.Fatalf("failed to compile BPF program: %v", err)
+	}
+
+	p := ipv4.NewPacketConn(l)
+	if err = p.SetBPF(prog); err != nil {
+		t.Fatalf("failed to attach BPF program to listener: %v", err)
+	}
+
+	s, err := net.Dial("udp4", l.LocalAddr().String())
+	if err != nil {
+		t.Fatalf("failed to dial connection to listener: %v", err)
+	}
+
+	done := func() {
+		_ = s.Close()
+		_ = l.Close()
+	}
+
+	return &osVirtualMachine{
+		l: l,
+		s: s,
+	}, done
+}
+
+// Run sends the input bytes into the OS's BPF VM and returns its verdict.
+func (vm *osVirtualMachine) Run(in []byte) (int, error) {
+	go func() {
+		_, _ = vm.s.Write(in)
+	}()
+
+	vm.l.SetDeadline(time.Now().Add(50 * time.Millisecond))
+
+	var b [512]byte
+	n, _, err := vm.l.ReadFrom(b[:])
+	if err != nil {
+		// A timeout indicates that BPF filtered out the packet, and thus,
+		// no input should be returned.
+		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
+			return n, nil
+		}
+
+		return n, err
+	}
+
+	return n, nil
+}
diff --git a/bpf/vm_instructions.go b/bpf/vm_instructions.go
new file mode 100644
index 0000000..7507923
--- /dev/null
+++ b/bpf/vm_instructions.go
@@ -0,0 +1,165 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf
+
+import (
+	"encoding/binary"
+	"fmt"
+)
+
+func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 {
+	return aluOpCommon(ins.Op, regA, ins.Val)
+}
+
+func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) {
+	// Guard against division or modulus by zero by terminating
+	// the program, as the OS BPF VM does
+	if regX == 0 {
+		switch ins.Op {
+		case ALUOpDiv, ALUOpMod:
+			return 0, false
+		}
+	}
+
+	return aluOpCommon(ins.Op, regA, regX), true
+}
+
+func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 {
+	switch op {
+	case ALUOpAdd:
+		return regA + value
+	case ALUOpSub:
+		return regA - value
+	case ALUOpMul:
+		return regA * value
+	case ALUOpDiv:
+		// Division by zero not permitted by NewVM and aluOpX checks
+		return regA / value
+	case ALUOpOr:
+		return regA | value
+	case ALUOpAnd:
+		return regA & value
+	case ALUOpShiftLeft:
+		return regA << value
+	case ALUOpShiftRight:
+		return regA >> value
+	case ALUOpMod:
+		// Modulus by zero not permitted by NewVM and aluOpX checks
+		return regA % value
+	case ALUOpXor:
+		return regA ^ value
+	default:
+		return regA
+	}
+}
+
+func jumpIf(ins JumpIf, value uint32) int {
+	var ok bool
+	inV := uint32(ins.Val)
+
+	switch ins.Cond {
+	case JumpEqual:
+		ok = value == inV
+	case JumpNotEqual:
+		ok = value != inV
+	case JumpGreaterThan:
+		ok = value > inV
+	case JumpLessThan:
+		ok = value < inV
+	case JumpGreaterOrEqual:
+		ok = value >= inV
+	case JumpLessOrEqual:
+		ok = value <= inV
+	case JumpBitsSet:
+		ok = (value & inV) != 0
+	case JumpBitsNotSet:
+		ok = (value & inV) == 0
+	}
+
+	if ok {
+		return int(ins.SkipTrue)
+	}
+
+	return int(ins.SkipFalse)
+}
+
+func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
+	offset := int(ins.Off)
+	size := int(ins.Size)
+
+	return loadCommon(in, offset, size)
+}
+
+func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) {
+	switch ins.Dst {
+	case RegA:
+		regA = ins.Val
+	case RegX:
+		regX = ins.Val
+	}
+
+	return regA, regX
+}
+
+func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) {
+	offset := int(ins.Off) + int(regX)
+	size := int(ins.Size)
+
+	return loadCommon(in, offset, size)
+}
+
+func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) {
+	offset := int(ins.Off)
+
+	if !inBounds(len(in), offset, 0) {
+		return 0, false
+	}
+
+	// Mask off high 4 bits and multiply low 4 bits by 4
+	return uint32(in[offset]&0x0f) * 4, true
+}
+
+func inBounds(inLen int, offset int, size int) bool {
+	return offset+size <= inLen
+}
+
+func loadCommon(in []byte, offset int, size int) (uint32, bool) {
+	if !inBounds(len(in), offset, size) {
+		return 0, false
+	}
+
+	switch size {
+	case 1:
+		return uint32(in[offset]), true
+	case 2:
+		return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
+	case 4:
+		return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
+	default:
+		panic(fmt.Sprintf("invalid load size: %d", size))
+	}
+}
+
+func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) {
+	switch ins.Dst {
+	case RegA:
+		regA = regScratch[ins.N]
+	case RegX:
+		regX = regScratch[ins.N]
+	}
+
+	return regA, regX
+}
+
+func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 {
+	switch ins.Src {
+	case RegA:
+		regScratch[ins.N] = regA
+	case RegX:
+		regScratch[ins.N] = regX
+	}
+
+	return regScratch
+}
diff --git a/bpf/vm_jump_test.go b/bpf/vm_jump_test.go
new file mode 100644
index 0000000..e0a3a98
--- /dev/null
+++ b/bpf/vm_jump_test.go
@@ -0,0 +1,380 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf_test
+
+import (
+	"testing"
+
+	"golang.org/x/net/bpf"
+)
+
+func TestVMJumpOne(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.Jump{
+			Skip: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 9,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		1,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMJumpOutOfProgram(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.Jump{
+			Skip: 1,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "cannot jump 1 instructions; jumping past program bounds" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMJumpIfTrueOutOfProgram(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			SkipTrue: 2,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "cannot jump 2 instructions in true case; jumping past program bounds" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMJumpIfFalseOutOfProgram(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.JumpIf{
+			Cond:      bpf.JumpEqual,
+			SkipFalse: 3,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "cannot jump 3 instructions in false case; jumping past program bounds" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMJumpIfEqual(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      1,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 9,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		1,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMJumpIfNotEqual(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.JumpIf{
+			Cond:      bpf.JumpNotEqual,
+			Val:       1,
+			SkipFalse: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 9,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		1,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMJumpIfGreaterThan(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 4,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpGreaterThan,
+			Val:      0x00010202,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 12,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1, 2, 3,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 4, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMJumpIfLessThan(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 4,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpLessThan,
+			Val:      0xff010203,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 12,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1, 2, 3,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 4, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMJumpIfGreaterOrEqual(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 4,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpGreaterOrEqual,
+			Val:      0x00010203,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 12,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1, 2, 3,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 4, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMJumpIfLessOrEqual(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 4,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpLessOrEqual,
+			Val:      0xff010203,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 12,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1, 2, 3,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 4, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMJumpIfBitsSet(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 2,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpBitsSet,
+			Val:      0x1122,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 10,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0x01, 0x02,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 2, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMJumpIfBitsNotSet(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 2,
+		},
+		bpf.JumpIf{
+			Cond:     bpf.JumpBitsNotSet,
+			Val:      0x1221,
+			SkipTrue: 1,
+		},
+		bpf.RetConstant{
+			Val: 0,
+		},
+		bpf.RetConstant{
+			Val: 10,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0x01, 0x02,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 2, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
diff --git a/bpf/vm_load_test.go b/bpf/vm_load_test.go
new file mode 100644
index 0000000..04578b6
--- /dev/null
+++ b/bpf/vm_load_test.go
@@ -0,0 +1,246 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf_test
+
+import (
+	"net"
+	"testing"
+
+	"golang.org/x/net/bpf"
+	"golang.org/x/net/ipv4"
+)
+
+func TestVMLoadAbsoluteOffsetOutOfBounds(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  100,
+			Size: 2,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1, 2, 3,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 0, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMLoadAbsoluteOffsetPlusSizeOutOfBounds(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 2,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 0, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMLoadAbsoluteBadInstructionSize(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Size: 5,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "assembling instruction 1: invalid load byte length 0" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMLoadConstantOK(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadConstant{
+			Dst: bpf.RegX,
+			Val: 9,
+		},
+		bpf.TXA{},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMLoadIndirectOutOfBounds(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadIndirect{
+			Off:  100,
+			Size: 1,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 0, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMLoadMemShiftOutOfBounds(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadMemShift{
+			Off: 100,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 0, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+const (
+	dhcp4Port = 53
+)
+
+func TestVMLoadMemShiftLoadIndirectNoResult(t *testing.T) {
+	vm, in, done := testDHCPv4(t)
+	defer done()
+
+	// Append mostly empty UDP header with incorrect DHCPv4 port
+	in = append(in, []byte{
+		0, 0,
+		0, dhcp4Port + 1,
+		0, 0,
+		0, 0,
+	}...)
+
+	out, err := vm.Run(in)
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 0, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMLoadMemShiftLoadIndirectOK(t *testing.T) {
+	vm, in, done := testDHCPv4(t)
+	defer done()
+
+	// Append mostly empty UDP header with correct DHCPv4 port
+	in = append(in, []byte{
+		0, 0,
+		0, dhcp4Port,
+		0, 0,
+		0, 0,
+	}...)
+
+	out, err := vm.Run(in)
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := len(in)-8, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func testDHCPv4(t *testing.T) (virtualMachine, []byte, func()) {
+	// DHCPv4 test data courtesy of David Anderson:
+	// https://github.com/google/netboot/blob/master/dhcp4/conn_linux.go#L59-L70
+	vm, done, err := testVM(t, []bpf.Instruction{
+		// Load IPv4 packet length
+		bpf.LoadMemShift{Off: 8},
+		// Get UDP dport
+		bpf.LoadIndirect{Off: 8 + 2, Size: 2},
+		// Correct dport?
+		bpf.JumpIf{Cond: bpf.JumpEqual, Val: dhcp4Port, SkipFalse: 1},
+		// Accept
+		bpf.RetConstant{Val: 1500},
+		// Ignore
+		bpf.RetConstant{Val: 0},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+
+	// Minimal requirements to make a valid IPv4 header
+	h := &ipv4.Header{
+		Len: ipv4.HeaderLen,
+		Src: net.IPv4(192, 168, 1, 1),
+		Dst: net.IPv4(192, 168, 1, 2),
+	}
+	hb, err := h.Marshal()
+	if err != nil {
+		t.Fatalf("failed to marshal IPv4 header: %v", err)
+	}
+
+	hb = append([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+	}, hb...)
+
+	return vm, hb, done
+}
diff --git a/bpf/vm_ret_test.go b/bpf/vm_ret_test.go
new file mode 100644
index 0000000..2d86eae
--- /dev/null
+++ b/bpf/vm_ret_test.go
@@ -0,0 +1,115 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf_test
+
+import (
+	"testing"
+
+	"golang.org/x/net/bpf"
+)
+
+func TestVMRetA(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		9,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMRetALargerThanInput(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 2,
+		},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 255,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 2, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMRetConstant(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.RetConstant{
+			Val: 9,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 1, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMRetConstantLargerThanInput(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.RetConstant{
+			Val: 16,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0, 1,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 2, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
diff --git a/bpf/vm_scratch_test.go b/bpf/vm_scratch_test.go
new file mode 100644
index 0000000..e600e3c
--- /dev/null
+++ b/bpf/vm_scratch_test.go
@@ -0,0 +1,247 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf_test
+
+import (
+	"testing"
+
+	"golang.org/x/net/bpf"
+)
+
+func TestVMStoreScratchInvalidScratchRegisterTooSmall(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.StoreScratch{
+			Src: bpf.RegA,
+			N:   -1,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMStoreScratchInvalidScratchRegisterTooLarge(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.StoreScratch{
+			Src: bpf.RegA,
+			N:   16,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMStoreScratchUnknownSourceRegister(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.StoreScratch{
+			Src: 100,
+			N:   0,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "assembling instruction 1: invalid source register 100" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMLoadScratchInvalidScratchRegisterTooSmall(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.LoadScratch{
+			Dst: bpf.RegX,
+			N:   -1,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMLoadScratchInvalidScratchRegisterTooLarge(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.LoadScratch{
+			Dst: bpf.RegX,
+			N:   16,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMLoadScratchUnknownDestinationRegister(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.LoadScratch{
+			Dst: 100,
+			N:   0,
+		},
+		bpf.RetA{},
+	})
+	if errStr(err) != "assembling instruction 1: invalid target register 100" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMStoreScratchLoadScratchOneValue(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		// Load byte 255
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		// Copy to X and store in scratch[0]
+		bpf.TAX{},
+		bpf.StoreScratch{
+			Src: bpf.RegX,
+			N:   0,
+		},
+		// Load byte 1
+		bpf.LoadAbsolute{
+			Off:  9,
+			Size: 1,
+		},
+		// Overwrite 1 with 255 from scratch[0]
+		bpf.LoadScratch{
+			Dst: bpf.RegA,
+			N:   0,
+		},
+		// Return 255
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		255, 1, 2,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 3, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
+
+func TestVMStoreScratchLoadScratchMultipleValues(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		// Load byte 10
+		bpf.LoadAbsolute{
+			Off:  8,
+			Size: 1,
+		},
+		// Store in scratch[0]
+		bpf.StoreScratch{
+			Src: bpf.RegA,
+			N:   0,
+		},
+		// Load byte 20
+		bpf.LoadAbsolute{
+			Off:  9,
+			Size: 1,
+		},
+		// Store in scratch[1]
+		bpf.StoreScratch{
+			Src: bpf.RegA,
+			N:   1,
+		},
+		// Load byte 30
+		bpf.LoadAbsolute{
+			Off:  10,
+			Size: 1,
+		},
+		// Store in scratch[2]
+		bpf.StoreScratch{
+			Src: bpf.RegA,
+			N:   2,
+		},
+		// Load byte 1
+		bpf.LoadAbsolute{
+			Off:  11,
+			Size: 1,
+		},
+		// Store in scratch[3]
+		bpf.StoreScratch{
+			Src: bpf.RegA,
+			N:   3,
+		},
+		// Load in byte 10 to X
+		bpf.LoadScratch{
+			Dst: bpf.RegX,
+			N:   0,
+		},
+		// Copy X -> A
+		bpf.TXA{},
+		// Verify value is 10
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      10,
+			SkipTrue: 1,
+		},
+		// Fail test if incorrect
+		bpf.RetConstant{
+			Val: 0,
+		},
+		// Load in byte 20 to A
+		bpf.LoadScratch{
+			Dst: bpf.RegA,
+			N:   1,
+		},
+		// Verify value is 20
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      20,
+			SkipTrue: 1,
+		},
+		// Fail test if incorrect
+		bpf.RetConstant{
+			Val: 0,
+		},
+		// Load in byte 30 to A
+		bpf.LoadScratch{
+			Dst: bpf.RegA,
+			N:   2,
+		},
+		// Verify value is 30
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      30,
+			SkipTrue: 1,
+		},
+		// Fail test if incorrect
+		bpf.RetConstant{
+			Val: 0,
+		},
+		// Return first two bytes on success
+		bpf.RetConstant{
+			Val: 10,
+		},
+	})
+	if err != nil {
+		t.Fatalf("failed to load BPF program: %v", err)
+	}
+	defer done()
+
+	out, err := vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		10, 20, 30, 1,
+	})
+	if err != nil {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+	if want, got := 2, out; want != got {
+		t.Fatalf("unexpected number of output bytes:\n- want: %d\n-  got: %d",
+			want, got)
+	}
+}
diff --git a/bpf/vm_test.go b/bpf/vm_test.go
new file mode 100644
index 0000000..be3e761
--- /dev/null
+++ b/bpf/vm_test.go
@@ -0,0 +1,144 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bpf_test
+
+import (
+	"fmt"
+	"testing"
+
+	"golang.org/x/net/bpf"
+)
+
+var _ bpf.Instruction = unknown{}
+
+type unknown struct{}
+
+func (unknown) Assemble() (bpf.RawInstruction, error) {
+	return bpf.RawInstruction{}, nil
+}
+
+func TestVMUnknownInstruction(t *testing.T) {
+	vm, done, err := testVM(t, []bpf.Instruction{
+		bpf.LoadConstant{
+			Dst: bpf.RegA,
+			Val: 100,
+		},
+		// Should terminate the program with an error immediately
+		unknown{},
+		bpf.RetA{},
+	})
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	defer done()
+
+	_, err = vm.Run([]byte{
+		0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff,
+		0x00, 0x00,
+	})
+	if errStr(err) != "unknown Instruction at index 1: bpf_test.unknown" {
+		t.Fatalf("unexpected error while running program: %v", err)
+	}
+}
+
+func TestVMNoReturnInstruction(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{
+		bpf.LoadConstant{
+			Dst: bpf.RegA,
+			Val: 1,
+		},
+	})
+	if errStr(err) != "BPF program must end with RetA or RetConstant" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func TestVMNoInputInstructions(t *testing.T) {
+	_, _, err := testVM(t, []bpf.Instruction{})
+	if errStr(err) != "one or more Instructions must be specified" {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+// ExampleNewVM demonstrates usage of a VM, using an Ethernet frame
+// as input and checking its EtherType to determine if it should be accepted.
+func ExampleNewVM() {
+	// Offset | Length | Comment
+	// -------------------------
+	//   00   |   06   | Ethernet destination MAC address
+	//   06   |   06   | Ethernet source MAC address
+	//   12   |   02   | Ethernet EtherType
+	const (
+		etOff = 12
+		etLen = 2
+
+		etARP = 0x0806
+	)
+
+	// Set up a VM to filter traffic based on if its EtherType
+	// matches the ARP EtherType.
+	vm, err := bpf.NewVM([]bpf.Instruction{
+		// Load EtherType value from Ethernet header
+		bpf.LoadAbsolute{
+			Off:  etOff,
+			Size: etLen,
+		},
+		// If EtherType is equal to the ARP EtherType, jump to allow
+		// packet to be accepted
+		bpf.JumpIf{
+			Cond:     bpf.JumpEqual,
+			Val:      etARP,
+			SkipTrue: 1,
+		},
+		// EtherType does not match the ARP EtherType
+		bpf.RetConstant{
+			Val: 0,
+		},
+		// EtherType matches the ARP EtherType, accept up to 1500
+		// bytes of packet
+		bpf.RetConstant{
+			Val: 1500,
+		},
+	})
+	if err != nil {
+		panic(fmt.Sprintf("failed to load BPF program: %v", err))
+	}
+
+	// Create an Ethernet frame with the ARP EtherType for testing
+	frame := []byte{
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+		0x08, 0x06,
+		// Payload ommitted for brevity
+	}
+
+	// Run our VM's BPF program using the Ethernet frame as input
+	out, err := vm.Run(frame)
+	if err != nil {
+		panic(fmt.Sprintf("failed to accept Ethernet frame: %v", err))
+	}
+
+	// BPF VM can return a byte count greater than the number of input
+	// bytes, so trim the output to match the input byte length
+	if out > len(frame) {
+		out = len(frame)
+	}
+
+	fmt.Printf("out: %d bytes", out)
+
+	// Output:
+	// out: 14 bytes
+}
+
+// errStr returns the string representation of an error, or
+// "<nil>" if it is nil.
+func errStr(err error) string {
+	if err == nil {
+		return "<nil>"
+	}
+
+	return err.Error()
+}