vm_bpf_test.go 5.1 KB

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