summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/network
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/tcpip/network')
-rw-r--r--pkg/tcpip/network/BUILD26
-rw-r--r--pkg/tcpip/network/arp/BUILD35
-rw-r--r--pkg/tcpip/network/arp/arp_state_autogen.go3
-rw-r--r--pkg/tcpip/network/arp/arp_test.go578
-rw-r--r--pkg/tcpip/network/fragmentation/BUILD52
-rw-r--r--pkg/tcpip/network/fragmentation/frag_heap_test.go126
-rw-r--r--pkg/tcpip/network/fragmentation/fragmentation_state_autogen.go64
-rw-r--r--pkg/tcpip/network/fragmentation/fragmentation_test.go585
-rw-r--r--pkg/tcpip/network/fragmentation/reassembler_list.go193
-rw-r--r--pkg/tcpip/network/fragmentation/reassembler_test.go130
-rw-r--r--pkg/tcpip/network/hash/BUILD13
-rw-r--r--pkg/tcpip/network/hash/hash_state_autogen.go3
-rw-r--r--pkg/tcpip/network/ip_test.go1493
-rw-r--r--pkg/tcpip/network/ipv4/BUILD46
-rw-r--r--pkg/tcpip/network/ipv4/ipv4_state_autogen.go3
-rw-r--r--pkg/tcpip/network/ipv4/ipv4_test.go2531
-rw-r--r--pkg/tcpip/network/ipv6/BUILD51
-rw-r--r--pkg/tcpip/network/ipv6/icmp_test.go1813
-rw-r--r--pkg/tcpip/network/ipv6/ipv6_state_autogen.go3
-rw-r--r--pkg/tcpip/network/ipv6/ipv6_test.go2823
-rw-r--r--pkg/tcpip/network/ipv6/ndp_test.go1393
-rw-r--r--pkg/tcpip/network/testutil/BUILD21
-rw-r--r--pkg/tcpip/network/testutil/testutil.go144
23 files changed, 269 insertions, 11860 deletions
diff --git a/pkg/tcpip/network/BUILD b/pkg/tcpip/network/BUILD
deleted file mode 100644
index c118a2929..000000000
--- a/pkg/tcpip/network/BUILD
+++ /dev/null
@@ -1,26 +0,0 @@
-load("//tools:defs.bzl", "go_test")
-
-package(licenses = ["notice"])
-
-go_test(
- name = "ip_test",
- size = "small",
- srcs = [
- "ip_test.go",
- ],
- deps = [
- "//pkg/sync",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/checker",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/loopback",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/ipv6",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/icmp",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- ],
-)
diff --git a/pkg/tcpip/network/arp/BUILD b/pkg/tcpip/network/arp/BUILD
deleted file mode 100644
index 8a6bcfc2c..000000000
--- a/pkg/tcpip/network/arp/BUILD
+++ /dev/null
@@ -1,35 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "arp",
- srcs = ["arp.go"],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/header/parse",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "arp_test",
- size = "small",
- srcs = ["arp_test.go"],
- deps = [
- ":arp",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/link/channel",
- "//pkg/tcpip/link/sniffer",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/icmp",
- "@com_github_google_go_cmp//cmp:go_default_library",
- "@com_github_google_go_cmp//cmp/cmpopts:go_default_library",
- ],
-)
diff --git a/pkg/tcpip/network/arp/arp_state_autogen.go b/pkg/tcpip/network/arp/arp_state_autogen.go
new file mode 100644
index 000000000..5cd8535e3
--- /dev/null
+++ b/pkg/tcpip/network/arp/arp_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package arp
diff --git a/pkg/tcpip/network/arp/arp_test.go b/pkg/tcpip/network/arp/arp_test.go
deleted file mode 100644
index 087ee9c66..000000000
--- a/pkg/tcpip/network/arp/arp_test.go
+++ /dev/null
@@ -1,578 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package arp_test
-
-import (
- "context"
- "fmt"
- "strconv"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "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/arp"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
-)
-
-const (
- nicID = 1
-
- stackAddr = tcpip.Address("\x0a\x00\x00\x01")
- stackLinkAddr = tcpip.LinkAddress("\x0a\x0a\x0b\x0b\x0c\x0c")
-
- remoteAddr = tcpip.Address("\x0a\x00\x00\x02")
- remoteLinkAddr = tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06")
-
- unknownAddr = tcpip.Address("\x0a\x00\x00\x03")
-
- defaultChannelSize = 1
- defaultMTU = 65536
-
- // eventChanSize defines the size of event channels used by the neighbor
- // cache's event dispatcher. The size chosen here needs to be sufficient to
- // queue all the events received during tests before consumption.
- // If eventChanSize is too small, the tests may deadlock.
- eventChanSize = 32
-)
-
-type eventType uint8
-
-const (
- entryAdded eventType = iota
- entryChanged
- entryRemoved
-)
-
-func (t eventType) String() string {
- switch t {
- case entryAdded:
- return "add"
- case entryChanged:
- return "change"
- case entryRemoved:
- return "remove"
- default:
- return fmt.Sprintf("unknown (%d)", t)
- }
-}
-
-type eventInfo struct {
- eventType eventType
- nicID tcpip.NICID
- entry stack.NeighborEntry
-}
-
-func (e eventInfo) String() string {
- return fmt.Sprintf("%s event for NIC #%d, %#v", e.eventType, e.nicID, e.entry)
-}
-
-// arpDispatcher implements NUDDispatcher to validate the dispatching of
-// events upon certain NUD state machine events.
-type arpDispatcher struct {
- // C is where events are queued
- C chan eventInfo
-}
-
-var _ stack.NUDDispatcher = (*arpDispatcher)(nil)
-
-func (d *arpDispatcher) OnNeighborAdded(nicID tcpip.NICID, entry stack.NeighborEntry) {
- e := eventInfo{
- eventType: entryAdded,
- nicID: nicID,
- entry: entry,
- }
- d.C <- e
-}
-
-func (d *arpDispatcher) OnNeighborChanged(nicID tcpip.NICID, entry stack.NeighborEntry) {
- e := eventInfo{
- eventType: entryChanged,
- nicID: nicID,
- entry: entry,
- }
- d.C <- e
-}
-
-func (d *arpDispatcher) OnNeighborRemoved(nicID tcpip.NICID, entry stack.NeighborEntry) {
- e := eventInfo{
- eventType: entryRemoved,
- nicID: nicID,
- entry: entry,
- }
- d.C <- e
-}
-
-func (d *arpDispatcher) waitForEvent(ctx context.Context, want eventInfo) error {
- select {
- case got := <-d.C:
- if diff := cmp.Diff(got, want, cmp.AllowUnexported(got), cmpopts.IgnoreFields(stack.NeighborEntry{}, "UpdatedAtNanos")); diff != "" {
- return fmt.Errorf("got invalid event (-got +want):\n%s", diff)
- }
- case <-ctx.Done():
- return fmt.Errorf("%s for %s", ctx.Err(), want)
- }
- return nil
-}
-
-func (d *arpDispatcher) waitForEventWithTimeout(want eventInfo, timeout time.Duration) error {
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
- return d.waitForEvent(ctx, want)
-}
-
-func (d *arpDispatcher) nextEvent() (eventInfo, bool) {
- select {
- case event := <-d.C:
- return event, true
- default:
- return eventInfo{}, false
- }
-}
-
-type testContext struct {
- s *stack.Stack
- linkEP *channel.Endpoint
- nudDisp *arpDispatcher
-}
-
-func newTestContext(t *testing.T, useNeighborCache bool) *testContext {
- c := stack.DefaultNUDConfigurations()
- // Transition from Reachable to Stale almost immediately to test if receiving
- // probes refreshes positive reachability.
- c.BaseReachableTime = time.Microsecond
-
- d := arpDispatcher{
- // Create an event channel large enough so the neighbor cache doesn't block
- // while dispatching events. Blocking could interfere with the timing of
- // NUD transitions.
- C: make(chan eventInfo, eventChanSize),
- }
-
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, arp.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4},
- NUDConfigs: c,
- NUDDisp: &d,
- UseNeighborCache: useNeighborCache,
- })
-
- ep := channel.New(defaultChannelSize, defaultMTU, stackLinkAddr)
- ep.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- wep := stack.LinkEndpoint(ep)
-
- if testing.Verbose() {
- wep = sniffer.New(ep)
- }
- if err := s.CreateNIC(nicID, wep); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- if err := s.AddAddress(nicID, ipv4.ProtocolNumber, stackAddr); err != nil {
- t.Fatalf("AddAddress for ipv4 failed: %v", err)
- }
- if !useNeighborCache {
- // The remote address needs to be assigned to the NIC so we can receive and
- // verify outgoing ARP packets. The neighbor cache isn't concerned with
- // this; the tests that use linkAddrCache expect the ARP responses to be
- // received by the same NIC.
- if err := s.AddAddress(nicID, ipv4.ProtocolNumber, remoteAddr); err != nil {
- t.Fatalf("AddAddress for ipv4 failed: %v", err)
- }
- }
- if err := s.AddAddress(nicID, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
- t.Fatalf("AddAddress for arp failed: %v", err)
- }
-
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv4EmptySubnet,
- NIC: nicID,
- }})
-
- return &testContext{
- s: s,
- linkEP: ep,
- nudDisp: &d,
- }
-}
-
-func (c *testContext) cleanup() {
- c.linkEP.Close()
-}
-
-func TestDirectRequest(t *testing.T) {
- c := newTestContext(t, false /* useNeighborCache */)
- defer c.cleanup()
-
- const senderMAC = "\x01\x02\x03\x04\x05\x06"
- const senderIPv4 = "\x0a\x00\x00\x02"
-
- v := make(buffer.View, header.ARPSize)
- h := header.ARP(v)
- h.SetIPv4OverEthernet()
- h.SetOp(header.ARPRequest)
- copy(h.HardwareAddressSender(), senderMAC)
- copy(h.ProtocolAddressSender(), senderIPv4)
-
- inject := func(addr tcpip.Address) {
- copy(h.ProtocolAddressTarget(), addr)
- c.linkEP.InjectInbound(arp.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: v.ToVectorisedView(),
- }))
- }
-
- for i, address := range []tcpip.Address{stackAddr, remoteAddr} {
- t.Run(strconv.Itoa(i), func(t *testing.T) {
- inject(address)
- pi, _ := c.linkEP.ReadContext(context.Background())
- if pi.Proto != arp.ProtocolNumber {
- t.Fatalf("expected ARP response, got network protocol number %d", pi.Proto)
- }
- rep := header.ARP(pi.Pkt.NetworkHeader().View())
- if !rep.IsValid() {
- t.Fatalf("invalid ARP response: len = %d; response = %x", len(rep), rep)
- }
- if got, want := tcpip.LinkAddress(rep.HardwareAddressSender()), stackLinkAddr; got != want {
- t.Errorf("got HardwareAddressSender = %s, want = %s", got, want)
- }
- if got, want := tcpip.Address(rep.ProtocolAddressSender()), tcpip.Address(h.ProtocolAddressTarget()); got != want {
- t.Errorf("got ProtocolAddressSender = %s, want = %s", got, want)
- }
- if got, want := tcpip.LinkAddress(rep.HardwareAddressTarget()), tcpip.LinkAddress(h.HardwareAddressSender()); got != want {
- t.Errorf("got HardwareAddressTarget = %s, want = %s", got, want)
- }
- if got, want := tcpip.Address(rep.ProtocolAddressTarget()), tcpip.Address(h.ProtocolAddressSender()); got != want {
- t.Errorf("got ProtocolAddressTarget = %s, want = %s", got, want)
- }
- })
- }
-
- inject(unknownAddr)
- // Sleep tests are gross, but this will only potentially flake
- // if there's a bug. If there is no bug this will reliably
- // succeed.
- ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
- defer cancel()
- if pkt, ok := c.linkEP.ReadContext(ctx); ok {
- t.Errorf("stackAddrBad: unexpected packet sent, Proto=%v", pkt.Proto)
- }
-}
-
-func TestDirectRequestWithNeighborCache(t *testing.T) {
- c := newTestContext(t, true /* useNeighborCache */)
- defer c.cleanup()
-
- tests := []struct {
- name string
- senderAddr tcpip.Address
- senderLinkAddr tcpip.LinkAddress
- targetAddr tcpip.Address
- isValid bool
- }{
- {
- name: "Loopback",
- senderAddr: stackAddr,
- senderLinkAddr: stackLinkAddr,
- targetAddr: stackAddr,
- isValid: true,
- },
- {
- name: "Remote",
- senderAddr: remoteAddr,
- senderLinkAddr: remoteLinkAddr,
- targetAddr: stackAddr,
- isValid: true,
- },
- {
- name: "RemoteInvalidTarget",
- senderAddr: remoteAddr,
- senderLinkAddr: remoteLinkAddr,
- targetAddr: unknownAddr,
- isValid: false,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- // Inject an incoming ARP request.
- v := make(buffer.View, header.ARPSize)
- h := header.ARP(v)
- h.SetIPv4OverEthernet()
- h.SetOp(header.ARPRequest)
- copy(h.HardwareAddressSender(), test.senderLinkAddr)
- copy(h.ProtocolAddressSender(), test.senderAddr)
- copy(h.ProtocolAddressTarget(), test.targetAddr)
- c.linkEP.InjectInbound(arp.ProtocolNumber, &stack.PacketBuffer{
- Data: v.ToVectorisedView(),
- })
-
- if !test.isValid {
- // No packets should be sent after receiving an invalid ARP request.
- // There is no need to perform a blocking read here, since packets are
- // sent in the same function that handles ARP requests.
- if pkt, ok := c.linkEP.Read(); ok {
- t.Errorf("unexpected packet sent with network protocol number %d", pkt.Proto)
- }
- return
- }
-
- // Verify an ARP response was sent.
- pi, ok := c.linkEP.Read()
- if !ok {
- t.Fatal("expected ARP response to be sent, got none")
- }
-
- if pi.Proto != arp.ProtocolNumber {
- t.Fatalf("expected ARP response, got network protocol number %d", pi.Proto)
- }
- rep := header.ARP(pi.Pkt.NetworkHeader().View())
- if !rep.IsValid() {
- t.Fatalf("invalid ARP response: len = %d; response = %x", len(rep), rep)
- }
- if got, want := tcpip.LinkAddress(rep.HardwareAddressSender()), stackLinkAddr; got != want {
- t.Errorf("got HardwareAddressSender() = %s, want = %s", got, want)
- }
- if got, want := tcpip.Address(rep.ProtocolAddressSender()), tcpip.Address(h.ProtocolAddressTarget()); got != want {
- t.Errorf("got ProtocolAddressSender() = %s, want = %s", got, want)
- }
- if got, want := tcpip.LinkAddress(rep.HardwareAddressTarget()), tcpip.LinkAddress(h.HardwareAddressSender()); got != want {
- t.Errorf("got HardwareAddressTarget() = %s, want = %s", got, want)
- }
- if got, want := tcpip.Address(rep.ProtocolAddressTarget()), tcpip.Address(h.ProtocolAddressSender()); got != want {
- t.Errorf("got ProtocolAddressTarget() = %s, want = %s", got, want)
- }
-
- // Verify the sender was saved in the neighbor cache.
- wantEvent := eventInfo{
- eventType: entryAdded,
- nicID: nicID,
- entry: stack.NeighborEntry{
- Addr: test.senderAddr,
- LinkAddr: tcpip.LinkAddress(test.senderLinkAddr),
- State: stack.Stale,
- },
- }
- if err := c.nudDisp.waitForEventWithTimeout(wantEvent, time.Second); err != nil {
- t.Fatal(err)
- }
-
- neighbors, err := c.s.Neighbors(nicID)
- if err != nil {
- t.Fatalf("c.s.Neighbors(%d): %s", nicID, err)
- }
-
- neighborByAddr := make(map[tcpip.Address]stack.NeighborEntry)
- for _, n := range neighbors {
- if existing, ok := neighborByAddr[n.Addr]; ok {
- if diff := cmp.Diff(existing, n); diff != "" {
- t.Fatalf("duplicate neighbor entry found (-existing +got):\n%s", diff)
- }
- t.Fatalf("exact neighbor entry duplicate found for addr=%s", n.Addr)
- }
- neighborByAddr[n.Addr] = n
- }
-
- neigh, ok := neighborByAddr[test.senderAddr]
- if !ok {
- t.Fatalf("expected neighbor entry with Addr = %s", test.senderAddr)
- }
- if got, want := neigh.LinkAddr, test.senderLinkAddr; got != want {
- t.Errorf("got neighbor LinkAddr = %s, want = %s", got, want)
- }
- if got, want := neigh.State, stack.Stale; got != want {
- t.Errorf("got neighbor State = %s, want = %s", got, want)
- }
-
- // No more events should be dispatched
- for {
- event, ok := c.nudDisp.nextEvent()
- if !ok {
- break
- }
- t.Errorf("unexpected %s", event)
- }
- })
- }
-}
-
-var _ stack.NetworkInterface = (*testInterface)(nil)
-
-type testInterface struct {
- stack.LinkEndpoint
-
- nicID tcpip.NICID
-}
-
-func (t *testInterface) ID() tcpip.NICID {
- return t.nicID
-}
-
-func (*testInterface) IsLoopback() bool {
- return false
-}
-
-func (*testInterface) Name() string {
- return ""
-}
-
-func (*testInterface) Enabled() bool {
- return true
-}
-
-func (t *testInterface) WritePacketToRemote(remoteLinkAddr tcpip.LinkAddress, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
- r := stack.Route{
- NetProto: protocol,
- RemoteLinkAddress: remoteLinkAddr,
- }
- return t.LinkEndpoint.WritePacket(&r, gso, protocol, pkt)
-}
-
-func TestLinkAddressRequest(t *testing.T) {
- const nicID = 1
-
- testAddr := tcpip.Address([]byte{1, 2, 3, 4})
-
- tests := []struct {
- name string
- nicAddr tcpip.Address
- localAddr tcpip.Address
- remoteLinkAddr tcpip.LinkAddress
-
- expectedErr *tcpip.Error
- expectedLocalAddr tcpip.Address
- expectedRemoteLinkAddr tcpip.LinkAddress
- }{
- {
- name: "Unicast",
- nicAddr: stackAddr,
- localAddr: stackAddr,
- remoteLinkAddr: remoteLinkAddr,
- expectedLocalAddr: stackAddr,
- expectedRemoteLinkAddr: remoteLinkAddr,
- },
- {
- name: "Multicast",
- nicAddr: stackAddr,
- localAddr: stackAddr,
- remoteLinkAddr: "",
- expectedLocalAddr: stackAddr,
- expectedRemoteLinkAddr: header.EthernetBroadcastAddress,
- },
- {
- name: "Unicast with unspecified source",
- nicAddr: stackAddr,
- remoteLinkAddr: remoteLinkAddr,
- expectedLocalAddr: stackAddr,
- expectedRemoteLinkAddr: remoteLinkAddr,
- },
- {
- name: "Multicast with unspecified source",
- nicAddr: stackAddr,
- remoteLinkAddr: "",
- expectedLocalAddr: stackAddr,
- expectedRemoteLinkAddr: header.EthernetBroadcastAddress,
- },
- {
- name: "Unicast with unassigned address",
- localAddr: testAddr,
- remoteLinkAddr: remoteLinkAddr,
- expectedErr: tcpip.ErrBadLocalAddress,
- },
- {
- name: "Multicast with unassigned address",
- localAddr: testAddr,
- remoteLinkAddr: "",
- expectedErr: tcpip.ErrBadLocalAddress,
- },
- {
- name: "Unicast with no local address available",
- remoteLinkAddr: remoteLinkAddr,
- expectedErr: tcpip.ErrNetworkUnreachable,
- },
- {
- name: "Multicast with no local address available",
- remoteLinkAddr: "",
- expectedErr: tcpip.ErrNetworkUnreachable,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol},
- })
- p := s.NetworkProtocolInstance(arp.ProtocolNumber)
- linkRes, ok := p.(stack.LinkAddressResolver)
- if !ok {
- t.Fatal("expected ARP protocol to implement stack.LinkAddressResolver")
- }
-
- linkEP := channel.New(defaultChannelSize, defaultMTU, stackLinkAddr)
- if err := s.CreateNIC(nicID, linkEP); err != nil {
- t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
- }
-
- if len(test.nicAddr) != 0 {
- if err := s.AddAddress(nicID, ipv4.ProtocolNumber, test.nicAddr); err != nil {
- t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, test.nicAddr, err)
- }
- }
-
- // We pass a test network interface to LinkAddressRequest with the same
- // NIC ID and link endpoint used by the NIC we created earlier so that we
- // can mock a link address request and observe the packets sent to the
- // link endpoint even though the stack uses the real NIC to validate the
- // local address.
- if err := linkRes.LinkAddressRequest(remoteAddr, test.localAddr, test.remoteLinkAddr, &testInterface{LinkEndpoint: linkEP, nicID: nicID}); err != test.expectedErr {
- t.Fatalf("got p.LinkAddressRequest(%s, %s, %s, _) = %s, want = %s", remoteAddr, test.localAddr, test.remoteLinkAddr, err, test.expectedErr)
- }
-
- if test.expectedErr != nil {
- return
- }
-
- pkt, ok := linkEP.Read()
- if !ok {
- t.Fatal("expected to send a link address request")
- }
-
- if pkt.Route.RemoteLinkAddress != test.expectedRemoteLinkAddr {
- t.Errorf("got pkt.Route.RemoteLinkAddress = %s, want = %s", pkt.Route.RemoteLinkAddress, test.expectedRemoteLinkAddr)
- }
-
- rep := header.ARP(stack.PayloadSince(pkt.Pkt.NetworkHeader()))
- if got := tcpip.LinkAddress(rep.HardwareAddressSender()); got != stackLinkAddr {
- t.Errorf("got HardwareAddressSender = %s, want = %s", got, stackLinkAddr)
- }
- if got := tcpip.Address(rep.ProtocolAddressSender()); got != test.expectedLocalAddr {
- t.Errorf("got ProtocolAddressSender = %s, want = %s", got, test.expectedLocalAddr)
- }
- if got, want := tcpip.LinkAddress(rep.HardwareAddressTarget()), tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"); got != want {
- t.Errorf("got HardwareAddressTarget = %s, want = %s", got, want)
- }
- if got := tcpip.Address(rep.ProtocolAddressTarget()); got != remoteAddr {
- t.Errorf("got ProtocolAddressTarget = %s, want = %s", got, remoteAddr)
- }
- })
- }
-}
diff --git a/pkg/tcpip/network/fragmentation/BUILD b/pkg/tcpip/network/fragmentation/BUILD
deleted file mode 100644
index 47fb63290..000000000
--- a/pkg/tcpip/network/fragmentation/BUILD
+++ /dev/null
@@ -1,52 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-load("//tools/go_generics:defs.bzl", "go_template_instance")
-
-package(licenses = ["notice"])
-
-go_template_instance(
- name = "reassembler_list",
- out = "reassembler_list.go",
- package = "fragmentation",
- prefix = "reassembler",
- template = "//pkg/ilist:generic_list",
- types = {
- "Element": "*reassembler",
- "Linker": "*reassembler",
- },
-)
-
-go_library(
- name = "fragmentation",
- srcs = [
- "frag_heap.go",
- "fragmentation.go",
- "reassembler.go",
- "reassembler_list.go",
- ],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/log",
- "//pkg/sync",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "fragmentation_test",
- size = "small",
- srcs = [
- "frag_heap_test.go",
- "fragmentation_test.go",
- "reassembler_test.go",
- ],
- library = ":fragmentation",
- deps = [
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/faketime",
- "//pkg/tcpip/network/testutil",
- "@com_github_google_go_cmp//cmp:go_default_library",
- ],
-)
diff --git a/pkg/tcpip/network/fragmentation/frag_heap_test.go b/pkg/tcpip/network/fragmentation/frag_heap_test.go
deleted file mode 100644
index 9ececcb9f..000000000
--- a/pkg/tcpip/network/fragmentation/frag_heap_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fragmentation
-
-import (
- "container/heap"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
-)
-
-var reassambleTestCases = []struct {
- comment string
- in []fragment
- want buffer.VectorisedView
-}{
- {
- comment: "Non-overlapping in-order",
- in: []fragment{
- {offset: 0, vv: vv(1, "0")},
- {offset: 1, vv: vv(1, "1")},
- },
- want: vv(2, "0", "1"),
- },
- {
- comment: "Non-overlapping out-of-order",
- in: []fragment{
- {offset: 1, vv: vv(1, "1")},
- {offset: 0, vv: vv(1, "0")},
- },
- want: vv(2, "0", "1"),
- },
- {
- comment: "Duplicated packets",
- in: []fragment{
- {offset: 0, vv: vv(1, "0")},
- {offset: 0, vv: vv(1, "0")},
- },
- want: vv(1, "0"),
- },
- {
- comment: "Overlapping in-order",
- in: []fragment{
- {offset: 0, vv: vv(2, "01")},
- {offset: 1, vv: vv(2, "12")},
- },
- want: vv(3, "01", "2"),
- },
- {
- comment: "Overlapping out-of-order",
- in: []fragment{
- {offset: 1, vv: vv(2, "12")},
- {offset: 0, vv: vv(2, "01")},
- },
- want: vv(3, "01", "2"),
- },
- {
- comment: "Overlapping subset in-order",
- in: []fragment{
- {offset: 0, vv: vv(3, "012")},
- {offset: 1, vv: vv(1, "1")},
- },
- want: vv(3, "012"),
- },
- {
- comment: "Overlapping subset out-of-order",
- in: []fragment{
- {offset: 1, vv: vv(1, "1")},
- {offset: 0, vv: vv(3, "012")},
- },
- want: vv(3, "012"),
- },
-}
-
-func TestReassamble(t *testing.T) {
- for _, c := range reassambleTestCases {
- t.Run(c.comment, func(t *testing.T) {
- h := make(fragHeap, 0, 8)
- heap.Init(&h)
- for _, f := range c.in {
- heap.Push(&h, f)
- }
- got, err := h.reassemble()
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(got, c.want) {
- t.Errorf("got reassemble(%+v) = %v, want = %v", c.in, got, c.want)
- }
- })
- }
-}
-
-func TestReassambleFailsForNonZeroOffset(t *testing.T) {
- h := make(fragHeap, 0, 8)
- heap.Init(&h)
- heap.Push(&h, fragment{offset: 1, vv: vv(1, "0")})
- _, err := h.reassemble()
- if err == nil {
- t.Errorf("reassemble() did not fail when the first packet had offset != 0")
- }
-}
-
-func TestReassambleFailsForHoles(t *testing.T) {
- h := make(fragHeap, 0, 8)
- heap.Init(&h)
- heap.Push(&h, fragment{offset: 0, vv: vv(1, "0")})
- heap.Push(&h, fragment{offset: 2, vv: vv(1, "1")})
- _, err := h.reassemble()
- if err == nil {
- t.Errorf("reassemble() did not fail when there was a hole in the packet")
- }
-}
diff --git a/pkg/tcpip/network/fragmentation/fragmentation_state_autogen.go b/pkg/tcpip/network/fragmentation/fragmentation_state_autogen.go
new file mode 100644
index 000000000..193268aef
--- /dev/null
+++ b/pkg/tcpip/network/fragmentation/fragmentation_state_autogen.go
@@ -0,0 +1,64 @@
+// automatically generated by stateify.
+
+package fragmentation
+
+import (
+ "gvisor.dev/gvisor/pkg/state"
+)
+
+func (l *reassemblerList) StateTypeName() string {
+ return "pkg/tcpip/network/fragmentation.reassemblerList"
+}
+
+func (l *reassemblerList) StateFields() []string {
+ return []string{
+ "head",
+ "tail",
+ }
+}
+
+func (l *reassemblerList) beforeSave() {}
+
+func (l *reassemblerList) StateSave(stateSinkObject state.Sink) {
+ l.beforeSave()
+ stateSinkObject.Save(0, &l.head)
+ stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *reassemblerList) afterLoad() {}
+
+func (l *reassemblerList) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &l.head)
+ stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *reassemblerEntry) StateTypeName() string {
+ return "pkg/tcpip/network/fragmentation.reassemblerEntry"
+}
+
+func (e *reassemblerEntry) StateFields() []string {
+ return []string{
+ "next",
+ "prev",
+ }
+}
+
+func (e *reassemblerEntry) beforeSave() {}
+
+func (e *reassemblerEntry) StateSave(stateSinkObject state.Sink) {
+ e.beforeSave()
+ stateSinkObject.Save(0, &e.next)
+ stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *reassemblerEntry) afterLoad() {}
+
+func (e *reassemblerEntry) StateLoad(stateSourceObject state.Source) {
+ stateSourceObject.Load(0, &e.next)
+ stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+ state.Register((*reassemblerList)(nil))
+ state.Register((*reassemblerEntry)(nil))
+}
diff --git a/pkg/tcpip/network/fragmentation/fragmentation_test.go b/pkg/tcpip/network/fragmentation/fragmentation_test.go
deleted file mode 100644
index 5dcd10730..000000000
--- a/pkg/tcpip/network/fragmentation/fragmentation_test.go
+++ /dev/null
@@ -1,585 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fragmentation
-
-import (
- "errors"
- "reflect"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/faketime"
- "gvisor.dev/gvisor/pkg/tcpip/network/testutil"
-)
-
-// reassembleTimeout is dummy timeout used for testing, where the clock never
-// advances.
-const reassembleTimeout = 1
-
-// vv is a helper to build VectorisedView from different strings.
-func vv(size int, pieces ...string) buffer.VectorisedView {
- views := make([]buffer.View, len(pieces))
- for i, p := range pieces {
- views[i] = []byte(p)
- }
-
- return buffer.NewVectorisedView(size, views)
-}
-
-type processInput struct {
- id FragmentID
- first uint16
- last uint16
- more bool
- proto uint8
- vv buffer.VectorisedView
-}
-
-type processOutput struct {
- vv buffer.VectorisedView
- proto uint8
- done bool
-}
-
-var processTestCases = []struct {
- comment string
- in []processInput
- out []processOutput
-}{
- {
- comment: "One ID",
- in: []processInput{
- {id: FragmentID{ID: 0}, first: 0, last: 1, more: true, vv: vv(2, "01")},
- {id: FragmentID{ID: 0}, first: 2, last: 3, more: false, vv: vv(2, "23")},
- },
- out: []processOutput{
- {vv: buffer.VectorisedView{}, done: false},
- {vv: vv(4, "01", "23"), done: true},
- },
- },
- {
- comment: "Next Header protocol mismatch",
- in: []processInput{
- {id: FragmentID{ID: 0}, first: 0, last: 1, more: true, proto: 6, vv: vv(2, "01")},
- {id: FragmentID{ID: 0}, first: 2, last: 3, more: false, proto: 17, vv: vv(2, "23")},
- },
- out: []processOutput{
- {vv: buffer.VectorisedView{}, done: false},
- {vv: vv(4, "01", "23"), proto: 6, done: true},
- },
- },
- {
- comment: "Two IDs",
- in: []processInput{
- {id: FragmentID{ID: 0}, first: 0, last: 1, more: true, vv: vv(2, "01")},
- {id: FragmentID{ID: 1}, first: 0, last: 1, more: true, vv: vv(2, "ab")},
- {id: FragmentID{ID: 1}, first: 2, last: 3, more: false, vv: vv(2, "cd")},
- {id: FragmentID{ID: 0}, first: 2, last: 3, more: false, vv: vv(2, "23")},
- },
- out: []processOutput{
- {vv: buffer.VectorisedView{}, done: false},
- {vv: buffer.VectorisedView{}, done: false},
- {vv: vv(4, "ab", "cd"), done: true},
- {vv: vv(4, "01", "23"), done: true},
- },
- },
-}
-
-func TestFragmentationProcess(t *testing.T) {
- for _, c := range processTestCases {
- t.Run(c.comment, func(t *testing.T) {
- f := NewFragmentation(minBlockSize, 1024, 512, reassembleTimeout, &faketime.NullClock{})
- firstFragmentProto := c.in[0].proto
- for i, in := range c.in {
- vv, proto, done, err := f.Process(in.id, in.first, in.last, in.more, in.proto, in.vv, nil)
- if err != nil {
- t.Fatalf("f.Process(%+v, %d, %d, %t, %d, %X) failed: %s",
- in.id, in.first, in.last, in.more, in.proto, in.vv.ToView(), err)
- }
- if !reflect.DeepEqual(vv, c.out[i].vv) {
- t.Errorf("got Process(%+v, %d, %d, %t, %d, %X) = (%X, _, _, _), want = (%X, _, _, _)",
- in.id, in.first, in.last, in.more, in.proto, in.vv.ToView(), vv.ToView(), c.out[i].vv.ToView())
- }
- if done != c.out[i].done {
- t.Errorf("got Process(%+v, %d, %d, %t, %d, _) = (_, _, %t, _), want = (_, _, %t, _)",
- in.id, in.first, in.last, in.more, in.proto, done, c.out[i].done)
- }
- if c.out[i].done {
- if firstFragmentProto != proto {
- t.Errorf("got Process(%+v, %d, %d, %t, %d, _) = (_, %d, _, _), want = (_, %d, _, _)",
- in.id, in.first, in.last, in.more, in.proto, proto, firstFragmentProto)
- }
- if _, ok := f.reassemblers[in.id]; ok {
- t.Errorf("Process(%d) did not remove buffer from reassemblers", i)
- }
- for n := f.rList.Front(); n != nil; n = n.Next() {
- if n.id == in.id {
- t.Errorf("Process(%d) did not remove buffer from rList", i)
- }
- }
- }
- }
- })
- }
-}
-
-func TestReassemblingTimeout(t *testing.T) {
- const (
- reassemblyTimeout = time.Millisecond
- protocol = 0xff
- )
-
- type fragment struct {
- first uint16
- last uint16
- more bool
- data string
- }
-
- type event struct {
- // name is a nickname of this event.
- name string
-
- // clockAdvance is a duration to advance the clock. The clock advances
- // before a fragment specified in the fragment field is processed.
- clockAdvance time.Duration
-
- // fragment is a fragment to process. This can be nil if there is no
- // fragment to process.
- fragment *fragment
-
- // expectDone is true if the fragmentation instance should report the
- // reassembly is done after the fragment is processd.
- expectDone bool
-
- // sizeAfterEvent is the expected size of the fragmentation instance after
- // the event.
- sizeAfterEvent int
- }
-
- half1 := &fragment{first: 0, last: 0, more: true, data: "0"}
- half2 := &fragment{first: 1, last: 1, more: false, data: "1"}
-
- tests := []struct {
- name string
- events []event
- }{
- {
- name: "half1 and half2 are reassembled successfully",
- events: []event{
- {
- name: "half1",
- fragment: half1,
- expectDone: false,
- sizeAfterEvent: 1,
- },
- {
- name: "half2",
- fragment: half2,
- expectDone: true,
- sizeAfterEvent: 0,
- },
- },
- },
- {
- name: "half1 timeout, half2 timeout",
- events: []event{
- {
- name: "half1",
- fragment: half1,
- expectDone: false,
- sizeAfterEvent: 1,
- },
- {
- name: "half1 just before reassembly timeout",
- clockAdvance: reassemblyTimeout - 1,
- sizeAfterEvent: 1,
- },
- {
- name: "half1 reassembly timeout",
- clockAdvance: 1,
- sizeAfterEvent: 0,
- },
- {
- name: "half2",
- fragment: half2,
- expectDone: false,
- sizeAfterEvent: 1,
- },
- {
- name: "half2 just before reassembly timeout",
- clockAdvance: reassemblyTimeout - 1,
- sizeAfterEvent: 1,
- },
- {
- name: "half2 reassembly timeout",
- clockAdvance: 1,
- sizeAfterEvent: 0,
- },
- },
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- clock := faketime.NewManualClock()
- f := NewFragmentation(minBlockSize, HighFragThreshold, LowFragThreshold, reassemblyTimeout, clock)
- for _, event := range test.events {
- clock.Advance(event.clockAdvance)
- if frag := event.fragment; frag != nil {
- _, _, done, err := f.Process(FragmentID{}, frag.first, frag.last, frag.more, protocol, vv(len(frag.data), frag.data), nil)
- if err != nil {
- t.Fatalf("%s: f.Process failed: %s", event.name, err)
- }
- if done != event.expectDone {
- t.Fatalf("%s: got done = %t, want = %t", event.name, done, event.expectDone)
- }
- }
- if got, want := f.size, event.sizeAfterEvent; got != want {
- t.Errorf("%s: got f.size = %d, want = %d", event.name, got, want)
- }
- }
- })
- }
-}
-
-func TestMemoryLimits(t *testing.T) {
- f := NewFragmentation(minBlockSize, 3, 1, reassembleTimeout, &faketime.NullClock{})
- // Send first fragment with id = 0.
- f.Process(FragmentID{ID: 0}, 0, 0, true, 0xFF, vv(1, "0"), nil)
- // Send first fragment with id = 1.
- f.Process(FragmentID{ID: 1}, 0, 0, true, 0xFF, vv(1, "1"), nil)
- // Send first fragment with id = 2.
- f.Process(FragmentID{ID: 2}, 0, 0, true, 0xFF, vv(1, "2"), nil)
-
- // Send first fragment with id = 3. This should caused id = 0 and id = 1 to be
- // evicted.
- f.Process(FragmentID{ID: 3}, 0, 0, true, 0xFF, vv(1, "3"), nil)
-
- if _, ok := f.reassemblers[FragmentID{ID: 0}]; ok {
- t.Errorf("Memory limits are not respected: id=0 has not been evicted.")
- }
- if _, ok := f.reassemblers[FragmentID{ID: 1}]; ok {
- t.Errorf("Memory limits are not respected: id=1 has not been evicted.")
- }
- if _, ok := f.reassemblers[FragmentID{ID: 3}]; !ok {
- t.Errorf("Implementation of memory limits is wrong: id=3 is not present.")
- }
-}
-
-func TestMemoryLimitsIgnoresDuplicates(t *testing.T) {
- f := NewFragmentation(minBlockSize, 1, 0, reassembleTimeout, &faketime.NullClock{})
- // Send first fragment with id = 0.
- f.Process(FragmentID{}, 0, 0, true, 0xFF, vv(1, "0"), nil)
- // Send the same packet again.
- f.Process(FragmentID{}, 0, 0, true, 0xFF, vv(1, "0"), nil)
-
- got := f.size
- want := 1
- if got != want {
- t.Errorf("Wrong size, duplicates are not handled correctly: got=%d, want=%d.", got, want)
- }
-}
-
-func TestErrors(t *testing.T) {
- tests := []struct {
- name string
- blockSize uint16
- first uint16
- last uint16
- more bool
- data string
- err error
- }{
- {
- name: "exact block size without more",
- blockSize: 2,
- first: 2,
- last: 3,
- more: false,
- data: "01",
- },
- {
- name: "exact block size with more",
- blockSize: 2,
- first: 2,
- last: 3,
- more: true,
- data: "01",
- },
- {
- name: "exact block size with more and extra data",
- blockSize: 2,
- first: 2,
- last: 3,
- more: true,
- data: "012",
- },
- {
- name: "exact block size with more and too little data",
- blockSize: 2,
- first: 2,
- last: 3,
- more: true,
- data: "0",
- err: ErrInvalidArgs,
- },
- {
- name: "not exact block size with more",
- blockSize: 2,
- first: 2,
- last: 2,
- more: true,
- data: "0",
- err: ErrInvalidArgs,
- },
- {
- name: "not exact block size without more",
- blockSize: 2,
- first: 2,
- last: 2,
- more: false,
- data: "0",
- },
- {
- name: "first not a multiple of block size",
- blockSize: 2,
- first: 3,
- last: 4,
- more: true,
- data: "01",
- err: ErrInvalidArgs,
- },
- {
- name: "first more than last",
- blockSize: 2,
- first: 4,
- last: 3,
- more: true,
- data: "01",
- err: ErrInvalidArgs,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- f := NewFragmentation(test.blockSize, HighFragThreshold, LowFragThreshold, reassembleTimeout, &faketime.NullClock{})
- _, _, done, err := f.Process(FragmentID{}, test.first, test.last, test.more, 0, vv(len(test.data), test.data), nil)
- if !errors.Is(err, test.err) {
- t.Errorf("got Process(_, %d, %d, %t, _, %q) = (_, _, _, %v), want = (_, _, _, %v)", test.first, test.last, test.more, test.data, err, test.err)
- }
- if done {
- t.Errorf("got Process(_, %d, %d, %t, _, %q) = (_, _, true, _), want = (_, _, false, _)", test.first, test.last, test.more, test.data)
- }
- })
- }
-}
-
-type fragmentInfo struct {
- remaining int
- copied int
- offset int
- more bool
-}
-
-func TestPacketFragmenter(t *testing.T) {
- const (
- reserve = 60
- proto = 0
- )
-
- tests := []struct {
- name string
- fragmentPayloadLen uint32
- transportHeaderLen int
- payloadSize int
- wantFragments []fragmentInfo
- }{
- {
- name: "Packet exactly fits in MTU",
- fragmentPayloadLen: 1280,
- transportHeaderLen: 0,
- payloadSize: 1280,
- wantFragments: []fragmentInfo{
- {remaining: 0, copied: 1280, offset: 0, more: false},
- },
- },
- {
- name: "Packet exactly does not fit in MTU",
- fragmentPayloadLen: 1000,
- transportHeaderLen: 0,
- payloadSize: 1001,
- wantFragments: []fragmentInfo{
- {remaining: 1, copied: 1000, offset: 0, more: true},
- {remaining: 0, copied: 1, offset: 1000, more: false},
- },
- },
- {
- name: "Packet has a transport header",
- fragmentPayloadLen: 560,
- transportHeaderLen: 40,
- payloadSize: 560,
- wantFragments: []fragmentInfo{
- {remaining: 1, copied: 560, offset: 0, more: true},
- {remaining: 0, copied: 40, offset: 560, more: false},
- },
- },
- {
- name: "Packet has a huge transport header",
- fragmentPayloadLen: 500,
- transportHeaderLen: 1300,
- payloadSize: 500,
- wantFragments: []fragmentInfo{
- {remaining: 3, copied: 500, offset: 0, more: true},
- {remaining: 2, copied: 500, offset: 500, more: true},
- {remaining: 1, copied: 500, offset: 1000, more: true},
- {remaining: 0, copied: 300, offset: 1500, more: false},
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- pkt := testutil.MakeRandPkt(test.transportHeaderLen, reserve, []int{test.payloadSize}, proto)
- var originalPayload buffer.VectorisedView
- originalPayload.AppendView(pkt.TransportHeader().View())
- originalPayload.Append(pkt.Data)
- var reassembledPayload buffer.VectorisedView
- pf := MakePacketFragmenter(pkt, test.fragmentPayloadLen, reserve)
- for i := 0; ; i++ {
- fragPkt, offset, copied, more := pf.BuildNextFragment()
- wantFragment := test.wantFragments[i]
- if got := pf.RemainingFragmentCount(); got != wantFragment.remaining {
- t.Errorf("(fragment #%d) got pf.RemainingFragmentCount() = %d, want = %d", i, got, wantFragment.remaining)
- }
- if copied != wantFragment.copied {
- t.Errorf("(fragment #%d) got copied = %d, want = %d", i, copied, wantFragment.copied)
- }
- if offset != wantFragment.offset {
- t.Errorf("(fragment #%d) got offset = %d, want = %d", i, offset, wantFragment.offset)
- }
- if more != wantFragment.more {
- t.Errorf("(fragment #%d) got more = %t, want = %t", i, more, wantFragment.more)
- }
- if got := uint32(fragPkt.Size()); got > test.fragmentPayloadLen {
- t.Errorf("(fragment #%d) got fragPkt.Size() = %d, want <= %d", i, got, test.fragmentPayloadLen)
- }
- if got := fragPkt.AvailableHeaderBytes(); got != reserve {
- t.Errorf("(fragment #%d) got fragPkt.AvailableHeaderBytes() = %d, want = %d", i, got, reserve)
- }
- if got := fragPkt.TransportHeader().View().Size(); got != 0 {
- t.Errorf("(fragment #%d) got fragPkt.TransportHeader().View().Size() = %d, want = 0", i, got)
- }
- reassembledPayload.Append(fragPkt.Data)
- if !more {
- if i != len(test.wantFragments)-1 {
- t.Errorf("got fragment count = %d, want = %d", i, len(test.wantFragments)-1)
- }
- break
- }
- }
- if diff := cmp.Diff(reassembledPayload.ToView(), originalPayload.ToView()); diff != "" {
- t.Errorf("reassembledPayload mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-func TestReleaseCallback(t *testing.T) {
- const (
- proto = 99
- )
-
- var result int
- var callbackReasonIsTimeout bool
- cb1 := func(timedOut bool) { result = 1; callbackReasonIsTimeout = timedOut }
- cb2 := func(timedOut bool) { result = 2; callbackReasonIsTimeout = timedOut }
-
- tests := []struct {
- name string
- callbacks []func(bool)
- timeout bool
- wantResult int
- wantCallbackReasonIsTimeout bool
- }{
- {
- name: "callback runs on release",
- callbacks: []func(bool){cb1},
- timeout: false,
- wantResult: 1,
- wantCallbackReasonIsTimeout: false,
- },
- {
- name: "first callback is nil",
- callbacks: []func(bool){nil, cb2},
- timeout: false,
- wantResult: 2,
- wantCallbackReasonIsTimeout: false,
- },
- {
- name: "two callbacks - first one is set",
- callbacks: []func(bool){cb1, cb2},
- timeout: false,
- wantResult: 1,
- wantCallbackReasonIsTimeout: false,
- },
- {
- name: "callback runs on timeout",
- callbacks: []func(bool){cb1},
- timeout: true,
- wantResult: 1,
- wantCallbackReasonIsTimeout: true,
- },
- {
- name: "no callbacks",
- callbacks: []func(bool){nil},
- timeout: false,
- wantResult: 0,
- wantCallbackReasonIsTimeout: false,
- },
- }
-
- id := FragmentID{ID: 0}
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- result = 0
- callbackReasonIsTimeout = false
-
- f := NewFragmentation(minBlockSize, HighFragThreshold, LowFragThreshold, reassembleTimeout, &faketime.NullClock{})
-
- for i, cb := range test.callbacks {
- _, _, _, err := f.Process(id, uint16(i), uint16(i), true, proto, vv(1, "0"), cb)
- if err != nil {
- t.Errorf("f.Process error = %s", err)
- }
- }
-
- r, ok := f.reassemblers[id]
- if !ok {
- t.Fatalf("Reassemberr not found")
- }
- f.release(r, test.timeout)
-
- if result != test.wantResult {
- t.Errorf("got result = %d, want = %d", result, test.wantResult)
- }
- if callbackReasonIsTimeout != test.wantCallbackReasonIsTimeout {
- t.Errorf("got callbackReasonIsTimeout = %t, want = %t", callbackReasonIsTimeout, test.wantCallbackReasonIsTimeout)
- }
- })
- }
-}
diff --git a/pkg/tcpip/network/fragmentation/reassembler_list.go b/pkg/tcpip/network/fragmentation/reassembler_list.go
new file mode 100644
index 000000000..bb5bd75c4
--- /dev/null
+++ b/pkg/tcpip/network/fragmentation/reassembler_list.go
@@ -0,0 +1,193 @@
+package fragmentation
+
+// ElementMapper provides an identity mapping by default.
+//
+// This can be replaced to provide a struct that maps elements to linker
+// objects, if they are not the same. An ElementMapper is not typically
+// required if: Linker is left as is, Element is left as is, or Linker and
+// Element are the same type.
+type reassemblerElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (reassemblerElementMapper) linkerFor(elem *reassembler) *reassembler { return elem }
+
+// List is an intrusive list. Entries can be added to or removed from the list
+// in O(1) time and with no additional memory allocations.
+//
+// The zero value for List is an empty list ready to use.
+//
+// To iterate over a list (where l is a List):
+// for e := l.Front(); e != nil; e = e.Next() {
+// // do something with e.
+// }
+//
+// +stateify savable
+type reassemblerList struct {
+ head *reassembler
+ tail *reassembler
+}
+
+// Reset resets list l to the empty state.
+func (l *reassemblerList) Reset() {
+ l.head = nil
+ l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+func (l *reassemblerList) Empty() bool {
+ return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+func (l *reassemblerList) Front() *reassembler {
+ return l.head
+}
+
+// Back returns the last element of list l or nil.
+func (l *reassemblerList) Back() *reassembler {
+ return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+func (l *reassemblerList) Len() (count int) {
+ for e := l.Front(); e != nil; e = (reassemblerElementMapper{}.linkerFor(e)).Next() {
+ count++
+ }
+ return count
+}
+
+// PushFront inserts the element e at the front of list l.
+func (l *reassemblerList) PushFront(e *reassembler) {
+ linker := reassemblerElementMapper{}.linkerFor(e)
+ linker.SetNext(l.head)
+ linker.SetPrev(nil)
+ if l.head != nil {
+ reassemblerElementMapper{}.linkerFor(l.head).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+
+ l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+func (l *reassemblerList) PushBack(e *reassembler) {
+ linker := reassemblerElementMapper{}.linkerFor(e)
+ linker.SetNext(nil)
+ linker.SetPrev(l.tail)
+ if l.tail != nil {
+ reassemblerElementMapper{}.linkerFor(l.tail).SetNext(e)
+ } else {
+ l.head = e
+ }
+
+ l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+func (l *reassemblerList) PushBackList(m *reassemblerList) {
+ if l.head == nil {
+ l.head = m.head
+ l.tail = m.tail
+ } else if m.head != nil {
+ reassemblerElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+ reassemblerElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+ l.tail = m.tail
+ }
+ m.head = nil
+ m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+func (l *reassemblerList) InsertAfter(b, e *reassembler) {
+ bLinker := reassemblerElementMapper{}.linkerFor(b)
+ eLinker := reassemblerElementMapper{}.linkerFor(e)
+
+ a := bLinker.Next()
+
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ bLinker.SetNext(e)
+
+ if a != nil {
+ reassemblerElementMapper{}.linkerFor(a).SetPrev(e)
+ } else {
+ l.tail = e
+ }
+}
+
+// InsertBefore inserts e before a.
+func (l *reassemblerList) InsertBefore(a, e *reassembler) {
+ aLinker := reassemblerElementMapper{}.linkerFor(a)
+ eLinker := reassemblerElementMapper{}.linkerFor(e)
+
+ b := aLinker.Prev()
+ eLinker.SetNext(a)
+ eLinker.SetPrev(b)
+ aLinker.SetPrev(e)
+
+ if b != nil {
+ reassemblerElementMapper{}.linkerFor(b).SetNext(e)
+ } else {
+ l.head = e
+ }
+}
+
+// Remove removes e from l.
+func (l *reassemblerList) Remove(e *reassembler) {
+ linker := reassemblerElementMapper{}.linkerFor(e)
+ prev := linker.Prev()
+ next := linker.Next()
+
+ if prev != nil {
+ reassemblerElementMapper{}.linkerFor(prev).SetNext(next)
+ } else if l.head == e {
+ l.head = next
+ }
+
+ if next != nil {
+ reassemblerElementMapper{}.linkerFor(next).SetPrev(prev)
+ } else if l.tail == e {
+ l.tail = prev
+ }
+
+ linker.SetNext(nil)
+ linker.SetPrev(nil)
+}
+
+// Entry is a default implementation of Linker. Users can add anonymous fields
+// of this type to their structs to make them automatically implement the
+// methods needed by List.
+//
+// +stateify savable
+type reassemblerEntry struct {
+ next *reassembler
+ prev *reassembler
+}
+
+// Next returns the entry that follows e in the list.
+func (e *reassemblerEntry) Next() *reassembler {
+ return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+func (e *reassemblerEntry) Prev() *reassembler {
+ return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+func (e *reassemblerEntry) SetNext(elem *reassembler) {
+ e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+func (e *reassemblerEntry) SetPrev(elem *reassembler) {
+ e.prev = elem
+}
diff --git a/pkg/tcpip/network/fragmentation/reassembler_test.go b/pkg/tcpip/network/fragmentation/reassembler_test.go
deleted file mode 100644
index fa2a70dc8..000000000
--- a/pkg/tcpip/network/fragmentation/reassembler_test.go
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package fragmentation
-
-import (
- "math"
- "reflect"
- "testing"
-
- "gvisor.dev/gvisor/pkg/tcpip/faketime"
-)
-
-type updateHolesInput struct {
- first uint16
- last uint16
- more bool
-}
-
-var holesTestCases = []struct {
- comment string
- in []updateHolesInput
- want []hole
-}{
- {
- comment: "No fragments. Expected holes: {[0 -> inf]}.",
- in: []updateHolesInput{},
- want: []hole{{first: 0, last: math.MaxUint16, deleted: false}},
- },
- {
- comment: "One fragment at beginning. Expected holes: {[2, inf]}.",
- in: []updateHolesInput{{first: 0, last: 1, more: true}},
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 2, last: math.MaxUint16, deleted: false},
- },
- },
- {
- comment: "One fragment in the middle. Expected holes: {[0, 0], [3, inf]}.",
- in: []updateHolesInput{{first: 1, last: 2, more: true}},
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 0, last: 0, deleted: false},
- {first: 3, last: math.MaxUint16, deleted: false},
- },
- },
- {
- comment: "One fragment at the end. Expected holes: {[0, 0]}.",
- in: []updateHolesInput{{first: 1, last: 2, more: false}},
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 0, last: 0, deleted: false},
- },
- },
- {
- comment: "One fragment completing a packet. Expected holes: {}.",
- in: []updateHolesInput{{first: 0, last: 1, more: false}},
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- },
- },
- {
- comment: "Two non-overlapping fragments completing a packet. Expected holes: {}.",
- in: []updateHolesInput{
- {first: 0, last: 1, more: true},
- {first: 2, last: 3, more: false},
- },
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 2, last: math.MaxUint16, deleted: true},
- },
- },
- {
- comment: "Two overlapping fragments completing a packet. Expected holes: {}.",
- in: []updateHolesInput{
- {first: 0, last: 2, more: true},
- {first: 2, last: 3, more: false},
- },
- want: []hole{
- {first: 0, last: math.MaxUint16, deleted: true},
- {first: 3, last: math.MaxUint16, deleted: true},
- },
- },
-}
-
-func TestUpdateHoles(t *testing.T) {
- for _, c := range holesTestCases {
- r := newReassembler(FragmentID{}, &faketime.NullClock{})
- for _, i := range c.in {
- r.updateHoles(i.first, i.last, i.more)
- }
- if !reflect.DeepEqual(r.holes, c.want) {
- t.Errorf("Test \"%s\" produced unexepetced holes. Got %v. Want %v", c.comment, r.holes, c.want)
- }
- }
-}
-
-func TestSetCallback(t *testing.T) {
- result := 0
- reasonTimeout := false
-
- cb1 := func(timedOut bool) { result = 1; reasonTimeout = timedOut }
- cb2 := func(timedOut bool) { result = 2; reasonTimeout = timedOut }
-
- r := newReassembler(FragmentID{}, &faketime.NullClock{})
- if !r.setCallback(cb1) {
- t.Errorf("setCallback failed")
- }
- if r.setCallback(cb2) {
- t.Errorf("setCallback should fail if one is already set")
- }
- r.release(true)
- if result != 1 {
- t.Errorf("got result = %d, want = 1", result)
- }
- if !reasonTimeout {
- t.Errorf("got reasonTimeout = %t, want = true", reasonTimeout)
- }
-}
diff --git a/pkg/tcpip/network/hash/BUILD b/pkg/tcpip/network/hash/BUILD
deleted file mode 100644
index 872165866..000000000
--- a/pkg/tcpip/network/hash/BUILD
+++ /dev/null
@@ -1,13 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "hash",
- srcs = ["hash.go"],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/rand",
- "//pkg/tcpip/header",
- ],
-)
diff --git a/pkg/tcpip/network/hash/hash_state_autogen.go b/pkg/tcpip/network/hash/hash_state_autogen.go
new file mode 100644
index 000000000..9467fe298
--- /dev/null
+++ b/pkg/tcpip/network/hash/hash_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package hash
diff --git a/pkg/tcpip/network/ip_test.go b/pkg/tcpip/network/ip_test.go
deleted file mode 100644
index 8873bd91f..000000000
--- a/pkg/tcpip/network/ip_test.go
+++ /dev/null
@@ -1,1493 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ip_test
-
-import (
- "strings"
- "testing"
-
- "gvisor.dev/gvisor/pkg/sync"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "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/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
-)
-
-const (
- localIPv4Addr = "\x0a\x00\x00\x01"
- remoteIPv4Addr = "\x0a\x00\x00\x02"
- ipv4SubnetAddr = "\x0a\x00\x00\x00"
- ipv4SubnetMask = "\xff\xff\xff\x00"
- ipv4Gateway = "\x0a\x00\x00\x03"
- localIPv6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
- remoteIPv6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
- ipv6SubnetAddr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- ipv6SubnetMask = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00"
- ipv6Gateway = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03"
- nicID = 1
-)
-
-var localIPv4AddrWithPrefix = tcpip.AddressWithPrefix{
- Address: localIPv4Addr,
- PrefixLen: 24,
-}
-
-var localIPv6AddrWithPrefix = tcpip.AddressWithPrefix{
- Address: localIPv6Addr,
- PrefixLen: 120,
-}
-
-// testObject implements two interfaces: LinkEndpoint and TransportDispatcher.
-// The former is used to pretend that it's a link endpoint so that we can
-// inspect packets written by the network endpoints. The latter is used to
-// pretend that it's the network stack so that it can inspect incoming packets
-// that have been handled by the network endpoints.
-//
-// Packets are checked by comparing their fields/values against the expected
-// values stored in the test object itself.
-type testObject struct {
- t *testing.T
- protocol tcpip.TransportProtocolNumber
- contents []byte
- srcAddr tcpip.Address
- dstAddr tcpip.Address
- v4 bool
- typ stack.ControlType
- extra uint32
-
- dataCalls int
- controlCalls int
-}
-
-// checkValues verifies that the transport protocol, data contents, src & dst
-// addresses of a packet match what's expected. If any field doesn't match, the
-// test fails.
-func (t *testObject) checkValues(protocol tcpip.TransportProtocolNumber, vv buffer.VectorisedView, srcAddr, dstAddr tcpip.Address) {
- v := vv.ToView()
- if protocol != t.protocol {
- t.t.Errorf("protocol = %v, want %v", protocol, t.protocol)
- }
-
- if srcAddr != t.srcAddr {
- t.t.Errorf("srcAddr = %v, want %v", srcAddr, t.srcAddr)
- }
-
- if dstAddr != t.dstAddr {
- t.t.Errorf("dstAddr = %v, want %v", dstAddr, t.dstAddr)
- }
-
- if len(v) != len(t.contents) {
- t.t.Fatalf("len(payload) = %v, want %v", len(v), len(t.contents))
- }
-
- for i := range t.contents {
- if t.contents[i] != v[i] {
- t.t.Fatalf("payload[%v] = %v, want %v", i, v[i], t.contents[i])
- }
- }
-}
-
-// DeliverTransportPacket is called by network endpoints after parsing incoming
-// packets. This is used by the test object to verify that the results of the
-// parsing are expected.
-func (t *testObject) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt *stack.PacketBuffer) stack.TransportPacketDisposition {
- netHdr := pkt.Network()
- t.checkValues(protocol, pkt.Data, netHdr.SourceAddress(), netHdr.DestinationAddress())
- t.dataCalls++
- return stack.TransportPacketHandled
-}
-
-// DeliverTransportControlPacket is called by network endpoints after parsing
-// incoming control (ICMP) packets. This is used by the test object to verify
-// that the results of the parsing are expected.
-func (t *testObject) DeliverTransportControlPacket(local, remote tcpip.Address, net tcpip.NetworkProtocolNumber, trans tcpip.TransportProtocolNumber, typ stack.ControlType, extra uint32, pkt *stack.PacketBuffer) {
- t.checkValues(trans, pkt.Data, remote, local)
- if typ != t.typ {
- t.t.Errorf("typ = %v, want %v", typ, t.typ)
- }
- if extra != t.extra {
- t.t.Errorf("extra = %v, want %v", extra, t.extra)
- }
- t.controlCalls++
-}
-
-// Attach is only implemented to satisfy the LinkEndpoint interface.
-func (*testObject) Attach(stack.NetworkDispatcher) {}
-
-// IsAttached implements stack.LinkEndpoint.IsAttached.
-func (*testObject) IsAttached() bool {
- return true
-}
-
-// MTU implements stack.LinkEndpoint.MTU. It just returns a constant that
-// matches the linux loopback MTU.
-func (*testObject) MTU() uint32 {
- return 65536
-}
-
-// Capabilities implements stack.LinkEndpoint.Capabilities.
-func (*testObject) Capabilities() stack.LinkEndpointCapabilities {
- return 0
-}
-
-// MaxHeaderLength is only implemented to satisfy the LinkEndpoint interface.
-func (*testObject) MaxHeaderLength() uint16 {
- return 0
-}
-
-// LinkAddress returns the link address of this endpoint.
-func (*testObject) LinkAddress() tcpip.LinkAddress {
- return ""
-}
-
-// Wait implements stack.LinkEndpoint.Wait.
-func (*testObject) Wait() {}
-
-// WritePacket is called by network endpoints after producing a packet and
-// writing it to the link endpoint. This is used by the test object to verify
-// that the produced packet is as expected.
-func (t *testObject) WritePacket(_ *stack.Route, _ *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
- var prot tcpip.TransportProtocolNumber
- var srcAddr tcpip.Address
- var dstAddr tcpip.Address
-
- if t.v4 {
- h := header.IPv4(pkt.NetworkHeader().View())
- prot = tcpip.TransportProtocolNumber(h.Protocol())
- srcAddr = h.SourceAddress()
- dstAddr = h.DestinationAddress()
-
- } else {
- h := header.IPv6(pkt.NetworkHeader().View())
- prot = tcpip.TransportProtocolNumber(h.NextHeader())
- srcAddr = h.SourceAddress()
- dstAddr = h.DestinationAddress()
- }
- t.checkValues(prot, pkt.Data, srcAddr, dstAddr)
- return nil
-}
-
-// WritePackets implements stack.LinkEndpoint.WritePackets.
-func (*testObject) WritePackets(_ *stack.Route, _ *stack.GSO, pkt stack.PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, *tcpip.Error) {
- panic("not implemented")
-}
-
-func (*testObject) WriteRawPacket(_ buffer.VectorisedView) *tcpip.Error {
- return tcpip.ErrNotSupported
-}
-
-// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
-func (*testObject) ARPHardwareType() header.ARPHardwareType {
- panic("not implemented")
-}
-
-// AddHeader implements stack.LinkEndpoint.AddHeader.
-func (*testObject) AddHeader(local, remote tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
- panic("not implemented")
-}
-
-func buildIPv4Route(local, remote tcpip.Address) (stack.Route, *tcpip.Error) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
- })
- s.CreateNIC(nicID, loopback.New())
- s.AddAddress(nicID, ipv4.ProtocolNumber, local)
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv4EmptySubnet,
- Gateway: ipv4Gateway,
- NIC: 1,
- }})
-
- return s.FindRoute(nicID, local, remote, ipv4.ProtocolNumber, false /* multicastLoop */)
-}
-
-func buildIPv6Route(local, remote tcpip.Address) (stack.Route, *tcpip.Error) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
- })
- s.CreateNIC(nicID, loopback.New())
- s.AddAddress(nicID, ipv6.ProtocolNumber, local)
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv6EmptySubnet,
- Gateway: ipv6Gateway,
- NIC: 1,
- }})
-
- return s.FindRoute(nicID, local, remote, ipv6.ProtocolNumber, false /* multicastLoop */)
-}
-
-func buildDummyStackWithLinkEndpoint(t *testing.T) (*stack.Stack, *channel.Endpoint) {
- t.Helper()
-
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
- })
- e := channel.New(0, 1280, "")
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- v4Addr := tcpip.ProtocolAddress{Protocol: header.IPv4ProtocolNumber, AddressWithPrefix: localIPv4AddrWithPrefix}
- if err := s.AddProtocolAddress(nicID, v4Addr); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %#v) = %s", nicID, v4Addr, err)
- }
-
- v6Addr := tcpip.ProtocolAddress{Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: localIPv6AddrWithPrefix}
- if err := s.AddProtocolAddress(nicID, v6Addr); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %#v) = %s", nicID, v6Addr, err)
- }
-
- return s, e
-}
-
-func buildDummyStack(t *testing.T) *stack.Stack {
- t.Helper()
-
- s, _ := buildDummyStackWithLinkEndpoint(t)
- return s
-}
-
-var _ stack.NetworkInterface = (*testInterface)(nil)
-
-type testInterface struct {
- testObject
-
- mu struct {
- sync.RWMutex
- disabled bool
- }
-}
-
-func (*testInterface) ID() tcpip.NICID {
- return nicID
-}
-
-func (*testInterface) IsLoopback() bool {
- return false
-}
-
-func (*testInterface) Name() string {
- return ""
-}
-
-func (t *testInterface) Enabled() bool {
- t.mu.RLock()
- defer t.mu.RUnlock()
- return !t.mu.disabled
-}
-
-func (t *testInterface) setEnabled(v bool) {
- t.mu.Lock()
- defer t.mu.Unlock()
- t.mu.disabled = !v
-}
-
-func (*testInterface) WritePacketToRemote(tcpip.LinkAddress, *stack.GSO, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) *tcpip.Error {
- return tcpip.ErrNotSupported
-}
-
-func TestSourceAddressValidation(t *testing.T) {
- rxIPv4ICMP := func(e *channel.Endpoint, src tcpip.Address) {
- totalLen := header.IPv4MinimumSize + header.ICMPv4MinimumSize
- hdr := buffer.NewPrependable(totalLen)
- pkt := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
- pkt.SetType(header.ICMPv4Echo)
- pkt.SetCode(0)
- pkt.SetChecksum(0)
- pkt.SetChecksum(^header.Checksum(pkt, 0))
- ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(totalLen),
- Protocol: uint8(icmp.ProtocolNumber4),
- TTL: ipv4.DefaultTTL,
- SrcAddr: src,
- DstAddr: localIPv4Addr,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
- }
-
- rxIPv6ICMP := func(e *channel.Endpoint, src tcpip.Address) {
- totalLen := header.IPv6MinimumSize + header.ICMPv6MinimumSize
- hdr := buffer.NewPrependable(totalLen)
- pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6MinimumSize))
- pkt.SetType(header.ICMPv6EchoRequest)
- pkt.SetCode(0)
- pkt.SetChecksum(0)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, src, localIPv6Addr, buffer.VectorisedView{}))
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: header.ICMPv6MinimumSize,
- NextHeader: uint8(icmp.ProtocolNumber6),
- HopLimit: ipv6.DefaultTTL,
- SrcAddr: src,
- DstAddr: localIPv6Addr,
- })
- e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
- }
-
- tests := []struct {
- name string
- srcAddress tcpip.Address
- rxICMP func(*channel.Endpoint, tcpip.Address)
- valid bool
- }{
- {
- name: "IPv4 valid",
- srcAddress: "\x01\x02\x03\x04",
- rxICMP: rxIPv4ICMP,
- valid: true,
- },
- {
- name: "IPv6 valid",
- srcAddress: "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10",
- rxICMP: rxIPv6ICMP,
- valid: true,
- },
- {
- name: "IPv4 unspecified",
- srcAddress: header.IPv4Any,
- rxICMP: rxIPv4ICMP,
- valid: true,
- },
- {
- name: "IPv6 unspecified",
- srcAddress: header.IPv4Any,
- rxICMP: rxIPv6ICMP,
- valid: true,
- },
- {
- name: "IPv4 multicast",
- srcAddress: "\xe0\x00\x00\x01",
- rxICMP: rxIPv4ICMP,
- valid: false,
- },
- {
- name: "IPv6 multicast",
- srcAddress: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
- rxICMP: rxIPv6ICMP,
- valid: false,
- },
- {
- name: "IPv4 broadcast",
- srcAddress: header.IPv4Broadcast,
- rxICMP: rxIPv4ICMP,
- valid: false,
- },
- {
- name: "IPv4 subnet broadcast",
- srcAddress: func() tcpip.Address {
- subnet := localIPv4AddrWithPrefix.Subnet()
- return subnet.Broadcast()
- }(),
- rxICMP: rxIPv4ICMP,
- valid: false,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s, e := buildDummyStackWithLinkEndpoint(t)
- test.rxICMP(e, test.srcAddress)
-
- var wantValid uint64
- if test.valid {
- wantValid = 1
- }
-
- if got, want := s.Stats().IP.InvalidSourceAddressesReceived.Value(), 1-wantValid; got != want {
- t.Errorf("got s.Stats().IP.InvalidSourceAddressesReceived.Value() = %d, want = %d", got, want)
- }
- if got := s.Stats().IP.PacketsDelivered.Value(); got != wantValid {
- t.Errorf("got s.Stats().IP.PacketsDelivered.Value() = %d, want = %d", got, wantValid)
- }
- })
- }
-}
-
-func TestEnableWhenNICDisabled(t *testing.T) {
- tests := []struct {
- name string
- protocolFactory stack.NetworkProtocolFactory
- protoNum tcpip.NetworkProtocolNumber
- }{
- {
- name: "IPv4",
- protocolFactory: ipv4.NewProtocol,
- protoNum: ipv4.ProtocolNumber,
- },
- {
- name: "IPv6",
- protocolFactory: ipv6.NewProtocol,
- protoNum: ipv6.ProtocolNumber,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- var nic testInterface
- nic.setEnabled(false)
-
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{test.protocolFactory},
- })
- p := s.NetworkProtocolInstance(test.protoNum)
-
- // We pass nil for all parameters except the NetworkInterface and Stack
- // since Enable only depends on these.
- ep := p.NewEndpoint(&nic, nil, nil, nil)
-
- // The endpoint should initially be disabled, regardless the NIC's enabled
- // status.
- if ep.Enabled() {
- t.Fatal("got ep.Enabled() = true, want = false")
- }
- nic.setEnabled(true)
- if ep.Enabled() {
- t.Fatal("got ep.Enabled() = true, want = false")
- }
-
- // Attempting to enable the endpoint while the NIC is disabled should
- // fail.
- nic.setEnabled(false)
- if err := ep.Enable(); err != tcpip.ErrNotPermitted {
- t.Fatalf("got ep.Enable() = %s, want = %s", err, tcpip.ErrNotPermitted)
- }
- // ep should consider the NIC's enabled status when determining its own
- // enabled status so we "enable" the NIC to read just the endpoint's
- // enabled status.
- nic.setEnabled(true)
- if ep.Enabled() {
- t.Fatal("got ep.Enabled() = true, want = false")
- }
-
- // Enabling the interface after the NIC has been enabled should succeed.
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
- if !ep.Enabled() {
- t.Fatal("got ep.Enabled() = false, want = true")
- }
-
- // ep should consider the NIC's enabled status when determining its own
- // enabled status.
- nic.setEnabled(false)
- if ep.Enabled() {
- t.Fatal("got ep.Enabled() = true, want = false")
- }
-
- // Disabling the endpoint when the NIC is enabled should make the endpoint
- // disabled.
- nic.setEnabled(true)
- ep.Disable()
- if ep.Enabled() {
- t.Fatal("got ep.Enabled() = true, want = false")
- }
- })
- }
-}
-
-func TestIPv4Send(t *testing.T) {
- s := buildDummyStack(t)
- proto := s.NetworkProtocolInstance(ipv4.ProtocolNumber)
- nic := testInterface{
- testObject: testObject{
- t: t,
- v4: true,
- },
- }
- ep := proto.NewEndpoint(&nic, nil, nil, nil)
- defer ep.Close()
-
- // Allocate and initialize the payload view.
- payload := buffer.NewView(100)
- for i := 0; i < len(payload); i++ {
- payload[i] = uint8(i)
- }
-
- // Setup the packet buffer.
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: int(ep.MaxHeaderLength()),
- Data: payload.ToVectorisedView(),
- })
-
- // Issue the write.
- nic.testObject.protocol = 123
- nic.testObject.srcAddr = localIPv4Addr
- nic.testObject.dstAddr = remoteIPv4Addr
- nic.testObject.contents = payload
-
- r, err := buildIPv4Route(localIPv4Addr, remoteIPv4Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
- if err := ep.WritePacket(&r, nil /* gso */, stack.NetworkHeaderParams{
- Protocol: 123,
- TTL: 123,
- TOS: stack.DefaultTOS,
- }, pkt); err != nil {
- t.Fatalf("WritePacket failed: %v", err)
- }
-}
-
-func TestIPv4Receive(t *testing.T) {
- s := buildDummyStack(t)
- proto := s.NetworkProtocolInstance(ipv4.ProtocolNumber)
- nic := testInterface{
- testObject: testObject{
- t: t,
- v4: true,
- },
- }
- ep := proto.NewEndpoint(&nic, nil, nil, &nic.testObject)
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- totalLen := header.IPv4MinimumSize + 30
- view := buffer.NewView(totalLen)
- ip := header.IPv4(view)
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(totalLen),
- TTL: 20,
- Protocol: 10,
- SrcAddr: remoteIPv4Addr,
- DstAddr: localIPv4Addr,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- // Make payload be non-zero.
- for i := header.IPv4MinimumSize; i < totalLen; i++ {
- view[i] = uint8(i)
- }
-
- // Give packet to ipv4 endpoint, dispatcher will validate that it's ok.
- nic.testObject.protocol = 10
- nic.testObject.srcAddr = remoteIPv4Addr
- nic.testObject.dstAddr = localIPv4Addr
- nic.testObject.contents = view[header.IPv4MinimumSize:totalLen]
-
- r, err := buildIPv4Route(localIPv4Addr, remoteIPv4Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: view.ToVectorisedView(),
- })
- if _, _, ok := proto.Parse(pkt); !ok {
- t.Fatalf("failed to parse packet: %x", pkt.Data.ToView())
- }
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- if nic.testObject.dataCalls != 1 {
- t.Fatalf("Bad number of data calls: got %x, want 1", nic.testObject.dataCalls)
- }
-}
-
-func TestIPv4ReceiveControl(t *testing.T) {
- const mtu = 0xbeef - header.IPv4MinimumSize
- cases := []struct {
- name string
- expectedCount int
- fragmentOffset uint16
- code header.ICMPv4Code
- expectedTyp stack.ControlType
- expectedExtra uint32
- trunc int
- }{
- {"FragmentationNeeded", 1, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, 0},
- {"Truncated (10 bytes missing)", 0, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, 10},
- {"Truncated (missing IPv4 header)", 0, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, header.IPv4MinimumSize + 8},
- {"Truncated (missing 'extra info')", 0, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, 4 + header.IPv4MinimumSize + 8},
- {"Truncated (missing ICMP header)", 0, 0, header.ICMPv4FragmentationNeeded, stack.ControlPacketTooBig, mtu, header.ICMPv4MinimumSize + header.IPv4MinimumSize + 8},
- {"Port unreachable", 1, 0, header.ICMPv4PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Non-zero fragment offset", 0, 100, header.ICMPv4PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Zero-length packet", 0, 0, header.ICMPv4PortUnreachable, stack.ControlPortUnreachable, 0, 2*header.IPv4MinimumSize + header.ICMPv4MinimumSize + 8},
- }
- r, err := buildIPv4Route(localIPv4Addr, "\x0a\x00\x00\xbb")
- if err != nil {
- t.Fatal(err)
- }
- for _, c := range cases {
- t.Run(c.name, func(t *testing.T) {
- s := buildDummyStack(t)
- proto := s.NetworkProtocolInstance(ipv4.ProtocolNumber)
- nic := testInterface{
- testObject: testObject{
- t: t,
- },
- }
- ep := proto.NewEndpoint(&nic, nil, nil, &nic.testObject)
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- const dataOffset = header.IPv4MinimumSize*2 + header.ICMPv4MinimumSize
- view := buffer.NewView(dataOffset + 8)
-
- // Create the outer IPv4 header.
- ip := header.IPv4(view)
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(len(view) - c.trunc),
- TTL: 20,
- Protocol: uint8(header.ICMPv4ProtocolNumber),
- SrcAddr: "\x0a\x00\x00\xbb",
- DstAddr: localIPv4Addr,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- // Create the ICMP header.
- icmp := header.ICMPv4(view[header.IPv4MinimumSize:])
- icmp.SetType(header.ICMPv4DstUnreachable)
- icmp.SetCode(c.code)
- icmp.SetIdent(0xdead)
- icmp.SetSequence(0xbeef)
-
- // Create the inner IPv4 header.
- ip = header.IPv4(view[header.IPv4MinimumSize+header.ICMPv4MinimumSize:])
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: 100,
- TTL: 20,
- Protocol: 10,
- FragmentOffset: c.fragmentOffset,
- SrcAddr: localIPv4Addr,
- DstAddr: remoteIPv4Addr,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- // Make payload be non-zero.
- for i := dataOffset; i < len(view); i++ {
- view[i] = uint8(i)
- }
-
- icmp.SetChecksum(0)
- checksum := ^header.Checksum(icmp, 0 /* initial */)
- icmp.SetChecksum(checksum)
-
- // Give packet to IPv4 endpoint, dispatcher will validate that
- // it's ok.
- nic.testObject.protocol = 10
- nic.testObject.srcAddr = remoteIPv4Addr
- nic.testObject.dstAddr = localIPv4Addr
- nic.testObject.contents = view[dataOffset:]
- nic.testObject.typ = c.expectedTyp
- nic.testObject.extra = c.expectedExtra
-
- pkt := truncatedPacket(view, c.trunc, header.IPv4MinimumSize)
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- if want := c.expectedCount; nic.testObject.controlCalls != want {
- t.Fatalf("Bad number of control calls for %q case: got %v, want %v", c.name, nic.testObject.controlCalls, want)
- }
- })
- }
-}
-
-func TestIPv4FragmentationReceive(t *testing.T) {
- s := buildDummyStack(t)
- proto := s.NetworkProtocolInstance(ipv4.ProtocolNumber)
- nic := testInterface{
- testObject: testObject{
- t: t,
- v4: true,
- },
- }
- ep := proto.NewEndpoint(&nic, nil, nil, &nic.testObject)
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- totalLen := header.IPv4MinimumSize + 24
-
- frag1 := buffer.NewView(totalLen)
- ip1 := header.IPv4(frag1)
- ip1.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(totalLen),
- TTL: 20,
- Protocol: 10,
- FragmentOffset: 0,
- Flags: header.IPv4FlagMoreFragments,
- SrcAddr: remoteIPv4Addr,
- DstAddr: localIPv4Addr,
- })
- ip1.SetChecksum(^ip1.CalculateChecksum())
-
- // Make payload be non-zero.
- for i := header.IPv4MinimumSize; i < totalLen; i++ {
- frag1[i] = uint8(i)
- }
-
- frag2 := buffer.NewView(totalLen)
- ip2 := header.IPv4(frag2)
- ip2.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(totalLen),
- TTL: 20,
- Protocol: 10,
- FragmentOffset: 24,
- SrcAddr: remoteIPv4Addr,
- DstAddr: localIPv4Addr,
- })
- ip2.SetChecksum(^ip2.CalculateChecksum())
-
- // Make payload be non-zero.
- for i := header.IPv4MinimumSize; i < totalLen; i++ {
- frag2[i] = uint8(i)
- }
-
- // Give packet to ipv4 endpoint, dispatcher will validate that it's ok.
- nic.testObject.protocol = 10
- nic.testObject.srcAddr = remoteIPv4Addr
- nic.testObject.dstAddr = localIPv4Addr
- nic.testObject.contents = append(frag1[header.IPv4MinimumSize:totalLen], frag2[header.IPv4MinimumSize:totalLen]...)
-
- r, err := buildIPv4Route(localIPv4Addr, remoteIPv4Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
-
- // Send first segment.
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: frag1.ToVectorisedView(),
- })
- if _, _, ok := proto.Parse(pkt); !ok {
- t.Fatalf("failed to parse packet: %x", pkt.Data.ToView())
- }
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- if nic.testObject.dataCalls != 0 {
- t.Fatalf("Bad number of data calls: got %x, want 0", nic.testObject.dataCalls)
- }
-
- // Send second segment.
- pkt = stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: frag2.ToVectorisedView(),
- })
- if _, _, ok := proto.Parse(pkt); !ok {
- t.Fatalf("failed to parse packet: %x", pkt.Data.ToView())
- }
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- if nic.testObject.dataCalls != 1 {
- t.Fatalf("Bad number of data calls: got %x, want 1", nic.testObject.dataCalls)
- }
-}
-
-func TestIPv6Send(t *testing.T) {
- s := buildDummyStack(t)
- proto := s.NetworkProtocolInstance(ipv6.ProtocolNumber)
- nic := testInterface{
- testObject: testObject{
- t: t,
- },
- }
- ep := proto.NewEndpoint(&nic, nil, nil, nil)
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- // Allocate and initialize the payload view.
- payload := buffer.NewView(100)
- for i := 0; i < len(payload); i++ {
- payload[i] = uint8(i)
- }
-
- // Setup the packet buffer.
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: int(ep.MaxHeaderLength()),
- Data: payload.ToVectorisedView(),
- })
-
- // Issue the write.
- nic.testObject.protocol = 123
- nic.testObject.srcAddr = localIPv6Addr
- nic.testObject.dstAddr = remoteIPv6Addr
- nic.testObject.contents = payload
-
- r, err := buildIPv6Route(localIPv6Addr, remoteIPv6Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
- if err := ep.WritePacket(&r, nil /* gso */, stack.NetworkHeaderParams{
- Protocol: 123,
- TTL: 123,
- TOS: stack.DefaultTOS,
- }, pkt); err != nil {
- t.Fatalf("WritePacket failed: %v", err)
- }
-}
-
-func TestIPv6Receive(t *testing.T) {
- s := buildDummyStack(t)
- proto := s.NetworkProtocolInstance(ipv6.ProtocolNumber)
- nic := testInterface{
- testObject: testObject{
- t: t,
- },
- }
- ep := proto.NewEndpoint(&nic, nil, nil, &nic.testObject)
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- totalLen := header.IPv6MinimumSize + 30
- view := buffer.NewView(totalLen)
- ip := header.IPv6(view)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(totalLen - header.IPv6MinimumSize),
- NextHeader: 10,
- HopLimit: 20,
- SrcAddr: remoteIPv6Addr,
- DstAddr: localIPv6Addr,
- })
-
- // Make payload be non-zero.
- for i := header.IPv6MinimumSize; i < totalLen; i++ {
- view[i] = uint8(i)
- }
-
- // Give packet to ipv6 endpoint, dispatcher will validate that it's ok.
- nic.testObject.protocol = 10
- nic.testObject.srcAddr = remoteIPv6Addr
- nic.testObject.dstAddr = localIPv6Addr
- nic.testObject.contents = view[header.IPv6MinimumSize:totalLen]
-
- r, err := buildIPv6Route(localIPv6Addr, remoteIPv6Addr)
- if err != nil {
- t.Fatalf("could not find route: %v", err)
- }
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: view.ToVectorisedView(),
- })
- if _, _, ok := proto.Parse(pkt); !ok {
- t.Fatalf("failed to parse packet: %x", pkt.Data.ToView())
- }
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- if nic.testObject.dataCalls != 1 {
- t.Fatalf("Bad number of data calls: got %x, want 1", nic.testObject.dataCalls)
- }
-}
-
-func TestIPv6ReceiveControl(t *testing.T) {
- newUint16 := func(v uint16) *uint16 { return &v }
-
- const mtu = 0xffff
- const outerSrcAddr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa"
- cases := []struct {
- name string
- expectedCount int
- fragmentOffset *uint16
- typ header.ICMPv6Type
- code header.ICMPv6Code
- expectedTyp stack.ControlType
- expectedExtra uint32
- trunc int
- }{
- {"PacketTooBig", 1, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, 0},
- {"Truncated (10 bytes missing)", 0, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, 10},
- {"Truncated (missing IPv6 header)", 0, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, header.IPv6MinimumSize + 8},
- {"Truncated PacketTooBig (missing 'extra info')", 0, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, 4 + header.IPv6MinimumSize + 8},
- {"Truncated (missing ICMP header)", 0, nil, header.ICMPv6PacketTooBig, 0, stack.ControlPacketTooBig, mtu, header.ICMPv6PacketTooBigMinimumSize + header.IPv6MinimumSize + 8},
- {"Port unreachable", 1, nil, header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Truncated DstUnreachable (missing 'extra info')", 0, nil, header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 4 + header.IPv6MinimumSize + 8},
- {"Fragmented, zero offset", 1, newUint16(0), header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Non-zero fragment offset", 0, newUint16(100), header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 0},
- {"Zero-length packet", 0, nil, header.ICMPv6DstUnreachable, header.ICMPv6PortUnreachable, stack.ControlPortUnreachable, 0, 2*header.IPv6MinimumSize + header.ICMPv6DstUnreachableMinimumSize + 8},
- }
- r, err := buildIPv6Route(
- localIPv6Addr,
- "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa",
- )
- if err != nil {
- t.Fatal(err)
- }
- for _, c := range cases {
- t.Run(c.name, func(t *testing.T) {
- s := buildDummyStack(t)
- proto := s.NetworkProtocolInstance(ipv6.ProtocolNumber)
- nic := testInterface{
- testObject: testObject{
- t: t,
- },
- }
- ep := proto.NewEndpoint(&nic, nil, nil, &nic.testObject)
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- dataOffset := header.IPv6MinimumSize*2 + header.ICMPv6MinimumSize
- if c.fragmentOffset != nil {
- dataOffset += header.IPv6FragmentHeaderSize
- }
- view := buffer.NewView(dataOffset + 8)
-
- // Create the outer IPv6 header.
- ip := header.IPv6(view)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(len(view) - header.IPv6MinimumSize - c.trunc),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: 20,
- SrcAddr: outerSrcAddr,
- DstAddr: localIPv6Addr,
- })
-
- // Create the ICMP header.
- icmp := header.ICMPv6(view[header.IPv6MinimumSize:])
- icmp.SetType(c.typ)
- icmp.SetCode(c.code)
- icmp.SetIdent(0xdead)
- icmp.SetSequence(0xbeef)
-
- // Create the inner IPv6 header.
- ip = header.IPv6(view[header.IPv6MinimumSize+header.ICMPv6PayloadOffset:])
- ip.Encode(&header.IPv6Fields{
- PayloadLength: 100,
- NextHeader: 10,
- HopLimit: 20,
- SrcAddr: localIPv6Addr,
- DstAddr: remoteIPv6Addr,
- })
-
- // Build the fragmentation header if needed.
- if c.fragmentOffset != nil {
- ip.SetNextHeader(header.IPv6FragmentHeader)
- frag := header.IPv6Fragment(view[2*header.IPv6MinimumSize+header.ICMPv6MinimumSize:])
- frag.Encode(&header.IPv6FragmentFields{
- NextHeader: 10,
- FragmentOffset: *c.fragmentOffset,
- M: true,
- Identification: 0x12345678,
- })
- }
-
- // Make payload be non-zero.
- for i := dataOffset; i < len(view); i++ {
- view[i] = uint8(i)
- }
-
- // Give packet to IPv6 endpoint, dispatcher will validate that
- // it's ok.
- nic.testObject.protocol = 10
- nic.testObject.srcAddr = remoteIPv6Addr
- nic.testObject.dstAddr = localIPv6Addr
- nic.testObject.contents = view[dataOffset:]
- nic.testObject.typ = c.expectedTyp
- nic.testObject.extra = c.expectedExtra
-
- // Set ICMPv6 checksum.
- icmp.SetChecksum(header.ICMPv6Checksum(icmp, outerSrcAddr, localIPv6Addr, buffer.VectorisedView{}))
-
- pkt := truncatedPacket(view, c.trunc, header.IPv6MinimumSize)
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- if want := c.expectedCount; nic.testObject.controlCalls != want {
- t.Fatalf("Bad number of control calls for %q case: got %v, want %v", c.name, nic.testObject.controlCalls, want)
- }
- })
- }
-}
-
-// truncatedPacket returns a PacketBuffer based on a truncated view. If view,
-// after truncation, is large enough to hold a network header, it makes part of
-// view the packet's NetworkHeader and the rest its Data. Otherwise all of view
-// becomes Data.
-func truncatedPacket(view buffer.View, trunc, netHdrLen int) *stack.PacketBuffer {
- v := view[:len(view)-trunc]
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: v.ToVectorisedView(),
- })
- _, _ = pkt.NetworkHeader().Consume(netHdrLen)
- return pkt
-}
-
-func TestWriteHeaderIncludedPacket(t *testing.T) {
- const (
- nicID = 1
- transportProto = 5
-
- dataLen = 4
- optionsLen = 4
- )
-
- dataBuf := [dataLen]byte{1, 2, 3, 4}
- data := dataBuf[:]
-
- ipv4OptionsBuf := [optionsLen]byte{0, 1, 0, 1}
- ipv4Options := ipv4OptionsBuf[:]
-
- ipv6FragmentExtHdrBuf := [header.IPv6FragmentExtHdrLength]byte{transportProto, 0, 62, 4, 1, 2, 3, 4}
- ipv6FragmentExtHdr := ipv6FragmentExtHdrBuf[:]
-
- var ipv6PayloadWithExtHdrBuf [dataLen + header.IPv6FragmentExtHdrLength]byte
- ipv6PayloadWithExtHdr := ipv6PayloadWithExtHdrBuf[:]
- if n := copy(ipv6PayloadWithExtHdr, ipv6FragmentExtHdr); n != len(ipv6FragmentExtHdr) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(ipv6FragmentExtHdr))
- }
- if n := copy(ipv6PayloadWithExtHdr[header.IPv6FragmentExtHdrLength:], data); n != len(data) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
- }
-
- tests := []struct {
- name string
- protoFactory stack.NetworkProtocolFactory
- protoNum tcpip.NetworkProtocolNumber
- nicAddr tcpip.Address
- remoteAddr tcpip.Address
- pktGen func(*testing.T, tcpip.Address) buffer.VectorisedView
- checker func(*testing.T, *stack.PacketBuffer, tcpip.Address)
- expectedErr *tcpip.Error
- }{
- {
- name: "IPv4",
- protoFactory: ipv4.NewProtocol,
- protoNum: ipv4.ProtocolNumber,
- nicAddr: localIPv4Addr,
- remoteAddr: remoteIPv4Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- totalLen := header.IPv4MinimumSize + len(data)
- hdr := buffer.NewPrependable(totalLen)
- if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
- }
- ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- Protocol: transportProto,
- TTL: ipv4.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- return hdr.View().ToVectorisedView()
- },
- checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
- if src == header.IPv4Any {
- src = localIPv4Addr
- }
-
- netHdr := pkt.NetworkHeader()
-
- if len(netHdr.View()) != header.IPv4MinimumSize {
- t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), header.IPv4MinimumSize)
- }
-
- checker.IPv4(t, stack.PayloadSince(netHdr),
- checker.SrcAddr(src),
- checker.DstAddr(remoteIPv4Addr),
- checker.IPv4HeaderLength(header.IPv4MinimumSize),
- checker.IPFullLength(uint16(header.IPv4MinimumSize+len(data))),
- checker.IPPayload(data),
- )
- },
- },
- {
- name: "IPv4 with IHL too small",
- protoFactory: ipv4.NewProtocol,
- protoNum: ipv4.ProtocolNumber,
- nicAddr: localIPv4Addr,
- remoteAddr: remoteIPv4Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- totalLen := header.IPv4MinimumSize + len(data)
- hdr := buffer.NewPrependable(totalLen)
- if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
- }
- ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize - 1,
- Protocol: transportProto,
- TTL: ipv4.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- return hdr.View().ToVectorisedView()
- },
- expectedErr: tcpip.ErrMalformedHeader,
- },
- {
- name: "IPv4 too small",
- protoFactory: ipv4.NewProtocol,
- protoNum: ipv4.ProtocolNumber,
- nicAddr: localIPv4Addr,
- remoteAddr: remoteIPv4Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- ip := header.IPv4(make([]byte, header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- Protocol: transportProto,
- TTL: ipv4.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- return buffer.View(ip[:len(ip)-1]).ToVectorisedView()
- },
- expectedErr: tcpip.ErrMalformedHeader,
- },
- {
- name: "IPv4 minimum size",
- protoFactory: ipv4.NewProtocol,
- protoNum: ipv4.ProtocolNumber,
- nicAddr: localIPv4Addr,
- remoteAddr: remoteIPv4Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- ip := header.IPv4(make([]byte, header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- Protocol: transportProto,
- TTL: ipv4.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- return buffer.View(ip).ToVectorisedView()
- },
- checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
- if src == header.IPv4Any {
- src = localIPv4Addr
- }
-
- netHdr := pkt.NetworkHeader()
-
- if len(netHdr.View()) != header.IPv4MinimumSize {
- t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), header.IPv4MinimumSize)
- }
-
- checker.IPv4(t, stack.PayloadSince(netHdr),
- checker.SrcAddr(src),
- checker.DstAddr(remoteIPv4Addr),
- checker.IPv4HeaderLength(header.IPv4MinimumSize),
- checker.IPFullLength(header.IPv4MinimumSize),
- checker.IPPayload(nil),
- )
- },
- },
- {
- name: "IPv4 with options",
- protoFactory: ipv4.NewProtocol,
- protoNum: ipv4.ProtocolNumber,
- nicAddr: localIPv4Addr,
- remoteAddr: remoteIPv4Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- ipHdrLen := header.IPv4MinimumSize + len(ipv4Options)
- totalLen := ipHdrLen + len(data)
- hdr := buffer.NewPrependable(totalLen)
- if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
- }
- ip := header.IPv4(hdr.Prepend(ipHdrLen))
- ip.Encode(&header.IPv4Fields{
- IHL: uint8(ipHdrLen),
- Protocol: transportProto,
- TTL: ipv4.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- if n := copy(ip.Options(), ipv4Options); n != len(ipv4Options) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(ipv4Options))
- }
- return hdr.View().ToVectorisedView()
- },
- checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
- if src == header.IPv4Any {
- src = localIPv4Addr
- }
-
- netHdr := pkt.NetworkHeader()
-
- hdrLen := header.IPv4MinimumSize + len(ipv4Options)
- if len(netHdr.View()) != hdrLen {
- t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), hdrLen)
- }
-
- checker.IPv4(t, stack.PayloadSince(netHdr),
- checker.SrcAddr(src),
- checker.DstAddr(remoteIPv4Addr),
- checker.IPv4HeaderLength(hdrLen),
- checker.IPFullLength(uint16(hdrLen+len(data))),
- checker.IPv4Options(ipv4Options),
- checker.IPPayload(data),
- )
- },
- },
- {
- name: "IPv4 with options and data across views",
- protoFactory: ipv4.NewProtocol,
- protoNum: ipv4.ProtocolNumber,
- nicAddr: localIPv4Addr,
- remoteAddr: remoteIPv4Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- ip := header.IPv4(make([]byte, header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: uint8(header.IPv4MinimumSize + len(ipv4Options)),
- Protocol: transportProto,
- TTL: ipv4.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- vv := buffer.View(ip).ToVectorisedView()
- vv.AppendView(ipv4Options)
- vv.AppendView(data)
- return vv
- },
- checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
- if src == header.IPv4Any {
- src = localIPv4Addr
- }
-
- netHdr := pkt.NetworkHeader()
-
- hdrLen := header.IPv4MinimumSize + len(ipv4Options)
- if len(netHdr.View()) != hdrLen {
- t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), hdrLen)
- }
-
- checker.IPv4(t, stack.PayloadSince(netHdr),
- checker.SrcAddr(src),
- checker.DstAddr(remoteIPv4Addr),
- checker.IPv4HeaderLength(hdrLen),
- checker.IPFullLength(uint16(hdrLen+len(data))),
- checker.IPv4Options(ipv4Options),
- checker.IPPayload(data),
- )
- },
- },
- {
- name: "IPv6",
- protoFactory: ipv6.NewProtocol,
- protoNum: ipv6.ProtocolNumber,
- nicAddr: localIPv6Addr,
- remoteAddr: remoteIPv6Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- totalLen := header.IPv6MinimumSize + len(data)
- hdr := buffer.NewPrependable(totalLen)
- if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
- }
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- NextHeader: transportProto,
- HopLimit: ipv6.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- return hdr.View().ToVectorisedView()
- },
- checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
- if src == header.IPv6Any {
- src = localIPv6Addr
- }
-
- netHdr := pkt.NetworkHeader()
-
- if len(netHdr.View()) != header.IPv6MinimumSize {
- t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), header.IPv6MinimumSize)
- }
-
- checker.IPv6(t, stack.PayloadSince(netHdr),
- checker.SrcAddr(src),
- checker.DstAddr(remoteIPv6Addr),
- checker.IPFullLength(uint16(header.IPv6MinimumSize+len(data))),
- checker.IPPayload(data),
- )
- },
- },
- {
- name: "IPv6 with extension header",
- protoFactory: ipv6.NewProtocol,
- protoNum: ipv6.ProtocolNumber,
- nicAddr: localIPv6Addr,
- remoteAddr: remoteIPv6Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- totalLen := header.IPv6MinimumSize + len(ipv6FragmentExtHdr) + len(data)
- hdr := buffer.NewPrependable(totalLen)
- if n := copy(hdr.Prepend(len(data)), data); n != len(data) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(data))
- }
- if n := copy(hdr.Prepend(len(ipv6FragmentExtHdr)), ipv6FragmentExtHdr); n != len(ipv6FragmentExtHdr) {
- t.Fatalf("copied %d bytes, expected %d bytes", n, len(ipv6FragmentExtHdr))
- }
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- NextHeader: uint8(header.IPv6FragmentExtHdrIdentifier),
- HopLimit: ipv6.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- return hdr.View().ToVectorisedView()
- },
- checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
- if src == header.IPv6Any {
- src = localIPv6Addr
- }
-
- netHdr := pkt.NetworkHeader()
-
- if want := header.IPv6MinimumSize + len(ipv6FragmentExtHdr); len(netHdr.View()) != want {
- t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), want)
- }
-
- checker.IPv6(t, stack.PayloadSince(netHdr),
- checker.SrcAddr(src),
- checker.DstAddr(remoteIPv6Addr),
- checker.IPFullLength(uint16(header.IPv6MinimumSize+len(ipv6PayloadWithExtHdr))),
- checker.IPPayload(ipv6PayloadWithExtHdr),
- )
- },
- },
- {
- name: "IPv6 minimum size",
- protoFactory: ipv6.NewProtocol,
- protoNum: ipv6.ProtocolNumber,
- nicAddr: localIPv6Addr,
- remoteAddr: remoteIPv6Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- ip := header.IPv6(make([]byte, header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- NextHeader: transportProto,
- HopLimit: ipv6.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- return buffer.View(ip).ToVectorisedView()
- },
- checker: func(t *testing.T, pkt *stack.PacketBuffer, src tcpip.Address) {
- if src == header.IPv6Any {
- src = localIPv6Addr
- }
-
- netHdr := pkt.NetworkHeader()
-
- if len(netHdr.View()) != header.IPv6MinimumSize {
- t.Errorf("got len(netHdr.View()) = %d, want = %d", len(netHdr.View()), header.IPv6MinimumSize)
- }
-
- checker.IPv6(t, stack.PayloadSince(netHdr),
- checker.SrcAddr(src),
- checker.DstAddr(remoteIPv6Addr),
- checker.IPFullLength(header.IPv6MinimumSize),
- checker.IPPayload(nil),
- )
- },
- },
- {
- name: "IPv6 too small",
- protoFactory: ipv6.NewProtocol,
- protoNum: ipv6.ProtocolNumber,
- nicAddr: localIPv6Addr,
- remoteAddr: remoteIPv6Addr,
- pktGen: func(t *testing.T, src tcpip.Address) buffer.VectorisedView {
- ip := header.IPv6(make([]byte, header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- NextHeader: transportProto,
- HopLimit: ipv6.DefaultTTL,
- SrcAddr: src,
- DstAddr: header.IPv4Any,
- })
- return buffer.View(ip[:len(ip)-1]).ToVectorisedView()
- },
- expectedErr: tcpip.ErrMalformedHeader,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- subTests := []struct {
- name string
- srcAddr tcpip.Address
- }{
- {
- name: "unspecified source",
- srcAddr: tcpip.Address(strings.Repeat("\x00", len(test.nicAddr))),
- },
- {
- name: "random source",
- srcAddr: tcpip.Address(strings.Repeat("\xab", len(test.nicAddr))),
- },
- }
-
- for _, subTest := range subTests {
- t.Run(subTest.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{test.protoFactory},
- })
- e := channel.New(1, 1280, "")
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
- }
- if err := s.AddAddress(nicID, test.protoNum, test.nicAddr); err != nil {
- t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, test.protoNum, test.nicAddr, err)
- }
-
- s.SetRouteTable([]tcpip.Route{{Destination: test.remoteAddr.WithPrefix().Subnet(), NIC: nicID}})
-
- r, err := s.FindRoute(nicID, test.nicAddr, test.remoteAddr, test.protoNum, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("s.FindRoute(%d, %s, %s, %d, false): %s", nicID, test.remoteAddr, test.nicAddr, test.protoNum, err)
- }
- defer r.Release()
-
- if err := r.WriteHeaderIncludedPacket(stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: test.pktGen(t, subTest.srcAddr),
- })); err != test.expectedErr {
- t.Fatalf("got r.WriteHeaderIncludedPacket(_) = %s, want = %s", err, test.expectedErr)
- }
-
- if test.expectedErr != nil {
- return
- }
-
- pkt, ok := e.Read()
- if !ok {
- t.Fatal("expected a packet to be written")
- }
- test.checker(t, pkt.Pkt, subTest.srcAddr)
- })
- }
- })
- }
-}
diff --git a/pkg/tcpip/network/ipv4/BUILD b/pkg/tcpip/network/ipv4/BUILD
deleted file mode 100644
index 6252614ec..000000000
--- a/pkg/tcpip/network/ipv4/BUILD
+++ /dev/null
@@ -1,46 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "ipv4",
- srcs = [
- "icmp.go",
- "ipv4.go",
- ],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/sync",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/header/parse",
- "//pkg/tcpip/network/fragmentation",
- "//pkg/tcpip/network/hash",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "ipv4_test",
- size = "small",
- srcs = ["ipv4_test.go"],
- 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/arp",
- "//pkg/tcpip/network/ipv4",
- "//pkg/tcpip/network/testutil",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/icmp",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- "@com_github_google_go_cmp//cmp:go_default_library",
- ],
-)
diff --git a/pkg/tcpip/network/ipv4/ipv4_state_autogen.go b/pkg/tcpip/network/ipv4/ipv4_state_autogen.go
new file mode 100644
index 000000000..250b2128e
--- /dev/null
+++ b/pkg/tcpip/network/ipv4/ipv4_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package ipv4
diff --git a/pkg/tcpip/network/ipv4/ipv4_test.go b/pkg/tcpip/network/ipv4/ipv4_test.go
deleted file mode 100644
index c7f434591..000000000
--- a/pkg/tcpip/network/ipv4/ipv4_test.go
+++ /dev/null
@@ -1,2531 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ipv4_test
-
-import (
- "context"
- "encoding/hex"
- "fmt"
- "math"
- "net"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "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/arp"
- "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.dev/gvisor/pkg/tcpip/network/testutil"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- extraHeaderReserve = 50
- defaultMTU = 65536
-)
-
-func TestExcludeBroadcast(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
-
- ep := stack.LinkEndpoint(channel.New(256, defaultMTU, ""))
- if testing.Verbose() {
- ep = sniffer.New(ep)
- }
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
-
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv4EmptySubnet,
- NIC: 1,
- }})
-
- randomAddr := tcpip.FullAddress{NIC: 1, Addr: "\x0a\x00\x00\x01", Port: 53}
-
- var wq waiter.Queue
- t.Run("WithoutPrimaryAddress", func(t *testing.T) {
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatal(err)
- }
- defer ep.Close()
-
- // Cannot connect using a broadcast address as the source.
- if err := ep.Connect(randomAddr); err != tcpip.ErrNoRoute {
- t.Errorf("got ep.Connect(...) = %v, want = %v", err, tcpip.ErrNoRoute)
- }
-
- // However, we can bind to a broadcast address to listen.
- if err := ep.Bind(tcpip.FullAddress{Addr: header.IPv4Broadcast, Port: 53, NIC: 1}); err != nil {
- t.Errorf("Bind failed: %v", err)
- }
- })
-
- t.Run("WithPrimaryAddress", func(t *testing.T) {
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
- if err != nil {
- t.Fatal(err)
- }
- defer ep.Close()
-
- // Add a valid primary endpoint address, now we can connect.
- if err := s.AddAddress(1, ipv4.ProtocolNumber, "\x0a\x00\x00\x02"); err != nil {
- t.Fatalf("AddAddress failed: %v", err)
- }
- if err := ep.Connect(randomAddr); err != nil {
- t.Errorf("Connect failed: %v", err)
- }
- })
-}
-
-// TestIPv4Sanity sends IP/ICMP packets with various problems to the stack and
-// checks the response.
-func TestIPv4Sanity(t *testing.T) {
- const (
- ttl = 255
- nicID = 1
- randomSequence = 123
- randomIdent = 42
- )
- var (
- ipv4Addr = tcpip.AddressWithPrefix{
- Address: tcpip.Address(net.ParseIP("192.168.1.58").To4()),
- PrefixLen: 24,
- }
- remoteIPv4Addr = tcpip.Address(net.ParseIP("10.0.0.1").To4())
- )
-
- tests := []struct {
- name string
- headerLength uint8 // value of 0 means "use correct size"
- badHeaderChecksum bool
- maxTotalLength uint16
- transportProtocol uint8
- TTL uint8
- options []byte
- replyOptions []byte // if succeeds, reply should look like this
- shouldFail bool
- expectErrorICMP bool
- ICMPType header.ICMPv4Type
- ICMPCode header.ICMPv4Code
- paramProblemPointer uint8
- }{
- {
- name: "valid no options",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- },
- {
- name: "bad header checksum",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- badHeaderChecksum: true,
- shouldFail: true,
- },
- // The TTL tests check that we are not rejecting an incoming packet
- // with a zero or one TTL, which has been a point of confusion in the
- // past as RFC 791 says: "If this field contains the value zero, then the
- // datagram must be destroyed". However RFC 1122 section 3.2.1.7 clarifies
- // for the case of the destination host, stating as follows.
- //
- // A host MUST NOT send a datagram with a Time-to-Live (TTL)
- // value of zero.
- //
- // A host MUST NOT discard a datagram just because it was
- // received with TTL less than 2.
- {
- name: "zero TTL",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: 0,
- },
- {
- name: "one TTL",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: 1,
- },
- {
- name: "End options",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{0, 0, 0, 0},
- replyOptions: []byte{0, 0, 0, 0},
- },
- {
- name: "NOP options",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{1, 1, 1, 1},
- replyOptions: []byte{1, 1, 1, 1},
- },
- {
- name: "NOP and End options",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{1, 1, 0, 0},
- replyOptions: []byte{1, 1, 0, 0},
- },
- {
- name: "bad header length",
- headerLength: header.IPv4MinimumSize - 1,
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- shouldFail: true,
- },
- {
- name: "bad total length (0)",
- maxTotalLength: 0,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- shouldFail: true,
- },
- {
- name: "bad total length (ip - 1)",
- maxTotalLength: uint16(header.IPv4MinimumSize - 1),
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- shouldFail: true,
- },
- {
- name: "bad total length (ip + icmp - 1)",
- maxTotalLength: uint16(header.IPv4MinimumSize + header.ICMPv4MinimumSize - 1),
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- shouldFail: true,
- },
- {
- name: "bad protocol",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: 99,
- TTL: ttl,
- shouldFail: true,
- expectErrorICMP: true,
- ICMPType: header.ICMPv4DstUnreachable,
- ICMPCode: header.ICMPv4ProtoUnreachable,
- },
- {
- name: "timestamp option overflow",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 12, 13, 0x11,
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- },
- replyOptions: []byte{
- 68, 12, 13, 0x21,
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- },
- },
- {
- name: "timestamp option overflow full",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 12, 13, 0xF1,
- // ^ Counter full (15/0xF)
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- },
- shouldFail: true,
- expectErrorICMP: true,
- ICMPType: header.ICMPv4ParamProblem,
- ICMPCode: header.ICMPv4UnusedCode,
- paramProblemPointer: header.IPv4MinimumSize + 3,
- replyOptions: []byte{},
- },
- {
- name: "unknown option",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{10, 4, 9, 0},
- // ^^
- // The unknown option should be stripped out of the reply.
- replyOptions: []byte{},
- },
- {
- name: "bad option - length 0",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 0, 9, 0,
- // ^
- 1, 2, 3, 4,
- },
- shouldFail: true,
- },
- {
- name: "bad option - length big",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 9, 9, 0,
- // ^
- // There are only 8 bytes allocated to options so 9 bytes of timestamp
- // space is not possible. (Second byte)
- 1, 2, 3, 4,
- },
- shouldFail: true,
- },
- {
- // This tests for some linux compatible behaviour.
- // The ICMP pointer returned is 22 for Linux but the
- // error is actually in spot 21.
- name: "bad option - length bad",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- // Timestamps are in multiples of 4 or 8 but never 7.
- // The option space should be padded out.
- options: []byte{
- 68, 7, 5, 0,
- // ^ ^ Linux points here which is wrong.
- // | Not a multiple of 4
- 1, 2, 3,
- },
- shouldFail: true,
- expectErrorICMP: true,
- ICMPType: header.ICMPv4ParamProblem,
- ICMPCode: header.ICMPv4UnusedCode,
- paramProblemPointer: header.IPv4MinimumSize + 2,
- },
- {
- name: "multiple type 0 with room",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 24, 21, 0x00,
- 1, 2, 3, 4,
- 5, 6, 7, 8,
- 9, 10, 11, 12,
- 13, 14, 15, 16,
- 0, 0, 0, 0,
- },
- replyOptions: []byte{
- 68, 24, 25, 0x00,
- 1, 2, 3, 4,
- 5, 6, 7, 8,
- 9, 10, 11, 12,
- 13, 14, 15, 16,
- 0x00, 0xad, 0x1c, 0x40, // time we expect from fakeclock
- },
- },
- {
- // The timestamp area is full so add to the overflow count.
- name: "multiple type 1 timestamps",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 20, 21, 0x11,
- // ^
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- 192, 168, 1, 13,
- 5, 6, 7, 8,
- },
- // Overflow count is the top nibble of the 4th byte.
- replyOptions: []byte{
- 68, 20, 21, 0x21,
- // ^
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- 192, 168, 1, 13,
- 5, 6, 7, 8,
- },
- },
- {
- name: "multiple type 1 timestamps with room",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 28, 21, 0x01,
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- 192, 168, 1, 13,
- 5, 6, 7, 8,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- },
- replyOptions: []byte{
- 68, 28, 29, 0x01,
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- 192, 168, 1, 13,
- 5, 6, 7, 8,
- 192, 168, 1, 58, // New IP Address.
- 0x00, 0xad, 0x1c, 0x40, // time we expect from fakeclock
- },
- },
- {
- // Needs 8 bytes for a type 1 timestamp but there are only 4 free.
- name: "bad timer element alignment",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 20, 17, 0x01,
- // ^^ ^^ 20 byte area, next free spot at 17.
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- },
- shouldFail: true,
- expectErrorICMP: true,
- ICMPType: header.ICMPv4ParamProblem,
- ICMPCode: header.ICMPv4UnusedCode,
- paramProblemPointer: header.IPv4MinimumSize + 2,
- },
- // End of option list with illegal option after it, which should be ignored.
- {
- name: "end of options list",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 68, 12, 13, 0x11,
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- 0, 10, 3, 99,
- },
- replyOptions: []byte{
- 68, 12, 13, 0x21,
- 192, 168, 1, 12,
- 1, 2, 3, 4,
- 0, 0, 0, 0, // 3 bytes unknown option
- }, // ^ End of options hides following bytes.
- },
- {
- // Timestamp with a size too small.
- name: "timestamp truncated",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{68, 1, 0, 0},
- // ^ Smallest possible is 8.
- shouldFail: true,
- },
- {
- name: "single record route with room",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 7, 7, 4, // 3 byte header
- 0, 0, 0, 0,
- 0,
- },
- replyOptions: []byte{
- 7, 7, 8, // 3 byte header
- 192, 168, 1, 58, // New IP Address.
- 0, // padding to multiple of 4 bytes.
- },
- },
- {
- name: "multiple record route with room",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 7, 23, 20, // 3 byte header
- 1, 2, 3, 4,
- 5, 6, 7, 8,
- 9, 10, 11, 12,
- 13, 14, 15, 16,
- 0, 0, 0, 0,
- 0,
- },
- replyOptions: []byte{
- 7, 23, 24,
- 1, 2, 3, 4,
- 5, 6, 7, 8,
- 9, 10, 11, 12,
- 13, 14, 15, 16,
- 192, 168, 1, 58, // New IP Address.
- 0, // padding to multiple of 4 bytes.
- },
- },
- {
- name: "single record route with no room",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 7, 7, 8, // 3 byte header
- 1, 2, 3, 4,
- 0,
- },
- replyOptions: []byte{
- 7, 7, 8, // 3 byte header
- 1, 2, 3, 4,
- 0, // padding to multiple of 4 bytes.
- },
- },
- {
- // Unlike timestamp, this should just succeed.
- name: "multiple record route with no room",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 7, 23, 24, // 3 byte header
- 1, 2, 3, 4,
- 5, 6, 7, 8,
- 9, 10, 11, 12,
- 13, 14, 15, 16,
- 17, 18, 19, 20,
- 0,
- },
- replyOptions: []byte{
- 7, 23, 24,
- 1, 2, 3, 4,
- 5, 6, 7, 8,
- 9, 10, 11, 12,
- 13, 14, 15, 16,
- 17, 18, 19, 20,
- 0, // padding to multiple of 4 bytes.
- },
- },
- {
- // Confirm linux bug for bug compatibility.
- // Linux returns slot 22 but the error is in slot 21.
- name: "multiple record route with not enough room",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 7, 8, 8, // 3 byte header
- // ^ ^ Linux points here. We must too.
- // | Not enough room. 1 byte free, need 4.
- 1, 2, 3, 4,
- 0,
- },
- shouldFail: true,
- expectErrorICMP: true,
- ICMPType: header.ICMPv4ParamProblem,
- ICMPCode: header.ICMPv4UnusedCode,
- paramProblemPointer: header.IPv4MinimumSize + 2,
- replyOptions: []byte{},
- },
- {
- name: "duplicate record route",
- maxTotalLength: ipv4.MaxTotalSize,
- transportProtocol: uint8(header.ICMPv4ProtocolNumber),
- TTL: ttl,
- options: []byte{
- 7, 7, 8, // 3 byte header
- 1, 2, 3, 4,
- 7, 7, 8, // 3 byte header
- 1, 2, 3, 4,
- 0, 0, // pad
- },
- shouldFail: true,
- expectErrorICMP: true,
- ICMPType: header.ICMPv4ParamProblem,
- ICMPCode: header.ICMPv4UnusedCode,
- paramProblemPointer: header.IPv4MinimumSize + 7,
- replyOptions: []byte{},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4},
- Clock: clock,
- })
- // We expect at most a single packet in response to our ICMP Echo Request.
- e := channel.New(1, ipv4.MaxTotalSize, "")
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
- }
- ipv4ProtoAddr := tcpip.ProtocolAddress{Protocol: header.IPv4ProtocolNumber, AddressWithPrefix: ipv4Addr}
- if err := s.AddProtocolAddress(nicID, ipv4ProtoAddr); err != nil {
- t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID, ipv4ProtoAddr, err)
- }
- // Advance the clock by some unimportant amount to make
- // sure it's all set up.
- clock.Advance(time.Millisecond * 0x10203040)
-
- // Default routes for IPv4 so ICMP can find a route to the remote
- // node when attempting to send the ICMP Echo Reply.
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: header.IPv4EmptySubnet,
- NIC: nicID,
- },
- })
-
- // Round up the header size to the next multiple of 4 as RFC 791, page 11
- // says: "Internet Header Length is the length of the internet header
- // in 32 bit words..." and on page 23: "The internet header padding is
- // used to ensure that the internet header ends on a 32 bit boundary."
- ipHeaderLength := ((header.IPv4MinimumSize + len(test.options)) + header.IPv4IHLStride - 1) & ^(header.IPv4IHLStride - 1)
-
- if ipHeaderLength > header.IPv4MaximumHeaderSize {
- t.Fatalf("too many bytes in options: got = %d, want <= %d ", ipHeaderLength, header.IPv4MaximumHeaderSize)
- }
- totalLen := uint16(ipHeaderLength + header.ICMPv4MinimumSize)
- hdr := buffer.NewPrependable(int(totalLen))
- icmp := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
-
- // Specify ident/seq to make sure we get the same in the response.
- icmp.SetIdent(randomIdent)
- icmp.SetSequence(randomSequence)
- icmp.SetType(header.ICMPv4Echo)
- icmp.SetCode(header.ICMPv4UnusedCode)
- icmp.SetChecksum(0)
- icmp.SetChecksum(^header.Checksum(icmp, 0))
- ip := header.IPv4(hdr.Prepend(ipHeaderLength))
- if test.maxTotalLength < totalLen {
- totalLen = test.maxTotalLength
- }
- ip.Encode(&header.IPv4Fields{
- IHL: uint8(ipHeaderLength),
- TotalLength: totalLen,
- Protocol: test.transportProtocol,
- TTL: test.TTL,
- SrcAddr: remoteIPv4Addr,
- DstAddr: ipv4Addr.Address,
- })
- if n := copy(ip.Options(), test.options); n != len(test.options) {
- t.Fatalf("options larger than available space: copied %d/%d bytes", n, len(test.options))
- }
- // Override the correct value if the test case specified one.
- if test.headerLength != 0 {
- ip.SetHeaderLength(test.headerLength)
- }
- ip.SetChecksum(0)
- ipHeaderChecksum := ip.CalculateChecksum()
- if test.badHeaderChecksum {
- ipHeaderChecksum += 42
- }
- ip.SetChecksum(^ipHeaderChecksum)
- requestPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- })
- e.InjectInbound(header.IPv4ProtocolNumber, requestPkt)
- reply, ok := e.Read()
- if !ok {
- if test.shouldFail {
- if test.expectErrorICMP {
- t.Fatalf("ICMP error response (type %d, code %d) missing", test.ICMPType, test.ICMPCode)
- }
- return // Expected silent failure.
- }
- t.Fatal("expected ICMP echo reply missing")
- }
-
- // We didn't expect a packet. Register our surprise but carry on to
- // provide more information about what we got.
- if test.shouldFail && !test.expectErrorICMP {
- t.Error("unexpected packet response")
- }
-
- // Check the route that brought the packet to us.
- if reply.Route.LocalAddress != ipv4Addr.Address {
- t.Errorf("got pkt.Route.LocalAddress = %s, want = %s", reply.Route.LocalAddress, ipv4Addr.Address)
- }
- if reply.Route.RemoteAddress != remoteIPv4Addr {
- t.Errorf("got pkt.Route.RemoteAddress = %s, want = %s", reply.Route.RemoteAddress, remoteIPv4Addr)
- }
-
- // Make sure it's all in one buffer for checker.
- replyIPHeader := header.IPv4(stack.PayloadSince(reply.Pkt.NetworkHeader()))
-
- // At this stage we only know it's probably an IP+ICMP header so verify
- // that much.
- checker.IPv4(t, replyIPHeader,
- checker.SrcAddr(ipv4Addr.Address),
- checker.DstAddr(remoteIPv4Addr),
- checker.ICMPv4(
- checker.ICMPv4Checksum(),
- ),
- )
-
- // Don't proceed any further if the checker found problems.
- if t.Failed() {
- t.FailNow()
- }
-
- // OK it's ICMP. We can safely look at the type now.
- replyICMPHeader := header.ICMPv4(replyIPHeader.Payload())
- switch replyICMPHeader.Type() {
- case header.ICMPv4ParamProblem:
- if !test.shouldFail {
- t.Fatalf("got Parameter Problem with pointer %d, wanted Echo Reply", replyICMPHeader.Pointer())
- }
- if !test.expectErrorICMP {
- t.Fatalf("got Parameter Problem with pointer %d, wanted no response", replyICMPHeader.Pointer())
- }
- checker.IPv4(t, replyIPHeader,
- checker.IPFullLength(uint16(header.IPv4MinimumSize+header.ICMPv4MinimumSize+requestPkt.Size())),
- checker.IPv4HeaderLength(header.IPv4MinimumSize),
- checker.ICMPv4(
- checker.ICMPv4Type(test.ICMPType),
- checker.ICMPv4Code(test.ICMPCode),
- checker.ICMPv4Pointer(test.paramProblemPointer),
- checker.ICMPv4Payload([]byte(hdr.View())),
- ),
- )
- return
- case header.ICMPv4DstUnreachable:
- if !test.shouldFail {
- t.Fatalf("got ICMP error packet type %d, code %d, wanted Echo Reply",
- header.ICMPv4DstUnreachable, replyICMPHeader.Code())
- }
- if !test.expectErrorICMP {
- t.Fatalf("got ICMP error packet type %d, code %d, wanted no response",
- header.ICMPv4DstUnreachable, replyICMPHeader.Code())
- }
- checker.IPv4(t, replyIPHeader,
- checker.IPFullLength(uint16(header.IPv4MinimumSize+header.ICMPv4MinimumSize+requestPkt.Size())),
- checker.IPv4HeaderLength(header.IPv4MinimumSize),
- checker.ICMPv4(
- checker.ICMPv4Type(test.ICMPType),
- checker.ICMPv4Code(test.ICMPCode),
- checker.ICMPv4Payload([]byte(hdr.View())),
- ),
- )
- return
- case header.ICMPv4EchoReply:
- if test.shouldFail {
- if !test.expectErrorICMP {
- t.Error("got Echo Reply packet, want no response")
- } else {
- t.Errorf("got Echo Reply, want ICMP error type %d, code %d", test.ICMPType, test.ICMPCode)
- }
- }
- // If the IP options change size then the packet will change size, so
- // some IP header fields will need to be adjusted for the checks.
- sizeChange := len(test.replyOptions) - len(test.options)
-
- checker.IPv4(t, replyIPHeader,
- checker.IPv4HeaderLength(ipHeaderLength+sizeChange),
- checker.IPv4Options(test.replyOptions),
- checker.IPFullLength(uint16(requestPkt.Size()+sizeChange)),
- checker.ICMPv4(
- checker.ICMPv4Checksum(),
- checker.ICMPv4Code(header.ICMPv4UnusedCode),
- checker.ICMPv4Seq(randomSequence),
- checker.ICMPv4Ident(randomIdent),
- ),
- )
- default:
- t.Fatalf("unexpected ICMP response, got type %d, want = %d, %d or %d",
- replyICMPHeader.Type(), header.ICMPv4EchoReply, header.ICMPv4DstUnreachable, header.ICMPv4ParamProblem)
- }
- })
- }
-}
-
-// comparePayloads compared the contents of all the packets against the contents
-// of the source packet.
-func compareFragments(packets []*stack.PacketBuffer, sourcePacket *stack.PacketBuffer, mtu uint32, wantFragments []fragmentInfo, proto tcpip.TransportProtocolNumber) error {
- // Make a complete array of the sourcePacket packet.
- source := header.IPv4(packets[0].NetworkHeader().View())
- vv := buffer.NewVectorisedView(sourcePacket.Size(), sourcePacket.Views())
- source = append(source, vv.ToView()...)
-
- // Make a copy of the IP header, which will be modified in some fields to make
- // an expected header.
- sourceCopy := header.IPv4(append(buffer.View(nil), source[:source.HeaderLength()]...))
- sourceCopy.SetChecksum(0)
- sourceCopy.SetFlagsFragmentOffset(0, 0)
- sourceCopy.SetTotalLength(0)
- // Build up an array of the bytes sent.
- var reassembledPayload buffer.VectorisedView
- for i, packet := range packets {
- // Confirm that the packet is valid.
- allBytes := buffer.NewVectorisedView(packet.Size(), packet.Views())
- fragmentIPHeader := header.IPv4(allBytes.ToView())
- if !fragmentIPHeader.IsValid(len(fragmentIPHeader)) {
- return fmt.Errorf("fragment #%d: IP packet is invalid:\n%s", i, hex.Dump(fragmentIPHeader))
- }
- if got := len(fragmentIPHeader); got > int(mtu) {
- return fmt.Errorf("fragment #%d: got len(fragmentIPHeader) = %d, want <= %d", i, got, mtu)
- }
- if got := fragmentIPHeader.TransportProtocol(); got != proto {
- return fmt.Errorf("fragment #%d: got fragmentIPHeader.TransportProtocol() = %d, want = %d", i, got, uint8(proto))
- }
- if got := packet.AvailableHeaderBytes(); got != extraHeaderReserve {
- return fmt.Errorf("fragment #%d: got packet.AvailableHeaderBytes() = %d, want = %d", i, got, extraHeaderReserve)
- }
- if got, want := packet.NetworkProtocolNumber, sourcePacket.NetworkProtocolNumber; got != want {
- return fmt.Errorf("fragment #%d: got fragment.NetworkProtocolNumber = %d, want = %d", i, got, want)
- }
- if got, want := fragmentIPHeader.CalculateChecksum(), uint16(0xffff); got != want {
- return fmt.Errorf("fragment #%d: got ip.CalculateChecksum() = %#x, want = %#x", i, got, want)
- }
- if wantFragments[i].more {
- sourceCopy.SetFlagsFragmentOffset(sourceCopy.Flags()|header.IPv4FlagMoreFragments, wantFragments[i].offset)
- } else {
- sourceCopy.SetFlagsFragmentOffset(sourceCopy.Flags()&^header.IPv4FlagMoreFragments, wantFragments[i].offset)
- }
- reassembledPayload.AppendView(packet.TransportHeader().View())
- reassembledPayload.Append(packet.Data)
- // Clear out the checksum and length from the ip because we can't compare
- // it.
- sourceCopy.SetTotalLength(wantFragments[i].payloadSize + header.IPv4MinimumSize)
- sourceCopy.SetChecksum(0)
- sourceCopy.SetChecksum(^sourceCopy.CalculateChecksum())
- if diff := cmp.Diff(fragmentIPHeader[:fragmentIPHeader.HeaderLength()], sourceCopy[:sourceCopy.HeaderLength()]); diff != "" {
- return fmt.Errorf("fragment #%d: fragmentIPHeader mismatch (-want +got):\n%s", i, diff)
- }
- }
-
- expected := buffer.View(source[source.HeaderLength():])
- if diff := cmp.Diff(expected, reassembledPayload.ToView()); diff != "" {
- return fmt.Errorf("reassembledPayload mismatch (-want +got):\n%s", diff)
- }
-
- return nil
-}
-
-type fragmentInfo struct {
- offset uint16
- more bool
- payloadSize uint16
-}
-
-var fragmentationTests = []struct {
- description string
- mtu uint32
- gso *stack.GSO
- transportHeaderLength int
- payloadSize int
- wantFragments []fragmentInfo
-}{
- {
- description: "No fragmentation",
- mtu: 1280,
- gso: nil,
- transportHeaderLength: 0,
- payloadSize: 1000,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1000, more: false},
- },
- },
- {
- description: "Fragmented",
- mtu: 1280,
- gso: nil,
- transportHeaderLength: 0,
- payloadSize: 2000,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1256, more: true},
- {offset: 1256, payloadSize: 744, more: false},
- },
- },
- {
- description: "Fragmented with the minimum mtu",
- mtu: header.IPv4MinimumMTU,
- gso: nil,
- transportHeaderLength: 0,
- payloadSize: 100,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 48, more: true},
- {offset: 48, payloadSize: 48, more: true},
- {offset: 96, payloadSize: 4, more: false},
- },
- },
- {
- description: "Fragmented with mtu not a multiple of 8",
- mtu: header.IPv4MinimumMTU + 1,
- gso: nil,
- transportHeaderLength: 0,
- payloadSize: 100,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 48, more: true},
- {offset: 48, payloadSize: 48, more: true},
- {offset: 96, payloadSize: 4, more: false},
- },
- },
- {
- description: "No fragmentation with big header",
- mtu: 2000,
- gso: nil,
- transportHeaderLength: 100,
- payloadSize: 1000,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1100, more: false},
- },
- },
- {
- description: "Fragmented with gso none",
- mtu: 1280,
- gso: &stack.GSO{Type: stack.GSONone},
- transportHeaderLength: 0,
- payloadSize: 1400,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1256, more: true},
- {offset: 1256, payloadSize: 144, more: false},
- },
- },
- {
- description: "Fragmented with big header",
- mtu: 1280,
- gso: nil,
- transportHeaderLength: 100,
- payloadSize: 1200,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1256, more: true},
- {offset: 1256, payloadSize: 44, more: false},
- },
- },
- {
- description: "Fragmented with MTU smaller than header",
- mtu: 300,
- gso: nil,
- transportHeaderLength: 1000,
- payloadSize: 500,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 280, more: true},
- {offset: 280, payloadSize: 280, more: true},
- {offset: 560, payloadSize: 280, more: true},
- {offset: 840, payloadSize: 280, more: true},
- {offset: 1120, payloadSize: 280, more: true},
- {offset: 1400, payloadSize: 100, more: false},
- },
- },
-}
-
-func TestFragmentationWritePacket(t *testing.T) {
- const ttl = 42
-
- for _, ft := range fragmentationTests {
- t.Run(ft.description, func(t *testing.T) {
- ep := testutil.NewMockLinkEndpoint(ft.mtu, nil, math.MaxInt32)
- r := buildRoute(t, ep)
- pkt := testutil.MakeRandPkt(ft.transportHeaderLength, extraHeaderReserve+header.IPv4MinimumSize, []int{ft.payloadSize}, header.IPv4ProtocolNumber)
- source := pkt.Clone()
- err := r.WritePacket(ft.gso, stack.NetworkHeaderParams{
- Protocol: tcp.ProtocolNumber,
- TTL: ttl,
- TOS: stack.DefaultTOS,
- }, pkt)
- if err != nil {
- t.Fatalf("r.WritePacket(_, _, _) = %s", err)
- }
- if got := len(ep.WrittenPackets); got != len(ft.wantFragments) {
- t.Errorf("got len(ep.WrittenPackets) = %d, want = %d", got, len(ft.wantFragments))
- }
- if got := int(r.Stats().IP.PacketsSent.Value()); got != len(ft.wantFragments) {
- t.Errorf("got c.Route.Stats().IP.PacketsSent.Value() = %d, want = %d", got, len(ft.wantFragments))
- }
- if got := r.Stats().IP.OutgoingPacketErrors.Value(); got != 0 {
- t.Errorf("got r.Stats().IP.OutgoingPacketErrors.Value() = %d, want = 0", got)
- }
- if err := compareFragments(ep.WrittenPackets, source, ft.mtu, ft.wantFragments, tcp.ProtocolNumber); err != nil {
- t.Error(err)
- }
- })
- }
-}
-
-func TestFragmentationWritePackets(t *testing.T) {
- const ttl = 42
- writePacketsTests := []struct {
- description string
- insertBefore int
- insertAfter int
- }{
- {
- description: "Single packet",
- insertBefore: 0,
- insertAfter: 0,
- },
- {
- description: "With packet before",
- insertBefore: 1,
- insertAfter: 0,
- },
- {
- description: "With packet after",
- insertBefore: 0,
- insertAfter: 1,
- },
- {
- description: "With packet before and after",
- insertBefore: 1,
- insertAfter: 1,
- },
- }
- tinyPacket := testutil.MakeRandPkt(header.TCPMinimumSize, extraHeaderReserve+header.IPv4MinimumSize, []int{1}, header.IPv4ProtocolNumber)
-
- for _, test := range writePacketsTests {
- t.Run(test.description, func(t *testing.T) {
- for _, ft := range fragmentationTests {
- t.Run(ft.description, func(t *testing.T) {
- var pkts stack.PacketBufferList
- for i := 0; i < test.insertBefore; i++ {
- pkts.PushBack(tinyPacket.Clone())
- }
- pkt := testutil.MakeRandPkt(ft.transportHeaderLength, extraHeaderReserve+header.IPv4MinimumSize, []int{ft.payloadSize}, header.IPv4ProtocolNumber)
- pkts.PushBack(pkt.Clone())
- for i := 0; i < test.insertAfter; i++ {
- pkts.PushBack(tinyPacket.Clone())
- }
-
- ep := testutil.NewMockLinkEndpoint(ft.mtu, nil, math.MaxInt32)
- r := buildRoute(t, ep)
-
- wantTotalPackets := len(ft.wantFragments) + test.insertBefore + test.insertAfter
- n, err := r.WritePackets(ft.gso, pkts, stack.NetworkHeaderParams{
- Protocol: tcp.ProtocolNumber,
- TTL: ttl,
- TOS: stack.DefaultTOS,
- })
- if err != nil {
- t.Errorf("got WritePackets(_, _, _) = (_, %s), want = (_, nil)", err)
- }
- if n != wantTotalPackets {
- t.Errorf("got WritePackets(_, _, _) = (%d, _), want = (%d, _)", n, wantTotalPackets)
- }
- if got := len(ep.WrittenPackets); got != wantTotalPackets {
- t.Errorf("got len(ep.WrittenPackets) = %d, want = %d", got, wantTotalPackets)
- }
- if got := int(r.Stats().IP.PacketsSent.Value()); got != wantTotalPackets {
- t.Errorf("got c.Route.Stats().IP.PacketsSent.Value() = %d, want = %d", got, wantTotalPackets)
- }
- if got := int(r.Stats().IP.OutgoingPacketErrors.Value()); got != 0 {
- t.Errorf("got r.Stats().IP.OutgoingPacketErrors.Value() = %d, want = 0", got)
- }
-
- if wantTotalPackets == 0 {
- return
- }
-
- fragments := ep.WrittenPackets[test.insertBefore : len(ft.wantFragments)+test.insertBefore]
- if err := compareFragments(fragments, pkt, ft.mtu, ft.wantFragments, tcp.ProtocolNumber); err != nil {
- t.Error(err)
- }
- })
- }
- })
- }
-}
-
-// TestFragmentationErrors checks that errors are returned from WritePacket
-// correctly.
-func TestFragmentationErrors(t *testing.T) {
- const ttl = 42
-
- tests := []struct {
- description string
- mtu uint32
- transportHeaderLength int
- payloadSize int
- allowPackets int
- outgoingErrors int
- mockError *tcpip.Error
- wantError *tcpip.Error
- }{
- {
- description: "No frag",
- mtu: 2000,
- payloadSize: 1000,
- transportHeaderLength: 0,
- allowPackets: 0,
- outgoingErrors: 1,
- mockError: tcpip.ErrAborted,
- wantError: tcpip.ErrAborted,
- },
- {
- description: "Error on first frag",
- mtu: 500,
- payloadSize: 1000,
- transportHeaderLength: 0,
- allowPackets: 0,
- outgoingErrors: 3,
- mockError: tcpip.ErrAborted,
- wantError: tcpip.ErrAborted,
- },
- {
- description: "Error on second frag",
- mtu: 500,
- payloadSize: 1000,
- transportHeaderLength: 0,
- allowPackets: 1,
- outgoingErrors: 2,
- mockError: tcpip.ErrAborted,
- wantError: tcpip.ErrAborted,
- },
- {
- description: "Error on first frag MTU smaller than header",
- mtu: 500,
- transportHeaderLength: 1000,
- payloadSize: 500,
- allowPackets: 0,
- outgoingErrors: 4,
- mockError: tcpip.ErrAborted,
- wantError: tcpip.ErrAborted,
- },
- {
- description: "Error when MTU is smaller than IPv4 minimum MTU",
- mtu: header.IPv4MinimumMTU - 1,
- transportHeaderLength: 0,
- payloadSize: 500,
- allowPackets: 0,
- outgoingErrors: 1,
- mockError: nil,
- wantError: tcpip.ErrInvalidEndpointState,
- },
- }
-
- for _, ft := range tests {
- t.Run(ft.description, func(t *testing.T) {
- pkt := testutil.MakeRandPkt(ft.transportHeaderLength, extraHeaderReserve+header.IPv4MinimumSize, []int{ft.payloadSize}, header.IPv4ProtocolNumber)
- ep := testutil.NewMockLinkEndpoint(ft.mtu, ft.mockError, ft.allowPackets)
- r := buildRoute(t, ep)
- err := r.WritePacket(&stack.GSO{}, stack.NetworkHeaderParams{
- Protocol: tcp.ProtocolNumber,
- TTL: ttl,
- TOS: stack.DefaultTOS,
- }, pkt)
- if err != ft.wantError {
- t.Errorf("got WritePacket(_, _, _) = %s, want = %s", err, ft.wantError)
- }
- if got := int(r.Stats().IP.PacketsSent.Value()); got != ft.allowPackets {
- t.Errorf("got r.Stats().IP.PacketsSent.Value() = %d, want = %d", got, ft.allowPackets)
- }
- if got := int(r.Stats().IP.OutgoingPacketErrors.Value()); got != ft.outgoingErrors {
- t.Errorf("got r.Stats().IP.OutgoingPacketErrors.Value() = %d, want = %d", got, ft.outgoingErrors)
- }
- })
- }
-}
-
-func TestInvalidFragments(t *testing.T) {
- const (
- nicID = 1
- linkAddr = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
- addr1 = "\x0a\x00\x00\x01"
- addr2 = "\x0a\x00\x00\x02"
- tos = 0
- ident = 1
- ttl = 48
- protocol = 6
- )
-
- payloadGen := func(payloadLen int) []byte {
- payload := make([]byte, payloadLen)
- for i := 0; i < len(payload); i++ {
- payload[i] = 0x30
- }
- return payload
- }
-
- type fragmentData struct {
- ipv4fields header.IPv4Fields
- payload []byte
- autoChecksum bool // if true, the Checksum field will be overwritten.
- }
-
- tests := []struct {
- name string
- fragments []fragmentData
- wantMalformedIPPackets uint64
- wantMalformedFragments uint64
- }{
- {
- name: "IHL and TotalLength zero, FragmentOffset non-zero",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: 0,
- TOS: tos,
- TotalLength: 0,
- ID: ident,
- Flags: header.IPv4FlagDontFragment | header.IPv4FlagMoreFragments,
- FragmentOffset: 59776,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(12),
- autoChecksum: true,
- },
- },
- wantMalformedIPPackets: 1,
- wantMalformedFragments: 0,
- },
- {
- name: "IHL and TotalLength zero, FragmentOffset zero",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: 0,
- TOS: tos,
- TotalLength: 0,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(12),
- autoChecksum: true,
- },
- },
- wantMalformedIPPackets: 1,
- wantMalformedFragments: 0,
- },
- {
- // Payload 17 octets and Fragment offset 65520
- // Leading to the fragment end to be past 65536.
- name: "fragment ends past 65536",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 17,
- ID: ident,
- Flags: 0,
- FragmentOffset: 65520,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(17),
- autoChecksum: true,
- },
- },
- wantMalformedIPPackets: 1,
- wantMalformedFragments: 1,
- },
- {
- // Payload 16 octets and fragment offset 65520
- // Leading to the fragment end to be exactly 65536.
- name: "fragment ends exactly at 65536",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 16,
- ID: ident,
- Flags: 0,
- FragmentOffset: 65520,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(16),
- autoChecksum: true,
- },
- },
- wantMalformedIPPackets: 0,
- wantMalformedFragments: 0,
- },
- {
- name: "IHL less than IPv4 minimum size",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize - 12,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 28,
- ID: ident,
- Flags: 0,
- FragmentOffset: 1944,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(28),
- autoChecksum: true,
- },
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize - 12,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize - 12,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(28),
- autoChecksum: true,
- },
- },
- wantMalformedIPPackets: 2,
- wantMalformedFragments: 0,
- },
- {
- name: "fragment with short TotalLength and extra payload",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize + 4,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 28,
- ID: ident,
- Flags: 0,
- FragmentOffset: 28816,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(28),
- autoChecksum: true,
- },
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize + 4,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 4,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(28),
- autoChecksum: true,
- },
- },
- wantMalformedIPPackets: 1,
- wantMalformedFragments: 1,
- },
- {
- name: "multiple fragments with More Fragments flag set to false",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 8,
- ID: ident,
- Flags: 0,
- FragmentOffset: 128,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(8),
- autoChecksum: true,
- },
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 8,
- ID: ident,
- Flags: 0,
- FragmentOffset: 8,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(8),
- autoChecksum: true,
- },
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 8,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: payloadGen(8),
- autoChecksum: true,
- },
- },
- wantMalformedIPPackets: 1,
- wantMalformedFragments: 1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{
- ipv4.NewProtocol,
- },
- })
- e := channel.New(0, 1500, linkAddr)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ipv4.ProtocolNumber, addr2); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv4ProtocolNumber, addr2, err)
- }
-
- for _, f := range test.fragments {
- pktSize := header.IPv4MinimumSize + len(f.payload)
- hdr := buffer.NewPrependable(pktSize)
-
- ip := header.IPv4(hdr.Prepend(pktSize))
- ip.Encode(&f.ipv4fields)
- copy(ip[header.IPv4MinimumSize:], f.payload)
-
- if f.autoChecksum {
- ip.SetChecksum(0)
- ip.SetChecksum(^ip.CalculateChecksum())
- }
-
- vv := hdr.View().ToVectorisedView()
- e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: vv,
- }))
- }
-
- if got, want := s.Stats().IP.MalformedPacketsReceived.Value(), test.wantMalformedIPPackets; got != want {
- t.Errorf("incorrect Stats.IP.MalformedPacketsReceived, got: %d, want: %d", got, want)
- }
- if got, want := s.Stats().IP.MalformedFragmentsReceived.Value(), test.wantMalformedFragments; got != want {
- t.Errorf("incorrect Stats.IP.MalformedFragmentsReceived, got: %d, want: %d", got, want)
- }
- })
- }
-}
-
-func TestFragmentReassemblyTimeout(t *testing.T) {
- const (
- nicID = 1
- linkAddr = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
- addr1 = "\x0a\x00\x00\x01"
- addr2 = "\x0a\x00\x00\x02"
- tos = 0
- ident = 1
- ttl = 48
- protocol = 99
- data = "TEST_FRAGMENT_REASSEMBLY_TIMEOUT"
- )
-
- type fragmentData struct {
- ipv4fields header.IPv4Fields
- payload []byte
- }
-
- tests := []struct {
- name string
- fragments []fragmentData
- expectICMP bool
- }{
- {
- name: "first fragment only",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 16,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: []byte(data)[:16],
- },
- },
- expectICMP: true,
- },
- {
- name: "two first fragments",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 16,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: []byte(data)[:16],
- },
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 16,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: []byte(data)[:16],
- },
- },
- expectICMP: true,
- },
- {
- name: "second fragment only",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16),
- ID: ident,
- Flags: 0,
- FragmentOffset: 8,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: []byte(data)[16:],
- },
- },
- expectICMP: false,
- },
- {
- name: "two fragments with a gap",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 8,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: []byte(data)[:8],
- },
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16),
- ID: ident,
- Flags: 0,
- FragmentOffset: 16,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: []byte(data)[16:],
- },
- },
- expectICMP: true,
- },
- {
- name: "two fragments with a gap in reverse order",
- fragments: []fragmentData{
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16),
- ID: ident,
- Flags: 0,
- FragmentOffset: 16,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: []byte(data)[16:],
- },
- {
- ipv4fields: header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TOS: tos,
- TotalLength: header.IPv4MinimumSize + 8,
- ID: ident,
- Flags: header.IPv4FlagMoreFragments,
- FragmentOffset: 0,
- TTL: ttl,
- Protocol: protocol,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- payload: []byte(data)[:8],
- },
- },
- expectICMP: true,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{
- ipv4.NewProtocol,
- },
- Clock: clock,
- })
- e := channel.New(1, 1500, linkAddr)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ipv4.ProtocolNumber, addr2); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv4ProtocolNumber, addr2, err)
- }
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv4EmptySubnet,
- NIC: nicID,
- }})
-
- var firstFragmentSent buffer.View
- for _, f := range test.fragments {
- pktSize := header.IPv4MinimumSize
- hdr := buffer.NewPrependable(pktSize)
-
- ip := header.IPv4(hdr.Prepend(pktSize))
- ip.Encode(&f.ipv4fields)
-
- ip.SetChecksum(0)
- ip.SetChecksum(^ip.CalculateChecksum())
-
- vv := hdr.View().ToVectorisedView()
- vv.AppendView(f.payload)
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: vv,
- })
-
- if firstFragmentSent == nil && ip.FragmentOffset() == 0 {
- firstFragmentSent = stack.PayloadSince(pkt.NetworkHeader())
- }
-
- e.InjectInbound(header.IPv4ProtocolNumber, pkt)
- }
-
- clock.Advance(ipv4.ReassembleTimeout)
-
- reply, ok := e.Read()
- if !test.expectICMP {
- if ok {
- t.Fatalf("unexpected ICMP error message received: %#v", reply)
- }
- return
- }
- if !ok {
- t.Fatal("expected ICMP error message missing")
- }
- if firstFragmentSent == nil {
- t.Fatalf("unexpected ICMP error message received: %#v", reply)
- }
-
- checker.IPv4(t, stack.PayloadSince(reply.Pkt.NetworkHeader()),
- checker.SrcAddr(addr2),
- checker.DstAddr(addr1),
- checker.IPFullLength(uint16(header.IPv4MinimumSize+header.ICMPv4MinimumSize+firstFragmentSent.Size())),
- checker.IPv4HeaderLength(header.IPv4MinimumSize),
- checker.ICMPv4(
- checker.ICMPv4Type(header.ICMPv4TimeExceeded),
- checker.ICMPv4Code(header.ICMPv4ReassemblyTimeout),
- checker.ICMPv4Checksum(),
- checker.ICMPv4Payload([]byte(firstFragmentSent)),
- ),
- )
- })
- }
-}
-
-// TestReceiveFragments feeds fragments in through the incoming packet path to
-// test reassembly
-func TestReceiveFragments(t *testing.T) {
- const (
- nicID = 1
-
- addr1 = "\x0c\xa8\x00\x01" // 192.168.0.1
- addr2 = "\x0c\xa8\x00\x02" // 192.168.0.2
- addr3 = "\x0c\xa8\x00\x03" // 192.168.0.3
- )
-
- // Build and return a UDP header containing payload.
- udpGen := func(payloadLen int, multiplier uint8, src, dst tcpip.Address) buffer.View {
- payload := buffer.NewView(payloadLen)
- for i := 0; i < len(payload); i++ {
- payload[i] = uint8(i) * multiplier
- }
-
- udpLength := header.UDPMinimumSize + len(payload)
-
- hdr := buffer.NewPrependable(udpLength)
- u := header.UDP(hdr.Prepend(udpLength))
- u.Encode(&header.UDPFields{
- SrcPort: 5555,
- DstPort: 80,
- Length: uint16(udpLength),
- })
- copy(u.Payload(), payload)
- sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, src, dst, uint16(udpLength))
- sum = header.Checksum(payload, sum)
- u.SetChecksum(^u.CalculateChecksum(sum))
- return hdr.View()
- }
-
- // UDP header plus a payload of 0..256
- ipv4Payload1Addr1ToAddr2 := udpGen(256, 1, addr1, addr2)
- udpPayload1Addr1ToAddr2 := ipv4Payload1Addr1ToAddr2[header.UDPMinimumSize:]
- ipv4Payload1Addr3ToAddr2 := udpGen(256, 1, addr3, addr2)
- udpPayload1Addr3ToAddr2 := ipv4Payload1Addr3ToAddr2[header.UDPMinimumSize:]
- // UDP header plus a payload of 0..256 in increments of 2.
- ipv4Payload2Addr1ToAddr2 := udpGen(128, 2, addr1, addr2)
- udpPayload2Addr1ToAddr2 := ipv4Payload2Addr1ToAddr2[header.UDPMinimumSize:]
- // UDP header plus a payload of 0..256 in increments of 3.
- // Used to test cases where the fragment blocks are not a multiple of
- // the fragment block size of 8 (RFC 791 section 3.1 page 14).
- ipv4Payload3Addr1ToAddr2 := udpGen(127, 3, addr1, addr2)
- udpPayload3Addr1ToAddr2 := ipv4Payload3Addr1ToAddr2[header.UDPMinimumSize:]
- // Used to test the max reassembled payload length (65,535 octets).
- ipv4Payload4Addr1ToAddr2 := udpGen(header.UDPMaximumSize-header.UDPMinimumSize, 4, addr1, addr2)
- udpPayload4Addr1ToAddr2 := ipv4Payload4Addr1ToAddr2[header.UDPMinimumSize:]
-
- type fragmentData struct {
- srcAddr tcpip.Address
- dstAddr tcpip.Address
- id uint16
- flags uint8
- fragmentOffset uint16
- payload buffer.View
- }
-
- tests := []struct {
- name string
- fragments []fragmentData
- expectedPayloads [][]byte
- }{
- {
- name: "No fragmentation",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2,
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "No fragmentation with size not a multiple of fragment block size",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 0,
- payload: ipv4Payload3Addr1ToAddr2,
- },
- },
- expectedPayloads: [][]byte{udpPayload3Addr1ToAddr2},
- },
- {
- name: "More fragments without payload",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2,
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Non-zero fragment offset without payload",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 8,
- payload: ipv4Payload1Addr1ToAddr2,
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two fragments",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2[:64],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 64,
- payload: ipv4Payload1Addr1ToAddr2[64:],
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Two fragments out of order",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 64,
- payload: ipv4Payload1Addr1ToAddr2[64:],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2[:64],
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Two fragments with last fragment size not a multiple of fragment block size",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload3Addr1ToAddr2[:64],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 64,
- payload: ipv4Payload3Addr1ToAddr2[64:],
- },
- },
- expectedPayloads: [][]byte{udpPayload3Addr1ToAddr2},
- },
- {
- name: "Two fragments with first fragment size not a multiple of fragment block size",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload3Addr1ToAddr2[:63],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 63,
- payload: ipv4Payload3Addr1ToAddr2[63:],
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Second fragment has MoreFlags set",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2[:64],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 64,
- payload: ipv4Payload1Addr1ToAddr2[64:],
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two fragments with different IDs",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2[:64],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 2,
- flags: 0,
- fragmentOffset: 64,
- payload: ipv4Payload1Addr1ToAddr2[64:],
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two interleaved fragmented packets",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2[:64],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 2,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload2Addr1ToAddr2[:64],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 64,
- payload: ipv4Payload1Addr1ToAddr2[64:],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 2,
- flags: 0,
- fragmentOffset: 64,
- payload: ipv4Payload2Addr1ToAddr2[64:],
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2, udpPayload2Addr1ToAddr2},
- },
- {
- name: "Two interleaved fragmented packets from different sources but with same ID",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2[:64],
- },
- {
- srcAddr: addr3,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr3ToAddr2[:32],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 64,
- payload: ipv4Payload1Addr1ToAddr2[64:],
- },
- {
- srcAddr: addr3,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 32,
- payload: ipv4Payload1Addr3ToAddr2[32:],
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2, udpPayload1Addr3ToAddr2},
- },
- {
- name: "Fragment without followup",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload1Addr1ToAddr2[:64],
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two fragments reassembled into a maximum UDP packet",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: header.IPv4FlagMoreFragments,
- fragmentOffset: 0,
- payload: ipv4Payload4Addr1ToAddr2[:65512],
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- id: 1,
- flags: 0,
- fragmentOffset: 65512,
- payload: ipv4Payload4Addr1ToAddr2[65512:],
- },
- },
- expectedPayloads: [][]byte{udpPayload4Addr1ToAddr2},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- // Setup a stack and endpoint.
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
- e := channel.New(0, 1280, tcpip.LinkAddress("\xf0\x00"))
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, header.IPv4ProtocolNumber, addr2); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv4ProtocolNumber, addr2, err)
- }
-
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
- defer close(ch)
- ep, err := s.NewEndpoint(udp.ProtocolNumber, header.IPv4ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, header.IPv4ProtocolNumber, err)
- }
- defer ep.Close()
-
- bindAddr := tcpip.FullAddress{Addr: addr2, Port: 80}
- if err := ep.Bind(bindAddr); err != nil {
- t.Fatalf("Bind(%+v): %s", bindAddr, err)
- }
-
- // Prepare and send the fragments.
- for _, frag := range test.fragments {
- hdr := buffer.NewPrependable(header.IPv4MinimumSize)
-
- // Serialize IPv4 fixed header.
- ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: header.IPv4MinimumSize + uint16(len(frag.payload)),
- ID: frag.id,
- Flags: frag.flags,
- FragmentOffset: frag.fragmentOffset,
- TTL: 64,
- Protocol: uint8(header.UDPProtocolNumber),
- SrcAddr: frag.srcAddr,
- DstAddr: frag.dstAddr,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
-
- vv := hdr.View().ToVectorisedView()
- vv.AppendView(frag.payload)
-
- e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: vv,
- }))
- }
-
- if got, want := s.Stats().UDP.PacketsReceived.Value(), uint64(len(test.expectedPayloads)); got != want {
- t.Errorf("got UDP Rx Packets = %d, want = %d", got, want)
- }
-
- for i, expectedPayload := range test.expectedPayloads {
- gotPayload, _, err := ep.Read(nil)
- if err != nil {
- t.Fatalf("(i=%d) Read(nil): %s", i, err)
- }
- if diff := cmp.Diff(buffer.View(expectedPayload), gotPayload); diff != "" {
- t.Errorf("(i=%d) got UDP payload mismatch (-want +got):\n%s", i, diff)
- }
- }
-
- if gotPayload, _, err := ep.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("(last) got Read(nil) = (%x, _, %v), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
- }
- })
- }
-}
-
-func TestWriteStats(t *testing.T) {
- const nPackets = 3
-
- tests := []struct {
- name string
- setup func(*testing.T, *stack.Stack)
- allowPackets int
- expectSent int
- expectDropped int
- expectWritten int
- }{
- {
- name: "Accept all",
- // No setup needed, tables accept everything by default.
- setup: func(*testing.T, *stack.Stack) {},
- allowPackets: math.MaxInt32,
- expectSent: nPackets,
- expectDropped: 0,
- expectWritten: nPackets,
- }, {
- name: "Accept all with error",
- // No setup needed, tables accept everything by default.
- setup: func(*testing.T, *stack.Stack) {},
- allowPackets: nPackets - 1,
- expectSent: nPackets - 1,
- expectDropped: 0,
- expectWritten: nPackets - 1,
- }, {
- name: "Drop all",
- setup: func(t *testing.T, stk *stack.Stack) {
- // Install Output DROP rule.
- t.Helper()
- ipt := stk.IPTables()
- filter := ipt.GetTable(stack.FilterID, false /* ipv6 */)
- ruleIdx := filter.BuiltinChains[stack.Output]
- filter.Rules[ruleIdx].Target = &stack.DropTarget{}
- if err := ipt.ReplaceTable(stack.FilterID, filter, false /* ipv6 */); err != nil {
- t.Fatalf("failed to replace table: %s", err)
- }
- },
- allowPackets: math.MaxInt32,
- expectSent: 0,
- expectDropped: nPackets,
- expectWritten: nPackets,
- }, {
- name: "Drop some",
- setup: func(t *testing.T, stk *stack.Stack) {
- // Install Output DROP rule that matches only 1
- // of the 3 packets.
- t.Helper()
- ipt := stk.IPTables()
- filter := ipt.GetTable(stack.FilterID, false /* ipv6 */)
- // We'll match and DROP the last packet.
- ruleIdx := filter.BuiltinChains[stack.Output]
- filter.Rules[ruleIdx].Target = &stack.DropTarget{}
- filter.Rules[ruleIdx].Matchers = []stack.Matcher{&limitedMatcher{nPackets - 1}}
- // Make sure the next rule is ACCEPT.
- filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
- if err := ipt.ReplaceTable(stack.FilterID, filter, false /* ipv6 */); err != nil {
- t.Fatalf("failed to replace table: %s", err)
- }
- },
- allowPackets: math.MaxInt32,
- expectSent: nPackets - 1,
- expectDropped: 1,
- expectWritten: nPackets,
- },
- }
-
- // Parameterize the tests to run with both WritePacket and WritePackets.
- writers := []struct {
- name string
- writePackets func(*stack.Route, stack.PacketBufferList) (int, *tcpip.Error)
- }{
- {
- name: "WritePacket",
- writePackets: func(rt *stack.Route, pkts stack.PacketBufferList) (int, *tcpip.Error) {
- nWritten := 0
- for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
- if err := rt.WritePacket(nil, stack.NetworkHeaderParams{}, pkt); err != nil {
- return nWritten, err
- }
- nWritten++
- }
- return nWritten, nil
- },
- }, {
- name: "WritePackets",
- writePackets: func(rt *stack.Route, pkts stack.PacketBufferList) (int, *tcpip.Error) {
- return rt.WritePackets(nil, pkts, stack.NetworkHeaderParams{})
- },
- },
- }
-
- for _, writer := range writers {
- t.Run(writer.name, func(t *testing.T) {
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ep := testutil.NewMockLinkEndpoint(header.IPv4MinimumMTU, tcpip.ErrInvalidEndpointState, test.allowPackets)
- rt := buildRoute(t, ep)
-
- var pkts stack.PacketBufferList
- for i := 0; i < nPackets; i++ {
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: header.UDPMinimumSize + int(rt.MaxHeaderLength()),
- Data: buffer.NewView(0).ToVectorisedView(),
- })
- pkt.TransportHeader().Push(header.UDPMinimumSize)
- pkts.PushBack(pkt)
- }
-
- test.setup(t, rt.Stack())
-
- nWritten, _ := writer.writePackets(&rt, pkts)
-
- if got := int(rt.Stats().IP.PacketsSent.Value()); got != test.expectSent {
- t.Errorf("sent %d packets, but expected to send %d", got, test.expectSent)
- }
- if got := int(rt.Stats().IP.IPTablesOutputDropped.Value()); got != test.expectDropped {
- t.Errorf("dropped %d packets, but expected to drop %d", got, test.expectDropped)
- }
- if nWritten != test.expectWritten {
- t.Errorf("wrote %d packets, but expected WritePackets to return %d", nWritten, test.expectWritten)
- }
- })
- }
- })
- }
-}
-
-func buildRoute(t *testing.T, ep stack.LinkEndpoint) stack.Route {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
- })
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatalf("CreateNIC(1, _) failed: %s", err)
- }
- const (
- src = "\x10\x00\x00\x01"
- dst = "\x10\x00\x00\x02"
- )
- if err := s.AddAddress(1, ipv4.ProtocolNumber, src); err != nil {
- t.Fatalf("AddAddress(1, %d, %s) failed: %s", ipv4.ProtocolNumber, src, err)
- }
- {
- mask := tcpip.AddressMask(header.IPv4Broadcast)
- subnet, err := tcpip.NewSubnet(dst, mask)
- if err != nil {
- t.Fatalf("NewSubnet(%s, %s) failed: %v", dst, mask, err)
- }
- s.SetRouteTable([]tcpip.Route{{
- Destination: subnet,
- NIC: 1,
- }})
- }
- rt, err := s.FindRoute(1, src, dst, ipv4.ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(1, %s, %s, %d, false) = %s", src, dst, ipv4.ProtocolNumber, err)
- }
- return rt
-}
-
-// limitedMatcher is an iptables matcher that matches after a certain number of
-// packets are checked against it.
-type limitedMatcher struct {
- limit int
-}
-
-// Name implements Matcher.Name.
-func (*limitedMatcher) Name() string {
- return "limitedMatcher"
-}
-
-// Match implements Matcher.Match.
-func (lm *limitedMatcher) Match(stack.Hook, *stack.PacketBuffer, string) (bool, bool) {
- if lm.limit == 0 {
- return true, false
- }
- lm.limit--
- return false, false
-}
-
-func TestPacketQueing(t *testing.T) {
- const nicID = 1
-
- var (
- host1NICLinkAddr = tcpip.LinkAddress("\x02\x03\x03\x04\x05\x06")
- host2NICLinkAddr = tcpip.LinkAddress("\x02\x03\x03\x04\x05\x09")
-
- host1IPv4Addr = tcpip.ProtocolAddress{
- Protocol: ipv4.ProtocolNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: tcpip.Address(net.ParseIP("192.168.0.1").To4()),
- PrefixLen: 24,
- },
- }
- host2IPv4Addr = tcpip.ProtocolAddress{
- Protocol: ipv4.ProtocolNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: tcpip.Address(net.ParseIP("192.168.0.2").To4()),
- PrefixLen: 8,
- },
- }
- )
-
- tests := []struct {
- name string
- rxPkt func(*channel.Endpoint)
- checkResp func(*testing.T, *channel.Endpoint)
- }{
- {
- name: "ICMP Error",
- rxPkt: func(e *channel.Endpoint) {
- hdr := buffer.NewPrependable(header.IPv4MinimumSize + header.UDPMinimumSize)
- u := header.UDP(hdr.Prepend(header.UDPMinimumSize))
- u.Encode(&header.UDPFields{
- SrcPort: 5555,
- DstPort: 80,
- Length: header.UDPMinimumSize,
- })
- sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, host2IPv4Addr.AddressWithPrefix.Address, host1IPv4Addr.AddressWithPrefix.Address, header.UDPMinimumSize)
- sum = header.Checksum(header.UDP([]byte{}), sum)
- u.SetChecksum(^u.CalculateChecksum(sum))
- ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: header.IPv4MinimumSize + header.UDPMinimumSize,
- TTL: ipv4.DefaultTTL,
- Protocol: uint8(udp.ProtocolNumber),
- SrcAddr: host2IPv4Addr.AddressWithPrefix.Address,
- DstAddr: host1IPv4Addr.AddressWithPrefix.Address,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
- e.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
- },
- checkResp: func(t *testing.T, e *channel.Endpoint) {
- p, ok := e.ReadContext(context.Background())
- if !ok {
- t.Fatalf("timed out waiting for packet")
- }
- if p.Proto != header.IPv4ProtocolNumber {
- t.Errorf("got p.Proto = %d, want = %d", p.Proto, header.IPv4ProtocolNumber)
- }
- if p.Route.RemoteLinkAddress != host2NICLinkAddr {
- t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, host2NICLinkAddr)
- }
- checker.IPv4(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(host1IPv4Addr.AddressWithPrefix.Address),
- checker.DstAddr(host2IPv4Addr.AddressWithPrefix.Address),
- checker.ICMPv4(
- checker.ICMPv4Type(header.ICMPv4DstUnreachable),
- checker.ICMPv4Code(header.ICMPv4PortUnreachable)))
- },
- },
-
- {
- name: "Ping",
- rxPkt: func(e *channel.Endpoint) {
- totalLen := header.IPv4MinimumSize + header.ICMPv4MinimumSize
- hdr := buffer.NewPrependable(totalLen)
- pkt := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
- pkt.SetType(header.ICMPv4Echo)
- pkt.SetCode(0)
- pkt.SetChecksum(0)
- pkt.SetChecksum(^header.Checksum(pkt, 0))
- ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
- ip.Encode(&header.IPv4Fields{
- IHL: header.IPv4MinimumSize,
- TotalLength: uint16(totalLen),
- Protocol: uint8(icmp.ProtocolNumber4),
- TTL: ipv4.DefaultTTL,
- SrcAddr: host2IPv4Addr.AddressWithPrefix.Address,
- DstAddr: host1IPv4Addr.AddressWithPrefix.Address,
- })
- ip.SetChecksum(^ip.CalculateChecksum())
- e.InjectInbound(header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
- },
- checkResp: func(t *testing.T, e *channel.Endpoint) {
- p, ok := e.ReadContext(context.Background())
- if !ok {
- t.Fatalf("timed out waiting for packet")
- }
- if p.Proto != header.IPv4ProtocolNumber {
- t.Errorf("got p.Proto = %d, want = %d", p.Proto, header.IPv4ProtocolNumber)
- }
- if p.Route.RemoteLinkAddress != host2NICLinkAddr {
- t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, host2NICLinkAddr)
- }
- checker.IPv4(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(host1IPv4Addr.AddressWithPrefix.Address),
- checker.DstAddr(host2IPv4Addr.AddressWithPrefix.Address),
- checker.ICMPv4(
- checker.ICMPv4Type(header.ICMPv4EchoReply),
- checker.ICMPv4Code(header.ICMPv4UnusedCode)))
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- e := channel.New(1, defaultMTU, host1NICLinkAddr)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
- }
- if err := s.AddAddress(nicID, arp.ProtocolNumber, arp.ProtocolAddress); err != nil {
- t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, arp.ProtocolNumber, arp.ProtocolAddress, err)
- }
- if err := s.AddProtocolAddress(nicID, host1IPv4Addr); err != nil {
- t.Fatalf("s.AddProtocolAddress(%d, %#v): %s", nicID, host1IPv4Addr, err)
- }
-
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: host1IPv4Addr.AddressWithPrefix.Subnet(),
- NIC: nicID,
- },
- })
-
- // Receive a packet to trigger link resolution before a response is sent.
- test.rxPkt(e)
-
- // Wait for a ARP request since link address resolution should be
- // performed.
- {
- p, ok := e.ReadContext(context.Background())
- if !ok {
- t.Fatalf("timed out waiting for packet")
- }
- if p.Proto != arp.ProtocolNumber {
- t.Errorf("got p.Proto = %d, want = %d", p.Proto, arp.ProtocolNumber)
- }
- if p.Route.RemoteLinkAddress != header.EthernetBroadcastAddress {
- t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, header.EthernetBroadcastAddress)
- }
- rep := header.ARP(p.Pkt.NetworkHeader().View())
- if got := rep.Op(); got != header.ARPRequest {
- t.Errorf("got Op() = %d, want = %d", got, header.ARPRequest)
- }
- if got := tcpip.LinkAddress(rep.HardwareAddressSender()); got != host1NICLinkAddr {
- t.Errorf("got HardwareAddressSender = %s, want = %s", got, host1NICLinkAddr)
- }
- if got := tcpip.Address(rep.ProtocolAddressSender()); got != host1IPv4Addr.AddressWithPrefix.Address {
- t.Errorf("got ProtocolAddressSender = %s, want = %s", got, host1IPv4Addr.AddressWithPrefix.Address)
- }
- if got := tcpip.Address(rep.ProtocolAddressTarget()); got != host2IPv4Addr.AddressWithPrefix.Address {
- t.Errorf("got ProtocolAddressTarget = %s, want = %s", got, host2IPv4Addr.AddressWithPrefix.Address)
- }
- }
-
- // Send an ARP reply to complete link address resolution.
- {
- hdr := buffer.View(make([]byte, header.ARPSize))
- packet := header.ARP(hdr)
- packet.SetIPv4OverEthernet()
- packet.SetOp(header.ARPReply)
- copy(packet.HardwareAddressSender(), host2NICLinkAddr)
- copy(packet.ProtocolAddressSender(), host2IPv4Addr.AddressWithPrefix.Address)
- copy(packet.HardwareAddressTarget(), host1NICLinkAddr)
- copy(packet.ProtocolAddressTarget(), host1IPv4Addr.AddressWithPrefix.Address)
- e.InjectInbound(arp.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.ToVectorisedView(),
- }))
- }
-
- // Expect the response now that the link address has resolved.
- test.checkResp(t, e)
-
- // Since link resolution was already performed, it shouldn't be performed
- // again.
- test.rxPkt(e)
- test.checkResp(t, e)
- })
- }
-}
diff --git a/pkg/tcpip/network/ipv6/BUILD b/pkg/tcpip/network/ipv6/BUILD
deleted file mode 100644
index 0ac24a6fb..000000000
--- a/pkg/tcpip/network/ipv6/BUILD
+++ /dev/null
@@ -1,51 +0,0 @@
-load("//tools:defs.bzl", "go_library", "go_test")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "ipv6",
- srcs = [
- "dhcpv6configurationfromndpra_string.go",
- "icmp.go",
- "ipv6.go",
- "ndp.go",
- ],
- visibility = ["//visibility:public"],
- deps = [
- "//pkg/sync",
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/header/parse",
- "//pkg/tcpip/network/fragmentation",
- "//pkg/tcpip/network/hash",
- "//pkg/tcpip/stack",
- ],
-)
-
-go_test(
- name = "ipv6_test",
- size = "small",
- srcs = [
- "icmp_test.go",
- "ipv6_test.go",
- "ndp_test.go",
- ],
- library = ":ipv6",
- 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/testutil",
- "//pkg/tcpip/stack",
- "//pkg/tcpip/transport/icmp",
- "//pkg/tcpip/transport/tcp",
- "//pkg/tcpip/transport/udp",
- "//pkg/waiter",
- "@com_github_google_go_cmp//cmp:go_default_library",
- ],
-)
diff --git a/pkg/tcpip/network/ipv6/icmp_test.go b/pkg/tcpip/network/ipv6/icmp_test.go
deleted file mode 100644
index 76013daa1..000000000
--- a/pkg/tcpip/network/ipv6/icmp_test.go
+++ /dev/null
@@ -1,1813 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ipv6
-
-import (
- "context"
- "net"
- "reflect"
- "strings"
- "testing"
- "time"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "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/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- nicID = 1
-
- linkAddr0 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
- linkAddr1 = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
- linkAddr2 = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0f")
-
- defaultChannelSize = 1
- defaultMTU = 65536
-
- // Extra time to use when waiting for an async event to occur.
- defaultAsyncPositiveEventTimeout = 30 * time.Second
-)
-
-var (
- lladdr0 = header.LinkLocalAddr(linkAddr0)
- lladdr1 = header.LinkLocalAddr(linkAddr1)
- lladdr2 = header.LinkLocalAddr(linkAddr2)
-)
-
-type stubLinkEndpoint struct {
- stack.LinkEndpoint
-}
-
-func (*stubLinkEndpoint) MTU() uint32 {
- return defaultMTU
-}
-
-func (*stubLinkEndpoint) Capabilities() stack.LinkEndpointCapabilities {
- // Indicate that resolution for link layer addresses is required to send
- // packets over this link. This is needed so the NIC knows to allocate a
- // neighbor table.
- return stack.CapabilityResolutionRequired
-}
-
-func (*stubLinkEndpoint) MaxHeaderLength() uint16 {
- return 0
-}
-
-func (*stubLinkEndpoint) LinkAddress() tcpip.LinkAddress {
- return ""
-}
-
-func (*stubLinkEndpoint) WritePacket(*stack.Route, *stack.GSO, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) *tcpip.Error {
- return nil
-}
-
-func (*stubLinkEndpoint) Attach(stack.NetworkDispatcher) {}
-
-type stubDispatcher struct {
- stack.TransportDispatcher
-}
-
-func (*stubDispatcher) DeliverTransportPacket(tcpip.TransportProtocolNumber, *stack.PacketBuffer) stack.TransportPacketDisposition {
- return stack.TransportPacketHandled
-}
-
-type stubLinkAddressCache struct {
- stack.LinkAddressCache
-}
-
-func (*stubLinkAddressCache) CheckLocalAddress(tcpip.NICID, tcpip.NetworkProtocolNumber, tcpip.Address) tcpip.NICID {
- return 0
-}
-
-func (*stubLinkAddressCache) AddLinkAddress(tcpip.NICID, tcpip.Address, tcpip.LinkAddress) {
-}
-
-type stubNUDHandler struct {
- probeCount int
- confirmationCount int
-}
-
-var _ stack.NUDHandler = (*stubNUDHandler)(nil)
-
-func (s *stubNUDHandler) HandleProbe(tcpip.Address, tcpip.NetworkProtocolNumber, tcpip.LinkAddress, stack.LinkAddressResolver) {
- s.probeCount++
-}
-
-func (s *stubNUDHandler) HandleConfirmation(tcpip.Address, tcpip.LinkAddress, stack.ReachabilityConfirmationFlags) {
- s.confirmationCount++
-}
-
-func (*stubNUDHandler) HandleUpperLevelConfirmation(tcpip.Address) {
-}
-
-var _ stack.NetworkInterface = (*testInterface)(nil)
-
-type testInterface struct {
- stack.LinkEndpoint
-
- nicID tcpip.NICID
-}
-
-func (*testInterface) ID() tcpip.NICID {
- return nicID
-}
-
-func (*testInterface) IsLoopback() bool {
- return false
-}
-
-func (*testInterface) Name() string {
- return ""
-}
-
-func (*testInterface) Enabled() bool {
- return true
-}
-
-func (t *testInterface) WritePacketToRemote(remoteLinkAddr tcpip.LinkAddress, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
- r := stack.Route{
- NetProto: protocol,
- RemoteLinkAddress: remoteLinkAddr,
- }
- return t.LinkEndpoint.WritePacket(&r, gso, protocol, pkt)
-}
-
-func TestICMPCounts(t *testing.T) {
- tests := []struct {
- name string
- useNeighborCache bool
- }{
- {
- name: "linkAddrCache",
- useNeighborCache: false,
- },
- {
- name: "neighborCache",
- useNeighborCache: true,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
- UseNeighborCache: test.useNeighborCache,
- })
- {
- if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
- t.Fatalf("CreateNIC(_, _) = %s", err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err)
- }
- }
- {
- subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: nicID,
- }},
- )
- }
-
- netProto := s.NetworkProtocolInstance(ProtocolNumber)
- if netProto == nil {
- t.Fatalf("cannot find protocol instance for network protocol %d", ProtocolNumber)
- }
- ep := netProto.NewEndpoint(&testInterface{}, &stubLinkAddressCache{}, &stubNUDHandler{}, &stubDispatcher{})
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- r, err := s.FindRoute(nicID, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(%d, %s, %s, _, false) = (_, %s), want = (_, nil)", nicID, lladdr0, lladdr1, err)
- }
- defer r.Release()
-
- var tllData [header.NDPLinkLayerAddressSize]byte
- header.NDPOptions(tllData[:]).Serialize(header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(linkAddr1),
- })
-
- types := []struct {
- typ header.ICMPv6Type
- size int
- extraData []byte
- }{
- {
- typ: header.ICMPv6DstUnreachable,
- size: header.ICMPv6DstUnreachableMinimumSize,
- },
- {
- typ: header.ICMPv6PacketTooBig,
- size: header.ICMPv6PacketTooBigMinimumSize,
- },
- {
- typ: header.ICMPv6TimeExceeded,
- size: header.ICMPv6MinimumSize,
- },
- {
- typ: header.ICMPv6ParamProblem,
- size: header.ICMPv6MinimumSize,
- },
- {
- typ: header.ICMPv6EchoRequest,
- size: header.ICMPv6EchoMinimumSize,
- },
- {
- typ: header.ICMPv6EchoReply,
- size: header.ICMPv6EchoMinimumSize,
- },
- {
- typ: header.ICMPv6RouterSolicit,
- size: header.ICMPv6MinimumSize,
- },
- {
- typ: header.ICMPv6RouterAdvert,
- size: header.ICMPv6HeaderSize + header.NDPRAMinimumSize,
- },
- {
- typ: header.ICMPv6NeighborSolicit,
- size: header.ICMPv6NeighborSolicitMinimumSize,
- },
- {
- typ: header.ICMPv6NeighborAdvert,
- size: header.ICMPv6NeighborAdvertMinimumSize,
- extraData: tllData[:],
- },
- {
- typ: header.ICMPv6RedirectMsg,
- size: header.ICMPv6MinimumSize,
- },
- }
-
- handleIPv6Payload := func(icmp header.ICMPv6) {
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: header.IPv6MinimumSize,
- Data: buffer.View(icmp).ToVectorisedView(),
- })
- ip := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(len(icmp)),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: header.NDPHopLimit,
- SrcAddr: r.LocalAddress,
- DstAddr: r.RemoteAddress,
- })
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- }
-
- for _, typ := range types {
- icmp := header.ICMPv6(buffer.NewView(typ.size + len(typ.extraData)))
- copy(icmp[typ.size:], typ.extraData)
- icmp.SetType(typ.typ)
- icmp.SetChecksum(header.ICMPv6Checksum(icmp[:typ.size], r.LocalAddress, r.RemoteAddress, buffer.View(typ.extraData).ToVectorisedView()))
- handleIPv6Payload(icmp)
- }
-
- // Construct an empty ICMP packet so that
- // Stats().ICMP.ICMPv6ReceivedPacketStats.Invalid is incremented.
- handleIPv6Payload(header.ICMPv6(buffer.NewView(header.IPv6MinimumSize)))
-
- icmpv6Stats := s.Stats().ICMP.V6PacketsReceived
- visitStats(reflect.ValueOf(&icmpv6Stats).Elem(), func(name string, s *tcpip.StatCounter) {
- if got, want := s.Value(), uint64(1); got != want {
- t.Errorf("got %s = %d, want = %d", name, got, want)
- }
- })
- if t.Failed() {
- t.Logf("stats:\n%+v", s.Stats())
- }
- })
- }
-}
-
-func TestICMPCountsWithNeighborCache(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
- UseNeighborCache: true,
- })
- {
- if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
- t.Fatalf("CreateNIC(_, _) = %s", err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err)
- }
- }
- {
- subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: nicID,
- }},
- )
- }
-
- netProto := s.NetworkProtocolInstance(ProtocolNumber)
- if netProto == nil {
- t.Fatalf("cannot find protocol instance for network protocol %d", ProtocolNumber)
- }
- ep := netProto.NewEndpoint(&testInterface{}, nil, &stubNUDHandler{}, &stubDispatcher{})
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- r, err := s.FindRoute(nicID, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(%d, %s, %s, _, false) = (_, %s), want = (_, nil)", nicID, lladdr0, lladdr1, err)
- }
- defer r.Release()
-
- var tllData [header.NDPLinkLayerAddressSize]byte
- header.NDPOptions(tllData[:]).Serialize(header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(linkAddr1),
- })
-
- types := []struct {
- typ header.ICMPv6Type
- size int
- extraData []byte
- }{
- {
- typ: header.ICMPv6DstUnreachable,
- size: header.ICMPv6DstUnreachableMinimumSize,
- },
- {
- typ: header.ICMPv6PacketTooBig,
- size: header.ICMPv6PacketTooBigMinimumSize,
- },
- {
- typ: header.ICMPv6TimeExceeded,
- size: header.ICMPv6MinimumSize,
- },
- {
- typ: header.ICMPv6ParamProblem,
- size: header.ICMPv6MinimumSize,
- },
- {
- typ: header.ICMPv6EchoRequest,
- size: header.ICMPv6EchoMinimumSize,
- },
- {
- typ: header.ICMPv6EchoReply,
- size: header.ICMPv6EchoMinimumSize,
- },
- {
- typ: header.ICMPv6RouterSolicit,
- size: header.ICMPv6MinimumSize,
- },
- {
- typ: header.ICMPv6RouterAdvert,
- size: header.ICMPv6HeaderSize + header.NDPRAMinimumSize,
- },
- {
- typ: header.ICMPv6NeighborSolicit,
- size: header.ICMPv6NeighborSolicitMinimumSize,
- },
- {
- typ: header.ICMPv6NeighborAdvert,
- size: header.ICMPv6NeighborAdvertMinimumSize,
- extraData: tllData[:],
- },
- {
- typ: header.ICMPv6RedirectMsg,
- size: header.ICMPv6MinimumSize,
- },
- }
-
- handleIPv6Payload := func(icmp header.ICMPv6) {
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: header.IPv6MinimumSize,
- Data: buffer.View(icmp).ToVectorisedView(),
- })
- ip := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(len(icmp)),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: header.NDPHopLimit,
- SrcAddr: r.LocalAddress,
- DstAddr: r.RemoteAddress,
- })
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- }
-
- for _, typ := range types {
- icmp := header.ICMPv6(buffer.NewView(typ.size + len(typ.extraData)))
- copy(icmp[typ.size:], typ.extraData)
- icmp.SetType(typ.typ)
- icmp.SetChecksum(header.ICMPv6Checksum(icmp[:typ.size], r.LocalAddress, r.RemoteAddress, buffer.View(typ.extraData).ToVectorisedView()))
- handleIPv6Payload(icmp)
- }
-
- // Construct an empty ICMP packet so that
- // Stats().ICMP.ICMPv6ReceivedPacketStats.Invalid is incremented.
- handleIPv6Payload(header.ICMPv6(buffer.NewView(header.IPv6MinimumSize)))
-
- icmpv6Stats := s.Stats().ICMP.V6PacketsReceived
- visitStats(reflect.ValueOf(&icmpv6Stats).Elem(), func(name string, s *tcpip.StatCounter) {
- if got, want := s.Value(), uint64(1); got != want {
- t.Errorf("got %s = %d, want = %d", name, got, want)
- }
- })
- if t.Failed() {
- t.Logf("stats:\n%+v", s.Stats())
- }
-}
-
-func visitStats(v reflect.Value, f func(string, *tcpip.StatCounter)) {
- t := v.Type()
- for i := 0; i < v.NumField(); i++ {
- v := v.Field(i)
- if s, ok := v.Interface().(*tcpip.StatCounter); ok {
- f(t.Field(i).Name, s)
- } else {
- visitStats(v, f)
- }
- }
-}
-
-type testContext struct {
- s0 *stack.Stack
- s1 *stack.Stack
-
- linkEP0 *channel.Endpoint
- linkEP1 *channel.Endpoint
-}
-
-type endpointWithResolutionCapability struct {
- stack.LinkEndpoint
-}
-
-func (e endpointWithResolutionCapability) Capabilities() stack.LinkEndpointCapabilities {
- return e.LinkEndpoint.Capabilities() | stack.CapabilityResolutionRequired
-}
-
-func newTestContext(t *testing.T) *testContext {
- c := &testContext{
- s0: stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
- }),
- s1: stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
- }),
- }
-
- c.linkEP0 = channel.New(defaultChannelSize, defaultMTU, linkAddr0)
-
- wrappedEP0 := stack.LinkEndpoint(endpointWithResolutionCapability{LinkEndpoint: c.linkEP0})
- if testing.Verbose() {
- wrappedEP0 = sniffer.New(wrappedEP0)
- }
- if err := c.s0.CreateNIC(nicID, wrappedEP0); err != nil {
- t.Fatalf("CreateNIC s0: %v", err)
- }
- if err := c.s0.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress lladdr0: %v", err)
- }
-
- c.linkEP1 = channel.New(defaultChannelSize, defaultMTU, linkAddr1)
- wrappedEP1 := stack.LinkEndpoint(endpointWithResolutionCapability{LinkEndpoint: c.linkEP1})
- if err := c.s1.CreateNIC(nicID, wrappedEP1); err != nil {
- t.Fatalf("CreateNIC failed: %v", err)
- }
- if err := c.s1.AddAddress(nicID, ProtocolNumber, lladdr1); err != nil {
- t.Fatalf("AddAddress lladdr1: %v", err)
- }
-
- subnet0, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- c.s0.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet0,
- NIC: nicID,
- }},
- )
- subnet1, err := tcpip.NewSubnet(lladdr0, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr0))))
- if err != nil {
- t.Fatal(err)
- }
- c.s1.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet1,
- NIC: nicID,
- }},
- )
-
- return c
-}
-
-func (c *testContext) cleanup() {
- c.linkEP0.Close()
- c.linkEP1.Close()
-}
-
-type routeArgs struct {
- src, dst *channel.Endpoint
- typ header.ICMPv6Type
- remoteLinkAddr tcpip.LinkAddress
-}
-
-func routeICMPv6Packet(t *testing.T, args routeArgs, fn func(*testing.T, header.ICMPv6)) {
- t.Helper()
-
- pi, _ := args.src.ReadContext(context.Background())
-
- {
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buffer.NewVectorisedView(pi.Pkt.Size(), pi.Pkt.Views()),
- })
- args.dst.InjectLinkAddr(pi.Proto, args.dst.LinkAddress(), pkt)
- }
-
- if pi.Proto != ProtocolNumber {
- t.Errorf("unexpected protocol number %d", pi.Proto)
- return
- }
-
- if len(args.remoteLinkAddr) != 0 && args.remoteLinkAddr != pi.Route.RemoteLinkAddress {
- t.Errorf("got remote link address = %s, want = %s", pi.Route.RemoteLinkAddress, args.remoteLinkAddr)
- }
-
- // Pull the full payload since network header. Needed for header.IPv6 to
- // extract its payload.
- ipv6 := header.IPv6(stack.PayloadSince(pi.Pkt.NetworkHeader()))
- transProto := tcpip.TransportProtocolNumber(ipv6.NextHeader())
- if transProto != header.ICMPv6ProtocolNumber {
- t.Errorf("unexpected transport protocol number %d", transProto)
- return
- }
- icmpv6 := header.ICMPv6(ipv6.Payload())
- if got, want := icmpv6.Type(), args.typ; got != want {
- t.Errorf("got ICMPv6 type = %d, want = %d", got, want)
- return
- }
- if fn != nil {
- fn(t, icmpv6)
- }
-}
-
-func TestLinkResolution(t *testing.T) {
- c := newTestContext(t)
- defer c.cleanup()
-
- r, err := c.s0.FindRoute(nicID, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(%d, %s, %s, _, false) = (_, %s), want = (_, nil)", nicID, lladdr0, lladdr1, err)
- }
- defer r.Release()
-
- hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.IPv6MinimumSize + header.ICMPv6EchoMinimumSize)
- pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6EchoMinimumSize))
- pkt.SetType(header.ICMPv6EchoRequest)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
- payload := tcpip.SlicePayload(hdr.View())
-
- // We can't send our payload directly over the route because that
- // doesn't provoke NDP discovery.
- var wq waiter.Queue
- ep, err := c.s0.NewEndpoint(header.ICMPv6ProtocolNumber, ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint(_) = (_, %s), want = (_, nil)", err)
- }
-
- for {
- _, resCh, err := ep.Write(payload, tcpip.WriteOptions{To: &tcpip.FullAddress{NIC: nicID, Addr: lladdr1}})
- if resCh != nil {
- if err != tcpip.ErrNoLinkAddress {
- t.Fatalf("ep.Write(_) = (_, <non-nil>, %s), want = (_, <non-nil>, tcpip.ErrNoLinkAddress)", err)
- }
- for _, args := range []routeArgs{
- {src: c.linkEP0, dst: c.linkEP1, typ: header.ICMPv6NeighborSolicit, remoteLinkAddr: header.EthernetAddressFromMulticastIPv6Address(header.SolicitedNodeAddr(lladdr1))},
- {src: c.linkEP1, dst: c.linkEP0, typ: header.ICMPv6NeighborAdvert},
- } {
- routeICMPv6Packet(t, args, func(t *testing.T, icmpv6 header.ICMPv6) {
- if got, want := tcpip.Address(icmpv6[8:][:16]), lladdr1; got != want {
- t.Errorf("%d: got target = %s, want = %s", icmpv6.Type(), got, want)
- }
- })
- }
- <-resCh
- continue
- }
- if err != nil {
- t.Fatalf("ep.Write(_) = (_, _, %s)", err)
- }
- break
- }
-
- for _, args := range []routeArgs{
- {src: c.linkEP0, dst: c.linkEP1, typ: header.ICMPv6EchoRequest},
- {src: c.linkEP1, dst: c.linkEP0, typ: header.ICMPv6EchoReply},
- } {
- routeICMPv6Packet(t, args, nil)
- }
-}
-
-func TestICMPChecksumValidationSimple(t *testing.T) {
- var tllData [header.NDPLinkLayerAddressSize]byte
- header.NDPOptions(tllData[:]).Serialize(header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(linkAddr1),
- })
-
- types := []struct {
- name string
- typ header.ICMPv6Type
- size int
- extraData []byte
- statCounter func(tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter
- routerOnly bool
- }{
- {
- name: "DstUnreachable",
- typ: header.ICMPv6DstUnreachable,
- size: header.ICMPv6DstUnreachableMinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.DstUnreachable
- },
- },
- {
- name: "PacketTooBig",
- typ: header.ICMPv6PacketTooBig,
- size: header.ICMPv6PacketTooBigMinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.PacketTooBig
- },
- },
- {
- name: "TimeExceeded",
- typ: header.ICMPv6TimeExceeded,
- size: header.ICMPv6MinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.TimeExceeded
- },
- },
- {
- name: "ParamProblem",
- typ: header.ICMPv6ParamProblem,
- size: header.ICMPv6MinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.ParamProblem
- },
- },
- {
- name: "EchoRequest",
- typ: header.ICMPv6EchoRequest,
- size: header.ICMPv6EchoMinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.EchoRequest
- },
- },
- {
- name: "EchoReply",
- typ: header.ICMPv6EchoReply,
- size: header.ICMPv6EchoMinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.EchoReply
- },
- },
- {
- name: "RouterSolicit",
- typ: header.ICMPv6RouterSolicit,
- size: header.ICMPv6MinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RouterSolicit
- },
- // Hosts MUST silently discard any received Router Solicitation messages.
- routerOnly: true,
- },
- {
- name: "RouterAdvert",
- typ: header.ICMPv6RouterAdvert,
- size: header.ICMPv6HeaderSize + header.NDPRAMinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RouterAdvert
- },
- },
- {
- name: "NeighborSolicit",
- typ: header.ICMPv6NeighborSolicit,
- size: header.ICMPv6NeighborSolicitMinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.NeighborSolicit
- },
- },
- {
- name: "NeighborAdvert",
- typ: header.ICMPv6NeighborAdvert,
- size: header.ICMPv6NeighborAdvertMinimumSize,
- extraData: tllData[:],
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.NeighborAdvert
- },
- },
- {
- name: "RedirectMsg",
- typ: header.ICMPv6RedirectMsg,
- size: header.ICMPv6MinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RedirectMsg
- },
- },
- }
-
- tests := []struct {
- name string
- useNeighborCache bool
- }{
- {
- name: "linkAddrCache",
- useNeighborCache: false,
- },
- {
- name: "neighborCache",
- useNeighborCache: true,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- for _, typ := range types {
- for _, isRouter := range []bool{false, true} {
- name := typ.name
- if isRouter {
- name += " (Router)"
- }
- t.Run(name, func(t *testing.T) {
- e := channel.New(0, 1280, linkAddr0)
-
- // Indicate that resolution for link layer addresses is required to
- // send packets over this link. This is needed so the NIC knows to
- // allocate a neighbor table.
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- UseNeighborCache: test.useNeighborCache,
- })
- if isRouter {
- // Enabling forwarding makes the stack act as a router.
- s.SetForwarding(ProtocolNumber, true)
- }
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(_, _) = %s", err)
- }
-
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err)
- }
- {
- subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: nicID,
- }},
- )
- }
-
- handleIPv6Payload := func(checksum bool) {
- icmp := header.ICMPv6(buffer.NewView(typ.size + len(typ.extraData)))
- copy(icmp[typ.size:], typ.extraData)
- icmp.SetType(typ.typ)
- if checksum {
- icmp.SetChecksum(header.ICMPv6Checksum(icmp, lladdr1, lladdr0, buffer.View{}.ToVectorisedView()))
- }
- ip := header.IPv6(buffer.NewView(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(len(icmp)),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: header.NDPHopLimit,
- SrcAddr: lladdr1,
- DstAddr: lladdr0,
- })
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buffer.NewVectorisedView(len(ip)+len(icmp), []buffer.View{buffer.View(ip), buffer.View(icmp)}),
- })
- e.InjectInbound(ProtocolNumber, pkt)
- }
-
- stats := s.Stats().ICMP.V6PacketsReceived
- invalid := stats.Invalid
- routerOnly := stats.RouterOnlyPacketsDroppedByHost
- typStat := typ.statCounter(stats)
-
- // Initial stat counts should be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
- if got := routerOnly.Value(); got != 0 {
- t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 0", got)
- }
- if got := typStat.Value(); got != 0 {
- t.Fatalf("got %s = %d, want = 0", typ.name, got)
- }
-
- // Without setting checksum, the incoming packet should
- // be invalid.
- handleIPv6Payload(false)
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
- // Router only count should not have increased.
- if got := routerOnly.Value(); got != 0 {
- t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 0", got)
- }
- // Rx count of type typ.typ should not have increased.
- if got := typStat.Value(); got != 0 {
- t.Fatalf("got %s = %d, want = 0", typ.name, got)
- }
-
- // When checksum is set, it should be received.
- handleIPv6Payload(true)
- if got := typStat.Value(); got != 1 {
- t.Fatalf("got %s = %d, want = 1", typ.name, got)
- }
- // Invalid count should not have increased again.
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
- if !isRouter && typ.routerOnly && test.useNeighborCache {
- // Router only count should have increased.
- if got := routerOnly.Value(); got != 1 {
- t.Fatalf("got RouterOnlyPacketsReceivedByHost = %d, want = 1", got)
- }
- }
- })
- }
- }
- })
- }
-}
-
-func TestICMPChecksumValidationWithPayload(t *testing.T) {
- const simpleBodySize = 64
- simpleBody := func(view buffer.View) {
- for i := 0; i < simpleBodySize; i++ {
- view[i] = uint8(i)
- }
- }
-
- const errorICMPBodySize = header.IPv6MinimumSize + simpleBodySize
- errorICMPBody := func(view buffer.View) {
- ip := header.IPv6(view)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: simpleBodySize,
- NextHeader: 10,
- HopLimit: 20,
- SrcAddr: lladdr0,
- DstAddr: lladdr1,
- })
- simpleBody(view[header.IPv6MinimumSize:])
- }
-
- types := []struct {
- name string
- typ header.ICMPv6Type
- size int
- statCounter func(tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter
- payloadSize int
- payload func(buffer.View)
- }{
- {
- "DstUnreachable",
- header.ICMPv6DstUnreachable,
- header.ICMPv6DstUnreachableMinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.DstUnreachable
- },
- errorICMPBodySize,
- errorICMPBody,
- },
- {
- "PacketTooBig",
- header.ICMPv6PacketTooBig,
- header.ICMPv6PacketTooBigMinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.PacketTooBig
- },
- errorICMPBodySize,
- errorICMPBody,
- },
- {
- "TimeExceeded",
- header.ICMPv6TimeExceeded,
- header.ICMPv6MinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.TimeExceeded
- },
- errorICMPBodySize,
- errorICMPBody,
- },
- {
- "ParamProblem",
- header.ICMPv6ParamProblem,
- header.ICMPv6MinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.ParamProblem
- },
- errorICMPBodySize,
- errorICMPBody,
- },
- {
- "EchoRequest",
- header.ICMPv6EchoRequest,
- header.ICMPv6EchoMinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.EchoRequest
- },
- simpleBodySize,
- simpleBody,
- },
- {
- "EchoReply",
- header.ICMPv6EchoReply,
- header.ICMPv6EchoMinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.EchoReply
- },
- simpleBodySize,
- simpleBody,
- },
- }
-
- for _, typ := range types {
- t.Run(typ.name, func(t *testing.T) {
- e := channel.New(10, 1280, linkAddr0)
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(_, _) = %s", err)
- }
-
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err)
- }
- {
- subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: nicID,
- }},
- )
- }
-
- handleIPv6Payload := func(typ header.ICMPv6Type, size, payloadSize int, payloadFn func(buffer.View), checksum bool) {
- icmpSize := size + payloadSize
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + icmpSize)
- icmpHdr := header.ICMPv6(hdr.Prepend(icmpSize))
- icmpHdr.SetType(typ)
- payloadFn(icmpHdr.Payload())
-
- if checksum {
- icmpHdr.SetChecksum(header.ICMPv6Checksum(icmpHdr, lladdr1, lladdr0, buffer.VectorisedView{}))
- }
-
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(icmpSize),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: header.NDPHopLimit,
- SrcAddr: lladdr1,
- DstAddr: lladdr0,
- })
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- })
- e.InjectInbound(ProtocolNumber, pkt)
- }
-
- stats := s.Stats().ICMP.V6PacketsReceived
- invalid := stats.Invalid
- typStat := typ.statCounter(stats)
-
- // Initial stat counts should be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
- if got := typStat.Value(); got != 0 {
- t.Fatalf("got = %d, want = 0", got)
- }
-
- // Without setting checksum, the incoming packet should
- // be invalid.
- handleIPv6Payload(typ.typ, typ.size, typ.payloadSize, typ.payload, false)
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
- // Rx count of type typ.typ should not have increased.
- if got := typStat.Value(); got != 0 {
- t.Fatalf("got = %d, want = 0", got)
- }
-
- // When checksum is set, it should be received.
- handleIPv6Payload(typ.typ, typ.size, typ.payloadSize, typ.payload, true)
- if got := typStat.Value(); got != 1 {
- t.Fatalf("got = %d, want = 0", got)
- }
- // Invalid count should not have increased again.
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
- })
- }
-}
-
-func TestICMPChecksumValidationWithPayloadMultipleViews(t *testing.T) {
- const simpleBodySize = 64
- simpleBody := func(view buffer.View) {
- for i := 0; i < simpleBodySize; i++ {
- view[i] = uint8(i)
- }
- }
-
- const errorICMPBodySize = header.IPv6MinimumSize + simpleBodySize
- errorICMPBody := func(view buffer.View) {
- ip := header.IPv6(view)
- ip.Encode(&header.IPv6Fields{
- PayloadLength: simpleBodySize,
- NextHeader: 10,
- HopLimit: 20,
- SrcAddr: lladdr0,
- DstAddr: lladdr1,
- })
- simpleBody(view[header.IPv6MinimumSize:])
- }
-
- types := []struct {
- name string
- typ header.ICMPv6Type
- size int
- statCounter func(tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter
- payloadSize int
- payload func(buffer.View)
- }{
- {
- "DstUnreachable",
- header.ICMPv6DstUnreachable,
- header.ICMPv6DstUnreachableMinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.DstUnreachable
- },
- errorICMPBodySize,
- errorICMPBody,
- },
- {
- "PacketTooBig",
- header.ICMPv6PacketTooBig,
- header.ICMPv6PacketTooBigMinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.PacketTooBig
- },
- errorICMPBodySize,
- errorICMPBody,
- },
- {
- "TimeExceeded",
- header.ICMPv6TimeExceeded,
- header.ICMPv6MinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.TimeExceeded
- },
- errorICMPBodySize,
- errorICMPBody,
- },
- {
- "ParamProblem",
- header.ICMPv6ParamProblem,
- header.ICMPv6MinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.ParamProblem
- },
- errorICMPBodySize,
- errorICMPBody,
- },
- {
- "EchoRequest",
- header.ICMPv6EchoRequest,
- header.ICMPv6EchoMinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.EchoRequest
- },
- simpleBodySize,
- simpleBody,
- },
- {
- "EchoReply",
- header.ICMPv6EchoReply,
- header.ICMPv6EchoMinimumSize,
- func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.EchoReply
- },
- simpleBodySize,
- simpleBody,
- },
- }
-
- for _, typ := range types {
- t.Run(typ.name, func(t *testing.T) {
- e := channel.New(10, 1280, linkAddr0)
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- })
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
- }
- {
- subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: nicID,
- }},
- )
- }
-
- handleIPv6Payload := func(typ header.ICMPv6Type, size, payloadSize int, payloadFn func(buffer.View), checksum bool) {
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + size)
- icmpHdr := header.ICMPv6(hdr.Prepend(size))
- icmpHdr.SetType(typ)
-
- payload := buffer.NewView(payloadSize)
- payloadFn(payload)
-
- if checksum {
- icmpHdr.SetChecksum(header.ICMPv6Checksum(icmpHdr, lladdr1, lladdr0, payload.ToVectorisedView()))
- }
-
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(size + payloadSize),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: header.NDPHopLimit,
- SrcAddr: lladdr1,
- DstAddr: lladdr0,
- })
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: buffer.NewVectorisedView(header.IPv6MinimumSize+size+payloadSize, []buffer.View{hdr.View(), payload}),
- })
- e.InjectInbound(ProtocolNumber, pkt)
- }
-
- stats := s.Stats().ICMP.V6PacketsReceived
- invalid := stats.Invalid
- typStat := typ.statCounter(stats)
-
- // Initial stat counts should be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
- if got := typStat.Value(); got != 0 {
- t.Fatalf("got = %d, want = 0", got)
- }
-
- // Without setting checksum, the incoming packet should
- // be invalid.
- handleIPv6Payload(typ.typ, typ.size, typ.payloadSize, typ.payload, false)
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
- // Rx count of type typ.typ should not have increased.
- if got := typStat.Value(); got != 0 {
- t.Fatalf("got = %d, want = 0", got)
- }
-
- // When checksum is set, it should be received.
- handleIPv6Payload(typ.typ, typ.size, typ.payloadSize, typ.payload, true)
- if got := typStat.Value(); got != 1 {
- t.Fatalf("got = %d, want = 0", got)
- }
- // Invalid count should not have increased again.
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
- })
- }
-}
-
-func TestLinkAddressRequest(t *testing.T) {
- const nicID = 1
-
- snaddr := header.SolicitedNodeAddr(lladdr0)
- mcaddr := header.EthernetAddressFromMulticastIPv6Address(snaddr)
-
- tests := []struct {
- name string
- nicAddr tcpip.Address
- localAddr tcpip.Address
- remoteLinkAddr tcpip.LinkAddress
-
- expectedErr *tcpip.Error
- expectedRemoteAddr tcpip.Address
- expectedRemoteLinkAddr tcpip.LinkAddress
- }{
- {
- name: "Unicast",
- nicAddr: lladdr1,
- localAddr: lladdr1,
- remoteLinkAddr: linkAddr1,
- expectedRemoteAddr: lladdr0,
- expectedRemoteLinkAddr: linkAddr1,
- },
- {
- name: "Multicast",
- nicAddr: lladdr1,
- localAddr: lladdr1,
- remoteLinkAddr: "",
- expectedRemoteAddr: snaddr,
- expectedRemoteLinkAddr: mcaddr,
- },
- {
- name: "Unicast with unspecified source",
- nicAddr: lladdr1,
- remoteLinkAddr: linkAddr1,
- expectedRemoteAddr: lladdr0,
- expectedRemoteLinkAddr: linkAddr1,
- },
- {
- name: "Multicast with unspecified source",
- nicAddr: lladdr1,
- remoteLinkAddr: "",
- expectedRemoteAddr: snaddr,
- expectedRemoteLinkAddr: mcaddr,
- },
- {
- name: "Unicast with unassigned address",
- localAddr: lladdr1,
- remoteLinkAddr: linkAddr1,
- expectedErr: tcpip.ErrNetworkUnreachable,
- },
- {
- name: "Multicast with unassigned address",
- localAddr: lladdr1,
- remoteLinkAddr: "",
- expectedErr: tcpip.ErrNetworkUnreachable,
- },
- {
- name: "Unicast with no local address available",
- remoteLinkAddr: linkAddr1,
- expectedErr: tcpip.ErrNetworkUnreachable,
- },
- {
- name: "Multicast with no local address available",
- remoteLinkAddr: "",
- expectedErr: tcpip.ErrNetworkUnreachable,
- },
- }
-
- for _, test := range tests {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- })
- p := s.NetworkProtocolInstance(ProtocolNumber)
- linkRes, ok := p.(stack.LinkAddressResolver)
- if !ok {
- t.Fatalf("expected IPv6 protocol to implement stack.LinkAddressResolver")
- }
-
- linkEP := channel.New(defaultChannelSize, defaultMTU, linkAddr0)
- if err := s.CreateNIC(nicID, linkEP); err != nil {
- t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
- }
- if len(test.nicAddr) != 0 {
- if err := s.AddAddress(nicID, ProtocolNumber, test.nicAddr); err != nil {
- t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, ProtocolNumber, test.nicAddr, err)
- }
- }
-
- // We pass a test network interface to LinkAddressRequest with the same NIC
- // ID and link endpoint used by the NIC we created earlier so that we can
- // mock a link address request and observe the packets sent to the link
- // endpoint even though the stack uses the real NIC.
- if err := linkRes.LinkAddressRequest(lladdr0, test.localAddr, test.remoteLinkAddr, &testInterface{LinkEndpoint: linkEP, nicID: nicID}); err != test.expectedErr {
- t.Errorf("got p.LinkAddressRequest(%s, %s, %s, _) = %s, want = %s", lladdr0, test.localAddr, test.remoteLinkAddr, err, test.expectedErr)
- }
-
- if test.expectedErr != nil {
- return
- }
-
- pkt, ok := linkEP.Read()
- if !ok {
- t.Fatal("expected to send a link address request")
- }
- if pkt.Route.RemoteLinkAddress != test.expectedRemoteLinkAddr {
- t.Errorf("got pkt.Route.RemoteLinkAddress = %s, want = %s", pkt.Route.RemoteLinkAddress, test.expectedRemoteLinkAddr)
- }
- if pkt.Route.RemoteAddress != test.expectedRemoteAddr {
- t.Errorf("got pkt.Route.RemoteAddress = %s, want = %s", pkt.Route.RemoteAddress, test.expectedRemoteAddr)
- }
- if pkt.Route.LocalAddress != lladdr1 {
- t.Errorf("got pkt.Route.LocalAddress = %s, want = %s", pkt.Route.LocalAddress, lladdr1)
- }
- checker.IPv6(t, stack.PayloadSince(pkt.Pkt.NetworkHeader()),
- checker.SrcAddr(lladdr1),
- checker.DstAddr(test.expectedRemoteAddr),
- checker.TTL(header.NDPHopLimit),
- checker.NDPNS(
- checker.NDPNSTargetAddress(lladdr0),
- checker.NDPNSOptions([]header.NDPOption{header.NDPSourceLinkLayerAddressOption(linkAddr0)}),
- ))
- }
-}
-
-func TestPacketQueing(t *testing.T) {
- const nicID = 1
-
- var (
- host1NICLinkAddr = tcpip.LinkAddress("\x02\x03\x03\x04\x05\x06")
- host2NICLinkAddr = tcpip.LinkAddress("\x02\x03\x03\x04\x05\x09")
-
- host1IPv6Addr = tcpip.ProtocolAddress{
- Protocol: ProtocolNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: tcpip.Address(net.ParseIP("a::1").To16()),
- PrefixLen: 64,
- },
- }
- host2IPv6Addr = tcpip.ProtocolAddress{
- Protocol: ProtocolNumber,
- AddressWithPrefix: tcpip.AddressWithPrefix{
- Address: tcpip.Address(net.ParseIP("a::2").To16()),
- PrefixLen: 64,
- },
- }
- )
-
- tests := []struct {
- name string
- rxPkt func(*channel.Endpoint)
- checkResp func(*testing.T, *channel.Endpoint)
- }{
- {
- name: "ICMP Error",
- rxPkt: func(e *channel.Endpoint) {
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.UDPMinimumSize)
- u := header.UDP(hdr.Prepend(header.UDPMinimumSize))
- u.Encode(&header.UDPFields{
- SrcPort: 5555,
- DstPort: 80,
- Length: header.UDPMinimumSize,
- })
- sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, host2IPv6Addr.AddressWithPrefix.Address, host1IPv6Addr.AddressWithPrefix.Address, header.UDPMinimumSize)
- sum = header.Checksum(header.UDP([]byte{}), sum)
- u.SetChecksum(^u.CalculateChecksum(sum))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(udp.ProtocolNumber),
- HopLimit: DefaultTTL,
- SrcAddr: host2IPv6Addr.AddressWithPrefix.Address,
- DstAddr: host1IPv6Addr.AddressWithPrefix.Address,
- })
- e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
- },
- checkResp: func(t *testing.T, e *channel.Endpoint) {
- p, ok := e.ReadContext(context.Background())
- if !ok {
- t.Fatalf("timed out waiting for packet")
- }
- if p.Proto != ProtocolNumber {
- t.Errorf("got p.Proto = %d, want = %d", p.Proto, ProtocolNumber)
- }
- if p.Route.RemoteLinkAddress != host2NICLinkAddr {
- t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, host2NICLinkAddr)
- }
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(host1IPv6Addr.AddressWithPrefix.Address),
- checker.DstAddr(host2IPv6Addr.AddressWithPrefix.Address),
- checker.ICMPv6(
- checker.ICMPv6Type(header.ICMPv6DstUnreachable),
- checker.ICMPv6Code(header.ICMPv6PortUnreachable)))
- },
- },
-
- {
- name: "Ping",
- rxPkt: func(e *channel.Endpoint) {
- totalLen := header.IPv6MinimumSize + header.ICMPv6MinimumSize
- hdr := buffer.NewPrependable(totalLen)
- pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6MinimumSize))
- pkt.SetType(header.ICMPv6EchoRequest)
- pkt.SetCode(0)
- pkt.SetChecksum(0)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, host2IPv6Addr.AddressWithPrefix.Address, host1IPv6Addr.AddressWithPrefix.Address, buffer.VectorisedView{}))
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: header.ICMPv6MinimumSize,
- NextHeader: uint8(icmp.ProtocolNumber6),
- HopLimit: DefaultTTL,
- SrcAddr: host2IPv6Addr.AddressWithPrefix.Address,
- DstAddr: host1IPv6Addr.AddressWithPrefix.Address,
- })
- e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
- },
- checkResp: func(t *testing.T, e *channel.Endpoint) {
- p, ok := e.ReadContext(context.Background())
- if !ok {
- t.Fatalf("timed out waiting for packet")
- }
- if p.Proto != ProtocolNumber {
- t.Errorf("got p.Proto = %d, want = %d", p.Proto, ProtocolNumber)
- }
- if p.Route.RemoteLinkAddress != host2NICLinkAddr {
- t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, host2NICLinkAddr)
- }
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(host1IPv6Addr.AddressWithPrefix.Address),
- checker.DstAddr(host2IPv6Addr.AddressWithPrefix.Address),
- checker.ICMPv6(
- checker.ICMPv6Type(header.ICMPv6EchoReply),
- checker.ICMPv6Code(header.ICMPv6UnusedCode)))
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
-
- e := channel.New(1, header.IPv6MinimumMTU, host1NICLinkAddr)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
-
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
- }
- if err := s.AddProtocolAddress(nicID, host1IPv6Addr); err != nil {
- t.Fatalf("s.AddProtocolAddress(%d, %#v): %s", nicID, host1IPv6Addr, err)
- }
-
- s.SetRouteTable([]tcpip.Route{
- tcpip.Route{
- Destination: host1IPv6Addr.AddressWithPrefix.Subnet(),
- NIC: nicID,
- },
- })
-
- // Receive a packet to trigger link resolution before a response is sent.
- test.rxPkt(e)
-
- // Wait for a neighbor solicitation since link address resolution should
- // be performed.
- {
- p, ok := e.ReadContext(context.Background())
- if !ok {
- t.Fatalf("timed out waiting for packet")
- }
- if p.Proto != ProtocolNumber {
- t.Errorf("got Proto = %d, want = %d", p.Proto, ProtocolNumber)
- }
- snmc := header.SolicitedNodeAddr(host2IPv6Addr.AddressWithPrefix.Address)
- if want := header.EthernetAddressFromMulticastIPv6Address(snmc); p.Route.RemoteLinkAddress != want {
- t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, want)
- }
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(host1IPv6Addr.AddressWithPrefix.Address),
- checker.DstAddr(snmc),
- checker.TTL(header.NDPHopLimit),
- checker.NDPNS(
- checker.NDPNSTargetAddress(host2IPv6Addr.AddressWithPrefix.Address),
- checker.NDPNSOptions([]header.NDPOption{header.NDPSourceLinkLayerAddressOption(host1NICLinkAddr)}),
- ))
- }
-
- // Send a neighbor advertisement to complete link address resolution.
- {
- naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + naSize)
- pkt := header.ICMPv6(hdr.Prepend(naSize))
- pkt.SetType(header.ICMPv6NeighborAdvert)
- na := header.NDPNeighborAdvert(pkt.NDPPayload())
- na.SetSolicitedFlag(true)
- na.SetOverrideFlag(true)
- na.SetTargetAddress(host2IPv6Addr.AddressWithPrefix.Address)
- na.Options().Serialize(header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(host2NICLinkAddr),
- })
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, host2IPv6Addr.AddressWithPrefix.Address, host1IPv6Addr.AddressWithPrefix.Address, buffer.VectorisedView{}))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(icmp.ProtocolNumber6),
- HopLimit: header.NDPHopLimit,
- SrcAddr: host2IPv6Addr.AddressWithPrefix.Address,
- DstAddr: host1IPv6Addr.AddressWithPrefix.Address,
- })
- e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
- }
-
- // Expect the response now that the link address has resolved.
- test.checkResp(t, e)
-
- // Since link resolution was already performed, it shouldn't be performed
- // again.
- test.rxPkt(e)
- test.checkResp(t, e)
- })
- }
-}
-
-func TestCallsToNeighborCache(t *testing.T) {
- tests := []struct {
- name string
- createPacket func() header.ICMPv6
- multicast bool
- source tcpip.Address
- destination tcpip.Address
- wantProbeCount int
- wantConfirmationCount int
- }{
- {
- name: "Unicast Neighbor Solicitation without source link-layer address option",
- createPacket: func() header.ICMPv6 {
- nsSize := header.ICMPv6NeighborSolicitMinimumSize + header.NDPLinkLayerAddressSize
- icmp := header.ICMPv6(buffer.NewView(nsSize))
- icmp.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(icmp.NDPPayload())
- ns.SetTargetAddress(lladdr0)
- return icmp
- },
- source: lladdr1,
- destination: lladdr0,
- // "The source link-layer address option SHOULD be included in unicast
- // solicitations." - RFC 4861 section 4.3
- //
- // A Neighbor Advertisement needs to be sent in response, but the
- // Neighbor Cache shouldn't be updated since we have no useful
- // information about the sender.
- wantProbeCount: 0,
- },
- {
- name: "Unicast Neighbor Solicitation with source link-layer address option",
- createPacket: func() header.ICMPv6 {
- nsSize := header.ICMPv6NeighborSolicitMinimumSize + header.NDPLinkLayerAddressSize
- icmp := header.ICMPv6(buffer.NewView(nsSize))
- icmp.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(icmp.NDPPayload())
- ns.SetTargetAddress(lladdr0)
- ns.Options().Serialize(header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(linkAddr1),
- })
- return icmp
- },
- source: lladdr1,
- destination: lladdr0,
- wantProbeCount: 1,
- },
- {
- name: "Multicast Neighbor Solicitation without source link-layer address option",
- createPacket: func() header.ICMPv6 {
- nsSize := header.ICMPv6NeighborSolicitMinimumSize + header.NDPLinkLayerAddressSize
- icmp := header.ICMPv6(buffer.NewView(nsSize))
- icmp.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(icmp.NDPPayload())
- ns.SetTargetAddress(lladdr0)
- return icmp
- },
- source: lladdr1,
- destination: header.SolicitedNodeAddr(lladdr0),
- // "The source link-layer address option MUST be included in multicast
- // solicitations." - RFC 4861 section 4.3
- wantProbeCount: 0,
- },
- {
- name: "Multicast Neighbor Solicitation with source link-layer address option",
- createPacket: func() header.ICMPv6 {
- nsSize := header.ICMPv6NeighborSolicitMinimumSize + header.NDPLinkLayerAddressSize
- icmp := header.ICMPv6(buffer.NewView(nsSize))
- icmp.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(icmp.NDPPayload())
- ns.SetTargetAddress(lladdr0)
- ns.Options().Serialize(header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(linkAddr1),
- })
- return icmp
- },
- source: lladdr1,
- destination: header.SolicitedNodeAddr(lladdr0),
- wantProbeCount: 1,
- },
- {
- name: "Unicast Neighbor Advertisement without target link-layer address option",
- createPacket: func() header.ICMPv6 {
- naSize := header.ICMPv6NeighborAdvertMinimumSize
- icmp := header.ICMPv6(buffer.NewView(naSize))
- icmp.SetType(header.ICMPv6NeighborAdvert)
- na := header.NDPNeighborAdvert(icmp.NDPPayload())
- na.SetSolicitedFlag(true)
- na.SetOverrideFlag(false)
- na.SetTargetAddress(lladdr1)
- return icmp
- },
- source: lladdr1,
- destination: lladdr0,
- // "When responding to unicast solicitations, the target link-layer
- // address option can be omitted since the sender of the solicitation has
- // the correct link-layer address; otherwise, it would not be able to
- // send the unicast solicitation in the first place."
- // - RFC 4861 section 4.4
- wantConfirmationCount: 1,
- },
- {
- name: "Unicast Neighbor Advertisement with target link-layer address option",
- createPacket: func() header.ICMPv6 {
- naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize
- icmp := header.ICMPv6(buffer.NewView(naSize))
- icmp.SetType(header.ICMPv6NeighborAdvert)
- na := header.NDPNeighborAdvert(icmp.NDPPayload())
- na.SetSolicitedFlag(true)
- na.SetOverrideFlag(false)
- na.SetTargetAddress(lladdr1)
- na.Options().Serialize(header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(linkAddr1),
- })
- return icmp
- },
- source: lladdr1,
- destination: lladdr0,
- wantConfirmationCount: 1,
- },
- {
- name: "Multicast Neighbor Advertisement without target link-layer address option",
- createPacket: func() header.ICMPv6 {
- naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize
- icmp := header.ICMPv6(buffer.NewView(naSize))
- icmp.SetType(header.ICMPv6NeighborAdvert)
- na := header.NDPNeighborAdvert(icmp.NDPPayload())
- na.SetSolicitedFlag(false)
- na.SetOverrideFlag(false)
- na.SetTargetAddress(lladdr1)
- return icmp
- },
- source: lladdr1,
- destination: header.IPv6AllNodesMulticastAddress,
- // "Target link-layer address MUST be included for multicast solicitations
- // in order to avoid infinite Neighbor Solicitation "recursion" when the
- // peer node does not have a cache entry to return a Neighbor
- // Advertisements message." - RFC 4861 section 4.4
- wantConfirmationCount: 0,
- },
- {
- name: "Multicast Neighbor Advertisement with target link-layer address option",
- createPacket: func() header.ICMPv6 {
- naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize
- icmp := header.ICMPv6(buffer.NewView(naSize))
- icmp.SetType(header.ICMPv6NeighborAdvert)
- na := header.NDPNeighborAdvert(icmp.NDPPayload())
- na.SetSolicitedFlag(false)
- na.SetOverrideFlag(false)
- na.SetTargetAddress(lladdr1)
- na.Options().Serialize(header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(linkAddr1),
- })
- return icmp
- },
- source: lladdr1,
- destination: header.IPv6AllNodesMulticastAddress,
- wantConfirmationCount: 1,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
- UseNeighborCache: true,
- })
- {
- if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
- t.Fatalf("CreateNIC(_, _) = %s", err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, lladdr0, err)
- }
- }
- {
- subnet, err := tcpip.NewSubnet(lladdr1, tcpip.AddressMask(strings.Repeat("\xff", len(lladdr1))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: nicID,
- }},
- )
- }
-
- netProto := s.NetworkProtocolInstance(ProtocolNumber)
- if netProto == nil {
- t.Fatalf("cannot find protocol instance for network protocol %d", ProtocolNumber)
- }
- nudHandler := &stubNUDHandler{}
- ep := netProto.NewEndpoint(&testInterface{LinkEndpoint: channel.New(0, header.IPv6MinimumMTU, linkAddr0)}, &stubLinkAddressCache{}, nudHandler, &stubDispatcher{})
- defer ep.Close()
-
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
-
- r, err := s.FindRoute(nicID, lladdr0, test.source, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(%d, %s, %s, _, false) = (_, %s), want = (_, nil)", nicID, lladdr0, lladdr1, err)
- }
- defer r.Release()
-
- // TODO(gvisor.dev/issue/4517): Remove the need for this manual patch.
- r.LocalAddress = test.destination
-
- icmp := test.createPacket()
- icmp.SetChecksum(header.ICMPv6Checksum(icmp, r.RemoteAddress, r.LocalAddress, buffer.VectorisedView{}))
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: header.IPv6MinimumSize,
- Data: buffer.View(icmp).ToVectorisedView(),
- })
- ip := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(len(icmp)),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: header.NDPHopLimit,
- SrcAddr: r.RemoteAddress,
- DstAddr: r.LocalAddress,
- })
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
-
- // Confirm the endpoint calls the correct NUDHandler method.
- if nudHandler.probeCount != test.wantProbeCount {
- t.Errorf("got nudHandler.probeCount = %d, want = %d", nudHandler.probeCount, test.wantProbeCount)
- }
- if nudHandler.confirmationCount != test.wantConfirmationCount {
- t.Errorf("got nudHandler.confirmationCount = %d, want = %d", nudHandler.confirmationCount, test.wantConfirmationCount)
- }
- })
- }
-}
diff --git a/pkg/tcpip/network/ipv6/ipv6_state_autogen.go b/pkg/tcpip/network/ipv6/ipv6_state_autogen.go
new file mode 100644
index 000000000..40c67d440
--- /dev/null
+++ b/pkg/tcpip/network/ipv6/ipv6_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package ipv6
diff --git a/pkg/tcpip/network/ipv6/ipv6_test.go b/pkg/tcpip/network/ipv6/ipv6_test.go
deleted file mode 100644
index 1bfcdde25..000000000
--- a/pkg/tcpip/network/ipv6/ipv6_test.go
+++ /dev/null
@@ -1,2823 +0,0 @@
-// Copyright 2019 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 ipv6
-
-import (
- "encoding/hex"
- "fmt"
- "math"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "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/network/testutil"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
- "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
- "gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
- addr1 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
- addr2 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
- // The least significant 3 bytes are the same as addr2 so both addr2 and
- // addr3 will have the same solicited-node address.
- addr3 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x02"
- addr4 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x03"
-
- // Tests use the extension header identifier values as uint8 instead of
- // header.IPv6ExtensionHeaderIdentifier.
- hopByHopExtHdrID = uint8(header.IPv6HopByHopOptionsExtHdrIdentifier)
- routingExtHdrID = uint8(header.IPv6RoutingExtHdrIdentifier)
- fragmentExtHdrID = uint8(header.IPv6FragmentExtHdrIdentifier)
- destinationExtHdrID = uint8(header.IPv6DestinationOptionsExtHdrIdentifier)
- noNextHdrID = uint8(header.IPv6NoNextHeaderIdentifier)
-
- extraHeaderReserve = 50
-)
-
-// testReceiveICMP tests receiving an ICMP packet from src to dst. want is the
-// expected Neighbor Advertisement received count after receiving the packet.
-func testReceiveICMP(t *testing.T, s *stack.Stack, e *channel.Endpoint, src, dst tcpip.Address, want uint64) {
- t.Helper()
-
- // Receive ICMP packet.
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.ICMPv6NeighborAdvertMinimumSize)
- pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborAdvertMinimumSize))
- pkt.SetType(header.ICMPv6NeighborAdvert)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, src, dst, buffer.VectorisedView{}))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: 255,
- SrcAddr: src,
- DstAddr: dst,
- })
-
- e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
-
- stats := s.Stats().ICMP.V6PacketsReceived
-
- if got := stats.NeighborAdvert.Value(); got != want {
- t.Fatalf("got NeighborAdvert = %d, want = %d", got, want)
- }
-}
-
-// testReceiveUDP tests receiving a UDP packet from src to dst. want is the
-// expected UDP received count after receiving the packet.
-func testReceiveUDP(t *testing.T, s *stack.Stack, e *channel.Endpoint, src, dst tcpip.Address, want uint64) {
- t.Helper()
-
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
- defer close(ch)
-
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint failed: %v", err)
- }
- defer ep.Close()
-
- if err := ep.Bind(tcpip.FullAddress{Addr: dst, Port: 80}); err != nil {
- t.Fatalf("ep.Bind(...) failed: %v", err)
- }
-
- // Receive UDP Packet.
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.UDPMinimumSize)
- u := header.UDP(hdr.Prepend(header.UDPMinimumSize))
- u.Encode(&header.UDPFields{
- SrcPort: 5555,
- DstPort: 80,
- Length: header.UDPMinimumSize,
- })
-
- // UDP pseudo-header checksum.
- sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, src, dst, header.UDPMinimumSize)
-
- // UDP checksum
- sum = header.Checksum(header.UDP([]byte{}), sum)
- u.SetChecksum(^u.CalculateChecksum(sum))
-
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(udp.ProtocolNumber),
- HopLimit: 255,
- SrcAddr: src,
- DstAddr: dst,
- })
-
- e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
-
- stat := s.Stats().UDP.PacketsReceived
-
- if got := stat.Value(); got != want {
- t.Fatalf("got UDPPacketsReceived = %d, want = %d", got, want)
- }
-}
-
-func compareFragments(packets []*stack.PacketBuffer, sourcePacket *stack.PacketBuffer, mtu uint32, wantFragments []fragmentInfo, proto tcpip.TransportProtocolNumber) error {
- // sourcePacket does not have its IP Header populated. Let's copy the one
- // from the first fragment.
- source := header.IPv6(packets[0].NetworkHeader().View())
- sourceIPHeadersLen := len(source)
- vv := buffer.NewVectorisedView(sourcePacket.Size(), sourcePacket.Views())
- source = append(source, vv.ToView()...)
-
- var reassembledPayload buffer.VectorisedView
- for i, fragment := range packets {
- // Confirm that the packet is valid.
- allBytes := buffer.NewVectorisedView(fragment.Size(), fragment.Views())
- fragmentIPHeaders := header.IPv6(allBytes.ToView())
- if !fragmentIPHeaders.IsValid(len(fragmentIPHeaders)) {
- return fmt.Errorf("fragment #%d: IP packet is invalid:\n%s", i, hex.Dump(fragmentIPHeaders))
- }
-
- fragmentIPHeadersLength := fragment.NetworkHeader().View().Size()
- if fragmentIPHeadersLength != sourceIPHeadersLen {
- return fmt.Errorf("fragment #%d: got fragmentIPHeadersLength = %d, want = %d", i, fragmentIPHeadersLength, sourceIPHeadersLen)
- }
-
- if got := len(fragmentIPHeaders); got > int(mtu) {
- return fmt.Errorf("fragment #%d: got len(fragmentIPHeaders) = %d, want <= %d", i, got, mtu)
- }
-
- sourceIPHeader := source[:header.IPv6MinimumSize]
- fragmentIPHeader := fragmentIPHeaders[:header.IPv6MinimumSize]
-
- if got := fragmentIPHeaders.PayloadLength(); got != wantFragments[i].payloadSize {
- return fmt.Errorf("fragment #%d: got fragmentIPHeaders.PayloadLength() = %d, want = %d", i, got, wantFragments[i].payloadSize)
- }
-
- // We expect the IPv6 Header to be similar across each fragment, besides the
- // payload length.
- sourceIPHeader.SetPayloadLength(0)
- fragmentIPHeader.SetPayloadLength(0)
- if diff := cmp.Diff(fragmentIPHeader, sourceIPHeader); diff != "" {
- return fmt.Errorf("fragment #%d: fragmentIPHeader mismatch (-want +got):\n%s", i, diff)
- }
-
- if got := fragment.AvailableHeaderBytes(); got != extraHeaderReserve {
- return fmt.Errorf("fragment #%d: got packet.AvailableHeaderBytes() = %d, want = %d", i, got, extraHeaderReserve)
- }
- if fragment.NetworkProtocolNumber != sourcePacket.NetworkProtocolNumber {
- return fmt.Errorf("fragment #%d: got fragment.NetworkProtocolNumber = %d, want = %d", i, fragment.NetworkProtocolNumber, sourcePacket.NetworkProtocolNumber)
- }
-
- if len(packets) > 1 {
- // If the source packet was big enough that it needed fragmentation, let's
- // inspect the fragment header. Because no other extension headers are
- // supported, it will always be the last extension header.
- fragmentHeader := header.IPv6Fragment(fragmentIPHeaders[fragmentIPHeadersLength-header.IPv6FragmentHeaderSize : fragmentIPHeadersLength])
-
- if got := fragmentHeader.More(); got != wantFragments[i].more {
- return fmt.Errorf("fragment #%d: got fragmentHeader.More() = %t, want = %t", i, got, wantFragments[i].more)
- }
- if got := fragmentHeader.FragmentOffset(); got != wantFragments[i].offset {
- return fmt.Errorf("fragment #%d: got fragmentHeader.FragmentOffset() = %d, want = %d", i, got, wantFragments[i].offset)
- }
- if got := fragmentHeader.NextHeader(); got != uint8(proto) {
- return fmt.Errorf("fragment #%d: got fragmentHeader.NextHeader() = %d, want = %d", i, got, uint8(proto))
- }
- }
-
- // Store the reassembled payload as we parse each fragment. The payload
- // includes the Transport header and everything after.
- reassembledPayload.AppendView(fragment.TransportHeader().View())
- reassembledPayload.Append(fragment.Data)
- }
-
- if diff := cmp.Diff(buffer.View(source[sourceIPHeadersLen:]), reassembledPayload.ToView()); diff != "" {
- return fmt.Errorf("reassembledPayload mismatch (-want +got):\n%s", diff)
- }
-
- return nil
-}
-
-// TestReceiveOnAllNodesMulticastAddr tests that IPv6 endpoints receive ICMP and
-// UDP packets destined to the IPv6 link-local all-nodes multicast address.
-func TestReceiveOnAllNodesMulticastAddr(t *testing.T) {
- tests := []struct {
- name string
- protocolFactory stack.TransportProtocolFactory
- rxf func(t *testing.T, s *stack.Stack, e *channel.Endpoint, src, dst tcpip.Address, want uint64)
- }{
- {"ICMP", icmp.NewProtocol6, testReceiveICMP},
- {"UDP", udp.NewProtocol, testReceiveUDP},
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{test.protocolFactory},
- })
- e := channel.New(10, header.IPv6MinimumMTU, linkAddr1)
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(_) = %s", err)
- }
-
- // Should receive a packet destined to the all-nodes
- // multicast address.
- test.rxf(t, s, e, addr1, header.IPv6AllNodesMulticastAddress, 1)
- })
- }
-}
-
-// TestReceiveOnSolicitedNodeAddr tests that IPv6 endpoints receive ICMP and UDP
-// packets destined to the IPv6 solicited-node address of an assigned IPv6
-// address.
-func TestReceiveOnSolicitedNodeAddr(t *testing.T) {
- tests := []struct {
- name string
- protocolFactory stack.TransportProtocolFactory
- rxf func(t *testing.T, s *stack.Stack, e *channel.Endpoint, src, dst tcpip.Address, want uint64)
- }{
- {"ICMP", icmp.NewProtocol6, testReceiveICMP},
- {"UDP", udp.NewProtocol, testReceiveUDP},
- }
-
- snmc := header.SolicitedNodeAddr(addr2)
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{test.protocolFactory},
- })
- e := channel.New(1, header.IPv6MinimumMTU, linkAddr1)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
-
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: header.IPv6EmptySubnet,
- NIC: nicID,
- },
- })
-
- // Should not receive a packet destined to the solicited node address of
- // addr2/addr3 yet as we haven't added those addresses.
- test.rxf(t, s, e, addr1, snmc, 0)
-
- if err := s.AddAddress(nicID, ProtocolNumber, addr2); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, addr2, err)
- }
-
- // Should receive a packet destined to the solicited node address of
- // addr2/addr3 now that we have added added addr2.
- test.rxf(t, s, e, addr1, snmc, 1)
-
- if err := s.AddAddress(nicID, ProtocolNumber, addr3); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, addr3, err)
- }
-
- // Should still receive a packet destined to the solicited node address of
- // addr2/addr3 now that we have added addr3.
- test.rxf(t, s, e, addr1, snmc, 2)
-
- if err := s.RemoveAddress(nicID, addr2); err != nil {
- t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr2, err)
- }
-
- // Should still receive a packet destined to the solicited node address of
- // addr2/addr3 now that we have removed addr2.
- test.rxf(t, s, e, addr1, snmc, 3)
-
- // Make sure addr3's endpoint does not get removed from the NIC by
- // incrementing its reference count with a route.
- r, err := s.FindRoute(nicID, addr3, addr4, ProtocolNumber, false)
- if err != nil {
- t.Fatalf("FindRoute(%d, %s, %s, %d, false): %s", nicID, addr3, addr4, ProtocolNumber, err)
- }
- defer r.Release()
-
- if err := s.RemoveAddress(nicID, addr3); err != nil {
- t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr3, err)
- }
-
- // Should not receive a packet destined to the solicited node address of
- // addr2/addr3 yet as both of them got removed, even though a route using
- // addr3 exists.
- test.rxf(t, s, e, addr1, snmc, 3)
- })
- }
-}
-
-// TestAddIpv6Address tests adding IPv6 addresses.
-func TestAddIpv6Address(t *testing.T) {
- tests := []struct {
- name string
- addr tcpip.Address
- }{
- // This test is in response to b/140943433.
- {
- "Nil",
- tcpip.Address([]byte(nil)),
- },
- {
- "ValidUnicast",
- addr1,
- },
- {
- "ValidLinkLocalUnicast",
- lladdr0,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- })
- if err := s.CreateNIC(1, &stubLinkEndpoint{}); err != nil {
- t.Fatalf("CreateNIC(_) = %s", err)
- }
-
- if err := s.AddAddress(1, ProtocolNumber, test.addr); err != nil {
- t.Fatalf("AddAddress(_, %d, nil) = %s", ProtocolNumber, err)
- }
-
- addr, err := s.GetMainNICAddress(1, header.IPv6ProtocolNumber)
- if err != nil {
- t.Fatalf("stack.GetMainNICAddress(_, _) err = %s", err)
- }
- if addr.Address != test.addr {
- t.Fatalf("got stack.GetMainNICAddress(_, _) = %s, want = %s", addr.Address, test.addr)
- }
- })
- }
-}
-
-func TestReceiveIPv6ExtHdrs(t *testing.T) {
- tests := []struct {
- name string
- extHdr func(nextHdr uint8) ([]byte, uint8)
- shouldAccept bool
- // Should we expect an ICMP response and if so, with what contents?
- expectICMP bool
- ICMPType header.ICMPv6Type
- ICMPCode header.ICMPv6Code
- pointer uint32
- multicast bool
- }{
- {
- name: "None",
- extHdr: func(nextHdr uint8) ([]byte, uint8) { return []byte{}, nextHdr },
- shouldAccept: true,
- expectICMP: false,
- },
- {
- name: "hopbyhop with unknown option skippable action",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Skippable unknown.
- 62, 6, 1, 2, 3, 4, 5, 6,
- }, hopByHopExtHdrID
- },
- shouldAccept: true,
- },
- {
- name: "hopbyhop with unknown option discard action",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard unknown.
- 127, 6, 1, 2, 3, 4, 5, 6,
- }, hopByHopExtHdrID
- },
- shouldAccept: false,
- expectICMP: false,
- },
- {
- name: "hopbyhop with unknown option discard and send icmp action (unicast)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard & send ICMP if option is unknown.
- 191, 6, 1, 2, 3, 4, 5, 6,
- //^ Unknown option.
- }, hopByHopExtHdrID
- },
- shouldAccept: false,
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6UnknownOption,
- pointer: header.IPv6FixedHeaderSize + 8,
- },
- {
- name: "hopbyhop with unknown option discard and send icmp action (multicast)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard & send ICMP if option is unknown.
- 191, 6, 1, 2, 3, 4, 5, 6,
- //^ Unknown option.
- }, hopByHopExtHdrID
- },
- multicast: true,
- shouldAccept: false,
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6UnknownOption,
- pointer: header.IPv6FixedHeaderSize + 8,
- },
- {
- name: "hopbyhop with unknown option discard and send icmp action unless multicast dest (unicast)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard & send ICMP unless packet is for multicast destination if
- // option is unknown.
- 255, 6, 1, 2, 3, 4, 5, 6,
- //^ Unknown option.
- }, hopByHopExtHdrID
- },
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6UnknownOption,
- pointer: header.IPv6FixedHeaderSize + 8,
- },
- {
- name: "hopbyhop with unknown option discard and send icmp action unless multicast dest (multicast)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard & send ICMP unless packet is for multicast destination if
- // option is unknown.
- 255, 6, 1, 2, 3, 4, 5, 6,
- //^ Unknown option.
- }, hopByHopExtHdrID
- },
- multicast: true,
- shouldAccept: false,
- expectICMP: false,
- },
- {
- name: "routing with zero segments left",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 0,
- 1, 0, 2, 3, 4, 5,
- }, routingExtHdrID
- },
- shouldAccept: true,
- },
- {
- name: "routing with non-zero segments left",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 0,
- 1, 1, 2, 3, 4, 5,
- }, routingExtHdrID
- },
- shouldAccept: false,
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6ErroneousHeader,
- pointer: header.IPv6FixedHeaderSize + 2,
- },
- {
- name: "atomic fragment with zero ID",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 0,
- 0, 0, 0, 0, 0, 0,
- }, fragmentExtHdrID
- },
- shouldAccept: true,
- },
- {
- name: "atomic fragment with non-zero ID",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 0,
- 0, 0, 1, 2, 3, 4,
- }, fragmentExtHdrID
- },
- shouldAccept: true,
- expectICMP: false,
- },
- {
- name: "fragment",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 0,
- 1, 0, 1, 2, 3, 4,
- }, fragmentExtHdrID
- },
- shouldAccept: false,
- expectICMP: false,
- },
- {
- name: "No next header",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{},
- noNextHdrID
- },
- shouldAccept: false,
- expectICMP: false,
- },
- {
- name: "destination with unknown option skippable action",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Skippable unknown.
- 62, 6, 1, 2, 3, 4, 5, 6,
- }, destinationExtHdrID
- },
- shouldAccept: true,
- expectICMP: false,
- },
- {
- name: "destination with unknown option discard action",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard unknown.
- 127, 6, 1, 2, 3, 4, 5, 6,
- }, destinationExtHdrID
- },
- shouldAccept: false,
- expectICMP: false,
- },
- {
- name: "destination with unknown option discard and send icmp action (unicast)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard & send ICMP if option is unknown.
- 191, 6, 1, 2, 3, 4, 5, 6,
- //^ 191 is an unknown option.
- }, destinationExtHdrID
- },
- shouldAccept: false,
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6UnknownOption,
- pointer: header.IPv6FixedHeaderSize + 8,
- },
- {
- name: "destination with unknown option discard and send icmp action (muilticast)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard & send ICMP if option is unknown.
- 191, 6, 1, 2, 3, 4, 5, 6,
- //^ 191 is an unknown option.
- }, destinationExtHdrID
- },
- multicast: true,
- shouldAccept: false,
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6UnknownOption,
- pointer: header.IPv6FixedHeaderSize + 8,
- },
- {
- name: "destination with unknown option discard and send icmp action unless multicast dest (unicast)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard & send ICMP unless packet is for multicast destination if
- // option is unknown.
- 255, 6, 1, 2, 3, 4, 5, 6,
- //^ 255 is unknown.
- }, destinationExtHdrID
- },
- shouldAccept: false,
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6UnknownOption,
- pointer: header.IPv6FixedHeaderSize + 8,
- },
- {
- name: "destination with unknown option discard and send icmp action unless multicast dest (multicast)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Discard & send ICMP unless packet is for multicast destination if
- // option is unknown.
- 255, 6, 1, 2, 3, 4, 5, 6,
- //^ 255 is unknown.
- }, destinationExtHdrID
- },
- shouldAccept: false,
- expectICMP: false,
- multicast: true,
- },
- {
- name: "atomic fragment - routing",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- // Fragment extension header.
- routingExtHdrID, 0, 0, 0, 1, 2, 3, 4,
-
- // Routing extension header.
- nextHdr, 0, 1, 0, 2, 3, 4, 5,
- }, fragmentExtHdrID
- },
- shouldAccept: true,
- },
- {
- name: "hop by hop (with skippable unknown) - routing",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- // Hop By Hop extension header with skippable unknown option.
- routingExtHdrID, 0, 62, 4, 1, 2, 3, 4,
-
- // Routing extension header.
- nextHdr, 0, 1, 0, 2, 3, 4, 5,
- }, hopByHopExtHdrID
- },
- shouldAccept: true,
- },
- {
- name: "routing - hop by hop (with skippable unknown)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- // Routing extension header.
- hopByHopExtHdrID, 0, 1, 0, 2, 3, 4, 5,
- // ^^^ The HopByHop extension header may not appear after the first
- // extension header.
-
- // Hop By Hop extension header with skippable unknown option.
- nextHdr, 0, 62, 4, 1, 2, 3, 4,
- }, routingExtHdrID
- },
- shouldAccept: false,
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6UnknownHeader,
- pointer: header.IPv6FixedHeaderSize,
- },
- {
- name: "routing - hop by hop (with send icmp unknown)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- // Routing extension header.
- hopByHopExtHdrID, 0, 1, 0, 2, 3, 4, 5,
- // ^^^ The HopByHop extension header may not appear after the first
- // extension header.
-
- nextHdr, 1,
-
- // Skippable unknown.
- 63, 4, 1, 2, 3, 4,
-
- // Skippable unknown.
- 191, 6, 1, 2, 3, 4, 5, 6,
- }, routingExtHdrID
- },
- shouldAccept: false,
- expectICMP: true,
- ICMPType: header.ICMPv6ParamProblem,
- ICMPCode: header.ICMPv6UnknownHeader,
- pointer: header.IPv6FixedHeaderSize,
- },
- {
- name: "No next header",
- extHdr: func(nextHdr uint8) ([]byte, uint8) { return []byte{}, noNextHdrID },
- shouldAccept: false,
- },
- {
- name: "hopbyhop (with skippable unknown) - routing - atomic fragment - destination (with skippable unknown)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- // Hop By Hop extension header with skippable unknown option.
- routingExtHdrID, 0, 62, 4, 1, 2, 3, 4,
-
- // Routing extension header.
- fragmentExtHdrID, 0, 1, 0, 2, 3, 4, 5,
-
- // Fragment extension header.
- destinationExtHdrID, 0, 0, 0, 1, 2, 3, 4,
-
- // Destination extension header with skippable unknown option.
- nextHdr, 0, 63, 4, 1, 2, 3, 4,
- }, hopByHopExtHdrID
- },
- shouldAccept: true,
- },
- {
- name: "hopbyhop (with discard unknown) - routing - atomic fragment - destination (with skippable unknown)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- // Hop By Hop extension header with discard action for unknown option.
- routingExtHdrID, 0, 65, 4, 1, 2, 3, 4,
-
- // Routing extension header.
- fragmentExtHdrID, 0, 1, 0, 2, 3, 4, 5,
-
- // Fragment extension header.
- destinationExtHdrID, 0, 0, 0, 1, 2, 3, 4,
-
- // Destination extension header with skippable unknown option.
- nextHdr, 0, 63, 4, 1, 2, 3, 4,
- }, hopByHopExtHdrID
- },
- shouldAccept: false,
- expectICMP: false,
- },
- {
- name: "hopbyhop (with skippable unknown) - routing - atomic fragment - destination (with discard unknown)",
- extHdr: func(nextHdr uint8) ([]byte, uint8) {
- return []byte{
- // Hop By Hop extension header with skippable unknown option.
- routingExtHdrID, 0, 62, 4, 1, 2, 3, 4,
-
- // Routing extension header.
- fragmentExtHdrID, 0, 1, 0, 2, 3, 4, 5,
-
- // Fragment extension header.
- destinationExtHdrID, 0, 0, 0, 1, 2, 3, 4,
-
- // Destination extension header with discard action for unknown
- // option.
- nextHdr, 0, 65, 4, 1, 2, 3, 4,
- }, hopByHopExtHdrID
- },
- shouldAccept: false,
- expectICMP: false,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
- e := channel.New(1, header.IPv6MinimumMTU, linkAddr1)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, addr2); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, addr2, err)
- }
-
- // Add a default route so that a return packet knows where to go.
- s.SetRouteTable([]tcpip.Route{
- {
- Destination: header.IPv6EmptySubnet,
- NIC: nicID,
- },
- })
-
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
- defer close(ch)
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, ProtocolNumber, err)
- }
- defer ep.Close()
-
- bindAddr := tcpip.FullAddress{Addr: addr2, Port: 80}
- if err := ep.Bind(bindAddr); err != nil {
- t.Fatalf("Bind(%+v): %s", bindAddr, err)
- }
-
- udpPayload := []byte{1, 2, 3, 4, 5, 6, 7, 8}
- udpLength := header.UDPMinimumSize + len(udpPayload)
- extHdrBytes, ipv6NextHdr := test.extHdr(uint8(header.UDPProtocolNumber))
- extHdrLen := len(extHdrBytes)
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + extHdrLen + udpLength)
-
- // Serialize UDP message.
- u := header.UDP(hdr.Prepend(udpLength))
- u.Encode(&header.UDPFields{
- SrcPort: 5555,
- DstPort: 80,
- Length: uint16(udpLength),
- })
- copy(u.Payload(), udpPayload)
- sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, addr1, addr2, uint16(udpLength))
- sum = header.Checksum(udpPayload, sum)
- u.SetChecksum(^u.CalculateChecksum(sum))
-
- // Copy extension header bytes between the UDP message and the IPv6
- // fixed header.
- copy(hdr.Prepend(extHdrLen), extHdrBytes)
-
- // Serialize IPv6 fixed header.
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- dstAddr := tcpip.Address(addr2)
- if test.multicast {
- dstAddr = header.IPv6AllNodesMulticastAddress
- }
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: ipv6NextHdr,
- HopLimit: 255,
- SrcAddr: addr1,
- DstAddr: dstAddr,
- })
-
- e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
-
- stats := s.Stats().UDP.PacketsReceived
-
- if !test.shouldAccept {
- if got := stats.Value(); got != 0 {
- t.Errorf("got UDP Rx Packets = %d, want = 0", got)
- }
-
- if !test.expectICMP {
- if p, ok := e.Read(); ok {
- t.Fatalf("unexpected packet received: %#v", p)
- }
- return
- }
-
- // ICMP required.
- p, ok := e.Read()
- if !ok {
- t.Fatalf("expected packet wasn't written out")
- }
-
- // Pack the output packet into a single buffer.View as the checkers
- // assume that.
- vv := buffer.NewVectorisedView(p.Pkt.Size(), p.Pkt.Views())
- pkt := vv.ToView()
- if got, want := len(pkt), header.IPv6FixedHeaderSize+header.ICMPv6MinimumSize+hdr.UsedLength(); got != want {
- t.Fatalf("got an ICMP packet of size = %d, want = %d", got, want)
- }
-
- ipHdr := header.IPv6(pkt)
- checker.IPv6(t, ipHdr, checker.ICMPv6(
- checker.ICMPv6Type(test.ICMPType),
- checker.ICMPv6Code(test.ICMPCode)))
-
- // We know we are looking at no extension headers in the error ICMP
- // packets.
- icmpPkt := header.ICMPv6(ipHdr.Payload())
- // We know we sent small packets that won't be truncated when reflected
- // back to us.
- originalPacket := icmpPkt.Payload()
- if got, want := icmpPkt.TypeSpecific(), test.pointer; got != want {
- t.Errorf("unexpected ICMPv6 pointer, got = %d, want = %d\n", got, want)
- }
- if diff := cmp.Diff(hdr.View(), buffer.View(originalPacket)); diff != "" {
- t.Errorf("ICMPv6 payload mismatch (-want +got):\n%s", diff)
- }
- return
- }
-
- // Expect a UDP packet.
- if got := stats.Value(); got != 1 {
- t.Errorf("got UDP Rx Packets = %d, want = 1", got)
- }
- gotPayload, _, err := ep.Read(nil)
- if err != nil {
- t.Fatalf("Read(nil): %s", err)
- }
- if diff := cmp.Diff(buffer.View(udpPayload), gotPayload); diff != "" {
- t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
- }
-
- // Should not have any more UDP packets.
- if gotPayload, _, err := ep.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("got Read(nil) = (%x, _, %v), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
- }
- })
- }
-}
-
-// fragmentData holds the IPv6 payload for a fragmented IPv6 packet.
-type fragmentData struct {
- srcAddr tcpip.Address
- dstAddr tcpip.Address
- nextHdr uint8
- data buffer.VectorisedView
-}
-
-func TestReceiveIPv6Fragments(t *testing.T) {
- const (
- udpPayload1Length = 256
- udpPayload2Length = 128
- // Used to test cases where the fragment blocks are not a multiple of
- // the fragment block size of 8 (RFC 8200 section 4.5).
- udpPayload3Length = 127
- udpPayload4Length = header.IPv6MaximumPayloadSize - header.UDPMinimumSize
- fragmentExtHdrLen = 8
- // Note, not all routing extension headers will be 8 bytes but this test
- // uses 8 byte routing extension headers for most sub tests.
- routingExtHdrLen = 8
- )
-
- udpGen := func(payload []byte, multiplier uint8, src, dst tcpip.Address) buffer.View {
- payloadLen := len(payload)
- for i := 0; i < payloadLen; i++ {
- payload[i] = uint8(i) * multiplier
- }
-
- udpLength := header.UDPMinimumSize + payloadLen
-
- hdr := buffer.NewPrependable(udpLength)
- u := header.UDP(hdr.Prepend(udpLength))
- u.Encode(&header.UDPFields{
- SrcPort: 5555,
- DstPort: 80,
- Length: uint16(udpLength),
- })
- copy(u.Payload(), payload)
- sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, src, dst, uint16(udpLength))
- sum = header.Checksum(payload, sum)
- u.SetChecksum(^u.CalculateChecksum(sum))
- return hdr.View()
- }
-
- var udpPayload1Addr1ToAddr2Buf [udpPayload1Length]byte
- udpPayload1Addr1ToAddr2 := udpPayload1Addr1ToAddr2Buf[:]
- ipv6Payload1Addr1ToAddr2 := udpGen(udpPayload1Addr1ToAddr2, 1, addr1, addr2)
-
- var udpPayload1Addr3ToAddr2Buf [udpPayload1Length]byte
- udpPayload1Addr3ToAddr2 := udpPayload1Addr3ToAddr2Buf[:]
- ipv6Payload1Addr3ToAddr2 := udpGen(udpPayload1Addr3ToAddr2, 4, addr3, addr2)
-
- var udpPayload2Addr1ToAddr2Buf [udpPayload2Length]byte
- udpPayload2Addr1ToAddr2 := udpPayload2Addr1ToAddr2Buf[:]
- ipv6Payload2Addr1ToAddr2 := udpGen(udpPayload2Addr1ToAddr2, 2, addr1, addr2)
-
- var udpPayload3Addr1ToAddr2Buf [udpPayload3Length]byte
- udpPayload3Addr1ToAddr2 := udpPayload3Addr1ToAddr2Buf[:]
- ipv6Payload3Addr1ToAddr2 := udpGen(udpPayload3Addr1ToAddr2, 3, addr1, addr2)
-
- var udpPayload4Addr1ToAddr2Buf [udpPayload4Length]byte
- udpPayload4Addr1ToAddr2 := udpPayload4Addr1ToAddr2Buf[:]
- ipv6Payload4Addr1ToAddr2 := udpGen(udpPayload4Addr1ToAddr2, 4, addr1, addr2)
-
- tests := []struct {
- name string
- expectedPayload []byte
- fragments []fragmentData
- expectedPayloads [][]byte
- }{
- {
- name: "No fragmentation",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: uint8(header.UDPProtocolNumber),
- data: ipv6Payload1Addr1ToAddr2.ToVectorisedView(),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Atomic fragment",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2),
- []buffer.View{
- // Fragment extension header.
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 0, 0, 0, 0, 0}),
-
- ipv6Payload1Addr1ToAddr2,
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Atomic fragment with size not a multiple of fragment block size",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload3Addr1ToAddr2),
- []buffer.View{
- // Fragment extension header.
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 0, 0, 0, 0, 0}),
-
- ipv6Payload3Addr1ToAddr2,
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload3Addr1ToAddr2},
- },
- {
- name: "Two fragments",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Two fragments out of order",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Two fragments with different Next Header values",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- // NextHeader value is different than the one in the first fragment, so
- // this NextHeader should be ignored.
- buffer.View([]byte{uint8(header.IPv6NoNextHeaderIdentifier), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Two fragments with last fragment size not a multiple of fragment block size",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload3Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload3Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload3Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload3Addr1ToAddr2},
- },
- {
- name: "Two fragments with first fragment size not a multiple of fragment block size",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+63,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload3Addr1ToAddr2[:63],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload3Addr1ToAddr2)-63,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload3Addr1ToAddr2[63:],
- },
- ),
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two fragments with different IDs",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 2
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 2}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two fragments reassembled into a maximum UDP packet",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+65520,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload4Addr1ToAddr2[:65520],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload4Addr1ToAddr2)-65520,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8190, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 255, 240, 0, 0, 0, 1}),
-
- ipv6Payload4Addr1ToAddr2[65520:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload4Addr1ToAddr2},
- },
- {
- name: "Two fragments with per-fragment routing header with zero segments left",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: routingExtHdrID,
- data: buffer.NewVectorisedView(
- routingExtHdrLen+fragmentExtHdrLen+64,
- []buffer.View{
- // Routing extension header.
- //
- // Segments left = 0.
- buffer.View([]byte{fragmentExtHdrID, 0, 1, 0, 2, 3, 4, 5}),
-
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: routingExtHdrID,
- data: buffer.NewVectorisedView(
- routingExtHdrLen+fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Routing extension header.
- //
- // Segments left = 0.
- buffer.View([]byte{fragmentExtHdrID, 0, 1, 0, 2, 3, 4, 5}),
-
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Two fragments with per-fragment routing header with non-zero segments left",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: routingExtHdrID,
- data: buffer.NewVectorisedView(
- routingExtHdrLen+fragmentExtHdrLen+64,
- []buffer.View{
- // Routing extension header.
- //
- // Segments left = 1.
- buffer.View([]byte{fragmentExtHdrID, 0, 1, 1, 2, 3, 4, 5}),
-
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: routingExtHdrID,
- data: buffer.NewVectorisedView(
- routingExtHdrLen+fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Routing extension header.
- //
- // Segments left = 1.
- buffer.View([]byte{fragmentExtHdrID, 0, 1, 1, 2, 3, 4, 5}),
-
- // Fragment extension header.
- //
- // Fragment offset = 9, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 72, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two fragments with routing header with zero segments left",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- routingExtHdrLen+fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{routingExtHdrID, 0, 0, 1, 0, 0, 0, 1}),
-
- // Routing extension header.
- //
- // Segments left = 0.
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 1, 0, 2, 3, 4, 5}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 9, More = false, ID = 1
- buffer.View([]byte{routingExtHdrID, 0, 0, 72, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2},
- },
- {
- name: "Two fragments with routing header with non-zero segments left",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- routingExtHdrLen+fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{routingExtHdrID, 0, 0, 1, 0, 0, 0, 1}),
-
- // Routing extension header.
- //
- // Segments left = 1.
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 1, 1, 2, 3, 4, 5}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 9, More = false, ID = 1
- buffer.View([]byte{routingExtHdrID, 0, 0, 72, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two fragments with routing header with zero segments left across fragments",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- // The length of this payload is fragmentExtHdrLen+8 because the
- // first 8 bytes of the 16 byte routing extension header is in
- // this fragment.
- fragmentExtHdrLen+8,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{routingExtHdrID, 0, 0, 1, 0, 0, 0, 1}),
-
- // Routing extension header (part 1)
- //
- // Segments left = 0.
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 1, 1, 0, 2, 3, 4, 5}),
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- // The length of this payload is
- // fragmentExtHdrLen+8+len(ipv6Payload1Addr1ToAddr2) because the last 8 bytes of
- // the 16 byte routing extension header is in this fagment.
- fragmentExtHdrLen+8+len(ipv6Payload1Addr1ToAddr2),
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 1, More = false, ID = 1
- buffer.View([]byte{routingExtHdrID, 0, 0, 8, 0, 0, 0, 1}),
-
- // Routing extension header (part 2)
- buffer.View([]byte{6, 7, 8, 9, 10, 11, 12, 13}),
-
- ipv6Payload1Addr1ToAddr2,
- },
- ),
- },
- },
- expectedPayloads: nil,
- },
- {
- name: "Two fragments with routing header with non-zero segments left across fragments",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- // The length of this payload is fragmentExtHdrLen+8 because the
- // first 8 bytes of the 16 byte routing extension header is in
- // this fragment.
- fragmentExtHdrLen+8,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{routingExtHdrID, 0, 0, 1, 0, 0, 0, 1}),
-
- // Routing extension header (part 1)
- //
- // Segments left = 1.
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 1, 1, 1, 2, 3, 4, 5}),
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- // The length of this payload is
- // fragmentExtHdrLen+8+len(ipv6Payload1Addr1ToAddr2) because the last 8 bytes of
- // the 16 byte routing extension header is in this fagment.
- fragmentExtHdrLen+8+len(ipv6Payload1Addr1ToAddr2),
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 1, More = false, ID = 1
- buffer.View([]byte{routingExtHdrID, 0, 0, 8, 0, 0, 0, 1}),
-
- // Routing extension header (part 2)
- buffer.View([]byte{6, 7, 8, 9, 10, 11, 12, 13}),
-
- ipv6Payload1Addr1ToAddr2,
- },
- ),
- },
- },
- expectedPayloads: nil,
- },
- // As per RFC 6946, IPv6 atomic fragments MUST NOT interfere with "normal"
- // fragmented traffic.
- {
- name: "Two fragments with atomic",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- // This fragment has the same ID as the other fragments but is an atomic
- // fragment. It should not interfere with the other fragments.
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload2Addr1ToAddr2),
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 0, 0, 0, 0, 1}),
-
- ipv6Payload2Addr1ToAddr2,
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload2Addr1ToAddr2, udpPayload1Addr1ToAddr2},
- },
- {
- name: "Two interleaved fragmented packets",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+32,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 2
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 2}),
-
- ipv6Payload2Addr1ToAddr2[:32],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload2Addr1ToAddr2)-32,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 4, More = false, ID = 2
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 32, 0, 0, 0, 2}),
-
- ipv6Payload2Addr1ToAddr2[32:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2, udpPayload2Addr1ToAddr2},
- },
- {
- name: "Two interleaved fragmented packets from different sources but with same ID",
- fragments: []fragmentData{
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[:64],
- },
- ),
- },
- {
- srcAddr: addr3,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+32,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 0, More = true, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 1, 0, 0, 0, 1}),
-
- ipv6Payload1Addr3ToAddr2[:32],
- },
- ),
- },
- {
- srcAddr: addr1,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-64,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 8, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 64, 0, 0, 0, 1}),
-
- ipv6Payload1Addr1ToAddr2[64:],
- },
- ),
- },
- {
- srcAddr: addr3,
- dstAddr: addr2,
- nextHdr: fragmentExtHdrID,
- data: buffer.NewVectorisedView(
- fragmentExtHdrLen+len(ipv6Payload1Addr1ToAddr2)-32,
- []buffer.View{
- // Fragment extension header.
- //
- // Fragment offset = 4, More = false, ID = 1
- buffer.View([]byte{uint8(header.UDPProtocolNumber), 0, 0, 32, 0, 0, 0, 1}),
-
- ipv6Payload1Addr3ToAddr2[32:],
- },
- ),
- },
- },
- expectedPayloads: [][]byte{udpPayload1Addr1ToAddr2, udpPayload1Addr3ToAddr2},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
- })
- e := channel.New(0, header.IPv6MinimumMTU, linkAddr1)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, addr2); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, addr2, err)
- }
-
- wq := waiter.Queue{}
- we, ch := waiter.NewChannelEntry(nil)
- wq.EventRegister(&we, waiter.EventIn)
- defer wq.EventUnregister(&we)
- defer close(ch)
- ep, err := s.NewEndpoint(udp.ProtocolNumber, ProtocolNumber, &wq)
- if err != nil {
- t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, ProtocolNumber, err)
- }
- defer ep.Close()
-
- bindAddr := tcpip.FullAddress{Addr: addr2, Port: 80}
- if err := ep.Bind(bindAddr); err != nil {
- t.Fatalf("Bind(%+v): %s", bindAddr, err)
- }
-
- for _, f := range test.fragments {
- hdr := buffer.NewPrependable(header.IPv6MinimumSize)
-
- // Serialize IPv6 fixed header.
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(f.data.Size()),
- NextHeader: f.nextHdr,
- HopLimit: 255,
- SrcAddr: f.srcAddr,
- DstAddr: f.dstAddr,
- })
-
- vv := hdr.View().ToVectorisedView()
- vv.Append(f.data)
-
- e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: vv,
- }))
- }
-
- if got, want := s.Stats().UDP.PacketsReceived.Value(), uint64(len(test.expectedPayloads)); got != want {
- t.Errorf("got UDP Rx Packets = %d, want = %d", got, want)
- }
-
- for i, p := range test.expectedPayloads {
- gotPayload, _, err := ep.Read(nil)
- if err != nil {
- t.Fatalf("(i=%d) Read(nil): %s", i, err)
- }
- if diff := cmp.Diff(buffer.View(p), gotPayload); diff != "" {
- t.Errorf("(i=%d) got UDP payload mismatch (-want +got):\n%s", i, diff)
- }
- }
-
- if gotPayload, _, err := ep.Read(nil); err != tcpip.ErrWouldBlock {
- t.Fatalf("(last) got Read(nil) = (%x, _, %v), want = (_, _, %s)", gotPayload, err, tcpip.ErrWouldBlock)
- }
- })
- }
-}
-
-func TestInvalidIPv6Fragments(t *testing.T) {
- const (
- addr1 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
- addr2 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
- linkAddr1 = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
- nicID = 1
- hoplimit = 255
- ident = 1
- data = "TEST_INVALID_IPV6_FRAGMENTS"
- )
-
- type fragmentData struct {
- ipv6Fields header.IPv6Fields
- ipv6FragmentFields header.IPv6FragmentFields
- payload []byte
- }
-
- tests := []struct {
- name string
- fragments []fragmentData
- wantMalformedIPPackets uint64
- wantMalformedFragments uint64
- expectICMP bool
- expectICMPType header.ICMPv6Type
- expectICMPCode header.ICMPv6Code
- expectICMPTypeSpecific uint32
- }{
- {
- name: "fragment size is not a multiple of 8 and the M flag is true",
- fragments: []fragmentData{
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: header.IPv6FragmentHeaderSize + 9,
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 0 >> 3,
- M: true,
- Identification: ident,
- },
- payload: []byte(data)[:9],
- },
- },
- wantMalformedIPPackets: 1,
- wantMalformedFragments: 1,
- expectICMP: true,
- expectICMPType: header.ICMPv6ParamProblem,
- expectICMPCode: header.ICMPv6ErroneousHeader,
- expectICMPTypeSpecific: header.IPv6PayloadLenOffset,
- },
- {
- name: "fragments reassembled into a payload exceeding the max IPv6 payload size",
- fragments: []fragmentData{
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: header.IPv6FragmentHeaderSize + 16,
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: ((header.IPv6MaximumPayloadSize + 1) - 16) >> 3,
- M: false,
- Identification: ident,
- },
- payload: []byte(data)[:16],
- },
- },
- wantMalformedIPPackets: 1,
- wantMalformedFragments: 1,
- expectICMP: true,
- expectICMPType: header.ICMPv6ParamProblem,
- expectICMPCode: header.ICMPv6ErroneousHeader,
- expectICMPTypeSpecific: header.IPv6MinimumSize + 2, /* offset for 'Fragment Offset' in the fragment header */
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{
- NewProtocol,
- },
- })
- e := channel.New(1, 1500, linkAddr1)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, addr2); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, addr2, err)
- }
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv6EmptySubnet,
- NIC: nicID,
- }})
-
- var expectICMPPayload buffer.View
- for _, f := range test.fragments {
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.IPv6FragmentHeaderSize)
-
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize + header.IPv6FragmentHeaderSize))
- ip.Encode(&f.ipv6Fields)
-
- fragHDR := header.IPv6Fragment(hdr.View()[header.IPv6MinimumSize:])
- fragHDR.Encode(&f.ipv6FragmentFields)
-
- vv := hdr.View().ToVectorisedView()
- vv.AppendView(f.payload)
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: vv,
- })
-
- if test.expectICMP {
- expectICMPPayload = stack.PayloadSince(pkt.NetworkHeader())
- }
-
- e.InjectInbound(ProtocolNumber, pkt)
- }
-
- if got, want := s.Stats().IP.MalformedPacketsReceived.Value(), test.wantMalformedIPPackets; got != want {
- t.Errorf("got Stats.IP.MalformedPacketsReceived = %d, want = %d", got, want)
- }
- if got, want := s.Stats().IP.MalformedFragmentsReceived.Value(), test.wantMalformedFragments; got != want {
- t.Errorf("got Stats.IP.MalformedFragmentsReceived = %d, want = %d", got, want)
- }
-
- reply, ok := e.Read()
- if !test.expectICMP {
- if ok {
- t.Fatalf("unexpected ICMP error message received: %#v", reply)
- }
- return
- }
- if !ok {
- t.Fatal("expected ICMP error message missing")
- }
-
- checker.IPv6(t, stack.PayloadSince(reply.Pkt.NetworkHeader()),
- checker.SrcAddr(addr2),
- checker.DstAddr(addr1),
- checker.IPFullLength(uint16(header.IPv6MinimumSize+header.ICMPv6MinimumSize+expectICMPPayload.Size())),
- checker.ICMPv6(
- checker.ICMPv6Type(test.expectICMPType),
- checker.ICMPv6Code(test.expectICMPCode),
- checker.ICMPv6TypeSpecific(test.expectICMPTypeSpecific),
- checker.ICMPv6Payload([]byte(expectICMPPayload)),
- ),
- )
- })
- }
-}
-
-func TestFragmentReassemblyTimeout(t *testing.T) {
- const (
- addr1 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
- addr2 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
- linkAddr1 = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
- nicID = 1
- hoplimit = 255
- ident = 1
- data = "TEST_FRAGMENT_REASSEMBLY_TIMEOUT"
- )
-
- type fragmentData struct {
- ipv6Fields header.IPv6Fields
- ipv6FragmentFields header.IPv6FragmentFields
- payload []byte
- }
-
- tests := []struct {
- name string
- fragments []fragmentData
- expectICMP bool
- }{
- {
- name: "first fragment only",
- fragments: []fragmentData{
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: header.IPv6FragmentHeaderSize + 16,
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 0,
- M: true,
- Identification: ident,
- },
- payload: []byte(data)[:16],
- },
- },
- expectICMP: true,
- },
- {
- name: "two first fragments",
- fragments: []fragmentData{
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: header.IPv6FragmentHeaderSize + 16,
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 0,
- M: true,
- Identification: ident,
- },
- payload: []byte(data)[:16],
- },
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: header.IPv6FragmentHeaderSize + 16,
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 0,
- M: true,
- Identification: ident,
- },
- payload: []byte(data)[:16],
- },
- },
- expectICMP: true,
- },
- {
- name: "second fragment only",
- fragments: []fragmentData{
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: uint16(header.IPv6FragmentHeaderSize + len(data) - 16),
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 8,
- M: false,
- Identification: ident,
- },
- payload: []byte(data)[16:],
- },
- },
- expectICMP: false,
- },
- {
- name: "two fragments with a gap",
- fragments: []fragmentData{
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: header.IPv6FragmentHeaderSize + 16,
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 0,
- M: true,
- Identification: ident,
- },
- payload: []byte(data)[:16],
- },
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: uint16(header.IPv6FragmentHeaderSize + len(data) - 16),
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 8,
- M: false,
- Identification: ident,
- },
- payload: []byte(data)[16:],
- },
- },
- expectICMP: true,
- },
- {
- name: "two fragments with a gap in reverse order",
- fragments: []fragmentData{
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: uint16(header.IPv6FragmentHeaderSize + len(data) - 16),
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 8,
- M: false,
- Identification: ident,
- },
- payload: []byte(data)[16:],
- },
- {
- ipv6Fields: header.IPv6Fields{
- PayloadLength: header.IPv6FragmentHeaderSize + 16,
- NextHeader: header.IPv6FragmentHeader,
- HopLimit: hoplimit,
- SrcAddr: addr1,
- DstAddr: addr2,
- },
- ipv6FragmentFields: header.IPv6FragmentFields{
- NextHeader: uint8(header.UDPProtocolNumber),
- FragmentOffset: 0,
- M: true,
- Identification: ident,
- },
- payload: []byte(data)[:16],
- },
- },
- expectICMP: true,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- clock := faketime.NewManualClock()
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{
- NewProtocol,
- },
- Clock: clock,
- })
-
- e := channel.New(1, 1500, linkAddr1)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, addr2); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr2, err)
- }
- s.SetRouteTable([]tcpip.Route{{
- Destination: header.IPv6EmptySubnet,
- NIC: nicID,
- }})
-
- var firstFragmentSent buffer.View
- for _, f := range test.fragments {
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.IPv6FragmentHeaderSize)
-
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize + header.IPv6FragmentHeaderSize))
- ip.Encode(&f.ipv6Fields)
-
- fragHDR := header.IPv6Fragment(hdr.View()[header.IPv6MinimumSize:])
- fragHDR.Encode(&f.ipv6FragmentFields)
-
- vv := hdr.View().ToVectorisedView()
- vv.AppendView(f.payload)
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: vv,
- })
-
- if firstFragmentSent == nil && fragHDR.FragmentOffset() == 0 {
- firstFragmentSent = stack.PayloadSince(pkt.NetworkHeader())
- }
-
- e.InjectInbound(ProtocolNumber, pkt)
- }
-
- clock.Advance(ReassembleTimeout)
-
- reply, ok := e.Read()
- if !test.expectICMP {
- if ok {
- t.Fatalf("unexpected ICMP error message received: %#v", reply)
- }
- return
- }
- if !ok {
- t.Fatal("expected ICMP error message missing")
- }
- if firstFragmentSent == nil {
- t.Fatalf("unexpected ICMP error message received: %#v", reply)
- }
-
- checker.IPv6(t, stack.PayloadSince(reply.Pkt.NetworkHeader()),
- checker.SrcAddr(addr2),
- checker.DstAddr(addr1),
- checker.IPFullLength(uint16(header.IPv6MinimumSize+header.ICMPv6MinimumSize+firstFragmentSent.Size())),
- checker.ICMPv6(
- checker.ICMPv6Type(header.ICMPv6TimeExceeded),
- checker.ICMPv6Code(header.ICMPv6ReassemblyTimeout),
- checker.ICMPv6Payload([]byte(firstFragmentSent)),
- ),
- )
- })
- }
-}
-
-func TestWriteStats(t *testing.T) {
- const nPackets = 3
- tests := []struct {
- name string
- setup func(*testing.T, *stack.Stack)
- allowPackets int
- expectSent int
- expectDropped int
- expectWritten int
- }{
- {
- name: "Accept all",
- // No setup needed, tables accept everything by default.
- setup: func(*testing.T, *stack.Stack) {},
- allowPackets: math.MaxInt32,
- expectSent: nPackets,
- expectDropped: 0,
- expectWritten: nPackets,
- }, {
- name: "Accept all with error",
- // No setup needed, tables accept everything by default.
- setup: func(*testing.T, *stack.Stack) {},
- allowPackets: nPackets - 1,
- expectSent: nPackets - 1,
- expectDropped: 0,
- expectWritten: nPackets - 1,
- }, {
- name: "Drop all",
- setup: func(t *testing.T, stk *stack.Stack) {
- // Install Output DROP rule.
- t.Helper()
- ipt := stk.IPTables()
- filter := ipt.GetTable(stack.FilterID, true /* ipv6 */)
- ruleIdx := filter.BuiltinChains[stack.Output]
- filter.Rules[ruleIdx].Target = &stack.DropTarget{}
- if err := ipt.ReplaceTable(stack.FilterID, filter, true /* ipv6 */); err != nil {
- t.Fatalf("failed to replace table: %v", err)
- }
- },
- allowPackets: math.MaxInt32,
- expectSent: 0,
- expectDropped: nPackets,
- expectWritten: nPackets,
- }, {
- name: "Drop some",
- setup: func(t *testing.T, stk *stack.Stack) {
- // Install Output DROP rule that matches only 1
- // of the 3 packets.
- t.Helper()
- ipt := stk.IPTables()
- filter := ipt.GetTable(stack.FilterID, true /* ipv6 */)
- // We'll match and DROP the last packet.
- ruleIdx := filter.BuiltinChains[stack.Output]
- filter.Rules[ruleIdx].Target = &stack.DropTarget{}
- filter.Rules[ruleIdx].Matchers = []stack.Matcher{&limitedMatcher{nPackets - 1}}
- // Make sure the next rule is ACCEPT.
- filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
- if err := ipt.ReplaceTable(stack.FilterID, filter, true /* ipv6 */); err != nil {
- t.Fatalf("failed to replace table: %v", err)
- }
- },
- allowPackets: math.MaxInt32,
- expectSent: nPackets - 1,
- expectDropped: 1,
- expectWritten: nPackets,
- },
- }
-
- writers := []struct {
- name string
- writePackets func(*stack.Route, stack.PacketBufferList) (int, *tcpip.Error)
- }{
- {
- name: "WritePacket",
- writePackets: func(rt *stack.Route, pkts stack.PacketBufferList) (int, *tcpip.Error) {
- nWritten := 0
- for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
- if err := rt.WritePacket(nil, stack.NetworkHeaderParams{}, pkt); err != nil {
- return nWritten, err
- }
- nWritten++
- }
- return nWritten, nil
- },
- }, {
- name: "WritePackets",
- writePackets: func(rt *stack.Route, pkts stack.PacketBufferList) (int, *tcpip.Error) {
- return rt.WritePackets(nil, pkts, stack.NetworkHeaderParams{})
- },
- },
- }
-
- for _, writer := range writers {
- t.Run(writer.name, func(t *testing.T) {
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- ep := testutil.NewMockLinkEndpoint(header.IPv6MinimumMTU, tcpip.ErrInvalidEndpointState, test.allowPackets)
- rt := buildRoute(t, ep)
- var pkts stack.PacketBufferList
- for i := 0; i < nPackets; i++ {
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: header.UDPMinimumSize + int(rt.MaxHeaderLength()),
- Data: buffer.NewView(0).ToVectorisedView(),
- })
- pkt.TransportHeader().Push(header.UDPMinimumSize)
- pkts.PushBack(pkt)
- }
-
- test.setup(t, rt.Stack())
-
- nWritten, _ := writer.writePackets(&rt, pkts)
-
- if got := int(rt.Stats().IP.PacketsSent.Value()); got != test.expectSent {
- t.Errorf("sent %d packets, but expected to send %d", got, test.expectSent)
- }
- if got := int(rt.Stats().IP.IPTablesOutputDropped.Value()); got != test.expectDropped {
- t.Errorf("dropped %d packets, but expected to drop %d", got, test.expectDropped)
- }
- if nWritten != test.expectWritten {
- t.Errorf("wrote %d packets, but expected WritePackets to return %d", nWritten, test.expectWritten)
- }
- })
- }
- })
- }
-}
-
-func buildRoute(t *testing.T, ep stack.LinkEndpoint) stack.Route {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- })
- if err := s.CreateNIC(1, ep); err != nil {
- t.Fatalf("CreateNIC(1, _) failed: %s", err)
- }
- const (
- src = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
- dst = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
- )
- if err := s.AddAddress(1, ProtocolNumber, src); err != nil {
- t.Fatalf("AddAddress(1, %d, %s) failed: %s", ProtocolNumber, src, err)
- }
- {
- mask := tcpip.AddressMask("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff")
- subnet, err := tcpip.NewSubnet(dst, mask)
- if err != nil {
- t.Fatalf("NewSubnet(%s, %s) failed: %v", dst, mask, err)
- }
- s.SetRouteTable([]tcpip.Route{{
- Destination: subnet,
- NIC: 1,
- }})
- }
- rt, err := s.FindRoute(1, src, dst, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(1, %s, %s, %d, false) = %s, want = nil", src, dst, ProtocolNumber, err)
- }
- return rt
-}
-
-// limitedMatcher is an iptables matcher that matches after a certain number of
-// packets are checked against it.
-type limitedMatcher struct {
- limit int
-}
-
-// Name implements Matcher.Name.
-func (*limitedMatcher) Name() string {
- return "limitedMatcher"
-}
-
-// Match implements Matcher.Match.
-func (lm *limitedMatcher) Match(stack.Hook, *stack.PacketBuffer, string) (bool, bool) {
- if lm.limit == 0 {
- return true, false
- }
- lm.limit--
- return false, false
-}
-
-func TestClearEndpointFromProtocolOnClose(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- })
- proto := s.NetworkProtocolInstance(ProtocolNumber).(*protocol)
- ep := proto.NewEndpoint(&testInterface{}, nil, nil, nil).(*endpoint)
- {
- proto.mu.Lock()
- _, hasEP := proto.mu.eps[ep]
- proto.mu.Unlock()
- if !hasEP {
- t.Fatalf("expected protocol to have ep = %p in set of endpoints", ep)
- }
- }
-
- ep.Close()
-
- {
- proto.mu.Lock()
- _, hasEP := proto.mu.eps[ep]
- proto.mu.Unlock()
- if hasEP {
- t.Fatalf("unexpectedly found ep = %p in set of protocol's endpoints", ep)
- }
- }
-}
-
-type fragmentInfo struct {
- offset uint16
- more bool
- payloadSize uint16
-}
-
-var fragmentationTests = []struct {
- description string
- mtu uint32
- gso *stack.GSO
- transHdrLen int
- payloadSize int
- wantFragments []fragmentInfo
-}{
- {
- description: "No fragmentation",
- mtu: header.IPv6MinimumMTU,
- gso: nil,
- transHdrLen: 0,
- payloadSize: 1000,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1000, more: false},
- },
- },
- {
- description: "Fragmented",
- mtu: header.IPv6MinimumMTU,
- gso: nil,
- transHdrLen: 0,
- payloadSize: 2000,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1240, more: true},
- {offset: 154, payloadSize: 776, more: false},
- },
- },
- {
- description: "Fragmented with mtu not a multiple of 8",
- mtu: header.IPv6MinimumMTU + 1,
- gso: nil,
- transHdrLen: 0,
- payloadSize: 2000,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1240, more: true},
- {offset: 154, payloadSize: 776, more: false},
- },
- },
- {
- description: "No fragmentation with big header",
- mtu: 2000,
- gso: nil,
- transHdrLen: 100,
- payloadSize: 1000,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1100, more: false},
- },
- },
- {
- description: "Fragmented with gso none",
- mtu: header.IPv6MinimumMTU,
- gso: &stack.GSO{Type: stack.GSONone},
- transHdrLen: 0,
- payloadSize: 1400,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1240, more: true},
- {offset: 154, payloadSize: 176, more: false},
- },
- },
- {
- description: "Fragmented with big header",
- mtu: header.IPv6MinimumMTU,
- gso: nil,
- transHdrLen: 100,
- payloadSize: 1200,
- wantFragments: []fragmentInfo{
- {offset: 0, payloadSize: 1240, more: true},
- {offset: 154, payloadSize: 76, more: false},
- },
- },
-}
-
-func TestFragmentationWritePacket(t *testing.T) {
- const (
- ttl = 42
- tos = stack.DefaultTOS
- transportProto = tcp.ProtocolNumber
- )
-
- for _, ft := range fragmentationTests {
- t.Run(ft.description, func(t *testing.T) {
- pkt := testutil.MakeRandPkt(ft.transHdrLen, extraHeaderReserve+header.IPv6MinimumSize, []int{ft.payloadSize}, header.IPv6ProtocolNumber)
- source := pkt.Clone()
- ep := testutil.NewMockLinkEndpoint(ft.mtu, nil, math.MaxInt32)
- r := buildRoute(t, ep)
- err := r.WritePacket(ft.gso, stack.NetworkHeaderParams{
- Protocol: tcp.ProtocolNumber,
- TTL: ttl,
- TOS: stack.DefaultTOS,
- }, pkt)
- if err != nil {
- t.Fatalf("WritePacket(_, _, _): = %s", err)
- }
- if got := len(ep.WrittenPackets); got != len(ft.wantFragments) {
- t.Errorf("got len(ep.WrittenPackets) = %d, want = %d", got, len(ft.wantFragments))
- }
- if got := int(r.Stats().IP.PacketsSent.Value()); got != len(ft.wantFragments) {
- t.Errorf("got c.Route.Stats().IP.PacketsSent.Value() = %d, want = %d", got, len(ft.wantFragments))
- }
- if got := r.Stats().IP.OutgoingPacketErrors.Value(); got != 0 {
- t.Errorf("got r.Stats().IP.OutgoingPacketErrors.Value() = %d, want = 0", got)
- }
- if err := compareFragments(ep.WrittenPackets, source, ft.mtu, ft.wantFragments, tcp.ProtocolNumber); err != nil {
- t.Error(err)
- }
- })
- }
-}
-
-func TestFragmentationWritePackets(t *testing.T) {
- const ttl = 42
- tests := []struct {
- description string
- insertBefore int
- insertAfter int
- }{
- {
- description: "Single packet",
- insertBefore: 0,
- insertAfter: 0,
- },
- {
- description: "With packet before",
- insertBefore: 1,
- insertAfter: 0,
- },
- {
- description: "With packet after",
- insertBefore: 0,
- insertAfter: 1,
- },
- {
- description: "With packet before and after",
- insertBefore: 1,
- insertAfter: 1,
- },
- }
- tinyPacket := testutil.MakeRandPkt(header.TCPMinimumSize, extraHeaderReserve+header.IPv6MinimumSize, []int{1}, header.IPv6ProtocolNumber)
-
- for _, test := range tests {
- t.Run(test.description, func(t *testing.T) {
- for _, ft := range fragmentationTests {
- t.Run(ft.description, func(t *testing.T) {
- var pkts stack.PacketBufferList
- for i := 0; i < test.insertBefore; i++ {
- pkts.PushBack(tinyPacket.Clone())
- }
- pkt := testutil.MakeRandPkt(ft.transHdrLen, extraHeaderReserve+header.IPv6MinimumSize, []int{ft.payloadSize}, header.IPv6ProtocolNumber)
- source := pkt
- pkts.PushBack(pkt.Clone())
- for i := 0; i < test.insertAfter; i++ {
- pkts.PushBack(tinyPacket.Clone())
- }
-
- ep := testutil.NewMockLinkEndpoint(ft.mtu, nil, math.MaxInt32)
- r := buildRoute(t, ep)
-
- wantTotalPackets := len(ft.wantFragments) + test.insertBefore + test.insertAfter
- n, err := r.WritePackets(ft.gso, pkts, stack.NetworkHeaderParams{
- Protocol: tcp.ProtocolNumber,
- TTL: ttl,
- TOS: stack.DefaultTOS,
- })
- if n != wantTotalPackets || err != nil {
- t.Errorf("got WritePackets(_, _, _) = (%d, %s), want = (%d, nil)", n, err, wantTotalPackets)
- }
- if got := len(ep.WrittenPackets); got != wantTotalPackets {
- t.Errorf("got len(ep.WrittenPackets) = %d, want = %d", got, wantTotalPackets)
- }
- if got := int(r.Stats().IP.PacketsSent.Value()); got != wantTotalPackets {
- t.Errorf("got c.Route.Stats().IP.PacketsSent.Value() = %d, want = %d", got, wantTotalPackets)
- }
- if got := r.Stats().IP.OutgoingPacketErrors.Value(); got != 0 {
- t.Errorf("got r.Stats().IP.OutgoingPacketErrors.Value() = %d, want = 0", got)
- }
-
- if wantTotalPackets == 0 {
- return
- }
-
- fragments := ep.WrittenPackets[test.insertBefore : len(ft.wantFragments)+test.insertBefore]
- if err := compareFragments(fragments, source, ft.mtu, ft.wantFragments, tcp.ProtocolNumber); err != nil {
- t.Error(err)
- }
- })
- }
- })
- }
-}
-
-// TestFragmentationErrors checks that errors are returned from WritePacket
-// correctly.
-func TestFragmentationErrors(t *testing.T) {
- const ttl = 42
-
- tests := []struct {
- description string
- mtu uint32
- transHdrLen int
- payloadSize int
- allowPackets int
- outgoingErrors int
- mockError *tcpip.Error
- wantError *tcpip.Error
- }{
- {
- description: "No frag",
- mtu: 2000,
- payloadSize: 1000,
- transHdrLen: 0,
- allowPackets: 0,
- outgoingErrors: 1,
- mockError: tcpip.ErrAborted,
- wantError: tcpip.ErrAborted,
- },
- {
- description: "Error on first frag",
- mtu: 1300,
- payloadSize: 3000,
- transHdrLen: 0,
- allowPackets: 0,
- outgoingErrors: 3,
- mockError: tcpip.ErrAborted,
- wantError: tcpip.ErrAborted,
- },
- {
- description: "Error on second frag",
- mtu: 1500,
- payloadSize: 4000,
- transHdrLen: 0,
- allowPackets: 1,
- outgoingErrors: 2,
- mockError: tcpip.ErrAborted,
- wantError: tcpip.ErrAborted,
- },
- {
- description: "Error when MTU is smaller than transport header",
- mtu: header.IPv6MinimumMTU,
- transHdrLen: 1500,
- payloadSize: 500,
- allowPackets: 0,
- outgoingErrors: 1,
- mockError: nil,
- wantError: tcpip.ErrMessageTooLong,
- },
- {
- description: "Error when MTU is smaller than IPv6 minimum MTU",
- mtu: header.IPv6MinimumMTU - 1,
- transHdrLen: 0,
- payloadSize: 500,
- allowPackets: 0,
- outgoingErrors: 1,
- mockError: nil,
- wantError: tcpip.ErrInvalidEndpointState,
- },
- }
-
- for _, ft := range tests {
- t.Run(ft.description, func(t *testing.T) {
- pkt := testutil.MakeRandPkt(ft.transHdrLen, extraHeaderReserve+header.IPv6MinimumSize, []int{ft.payloadSize}, header.IPv6ProtocolNumber)
- ep := testutil.NewMockLinkEndpoint(ft.mtu, ft.mockError, ft.allowPackets)
- r := buildRoute(t, ep)
- err := r.WritePacket(&stack.GSO{}, stack.NetworkHeaderParams{
- Protocol: tcp.ProtocolNumber,
- TTL: ttl,
- TOS: stack.DefaultTOS,
- }, pkt)
- if err != ft.wantError {
- t.Errorf("got WritePacket(_, _, _) = %s, want = %s", err, ft.wantError)
- }
- if got := int(r.Stats().IP.PacketsSent.Value()); got != ft.allowPackets {
- t.Errorf("got r.Stats().IP.PacketsSent.Value() = %d, want = %d", got, ft.allowPackets)
- }
- if got := int(r.Stats().IP.OutgoingPacketErrors.Value()); got != ft.outgoingErrors {
- t.Errorf("got r.Stats().IP.OutgoingPacketErrors.Value() = %d, want = %d", got, ft.outgoingErrors)
- }
- })
- }
-}
diff --git a/pkg/tcpip/network/ipv6/ndp_test.go b/pkg/tcpip/network/ipv6/ndp_test.go
deleted file mode 100644
index 981d1371a..000000000
--- a/pkg/tcpip/network/ipv6/ndp_test.go
+++ /dev/null
@@ -1,1393 +0,0 @@
-// Copyright 2019 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 ipv6
-
-import (
- "context"
- "strings"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/checker"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/link/channel"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
- "gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
-)
-
-// setupStackAndEndpoint creates a stack with a single NIC with a link-local
-// address llladdr and an IPv6 endpoint to a remote with link-local address
-// rlladdr
-func setupStackAndEndpoint(t *testing.T, llladdr, rlladdr tcpip.Address, useNeighborCache bool) (*stack.Stack, stack.NetworkEndpoint) {
- t.Helper()
-
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
- UseNeighborCache: useNeighborCache,
- })
-
- if err := s.CreateNIC(1, &stubLinkEndpoint{}); err != nil {
- t.Fatalf("CreateNIC(_) = %s", err)
- }
- if err := s.AddAddress(1, ProtocolNumber, llladdr); err != nil {
- t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, llladdr, err)
- }
-
- {
- subnet, err := tcpip.NewSubnet(rlladdr, tcpip.AddressMask(strings.Repeat("\xff", len(rlladdr))))
- if err != nil {
- t.Fatal(err)
- }
- s.SetRouteTable(
- []tcpip.Route{{
- Destination: subnet,
- NIC: 1,
- }},
- )
- }
-
- netProto := s.NetworkProtocolInstance(ProtocolNumber)
- if netProto == nil {
- t.Fatalf("cannot find protocol instance for network protocol %d", ProtocolNumber)
- }
-
- ep := netProto.NewEndpoint(&testInterface{}, &stubLinkAddressCache{}, &stubNUDHandler{}, &stubDispatcher{})
- if err := ep.Enable(); err != nil {
- t.Fatalf("ep.Enable(): %s", err)
- }
- t.Cleanup(ep.Close)
-
- return s, ep
-}
-
-var _ NDPDispatcher = (*testNDPDispatcher)(nil)
-
-// testNDPDispatcher is an NDPDispatcher only allows default router discovery.
-type testNDPDispatcher struct {
- addr tcpip.Address
-}
-
-func (*testNDPDispatcher) OnDuplicateAddressDetectionStatus(tcpip.NICID, tcpip.Address, bool, *tcpip.Error) {
-}
-
-func (t *testNDPDispatcher) OnDefaultRouterDiscovered(_ tcpip.NICID, addr tcpip.Address) bool {
- t.addr = addr
- return true
-}
-
-func (t *testNDPDispatcher) OnDefaultRouterInvalidated(_ tcpip.NICID, addr tcpip.Address) {
- t.addr = addr
-}
-
-func (*testNDPDispatcher) OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet) bool {
- return false
-}
-
-func (*testNDPDispatcher) OnOnLinkPrefixInvalidated(tcpip.NICID, tcpip.Subnet) {
-}
-
-func (*testNDPDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) bool {
- return false
-}
-
-func (*testNDPDispatcher) OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) {
-}
-
-func (*testNDPDispatcher) OnAutoGenAddressInvalidated(tcpip.NICID, tcpip.AddressWithPrefix) {
-}
-
-func (*testNDPDispatcher) OnRecursiveDNSServerOption(tcpip.NICID, []tcpip.Address, time.Duration) {
-}
-
-func (*testNDPDispatcher) OnDNSSearchListOption(tcpip.NICID, []string, time.Duration) {
-}
-
-func (*testNDPDispatcher) OnDHCPv6Configuration(tcpip.NICID, DHCPv6ConfigurationFromNDPRA) {
-}
-
-func TestStackNDPEndpointInvalidateDefaultRouter(t *testing.T) {
- var ndpDisp testNDPDispatcher
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocolWithOptions(Options{
- NDPDisp: &ndpDisp,
- })},
- })
-
- if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
- t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
- }
-
- ep, err := s.GetNetworkEndpoint(nicID, ProtocolNumber)
- if err != nil {
- t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, ProtocolNumber, err)
- }
-
- ipv6EP := ep.(*endpoint)
- ipv6EP.mu.Lock()
- ipv6EP.mu.ndp.rememberDefaultRouter(lladdr1, time.Hour)
- ipv6EP.mu.Unlock()
-
- if ndpDisp.addr != lladdr1 {
- t.Fatalf("got ndpDisp.addr = %s, want = %s", ndpDisp.addr, lladdr1)
- }
-
- ndpDisp.addr = ""
- ndpEP := ep.(stack.NDPEndpoint)
- ndpEP.InvalidateDefaultRouter(lladdr1)
- if ndpDisp.addr != lladdr1 {
- t.Fatalf("got ndpDisp.addr = %s, want = %s", ndpDisp.addr, lladdr1)
- }
-}
-
-// TestNeighorSolicitationWithSourceLinkLayerOption tests that receiving a
-// valid NDP NS message with the Source Link Layer Address option results in a
-// new entry in the link address cache for the sender of the message.
-func TestNeighorSolicitationWithSourceLinkLayerOption(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- optsBuf []byte
- expectedLinkAddr tcpip.LinkAddress
- }{
- {
- name: "Valid",
- optsBuf: []byte{1, 1, 2, 3, 4, 5, 6, 7},
- expectedLinkAddr: "\x02\x03\x04\x05\x06\x07",
- },
- {
- name: "Too Small",
- optsBuf: []byte{1, 1, 2, 3, 4, 5, 6},
- },
- {
- name: "Invalid Length",
- optsBuf: []byte{1, 2, 2, 3, 4, 5, 6, 7},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- })
- e := channel.New(0, 1280, linkAddr0)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
- }
-
- ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize + len(test.optsBuf)
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
- pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
- pkt.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(pkt.NDPPayload())
- ns.SetTargetAddress(lladdr0)
- opts := ns.Options()
- copy(opts, test.optsBuf)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, lladdr1, lladdr0, buffer.VectorisedView{}))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: 255,
- SrcAddr: lladdr1,
- DstAddr: lladdr0,
- })
-
- invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
-
- // Invalid count should initially be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
-
- e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
-
- linkAddr, c, err := s.GetLinkAddress(nicID, lladdr1, lladdr0, ProtocolNumber, nil)
- if linkAddr != test.expectedLinkAddr {
- t.Errorf("got link address = %s, want = %s", linkAddr, test.expectedLinkAddr)
- }
-
- if test.expectedLinkAddr != "" {
- if err != nil {
- t.Errorf("s.GetLinkAddress(%d, %s, %s, %d, nil): %s", nicID, lladdr1, lladdr0, ProtocolNumber, err)
- }
- if c != nil {
- t.Errorf("got unexpected channel")
- }
-
- // Invalid count should not have increased.
- if got := invalid.Value(); got != 0 {
- t.Errorf("got invalid = %d, want = 0", got)
- }
- } else {
- if err != tcpip.ErrWouldBlock {
- t.Errorf("got s.GetLinkAddress(%d, %s, %s, %d, nil) = (_, _, %v), want = (_, _, %s)", nicID, lladdr1, lladdr0, ProtocolNumber, err, tcpip.ErrWouldBlock)
- }
- if c == nil {
- t.Errorf("expected channel from call to s.GetLinkAddress(%d, %s, %s, %d, nil)", nicID, lladdr1, lladdr0, ProtocolNumber)
- }
-
- // Invalid count should have increased.
- if got := invalid.Value(); got != 1 {
- t.Errorf("got invalid = %d, want = 1", got)
- }
- }
- })
- }
-}
-
-// TestNeighorSolicitationWithSourceLinkLayerOptionUsingNeighborCache tests
-// that receiving a valid NDP NS message with the Source Link Layer Address
-// option results in a new entry in the link address cache for the sender of
-// the message.
-func TestNeighorSolicitationWithSourceLinkLayerOptionUsingNeighborCache(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- optsBuf []byte
- expectedLinkAddr tcpip.LinkAddress
- }{
- {
- name: "Valid",
- optsBuf: []byte{1, 1, 2, 3, 4, 5, 6, 7},
- expectedLinkAddr: "\x02\x03\x04\x05\x06\x07",
- },
- {
- name: "Too Small",
- optsBuf: []byte{1, 1, 2, 3, 4, 5, 6},
- },
- {
- name: "Invalid Length",
- optsBuf: []byte{1, 2, 2, 3, 4, 5, 6, 7},
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- UseNeighborCache: true,
- })
- e := channel.New(0, 1280, linkAddr0)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
- }
-
- ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize + len(test.optsBuf)
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
- pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
- pkt.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(pkt.NDPPayload())
- ns.SetTargetAddress(lladdr0)
- opts := ns.Options()
- copy(opts, test.optsBuf)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, lladdr1, lladdr0, buffer.VectorisedView{}))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: 255,
- SrcAddr: lladdr1,
- DstAddr: lladdr0,
- })
-
- invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
-
- // Invalid count should initially be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
-
- e.InjectInbound(ProtocolNumber, &stack.PacketBuffer{
- Data: hdr.View().ToVectorisedView(),
- })
-
- neighbors, err := s.Neighbors(nicID)
- if err != nil {
- t.Fatalf("s.Neighbors(%d): %s", nicID, err)
- }
-
- neighborByAddr := make(map[tcpip.Address]stack.NeighborEntry)
- for _, n := range neighbors {
- if existing, ok := neighborByAddr[n.Addr]; ok {
- if diff := cmp.Diff(existing, n); diff != "" {
- t.Fatalf("s.Neighbors(%d) returned unexpected duplicate neighbor entry (-existing +got):\n%s", nicID, diff)
- }
- t.Fatalf("s.Neighbors(%d) returned unexpected duplicate neighbor entry: %#v", nicID, existing)
- }
- neighborByAddr[n.Addr] = n
- }
-
- if neigh, ok := neighborByAddr[lladdr1]; len(test.expectedLinkAddr) != 0 {
- // Invalid count should not have increased.
- if got := invalid.Value(); got != 0 {
- t.Errorf("got invalid = %d, want = 0", got)
- }
-
- if !ok {
- t.Fatalf("expected a neighbor entry for %q", lladdr1)
- }
- if neigh.LinkAddr != test.expectedLinkAddr {
- t.Errorf("got link address = %s, want = %s", neigh.LinkAddr, test.expectedLinkAddr)
- }
- if neigh.State != stack.Stale {
- t.Errorf("got NUD state = %s, want = %s", neigh.State, stack.Stale)
- }
- } else {
- // Invalid count should have increased.
- if got := invalid.Value(); got != 1 {
- t.Errorf("got invalid = %d, want = 1", got)
- }
-
- if ok {
- t.Fatalf("unexpectedly got neighbor entry: %#v", neigh)
- }
- }
- })
- }
-}
-
-func TestNeighorSolicitationResponse(t *testing.T) {
- const nicID = 1
- nicAddr := lladdr0
- remoteAddr := lladdr1
- nicAddrSNMC := header.SolicitedNodeAddr(nicAddr)
- nicLinkAddr := linkAddr0
- remoteLinkAddr0 := linkAddr1
- remoteLinkAddr1 := linkAddr2
-
- stacks := []struct {
- name string
- useNeighborCache bool
- }{
- {
- name: "linkAddrCache",
- useNeighborCache: false,
- },
- {
- name: "neighborCache",
- useNeighborCache: true,
- },
- }
-
- tests := []struct {
- name string
- nsOpts header.NDPOptionsSerializer
- nsSrcLinkAddr tcpip.LinkAddress
- nsSrc tcpip.Address
- nsDst tcpip.Address
- nsInvalid bool
- naDstLinkAddr tcpip.LinkAddress
- naSolicited bool
- naSrc tcpip.Address
- naDst tcpip.Address
- performsLinkResolution bool
- }{
- {
- name: "Unspecified source to solicited-node multicast destination",
- nsOpts: nil,
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: header.IPv6Any,
- nsDst: nicAddrSNMC,
- nsInvalid: false,
- naDstLinkAddr: header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllNodesMulticastAddress),
- naSolicited: false,
- naSrc: nicAddr,
- naDst: header.IPv6AllNodesMulticastAddress,
- },
- {
- name: "Unspecified source with source ll option to multicast destination",
- nsOpts: header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
- },
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: header.IPv6Any,
- nsDst: nicAddrSNMC,
- nsInvalid: true,
- },
- {
- name: "Unspecified source to unicast destination",
- nsOpts: nil,
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: header.IPv6Any,
- nsDst: nicAddr,
- nsInvalid: true,
- },
- {
- name: "Unspecified source with source ll option to unicast destination",
- nsOpts: header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
- },
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: header.IPv6Any,
- nsDst: nicAddr,
- nsInvalid: true,
- },
- {
- name: "Specified source with 1 source ll to multicast destination",
- nsOpts: header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
- },
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: remoteAddr,
- nsDst: nicAddrSNMC,
- nsInvalid: false,
- naDstLinkAddr: remoteLinkAddr0,
- naSolicited: true,
- naSrc: nicAddr,
- naDst: remoteAddr,
- },
- {
- name: "Specified source with 1 source ll different from route to multicast destination",
- nsOpts: header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr1[:]),
- },
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: remoteAddr,
- nsDst: nicAddrSNMC,
- nsInvalid: false,
- naDstLinkAddr: remoteLinkAddr1,
- naSolicited: true,
- naSrc: nicAddr,
- naDst: remoteAddr,
- },
- {
- name: "Specified source to multicast destination",
- nsOpts: nil,
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: remoteAddr,
- nsDst: nicAddrSNMC,
- nsInvalid: true,
- },
- {
- name: "Specified source with 2 source ll to multicast destination",
- nsOpts: header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr1[:]),
- },
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: remoteAddr,
- nsDst: nicAddrSNMC,
- nsInvalid: true,
- },
-
- {
- name: "Specified source to unicast destination",
- nsOpts: nil,
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: remoteAddr,
- nsDst: nicAddr,
- nsInvalid: false,
- naDstLinkAddr: remoteLinkAddr0,
- naSolicited: true,
- naSrc: nicAddr,
- naDst: remoteAddr,
- // Since we send a unicast solicitations to a node without an entry for
- // the remote, the node needs to perform neighbor discovery to get the
- // remote's link address to send the advertisement response.
- performsLinkResolution: true,
- },
- {
- name: "Specified source with 1 source ll to unicast destination",
- nsOpts: header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
- },
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: remoteAddr,
- nsDst: nicAddr,
- nsInvalid: false,
- naDstLinkAddr: remoteLinkAddr0,
- naSolicited: true,
- naSrc: nicAddr,
- naDst: remoteAddr,
- },
- {
- name: "Specified source with 1 source ll different from route to unicast destination",
- nsOpts: header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr1[:]),
- },
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: remoteAddr,
- nsDst: nicAddr,
- nsInvalid: false,
- naDstLinkAddr: remoteLinkAddr1,
- naSolicited: true,
- naSrc: nicAddr,
- naDst: remoteAddr,
- },
- {
- name: "Specified source with 2 source ll to unicast destination",
- nsOpts: header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr0[:]),
- header.NDPSourceLinkLayerAddressOption(remoteLinkAddr1[:]),
- },
- nsSrcLinkAddr: remoteLinkAddr0,
- nsSrc: remoteAddr,
- nsDst: nicAddr,
- nsInvalid: true,
- },
- }
-
- for _, stackTyp := range stacks {
- t.Run(stackTyp.name, func(t *testing.T) {
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- UseNeighborCache: stackTyp.useNeighborCache,
- })
- e := channel.New(1, 1280, nicLinkAddr)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, nicAddr); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, nicAddr, err)
- }
-
- s.SetRouteTable([]tcpip.Route{
- tcpip.Route{
- Destination: header.IPv6EmptySubnet,
- NIC: 1,
- },
- })
-
- ndpNSSize := header.ICMPv6NeighborSolicitMinimumSize + test.nsOpts.Length()
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNSSize)
- pkt := header.ICMPv6(hdr.Prepend(ndpNSSize))
- pkt.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(pkt.NDPPayload())
- ns.SetTargetAddress(nicAddr)
- opts := ns.Options()
- opts.Serialize(test.nsOpts)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, test.nsSrc, test.nsDst, buffer.VectorisedView{}))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: 255,
- SrcAddr: test.nsSrc,
- DstAddr: test.nsDst,
- })
-
- invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
-
- // Invalid count should initially be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
-
- e.InjectLinkAddr(ProtocolNumber, test.nsSrcLinkAddr, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
-
- if test.nsInvalid {
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
-
- if p, got := e.Read(); got {
- t.Fatalf("unexpected response to an invalid NS = %+v", p.Pkt)
- }
-
- // If we expected the NS to be invalid, we have nothing else to check.
- return
- }
-
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
-
- if test.performsLinkResolution {
- p, got := e.ReadContext(context.Background())
- if !got {
- t.Fatal("expected an NDP NS response")
- }
-
- if p.Route.LocalAddress != nicAddr {
- t.Errorf("got p.Route.LocalAddress = %s, want = %s", p.Route.LocalAddress, nicAddr)
- }
- if p.Route.LocalLinkAddress != nicLinkAddr {
- t.Errorf("p.Route.LocalLinkAddress = %s, want = %s", p.Route.LocalLinkAddress, nicLinkAddr)
- }
- respNSDst := header.SolicitedNodeAddr(test.nsSrc)
- if p.Route.RemoteAddress != respNSDst {
- t.Errorf("got p.Route.RemoteAddress = %s, want = %s", p.Route.RemoteAddress, respNSDst)
- }
- if want := header.EthernetAddressFromMulticastIPv6Address(respNSDst); p.Route.RemoteLinkAddress != want {
- t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, want)
- }
-
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(nicAddr),
- checker.DstAddr(respNSDst),
- checker.TTL(header.NDPHopLimit),
- checker.NDPNS(
- checker.NDPNSTargetAddress(test.nsSrc),
- checker.NDPNSOptions([]header.NDPOption{
- header.NDPSourceLinkLayerAddressOption(nicLinkAddr),
- }),
- ))
-
- ser := header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(linkAddr1),
- }
- ndpNASize := header.ICMPv6NeighborAdvertMinimumSize + ser.Length()
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
- pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
- pkt.SetType(header.ICMPv6NeighborAdvert)
- na := header.NDPNeighborAdvert(pkt.NDPPayload())
- na.SetSolicitedFlag(true)
- na.SetOverrideFlag(true)
- na.SetTargetAddress(test.nsSrc)
- na.Options().Serialize(ser)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, test.nsSrc, nicAddr, buffer.VectorisedView{}))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: header.NDPHopLimit,
- SrcAddr: test.nsSrc,
- DstAddr: nicAddr,
- })
- e.InjectLinkAddr(ProtocolNumber, "", stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
- }
-
- p, got := e.ReadContext(context.Background())
- if !got {
- t.Fatal("expected an NDP NA response")
- }
-
- if p.Route.LocalAddress != test.naSrc {
- t.Errorf("got p.Route.LocalAddress = %s, want = %s", p.Route.LocalAddress, test.naSrc)
- }
- if p.Route.LocalLinkAddress != nicLinkAddr {
- t.Errorf("p.Route.LocalLinkAddress = %s, want = %s", p.Route.LocalLinkAddress, nicLinkAddr)
- }
- if p.Route.RemoteAddress != test.naDst {
- t.Errorf("got p.Route.RemoteAddress = %s, want = %s", p.Route.RemoteAddress, test.naDst)
- }
- if p.Route.RemoteLinkAddress != test.naDstLinkAddr {
- t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, test.naDstLinkAddr)
- }
-
- checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
- checker.SrcAddr(test.naSrc),
- checker.DstAddr(test.naDst),
- checker.TTL(header.NDPHopLimit),
- checker.NDPNA(
- checker.NDPNASolicitedFlag(test.naSolicited),
- checker.NDPNATargetAddress(nicAddr),
- checker.NDPNAOptions([]header.NDPOption{
- header.NDPTargetLinkLayerAddressOption(nicLinkAddr[:]),
- }),
- ))
- })
- }
- })
- }
-}
-
-// TestNeighorAdvertisementWithTargetLinkLayerOption tests that receiving a
-// valid NDP NA message with the Target Link Layer Address option results in a
-// new entry in the link address cache for the target of the message.
-func TestNeighorAdvertisementWithTargetLinkLayerOption(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- optsBuf []byte
- expectedLinkAddr tcpip.LinkAddress
- }{
- {
- name: "Valid",
- optsBuf: []byte{2, 1, 2, 3, 4, 5, 6, 7},
- expectedLinkAddr: "\x02\x03\x04\x05\x06\x07",
- },
- {
- name: "Too Small",
- optsBuf: []byte{2, 1, 2, 3, 4, 5, 6},
- },
- {
- name: "Invalid Length",
- optsBuf: []byte{2, 2, 2, 3, 4, 5, 6, 7},
- },
- {
- name: "Multiple",
- optsBuf: []byte{
- 2, 1, 2, 3, 4, 5, 6, 7,
- 2, 1, 2, 3, 4, 5, 6, 8,
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- })
- e := channel.New(0, 1280, linkAddr0)
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
- }
-
- ndpNASize := header.ICMPv6NeighborAdvertMinimumSize + len(test.optsBuf)
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
- pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
- pkt.SetType(header.ICMPv6NeighborAdvert)
- ns := header.NDPNeighborAdvert(pkt.NDPPayload())
- ns.SetTargetAddress(lladdr1)
- opts := ns.Options()
- copy(opts, test.optsBuf)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, lladdr1, lladdr0, buffer.VectorisedView{}))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: 255,
- SrcAddr: lladdr1,
- DstAddr: lladdr0,
- })
-
- invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
-
- // Invalid count should initially be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
-
- e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
-
- linkAddr, c, err := s.GetLinkAddress(nicID, lladdr1, lladdr0, ProtocolNumber, nil)
- if linkAddr != test.expectedLinkAddr {
- t.Errorf("got link address = %s, want = %s", linkAddr, test.expectedLinkAddr)
- }
-
- if test.expectedLinkAddr != "" {
- if err != nil {
- t.Errorf("s.GetLinkAddress(%d, %s, %s, %d, nil): %s", nicID, lladdr1, lladdr0, ProtocolNumber, err)
- }
- if c != nil {
- t.Errorf("got unexpected channel")
- }
-
- // Invalid count should not have increased.
- if got := invalid.Value(); got != 0 {
- t.Errorf("got invalid = %d, want = 0", got)
- }
- } else {
- if err != tcpip.ErrWouldBlock {
- t.Errorf("got s.GetLinkAddress(%d, %s, %s, %d, nil) = (_, _, %v), want = (_, _, %s)", nicID, lladdr1, lladdr0, ProtocolNumber, err, tcpip.ErrWouldBlock)
- }
- if c == nil {
- t.Errorf("expected channel from call to s.GetLinkAddress(%d, %s, %s, %d, nil)", nicID, lladdr1, lladdr0, ProtocolNumber)
- }
-
- // Invalid count should have increased.
- if got := invalid.Value(); got != 1 {
- t.Errorf("got invalid = %d, want = 1", got)
- }
- }
- })
- }
-}
-
-// TestNeighorAdvertisementWithTargetLinkLayerOptionUsingNeighborCache tests
-// that receiving a valid NDP NA message with the Target Link Layer Address
-// option does not result in a new entry in the neighbor cache for the target
-// of the message.
-func TestNeighorAdvertisementWithTargetLinkLayerOptionUsingNeighborCache(t *testing.T) {
- const nicID = 1
-
- tests := []struct {
- name string
- optsBuf []byte
- isValid bool
- }{
- {
- name: "Valid",
- optsBuf: []byte{2, 1, 2, 3, 4, 5, 6, 7},
- isValid: true,
- },
- {
- name: "Too Small",
- optsBuf: []byte{2, 1, 2, 3, 4, 5, 6},
- },
- {
- name: "Invalid Length",
- optsBuf: []byte{2, 2, 2, 3, 4, 5, 6, 7},
- },
- {
- name: "Multiple",
- optsBuf: []byte{
- 2, 1, 2, 3, 4, 5, 6, 7,
- 2, 1, 2, 3, 4, 5, 6, 8,
- },
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- UseNeighborCache: true,
- })
- e := channel.New(0, 1280, linkAddr0)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
- if err := s.CreateNIC(nicID, e); err != nil {
- t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
- }
- if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
- t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
- }
-
- ndpNASize := header.ICMPv6NeighborAdvertMinimumSize + len(test.optsBuf)
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
- pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
- pkt.SetType(header.ICMPv6NeighborAdvert)
- ns := header.NDPNeighborAdvert(pkt.NDPPayload())
- ns.SetTargetAddress(lladdr1)
- opts := ns.Options()
- copy(opts, test.optsBuf)
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, lladdr1, lladdr0, buffer.VectorisedView{}))
- payloadLength := hdr.UsedLength()
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(header.ICMPv6ProtocolNumber),
- HopLimit: 255,
- SrcAddr: lladdr1,
- DstAddr: lladdr0,
- })
-
- invalid := s.Stats().ICMP.V6PacketsReceived.Invalid
-
- // Invalid count should initially be 0.
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
-
- e.InjectInbound(ProtocolNumber, &stack.PacketBuffer{
- Data: hdr.View().ToVectorisedView(),
- })
-
- neighbors, err := s.Neighbors(nicID)
- if err != nil {
- t.Fatalf("s.Neighbors(%d): %s", nicID, err)
- }
-
- neighborByAddr := make(map[tcpip.Address]stack.NeighborEntry)
- for _, n := range neighbors {
- if existing, ok := neighborByAddr[n.Addr]; ok {
- if diff := cmp.Diff(existing, n); diff != "" {
- t.Fatalf("s.Neighbors(%d) returned unexpected duplicate neighbor entry (-existing +got):\n%s", nicID, diff)
- }
- t.Fatalf("s.Neighbors(%d) returned unexpected duplicate neighbor entry: %#v", nicID, existing)
- }
- neighborByAddr[n.Addr] = n
- }
-
- if neigh, ok := neighborByAddr[lladdr1]; ok {
- t.Fatalf("unexpectedly got neighbor entry: %#v", neigh)
- }
-
- if test.isValid {
- // Invalid count should not have increased.
- if got := invalid.Value(); got != 0 {
- t.Errorf("got invalid = %d, want = 0", got)
- }
- } else {
- // Invalid count should have increased.
- if got := invalid.Value(); got != 1 {
- t.Errorf("got invalid = %d, want = 1", got)
- }
- }
- })
- }
-}
-
-func TestNDPValidation(t *testing.T) {
- stacks := []struct {
- name string
- useNeighborCache bool
- }{
- {
- name: "linkAddrCache",
- useNeighborCache: false,
- },
- {
- name: "neighborCache",
- useNeighborCache: true,
- },
- }
-
- for _, stackTyp := range stacks {
- t.Run(stackTyp.name, func(t *testing.T) {
- setup := func(t *testing.T) (*stack.Stack, stack.NetworkEndpoint, stack.Route) {
- t.Helper()
-
- // Create a stack with the assigned link-local address lladdr0
- // and an endpoint to lladdr1.
- s, ep := setupStackAndEndpoint(t, lladdr0, lladdr1, stackTyp.useNeighborCache)
-
- r, err := s.FindRoute(1, lladdr0, lladdr1, ProtocolNumber, false /* multicastLoop */)
- if err != nil {
- t.Fatalf("FindRoute(_) = _, %s, want = _, nil", err)
- }
-
- return s, ep, r
- }
-
- handleIPv6Payload := func(payload buffer.View, hopLimit uint8, atomicFragment bool, ep stack.NetworkEndpoint, r *stack.Route) {
- nextHdr := uint8(header.ICMPv6ProtocolNumber)
- var extensions buffer.View
- if atomicFragment {
- extensions = buffer.NewView(header.IPv6FragmentExtHdrLength)
- extensions[0] = nextHdr
- nextHdr = uint8(header.IPv6FragmentExtHdrIdentifier)
- }
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: header.IPv6MinimumSize + len(extensions),
- Data: payload.ToVectorisedView(),
- })
- ip := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize + len(extensions)))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(len(payload) + len(extensions)),
- NextHeader: nextHdr,
- HopLimit: hopLimit,
- SrcAddr: r.LocalAddress,
- DstAddr: r.RemoteAddress,
- })
- if n := copy(ip[header.IPv6MinimumSize:], extensions); n != len(extensions) {
- t.Fatalf("expected to write %d bytes of extensions, but wrote %d", len(extensions), n)
- }
- r.PopulatePacketInfo(pkt)
- ep.HandlePacket(pkt)
- }
-
- var tllData [header.NDPLinkLayerAddressSize]byte
- header.NDPOptions(tllData[:]).Serialize(header.NDPOptionsSerializer{
- header.NDPTargetLinkLayerAddressOption(linkAddr1),
- })
-
- var sllData [header.NDPLinkLayerAddressSize]byte
- header.NDPOptions(sllData[:]).Serialize(header.NDPOptionsSerializer{
- header.NDPSourceLinkLayerAddressOption(linkAddr1),
- })
-
- types := []struct {
- name string
- typ header.ICMPv6Type
- size int
- extraData []byte
- statCounter func(tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter
- routerOnly bool
- }{
- {
- name: "RouterSolicit",
- typ: header.ICMPv6RouterSolicit,
- size: header.ICMPv6MinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RouterSolicit
- },
- routerOnly: true,
- },
- {
- name: "RouterAdvert",
- typ: header.ICMPv6RouterAdvert,
- size: header.ICMPv6HeaderSize + header.NDPRAMinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RouterAdvert
- },
- },
- {
- name: "NeighborSolicit",
- typ: header.ICMPv6NeighborSolicit,
- size: header.ICMPv6NeighborSolicitMinimumSize,
- extraData: sllData[:],
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.NeighborSolicit
- },
- },
- {
- name: "NeighborAdvert",
- typ: header.ICMPv6NeighborAdvert,
- size: header.ICMPv6NeighborAdvertMinimumSize,
- extraData: tllData[:],
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.NeighborAdvert
- },
- },
- {
- name: "RedirectMsg",
- typ: header.ICMPv6RedirectMsg,
- size: header.ICMPv6MinimumSize,
- statCounter: func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
- return stats.RedirectMsg
- },
- },
- }
-
- subTests := []struct {
- name string
- atomicFragment bool
- hopLimit uint8
- code header.ICMPv6Code
- valid bool
- }{
- {
- name: "Valid",
- atomicFragment: false,
- hopLimit: header.NDPHopLimit,
- code: 0,
- valid: true,
- },
- {
- name: "Fragmented",
- atomicFragment: true,
- hopLimit: header.NDPHopLimit,
- code: 0,
- valid: false,
- },
- {
- name: "Invalid hop limit",
- atomicFragment: false,
- hopLimit: header.NDPHopLimit - 1,
- code: 0,
- valid: false,
- },
- {
- name: "Invalid ICMPv6 code",
- atomicFragment: false,
- hopLimit: header.NDPHopLimit,
- code: 1,
- valid: false,
- },
- }
-
- for _, typ := range types {
- for _, isRouter := range []bool{false, true} {
- name := typ.name
- if isRouter {
- name += " (Router)"
- }
-
- t.Run(name, func(t *testing.T) {
- for _, test := range subTests {
- t.Run(test.name, func(t *testing.T) {
- s, ep, r := setup(t)
- defer r.Release()
-
- if isRouter {
- // Enabling forwarding makes the stack act as a router.
- s.SetForwarding(ProtocolNumber, true)
- }
-
- stats := s.Stats().ICMP.V6PacketsReceived
- invalid := stats.Invalid
- routerOnly := stats.RouterOnlyPacketsDroppedByHost
- typStat := typ.statCounter(stats)
-
- icmp := header.ICMPv6(buffer.NewView(typ.size + len(typ.extraData)))
- copy(icmp[typ.size:], typ.extraData)
- icmp.SetType(typ.typ)
- icmp.SetCode(test.code)
- icmp.SetChecksum(header.ICMPv6Checksum(icmp[:typ.size], r.LocalAddress, r.RemoteAddress, buffer.View(typ.extraData).ToVectorisedView()))
-
- // Rx count of the NDP message should initially be 0.
- if got := typStat.Value(); got != 0 {
- t.Errorf("got %s = %d, want = 0", typ.name, got)
- }
-
- // Invalid count should initially be 0.
- if got := invalid.Value(); got != 0 {
- t.Errorf("got invalid = %d, want = 0", got)
- }
-
- // RouterOnlyPacketsReceivedByHost count should initially be 0.
- if got := routerOnly.Value(); got != 0 {
- t.Errorf("got RouterOnlyPacketsReceivedByHost = %d, want = 0", got)
- }
-
- if t.Failed() {
- t.FailNow()
- }
-
- handleIPv6Payload(buffer.View(icmp), test.hopLimit, test.atomicFragment, ep, &r)
-
- // Rx count of the NDP packet should have increased.
- if got := typStat.Value(); got != 1 {
- t.Errorf("got %s = %d, want = 1", typ.name, got)
- }
-
- want := uint64(0)
- if !test.valid {
- // Invalid count should have increased.
- want = 1
- }
- if got := invalid.Value(); got != want {
- t.Errorf("got invalid = %d, want = %d", got, want)
- }
-
- want = 0
- if test.valid && !isRouter && typ.routerOnly {
- // RouterOnlyPacketsReceivedByHost count should have increased.
- want = 1
- }
- if got := routerOnly.Value(); got != want {
- t.Errorf("got RouterOnlyPacketsReceivedByHost = %d, want = %d", got, want)
- }
-
- })
- }
- })
- }
- }
- })
- }
-
-}
-
-// TestRouterAdvertValidation tests that when the NIC is configured to handle
-// NDP Router Advertisement packets, it validates the Router Advertisement
-// properly before handling them.
-func TestRouterAdvertValidation(t *testing.T) {
- stacks := []struct {
- name string
- useNeighborCache bool
- }{
- {
- name: "linkAddrCache",
- useNeighborCache: false,
- },
- {
- name: "neighborCache",
- useNeighborCache: true,
- },
- }
-
- tests := []struct {
- name string
- src tcpip.Address
- hopLimit uint8
- code header.ICMPv6Code
- ndpPayload []byte
- expectedSuccess bool
- }{
- {
- "OK",
- lladdr0,
- 255,
- 0,
- []byte{
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- },
- true,
- },
- {
- "NonLinkLocalSourceAddr",
- addr1,
- 255,
- 0,
- []byte{
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- },
- false,
- },
- {
- "HopLimitNot255",
- lladdr0,
- 254,
- 0,
- []byte{
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- },
- false,
- },
- {
- "NonZeroCode",
- lladdr0,
- 255,
- 1,
- []byte{
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- },
- false,
- },
- {
- "NDPPayloadTooSmall",
- lladdr0,
- 255,
- 0,
- []byte{
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0,
- },
- false,
- },
- {
- "OKWithOptions",
- lladdr0,
- 255,
- 0,
- []byte{
- // RA payload
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
-
- // Option #1 (TargetLinkLayerAddress)
- 2, 1, 0, 0, 0, 0, 0, 0,
-
- // Option #2 (unrecognized)
- 255, 1, 0, 0, 0, 0, 0, 0,
-
- // Option #3 (PrefixInformation)
- 3, 4, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- },
- true,
- },
- {
- "OptionWithZeroLength",
- lladdr0,
- 255,
- 0,
- []byte{
- // RA payload
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
-
- // Option #1 (TargetLinkLayerAddress)
- // Invalid as it has 0 length.
- 2, 0, 0, 0, 0, 0, 0, 0,
-
- // Option #2 (unrecognized)
- 255, 1, 0, 0, 0, 0, 0, 0,
-
- // Option #3 (PrefixInformation)
- 3, 4, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- },
- false,
- },
- }
-
- for _, stackTyp := range stacks {
- t.Run(stackTyp.name, func(t *testing.T) {
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- e := channel.New(10, 1280, linkAddr1)
- e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
- s := stack.New(stack.Options{
- NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
- UseNeighborCache: stackTyp.useNeighborCache,
- })
-
- if err := s.CreateNIC(1, e); err != nil {
- t.Fatalf("CreateNIC(_) = %s", err)
- }
-
- icmpSize := header.ICMPv6HeaderSize + len(test.ndpPayload)
- hdr := buffer.NewPrependable(header.IPv6MinimumSize + icmpSize)
- pkt := header.ICMPv6(hdr.Prepend(icmpSize))
- pkt.SetType(header.ICMPv6RouterAdvert)
- pkt.SetCode(test.code)
- copy(pkt.NDPPayload(), test.ndpPayload)
- payloadLength := hdr.UsedLength()
- pkt.SetChecksum(header.ICMPv6Checksum(pkt, test.src, header.IPv6AllNodesMulticastAddress, buffer.VectorisedView{}))
- ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
- ip.Encode(&header.IPv6Fields{
- PayloadLength: uint16(payloadLength),
- NextHeader: uint8(icmp.ProtocolNumber6),
- HopLimit: test.hopLimit,
- SrcAddr: test.src,
- DstAddr: header.IPv6AllNodesMulticastAddress,
- })
-
- stats := s.Stats().ICMP.V6PacketsReceived
- invalid := stats.Invalid
- rxRA := stats.RouterAdvert
-
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
- if got := rxRA.Value(); got != 0 {
- t.Fatalf("got rxRA = %d, want = 0", got)
- }
-
- e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: hdr.View().ToVectorisedView(),
- }))
-
- if got := rxRA.Value(); got != 1 {
- t.Fatalf("got rxRA = %d, want = 1", got)
- }
-
- if test.expectedSuccess {
- if got := invalid.Value(); got != 0 {
- t.Fatalf("got invalid = %d, want = 0", got)
- }
- } else {
- if got := invalid.Value(); got != 1 {
- t.Fatalf("got invalid = %d, want = 1", got)
- }
- }
- })
- }
- })
- }
-}
diff --git a/pkg/tcpip/network/testutil/BUILD b/pkg/tcpip/network/testutil/BUILD
deleted file mode 100644
index d0ffc299a..000000000
--- a/pkg/tcpip/network/testutil/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("//tools:defs.bzl", "go_library")
-
-package(licenses = ["notice"])
-
-go_library(
- name = "testutil",
- srcs = [
- "testutil.go",
- ],
- visibility = [
- "//pkg/tcpip/network/fragmentation:__pkg__",
- "//pkg/tcpip/network/ipv4:__pkg__",
- "//pkg/tcpip/network/ipv6:__pkg__",
- ],
- deps = [
- "//pkg/tcpip",
- "//pkg/tcpip/buffer",
- "//pkg/tcpip/header",
- "//pkg/tcpip/stack",
- ],
-)
diff --git a/pkg/tcpip/network/testutil/testutil.go b/pkg/tcpip/network/testutil/testutil.go
deleted file mode 100644
index 7cc52985e..000000000
--- a/pkg/tcpip/network/testutil/testutil.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2020 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 testutil defines types and functions used to test Network Layer
-// functionality such as IP fragmentation.
-package testutil
-
-import (
- "fmt"
- "math/rand"
-
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/pkg/tcpip/buffer"
- "gvisor.dev/gvisor/pkg/tcpip/header"
- "gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-// MockLinkEndpoint is an endpoint used for testing, it stores packets written
-// to it and can mock errors.
-type MockLinkEndpoint struct {
- // WrittenPackets is where packets written to the endpoint are stored.
- WrittenPackets []*stack.PacketBuffer
-
- mtu uint32
- err *tcpip.Error
- allowPackets int
-}
-
-// NewMockLinkEndpoint creates a new MockLinkEndpoint.
-//
-// err is the error that will be returned once allowPackets packets are written
-// to the endpoint.
-func NewMockLinkEndpoint(mtu uint32, err *tcpip.Error, allowPackets int) *MockLinkEndpoint {
- return &MockLinkEndpoint{
- mtu: mtu,
- err: err,
- allowPackets: allowPackets,
- }
-}
-
-// MTU implements LinkEndpoint.MTU.
-func (ep *MockLinkEndpoint) MTU() uint32 { return ep.mtu }
-
-// Capabilities implements LinkEndpoint.Capabilities.
-func (*MockLinkEndpoint) Capabilities() stack.LinkEndpointCapabilities { return 0 }
-
-// MaxHeaderLength implements LinkEndpoint.MaxHeaderLength.
-func (*MockLinkEndpoint) MaxHeaderLength() uint16 { return 0 }
-
-// LinkAddress implements LinkEndpoint.LinkAddress.
-func (*MockLinkEndpoint) LinkAddress() tcpip.LinkAddress { return "" }
-
-// WritePacket implements LinkEndpoint.WritePacket.
-func (ep *MockLinkEndpoint) WritePacket(_ *stack.Route, _ *stack.GSO, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
- if ep.allowPackets == 0 {
- return ep.err
- }
- ep.allowPackets--
- ep.WrittenPackets = append(ep.WrittenPackets, pkt)
- return nil
-}
-
-// WritePackets implements LinkEndpoint.WritePackets.
-func (ep *MockLinkEndpoint) WritePackets(r *stack.Route, gso *stack.GSO, pkts stack.PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, *tcpip.Error) {
- var n int
-
- for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
- if err := ep.WritePacket(r, gso, protocol, pkt); err != nil {
- return n, err
- }
- n++
- }
-
- return n, nil
-}
-
-// WriteRawPacket implements LinkEndpoint.WriteRawPacket.
-func (ep *MockLinkEndpoint) WriteRawPacket(vv buffer.VectorisedView) *tcpip.Error {
- if ep.allowPackets == 0 {
- return ep.err
- }
- ep.allowPackets--
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- Data: vv,
- })
- ep.WrittenPackets = append(ep.WrittenPackets, pkt)
-
- return nil
-}
-
-// Attach implements LinkEndpoint.Attach.
-func (*MockLinkEndpoint) Attach(stack.NetworkDispatcher) {}
-
-// IsAttached implements LinkEndpoint.IsAttached.
-func (*MockLinkEndpoint) IsAttached() bool { return false }
-
-// Wait implements LinkEndpoint.Wait.
-func (*MockLinkEndpoint) Wait() {}
-
-// ARPHardwareType implements LinkEndpoint.ARPHardwareType.
-func (*MockLinkEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone }
-
-// AddHeader implements LinkEndpoint.AddHeader.
-func (*MockLinkEndpoint) AddHeader(_, _ tcpip.LinkAddress, _ tcpip.NetworkProtocolNumber, _ *stack.PacketBuffer) {
-}
-
-// MakeRandPkt generates a randomized packet. transportHeaderLength indicates
-// how many random bytes will be copied in the Transport Header.
-// extraHeaderReserveLength indicates how much extra space will be reserved for
-// the other headers. The payload is made from Views of the sizes listed in
-// viewSizes.
-func MakeRandPkt(transportHeaderLength int, extraHeaderReserveLength int, viewSizes []int, proto tcpip.NetworkProtocolNumber) *stack.PacketBuffer {
- var views buffer.VectorisedView
-
- for _, s := range viewSizes {
- newView := buffer.NewView(s)
- if _, err := rand.Read(newView); err != nil {
- panic(fmt.Sprintf("rand.Read: %s", err))
- }
- views.AppendView(newView)
- }
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: transportHeaderLength + extraHeaderReserveLength,
- Data: views,
- })
- pkt.NetworkProtocolNumber = proto
- if _, err := rand.Read(pkt.TransportHeader().Push(transportHeaderLength)); err != nil {
- panic(fmt.Sprintf("rand.Read: %s", err))
- }
- return pkt
-}