diff options
Diffstat (limited to 'pkg/tcpip/link/fdbased')
-rw-r--r-- | pkg/tcpip/link/fdbased/BUILD | 41 | ||||
-rw-r--r-- | pkg/tcpip/link/fdbased/endpoint_test.go | 623 | ||||
-rw-r--r-- | pkg/tcpip/link/fdbased/fdbased_state_autogen.go | 8 | ||||
-rw-r--r-- | pkg/tcpip/link/fdbased/fdbased_unsafe_state_autogen.go | 6 |
4 files changed, 14 insertions, 664 deletions
diff --git a/pkg/tcpip/link/fdbased/BUILD b/pkg/tcpip/link/fdbased/BUILD deleted file mode 100644 index d971194e6..000000000 --- a/pkg/tcpip/link/fdbased/BUILD +++ /dev/null @@ -1,41 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "fdbased", - srcs = [ - "endpoint.go", - "endpoint_unsafe.go", - "mmap.go", - "mmap_stub.go", - "mmap_unsafe.go", - "packet_dispatchers.go", - ], - visibility = ["//visibility:public"], - deps = [ - "//pkg/iovec", - "//pkg/sync", - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//pkg/tcpip/link/rawfile", - "//pkg/tcpip/stack", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "fdbased_test", - size = "small", - srcs = ["endpoint_test.go"], - library = ":fdbased", - deps = [ - "//pkg/tcpip", - "//pkg/tcpip/buffer", - "//pkg/tcpip/header", - "//pkg/tcpip/stack", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/pkg/tcpip/link/fdbased/endpoint_test.go b/pkg/tcpip/link/fdbased/endpoint_test.go deleted file mode 100644 index 8aad338b6..000000000 --- a/pkg/tcpip/link/fdbased/endpoint_test.go +++ /dev/null @@ -1,623 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build linux - -package fdbased - -import ( - "bytes" - "fmt" - "math/rand" - "reflect" - "testing" - "time" - "unsafe" - - "github.com/google/go-cmp/cmp" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/buffer" - "gvisor.dev/gvisor/pkg/tcpip/header" - "gvisor.dev/gvisor/pkg/tcpip/stack" -) - -const ( - mtu = 1500 - laddr = tcpip.LinkAddress("\x11\x22\x33\x44\x55\x66") - raddr = tcpip.LinkAddress("\x77\x88\x99\xaa\xbb\xcc") - proto = 10 - csumOffset = 48 - gsoMSS = 500 -) - -type packetInfo struct { - Raddr tcpip.LinkAddress - Proto tcpip.NetworkProtocolNumber - Contents *stack.PacketBuffer -} - -type packetContents struct { - LinkHeader buffer.View - NetworkHeader buffer.View - TransportHeader buffer.View - Data buffer.View -} - -func checkPacketInfoEqual(t *testing.T, got, want packetInfo) { - t.Helper() - if diff := cmp.Diff( - want, got, - cmp.Transformer("ExtractPacketBuffer", func(pk *stack.PacketBuffer) *packetContents { - if pk == nil { - return nil - } - return &packetContents{ - LinkHeader: pk.LinkHeader().View(), - NetworkHeader: pk.NetworkHeader().View(), - TransportHeader: pk.TransportHeader().View(), - Data: pk.Data().AsRange().ToOwnedView(), - } - }), - ); diff != "" { - t.Errorf("unexpected packetInfo (-want +got):\n%s", diff) - } -} - -type context struct { - t *testing.T - readFDs []int - writeFDs []int - ep stack.LinkEndpoint - ch chan packetInfo - done chan struct{} -} - -func newContext(t *testing.T, opt *Options) *context { - firstFDPair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_SEQPACKET, 0) - if err != nil { - t.Fatalf("Socketpair failed: %v", err) - } - secondFDPair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_SEQPACKET, 0) - if err != nil { - t.Fatalf("Socketpair failed: %v", err) - } - - done := make(chan struct{}, 2) - opt.ClosedFunc = func(tcpip.Error) { - done <- struct{}{} - } - - opt.FDs = []int{firstFDPair[1], secondFDPair[1]} - ep, err := New(opt) - if err != nil { - t.Fatalf("Failed to create FD endpoint: %v", err) - } - - c := &context{ - t: t, - readFDs: []int{firstFDPair[0], secondFDPair[0]}, - writeFDs: opt.FDs, - ep: ep, - ch: make(chan packetInfo, 100), - done: done, - } - - ep.Attach(c) - - return c -} - -func (c *context) cleanup() { - for _, fd := range c.readFDs { - unix.Close(fd) - } - <-c.done - <-c.done - for _, fd := range c.writeFDs { - unix.Close(fd) - } -} - -func (c *context) DeliverNetworkPacket(remote tcpip.LinkAddress, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { - c.ch <- packetInfo{remote, protocol, pkt} -} - -func (c *context) DeliverOutboundPacket(remote tcpip.LinkAddress, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { - panic("unimplemented") -} - -func TestNoEthernetProperties(t *testing.T) { - c := newContext(t, &Options{MTU: mtu}) - defer c.cleanup() - - if want, v := uint16(0), c.ep.MaxHeaderLength(); want != v { - t.Fatalf("MaxHeaderLength() = %v, want %v", v, want) - } - - if want, v := uint32(mtu), c.ep.MTU(); want != v { - t.Fatalf("MTU() = %v, want %v", v, want) - } -} - -func TestEthernetProperties(t *testing.T) { - c := newContext(t, &Options{EthernetHeader: true, MTU: mtu}) - defer c.cleanup() - - if want, v := uint16(header.EthernetMinimumSize), c.ep.MaxHeaderLength(); want != v { - t.Fatalf("MaxHeaderLength() = %v, want %v", v, want) - } - - if want, v := uint32(mtu), c.ep.MTU(); want != v { - t.Fatalf("MTU() = %v, want %v", v, want) - } -} - -func TestAddress(t *testing.T) { - addrs := []tcpip.LinkAddress{"", "abc", "def"} - for _, a := range addrs { - t.Run(fmt.Sprintf("Address: %q", a), func(t *testing.T) { - c := newContext(t, &Options{Address: a, MTU: mtu}) - defer c.cleanup() - - if want, v := a, c.ep.LinkAddress(); want != v { - t.Fatalf("LinkAddress() = %v, want %v", v, want) - } - }) - } -} - -func testWritePacket(t *testing.T, plen int, eth bool, gsoMaxSize uint32, hash uint32) { - c := newContext(t, &Options{Address: laddr, MTU: mtu, EthernetHeader: eth, GSOMaxSize: gsoMaxSize}) - defer c.cleanup() - - var r stack.RouteInfo - r.RemoteLinkAddress = raddr - - // Build payload. - payload := buffer.NewView(plen) - if _, err := rand.Read(payload); err != nil { - t.Fatalf("rand.Read(payload): %s", err) - } - - // Build packet buffer. - const netHdrLen = 100 - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: int(c.ep.MaxHeaderLength()) + netHdrLen, - Data: payload.ToVectorisedView(), - }) - pkt.Hash = hash - - // Build header. - b := pkt.NetworkHeader().Push(netHdrLen) - if _, err := rand.Read(b); err != nil { - t.Fatalf("rand.Read(b): %s", err) - } - - // Write. - want := append(append(buffer.View(nil), b...), payload...) - const l3HdrLen = header.IPv6MinimumSize - if gsoMaxSize != 0 { - pkt.GSOOptions = stack.GSO{ - Type: stack.GSOTCPv6, - NeedsCsum: true, - CsumOffset: csumOffset, - MSS: gsoMSS, - L3HdrLen: l3HdrLen, - } - } - if err := c.ep.WritePacket(r, proto, pkt); err != nil { - t.Fatalf("WritePacket failed: %v", err) - } - - // Read from the corresponding FD, then compare with what we wrote. - b = make([]byte, mtu) - fd := c.readFDs[hash%uint32(len(c.readFDs))] - n, err := unix.Read(fd, b) - if err != nil { - t.Fatalf("Read failed: %v", err) - } - b = b[:n] - if gsoMaxSize != 0 { - vnetHdr := *(*virtioNetHdr)(unsafe.Pointer(&b[0])) - if vnetHdr.flags&_VIRTIO_NET_HDR_F_NEEDS_CSUM == 0 { - t.Fatalf("virtioNetHdr.flags %v doesn't contain %v", vnetHdr.flags, _VIRTIO_NET_HDR_F_NEEDS_CSUM) - } - const csumStart = header.EthernetMinimumSize + l3HdrLen - if vnetHdr.csumStart != csumStart { - t.Fatalf("vnetHdr.csumStart = %v, want %v", vnetHdr.csumStart, csumStart) - } - if vnetHdr.csumOffset != csumOffset { - t.Fatalf("vnetHdr.csumOffset = %v, want %v", vnetHdr.csumOffset, csumOffset) - } - gsoType := uint8(0) - if plen > gsoMSS { - gsoType = _VIRTIO_NET_HDR_GSO_TCPV6 - } - if vnetHdr.gsoType != gsoType { - t.Fatalf("vnetHdr.gsoType = %v, want %v", vnetHdr.gsoType, gsoType) - } - b = b[virtioNetHdrSize:] - } - if eth { - h := header.Ethernet(b) - b = b[header.EthernetMinimumSize:] - - if a := h.SourceAddress(); a != laddr { - t.Fatalf("SourceAddress() = %v, want %v", a, laddr) - } - - if a := h.DestinationAddress(); a != raddr { - t.Fatalf("DestinationAddress() = %v, want %v", a, raddr) - } - - if et := h.Type(); et != proto { - t.Fatalf("Type() = %v, want %v", et, proto) - } - } - if len(b) != len(want) { - t.Fatalf("Read returned %v bytes, want %v", len(b), len(want)) - } - if !bytes.Equal(b, want) { - t.Fatalf("Read returned %x, want %x", b, want) - } -} - -func TestWritePacket(t *testing.T) { - lengths := []int{0, 100, 1000} - eths := []bool{true, false} - gsos := []uint32{0, 32768} - - for _, eth := range eths { - for _, plen := range lengths { - for _, gso := range gsos { - t.Run( - fmt.Sprintf("Eth=%v,PayloadLen=%v,GSOMaxSize=%v", eth, plen, gso), - func(t *testing.T) { - testWritePacket(t, plen, eth, gso, 0) - }, - ) - } - } - } -} - -func TestHashedWritePacket(t *testing.T) { - lengths := []int{0, 100, 1000} - eths := []bool{true, false} - gsos := []uint32{0, 32768} - hashes := []uint32{0, 1} - for _, eth := range eths { - for _, plen := range lengths { - for _, gso := range gsos { - for _, hash := range hashes { - t.Run( - fmt.Sprintf("Eth=%v,PayloadLen=%v,GSOMaxSize=%v,Hash=%d", eth, plen, gso, hash), - func(t *testing.T) { - testWritePacket(t, plen, eth, gso, hash) - }, - ) - } - } - } - } -} - -func TestPreserveSrcAddress(t *testing.T) { - baddr := tcpip.LinkAddress("\xcc\xbb\xaa\x77\x88\x99") - - c := newContext(t, &Options{Address: laddr, MTU: mtu, EthernetHeader: true}) - defer c.cleanup() - - // Set LocalLinkAddress in route to the value of the bridged address. - var r stack.RouteInfo - r.LocalLinkAddress = baddr - r.RemoteLinkAddress = raddr - - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - // WritePacket panics given a prependable with anything less than - // the minimum size of the ethernet header. - // TODO(b/153685824): Figure out if this should use c.ep.MaxHeaderLength(). - ReserveHeaderBytes: header.EthernetMinimumSize, - Data: buffer.VectorisedView{}, - }) - if err := c.ep.WritePacket(r, proto, pkt); err != nil { - t.Fatalf("WritePacket failed: %v", err) - } - - // Read from the FD, then compare with what we wrote. - b := make([]byte, mtu) - n, err := unix.Read(c.readFDs[0], b) - if err != nil { - t.Fatalf("Read failed: %v", err) - } - b = b[:n] - h := header.Ethernet(b) - - if a := h.SourceAddress(); a != baddr { - t.Fatalf("SourceAddress() = %v, want %v", a, baddr) - } -} - -func TestDeliverPacket(t *testing.T) { - lengths := []int{100, 1000} - eths := []bool{true, false} - - for _, eth := range eths { - for _, plen := range lengths { - t.Run(fmt.Sprintf("Eth=%v,PayloadLen=%v", eth, plen), func(t *testing.T) { - c := newContext(t, &Options{Address: laddr, MTU: mtu, EthernetHeader: eth}) - defer c.cleanup() - - // Build packet. - all := make([]byte, plen) - if _, err := rand.Read(all); err != nil { - t.Fatalf("rand.Read(all): %s", err) - } - // Make it look like an IPv4 packet. - all[0] = 0x40 - - wantPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: header.EthernetMinimumSize, - Data: buffer.NewViewFromBytes(all).ToVectorisedView(), - }) - if eth { - hdr := header.Ethernet(wantPkt.LinkHeader().Push(header.EthernetMinimumSize)) - hdr.Encode(&header.EthernetFields{ - SrcAddr: raddr, - DstAddr: laddr, - Type: proto, - }) - all = append(hdr, all...) - } - - // Write packet via the file descriptor. - if _, err := unix.Write(c.readFDs[0], all); err != nil { - t.Fatalf("Write failed: %v", err) - } - - // Receive packet through the endpoint. - select { - case pi := <-c.ch: - want := packetInfo{ - Raddr: raddr, - Proto: proto, - Contents: wantPkt, - } - if !eth { - want.Proto = header.IPv4ProtocolNumber - want.Raddr = "" - } - checkPacketInfoEqual(t, pi, want) - case <-time.After(10 * time.Second): - t.Fatalf("Timed out waiting for packet") - } - }) - } - } -} - -func TestBufConfigMaxLength(t *testing.T) { - got := 0 - for _, i := range BufConfig { - got += i - } - want := header.MaxIPPacketSize // maximum TCP packet size - if got < want { - t.Errorf("total buffer size is invalid: got %d, want >= %d", got, want) - } -} - -func TestBufConfigFirst(t *testing.T) { - // The stack assumes that the TCP/IP header is enterily contained in the first view. - // Therefore, the first view needs to be large enough to contain the maximum TCP/IP - // header, which is 120 bytes (60 bytes for IP + 60 bytes for TCP). - want := 120 - got := BufConfig[0] - if got < want { - t.Errorf("first view has an invalid size: got %d, want >= %d", got, want) - } -} - -var capLengthTestCases = []struct { - comment string - config []int - n int - wantUsed int - wantLengths []int -}{ - { - comment: "Single slice", - config: []int{2}, - n: 1, - wantUsed: 1, - wantLengths: []int{1}, - }, - { - comment: "Multiple slices", - config: []int{1, 2}, - n: 2, - wantUsed: 2, - wantLengths: []int{1, 1}, - }, - { - comment: "Entire buffer", - config: []int{1, 2}, - n: 3, - wantUsed: 2, - wantLengths: []int{1, 2}, - }, - { - comment: "Entire buffer but not on the last slice", - config: []int{1, 2, 3}, - n: 3, - wantUsed: 2, - wantLengths: []int{1, 2}, - }, -} - -func TestIovecBuffer(t *testing.T) { - for _, c := range capLengthTestCases { - t.Run(c.comment, func(t *testing.T) { - b := newIovecBuffer(c.config, false /* skipsVnetHdr */) - - // Test initial allocation. - iovecs := b.nextIovecs() - if got, want := len(iovecs), len(c.config); got != want { - t.Fatalf("len(iovecs) = %d, want %d", got, want) - } - - // Make a copy as iovecs points to internal slice. We will need this state - // later. - oldIovecs := append([]unix.Iovec(nil), iovecs...) - - // Test the views that get pulled. - vv := b.pullViews(c.n) - var lengths []int - for _, v := range vv.Views() { - lengths = append(lengths, len(v)) - } - if !reflect.DeepEqual(lengths, c.wantLengths) { - t.Errorf("Pulled view lengths = %v, want %v", lengths, c.wantLengths) - } - - // Test that new views get reallocated. - for i, newIov := range b.nextIovecs() { - if i < c.wantUsed { - if newIov.Base == oldIovecs[i].Base { - t.Errorf("b.views[%d] should have been reallocated", i) - } - } else { - if newIov.Base != oldIovecs[i].Base { - t.Errorf("b.views[%d] should not have been reallocated", i) - } - } - } - }) - } -} - -func TestIovecBufferSkipVnetHdr(t *testing.T) { - for _, test := range []struct { - desc string - readN int - wantLen int - }{ - { - desc: "nothing read", - readN: 0, - wantLen: 0, - }, - { - desc: "smaller than vnet header", - readN: virtioNetHdrSize - 1, - wantLen: 0, - }, - { - desc: "header skipped", - readN: virtioNetHdrSize + 100, - wantLen: 100, - }, - } { - t.Run(test.desc, func(t *testing.T) { - b := newIovecBuffer([]int{10, 20, 50, 50}, true) - // Pretend a read happend. - b.nextIovecs() - vv := b.pullViews(test.readN) - if got, want := vv.Size(), test.wantLen; got != want { - t.Errorf("b.pullView(%d).Size() = %d; want %d", test.readN, got, want) - } - if got, want := len(vv.ToOwnedView()), test.wantLen; got != want { - t.Errorf("b.pullView(%d).ToOwnedView() has length %d; want %d", test.readN, got, want) - } - }) - } -} - -// fakeNetworkDispatcher delivers packets to pkts. -type fakeNetworkDispatcher struct { - pkts []*stack.PacketBuffer -} - -func (d *fakeNetworkDispatcher) DeliverNetworkPacket(remote, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { - d.pkts = append(d.pkts, pkt) -} - -func (d *fakeNetworkDispatcher) DeliverOutboundPacket(remote, local tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { - panic("unimplemented") -} - -func TestDispatchPacketFormat(t *testing.T) { - for _, test := range []struct { - name string - newDispatcher func(fd int, e *endpoint) (linkDispatcher, error) - }{ - { - name: "readVDispatcher", - newDispatcher: newReadVDispatcher, - }, - { - name: "recvMMsgDispatcher", - newDispatcher: newRecvMMsgDispatcher, - }, - } { - t.Run(test.name, func(t *testing.T) { - // Create a socket pair to send/recv. - fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) - if err != nil { - t.Fatal(err) - } - defer unix.Close(fds[0]) - defer unix.Close(fds[1]) - - data := []byte{ - // Ethernet header. - 1, 2, 3, 4, 5, 60, - 1, 2, 3, 4, 5, 61, - 8, 0, - // Mock network header. - 40, 41, 42, 43, - } - err = unix.Sendmsg(fds[1], data, nil, nil, 0) - if err != nil { - t.Fatal(err) - } - - // Create and run dispatcher once. - sink := &fakeNetworkDispatcher{} - d, err := test.newDispatcher(fds[0], &endpoint{ - hdrSize: header.EthernetMinimumSize, - dispatcher: sink, - }) - if err != nil { - t.Fatal(err) - } - if ok, err := d.dispatch(); !ok || err != nil { - t.Fatalf("d.dispatch() = %v, %v", ok, err) - } - - // Verify packet. - if got, want := len(sink.pkts), 1; got != want { - t.Fatalf("len(sink.pkts) = %d, want %d", got, want) - } - pkt := sink.pkts[0] - if got, want := pkt.LinkHeader().View().Size(), header.EthernetMinimumSize; got != want { - t.Errorf("pkt.LinkHeader().View().Size() = %d, want %d", got, want) - } - if got, want := pkt.Data().Size(), 4; got != want { - t.Errorf("pkt.Data().Size() = %d, want %d", got, want) - } - }) - } -} diff --git a/pkg/tcpip/link/fdbased/fdbased_state_autogen.go b/pkg/tcpip/link/fdbased/fdbased_state_autogen.go new file mode 100644 index 000000000..b84e8f21c --- /dev/null +++ b/pkg/tcpip/link/fdbased/fdbased_state_autogen.go @@ -0,0 +1,8 @@ +// automatically generated by stateify. + +// +build linux +// +build linux,amd64 linux,arm64 +// +build !linux !amd64,!arm64 +// +build linux + +package fdbased diff --git a/pkg/tcpip/link/fdbased/fdbased_unsafe_state_autogen.go b/pkg/tcpip/link/fdbased/fdbased_unsafe_state_autogen.go new file mode 100644 index 000000000..e2ed505b2 --- /dev/null +++ b/pkg/tcpip/link/fdbased/fdbased_unsafe_state_autogen.go @@ -0,0 +1,6 @@ +// automatically generated by stateify. + +// +build linux +// +build linux,amd64 linux,arm64 + +package fdbased |