diff --git a/pkg/tcpip/transport/testing/context/BUILD b/pkg/tcpip/transport/testing/context/BUILD new file mode 100644 index 000000000..25a7120f5 --- /dev/null +++ b/pkg/tcpip/transport/testing/context/BUILD @@ -0,0 +1,30 @@ +load("//tools:defs.bzl", "go_library") + +package(licenses = ["notice"]) + +go_library( + name = "context", + testonly = 1, + srcs = [ + "context.go", + "flow.go", + ], + visibility = [ + "//visibility:public", + ], + deps = [ + "//pkg/tcpip", + "//pkg/tcpip/buffer", + "//pkg/tcpip/checker", + "//pkg/tcpip/faketime", + "//pkg/tcpip/header", + "//pkg/tcpip/link/channel", + "//pkg/tcpip/link/sniffer", + "//pkg/tcpip/network/ipv4", + "//pkg/tcpip/network/ipv6", + "//pkg/tcpip/stack", + "//pkg/waiter", + "@com_github_google_go_cmp//cmp:go_default_library", + "@org_golang_x_time//rate:go_default_library", + ], +) diff --git a/pkg/tcpip/transport/testing/context/context.go b/pkg/tcpip/transport/testing/context/context.go new file mode 100644 index 000000000..037abe237 --- /dev/null +++ b/pkg/tcpip/transport/testing/context/context.go @@ -0,0 +1,337 @@ +// Copyright 2022 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. + +// Package context provides a context used by datagram-based network endpoints +// tests. It also defines the TestFlow type to facilitate IP configurations. +package context + +import ( + "bytes" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/time/rate" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/buffer" + "gvisor.dev/gvisor/pkg/tcpip/checker" + "gvisor.dev/gvisor/pkg/tcpip/faketime" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" + "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" + "gvisor.dev/gvisor/pkg/tcpip/stack" + "gvisor.dev/gvisor/pkg/waiter" +) + +const ( + // NICID is the id of the nic created by the Context. + NICID = 1 + + // DefaultMTU is the MTU used by the Context, except where another value is + // explicitly specified during initialization. It is chosen to match the MTU + // of loopback interfaces on linux systems. + DefaultMTU = 65536 +) + +// Context is a testing context for datagram-based network endpoints. +type Context struct { + // T is the testing context. + T *testing.T + + // LinkEP is the link endpoint that is attached to the stack's NIC. + LinkEP *channel.Endpoint + + // Stack is the networking stack owned by the context. + Stack *stack.Stack + + // EP is the transport endpoint owned by the context. + EP tcpip.Endpoint + + // WQ is the wait queue associated with EP and is used to block for events on + // EP. + WQ waiter.Queue +} + +// Options contains options for creating a new test context. +type Options struct { + // MTU is the mtu that the link endpoint will be initialized with. + MTU uint32 + + // HandleLocal specifies if non-loopback interfaces are allowed to loop + // packets. + HandleLocal bool +} + +// New allocates and initializes a test context containing a configured stack. +func New(t *testing.T, transportProtocols []stack.TransportProtocolFactory) *Context { + t.Helper() + + options := Options{ + MTU: DefaultMTU, + HandleLocal: true, + } + + return NewWithOptions(t, transportProtocols, options) +} + +// NewWithOptions allocates and initializes a test context containing a +// configured stack with the provided options. +func NewWithOptions(t *testing.T, transportProtocols []stack.TransportProtocolFactory, options Options) *Context { + t.Helper() + + stackOptions := stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, + TransportProtocols: transportProtocols, + HandleLocal: options.HandleLocal, + Clock: &faketime.NullClock{}, + } + + s := stack.New(stackOptions) + // Disable ICMP rate limiter since we're using Null clock, which never + // advances time and thus never allows ICMP messages. + s.SetICMPLimit(rate.Inf) + ep := channel.New(256, options.MTU, "") + wep := stack.LinkEndpoint(ep) + + if testing.Verbose() { + wep = sniffer.New(ep) + } + if err := s.CreateNIC(NICID, wep); err != nil { + t.Fatalf("CreateNIC(%d, _): %s", NICID, err) + } + + protocolAddrV4 := tcpip.ProtocolAddress{ + Protocol: ipv4.ProtocolNumber, + AddressWithPrefix: tcpip.Address(StackAddr).WithPrefix(), + } + if err := s.AddProtocolAddress(NICID, protocolAddrV4, stack.AddressProperties{}); err != nil { + t.Fatalf("AddProtocolAddress(%d, %#v, {}): %s", NICID, protocolAddrV4, err) + } + + protocolAddrV6 := tcpip.ProtocolAddress{ + Protocol: ipv6.ProtocolNumber, + AddressWithPrefix: tcpip.Address(StackV6Addr).WithPrefix(), + } + if err := s.AddProtocolAddress(NICID, protocolAddrV6, stack.AddressProperties{}); err != nil { + t.Fatalf("AddProtocolAddress(%d, %#v, {}): %s", NICID, protocolAddrV6, err) + } + + s.SetRouteTable([]tcpip.Route{ + { + Destination: header.IPv4EmptySubnet, + NIC: NICID, + }, + { + Destination: header.IPv6EmptySubnet, + NIC: NICID, + }, + }) + + return &Context{ + T: t, + Stack: s, + LinkEP: ep, + } +} + +// Cleanup closes the context endpoint if required. +func (c *Context) Cleanup() { + _ = c.LinkEP.Drain() + if c.EP != nil { + c.EP.Close() + } +} + +// CreateEndpoint creates the Context's Endpoint. +func (c *Context) CreateEndpoint(network tcpip.NetworkProtocolNumber, transport tcpip.TransportProtocolNumber) { + c.T.Helper() + + var err tcpip.Error + c.EP, err = c.Stack.NewEndpoint(transport, network, &c.WQ) + if err != nil { + c.T.Fatalf("c.Stack.NewEndpoint(%d, %d, _) failed: %s", transport, network, err) + } +} + +// CreateEndpointForFlow creates the Context's Endpoint and configured it +// according to the given TestFlow. +func (c *Context) CreateEndpointForFlow(flow TestFlow, transport tcpip.TransportProtocolNumber) { + c.T.Helper() + + c.CreateEndpoint(flow.SockProto(), transport) + if flow.isV6Only() { + c.EP.SocketOptions().SetV6Only(true) + } else if flow.isBroadcast() { + c.EP.SocketOptions().SetBroadcast(true) + } +} + +// CheckEndpointWriteStats checks that the write statistic related to the given +// error has been incremented as expected. +func (c *Context) CheckEndpointWriteStats(incr uint64, want tcpip.TransportEndpointStats, err tcpip.Error) { + got := c.EP.Stats().(*tcpip.TransportEndpointStats).Clone() + switch err.(type) { + case nil: + want.PacketsSent.IncrementBy(incr) + case *tcpip.ErrMessageTooLong, *tcpip.ErrInvalidOptionValue: + want.WriteErrors.InvalidArgs.IncrementBy(incr) + case *tcpip.ErrClosedForSend: + want.WriteErrors.WriteClosed.IncrementBy(incr) + case *tcpip.ErrInvalidEndpointState: + want.WriteErrors.InvalidEndpointState.IncrementBy(incr) + case *tcpip.ErrNoRoute, *tcpip.ErrBroadcastDisabled, *tcpip.ErrNetworkUnreachable: + want.SendErrors.NoRoute.IncrementBy(incr) + default: + want.SendErrors.SendToNetworkFailed.IncrementBy(incr) + } + if got != want { + c.T.Errorf("Endpoint stats not matching for error %s: got %#v, want %#v", err, got, want) + } +} + +// CheckEndpointReadStats checks that the read statistic related to the given +// error has been incremented as expected. +func (c *Context) CheckEndpointReadStats(incr uint64, want tcpip.TransportEndpointStats, err tcpip.Error) { + c.T.Helper() + + got := c.EP.Stats().(*tcpip.TransportEndpointStats).Clone() + switch err.(type) { + case nil, *tcpip.ErrWouldBlock: + case *tcpip.ErrClosedForReceive: + want.ReadErrors.ReadClosed.IncrementBy(incr) + default: + c.T.Errorf("Endpoint error missing stats update for err %s", err) + } + if got != want { + c.T.Errorf("Endpoint stats not matching for error %s: got %#v, want %#v", err, got, want) + } +} + +// InjectPacket injects a packet into the context's link endpoint. +func (c *Context) InjectPacket(netProto tcpip.NetworkProtocolNumber, buf buffer.View) { + pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + Data: buf.ToVectorisedView(), + }) + defer pkt.DecRef() + c.LinkEP.InjectInbound(netProto, pkt) +} + +// readExpectations holds information about the expected outcome when reading +// from the context's endpoint. +type readExpectations struct { + nothingToRead bool + payload []byte + addresses Header4Tuple + readShouldFail bool +} + +// readFromEndpoint attempts to read a packet from the endpoint and compares the +// outcome with the given expectations. +func (c *Context) readFromEndpoint(expectations readExpectations, checkers ...checker.ControlMessagesChecker) { + c.T.Helper() + + // Try to receive the data. + we, ch := waiter.NewChannelEntry(waiter.ReadableEvents) + c.WQ.EventRegister(&we) + defer c.WQ.EventUnregister(&we) + + // Take a snapshot of the stats to validate them at the end of the test. + epstats := c.EP.Stats().(*tcpip.TransportEndpointStats).Clone() + + var buf bytes.Buffer + res, err := c.EP.Read(&buf, tcpip.ReadOptions{NeedRemoteAddr: true}) + if _, ok := err.(*tcpip.ErrWouldBlock); ok { + select { + case <-ch: + res, err = c.EP.Read(&buf, tcpip.ReadOptions{NeedRemoteAddr: true}) + default: + if expectations.nothingToRead { + return + } + c.T.Fatal("timed out waiting for data") + } + } + + if expectations.readShouldFail && err != nil { + c.CheckEndpointReadStats(1, epstats, err) + return + } + + if err != nil { + c.T.Fatal("Read failed:", err) + } + + if expectations.nothingToRead { + c.T.Fatalf("Read unexpectedly received data from %s", res.RemoteAddr.Addr) + } + + // Check the read result. + if diff := cmp.Diff(tcpip.ReadResult{ + Count: buf.Len(), + Total: buf.Len(), + RemoteAddr: tcpip.FullAddress{Addr: expectations.addresses.Src.Addr}, + }, res, checker.IgnoreCmpPath( + "ControlMessages", // ControlMessages are checked below. + "RemoteAddr.NIC", + "RemoteAddr.Port", + )); diff != "" { + c.T.Fatalf("Read: unexpected result (-want +got):\n%s", diff) + } + + // Check the payload. + v := buf.Bytes() + if !bytes.Equal(expectations.payload, v) { + c.T.Fatalf("got payload = %x, want = %x", v, expectations.payload) + } + + // Run any checkers against the ControlMessages. + for _, f := range checkers { + f(c.T, res.ControlMessages) + } + + c.CheckEndpointReadStats(1, epstats, err) +} + +// ReadFromEndpointExpectSuccess attempts to reads from the endpoint and +// performs checks on the received packet, according to the given flow and +// checkers. +func (c *Context) ReadFromEndpointExpectSuccess(payload []byte, flow TestFlow, checkers ...checker.ControlMessagesChecker) { + c.T.Helper() + + c.readFromEndpoint(readExpectations{ + payload: payload, + addresses: flow.MakeHeader4Tuple(Incoming), + }, checkers...) +} + +// ReadFromEndpointExpectNoPacket reads from the endpoint and checks that no +// packets was received. +func (c *Context) ReadFromEndpointExpectNoPacket() { + c.T.Helper() + + c.readFromEndpoint(readExpectations{ + nothingToRead: true, + }) +} + +// ReadFromEndpointExpectError reads from the endpoint and checks that an +// error was returned. +func (c *Context) ReadFromEndpointExpectError() { + c.T.Helper() + + c.readFromEndpoint(readExpectations{ + readShouldFail: true, + }) +} diff --git a/pkg/tcpip/transport/testing/context/flow.go b/pkg/tcpip/transport/testing/context/flow.go new file mode 100644 index 000000000..4ca3562b8 --- /dev/null +++ b/pkg/tcpip/transport/testing/context/flow.go @@ -0,0 +1,340 @@ +// Copyright 2022 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. + +package context + +import ( + "fmt" + "testing" + + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/checker" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" +) + +const ( + v4MappedAddrPrefix = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" + + // StackPort is the port TestFlow uses with StackAddr. + StackPort = 1234 + + // TestPort is the port TestFlow uses with TestAddr. + TestPort = 4096 + + // StackAddr is the IPv4 address assigned to the stack's NIC and is used by + // TestFlow as the local address. + StackAddr = "\x0a\x00\x00\x01" + + // StackV4MappedAddr is the IPv4-mapped IPv6 StackAddr. + StackV4MappedAddr = v4MappedAddrPrefix + StackAddr + + // TestAddr is the IPv4 address used by TestFlow as the remote address. + TestAddr = "\x0a\x00\x00\x02" + + // TestV4MappedAddr is the IPv4-mapped IPv6 TestAddr. + TestV4MappedAddr = v4MappedAddrPrefix + TestAddr + + // MulticastAddr is the IPv4 multicast address used by IPv4 multicast + // TestFlow. + MulticastAddr = "\xe8\x2b\xd3\xea" + + // MulticastV4MappedAddr is the IPv4-mapped IPv6 MulticastAddr. + MulticastV4MappedAddr = v4MappedAddrPrefix + MulticastAddr + + // BroadcastAddr is the IPv4 broadcast address. + BroadcastAddr = header.IPv4Broadcast + + // BroadcastV4MappedAddr is the IPv4-mapped IPv6 BroadcastAddr. + BroadcastV4MappedAddr = v4MappedAddrPrefix + BroadcastAddr + + // V4MappedWildcardAddr is the IPv4-mapped IPv6 wildcard (any) address. + V4MappedWildcardAddr = v4MappedAddrPrefix + "\x00\x00\x00\x00" + + // StackV6Addr is the IPv6 address assigned to the stack's NIC and is used by + // TestFlow as the local address. + StackV6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" + + // TestV6Addr is the IPv6 address used by TestFlow as the remote address. + TestV6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + + // MulticastV6Addr is the IPv6 multicast address used by IPv6 multicast + // TestFlow. + MulticastV6Addr = "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +) + +// Header4Tuple stores the 4-tuple {src-IP, src-port, dst-IP, dst-port} used in +// a packet header. These values are used to populate a header or verify one. +// Note that because they are used in packet headers, the addresses are never in +// a V4-mapped format. +type Header4Tuple struct { + Src tcpip.FullAddress + Dst tcpip.FullAddress +} + +// TestFlow implements a helper type used for sending and receiving test +// packets. A given test TestFlow value defines 1) the socket endpoint used for +// the test and 2) the type of packet send or received on the endpoint. E.g., a +// MulticastV6Only TestFlow is a IPv6 multicast packet passing through a V6-only +// endpoint. The type provides helper methods to characterize the TestFlow +// (e.g., IsV4) as well as return a proper Header4Tuple for it. +type TestFlow int + +const ( + _ TestFlow = iota + + // UnicastV4 is IPv4 unicast on an IPv4 socket + UnicastV4 + + // UnicastV4in6 is IPv4-mapped IPv6 unicast on an IPv6 dual socket + UnicastV4in6 + + // UnicastV6 is IPv6 unicast on an IPv6 socket + UnicastV6 + + // UnicastV6Only is IPv6 unicast on an IPv6-only socket + UnicastV6Only + + // MulticastV4 is IPv4 multicast on an IPv4 socket + MulticastV4 + + // MulticastV4in6 is IPv4-mapped IPv6 multicast on an IPv6 dual socket + MulticastV4in6 + + // MulticastV6 is IPv6 multicast on an IPv6 socket + MulticastV6 + + // MulticastV6Only IPv6 multicast on an IPv6-only socket + MulticastV6Only + + // Broadcast is IPv4 broadcast on an IPv4 socket + Broadcast + + // BroadcastIn6 is IPv4-mapped IPv6 broadcast on an IPv6 dual socket + BroadcastIn6 + + // ReverseMulticastV4 is IPv4 multicast src. Must fail. + ReverseMulticastV4 + + // ReverseMulticastV6 is IPv6 multicast src. Must fail. + ReverseMulticastV6 +) + +// String implements fmt.Stringer interface. +func (flow TestFlow) String() string { + switch flow { + case UnicastV4: + return "UnicastV4" + case UnicastV6: + return "UnicastV6" + case UnicastV6Only: + return "UnicastV6Only" + case UnicastV4in6: + return "UnicastV4in6" + case MulticastV4: + return "MulticastV4" + case MulticastV6: + return "MulticastV6" + case MulticastV6Only: + return "MulticastV6Only" + case MulticastV4in6: + return "MulticastV4in6" + case Broadcast: + return "Broadcast" + case BroadcastIn6: + return "BroadcastIn6" + case ReverseMulticastV4: + return "ReverseMulticastV4" + case ReverseMulticastV6: + return "ReverseMulticastV6" + default: + return "Unknown" + } +} + +// PacketDirection specifies the direction of a TestFlow. +type PacketDirection int + +const ( + _ PacketDirection = iota + + // Incoming indicates the direction from Test*Addr to Stack*Addr. + Incoming + + // Outgoing indicates the direction from Test*Addr to Stack*Addr. + Outgoing +) + +// MakeHeader4Tuple returns the Header4Tuple for the given TestFlow and direction. Note +// that the tuple contains no mapped addresses as those only exist at the socket +// level but not at the packet header level. +func (flow TestFlow) MakeHeader4Tuple(direction PacketDirection) Header4Tuple { + var h Header4Tuple + if flow.IsV4() { + switch direction { + case Outgoing: + h = Header4Tuple{ + Src: tcpip.FullAddress{Addr: StackAddr, Port: StackPort}, + Dst: tcpip.FullAddress{Addr: TestAddr, Port: TestPort}, + } + case Incoming: + h = Header4Tuple{ + Src: tcpip.FullAddress{Addr: TestAddr, Port: TestPort}, + Dst: tcpip.FullAddress{Addr: StackAddr, Port: StackPort}, + } + default: + panic(fmt.Sprintf("unknown direction %d", direction)) + } + + if flow.IsMulticast() { + h.Dst.Addr = MulticastAddr + } else if flow.isBroadcast() { + h.Dst.Addr = BroadcastAddr + } + } else { // IPv6 + switch direction { + case Outgoing: + h = Header4Tuple{ + Src: tcpip.FullAddress{Addr: StackV6Addr, Port: StackPort}, + Dst: tcpip.FullAddress{Addr: TestV6Addr, Port: TestPort}, + } + case Incoming: + h = Header4Tuple{ + Src: tcpip.FullAddress{Addr: TestV6Addr, Port: TestPort}, + Dst: tcpip.FullAddress{Addr: StackV6Addr, Port: StackPort}, + } + default: + panic(fmt.Sprintf("unknown direction %d", direction)) + } + + if flow.IsMulticast() { + h.Dst.Addr = MulticastV6Addr + } + } + + if flow.isReverseMulticast() { + h.Src.Addr = flow.GetMulticastAddr() + } + + return h +} + +// GetMulticastAddr returns the multicast address of a TestFlow. +func (flow TestFlow) GetMulticastAddr() tcpip.Address { + if flow.IsV4() { + return MulticastAddr + } + return MulticastV6Addr +} + +// MapAddrIfApplicable converts the given IPv4 address into its V4-mapped +// version if it is applicable to the TestFlow. +func (flow TestFlow) MapAddrIfApplicable(v4Addr tcpip.Address) tcpip.Address { + if flow.isMapped() { + return v4MappedAddrPrefix + v4Addr + } + return v4Addr +} + +// NetProto returns the network protocol of a TestFlow. +func (flow TestFlow) NetProto() tcpip.NetworkProtocolNumber { + if flow.IsV4() { + return ipv4.ProtocolNumber + } + return ipv6.ProtocolNumber +} + +// SockProto returns the network protocol number a socket must be configured +// with to support a given TestFlow. +func (flow TestFlow) SockProto() tcpip.NetworkProtocolNumber { + switch flow { + case UnicastV4in6, UnicastV6, UnicastV6Only, MulticastV4in6, MulticastV6, MulticastV6Only, BroadcastIn6, ReverseMulticastV6: + return ipv6.ProtocolNumber + case UnicastV4, MulticastV4, Broadcast, ReverseMulticastV4: + return ipv4.ProtocolNumber + default: + panic(fmt.Sprintf("invalid TestFlow given: %d", flow)) + } +} + +// CheckerFn returns the correct network checker for the current TestFlow. +func (flow TestFlow) CheckerFn() func(*testing.T, []byte, ...checker.NetworkChecker) { + if flow.IsV4() { + return checker.IPv4 + } + return checker.IPv6 +} + +// IsV4 returns true for IPv4 TestFlow's. +func (flow TestFlow) IsV4() bool { + return flow.SockProto() == ipv4.ProtocolNumber || flow.isMapped() +} + +// IsV6 returns true for IPv6 TestFlow's. +func (flow TestFlow) IsV6() bool { return !flow.IsV4() } + +func (flow TestFlow) isV6Only() bool { + switch flow { + case UnicastV6Only, MulticastV6Only: + return true + case UnicastV4, UnicastV4in6, UnicastV6, MulticastV4, MulticastV4in6, MulticastV6, Broadcast, BroadcastIn6, ReverseMulticastV4, ReverseMulticastV6: + return false + default: + panic(fmt.Sprintf("invalid TestFlow given: %d", flow)) + } +} + +// IsMulticast returns true if the TestFlow is multicast. +func (flow TestFlow) IsMulticast() bool { + switch flow { + case MulticastV4, MulticastV4in6, MulticastV6, MulticastV6Only: + return true + case UnicastV4, UnicastV4in6, UnicastV6, UnicastV6Only, Broadcast, BroadcastIn6, ReverseMulticastV4, ReverseMulticastV6: + return false + default: + panic(fmt.Sprintf("invalid TestFlow given: %d", flow)) + } +} + +func (flow TestFlow) isBroadcast() bool { + switch flow { + case Broadcast, BroadcastIn6: + return true + case UnicastV4, UnicastV4in6, UnicastV6, UnicastV6Only, MulticastV4, MulticastV4in6, MulticastV6, MulticastV6Only, ReverseMulticastV4, ReverseMulticastV6: + return false + default: + panic(fmt.Sprintf("invalid TestFlow given: %d", flow)) + } +} + +func (flow TestFlow) isMapped() bool { + switch flow { + case UnicastV4in6, MulticastV4in6, BroadcastIn6: + return true + case UnicastV4, UnicastV6, UnicastV6Only, MulticastV4, MulticastV6, MulticastV6Only, Broadcast, ReverseMulticastV4, ReverseMulticastV6: + return false + default: + panic(fmt.Sprintf("invalid TestFlow given: %d", flow)) + } +} + +func (flow TestFlow) isReverseMulticast() bool { + switch flow { + case ReverseMulticastV4, ReverseMulticastV6: + return true + default: + return false + } +} diff --git a/pkg/tcpip/transport/udp/BUILD b/pkg/tcpip/transport/udp/BUILD index 55a3cfdb7..c4804482c 100644 --- a/pkg/tcpip/transport/udp/BUILD +++ b/pkg/tcpip/transport/udp/BUILD @@ -58,14 +58,12 @@ go_test( "//pkg/tcpip/header", "//pkg/tcpip/link/channel", "//pkg/tcpip/link/loopback", - "//pkg/tcpip/link/sniffer", "//pkg/tcpip/network/ipv4", "//pkg/tcpip/network/ipv6", "//pkg/tcpip/stack", "//pkg/tcpip/testutil", "//pkg/tcpip/transport/icmp", + "//pkg/tcpip/transport/testing/context", "//pkg/waiter", - "@com_github_google_go_cmp//cmp:go_default_library", - "@org_golang_x_time//rate:go_default_library", ], ) diff --git a/pkg/tcpip/transport/udp/udp_test.go b/pkg/tcpip/transport/udp/udp_test.go index 9be405c45..2ded2cb5d 100644 --- a/pkg/tcpip/transport/udp/udp_test.go +++ b/pkg/tcpip/transport/udp/udp_test.go @@ -18,12 +18,11 @@ import ( "bytes" "fmt" "io/ioutil" + "math" "math/rand" "os" "testing" - "github.com/google/go-cmp/cmp" - "golang.org/x/time/rate" "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/refsvfs2" "gvisor.dev/gvisor/pkg/tcpip" @@ -33,479 +32,32 @@ import ( "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/link/channel" "gvisor.dev/gvisor/pkg/tcpip/link/loopback" - "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" "gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/testutil" "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" + "gvisor.dev/gvisor/pkg/tcpip/transport/testing/context" "gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/waiter" ) -// Addresses and ports used for testing. It is recommended that tests stick to -// using these addresses as it allows using the testFlow helper. -// Naming rules: 'stack*'' denotes local addresses and ports, while 'test*' -// represents the remote endpoint. const ( - v4MappedAddrPrefix = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" - stackV6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" - testV6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" - stackV4MappedAddr = v4MappedAddrPrefix + stackAddr - testV4MappedAddr = v4MappedAddrPrefix + testAddr - multicastV4MappedAddr = v4MappedAddrPrefix + multicastAddr - broadcastV4MappedAddr = v4MappedAddrPrefix + broadcastAddr - v4MappedWildcardAddr = v4MappedAddrPrefix + "\x00\x00\x00\x00" - - stackAddr = "\x0a\x00\x00\x01" - stackPort = 1234 - testAddr = "\x0a\x00\x00\x02" - testPort = 4096 - invalidPort = 8192 - multicastAddr = "\xe8\x2b\xd3\xea" - multicastV6Addr = "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - broadcastAddr = header.IPv4Broadcast - testTOS = 0x80 - - // defaultMTU is the MTU, in bytes, used throughout the tests, except - // where another value is explicitly used. It is chosen to match the MTU - // of loopback interfaces on linux systems. - defaultMTU = 65536 + testTOS = 0x80 + arbitraryPayloadSize = 30 ) -// header4Tuple stores the 4-tuple {src-IP, src-port, dst-IP, dst-port} used in -// a packet header. These values are used to populate a header or verify one. -// Note that because they are used in packet headers, the addresses are never in -// a V4-mapped format. -type header4Tuple struct { - srcAddr tcpip.FullAddress - dstAddr tcpip.FullAddress -} - -// testFlow implements a helper type used for sending and receiving test -// packets. A given test flow value defines 1) the socket endpoint used for the -// test and 2) the type of packet send or received on the endpoint. E.g., a -// multicastV6Only flow is a V6 multicast packet passing through a V6-only -// endpoint. The type provides helper methods to characterize the flow (e.g., -// isV4) as well as return a proper header4Tuple for it. -type testFlow int - -const ( - unicastV4 testFlow = iota // V4 unicast on a V4 socket - unicastV4in6 // V4-mapped unicast on a V6-dual socket - unicastV6 // V6 unicast on a V6 socket - unicastV6Only // V6 unicast on a V6-only socket - multicastV4 // V4 multicast on a V4 socket - multicastV4in6 // V4-mapped multicast on a V6-dual socket - multicastV6 // V6 multicast on a V6 socket - multicastV6Only // V6 multicast on a V6-only socket - broadcast // V4 broadcast on a V4 socket - broadcastIn6 // V4-mapped broadcast on a V6-dual socket - reverseMulticast4 // V4 multicast src. Must fail. - reverseMulticast6 // V6 multicast src. Must fail. -) - -func (flow testFlow) String() string { - switch flow { - case unicastV4: - return "unicastV4" - case unicastV6: - return "unicastV6" - case unicastV6Only: - return "unicastV6Only" - case unicastV4in6: - return "unicastV4in6" - case multicastV4: - return "multicastV4" - case multicastV6: - return "multicastV6" - case multicastV6Only: - return "multicastV6Only" - case multicastV4in6: - return "multicastV4in6" - case broadcast: - return "broadcast" - case broadcastIn6: - return "broadcastIn6" - case reverseMulticast4: - return "reverseMulticast4" - case reverseMulticast6: - return "reverseMulticast6" - default: - return "unknown" +// newRandomPayload returns a payload with the specified size and with +// randomized content. +func newRandomPayload(size int) []byte { + b := make([]byte, size) + for i := range b { + b[i] = byte(rand.Intn(math.MaxUint8 + 1)) } -} - -// packetDirection explains if a flow is incoming (read) or outgoing (write). -type packetDirection int - -const ( - incoming packetDirection = iota - outgoing -) - -// header4Tuple returns the header4Tuple for the given flow and direction. Note -// that the tuple contains no mapped addresses as those only exist at the socket -// level but not at the packet header level. -func (flow testFlow) header4Tuple(d packetDirection) header4Tuple { - var h header4Tuple - if flow.isV4() { - if d == outgoing { - h = header4Tuple{ - srcAddr: tcpip.FullAddress{Addr: stackAddr, Port: stackPort}, - dstAddr: tcpip.FullAddress{Addr: testAddr, Port: testPort}, - } - } else { - h = header4Tuple{ - srcAddr: tcpip.FullAddress{Addr: testAddr, Port: testPort}, - dstAddr: tcpip.FullAddress{Addr: stackAddr, Port: stackPort}, - } - } - if flow.isMulticast() { - h.dstAddr.Addr = multicastAddr - } else if flow.isBroadcast() { - h.dstAddr.Addr = broadcastAddr - } - } else { // IPv6 - if d == outgoing { - h = header4Tuple{ - srcAddr: tcpip.FullAddress{Addr: stackV6Addr, Port: stackPort}, - dstAddr: tcpip.FullAddress{Addr: testV6Addr, Port: testPort}, - } - } else { - h = header4Tuple{ - srcAddr: tcpip.FullAddress{Addr: testV6Addr, Port: testPort}, - dstAddr: tcpip.FullAddress{Addr: stackV6Addr, Port: stackPort}, - } - } - if flow.isMulticast() { - h.dstAddr.Addr = multicastV6Addr - } - } - if flow.isReverseMulticast() { - h.srcAddr.Addr = flow.getMcastAddr() - } - return h -} - -func (flow testFlow) getMcastAddr() tcpip.Address { - if flow.isV4() { - return multicastAddr - } - return multicastV6Addr -} - -// mapAddrIfApplicable converts the given V4 address into its V4-mapped version -// if it is applicable to the flow. -func (flow testFlow) mapAddrIfApplicable(v4Addr tcpip.Address) tcpip.Address { - if flow.isMapped() { - return v4MappedAddrPrefix + v4Addr - } - return v4Addr -} - -// netProto returns the protocol number used for the network packet. -func (flow testFlow) netProto() tcpip.NetworkProtocolNumber { - if flow.isV4() { - return ipv4.ProtocolNumber - } - return ipv6.ProtocolNumber -} - -// sockProto returns the protocol number used when creating the socket -// endpoint for this flow. -func (flow testFlow) sockProto() tcpip.NetworkProtocolNumber { - switch flow { - case unicastV4in6, unicastV6, unicastV6Only, multicastV4in6, multicastV6, multicastV6Only, broadcastIn6, reverseMulticast6: - return ipv6.ProtocolNumber - case unicastV4, multicastV4, broadcast, reverseMulticast4: - return ipv4.ProtocolNumber - default: - panic(fmt.Sprintf("invalid testFlow given: %d", flow)) - } -} - -func (flow testFlow) checkerFn() func(*testing.T, []byte, ...checker.NetworkChecker) { - if flow.isV4() { - return checker.IPv4 - } - return checker.IPv6 -} - -func (flow testFlow) isV6() bool { return !flow.isV4() } -func (flow testFlow) isV4() bool { - return flow.sockProto() == ipv4.ProtocolNumber || flow.isMapped() -} - -func (flow testFlow) isV6Only() bool { - switch flow { - case unicastV6Only, multicastV6Only: - return true - case unicastV4, unicastV4in6, unicastV6, multicastV4, multicastV4in6, multicastV6, broadcast, broadcastIn6, reverseMulticast4, reverseMulticast6: - return false - default: - panic(fmt.Sprintf("invalid testFlow given: %d", flow)) - } -} - -func (flow testFlow) isMulticast() bool { - switch flow { - case multicastV4, multicastV4in6, multicastV6, multicastV6Only: - return true - case unicastV4, unicastV4in6, unicastV6, unicastV6Only, broadcast, broadcastIn6, reverseMulticast4, reverseMulticast6: - return false - default: - panic(fmt.Sprintf("invalid testFlow given: %d", flow)) - } -} - -func (flow testFlow) isBroadcast() bool { - switch flow { - case broadcast, broadcastIn6: - return true - case unicastV4, unicastV4in6, unicastV6, unicastV6Only, multicastV4, multicastV4in6, multicastV6, multicastV6Only, reverseMulticast4, reverseMulticast6: - return false - default: - panic(fmt.Sprintf("invalid testFlow given: %d", flow)) - } -} - -func (flow testFlow) isMapped() bool { - switch flow { - case unicastV4in6, multicastV4in6, broadcastIn6: - return true - case unicastV4, unicastV6, unicastV6Only, multicastV4, multicastV6, multicastV6Only, broadcast, reverseMulticast4, reverseMulticast6: - return false - default: - panic(fmt.Sprintf("invalid testFlow given: %d", flow)) - } -} - -func (flow testFlow) isReverseMulticast() bool { - switch flow { - case reverseMulticast4, reverseMulticast6: - return true - default: - return false - } -} - -type testContext struct { - t *testing.T - linkEP *channel.Endpoint - s *stack.Stack - nicID tcpip.NICID - - ep tcpip.Endpoint - wq waiter.Queue -} - -func newDualTestContext(t *testing.T, mtu uint32) *testContext { - t.Helper() - return newDualTestContextWithHandleLocal(t, mtu, true) -} - -func newDualTestContextWithHandleLocal(t *testing.T, mtu uint32, handleLocal bool) *testContext { - const nicID = 1 - - t.Helper() - - options := stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol}, - TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}, - HandleLocal: handleLocal, - Clock: &faketime.NullClock{}, - } - s := stack.New(options) - // Disable ICMP rate limiter because we're using Null clock, which never advances time and thus - // never allows ICMP messages. - s.SetICMPLimit(rate.Inf) - ep := channel.New(256, mtu, "") - wep := stack.LinkEndpoint(ep) - - if testing.Verbose() { - wep = sniffer.New(ep) - } - if err := s.CreateNIC(nicID, wep); err != nil { - t.Fatalf("CreateNIC(%d, _): %s", nicID, err) - } - - protocolAddrV4 := tcpip.ProtocolAddress{ - Protocol: ipv4.ProtocolNumber, - AddressWithPrefix: tcpip.Address(stackAddr).WithPrefix(), - } - if err := s.AddProtocolAddress(nicID, protocolAddrV4, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddrV4, err) - } - - protocolAddrV6 := tcpip.ProtocolAddress{ - Protocol: ipv6.ProtocolNumber, - AddressWithPrefix: tcpip.Address(stackV6Addr).WithPrefix(), - } - if err := s.AddProtocolAddress(nicID, protocolAddrV6, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddrV6, err) - } - - s.SetRouteTable([]tcpip.Route{ - { - Destination: header.IPv4EmptySubnet, - NIC: nicID, - }, - { - Destination: header.IPv6EmptySubnet, - NIC: nicID, - }, - }) - - return &testContext{ - t: t, - s: s, - nicID: nicID, - linkEP: ep, - } -} - -func (c *testContext) cleanup() { - if c.ep != nil { - c.ep.Close() - } -} - -func (c *testContext) createEndpoint(proto tcpip.NetworkProtocolNumber) { - c.t.Helper() - - var err tcpip.Error - c.ep, err = c.s.NewEndpoint(udp.ProtocolNumber, proto, &c.wq) - if err != nil { - c.t.Fatal("NewEndpoint failed: ", err) - } -} - -func (c *testContext) createEndpointForFlow(flow testFlow) { - c.t.Helper() - - c.createEndpoint(flow.sockProto()) - if flow.isV6Only() { - c.ep.SocketOptions().SetV6Only(true) - } else if flow.isBroadcast() { - c.ep.SocketOptions().SetBroadcast(true) - } -} - -// getPacketAndVerify reads a packet from the link endpoint and verifies the -// header against expected values from the given test flow. In addition, it -// calls any extra checker functions provided. -func (c *testContext) getPacketAndVerify(flow testFlow, checkers ...checker.NetworkChecker) []byte { - c.t.Helper() - - p := c.linkEP.Read() - if p == nil { - c.t.Fatalf("Packet wasn't written out") - return nil - } - - if got, want := p.NetworkProtocolNumber, flow.netProto(); got != want { - c.t.Fatalf("got p.NetworkProtocolNumber = %d, want = %d", got, want) - } - - if got, want := p.TransportProtocolNumber, header.UDPProtocolNumber; got != want { - c.t.Errorf("got p.TransportProtocolNumber = %d, want = %d", got, want) - } - - vv := buffer.NewVectorisedView(p.Size(), p.Views()) - b := vv.ToView() - - h := flow.header4Tuple(outgoing) - checkers = append( - checkers, - checker.SrcAddr(h.srcAddr.Addr), - checker.DstAddr(h.dstAddr.Addr), - checker.UDP(checker.DstPort(h.dstAddr.Port)), - ) - flow.checkerFn()(c.t, b, checkers...) return b } -// injectPacket creates a packet of the given flow and with the given payload, -// and injects it into the link endpoint. If badChecksum is true, the packet has -// a bad checksum in the UDP header. -func (c *testContext) injectPacket(flow testFlow, payload []byte, badChecksum bool) { - c.t.Helper() - - h := flow.header4Tuple(incoming) - if flow.isV4() { - buf := c.buildV4Packet(payload, &h) - if badChecksum { - // Invalidate the UDP header checksum field, taking care to avoid - // overflow to zero, which would disable checksum validation. - for u := header.UDP(buf[header.IPv4MinimumSize:]); ; { - u.SetChecksum(u.Checksum() + 1) - if u.Checksum() != 0 { - break - } - } - } - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv4.ProtocolNumber, pkt) - } else { - buf := c.buildV6Packet(payload, &h) - if badChecksum { - // Invalidate the UDP header checksum field (Unlike IPv4, zero is - // a valid checksum value for IPv6 so no need to avoid it). - u := header.UDP(buf[header.IPv6MinimumSize:]) - u.SetChecksum(u.Checksum() + 1) - } - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv6.ProtocolNumber, pkt) - } -} - -// buildV6Packet creates a V6 test packet with the given payload and header -// values in a buffer. -func (c *testContext) buildV6Packet(payload []byte, h *header4Tuple) buffer.View { - // Allocate a buffer for data and headers. - buf := buffer.NewView(header.UDPMinimumSize + header.IPv6MinimumSize + len(payload)) - payloadStart := len(buf) - len(payload) - copy(buf[payloadStart:], payload) - - // Initialize the IP header. - ip := header.IPv6(buf) - ip.Encode(&header.IPv6Fields{ - TrafficClass: testTOS, - PayloadLength: uint16(header.UDPMinimumSize + len(payload)), - TransportProtocol: udp.ProtocolNumber, - HopLimit: 65, - SrcAddr: h.srcAddr.Addr, - DstAddr: h.dstAddr.Addr, - }) - - // Initialize the UDP header. - u := header.UDP(buf[header.IPv6MinimumSize:]) - u.Encode(&header.UDPFields{ - SrcPort: h.srcAddr.Port, - DstPort: h.dstAddr.Port, - Length: uint16(header.UDPMinimumSize + len(payload)), - }) - - // Calculate the UDP pseudo-header checksum. - xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.srcAddr.Addr, h.dstAddr.Addr, uint16(len(u))) - - // Calculate the UDP checksum and set it. - xsum = header.Checksum(payload, xsum) - u.SetChecksum(^u.CalculateChecksum(xsum)) - - return buf -} - -// buildV4Packet creates a V4 test packet with the given payload and header -// values in a buffer. -func (c *testContext) buildV4Packet(payload []byte, h *header4Tuple) buffer.View { +func buildV4Packet(payload []byte, h context.Header4Tuple, badChecksum bool) buffer.View { // Allocate a buffer for data and headers. buf := buffer.NewView(header.UDPMinimumSize + header.IPv4MinimumSize + len(payload)) payloadStart := len(buf) - len(payload) @@ -518,39 +70,107 @@ func (c *testContext) buildV4Packet(payload []byte, h *header4Tuple) buffer.View TotalLength: uint16(len(buf)), TTL: 65, Protocol: uint8(udp.ProtocolNumber), - SrcAddr: h.srcAddr.Addr, - DstAddr: h.dstAddr.Addr, + SrcAddr: h.Src.Addr, + DstAddr: h.Dst.Addr, }) ip.SetChecksum(^ip.CalculateChecksum()) // Initialize the UDP header. u := header.UDP(buf[header.IPv4MinimumSize:]) u.Encode(&header.UDPFields{ - SrcPort: h.srcAddr.Port, - DstPort: h.dstAddr.Port, + SrcPort: h.Src.Port, + DstPort: h.Dst.Port, Length: uint16(header.UDPMinimumSize + len(payload)), }) // Calculate the UDP pseudo-header checksum. - xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.srcAddr.Addr, h.dstAddr.Addr, uint16(len(u))) + xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.Src.Addr, h.Dst.Addr, uint16(len(u))) // Calculate the UDP checksum and set it. xsum = header.Checksum(payload, xsum) u.SetChecksum(^u.CalculateChecksum(xsum)) + if badChecksum { + // Invalidate the UDP header checksum field, taking care to avoid overflow + // to zero, which would disable checksum validation. + for { + u.SetChecksum(u.Checksum() + 1) + if u.Checksum() != 0 { + break + } + } + } + return buf } -func newPayload() []byte { - return newMinPayload(30) +func buildV6Packet(payload []byte, h context.Header4Tuple, badChecksum bool) buffer.View { + // Allocate a buffer for data and headers. + buf := buffer.NewView(header.UDPMinimumSize + header.IPv6MinimumSize + len(payload)) + payloadStart := len(buf) - len(payload) + copy(buf[payloadStart:], payload) + + // Initialize the IP header. + ip := header.IPv6(buf) + ip.Encode(&header.IPv6Fields{ + TrafficClass: testTOS, + PayloadLength: uint16(header.UDPMinimumSize + len(payload)), + TransportProtocol: udp.ProtocolNumber, + HopLimit: 65, + SrcAddr: h.Src.Addr, + DstAddr: h.Dst.Addr, + }) + + // Initialize the UDP header. + u := header.UDP(buf[header.IPv6MinimumSize:]) + u.Encode(&header.UDPFields{ + SrcPort: h.Src.Port, + DstPort: h.Dst.Port, + Length: uint16(header.UDPMinimumSize + len(payload)), + }) + + // Calculate the UDP pseudo-header checksum. + xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.Src.Addr, h.Dst.Addr, uint16(len(u))) + + // Calculate the UDP checksum and set it. + xsum = header.Checksum(payload, xsum) + u.SetChecksum(^u.CalculateChecksum(xsum)) + + if badChecksum { + // Invalidate the UDP header checksum field (Unlike IPv4, zero is a valid + // checksum value for IPv6 so no need to avoid it). + u := header.UDP(buf[header.IPv6MinimumSize:]) + u.SetChecksum(u.Checksum() + 1) + } + + return buf } -func newMinPayload(minSize int) []byte { - b := make([]byte, minSize+rand.Intn(100)) - for i := range b { - b[i] = byte(rand.Intn(256)) +func buildPacket(payload []byte, flow context.TestFlow, direction context.PacketDirection, badChecksum bool) buffer.View { + h := flow.MakeHeader4Tuple(direction) + if flow.IsV4() { + return buildV4Packet(payload, h, badChecksum) + } + return buildV6Packet(payload, h, badChecksum) +} + +func testRead(c *context.Context, flow context.TestFlow, checkers ...checker.ControlMessagesChecker) { + c.T.Helper() + + payload := newRandomPayload(arbitraryPayloadSize) + c.InjectPacket(flow.NetProto(), buildPacket(payload, flow, context.Incoming, false)) + c.ReadFromEndpointExpectSuccess(payload, flow, checkers...) +} + +func testFailingRead(c *context.Context, flow context.TestFlow, expectReadError bool) { + c.T.Helper() + + c.InjectPacket(flow.NetProto(), buildPacket(newRandomPayload(arbitraryPayloadSize), flow, context.Incoming, false)) + if expectReadError { + c.ReadFromEndpointExpectError() + } else { + c.ReadFromEndpointExpectNoPacket() } - return b } func TestBindToDeviceOption(t *testing.T) { @@ -604,126 +224,35 @@ func TestBindToDeviceOption(t *testing.T) { } } -// testReadInternal sends a packet of the given test flow into the stack by -// injecting it into the link endpoint. It then attempts to read it from the -// UDP endpoint and depending on if this was expected to succeed verifies its -// correctness including any additional checker functions provided. -func testReadInternal(c *testContext, flow testFlow, packetShouldBeDropped, expectReadError bool, checkers ...checker.ControlMessagesChecker) { - c.t.Helper() - - payload := newPayload() - c.injectPacket(flow, payload, false) - - // Try to receive the data. - we, ch := waiter.NewChannelEntry(waiter.ReadableEvents) - c.wq.EventRegister(&we) - defer c.wq.EventUnregister(&we) - - // Take a snapshot of the stats to validate them at the end of the test. - epstats := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone() - - var buf bytes.Buffer - res, err := c.ep.Read(&buf, tcpip.ReadOptions{NeedRemoteAddr: true}) - if _, ok := err.(*tcpip.ErrWouldBlock); ok { - // Wait for data to become available. - select { - case <-ch: - res, err = c.ep.Read(&buf, tcpip.ReadOptions{NeedRemoteAddr: true}) - - default: - if packetShouldBeDropped { - return // expected to time out - } - c.t.Fatal("timed out waiting for data") - } - } - - if expectReadError && err != nil { - c.checkEndpointReadStats(1, epstats, err) - return - } - - if err != nil { - c.t.Fatal("Read failed:", err) - } - - if packetShouldBeDropped { - c.t.Fatalf("Read unexpectedly received data from %s", res.RemoteAddr.Addr) - } - - // Check the read result. - h := flow.header4Tuple(incoming) - if diff := cmp.Diff(tcpip.ReadResult{ - Count: buf.Len(), - Total: buf.Len(), - RemoteAddr: tcpip.FullAddress{Addr: h.srcAddr.Addr}, - }, res, checker.IgnoreCmpPath( - "ControlMessages", // ControlMessages will be checked later. - "RemoteAddr.NIC", - "RemoteAddr.Port", - )); diff != "" { - c.t.Fatalf("Read: unexpected result (-want +got):\n%s", diff) - } - - // Check the payload. - v := buf.Bytes() - if !bytes.Equal(payload, v) { - c.t.Fatalf("got payload = %x, want = %x", v, payload) - } - - // Run any checkers against the ControlMessages. - for _, f := range checkers { - f(c.t, res.ControlMessages) - } - - c.checkEndpointReadStats(1, epstats, err) -} - -// testRead sends a packet of the given test flow into the stack by injecting it -// into the link endpoint. It then reads it from the UDP endpoint and verifies -// its correctness including any additional checker functions provided. -func testRead(c *testContext, flow testFlow, checkers ...checker.ControlMessagesChecker) { - c.t.Helper() - testReadInternal(c, flow, false /* packetShouldBeDropped */, false /* expectReadError */, checkers...) -} - -// testFailingRead sends a packet of the given test flow into the stack by -// injecting it into the link endpoint. It then tries to read it from the UDP -// endpoint and expects this to fail. -func testFailingRead(c *testContext, flow testFlow, expectReadError bool) { - c.t.Helper() - testReadInternal(c, flow, true /* packetShouldBeDropped */, expectReadError) -} - func TestBindEphemeralPort(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) - if err := c.ep.Bind(tcpip.FullAddress{}); err != nil { + if err := c.EP.Bind(tcpip.FullAddress{}); err != nil { t.Fatalf("ep.Bind(...) failed: %s", err) } } func TestBindReservedPort(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) - if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil { - c.t.Fatalf("Connect failed: %s", err) + if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort}); err != nil { + c.T.Fatalf("Connect failed: %s", err) } - addr, err := c.ep.GetLocalAddress() + addr, err := c.EP.GetLocalAddress() if err != nil { t.Fatalf("GetLocalAddress failed: %s", err) } // We can't bind the address reserved by the connected endpoint above. { - ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv6.ProtocolNumber, &c.wq) + ep, err := c.Stack.NewEndpoint(udp.ProtocolNumber, ipv6.ProtocolNumber, &c.WQ) if err != nil { t.Fatalf("NewEndpoint failed: %s", err) } @@ -737,7 +266,7 @@ func TestBindReservedPort(t *testing.T) { } func() { - ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &c.wq) + ep, err := c.Stack.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ) if err != nil { t.Fatalf("NewEndpoint failed: %s", err) } @@ -751,16 +280,16 @@ func TestBindReservedPort(t *testing.T) { } } // We can bind an ipv4 address on this port, though. - if err := ep.Bind(tcpip.FullAddress{Addr: stackAddr, Port: addr.Port}); err != nil { + if err := ep.Bind(tcpip.FullAddress{Addr: context.StackAddr, Port: addr.Port}); err != nil { t.Fatalf("ep.Bind(...) failed: %s", err) } }() // Once the connected endpoint releases its port reservation, we are able to // bind ipv4-any once again. - c.ep.Close() + c.EP.Close() func() { - ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &c.wq) + ep, err := c.Stack.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ) if err != nil { t.Fatalf("NewEndpoint failed: %s", err) } @@ -772,63 +301,65 @@ func TestBindReservedPort(t *testing.T) { } func TestV4ReadOnV6(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(unicastV4in6) + c.CreateEndpointForFlow(context.UnicastV4in6, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - // Test acceptance. - testRead(c, unicastV4in6) + payload := newRandomPayload(arbitraryPayloadSize) + buf := buildPacket(payload, context.UnicastV4in6, context.Incoming, false) + c.InjectPacket(header.IPv4ProtocolNumber, buf) + c.ReadFromEndpointExpectSuccess(payload, context.UnicastV4in6) } func TestV4ReadOnBoundToV4MappedWildcard(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(unicastV4in6) + c.CreateEndpointForFlow(context.UnicastV4in6, udp.ProtocolNumber) // Bind to v4 mapped wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Addr: v4MappedWildcardAddr, Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.V4MappedWildcardAddr, Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } // Test acceptance. - testRead(c, unicastV4in6) + testRead(c, context.UnicastV4in6) } func TestV4ReadOnBoundToV4Mapped(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(unicastV4in6) + c.CreateEndpointForFlow(context.UnicastV4in6, udp.ProtocolNumber) // Bind to local address. - if err := c.ep.Bind(tcpip.FullAddress{Addr: stackV4MappedAddr, Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackV4MappedAddr, Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } // Test acceptance. - testRead(c, unicastV4in6) + testRead(c, context.UnicastV4in6) } func TestV6ReadOnV6(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(unicastV6) + c.CreateEndpointForFlow(context.UnicastV6, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } // Test acceptance. - testRead(c, unicastV6) + testRead(c, context.UnicastV6) } // TestV4ReadSelfSource checks that packets coming from a local IP address are @@ -844,81 +375,78 @@ func TestV4ReadSelfSource(t *testing.T) { {"NoHandleLocal", true, &tcpip.ErrWouldBlock{}, 1}, } { t.Run(tt.name, func(t *testing.T) { - c := newDualTestContextWithHandleLocal(t, defaultMTU, tt.handleLocal) - defer c.cleanup() + c := context.NewWithOptions(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}, context.Options{ + MTU: context.DefaultMTU, + HandleLocal: tt.handleLocal, + }) + defer c.Cleanup() - c.createEndpointForFlow(unicastV4) + c.CreateEndpointForFlow(context.UnicastV4, udp.ProtocolNumber) - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { t.Fatalf("Bind failed: %s", err) } - payload := newPayload() - h := unicastV4.header4Tuple(incoming) - h.srcAddr = h.dstAddr + payload := newRandomPayload(arbitraryPayloadSize) + h := context.UnicastV4.MakeHeader4Tuple(context.Incoming) + h.Src = h.Dst + c.InjectPacket(header.IPv4ProtocolNumber, buildV4Packet(payload, h, false)) - buf := c.buildV4Packet(payload, &h) - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv4.ProtocolNumber, pkt) - - if got := c.s.Stats().IP.InvalidSourceAddressesReceived.Value(); got != tt.wantInvalidSource { - t.Errorf("c.s.Stats().IP.InvalidSourceAddressesReceived got %d, want %d", got, tt.wantInvalidSource) + if got := c.Stack.Stats().IP.InvalidSourceAddressesReceived.Value(); got != tt.wantInvalidSource { + t.Errorf("c.Stack.Stats().IP.InvalidSourceAddressesReceived got %d, want %d", got, tt.wantInvalidSource) } - if _, err := c.ep.Read(ioutil.Discard, tcpip.ReadOptions{}); err != tt.wantErr { - t.Errorf("got c.ep.Read = %s, want = %s", err, tt.wantErr) + if _, err := c.EP.Read(ioutil.Discard, tcpip.ReadOptions{}); err != tt.wantErr { + t.Errorf("got c.EP.Read = %s, want = %s", err, tt.wantErr) } }) } } func TestV4ReadOnV4(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(unicastV4) + c.CreateEndpointForFlow(context.UnicastV4, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } // Test acceptance. - testRead(c, unicastV4) + testRead(c, context.UnicastV4) } // TestReadOnBoundToMulticast checks that an endpoint can bind to a multicast // address and receive data sent to that address. func TestReadOnBoundToMulticast(t *testing.T) { - // FIXME(b/128189410): multicastV4in6 currently doesn't work as + // FIXME(b/128189410): context.MulticastV4in6 currently doesn't work as // AddMembershipOption doesn't handle V4in6 addresses. - for _, flow := range []testFlow{multicastV4, multicastV6, multicastV6Only} { + for _, flow := range []context.TestFlow{context.MulticastV4, context.MulticastV6, context.MulticastV6Only} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to multicast address. - mcastAddr := flow.mapAddrIfApplicable(flow.getMcastAddr()) - if err := c.ep.Bind(tcpip.FullAddress{Addr: mcastAddr, Port: stackPort}); err != nil { - c.t.Fatal("Bind failed:", err) + mcastAddr := flow.MapAddrIfApplicable(flow.GetMulticastAddr()) + if err := c.EP.Bind(tcpip.FullAddress{Addr: mcastAddr, Port: context.StackPort}); err != nil { + c.T.Fatal("Bind failed:", err) } // Join multicast group. ifoptSet := tcpip.AddMembershipOption{NIC: 1, MulticastAddr: mcastAddr} - if err := c.ep.SetSockOpt(&ifoptSet); err != nil { - c.t.Fatalf("SetSockOpt(&%#v): %s", ifoptSet, err) + if err := c.EP.SetSockOpt(&ifoptSet); err != nil { + c.T.Fatalf("SetSockOpt(&%#v): %s", ifoptSet, err) } // Check that we receive multicast packets but not unicast or broadcast // ones. testRead(c, flow) - testFailingRead(c, broadcast, false /* expectReadError */) - testFailingRead(c, unicastV4, false /* expectReadError */) + testFailingRead(c, context.Broadcast, false /* expectReadError */) + testFailingRead(c, context.UnicastV4, false /* expectReadError */) }) } } @@ -926,22 +454,22 @@ func TestReadOnBoundToMulticast(t *testing.T) { // TestV4ReadOnBoundToBroadcast checks that an endpoint can bind to a broadcast // address and can receive only broadcast data. func TestV4ReadOnBoundToBroadcast(t *testing.T) { - for _, flow := range []testFlow{broadcast, broadcastIn6} { + for _, flow := range []context.TestFlow{context.Broadcast, context.BroadcastIn6} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to broadcast address. - bcastAddr := flow.mapAddrIfApplicable(broadcastAddr) - if err := c.ep.Bind(tcpip.FullAddress{Addr: bcastAddr, Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + broadcastAddr := flow.MapAddrIfApplicable(context.BroadcastAddr) + if err := c.EP.Bind(tcpip.FullAddress{Addr: broadcastAddr, Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } // Check that we receive broadcast packets but not unicast ones. testRead(c, flow) - testFailingRead(c, unicastV4, false /* expectReadError */) + testFailingRead(c, context.UnicastV4, false /* expectReadError */) }) } } @@ -949,14 +477,14 @@ func TestV4ReadOnBoundToBroadcast(t *testing.T) { // TestReadFromMulticast checks that an endpoint will NOT receive a packet // that was sent with multicast SOURCE address. func TestReadFromMulticast(t *testing.T) { - for _, flow := range []testFlow{reverseMulticast4, reverseMulticast6} { + for _, flow := range []context.TestFlow{context.ReverseMulticastV4, context.ReverseMulticastV6} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { t.Fatalf("Bind failed: %s", err) } testFailingRead(c, flow, false /* expectReadError */) @@ -967,42 +495,44 @@ func TestReadFromMulticast(t *testing.T) { // TestV4ReadBroadcastOnBoundToWildcard checks that an endpoint can bind to ANY // and receive broadcast and unicast data. func TestV4ReadBroadcastOnBoundToWildcard(t *testing.T) { - for _, flow := range []testFlow{broadcast, broadcastIn6} { + for _, flow := range []context.TestFlow{context.Broadcast, context.BroadcastIn6} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s (", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s (", err) } // Check that we receive both broadcast and unicast packets. testRead(c, flow) - testRead(c, unicastV4) + testRead(c, context.UnicastV4) }) } } // testFailingWrite sends a packet of the given test flow into the UDP endpoint // and verifies it fails with the provided error code. -func testFailingWrite(c *testContext, flow testFlow, wantErr tcpip.Error) { - c.t.Helper() +// TODO(https://gvisor.dev/issue/5623): Extract the test write methods in the +// testing context. +func testFailingWrite(c *context.Context, flow context.TestFlow, wantErr tcpip.Error) { + c.T.Helper() // Take a snapshot of the stats to validate them at the end of the test. - epstats := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone() - h := flow.header4Tuple(outgoing) - writeDstAddr := flow.mapAddrIfApplicable(h.dstAddr.Addr) + epstats := c.EP.Stats().(*tcpip.TransportEndpointStats).Clone() + h := flow.MakeHeader4Tuple(context.Outgoing) + writeDstAddr := flow.MapAddrIfApplicable(h.Dst.Addr) var r bytes.Reader - r.Reset(newPayload()) - _, gotErr := c.ep.Write(&r, tcpip.WriteOptions{ - To: &tcpip.FullAddress{Addr: writeDstAddr, Port: h.dstAddr.Port}, + r.Reset(newRandomPayload(arbitraryPayloadSize)) + _, gotErr := c.EP.Write(&r, tcpip.WriteOptions{ + To: &tcpip.FullAddress{Addr: writeDstAddr, Port: h.Dst.Port}, }) - c.checkEndpointWriteStats(1, epstats, gotErr) + c.CheckEndpointWriteStats(1, epstats, gotErr) if gotErr != wantErr { - c.t.Fatalf("Write returned unexpected error: got %v, want %v", gotErr, wantErr) + c.T.Fatalf("Write returned unexpected error: got %v, want %v", gotErr, wantErr) } } @@ -1010,8 +540,10 @@ func testFailingWrite(c *testContext, flow testFlow, wantErr tcpip.Error) { // flow's destination address:port. It then receives it from the link endpoint // and verifies its correctness including any additional checker functions // provided. -func testWrite(c *testContext, flow testFlow, checkers ...checker.NetworkChecker) uint16 { - c.t.Helper() +// TODO(https://gvisor.dev/issue/5623): Extract the test write methods in the +// testing context. +func testWrite(c *context.Context, flow context.TestFlow, checkers ...checker.NetworkChecker) uint16 { + c.T.Helper() return testWriteAndVerifyInternal(c, flow, true, checkers...) } @@ -1019,217 +551,249 @@ func testWrite(c *testContext, flow testFlow, checkers ...checker.NetworkChecker // UDP endpoint without giving a destination address:port. It then receives it // from the link endpoint and verifies its correctness including any additional // checker functions provided. -func testWriteWithoutDestination(c *testContext, flow testFlow, checkers ...checker.NetworkChecker) uint16 { - c.t.Helper() +// TODO(https://gvisor.dev/issue/5623): Extract the test write methods in the +// testing context. +func testWriteWithoutDestination(c *context.Context, flow context.TestFlow, checkers ...checker.NetworkChecker) uint16 { + c.T.Helper() return testWriteAndVerifyInternal(c, flow, false, checkers...) } -func testWriteNoVerify(c *testContext, flow testFlow, setDest bool) buffer.View { - c.t.Helper() +// TODO(https://gvisor.dev/issue/5623): Extract the test write methods in the +// testing context. +func testWriteNoVerify(c *context.Context, flow context.TestFlow, setDest bool) buffer.View { + c.T.Helper() // Take a snapshot of the stats to validate them at the end of the test. - epstats := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone() + epstats := c.EP.Stats().(*tcpip.TransportEndpointStats).Clone() writeOpts := tcpip.WriteOptions{} if setDest { - h := flow.header4Tuple(outgoing) - writeDstAddr := flow.mapAddrIfApplicable(h.dstAddr.Addr) + h := flow.MakeHeader4Tuple(context.Outgoing) + writeDstAddr := flow.MapAddrIfApplicable(h.Dst.Addr) writeOpts = tcpip.WriteOptions{ - To: &tcpip.FullAddress{Addr: writeDstAddr, Port: h.dstAddr.Port}, + To: &tcpip.FullAddress{Addr: writeDstAddr, Port: h.Dst.Port}, } } var r bytes.Reader - payload := newPayload() + payload := newRandomPayload(arbitraryPayloadSize) r.Reset(payload) - n, err := c.ep.Write(&r, writeOpts) + n, err := c.EP.Write(&r, writeOpts) if err != nil { - c.t.Fatalf("Write failed: %s", err) + c.T.Fatalf("Write failed: %s", err) } if n != int64(len(payload)) { - c.t.Fatalf("Bad number of bytes written: got %v, want %v", n, len(payload)) + c.T.Fatalf("Bad number of bytes written: got %v, want %v", n, len(payload)) } - c.checkEndpointWriteStats(1, epstats, err) + c.CheckEndpointWriteStats(1, epstats, err) return payload } -func testWriteAndVerifyInternal(c *testContext, flow testFlow, setDest bool, checkers ...checker.NetworkChecker) uint16 { - c.t.Helper() +// TODO(https://gvisor.dev/issue/5623): Extract the test write methods in the +// testing context. +func testWriteAndVerifyInternal(c *context.Context, flow context.TestFlow, setDest bool, checkers ...checker.NetworkChecker) uint16 { + c.T.Helper() payload := testWriteNoVerify(c, flow, setDest) // Received the packet and check the payload. - b := c.getPacketAndVerify(flow, checkers...) + + p := c.LinkEP.Read() + if p == nil { + c.T.Fatalf("Packet wasn't written out") + } + + if got, want := p.NetworkProtocolNumber, flow.NetProto(); got != want { + c.T.Fatalf("got p.NetworkProtocolNumber = %d, want = %d", got, want) + } + + if got, want := p.TransportProtocolNumber, header.UDPProtocolNumber; got != want { + c.T.Errorf("got p.TransportProtocolNumber = %d, want = %d", got, want) + } + + vv := buffer.NewVectorisedView(p.Size(), p.Views()) + b := vv.ToView() + + h := flow.MakeHeader4Tuple(context.Outgoing) + checkers = append( + checkers, + checker.SrcAddr(h.Src.Addr), + checker.DstAddr(h.Dst.Addr), + checker.UDP(checker.DstPort(h.Dst.Port)), + ) + flow.CheckerFn()(c.T, b, checkers...) + var udpH header.UDP - if flow.isV4() { + if flow.IsV4() { udpH = header.IPv4(b).Payload() } else { udpH = header.IPv6(b).Payload() } if !bytes.Equal(payload, udpH.Payload()) { - c.t.Fatalf("Bad payload: got %x, want %x", udpH.Payload(), payload) + c.T.Fatalf("Bad payload: got %x, want %x", udpH.Payload(), payload) } return udpH.SourcePort() } -func testDualWrite(c *testContext) uint16 { - c.t.Helper() +func testDualWrite(c *context.Context) uint16 { + c.T.Helper() - v4Port := testWrite(c, unicastV4in6) - v6Port := testWrite(c, unicastV6) + v4Port := testWrite(c, context.UnicastV4in6) + v6Port := testWrite(c, context.UnicastV6) if v4Port != v6Port { - c.t.Fatalf("expected v4 and v6 ports to be equal: got v4Port = %d, v6Port = %d", v4Port, v6Port) + c.T.Fatalf("expected v4 and v6 ports to be equal: got v4Port = %d, v6Port = %d", v4Port, v6Port) } return v4Port } func TestDualWriteUnbound(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) testDualWrite(c) } func TestDualWriteBoundToWildcard(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } p := testDualWrite(c) - if p != stackPort { - c.t.Fatalf("Bad port: got %v, want %v", p, stackPort) + if p != context.StackPort { + c.T.Fatalf("Bad port: got %v, want %v", p, context.StackPort) } } func TestDualWriteConnectedToV6(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Connect to v6 address. - if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - testWrite(c, unicastV6) + testWrite(c, context.UnicastV6) // Write to V4 mapped address. - testFailingWrite(c, unicastV4in6, &tcpip.ErrNetworkUnreachable{}) + testFailingWrite(c, context.UnicastV4in6, &tcpip.ErrNetworkUnreachable{}) const want = 1 - if got := c.ep.Stats().(*tcpip.TransportEndpointStats).SendErrors.NoRoute.Value(); got != want { - c.t.Fatalf("Endpoint stat not updated. got %d want %d", got, want) + if got := c.EP.Stats().(*tcpip.TransportEndpointStats).SendErrors.NoRoute.Value(); got != want { + c.T.Fatalf("Endpoint stat not updated. got %d want %d", got, want) } } func TestDualWriteConnectedToV4Mapped(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Connect to v4 mapped address. - if err := c.ep.Connect(tcpip.FullAddress{Addr: testV4MappedAddr, Port: testPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV4MappedAddr, Port: context.TestPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - testWrite(c, unicastV4in6) + testWrite(c, context.UnicastV4in6) // Write to v6 address. - testFailingWrite(c, unicastV6, &tcpip.ErrInvalidEndpointState{}) + testFailingWrite(c, context.UnicastV6, &tcpip.ErrInvalidEndpointState{}) } func TestV4WriteOnV6Only(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(unicastV6Only) + c.CreateEndpointForFlow(context.UnicastV6Only, udp.ProtocolNumber) // Write to V4 mapped address. - testFailingWrite(c, unicastV4in6, &tcpip.ErrNoRoute{}) + testFailingWrite(c, context.UnicastV4in6, &tcpip.ErrNoRoute{}) } func TestV6WriteOnBoundToV4Mapped(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Bind to v4 mapped address. - if err := c.ep.Bind(tcpip.FullAddress{Addr: stackV4MappedAddr, Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackV4MappedAddr, Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } // Write to v6 address. - testFailingWrite(c, unicastV6, &tcpip.ErrInvalidEndpointState{}) + testFailingWrite(c, context.UnicastV6, &tcpip.ErrInvalidEndpointState{}) } func TestV6WriteOnConnected(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Connect to v6 address. - if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil { - c.t.Fatalf("Connect failed: %s", err) + if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort}); err != nil { + c.T.Fatalf("Connect failed: %s", err) } - testWriteWithoutDestination(c, unicastV6) + testWriteWithoutDestination(c, context.UnicastV6) } func TestV4WriteOnConnected(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Connect to v4 mapped address. - if err := c.ep.Connect(tcpip.FullAddress{Addr: testV4MappedAddr, Port: testPort}); err != nil { - c.t.Fatalf("Connect failed: %s", err) + if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV4MappedAddr, Port: context.TestPort}); err != nil { + c.T.Fatalf("Connect failed: %s", err) } - testWriteWithoutDestination(c, unicastV4) + testWriteWithoutDestination(c, context.UnicastV4) } func TestWriteOnConnectedInvalidPort(t *testing.T) { + const invalidPort = 8192 protocols := map[string]tcpip.NetworkProtocolNumber{ "ipv4": ipv4.ProtocolNumber, "ipv6": ipv6.ProtocolNumber, } - for name, pn := range protocols { + for name, proto := range protocols { t.Run(name, func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(pn) - if err := c.ep.Connect(tcpip.FullAddress{Addr: stackAddr, Port: invalidPort}); err != nil { - c.t.Fatalf("Connect failed: %s", err) + c.CreateEndpoint(proto, udp.ProtocolNumber) + if err := c.EP.Connect(tcpip.FullAddress{Addr: context.StackAddr, Port: invalidPort}); err != nil { + c.T.Fatalf("Connect failed: %s", err) } writeOpts := tcpip.WriteOptions{ - To: &tcpip.FullAddress{Addr: stackAddr, Port: invalidPort}, + To: &tcpip.FullAddress{Addr: context.StackAddr, Port: invalidPort}, } var r bytes.Reader - payload := newPayload() + payload := newRandomPayload(arbitraryPayloadSize) r.Reset(payload) - n, err := c.ep.Write(&r, writeOpts) + n, err := c.EP.Write(&r, writeOpts) if err != nil { - c.t.Fatalf("c.ep.Write(...) = %s, want nil", err) + c.T.Fatalf("c.EP.Write(...) = %s, want nil", err) } if got, want := n, int64(len(payload)); got != want { - c.t.Fatalf("c.ep.Write(...) wrote %d bytes, want %d bytes", got, want) + c.T.Fatalf("c.EP.Write(...) wrote %d bytes, want %d bytes", got, want) } { - err := c.ep.LastError() + err := c.EP.LastError() if _, ok := err.(*tcpip.ErrConnectionRefused); !ok { - c.t.Fatalf("expected c.ep.LastError() == ErrConnectionRefused, got: %+v", err) + c.T.Fatalf("expected c.EP.LastError() == ErrConnectionRefused, got: %+v", err) } } }) @@ -1239,16 +803,16 @@ func TestWriteOnConnectedInvalidPort(t *testing.T) { // TestWriteOnBoundToV4Multicast checks that we can send packets out of a socket // that is bound to a V4 multicast address. func TestWriteOnBoundToV4Multicast(t *testing.T) { - for _, flow := range []testFlow{unicastV4, multicastV4, broadcast} { + for _, flow := range []context.TestFlow{context.UnicastV4, context.MulticastV4, context.Broadcast} { t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to V4 mcast address. - if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastAddr, Port: stackPort}); err != nil { - c.t.Fatal("Bind failed:", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.MulticastAddr, Port: context.StackPort}); err != nil { + c.T.Fatal("Bind failed:", err) } testWrite(c, flow) @@ -1259,16 +823,16 @@ func TestWriteOnBoundToV4Multicast(t *testing.T) { // TestWriteOnBoundToV4MappedMulticast checks that we can send packets out of a // socket that is bound to a V4-mapped multicast address. func TestWriteOnBoundToV4MappedMulticast(t *testing.T) { - for _, flow := range []testFlow{unicastV4in6, multicastV4in6, broadcastIn6} { + for _, flow := range []context.TestFlow{context.UnicastV4in6, context.MulticastV4in6, context.BroadcastIn6} { t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to V4Mapped mcast address. - if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV4MappedAddr, Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.MulticastV4MappedAddr, Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } testWrite(c, flow) @@ -1279,16 +843,16 @@ func TestWriteOnBoundToV4MappedMulticast(t *testing.T) { // TestWriteOnBoundToV6Multicast checks that we can send packets out of a // socket that is bound to a V6 multicast address. func TestWriteOnBoundToV6Multicast(t *testing.T) { - for _, flow := range []testFlow{unicastV6, multicastV6} { + for _, flow := range []context.TestFlow{context.UnicastV6, context.MulticastV6} { t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to V6 mcast address. - if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV6Addr, Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.MulticastV6Addr, Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } testWrite(c, flow) @@ -1299,16 +863,16 @@ func TestWriteOnBoundToV6Multicast(t *testing.T) { // TestWriteOnBoundToV6Multicast checks that we can send packets out of a // V6-only socket that is bound to a V6 multicast address. func TestWriteOnBoundToV6OnlyMulticast(t *testing.T) { - for _, flow := range []testFlow{unicastV6Only, multicastV6Only} { + for _, flow := range []context.TestFlow{context.UnicastV6Only, context.MulticastV6Only} { t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to V6 mcast address. - if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV6Addr, Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.MulticastV6Addr, Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } testWrite(c, flow) @@ -1319,16 +883,16 @@ func TestWriteOnBoundToV6OnlyMulticast(t *testing.T) { // TestWriteOnBoundToBroadcast checks that we can send packets out of a // socket that is bound to the broadcast address. func TestWriteOnBoundToBroadcast(t *testing.T) { - for _, flow := range []testFlow{unicastV4, multicastV4, broadcast} { + for _, flow := range []context.TestFlow{context.UnicastV4, context.MulticastV4, context.Broadcast} { t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to V4 broadcast address. - if err := c.ep.Bind(tcpip.FullAddress{Addr: broadcastAddr, Port: stackPort}); err != nil { - c.t.Fatal("Bind failed:", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.BroadcastAddr, Port: context.StackPort}); err != nil { + c.T.Fatal("Bind failed:", err) } testWrite(c, flow) @@ -1339,16 +903,16 @@ func TestWriteOnBoundToBroadcast(t *testing.T) { // TestWriteOnBoundToV4MappedBroadcast checks that we can send packets out of a // socket that is bound to the V4-mapped broadcast address. func TestWriteOnBoundToV4MappedBroadcast(t *testing.T) { - for _, flow := range []testFlow{unicastV4in6, multicastV4in6, broadcastIn6} { + for _, flow := range []context.TestFlow{context.UnicastV4in6, context.MulticastV4in6, context.BroadcastIn6} { t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Bind to V4Mapped mcast address. - if err := c.ep.Bind(tcpip.FullAddress{Addr: broadcastV4MappedAddr, Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Addr: context.BroadcastV4MappedAddr, Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } testWrite(c, flow) @@ -1357,22 +921,22 @@ func TestWriteOnBoundToV4MappedBroadcast(t *testing.T) { } func TestReadIncrementsPacketsReceived(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() // Create IPv4 UDP endpoint - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - testRead(c, unicastV4) + testRead(c, context.UnicastV4) var want uint64 = 1 - if got := c.s.Stats().UDP.PacketsReceived.Value(); got != want { - c.t.Fatalf("Read did not increment PacketsReceived: got %v, want %v", got, want) + if got := c.Stack.Stats().UDP.PacketsReceived.Value(); got != want { + c.T.Fatalf("Read did not increment PacketsReceived: got %v, want %v", got, want) } } @@ -1380,66 +944,66 @@ func TestReadIPPacketInfo(t *testing.T) { tests := []struct { name string proto tcpip.NetworkProtocolNumber - flow testFlow + flow context.TestFlow checker func(tcpip.NICID) checker.ControlMessagesChecker }{ { name: "IPv4 unicast", proto: header.IPv4ProtocolNumber, - flow: unicastV4, + flow: context.UnicastV4, checker: func(id tcpip.NICID) checker.ControlMessagesChecker { return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ NIC: id, - LocalAddr: stackAddr, - DestinationAddr: stackAddr, + LocalAddr: context.StackAddr, + DestinationAddr: context.StackAddr, }) }, }, { name: "IPv4 multicast", proto: header.IPv4ProtocolNumber, - flow: multicastV4, + flow: context.MulticastV4, checker: func(id tcpip.NICID) checker.ControlMessagesChecker { return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ NIC: id, // TODO(gvisor.dev/issue/3556): Check for a unicast address. - LocalAddr: multicastAddr, - DestinationAddr: multicastAddr, + LocalAddr: context.MulticastAddr, + DestinationAddr: context.MulticastAddr, }) }, }, { name: "IPv4 broadcast", proto: header.IPv4ProtocolNumber, - flow: broadcast, + flow: context.Broadcast, checker: func(id tcpip.NICID) checker.ControlMessagesChecker { return checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{ NIC: id, // TODO(gvisor.dev/issue/3556): Check for a unicast address. - LocalAddr: broadcastAddr, - DestinationAddr: broadcastAddr, + LocalAddr: context.BroadcastAddr, + DestinationAddr: context.BroadcastAddr, }) }, }, { name: "IPv6 unicast", proto: header.IPv6ProtocolNumber, - flow: unicastV6, + flow: context.UnicastV6, checker: func(id tcpip.NICID) checker.ControlMessagesChecker { return checker.ReceiveIPv6PacketInfo(tcpip.IPv6PacketInfo{ NIC: id, - Addr: stackV6Addr, + Addr: context.StackV6Addr, }) }, }, { name: "IPv6 multicast", proto: header.IPv6ProtocolNumber, - flow: multicastV6, + flow: context.MulticastV6, checker: func(id tcpip.NICID) checker.ControlMessagesChecker { return checker.ReceiveIPv6PacketInfo(tcpip.IPv6PacketInfo{ NIC: id, - Addr: multicastV6Addr, + Addr: context.MulticastV6Addr, }) }, }, @@ -1447,35 +1011,35 @@ func TestReadIPPacketInfo(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(test.proto) + c.CreateEndpoint(test.proto, udp.ProtocolNumber) - bindAddr := tcpip.FullAddress{Port: stackPort} - if err := c.ep.Bind(bindAddr); err != nil { + bindAddr := tcpip.FullAddress{Port: context.StackPort} + if err := c.EP.Bind(bindAddr); err != nil { t.Fatalf("Bind(%+v): %s", bindAddr, err) } - if test.flow.isMulticast() { - ifoptSet := tcpip.AddMembershipOption{NIC: 1, MulticastAddr: test.flow.getMcastAddr()} - if err := c.ep.SetSockOpt(&ifoptSet); err != nil { - c.t.Fatalf("SetSockOpt(&%#v): %s:", ifoptSet, err) + if test.flow.IsMulticast() { + ifoptSet := tcpip.AddMembershipOption{NIC: context.NICID, MulticastAddr: test.flow.GetMulticastAddr()} + if err := c.EP.SetSockOpt(&ifoptSet); err != nil { + c.T.Fatalf("SetSockOpt(&%#v): %s:", ifoptSet, err) } } - switch f := test.flow.netProto(); f { + switch f := test.flow.NetProto(); f { case header.IPv4ProtocolNumber: - c.ep.SocketOptions().SetReceivePacketInfo(true) + c.EP.SocketOptions().SetReceivePacketInfo(true) case header.IPv6ProtocolNumber: - c.ep.SocketOptions().SetIPv6ReceivePacketInfo(true) + c.EP.SocketOptions().SetIPv6ReceivePacketInfo(true) default: t.Fatalf("unhandled protocol number = %d", f) } - testRead(c, test.flow, test.checker(c.nicID)) + testRead(c, test.flow, test.checker(context.NICID)) - if got := c.s.Stats().UDP.PacketsReceived.Value(); got != 1 { + if got := c.Stack.Stats().UDP.PacketsReceived.Value(); got != 1 { t.Fatalf("Read did not increment PacketsReceived: got = %d, want = 1", got) } }) @@ -1486,83 +1050,83 @@ func TestReadRecvOriginalDstAddr(t *testing.T) { tests := []struct { name string proto tcpip.NetworkProtocolNumber - flow testFlow + flow context.TestFlow expectedOriginalDstAddr tcpip.FullAddress }{ { name: "IPv4 unicast", proto: header.IPv4ProtocolNumber, - flow: unicastV4, - expectedOriginalDstAddr: tcpip.FullAddress{NIC: 1, Addr: stackAddr, Port: stackPort}, + flow: context.UnicastV4, + expectedOriginalDstAddr: tcpip.FullAddress{NIC: context.NICID, Addr: context.StackAddr, Port: context.StackPort}, }, { name: "IPv4 multicast", proto: header.IPv4ProtocolNumber, - flow: multicastV4, + flow: context.MulticastV4, // This should actually be a unicast address assigned to the interface. // // TODO(gvisor.dev/issue/3556): This check is validating incorrect - // behaviour. We still include the test so that once the bug is - // resolved, this test will start to fail and the individual tasked - // with fixing this bug knows to also fix this test :). - expectedOriginalDstAddr: tcpip.FullAddress{NIC: 1, Addr: multicastAddr, Port: stackPort}, + // behaviour. We still include the test so that once the bug is resolved, + // this test will start to fail and the individual tasked with fixing this + // bug knows to also fix this test :). + expectedOriginalDstAddr: tcpip.FullAddress{NIC: context.NICID, Addr: context.MulticastAddr, Port: context.StackPort}, }, { name: "IPv4 broadcast", proto: header.IPv4ProtocolNumber, - flow: broadcast, + flow: context.Broadcast, // This should actually be a unicast address assigned to the interface. // // TODO(gvisor.dev/issue/3556): This check is validating incorrect - // behaviour. We still include the test so that once the bug is - // resolved, this test will start to fail and the individual tasked - // with fixing this bug knows to also fix this test :). - expectedOriginalDstAddr: tcpip.FullAddress{NIC: 1, Addr: broadcastAddr, Port: stackPort}, + // behaviour. We still include the test so that once the bug is resolved, + // this test will start to fail and the individual tasked with fixing this + // bug knows to also fix this test :). + expectedOriginalDstAddr: tcpip.FullAddress{NIC: context.NICID, Addr: context.BroadcastAddr, Port: context.StackPort}, }, { name: "IPv6 unicast", proto: header.IPv6ProtocolNumber, - flow: unicastV6, - expectedOriginalDstAddr: tcpip.FullAddress{NIC: 1, Addr: stackV6Addr, Port: stackPort}, + flow: context.UnicastV6, + expectedOriginalDstAddr: tcpip.FullAddress{NIC: context.NICID, Addr: context.StackV6Addr, Port: context.StackPort}, }, { name: "IPv6 multicast", proto: header.IPv6ProtocolNumber, - flow: multicastV6, + flow: context.MulticastV6, // This should actually be a unicast address assigned to the interface. // // TODO(gvisor.dev/issue/3556): This check is validating incorrect - // behaviour. We still include the test so that once the bug is - // resolved, this test will start to fail and the individual tasked - // with fixing this bug knows to also fix this test :). - expectedOriginalDstAddr: tcpip.FullAddress{NIC: 1, Addr: multicastV6Addr, Port: stackPort}, + // behaviour. We still include the test so that once the bug is resolved, + // this test will start to fail and the individual tasked with fixing this + // bug knows to also fix this test :). + expectedOriginalDstAddr: tcpip.FullAddress{NIC: context.NICID, Addr: context.MulticastV6Addr, Port: context.StackPort}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(test.proto) + c.CreateEndpoint(test.proto, udp.ProtocolNumber) - bindAddr := tcpip.FullAddress{Port: stackPort} - if err := c.ep.Bind(bindAddr); err != nil { + bindAddr := tcpip.FullAddress{Port: context.StackPort} + if err := c.EP.Bind(bindAddr); err != nil { t.Fatalf("Bind(%#v): %s", bindAddr, err) } - if test.flow.isMulticast() { - ifoptSet := tcpip.AddMembershipOption{NIC: 1, MulticastAddr: test.flow.getMcastAddr()} - if err := c.ep.SetSockOpt(&ifoptSet); err != nil { - c.t.Fatalf("SetSockOpt(&%#v): %s:", ifoptSet, err) + if test.flow.IsMulticast() { + ifoptSet := tcpip.AddMembershipOption{NIC: context.NICID, MulticastAddr: test.flow.GetMulticastAddr()} + if err := c.EP.SetSockOpt(&ifoptSet); err != nil { + c.T.Fatalf("SetSockOpt(&%#v): %s:", ifoptSet, err) } } - c.ep.SocketOptions().SetReceiveOriginalDstAddress(true) + c.EP.SocketOptions().SetReceiveOriginalDstAddress(true) testRead(c, test.flow, checker.ReceiveOriginalDstAddr(test.expectedOriginalDstAddr)) - if got := c.s.Stats().UDP.PacketsReceived.Value(); got != 1 { + if got := c.Stack.Stats().UDP.PacketsReceived.Value(); got != 1 { t.Fatalf("Read did not increment PacketsReceived: got = %d, want = 1", got) } }) @@ -1570,34 +1134,34 @@ func TestReadRecvOriginalDstAddr(t *testing.T) { } func TestWriteIncrementsPacketsSent(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) testDualWrite(c) var want uint64 = 2 - if got := c.s.Stats().UDP.PacketsSent.Value(); got != want { - c.t.Fatalf("Write did not increment PacketsSent: got %v, want %v", got, want) + if got := c.Stack.Stats().UDP.PacketsSent.Value(); got != want { + c.T.Fatalf("Write did not increment PacketsSent: got %v, want %v", got, want) } } func TestNoChecksum(t *testing.T) { - for _, flow := range []testFlow{unicastV4, unicastV6} { + for _, flow := range []context.TestFlow{context.UnicastV4, context.UnicastV6} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) // Disable the checksum generation. - c.ep.SocketOptions().SetNoChecksum(true) + c.EP.SocketOptions().SetNoChecksum(true) // This option is effective on IPv4 only. - testWrite(c, flow, checker.UDP(checker.NoChecksum(flow.isV4()))) + testWrite(c, flow, checker.UDP(checker.NoChecksum(flow.IsV4()))) // Enable the checksum generation. - c.ep.SocketOptions().SetNoChecksum(false) + c.EP.SocketOptions().SetNoChecksum(false) testWrite(c, flow, checker.UDP(checker.NoChecksum(false))) }) } @@ -1618,15 +1182,15 @@ func (*testInterface) Enabled() bool { } func TestDefaultTTL(t *testing.T) { - for _, flow := range []testFlow{unicastV4, unicastV4in6, unicastV6, unicastV6Only, broadcast, broadcastIn6} { + for _, flow := range []context.TestFlow{context.UnicastV4, context.UnicastV4in6, context.UnicastV6, context.UnicastV6Only, context.Broadcast, context.BroadcastIn6} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) - proto := c.s.NetworkProtocolInstance(flow.netProto()) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) + proto := c.Stack.NetworkProtocolInstance(flow.NetProto()) if proto == nil { - t.Fatalf("c.s.NetworkProtocolInstance(flow.netProto()) did not return a protocol") + t.Fatalf("c.Stack.NetworkProtocolInstance(flow.NetProto()) did not return a protocol") } var initialDefaultTTL tcpip.DefaultTTLOption @@ -1637,39 +1201,39 @@ func TestDefaultTTL(t *testing.T) { newDefaultTTL := tcpip.DefaultTTLOption(initialDefaultTTL + 1) if err := proto.SetOption(&newDefaultTTL); err != nil { - c.t.Fatalf("proto.SetOption(&%T(%d))) failed: %s", newDefaultTTL, newDefaultTTL, err) + c.T.Fatalf("proto.SetOption(&%T(%d))) failed: %s", newDefaultTTL, newDefaultTTL, err) } testWrite(c, flow, checker.TTL(uint8(newDefaultTTL))) }) } } -func TestNonMulticastDefaultTTL(t *testing.T) { - for _, flow := range []testFlow{unicastV4, unicastV4in6, unicastV6, unicastV6Only, broadcast, broadcastIn6} { +func TestSetNonMulticastTTL(t *testing.T) { + for _, flow := range []context.TestFlow{context.UnicastV4, context.UnicastV4in6, context.UnicastV6, context.UnicastV6Only, context.Broadcast, context.BroadcastIn6} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { for _, wantTTL := range []uint8{1, 2, 50, 64, 128, 254, 255} { t.Run(fmt.Sprintf("TTL:%d", wantTTL), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) var relevantOpt tcpip.SockOptInt var irrelevantOpt tcpip.SockOptInt - if flow.isV4() { + if flow.IsV4() { relevantOpt = tcpip.IPv4TTLOption irrelevantOpt = tcpip.IPv6HopLimitOption } else { relevantOpt = tcpip.IPv6HopLimitOption irrelevantOpt = tcpip.IPv4TTLOption } - if err := c.ep.SetSockOptInt(relevantOpt, int(wantTTL)); err != nil { - c.t.Fatalf("SetSockOptInt(%d, %d) failed: %s", relevantOpt, wantTTL, err) + if err := c.EP.SetSockOptInt(relevantOpt, int(wantTTL)); err != nil { + c.T.Fatalf("SetSockOptInt(%d, %d) failed: %s", relevantOpt, wantTTL, err) } // Set a different ttl/hoplimit for the unused protocol, showing that // it does not affect the other protocol. - if err := c.ep.SetSockOptInt(irrelevantOpt, int(wantTTL+1)); err != nil { - c.t.Fatalf("SetSockOptInt(%d, %d) failed: %s", irrelevantOpt, wantTTL, err) + if err := c.EP.SetSockOptInt(irrelevantOpt, int(wantTTL+1)); err != nil { + c.T.Fatalf("SetSockOptInt(%d, %d) failed: %s", irrelevantOpt, wantTTL, err) } testWrite(c, flow, checker.TTL(wantTTL)) @@ -1680,17 +1244,17 @@ func TestNonMulticastDefaultTTL(t *testing.T) { } func TestSetMulticastTTL(t *testing.T) { - for _, flow := range []testFlow{multicastV4, multicastV4in6, multicastV6} { + for _, flow := range []context.TestFlow{context.MulticastV4, context.MulticastV4in6, context.MulticastV6} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { for _, wantTTL := range []uint8{1, 2, 50, 64, 128, 254, 255} { t.Run(fmt.Sprintf("TTL:%d", wantTTL), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) - if err := c.ep.SetSockOptInt(tcpip.MulticastTTLOption, int(wantTTL)); err != nil { - c.t.Fatalf("SetSockOptInt failed: %s", err) + if err := c.EP.SetSockOptInt(tcpip.MulticastTTLOption, int(wantTTL)); err != nil { + c.T.Fatalf("SetSockOptInt failed: %s", err) } testWrite(c, flow, checker.TTL(wantTTL)) @@ -1700,37 +1264,37 @@ func TestSetMulticastTTL(t *testing.T) { } } -var v4PacketFlows = [...]testFlow{unicastV4, multicastV4, broadcast, unicastV4in6, multicastV4in6, broadcastIn6} +var v4PacketFlows = [...]context.TestFlow{context.UnicastV4, context.MulticastV4, context.Broadcast, context.UnicastV4in6, context.MulticastV4in6, context.BroadcastIn6} func TestSetTOS(t *testing.T) { for _, flow := range v4PacketFlows { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) const tos = testTOS - v, err := c.ep.GetSockOptInt(tcpip.IPv4TOSOption) + v, err := c.EP.GetSockOptInt(tcpip.IPv4TOSOption) if err != nil { - c.t.Errorf("GetSockOptInt(IPv4TOSOption) failed: %s", err) + c.T.Errorf("GetSockOptInt(IPv4TOSOption) failed: %s", err) } // Test for expected default value. if v != 0 { - c.t.Errorf("got GetSockOptInt(IPv4TOSOption) = 0x%x, want = 0x%x", v, 0) + c.T.Errorf("got GetSockOptInt(IPv4TOSOption) = 0x%x, want = 0x%x", v, 0) } - if err := c.ep.SetSockOptInt(tcpip.IPv4TOSOption, tos); err != nil { - c.t.Errorf("SetSockOptInt(IPv4TOSOption, 0x%x) failed: %s", tos, err) + if err := c.EP.SetSockOptInt(tcpip.IPv4TOSOption, tos); err != nil { + c.T.Errorf("SetSockOptInt(IPv4TOSOption, 0x%x) failed: %s", tos, err) } - v, err = c.ep.GetSockOptInt(tcpip.IPv4TOSOption) + v, err = c.EP.GetSockOptInt(tcpip.IPv4TOSOption) if err != nil { - c.t.Errorf("GetSockOptInt(IPv4TOSOption) failed: %s", err) + c.T.Errorf("GetSockOptInt(IPv4TOSOption) failed: %s", err) } if v != tos { - c.t.Errorf("got GetSockOptInt(IPv4TOSOption) = 0x%x, want = 0x%x", v, tos) + c.T.Errorf("got GetSockOptInt(IPv4TOSOption) = 0x%x, want = 0x%x", v, tos) } testWrite(c, flow, checker.TOS(tos, 0)) @@ -1738,37 +1302,37 @@ func TestSetTOS(t *testing.T) { } } -var v6PacketFlows = [...]testFlow{unicastV6, unicastV6Only, multicastV6} +var v6PacketFlows = [...]context.TestFlow{context.UnicastV6, context.UnicastV6Only, context.MulticastV6} func TestSetTClass(t *testing.T) { for _, flow := range v6PacketFlows { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) const tClass = testTOS - v, err := c.ep.GetSockOptInt(tcpip.IPv6TrafficClassOption) + v, err := c.EP.GetSockOptInt(tcpip.IPv6TrafficClassOption) if err != nil { - c.t.Errorf("GetSockOptInt(IPv6TrafficClassOption) failed: %s", err) + c.T.Errorf("GetSockOptInt(IPv6TrafficClassOption) failed: %s", err) } // Test for expected default value. if v != 0 { - c.t.Errorf("got GetSockOptInt(IPv6TrafficClassOption) = 0x%x, want = 0x%x", v, 0) + c.T.Errorf("got GetSockOptInt(IPv6TrafficClassOption) = 0x%x, want = 0x%x", v, 0) } - if err := c.ep.SetSockOptInt(tcpip.IPv6TrafficClassOption, tClass); err != nil { - c.t.Errorf("SetSockOptInt(IPv6TrafficClassOption, 0x%x) failed: %s", tClass, err) + if err := c.EP.SetSockOptInt(tcpip.IPv6TrafficClassOption, tClass); err != nil { + c.T.Errorf("SetSockOptInt(IPv6TrafficClassOption, 0x%x) failed: %s", tClass, err) } - v, err = c.ep.GetSockOptInt(tcpip.IPv6TrafficClassOption) + v, err = c.EP.GetSockOptInt(tcpip.IPv6TrafficClassOption) if err != nil { - c.t.Errorf("GetSockOptInt(IPv6TrafficClassOption) failed: %s", err) + c.T.Errorf("GetSockOptInt(IPv6TrafficClassOption) failed: %s", err) } if v != tClass { - c.t.Errorf("got GetSockOptInt(IPv6TrafficClassOption) = 0x%x, want = 0x%x", v, tClass) + c.T.Errorf("got GetSockOptInt(IPv6TrafficClassOption) = 0x%x, want = 0x%x", v, tClass) } // The header getter for TClass is called TOS, so use that checker. @@ -1783,7 +1347,7 @@ func TestReceiveTosTClass(t *testing.T) { testCases := []struct { name string - tests []testFlow + tests []context.TestFlow }{ { name: RcvTOSOpt, @@ -1797,17 +1361,17 @@ func TestReceiveTosTClass(t *testing.T) { for _, testCase := range testCases { for _, flow := range testCase.tests { t.Run(fmt.Sprintf("%s:flow:%s", testCase.name, flow), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpointForFlow(flow) + c.CreateEndpointForFlow(flow, udp.ProtocolNumber) name := testCase.name - if flow.isMulticast() { - netProto := flow.netProto() - addr := flow.getMcastAddr() - if err := c.s.JoinGroup(netProto, c.nicID, addr); err != nil { - c.t.Fatalf("JoinGroup(%d, %d, %s): %s", netProto, c.nicID, addr, err) + if flow.IsMulticast() { + netProto := flow.NetProto() + addr := flow.GetMulticastAddr() + if err := c.Stack.JoinGroup(netProto, context.NICID, addr); err != nil { + c.T.Fatalf("JoinGroup(%d, %d, %s): %s", netProto, context.NICID, addr, err) } } @@ -1815,11 +1379,11 @@ func TestReceiveTosTClass(t *testing.T) { var optionSetter func(bool) switch name { case RcvTOSOpt: - optionGetter = c.ep.SocketOptions().GetReceiveTOS - optionSetter = c.ep.SocketOptions().SetReceiveTOS + optionGetter = c.EP.SocketOptions().GetReceiveTOS + optionSetter = c.EP.SocketOptions().SetReceiveTOS case RcvTClassOpt: - optionGetter = c.ep.SocketOptions().GetReceiveTClass - optionSetter = c.ep.SocketOptions().SetReceiveTClass + optionGetter = c.EP.SocketOptions().GetReceiveTClass + optionSetter = c.EP.SocketOptions().SetReceiveTClass default: t.Fatalf("unkown test variant: %s", name) } @@ -1828,7 +1392,7 @@ func TestReceiveTosTClass(t *testing.T) { v := optionGetter() // Test for expected default value. if v != false { - c.t.Errorf("got GetSockOptBool(%s) = %t, want = %t", name, v, false) + c.T.Errorf("got GetSockOptBool(%s) = %t, want = %t", name, v, false) } const want = true @@ -1836,13 +1400,13 @@ func TestReceiveTosTClass(t *testing.T) { got := optionGetter() if got != want { - c.t.Errorf("got GetSockOptBool(%s) = %t, want = %t", name, got, want) + c.T.Errorf("got GetSockOptBool(%s) = %t, want = %t", name, got, want) } // Verify that the correct received TOS or TClass is handed through as // ancillary data to the ControlMessages struct. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } switch name { case RcvTClassOpt: @@ -1858,15 +1422,15 @@ func TestReceiveTosTClass(t *testing.T) { } func TestMulticastInterfaceOption(t *testing.T) { - for _, flow := range []testFlow{multicastV4, multicastV4in6, multicastV6, multicastV6Only} { + for _, flow := range []context.TestFlow{context.MulticastV4, context.MulticastV4in6, context.MulticastV6, context.MulticastV6Only} { t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) { for _, bindTyp := range []string{"bound", "unbound"} { t.Run(bindTyp, func(t *testing.T) { for _, optTyp := range []string{"use local-addr", "use NICID", "use local-addr and NIC"} { t.Run(optTyp, func(t *testing.T) { - h := flow.header4Tuple(outgoing) - mcastAddr := h.dstAddr.Addr - localIfAddr := h.srcAddr.Addr + h := flow.MakeHeader4Tuple(context.Outgoing) + mcastAddr := h.Dst.Addr + localIfAddr := h.Src.Addr var ifoptSet tcpip.MulticastInterfaceOption switch optTyp { @@ -1881,35 +1445,35 @@ func TestMulticastInterfaceOption(t *testing.T) { t.Fatal("unknown test variant") } - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(flow.sockProto()) + c.CreateEndpoint(flow.SockProto(), udp.ProtocolNumber) if bindTyp == "bound" { // Bind the socket by connecting to the multicast address. // This may have an influence on how the multicast interface // is set. addr := tcpip.FullAddress{ - Addr: flow.mapAddrIfApplicable(mcastAddr), - Port: stackPort, + Addr: flow.MapAddrIfApplicable(mcastAddr), + Port: context.StackPort, } - if err := c.ep.Connect(addr); err != nil { - c.t.Fatalf("Connect failed: %s", err) + if err := c.EP.Connect(addr); err != nil { + c.T.Fatalf("Connect failed: %s", err) } } - if err := c.ep.SetSockOpt(&ifoptSet); err != nil { - c.t.Fatalf("SetSockOpt(&%#v): %s", ifoptSet, err) + if err := c.EP.SetSockOpt(&ifoptSet); err != nil { + c.T.Fatalf("SetSockOpt(&%#v): %s", ifoptSet, err) } // Verify multicast interface addr and NIC were set correctly. // Note that NIC must be 1 since this is our outgoing interface. var ifoptGot tcpip.MulticastInterfaceOption - if err := c.ep.GetSockOpt(&ifoptGot); err != nil { - c.t.Fatalf("GetSockOpt(&%T): %s", ifoptGot, err) + if err := c.EP.GetSockOpt(&ifoptGot); err != nil { + c.T.Fatalf("GetSockOpt(&%T): %s", ifoptGot, err) } else if ifoptWant := (tcpip.MulticastInterfaceOption{NIC: 1, InterfaceAddr: ifoptSet.InterfaceAddr}); ifoptGot != ifoptWant { - c.t.Errorf("got multicast interface option = %#v, want = %#v", ifoptGot, ifoptWant) + c.T.Errorf("got multicast interface option = %#v, want = %#v", ifoptGot, ifoptWant) } }) } @@ -1923,11 +1487,11 @@ func TestMulticastInterfaceOption(t *testing.T) { // Unreachable message when a udp datagram is received on ports for which there // is no bound udp socket. func TestV4UnknownDestination(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() testCases := []struct { - flow testFlow + flow context.TestFlow icmpRequired bool // largePayload if true, will result in a payload large enough // so that the final generated IPv4 packet is larger than @@ -1937,38 +1501,39 @@ func TestV4UnknownDestination(t *testing.T) { // header. badChecksum bool }{ - {unicastV4, true, false, false}, - {unicastV4, true, true, false}, - {unicastV4, false, false, true}, - {unicastV4, false, true, true}, - {multicastV4, false, false, false}, - {multicastV4, false, true, false}, - {broadcast, false, false, false}, - {broadcast, false, true, false}, + {context.UnicastV4, true, false, false}, + {context.UnicastV4, true, true, false}, + {context.UnicastV4, false, false, true}, + {context.UnicastV4, false, true, true}, + {context.MulticastV4, false, false, false}, + {context.MulticastV4, false, true, false}, + {context.Broadcast, false, false, false}, + {context.Broadcast, false, true, false}, } checksumErrors := uint64(0) for _, tc := range testCases { t.Run(fmt.Sprintf("flow:%s icmpRequired:%t largePayload:%t badChecksum:%t", tc.flow, tc.icmpRequired, tc.largePayload, tc.badChecksum), func(t *testing.T) { - payload := newPayload() + payloadSize := arbitraryPayloadSize if tc.largePayload { - payload = newMinPayload(576) + payloadSize += header.IPv4MinimumProcessableDatagramSize } - c.injectPacket(tc.flow, payload, tc.badChecksum) + payload := newRandomPayload(payloadSize) + c.InjectPacket(tc.flow.NetProto(), buildPacket(payload, tc.flow, context.Incoming, tc.badChecksum)) if tc.badChecksum { checksumErrors++ - if got, want := c.s.Stats().UDP.ChecksumErrors.Value(), checksumErrors; got != want { + if got, want := c.Stack.Stats().UDP.ChecksumErrors.Value(), checksumErrors; got != want { t.Fatalf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want) } } if !tc.icmpRequired { - if p := c.linkEP.Read(); p != nil { + if p := c.LinkEP.Read(); p != nil { t.Fatalf("unexpected packet received: %+v", p) } return } // ICMP required. - p := c.linkEP.Read() + p := c.LinkEP.Read() if p == nil { t.Fatalf("packet wasn't written out") } @@ -2019,11 +1584,11 @@ func TestV4UnknownDestination(t *testing.T) { // Unreachable message when a udp datagram is received on ports for which there // is no bound udp socket. func TestV6UnknownDestination(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() testCases := []struct { - flow testFlow + flow context.TestFlow icmpRequired bool // largePayload if true will result in a payload large enough to // create an IPv6 packet > header.IPv6MinimumMTU bytes. @@ -2032,36 +1597,37 @@ func TestV6UnknownDestination(t *testing.T) { // header. badChecksum bool }{ - {unicastV6, true, false, false}, - {unicastV6, true, true, false}, - {unicastV6, false, false, true}, - {unicastV6, false, true, true}, - {multicastV6, false, false, false}, - {multicastV6, false, true, false}, + {context.UnicastV6, true, false, false}, + {context.UnicastV6, true, true, false}, + {context.UnicastV6, false, false, true}, + {context.UnicastV6, false, true, true}, + {context.MulticastV6, false, false, false}, + {context.MulticastV6, false, true, false}, } checksumErrors := uint64(0) for _, tc := range testCases { t.Run(fmt.Sprintf("flow:%s icmpRequired:%t largePayload:%t badChecksum:%t", tc.flow, tc.icmpRequired, tc.largePayload, tc.badChecksum), func(t *testing.T) { - payload := newPayload() + payloadSize := arbitraryPayloadSize if tc.largePayload { - payload = newMinPayload(1280) + payloadSize += header.IPv6MinimumMTU } - c.injectPacket(tc.flow, payload, tc.badChecksum) + payload := newRandomPayload(payloadSize) + c.InjectPacket(tc.flow.NetProto(), buildPacket(payload, tc.flow, context.Incoming, tc.badChecksum)) if tc.badChecksum { checksumErrors++ - if got, want := c.s.Stats().UDP.ChecksumErrors.Value(), checksumErrors; got != want { + if got, want := c.Stack.Stats().UDP.ChecksumErrors.Value(), checksumErrors; got != want { t.Fatalf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want) } } if !tc.icmpRequired { - if p := c.linkEP.Read(); p != nil { + if p := c.LinkEP.Read(); p != nil { t.Fatalf("unexpected packet received: %+v", p) } return } // ICMP required. - p := c.linkEP.Read() + p := c.LinkEP.Read() if p == nil { t.Fatalf("packet wasn't written out") } @@ -2101,33 +1667,29 @@ func TestV6UnknownDestination(t *testing.T) { // TestIncrementMalformedPacketsReceived verifies if the malformed received // global and endpoint stats are incremented. func TestIncrementMalformedPacketsReceived(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - payload := newPayload() - h := unicastV6.header4Tuple(incoming) - buf := c.buildV6Packet(payload, &h) + payload := newRandomPayload(arbitraryPayloadSize) + h := context.UnicastV6.MakeHeader4Tuple(context.Incoming) + buf := buildV6Packet(payload, h, false) // Invalidate the UDP header length field. u := header.UDP(buf[header.IPv6MinimumSize:]) u.SetLength(u.Length() + 1) - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv6.ProtocolNumber, pkt) + c.InjectPacket(header.IPv6ProtocolNumber, buf) const want = 1 - if got := c.s.Stats().UDP.MalformedPacketsReceived.Value(); got != want { + if got := c.Stack.Stats().UDP.MalformedPacketsReceived.Value(); got != want { t.Errorf("got stats.UDP.MalformedPacketsReceived.Value() = %d, want = %d", got, want) } - if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.MalformedPacketsReceived.Value(); got != want { + if got := c.EP.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.MalformedPacketsReceived.Value(); got != want { t.Errorf("got EP Stats.ReceiveErrors.MalformedPacketsReceived stats = %d, want = %d", got, want) } } @@ -2135,16 +1697,16 @@ func TestIncrementMalformedPacketsReceived(t *testing.T) { // TestShortHeader verifies that when a packet with a too-short UDP header is // received, the malformed received global stat gets incremented. func TestShortHeader(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - h := unicastV6.header4Tuple(incoming) + h := context.UnicastV6.MakeHeader4Tuple(context.Incoming) // Allocate a buffer for an IPv6 and too-short UDP header. const udpSize = header.UDPMinimumSize - 1 @@ -2156,57 +1718,52 @@ func TestShortHeader(t *testing.T) { PayloadLength: uint16(udpSize), TransportProtocol: udp.ProtocolNumber, HopLimit: 65, - SrcAddr: h.srcAddr.Addr, - DstAddr: h.dstAddr.Addr, + SrcAddr: h.Src.Addr, + DstAddr: h.Dst.Addr, }) // Initialize the UDP header. udpHdr := header.UDP(buffer.NewView(header.UDPMinimumSize)) udpHdr.Encode(&header.UDPFields{ - SrcPort: h.srcAddr.Port, - DstPort: h.dstAddr.Port, + SrcPort: h.Src.Port, + DstPort: h.Dst.Port, Length: header.UDPMinimumSize, }) // Calculate the UDP pseudo-header checksum. - xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.srcAddr.Addr, h.dstAddr.Addr, uint16(len(udpHdr))) + xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.Src.Addr, h.Dst.Addr, uint16(len(udpHdr))) udpHdr.SetChecksum(^udpHdr.CalculateChecksum(xsum)) // Copy all but the last byte of the UDP header into the packet. copy(buf[header.IPv6MinimumSize:], udpHdr) // Inject packet. - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv6.ProtocolNumber, pkt) + c.InjectPacket(header.IPv6ProtocolNumber, buf) - if got, want := c.s.Stats().NICs.MalformedL4RcvdPackets.Value(), uint64(1); got != want { - t.Errorf("got c.s.Stats().NIC.MalformedL4RcvdPackets.Value() = %d, want = %d", got, want) + if got, want := c.Stack.Stats().NICs.MalformedL4RcvdPackets.Value(), uint64(1); got != want { + t.Errorf("got c.Stack.Stats().NIC.MalformedL4RcvdPackets.Value() = %d, want = %d", got, want) } } // TestBadChecksumErrors verifies if a checksum error is detected, // global and endpoint stats are incremented. func TestBadChecksumErrors(t *testing.T) { - for _, flow := range []testFlow{unicastV4, unicastV6} { + for _, flow := range []context.TestFlow{context.UnicastV4, context.UnicastV6} { t.Run(flow.String(), func(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(flow.sockProto()) + c.CreateEndpoint(flow.SockProto(), udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - payload := newPayload() - c.injectPacket(flow, payload, true /* badChecksum */) + c.InjectPacket(flow.NetProto(), buildPacket(newRandomPayload(arbitraryPayloadSize), flow, context.Incoming, true)) const want = 1 - if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want { + if got := c.Stack.Stats().UDP.ChecksumErrors.Value(); got != want { t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want) } - if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { + if got := c.EP.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want) } }) @@ -2216,33 +1773,28 @@ func TestBadChecksumErrors(t *testing.T) { // TestPayloadModifiedV4 verifies if a checksum error is detected, // global and endpoint stats are incremented. func TestPayloadModifiedV4(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv4.ProtocolNumber) + c.CreateEndpoint(ipv4.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - payload := newPayload() - h := unicastV4.header4Tuple(incoming) - buf := c.buildV4Packet(payload, &h) + payload := newRandomPayload(arbitraryPayloadSize) + h := context.UnicastV4.MakeHeader4Tuple(context.Incoming) + buf := buildV4Packet(payload, h, false) // Modify the payload so that the checksum value in the UDP header will be // incorrect. buf[len(buf)-1]++ - - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv4.ProtocolNumber, pkt) + c.InjectPacket(header.IPv4ProtocolNumber, buf) const want = 1 - if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want { + if got := c.Stack.Stats().UDP.ChecksumErrors.Value(); got != want { t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want) } - if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { + if got := c.EP.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want) } } @@ -2250,32 +1802,28 @@ func TestPayloadModifiedV4(t *testing.T) { // TestPayloadModifiedV6 verifies if a checksum error is detected, // global and endpoint stats are incremented. func TestPayloadModifiedV6(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - payload := newPayload() - h := unicastV6.header4Tuple(incoming) - buf := c.buildV6Packet(payload, &h) + payload := newRandomPayload(arbitraryPayloadSize) + h := context.UnicastV6.MakeHeader4Tuple(context.Incoming) + buf := buildV6Packet(payload, h, false) // Modify the payload so that the checksum value in the UDP header will be // incorrect. buf[len(buf)-1]++ - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv6.ProtocolNumber, pkt) + c.InjectPacket(header.IPv6ProtocolNumber, buf) const want = 1 - if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want { + if got := c.Stack.Stats().UDP.ChecksumErrors.Value(); got != want { t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want) } - if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { + if got := c.EP.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want) } } @@ -2283,32 +1831,28 @@ func TestPayloadModifiedV6(t *testing.T) { // TestChecksumZeroV4 verifies if the checksum value is zero, global and // endpoint states are *not* incremented (UDP checksum is optional on IPv4). func TestChecksumZeroV4(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv4.ProtocolNumber) + c.CreateEndpoint(ipv4.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - payload := newPayload() - h := unicastV4.header4Tuple(incoming) - buf := c.buildV4Packet(payload, &h) + payload := newRandomPayload(arbitraryPayloadSize) + h := context.UnicastV4.MakeHeader4Tuple(context.Incoming) + buf := buildV4Packet(payload, h, false) // Set the checksum field in the UDP header to zero. u := header.UDP(buf[header.IPv4MinimumSize:]) u.SetChecksum(0) - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv4.ProtocolNumber, pkt) + c.InjectPacket(header.IPv4ProtocolNumber, buf) const want = 0 - if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want { + if got := c.Stack.Stats().UDP.ChecksumErrors.Value(); got != want { t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want) } - if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { + if got := c.EP.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want) } } @@ -2316,32 +1860,28 @@ func TestChecksumZeroV4(t *testing.T) { // TestChecksumZeroV6 verifies if the checksum value is zero, global and // endpoint states are incremented (UDP checksum is *not* optional on IPv6). func TestChecksumZeroV6(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - payload := newPayload() - h := unicastV6.header4Tuple(incoming) - buf := c.buildV6Packet(payload, &h) + payload := newRandomPayload(arbitraryPayloadSize) + h := context.UnicastV6.MakeHeader4Tuple(context.Incoming) + buf := buildV6Packet(payload, h, false) // Set the checksum field in the UDP header to zero. u := header.UDP(buf[header.IPv6MinimumSize:]) u.SetChecksum(0) - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - Data: buf.ToVectorisedView(), - }) - defer pkt.DecRef() - c.linkEP.InjectInbound(ipv6.ProtocolNumber, pkt) + c.InjectPacket(header.IPv6ProtocolNumber, buf) const want = 1 - if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want { + if got := c.Stack.Stats().UDP.ChecksumErrors.Value(); got != want { t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want) } - if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { + if got := c.EP.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want { t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want) } } @@ -2349,31 +1889,31 @@ func TestChecksumZeroV6(t *testing.T) { // TestShutdownRead verifies endpoint read shutdown and error // stats increment on packet receive. func TestShutdownRead(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) // Bind to wildcard. - if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil { - c.t.Fatalf("Bind failed: %s", err) + if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil { + c.T.Fatalf("Bind failed: %s", err) } - if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil { - c.t.Fatalf("Connect failed: %s", err) + if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort}); err != nil { + c.T.Fatalf("Connect failed: %s", err) } - if err := c.ep.Shutdown(tcpip.ShutdownRead); err != nil { + if err := c.EP.Shutdown(tcpip.ShutdownRead); err != nil { t.Fatalf("Shutdown failed: %s", err) } - testFailingRead(c, unicastV6, true /* expectReadError */) + testFailingRead(c, context.UnicastV6, true /* expectReadError */) var want uint64 = 1 - if got := c.s.Stats().UDP.ReceiveBufferErrors.Value(); got != want { + if got := c.Stack.Stats().UDP.ReceiveBufferErrors.Value(); got != want { t.Errorf("got stats.UDP.ReceiveBufferErrors.Value() = %v, want = %v", got, want) } - if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ClosedReceiver.Value(); got != want { + if got := c.EP.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ClosedReceiver.Value(); got != want { t.Errorf("got EP Stats.ReceiveErrors.ClosedReceiver stats = %v, want = %v", got, want) } } @@ -2381,55 +1921,20 @@ func TestShutdownRead(t *testing.T) { // TestShutdownWrite verifies endpoint write shutdown and error // stats increment on packet write. func TestShutdownWrite(t *testing.T) { - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() + c := context.New(t, []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4}) + defer c.Cleanup() - c.createEndpoint(ipv6.ProtocolNumber) + c.CreateEndpoint(ipv6.ProtocolNumber, udp.ProtocolNumber) - if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil { - c.t.Fatalf("Connect failed: %s", err) + if err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort}); err != nil { + c.T.Fatalf("Connect failed: %s", err) } - if err := c.ep.Shutdown(tcpip.ShutdownWrite); err != nil { + if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil { t.Fatalf("Shutdown failed: %s", err) } - testFailingWrite(c, unicastV6, &tcpip.ErrClosedForSend{}) -} - -func (c *testContext) checkEndpointWriteStats(incr uint64, want tcpip.TransportEndpointStats, err tcpip.Error) { - got := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone() - switch err.(type) { - case nil: - want.PacketsSent.IncrementBy(incr) - case *tcpip.ErrMessageTooLong, *tcpip.ErrInvalidOptionValue: - want.WriteErrors.InvalidArgs.IncrementBy(incr) - case *tcpip.ErrClosedForSend: - want.WriteErrors.WriteClosed.IncrementBy(incr) - case *tcpip.ErrInvalidEndpointState: - want.WriteErrors.InvalidEndpointState.IncrementBy(incr) - case *tcpip.ErrNoRoute, *tcpip.ErrBroadcastDisabled, *tcpip.ErrNetworkUnreachable: - want.SendErrors.NoRoute.IncrementBy(incr) - default: - want.SendErrors.SendToNetworkFailed.IncrementBy(incr) - } - if got != want { - c.t.Errorf("Endpoint stats not matching for error %s got %+v want %+v", err, got, want) - } -} - -func (c *testContext) checkEndpointReadStats(incr uint64, want tcpip.TransportEndpointStats, err tcpip.Error) { - got := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone() - switch err.(type) { - case nil, *tcpip.ErrWouldBlock: - case *tcpip.ErrClosedForReceive: - want.ReadErrors.ReadClosed.IncrementBy(incr) - default: - c.t.Errorf("Endpoint error missing stats update err %v", err) - } - if got != want { - c.t.Errorf("Endpoint stats not matching for error %s got %+v want %+v", err, got, want) - } + testFailingWrite(c, context.UnicastV6, &tcpip.ErrClosedForSend{}) } func TestOutgoingSubnetBroadcast(t *testing.T) { @@ -2562,7 +2067,7 @@ func TestOutgoingSubnetBroadcast(t *testing.T) { TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol}, Clock: &faketime.NullClock{}, }) - e := channel.New(0, defaultMTU, "") + e := channel.New(0, context.DefaultMTU, "") if err := s.CreateNIC(nicID1, e); err != nil { t.Fatalf("CreateNIC(%d, _): %s", nicID1, err) }