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  }