github.com/Andyfoo/golang/x/net@v0.0.0-20190901054642-57c1bf301704/bpf/vm_bpf_test.go (about) 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 5 package bpf_test 6 7 import ( 8 "net" 9 "runtime" 10 "testing" 11 "time" 12 13 "github.com/Andyfoo/golang/x/net/bpf" 14 "github.com/Andyfoo/golang/x/net/ipv4" 15 "github.com/Andyfoo/golang/x/net/ipv6" 16 "github.com/Andyfoo/golang/x/net/nettest" 17 ) 18 19 // A virtualMachine is a BPF virtual machine which can process an 20 // input packet against a BPF program and render a verdict. 21 type virtualMachine interface { 22 Run(in []byte) (int, error) 23 } 24 25 // canUseOSVM indicates if the OS BPF VM is available on this platform. 26 func canUseOSVM() bool { 27 // OS BPF VM can only be used on platforms where x/net/ipv4 supports 28 // attaching a BPF program to a socket. 29 switch runtime.GOOS { 30 case "linux": 31 return true 32 } 33 34 return false 35 } 36 37 // All BPF tests against both the Go VM and OS VM are assumed to 38 // be used with a UDP socket. As a result, the entire contents 39 // of a UDP datagram is sent through the BPF program, but only 40 // the body after the UDP header will ever be returned in output. 41 42 // testVM sets up a Go BPF VM, and if available, a native OS BPF VM 43 // for integration testing. 44 func testVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func(), error) { 45 goVM, err := bpf.NewVM(filter) 46 if err != nil { 47 // Some tests expect an error, so this error must be returned 48 // instead of fatally exiting the test 49 return nil, nil, err 50 } 51 52 mvm := &multiVirtualMachine{ 53 goVM: goVM, 54 55 t: t, 56 } 57 58 // If available, add the OS VM for tests which verify that both the Go 59 // VM and OS VM have exactly the same output for the same input program 60 // and packet. 61 done := func() {} 62 if canUseOSVM() { 63 osVM, osVMDone := testOSVM(t, filter) 64 done = func() { osVMDone() } 65 mvm.osVM = osVM 66 } 67 68 return mvm, done, nil 69 } 70 71 // udpHeaderLen is the length of a UDP header. 72 const udpHeaderLen = 8 73 74 // A multiVirtualMachine is a virtualMachine which can call out to both the Go VM 75 // and the native OS VM, if the OS VM is available. 76 type multiVirtualMachine struct { 77 goVM virtualMachine 78 osVM virtualMachine 79 80 t *testing.T 81 } 82 83 func (mvm *multiVirtualMachine) Run(in []byte) (int, error) { 84 if len(in) < udpHeaderLen { 85 mvm.t.Fatalf("input must be at least length of UDP header (%d), got: %d", 86 udpHeaderLen, len(in)) 87 } 88 89 // All tests have a UDP header as part of input, because the OS VM 90 // packets always will. For the Go VM, this output is trimmed before 91 // being sent back to tests. 92 goOut, goErr := mvm.goVM.Run(in) 93 if goOut >= udpHeaderLen { 94 goOut -= udpHeaderLen 95 } 96 97 // If Go output is larger than the size of the packet, packet filtering 98 // interop tests must trim the output bytes to the length of the packet. 99 // The BPF VM should not do this on its own, as other uses of it do 100 // not trim the output byte count. 101 trim := len(in) - udpHeaderLen 102 if goOut > trim { 103 goOut = trim 104 } 105 106 // When the OS VM is not available, process using the Go VM alone 107 if mvm.osVM == nil { 108 return goOut, goErr 109 } 110 111 // The OS VM will apply its own UDP header, so remove the pseudo header 112 // that the Go VM needs. 113 osOut, err := mvm.osVM.Run(in[udpHeaderLen:]) 114 if err != nil { 115 mvm.t.Fatalf("error while running OS VM: %v", err) 116 } 117 118 // Verify both VMs return same number of bytes 119 var mismatch bool 120 if goOut != osOut { 121 mismatch = true 122 mvm.t.Logf("output byte count does not match:\n- go: %v\n- os: %v", goOut, osOut) 123 } 124 125 if mismatch { 126 mvm.t.Fatal("Go BPF and OS BPF packet outputs do not match") 127 } 128 129 return goOut, goErr 130 } 131 132 // An osVirtualMachine is a virtualMachine which uses the OS's BPF VM for 133 // processing BPF programs. 134 type osVirtualMachine struct { 135 l net.PacketConn 136 s net.Conn 137 } 138 139 // testOSVM creates a virtualMachine which uses the OS's BPF VM by injecting 140 // packets into a UDP listener with a BPF program attached to it. 141 func testOSVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func()) { 142 l, err := nettest.NewLocalPacketListener("udp") 143 if err != nil { 144 t.Fatalf("failed to open OS VM UDP listener: %v", err) 145 } 146 147 prog, err := bpf.Assemble(filter) 148 if err != nil { 149 t.Fatalf("failed to compile BPF program: %v", err) 150 } 151 152 ip := l.LocalAddr().(*net.UDPAddr).IP 153 if ip.To4() != nil && ip.To16() == nil { 154 err = ipv4.NewPacketConn(l).SetBPF(prog) 155 } else { 156 err = ipv6.NewPacketConn(l).SetBPF(prog) 157 } 158 if err != nil { 159 t.Fatalf("failed to attach BPF program to listener: %v", err) 160 } 161 162 s, err := net.Dial(l.LocalAddr().Network(), l.LocalAddr().String()) 163 if err != nil { 164 t.Fatalf("failed to dial connection to listener: %v", err) 165 } 166 167 done := func() { 168 _ = s.Close() 169 _ = l.Close() 170 } 171 172 return &osVirtualMachine{ 173 l: l, 174 s: s, 175 }, done 176 } 177 178 // Run sends the input bytes into the OS's BPF VM and returns its verdict. 179 func (vm *osVirtualMachine) Run(in []byte) (int, error) { 180 go func() { 181 _, _ = vm.s.Write(in) 182 }() 183 184 vm.l.SetDeadline(time.Now().Add(50 * time.Millisecond)) 185 186 var b [512]byte 187 n, _, err := vm.l.ReadFrom(b[:]) 188 if err != nil { 189 // A timeout indicates that BPF filtered out the packet, and thus, 190 // no input should be returned. 191 if nerr, ok := err.(net.Error); ok && nerr.Timeout() { 192 return n, nil 193 } 194 195 return n, err 196 } 197 198 return n, nil 199 }