1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package bpf_test
6
7import (
8	"fmt"
9	"testing"
10
11	"golang.org/x/net/bpf"
12)
13
14var _ bpf.Instruction = unknown{}
15
16type unknown struct{}
17
18func (unknown) Assemble() (bpf.RawInstruction, error) {
19	return bpf.RawInstruction{}, nil
20}
21
22func TestVMUnknownInstruction(t *testing.T) {
23	vm, done, err := testVM(t, []bpf.Instruction{
24		bpf.LoadConstant{
25			Dst: bpf.RegA,
26			Val: 100,
27		},
28		// Should terminate the program with an error immediately
29		unknown{},
30		bpf.RetA{},
31	})
32	if err != nil {
33		t.Fatalf("unexpected error: %v", err)
34	}
35	defer done()
36
37	_, err = vm.Run([]byte{
38		0xff, 0xff, 0xff, 0xff,
39		0xff, 0xff, 0xff, 0xff,
40		0x00, 0x00,
41	})
42	if errStr(err) != "unknown Instruction at index 1: bpf_test.unknown" {
43		t.Fatalf("unexpected error while running program: %v", err)
44	}
45}
46
47func TestVMNoReturnInstruction(t *testing.T) {
48	_, _, err := testVM(t, []bpf.Instruction{
49		bpf.LoadConstant{
50			Dst: bpf.RegA,
51			Val: 1,
52		},
53	})
54	if errStr(err) != "BPF program must end with RetA or RetConstant" {
55		t.Fatalf("unexpected error: %v", err)
56	}
57}
58
59func TestVMNoInputInstructions(t *testing.T) {
60	_, _, err := testVM(t, []bpf.Instruction{})
61	if errStr(err) != "one or more Instructions must be specified" {
62		t.Fatalf("unexpected error: %v", err)
63	}
64}
65
66// ExampleNewVM demonstrates usage of a VM, using an Ethernet frame
67// as input and checking its EtherType to determine if it should be accepted.
68func ExampleNewVM() {
69	// Offset | Length | Comment
70	// -------------------------
71	//   00   |   06   | Ethernet destination MAC address
72	//   06   |   06   | Ethernet source MAC address
73	//   12   |   02   | Ethernet EtherType
74	const (
75		etOff = 12
76		etLen = 2
77
78		etARP = 0x0806
79	)
80
81	// Set up a VM to filter traffic based on if its EtherType
82	// matches the ARP EtherType.
83	vm, err := bpf.NewVM([]bpf.Instruction{
84		// Load EtherType value from Ethernet header
85		bpf.LoadAbsolute{
86			Off:  etOff,
87			Size: etLen,
88		},
89		// If EtherType is equal to the ARP EtherType, jump to allow
90		// packet to be accepted
91		bpf.JumpIf{
92			Cond:     bpf.JumpEqual,
93			Val:      etARP,
94			SkipTrue: 1,
95		},
96		// EtherType does not match the ARP EtherType
97		bpf.RetConstant{
98			Val: 0,
99		},
100		// EtherType matches the ARP EtherType, accept up to 1500
101		// bytes of packet
102		bpf.RetConstant{
103			Val: 1500,
104		},
105	})
106	if err != nil {
107		panic(fmt.Sprintf("failed to load BPF program: %v", err))
108	}
109
110	// Create an Ethernet frame with the ARP EtherType for testing
111	frame := []byte{
112		0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
113		0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
114		0x08, 0x06,
115		// Payload omitted for brevity
116	}
117
118	// Run our VM's BPF program using the Ethernet frame as input
119	out, err := vm.Run(frame)
120	if err != nil {
121		panic(fmt.Sprintf("failed to accept Ethernet frame: %v", err))
122	}
123
124	// BPF VM can return a byte count greater than the number of input
125	// bytes, so trim the output to match the input byte length
126	if out > len(frame) {
127		out = len(frame)
128	}
129
130	fmt.Printf("out: %d bytes", out)
131
132	// Output:
133	// out: 14 bytes
134}
135
136// errStr returns the string representation of an error, or
137// "<nil>" if it is nil.
138func errStr(err error) string {
139	if err == nil {
140		return "<nil>"
141	}
142
143	return err.Error()
144}
145