// testOSVM creates a virtualMachine which uses the OS's BPF VM by injecting // packets into a UDP listener with a BPF program attached to it. func testOSVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func()) { l, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { t.Fatalf("failed to open OS VM UDP listener: %v", err) } prog, err := bpf.Assemble(filter) if err != nil { t.Fatalf("failed to compile BPF program: %v", err) } p := ipv4.NewPacketConn(l) if err = p.SetBPF(prog); err != nil { t.Fatalf("failed to attach BPF program to listener: %v", err) } s, err := net.Dial("udp4", l.LocalAddr().String()) if err != nil { t.Fatalf("failed to dial connection to listener: %v", err) } done := func() { _ = s.Close() _ = l.Close() } return &osVirtualMachine{ l: l, s: s, }, done }
// SetBPF attaches an assembled BPF program to a raw net.PacketConn. func (p *packetConn) SetBPF(filter []bpf.RawInstruction) error { // Base filter filters traffic based on EtherType base, err := bpf.Assemble(baseFilter(p.proto)) if err != nil { return err } // Append user filter to base filter, translate to raw format, // and apply to BPF device return syscall.SetBpf(p.fd, assembleBpfInsn(append(base, filter...))) }
// configureBPF configures a BPF device with the specified file descriptor to // use the specified network and interface and protocol. func configureBPF(fd int, ifi *net.Interface, proto Protocol) (int, error) { // Use specified interface with BPF device if err := syscall.SetBpfInterface(fd, ifi.Name); err != nil { return 0, err } // Inform BPF to send us its data immediately if err := syscall.SetBpfImmediate(fd, 1); err != nil { return 0, err } // Check buffer size of BPF device buflen, err := syscall.BpfBuflen(fd) if err != nil { return 0, err } // Do not automatically complete source address in ethernet headers if err := syscall.SetBpfHeadercmpl(fd, 0); err != nil { return 0, err } // Only retrieve incoming traffic using BPF device if err := setBPFDirection(fd, bpfDIn); err != nil { return 0, err } // Build and apply base BPF filter which checks for correct EtherType // on incoming packets prog, err := bpf.Assemble(baseInterfaceFilter(proto, ifi.MTU)) if err != nil { return 0, err } if err := syscall.SetBpf(fd, assembleBpfInsn(prog)); err != nil { return 0, err } // Flush any packets currently in the BPF device's buffer if err := syscall.FlushBpf(fd); err != nil { return 0, err } return buflen, nil }
// mustAssembleBPF assembles a BPF program to filter out packets not bound // for this server. func (s *Server) mustAssembleBPF(mtu int) []bpf.RawInstruction { // This BPF program filters out packets that are not bound for this server // by checking against both the AoE broadcast addresses and this server's // major/minor address combination. The structure of the incoming ethernet // frame and AoE header is as follows: // // Offset | Length | Comment // ------------------------- // 00 | 06 | Ethernet destination MAC address // 06 | 06 | Ethernet source MAC address // 12 | 02 | Ethernet EtherType // ------------------------- // 14 | 01 | AoE version + flags // 15 | 01 | AoE error // 16 | 02 | AoE major address // 18 | 01 | AoE minor address // // Thus, our BPF program needs to check for: // - major address: offset 16, length 2 // - minor address: offset 18, length 1 const ( majorOffset = 16 majorLen = 2 minorOffset = 18 minorLen = 1 ) // TODO(mdlayher): this routine likely belongs in package AoE, once the server // component is more complete. prog, err := bpf.Assemble([]bpf.Instruction{ // Load major address value from AoE header bpf.LoadAbsolute{ Off: majorOffset, Size: majorLen, }, // If major address is equal to broadcast address, jump to minor address // filtering bpf.JumpIf{ Cond: bpf.JumpEqual, Val: uint32(aoe.BroadcastMajor), SkipTrue: 2, }, // If major address is equal to our server's, jump to minor address // filtering bpf.JumpIf{ Cond: bpf.JumpEqual, Val: uint32(s.major), SkipTrue: 1, }, // Major address is not our server's or broadcast address bpf.RetConstant{ Val: 0, }, // Load minor address value from AoE header bpf.LoadAbsolute{ Off: minorOffset, Size: minorLen, }, // If minor address is equal to broadcast address, jump to accept packet bpf.JumpIf{ Cond: bpf.JumpEqual, Val: uint32(aoe.BroadcastMinor), SkipTrue: 2, }, // If minor address is equal to our server's, jump to accept packet bpf.JumpIf{ Cond: bpf.JumpEqual, Val: uint32(s.minor), SkipTrue: 1, }, // Minor address is not our server's or broadcast address bpf.RetConstant{ Val: 0, }, // Accept the packet bytes up to the interface's MTU bpf.RetConstant{ Val: uint32(mtu), }, }) if err != nil { panic(fmt.Sprintf("failed to assemble BPF program: %v", err)) } return prog }
func TestBPF(t *testing.T) { if runtime.GOOS != "linux" { t.Skipf("not supported on %s", runtime.GOOS) } l, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer l.Close() p := ipv4.NewPacketConn(l) // This filter accepts UDP packets whose first payload byte is // even. prog, err := bpf.Assemble([]bpf.Instruction{ // Load the first byte of the payload (skipping UDP header). bpf.LoadAbsolute{Off: 8, Size: 1}, // Select LSB of the byte. bpf.ALUOpConstant{Op: bpf.ALUOpAnd, Val: 1}, // Byte is even? bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0, SkipFalse: 1}, // Accept. bpf.RetConstant{Val: 4096}, // Ignore. bpf.RetConstant{Val: 0}, }) if err != nil { t.Fatalf("compiling BPF: %s", err) } if err = p.SetBPF(prog); err != nil { t.Fatalf("attaching filter to Conn: %s", err) } s, err := net.Dial("udp4", l.LocalAddr().String()) if err != nil { t.Fatal(err) } defer s.Close() go func() { for i := byte(0); i < 10; i++ { s.Write([]byte{i}) } }() l.SetDeadline(time.Now().Add(2 * time.Second)) seen := make([]bool, 5) for { var b [512]byte n, _, err := l.ReadFrom(b[:]) if err != nil { t.Fatalf("reading from listener: %s", err) } if n != 1 { t.Fatalf("unexpected packet length, want 1, got %d", n) } if b[0] >= 10 { t.Fatalf("unexpected byte, want 0-9, got %d", b[0]) } if b[0]%2 != 0 { t.Fatalf("got odd byte %d, wanted only even bytes", b[0]) } seen[b[0]/2] = true seenAll := true for _, v := range seen { if !v { seenAll = false break } } if seenAll { break } } }