vm_bpf_test.go 4.9 KB

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