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.
13 "golang.org/x/net/bpf"
14 "golang.org/x/net/ipv4"
17 // A virtualMachine is a BPF virtual machine which can process an
18 // input packet against a BPF program and render a verdict.
19 type virtualMachine interface {
20 Run(in []byte) (int, error)
23 // canUseOSVM indicates if the OS BPF VM is available on this platform.
24 func canUseOSVM() bool {
25 // OS BPF VM can only be used on platforms where x/net/ipv4 supports
26 // attaching a BPF program to a socket.
35 // All BPF tests against both the Go VM and OS VM are assumed to
36 // be used with a UDP socket. As a result, the entire contents
37 // of a UDP datagram is sent through the BPF program, but only
38 // the body after the UDP header will ever be returned in output.
40 // testVM sets up a Go BPF VM, and if available, a native OS BPF VM
41 // for integration testing.
42 func testVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func(), error) {
43 goVM, err := bpf.NewVM(filter)
45 // Some tests expect an error, so this error must be returned
46 // instead of fatally exiting the test
50 mvm := &multiVirtualMachine{
56 // If available, add the OS VM for tests which verify that both the Go
57 // VM and OS VM have exactly the same output for the same input program
61 osVM, osVMDone := testOSVM(t, filter)
62 done = func() { osVMDone() }
69 // udpHeaderLen is the length of a UDP header.
70 const udpHeaderLen = 8
72 // A multiVirtualMachine is a virtualMachine which can call out to both the Go VM
73 // and the native OS VM, if the OS VM is available.
74 type multiVirtualMachine struct {
81 func (mvm *multiVirtualMachine) Run(in []byte) (int, error) {
82 if len(in) < udpHeaderLen {
83 mvm.t.Fatalf("input must be at least length of UDP header (%d), got: %d",
84 udpHeaderLen, len(in))
87 // All tests have a UDP header as part of input, because the OS VM
88 // packets always will. For the Go VM, this output is trimmed before
89 // being sent back to tests.
90 goOut, goErr := mvm.goVM.Run(in)
91 if goOut >= udpHeaderLen {
95 // If Go output is larger than the size of the packet, packet filtering
96 // interop tests must trim the output bytes to the length of the packet.
97 // The BPF VM should not do this on its own, as other uses of it do
98 // not trim the output byte count.
99 trim := len(in) - udpHeaderLen
104 // When the OS VM is not available, process using the Go VM alone
109 // The OS VM will apply its own UDP header, so remove the pseudo header
110 // that the Go VM needs.
111 osOut, err := mvm.osVM.Run(in[udpHeaderLen:])
113 mvm.t.Fatalf("error while running OS VM: %v", err)
116 // Verify both VMs return same number of bytes
120 mvm.t.Logf("output byte count does not match:\n- go: %v\n- os: %v", goOut, osOut)
124 mvm.t.Fatal("Go BPF and OS BPF packet outputs do not match")
130 // An osVirtualMachine is a virtualMachine which uses the OS's BPF VM for
131 // processing BPF programs.
132 type osVirtualMachine struct {
137 // testOSVM creates a virtualMachine which uses the OS's BPF VM by injecting
138 // packets into a UDP listener with a BPF program attached to it.
139 func testOSVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func()) {
140 l, err := net.ListenPacket("udp4", "127.0.0.1:0")
142 t.Fatalf("failed to open OS VM UDP listener: %v", err)
145 prog, err := bpf.Assemble(filter)
147 t.Fatalf("failed to compile BPF program: %v", err)
150 p := ipv4.NewPacketConn(l)
151 if err = p.SetBPF(prog); err != nil {
152 t.Fatalf("failed to attach BPF program to listener: %v", err)
155 s, err := net.Dial("udp4", l.LocalAddr().String())
157 t.Fatalf("failed to dial connection to listener: %v", err)
165 return &osVirtualMachine{
171 // Run sends the input bytes into the OS's BPF VM and returns its verdict.
172 func (vm *osVirtualMachine) Run(in []byte) (int, error) {
174 _, _ = vm.s.Write(in)
177 vm.l.SetDeadline(time.Now().Add(50 * time.Millisecond))
180 n, _, err := vm.l.ReadFrom(b[:])
182 // A timeout indicates that BPF filtered out the packet, and thus,
183 // no input should be returned.
184 if nerr, ok := err.(net.Error); ok && nerr.Timeout() {