Merge release-20210315.0-21-gec0aa657e (automated)
diff --git a/pkg/goid/goid_test.go b/pkg/goid/goid_test.go
deleted file mode 100644
index 54be11d..0000000
--- a/pkg/goid/goid_test.go
+++ /dev/null
@@ -1,81 +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 goid
-
-import (
-	"runtime"
-	"sync"
-	"testing"
-	"time"
-)
-
-func TestUniquenessAndConsistency(t *testing.T) {
-	const (
-		numGoroutines = 5000
-
-		// maxID is not an intrinsic property of goroutine IDs; it is only a
-		// property of how the Go runtime currently assigns them. Future
-		// changes to the Go runtime may require that maxID be raised, or that
-		// assertions regarding it be removed entirely.
-		maxID = numGoroutines + 1000
-	)
-
-	var (
-		goidsMu   sync.Mutex
-		goids     = make(map[int64]struct{})
-		checkedWG sync.WaitGroup
-		exitCh    = make(chan struct{})
-	)
-	for i := 0; i < numGoroutines; i++ {
-		checkedWG.Add(1)
-		go func() {
-			id := Get()
-			if id > maxID {
-				t.Errorf("observed unexpectedly large goroutine ID %d", id)
-			}
-			goidsMu.Lock()
-			if _, dup := goids[id]; dup {
-				t.Errorf("observed duplicate goroutine ID %d", id)
-			}
-			goids[id] = struct{}{}
-			goidsMu.Unlock()
-			checkedWG.Done()
-			for {
-				if curID := Get(); curID != id {
-					t.Errorf("goroutine ID changed from %d to %d", id, curID)
-					// Don't spam logs by repeating the check; wait quietly for
-					// the test to finish.
-					<-exitCh
-					return
-				}
-				// Check if the test is over.
-				select {
-				case <-exitCh:
-					return
-				default:
-				}
-				// Yield to other goroutines, and possibly migrate to another P.
-				runtime.Gosched()
-			}
-		}()
-	}
-	// Wait for all goroutines to perform uniqueness checks.
-	checkedWG.Wait()
-	// Wait for an additional second to allow goroutines to spin checking for
-	// ID consistency.
-	time.Sleep(time.Second)
-	// Request that all goroutines exit.
-	close(exitCh)
-}
diff --git a/pkg/linewriter/linewriter_test.go b/pkg/linewriter/linewriter_test.go
deleted file mode 100644
index 96dc7e6..0000000
--- a/pkg/linewriter/linewriter_test.go
+++ /dev/null
@@ -1,81 +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 linewriter
-
-import (
-	"bytes"
-	"testing"
-)
-
-func TestWriter(t *testing.T) {
-	testCases := []struct {
-		input []string
-		want  []string
-	}{
-		{
-			input: []string{"1\n", "2\n"},
-			want:  []string{"1", "2"},
-		},
-		{
-			input: []string{"1\n", "\n", "2\n"},
-			want:  []string{"1", "", "2"},
-		},
-		{
-			input: []string{"1\n2\n", "3\n"},
-			want:  []string{"1", "2", "3"},
-		},
-		{
-			input: []string{"1", "2\n"},
-			want:  []string{"12"},
-		},
-		{
-			// Data with no newline yet is omitted.
-			input: []string{"1\n", "2\n", "3"},
-			want:  []string{"1", "2"},
-		},
-	}
-
-	for _, c := range testCases {
-		var lines [][]byte
-
-		w := NewWriter(func(p []byte) {
-			// We must not retain p, so we must make a copy.
-			b := make([]byte, len(p))
-			copy(b, p)
-
-			lines = append(lines, b)
-		})
-
-		for _, in := range c.input {
-			n, err := w.Write([]byte(in))
-			if err != nil {
-				t.Errorf("Write(%q) err got %v want nil (case %+v)", in, err, c)
-			}
-			if n != len(in) {
-				t.Errorf("Write(%q) b got %d want %d (case %+v)", in, n, len(in), c)
-			}
-		}
-
-		if len(lines) != len(c.want) {
-			t.Errorf("len(lines) got %d want %d (case %+v)", len(lines), len(c.want), c)
-		}
-
-		for i := range lines {
-			if !bytes.Equal(lines[i], []byte(c.want[i])) {
-				t.Errorf("item %d got %q want %q (case %+v)", i, lines[i], c.want[i], c)
-			}
-		}
-	}
-}
diff --git a/pkg/log/json_test.go b/pkg/log/json_test.go
deleted file mode 100644
index f25224f..0000000
--- a/pkg/log/json_test.go
+++ /dev/null
@@ -1,64 +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 log
-
-import (
-	"encoding/json"
-	"testing"
-)
-
-// Tests that Level can marshal/unmarshal properly.
-func TestLevelMarshal(t *testing.T) {
-	lvs := []Level{Warning, Info, Debug}
-	for _, lv := range lvs {
-		bs, err := lv.MarshalJSON()
-		if err != nil {
-			t.Errorf("error marshaling %v: %v", lv, err)
-		}
-		var lv2 Level
-		if err := lv2.UnmarshalJSON(bs); err != nil {
-			t.Errorf("error unmarshaling %v: %v", bs, err)
-		}
-		if lv != lv2 {
-			t.Errorf("marshal/unmarshal level got %v wanted %v", lv2, lv)
-		}
-	}
-}
-
-// Test that integers can be properly unmarshaled.
-func TestUnmarshalFromInt(t *testing.T) {
-	tcs := []struct {
-		i    int
-		want Level
-	}{
-		{0, Warning},
-		{1, Info},
-		{2, Debug},
-	}
-
-	for _, tc := range tcs {
-		j, err := json.Marshal(tc.i)
-		if err != nil {
-			t.Errorf("error marshaling %v: %v", tc.i, err)
-		}
-		var lv Level
-		if err := lv.UnmarshalJSON(j); err != nil {
-			t.Errorf("error unmarshaling %v: %v", j, err)
-		}
-		if lv != tc.want {
-			t.Errorf("marshal/unmarshal %v got %v want %v", tc.i, lv, tc.want)
-		}
-	}
-}
diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go
deleted file mode 100644
index 9ff1855..0000000
--- a/pkg/log/log_test.go
+++ /dev/null
@@ -1,105 +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 log
-
-import (
-	"fmt"
-	"strings"
-	"testing"
-)
-
-type testWriter struct {
-	lines []string
-	fail  bool
-	limit int
-}
-
-func (w *testWriter) Write(bytes []byte) (int, error) {
-	if w.fail {
-		return 0, fmt.Errorf("simulated failure")
-	}
-	if w.limit > 0 && len(w.lines) >= w.limit {
-		return len(bytes), nil
-	}
-	w.lines = append(w.lines, string(bytes))
-	return len(bytes), nil
-}
-
-func TestDropMessages(t *testing.T) {
-	tw := &testWriter{}
-	w := Writer{Next: tw}
-	if _, err := w.Write([]byte("line 1\n")); err != nil {
-		t.Fatalf("Write failed, err: %v", err)
-	}
-
-	tw.fail = true
-	if _, err := w.Write([]byte("error\n")); err == nil {
-		t.Fatalf("Write should have failed")
-	}
-	if _, err := w.Write([]byte("error\n")); err == nil {
-		t.Fatalf("Write should have failed")
-	}
-
-	fmt.Printf("writer: %#v\n", &w)
-
-	tw.fail = false
-	if _, err := w.Write([]byte("line 2\n")); err != nil {
-		t.Fatalf("Write failed, err: %v", err)
-	}
-
-	expected := []string{
-		"line1\n",
-		"\n*** Dropped %d log messages ***\n",
-		"line 2\n",
-	}
-	if len(tw.lines) != len(expected) {
-		t.Fatalf("Writer should have logged %d lines, got: %v, expected: %v", len(expected), tw.lines, expected)
-	}
-	for i, l := range tw.lines {
-		if l == expected[i] {
-			t.Fatalf("line %d doesn't match, got: %v, expected: %v", i, l, expected[i])
-		}
-	}
-}
-
-func TestCaller(t *testing.T) {
-	tw := &testWriter{}
-	e := GoogleEmitter{Writer: &Writer{Next: tw}}
-	bl := &BasicLogger{
-		Emitter: e,
-		Level:   Debug,
-	}
-	bl.Debugf("testing...\n") // Just for file + line.
-	if len(tw.lines) != 1 {
-		t.Errorf("expected 1 line, got %d", len(tw.lines))
-	}
-	if !strings.Contains(tw.lines[0], "log_test.go") {
-		t.Errorf("expected log_test.go, got %q", tw.lines[0])
-	}
-}
-
-func BenchmarkGoogleLogging(b *testing.B) {
-	tw := &testWriter{
-		limit: 1, // Only record one message.
-	}
-	e := GoogleEmitter{Writer: &Writer{Next: tw}}
-	bl := &BasicLogger{
-		Emitter: e,
-		Level:   Debug,
-	}
-	for i := 0; i < b.N; i++ {
-		bl.Debugf("hello %d, %d, %d", 1, 2, 3)
-	}
-}
diff --git a/pkg/rand/rand_linux_state_autogen.go b/pkg/rand/rand_linux_state_autogen.go
new file mode 100644
index 0000000..f727c93
--- /dev/null
+++ b/pkg/rand/rand_linux_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package rand
diff --git a/pkg/rand/rand_state_autogen.go b/pkg/rand/rand_state_autogen.go
new file mode 100644
index 0000000..e0a5cd1
--- /dev/null
+++ b/pkg/rand/rand_state_autogen.go
@@ -0,0 +1,5 @@
+// automatically generated by stateify.
+
+// +build !linux
+
+package rand
diff --git a/pkg/sleep/sleep_test.go b/pkg/sleep/sleep_test.go
deleted file mode 100644
index 1dd1170..0000000
--- a/pkg/sleep/sleep_test.go
+++ /dev/null
@@ -1,567 +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 sleep
-
-import (
-	"math/rand"
-	"runtime"
-	"testing"
-	"time"
-)
-
-// ZeroWakerNotAsserted tests that a zero-value waker is in non-asserted state.
-func ZeroWakerNotAsserted(t *testing.T) {
-	var w Waker
-	if w.IsAsserted() {
-		t.Fatalf("Zero waker is asserted")
-	}
-
-	if w.Clear() {
-		t.Fatalf("Zero waker is asserted")
-	}
-}
-
-// AssertedWakerAfterAssert tests that a waker properly reports its state as
-// asserted once its Assert() method is called.
-func AssertedWakerAfterAssert(t *testing.T) {
-	var w Waker
-	w.Assert()
-	if !w.IsAsserted() {
-		t.Fatalf("Asserted waker is not reported as such")
-	}
-
-	if !w.Clear() {
-		t.Fatalf("Asserted waker is not reported as such")
-	}
-}
-
-// AssertedWakerAfterTwoAsserts tests that a waker properly reports its state as
-// asserted once its Assert() method is called twice.
-func AssertedWakerAfterTwoAsserts(t *testing.T) {
-	var w Waker
-	w.Assert()
-	w.Assert()
-	if !w.IsAsserted() {
-		t.Fatalf("Asserted waker is not reported as such")
-	}
-
-	if !w.Clear() {
-		t.Fatalf("Asserted waker is not reported as such")
-	}
-}
-
-// NotAssertedWakerWithSleeper tests that a waker properly reports its state as
-// not asserted after a sleeper is associated with it.
-func NotAssertedWakerWithSleeper(t *testing.T) {
-	var w Waker
-	var s Sleeper
-	s.AddWaker(&w, 0)
-	if w.IsAsserted() {
-		t.Fatalf("Non-asserted waker is reported as asserted")
-	}
-
-	if w.Clear() {
-		t.Fatalf("Non-asserted waker is reported as asserted")
-	}
-}
-
-// NotAssertedWakerAfterWake tests that a waker properly reports its state as
-// not asserted after a previous assert is consumed by a sleeper. That is, tests
-// the "edge-triggered" behavior.
-func NotAssertedWakerAfterWake(t *testing.T) {
-	var w Waker
-	var s Sleeper
-	s.AddWaker(&w, 0)
-	w.Assert()
-	s.Fetch(true)
-	if w.IsAsserted() {
-		t.Fatalf("Consumed waker is reported as asserted")
-	}
-
-	if w.Clear() {
-		t.Fatalf("Consumed waker is reported as asserted")
-	}
-}
-
-// AssertedWakerBeforeAdd tests that a waker causes a sleeper to not sleep if
-// it's already asserted before being added.
-func AssertedWakerBeforeAdd(t *testing.T) {
-	var w Waker
-	var s Sleeper
-	w.Assert()
-	s.AddWaker(&w, 0)
-
-	if _, ok := s.Fetch(false); !ok {
-		t.Fatalf("Fetch failed even though asserted waker was added")
-	}
-}
-
-// ClearedWaker tests that a waker properly reports its state as not asserted
-// after it is cleared.
-func ClearedWaker(t *testing.T) {
-	var w Waker
-	w.Assert()
-	w.Clear()
-	if w.IsAsserted() {
-		t.Fatalf("Cleared waker is reported as asserted")
-	}
-
-	if w.Clear() {
-		t.Fatalf("Cleared waker is reported as asserted")
-	}
-}
-
-// ClearedWakerWithSleeper tests that a waker properly reports its state as
-// not asserted when it is cleared while it has a sleeper associated with it.
-func ClearedWakerWithSleeper(t *testing.T) {
-	var w Waker
-	var s Sleeper
-	s.AddWaker(&w, 0)
-	w.Clear()
-	if w.IsAsserted() {
-		t.Fatalf("Cleared waker is reported as asserted")
-	}
-
-	if w.Clear() {
-		t.Fatalf("Cleared waker is reported as asserted")
-	}
-}
-
-// ClearedWakerAssertedWithSleeper tests that a waker properly reports its state
-// as not asserted when it is cleared while it has a sleeper associated with it
-// and has been asserted.
-func ClearedWakerAssertedWithSleeper(t *testing.T) {
-	var w Waker
-	var s Sleeper
-	s.AddWaker(&w, 0)
-	w.Assert()
-	w.Clear()
-	if w.IsAsserted() {
-		t.Fatalf("Cleared waker is reported as asserted")
-	}
-
-	if w.Clear() {
-		t.Fatalf("Cleared waker is reported as asserted")
-	}
-}
-
-// TestBlock tests that a sleeper actually blocks waiting for the waker to
-// assert its state.
-func TestBlock(t *testing.T) {
-	var w Waker
-	var s Sleeper
-
-	s.AddWaker(&w, 0)
-
-	// Assert waker after one second.
-	before := time.Now()
-	go func() {
-		time.Sleep(1 * time.Second)
-		w.Assert()
-	}()
-
-	// Fetch the result and make sure it took at least 500ms.
-	if _, ok := s.Fetch(true); !ok {
-		t.Fatalf("Fetch failed unexpectedly")
-	}
-	if d := time.Now().Sub(before); d < 500*time.Millisecond {
-		t.Fatalf("Duration was too short: %v", d)
-	}
-
-	// Check that already-asserted waker completes inline.
-	w.Assert()
-	if _, ok := s.Fetch(true); !ok {
-		t.Fatalf("Fetch failed unexpectedly")
-	}
-
-	// Check that fetch sleeps if waker had been asserted but was reset
-	// before Fetch is called.
-	w.Assert()
-	w.Clear()
-	before = time.Now()
-	go func() {
-		time.Sleep(1 * time.Second)
-		w.Assert()
-	}()
-	if _, ok := s.Fetch(true); !ok {
-		t.Fatalf("Fetch failed unexpectedly")
-	}
-	if d := time.Now().Sub(before); d < 500*time.Millisecond {
-		t.Fatalf("Duration was too short: %v", d)
-	}
-}
-
-// TestNonBlock checks that a sleeper won't block if waker isn't asserted.
-func TestNonBlock(t *testing.T) {
-	var w Waker
-	var s Sleeper
-
-	// Don't block when there's no waker.
-	if _, ok := s.Fetch(false); ok {
-		t.Fatalf("Fetch succeeded when there is no waker")
-	}
-
-	// Don't block when waker isn't asserted.
-	s.AddWaker(&w, 0)
-	if _, ok := s.Fetch(false); ok {
-		t.Fatalf("Fetch succeeded when waker was not asserted")
-	}
-
-	// Don't block when waker was asserted, but isn't anymore.
-	w.Assert()
-	w.Clear()
-	if _, ok := s.Fetch(false); ok {
-		t.Fatalf("Fetch succeeded when waker was not asserted anymore")
-	}
-
-	// Don't block when waker was consumed by previous Fetch().
-	w.Assert()
-	if _, ok := s.Fetch(false); !ok {
-		t.Fatalf("Fetch failed even though waker was asserted")
-	}
-
-	if _, ok := s.Fetch(false); ok {
-		t.Fatalf("Fetch succeeded when waker had been consumed")
-	}
-}
-
-// TestMultiple checks that a sleeper can wait for and receives notifications
-// from multiple wakers.
-func TestMultiple(t *testing.T) {
-	s := Sleeper{}
-	w1 := Waker{}
-	w2 := Waker{}
-
-	s.AddWaker(&w1, 0)
-	s.AddWaker(&w2, 1)
-
-	w1.Assert()
-	w2.Assert()
-
-	v, ok := s.Fetch(false)
-	if !ok {
-		t.Fatalf("Fetch failed when there are asserted wakers")
-	}
-
-	if v != 0 && v != 1 {
-		t.Fatalf("Unexpected waker id: %v", v)
-	}
-
-	want := 1 - v
-	v, ok = s.Fetch(false)
-	if !ok {
-		t.Fatalf("Fetch failed when there is an asserted waker")
-	}
-
-	if v != want {
-		t.Fatalf("Unexpected waker id, got %v, want %v", v, want)
-	}
-}
-
-// TestDoneFunction tests if calling Done() on a sleeper works properly.
-func TestDoneFunction(t *testing.T) {
-	// Trivial case of no waker.
-	s := Sleeper{}
-	s.Done()
-
-	// Cases when the sleeper has n wakers, but none are asserted.
-	for n := 1; n < 20; n++ {
-		s := Sleeper{}
-		w := make([]Waker, n)
-		for j := 0; j < n; j++ {
-			s.AddWaker(&w[j], j)
-		}
-		s.Done()
-	}
-
-	// Cases when the sleeper has n wakers, and only the i-th one is
-	// asserted.
-	for n := 1; n < 20; n++ {
-		for i := 0; i < n; i++ {
-			s := Sleeper{}
-			w := make([]Waker, n)
-			for j := 0; j < n; j++ {
-				s.AddWaker(&w[j], j)
-			}
-			w[i].Assert()
-			s.Done()
-		}
-	}
-
-	// Cases when the sleeper has n wakers, and the i-th one is asserted
-	// and cleared.
-	for n := 1; n < 20; n++ {
-		for i := 0; i < n; i++ {
-			s := Sleeper{}
-			w := make([]Waker, n)
-			for j := 0; j < n; j++ {
-				s.AddWaker(&w[j], j)
-			}
-			w[i].Assert()
-			w[i].Clear()
-			s.Done()
-		}
-	}
-
-	// Cases when the sleeper has n wakers, with a random number of them
-	// asserted.
-	for n := 1; n < 20; n++ {
-		for iters := 0; iters < 1000; iters++ {
-			s := Sleeper{}
-			w := make([]Waker, n)
-			for j := 0; j < n; j++ {
-				s.AddWaker(&w[j], j)
-			}
-
-			// Pick the number of asserted elements, then assert
-			// random wakers.
-			asserted := rand.Int() % (n + 1)
-			for j := 0; j < asserted; j++ {
-				w[rand.Int()%n].Assert()
-			}
-			s.Done()
-		}
-	}
-}
-
-// TestRace tests that multiple wakers can continuously send wake requests to
-// the sleeper.
-func TestRace(t *testing.T) {
-	const wakers = 100
-	const wakeRequests = 10000
-
-	counts := make([]int, wakers)
-	w := make([]Waker, wakers)
-	s := Sleeper{}
-
-	// Associate each waker and start goroutines that will assert them.
-	for i := range w {
-		s.AddWaker(&w[i], i)
-		go func(w *Waker) {
-			n := 0
-			for n < wakeRequests {
-				if !w.IsAsserted() {
-					w.Assert()
-					n++
-				} else {
-					runtime.Gosched()
-				}
-			}
-		}(&w[i])
-	}
-
-	// Wait for all wake up notifications from all wakers.
-	for i := 0; i < wakers*wakeRequests; i++ {
-		v, _ := s.Fetch(true)
-		counts[v]++
-	}
-
-	// Check that we got the right number for each.
-	for i, v := range counts {
-		if v != wakeRequests {
-			t.Errorf("Waker %v only got %v wakes", i, v)
-		}
-	}
-}
-
-// TestRaceInOrder tests that multiple wakers can continuously send wake requests to
-// the sleeper and that the wakers are retrieved in the order asserted.
-func TestRaceInOrder(t *testing.T) {
-	w := make([]Waker, 10000)
-	s := Sleeper{}
-
-	// Associate each waker and start goroutines that will assert them.
-	for i := range w {
-		s.AddWaker(&w[i], i)
-	}
-	go func() {
-		for i := range w {
-			w[i].Assert()
-		}
-	}()
-
-	// Wait for all wake up notifications from all wakers.
-	for want := range w {
-		got, _ := s.Fetch(true)
-		if got != want {
-			t.Fatalf("got %d want %d", got, want)
-		}
-	}
-}
-
-// BenchmarkSleeperMultiSelect measures how long it takes to fetch a wake up
-// from 4 wakers when at least one is already asserted.
-func BenchmarkSleeperMultiSelect(b *testing.B) {
-	const count = 4
-	s := Sleeper{}
-	w := make([]Waker, count)
-	for i := range w {
-		s.AddWaker(&w[i], i)
-	}
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		w[count-1].Assert()
-		s.Fetch(true)
-	}
-}
-
-// BenchmarkGoMultiSelect measures how long it takes to fetch a zero-length
-// struct from one of 4 channels when at least one is ready.
-func BenchmarkGoMultiSelect(b *testing.B) {
-	const count = 4
-	ch := make([]chan struct{}, count)
-	for i := range ch {
-		ch[i] = make(chan struct{}, 1)
-	}
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		ch[count-1] <- struct{}{}
-		select {
-		case <-ch[0]:
-		case <-ch[1]:
-		case <-ch[2]:
-		case <-ch[3]:
-		}
-	}
-}
-
-// BenchmarkSleeperSingleSelect measures how long it takes to fetch a wake up
-// from one waker that is already asserted.
-func BenchmarkSleeperSingleSelect(b *testing.B) {
-	s := Sleeper{}
-	w := Waker{}
-	s.AddWaker(&w, 0)
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		w.Assert()
-		s.Fetch(true)
-	}
-}
-
-// BenchmarkGoSingleSelect measures how long it takes to fetch a zero-length
-// struct from a channel that already has it buffered.
-func BenchmarkGoSingleSelect(b *testing.B) {
-	ch := make(chan struct{}, 1)
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		ch <- struct{}{}
-		<-ch
-	}
-}
-
-// BenchmarkSleeperAssertNonWaiting measures how long it takes to assert a
-// channel that is already asserted.
-func BenchmarkSleeperAssertNonWaiting(b *testing.B) {
-	w := Waker{}
-	w.Assert()
-	for i := 0; i < b.N; i++ {
-		w.Assert()
-	}
-
-}
-
-// BenchmarkGoAssertNonWaiting measures how long it takes to write to a channel
-// that has already something written to it.
-func BenchmarkGoAssertNonWaiting(b *testing.B) {
-	ch := make(chan struct{}, 1)
-	ch <- struct{}{}
-	for i := 0; i < b.N; i++ {
-		select {
-		case ch <- struct{}{}:
-		default:
-		}
-	}
-}
-
-// BenchmarkSleeperWaitOnSingleSelect measures how long it takes to wait on one
-// waker channel while another goroutine wakes up the sleeper. This assumes that
-// a new goroutine doesn't run immediately (i.e., the creator of a new goroutine
-// is allowed to go to sleep before the new goroutine has a chance to run).
-func BenchmarkSleeperWaitOnSingleSelect(b *testing.B) {
-	s := Sleeper{}
-	w := Waker{}
-	s.AddWaker(&w, 0)
-	for i := 0; i < b.N; i++ {
-		go func() {
-			w.Assert()
-		}()
-		s.Fetch(true)
-	}
-
-}
-
-// BenchmarkGoWaitOnSingleSelect measures how long it takes to wait on one
-// channel while another goroutine wakes up the sleeper. This assumes that a new
-// goroutine doesn't run immediately (i.e., the creator of a new goroutine is
-// allowed to go to sleep before the new goroutine has a chance to run).
-func BenchmarkGoWaitOnSingleSelect(b *testing.B) {
-	ch := make(chan struct{}, 1)
-	for i := 0; i < b.N; i++ {
-		go func() {
-			ch <- struct{}{}
-		}()
-		<-ch
-	}
-}
-
-// BenchmarkSleeperWaitOnMultiSelect measures how long it takes to wait on 4
-// wakers while another goroutine wakes up the sleeper. This assumes that a new
-// goroutine doesn't run immediately (i.e., the creator of a new goroutine is
-// allowed to go to sleep before the new goroutine has a chance to run).
-func BenchmarkSleeperWaitOnMultiSelect(b *testing.B) {
-	const count = 4
-	s := Sleeper{}
-	w := make([]Waker, count)
-	for i := range w {
-		s.AddWaker(&w[i], i)
-	}
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		go func() {
-			w[count-1].Assert()
-		}()
-		s.Fetch(true)
-	}
-}
-
-// BenchmarkGoWaitOnMultiSelect measures how long it takes to wait on 4 channels
-// while another goroutine wakes up the sleeper. This assumes that a new
-// goroutine doesn't run immediately (i.e., the creator of a new goroutine is
-// allowed to go to sleep before the new goroutine has a chance to run).
-func BenchmarkGoWaitOnMultiSelect(b *testing.B) {
-	const count = 4
-	ch := make([]chan struct{}, count)
-	for i := range ch {
-		ch[i] = make(chan struct{}, 1)
-	}
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		go func() {
-			ch[count-1] <- struct{}{}
-		}()
-		select {
-		case <-ch[0]:
-		case <-ch[1]:
-		case <-ch[2]:
-		case <-ch[3]:
-		}
-	}
-}
diff --git a/pkg/sleep/sleep_unsafe_state_autogen.go b/pkg/sleep/sleep_unsafe_state_autogen.go
new file mode 100644
index 0000000..bea86dc
--- /dev/null
+++ b/pkg/sleep/sleep_unsafe_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package sleep
diff --git a/pkg/state/addr_range.go b/pkg/state/addr_range.go
new file mode 100644
index 0000000..0b7346e
--- /dev/null
+++ b/pkg/state/addr_range.go
@@ -0,0 +1,76 @@
+package state
+
+// A Range represents a contiguous range of T.
+//
+// +stateify savable
+type addrRange struct {
+	// Start is the inclusive start of the range.
+	Start uintptr
+
+	// End is the exclusive end of the range.
+	End uintptr
+}
+
+// WellFormed returns true if r.Start <= r.End. All other methods on a Range
+// require that the Range is well-formed.
+//
+//go:nosplit
+func (r addrRange) WellFormed() bool {
+	return r.Start <= r.End
+}
+
+// Length returns the length of the range.
+//
+//go:nosplit
+func (r addrRange) Length() uintptr {
+	return r.End - r.Start
+}
+
+// Contains returns true if r contains x.
+//
+//go:nosplit
+func (r addrRange) Contains(x uintptr) bool {
+	return r.Start <= x && x < r.End
+}
+
+// Overlaps returns true if r and r2 overlap.
+//
+//go:nosplit
+func (r addrRange) Overlaps(r2 addrRange) bool {
+	return r.Start < r2.End && r2.Start < r.End
+}
+
+// IsSupersetOf returns true if r is a superset of r2; that is, the range r2 is
+// contained within r.
+//
+//go:nosplit
+func (r addrRange) IsSupersetOf(r2 addrRange) bool {
+	return r.Start <= r2.Start && r.End >= r2.End
+}
+
+// Intersect returns a range consisting of the intersection between r and r2.
+// If r and r2 do not overlap, Intersect returns a range with unspecified
+// bounds, but for which Length() == 0.
+//
+//go:nosplit
+func (r addrRange) Intersect(r2 addrRange) addrRange {
+	if r.Start < r2.Start {
+		r.Start = r2.Start
+	}
+	if r.End > r2.End {
+		r.End = r2.End
+	}
+	if r.End < r.Start {
+		r.End = r.Start
+	}
+	return r
+}
+
+// CanSplitAt returns true if it is legal to split a segment spanning the range
+// r at x; that is, splitting at x would produce two ranges, both of which have
+// non-zero length.
+//
+//go:nosplit
+func (r addrRange) CanSplitAt(x uintptr) bool {
+	return r.Contains(x) && r.Start < x
+}
diff --git a/pkg/state/addr_set.go b/pkg/state/addr_set.go
new file mode 100644
index 0000000..8abc3dd
--- /dev/null
+++ b/pkg/state/addr_set.go
@@ -0,0 +1,1643 @@
+package state
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// trackGaps is an optional parameter.
+//
+// If trackGaps is 1, the Set will track maximum gap size recursively,
+// enabling the GapIterator.{Prev,Next}LargeEnoughGap functions. In this
+// case, Key must be an unsigned integer.
+//
+// trackGaps must be 0 or 1.
+const addrtrackGaps = 0
+
+var _ = uint8(addrtrackGaps << 7) // Will fail if not zero or one.
+
+// dynamicGap is a type that disappears if trackGaps is 0.
+type addrdynamicGap [addrtrackGaps]uintptr
+
+// Get returns the value of the gap.
+//
+// Precondition: trackGaps must be non-zero.
+func (d *addrdynamicGap) Get() uintptr {
+	return d[:][0]
+}
+
+// Set sets the value of the gap.
+//
+// Precondition: trackGaps must be non-zero.
+func (d *addrdynamicGap) Set(v uintptr) {
+	d[:][0] = v
+}
+
+const (
+	// minDegree is the minimum degree of an internal node in a Set B-tree.
+	//
+	// - Any non-root node has at least minDegree-1 segments.
+	//
+	// - Any non-root internal (non-leaf) node has at least minDegree children.
+	//
+	// - The root node may have fewer than minDegree-1 segments, but it may
+	// only have 0 segments if the tree is empty.
+	//
+	// Our implementation requires minDegree >= 3. Higher values of minDegree
+	// usually improve performance, but increase memory usage for small sets.
+	addrminDegree = 10
+
+	addrmaxDegree = 2 * addrminDegree
+)
+
+// A Set is a mapping of segments with non-overlapping Range keys. The zero
+// value for a Set is an empty set. Set values are not safely movable nor
+// copyable. Set is thread-compatible.
+//
+// +stateify savable
+type addrSet struct {
+	root addrnode `state:".(*addrSegmentDataSlices)"`
+}
+
+// IsEmpty returns true if the set contains no segments.
+func (s *addrSet) IsEmpty() bool {
+	return s.root.nrSegments == 0
+}
+
+// IsEmptyRange returns true iff no segments in the set overlap the given
+// range. This is semantically equivalent to s.SpanRange(r) == 0, but may be
+// more efficient.
+func (s *addrSet) IsEmptyRange(r addrRange) bool {
+	switch {
+	case r.Length() < 0:
+		panic(fmt.Sprintf("invalid range %v", r))
+	case r.Length() == 0:
+		return true
+	}
+	_, gap := s.Find(r.Start)
+	if !gap.Ok() {
+		return false
+	}
+	return r.End <= gap.End()
+}
+
+// Span returns the total size of all segments in the set.
+func (s *addrSet) Span() uintptr {
+	var sz uintptr
+	for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+		sz += seg.Range().Length()
+	}
+	return sz
+}
+
+// SpanRange returns the total size of the intersection of segments in the set
+// with the given range.
+func (s *addrSet) SpanRange(r addrRange) uintptr {
+	switch {
+	case r.Length() < 0:
+		panic(fmt.Sprintf("invalid range %v", r))
+	case r.Length() == 0:
+		return 0
+	}
+	var sz uintptr
+	for seg := s.LowerBoundSegment(r.Start); seg.Ok() && seg.Start() < r.End; seg = seg.NextSegment() {
+		sz += seg.Range().Intersect(r).Length()
+	}
+	return sz
+}
+
+// FirstSegment returns the first segment in the set. If the set is empty,
+// FirstSegment returns a terminal iterator.
+func (s *addrSet) FirstSegment() addrIterator {
+	if s.root.nrSegments == 0 {
+		return addrIterator{}
+	}
+	return s.root.firstSegment()
+}
+
+// LastSegment returns the last segment in the set. If the set is empty,
+// LastSegment returns a terminal iterator.
+func (s *addrSet) LastSegment() addrIterator {
+	if s.root.nrSegments == 0 {
+		return addrIterator{}
+	}
+	return s.root.lastSegment()
+}
+
+// FirstGap returns the first gap in the set.
+func (s *addrSet) FirstGap() addrGapIterator {
+	n := &s.root
+	for n.hasChildren {
+		n = n.children[0]
+	}
+	return addrGapIterator{n, 0}
+}
+
+// LastGap returns the last gap in the set.
+func (s *addrSet) LastGap() addrGapIterator {
+	n := &s.root
+	for n.hasChildren {
+		n = n.children[n.nrSegments]
+	}
+	return addrGapIterator{n, n.nrSegments}
+}
+
+// Find returns the segment or gap whose range contains the given key. If a
+// segment is found, the returned Iterator is non-terminal and the
+// returned GapIterator is terminal. Otherwise, the returned Iterator is
+// terminal and the returned GapIterator is non-terminal.
+func (s *addrSet) Find(key uintptr) (addrIterator, addrGapIterator) {
+	n := &s.root
+	for {
+
+		lower := 0
+		upper := n.nrSegments
+		for lower < upper {
+			i := lower + (upper-lower)/2
+			if r := n.keys[i]; key < r.End {
+				if key >= r.Start {
+					return addrIterator{n, i}, addrGapIterator{}
+				}
+				upper = i
+			} else {
+				lower = i + 1
+			}
+		}
+		i := lower
+		if !n.hasChildren {
+			return addrIterator{}, addrGapIterator{n, i}
+		}
+		n = n.children[i]
+	}
+}
+
+// FindSegment returns the segment whose range contains the given key. If no
+// such segment exists, FindSegment returns a terminal iterator.
+func (s *addrSet) FindSegment(key uintptr) addrIterator {
+	seg, _ := s.Find(key)
+	return seg
+}
+
+// LowerBoundSegment returns the segment with the lowest range that contains a
+// key greater than or equal to min. If no such segment exists,
+// LowerBoundSegment returns a terminal iterator.
+func (s *addrSet) LowerBoundSegment(min uintptr) addrIterator {
+	seg, gap := s.Find(min)
+	if seg.Ok() {
+		return seg
+	}
+	return gap.NextSegment()
+}
+
+// UpperBoundSegment returns the segment with the highest range that contains a
+// key less than or equal to max. If no such segment exists, UpperBoundSegment
+// returns a terminal iterator.
+func (s *addrSet) UpperBoundSegment(max uintptr) addrIterator {
+	seg, gap := s.Find(max)
+	if seg.Ok() {
+		return seg
+	}
+	return gap.PrevSegment()
+}
+
+// FindGap returns the gap containing the given key. If no such gap exists
+// (i.e. the set contains a segment containing that key), FindGap returns a
+// terminal iterator.
+func (s *addrSet) FindGap(key uintptr) addrGapIterator {
+	_, gap := s.Find(key)
+	return gap
+}
+
+// LowerBoundGap returns the gap with the lowest range that is greater than or
+// equal to min.
+func (s *addrSet) LowerBoundGap(min uintptr) addrGapIterator {
+	seg, gap := s.Find(min)
+	if gap.Ok() {
+		return gap
+	}
+	return seg.NextGap()
+}
+
+// UpperBoundGap returns the gap with the highest range that is less than or
+// equal to max.
+func (s *addrSet) UpperBoundGap(max uintptr) addrGapIterator {
+	seg, gap := s.Find(max)
+	if gap.Ok() {
+		return gap
+	}
+	return seg.PrevGap()
+}
+
+// Add inserts the given segment into the set and returns true. If the new
+// segment can be merged with adjacent segments, Add will do so. If the new
+// segment would overlap an existing segment, Add returns false. If Add
+// succeeds, all existing iterators are invalidated.
+func (s *addrSet) Add(r addrRange, val *objectEncodeState) bool {
+	if r.Length() <= 0 {
+		panic(fmt.Sprintf("invalid segment range %v", r))
+	}
+	gap := s.FindGap(r.Start)
+	if !gap.Ok() {
+		return false
+	}
+	if r.End > gap.End() {
+		return false
+	}
+	s.Insert(gap, r, val)
+	return true
+}
+
+// AddWithoutMerging inserts the given segment into the set and returns true.
+// If it would overlap an existing segment, AddWithoutMerging does nothing and
+// returns false. If AddWithoutMerging succeeds, all existing iterators are
+// invalidated.
+func (s *addrSet) AddWithoutMerging(r addrRange, val *objectEncodeState) bool {
+	if r.Length() <= 0 {
+		panic(fmt.Sprintf("invalid segment range %v", r))
+	}
+	gap := s.FindGap(r.Start)
+	if !gap.Ok() {
+		return false
+	}
+	if r.End > gap.End() {
+		return false
+	}
+	s.InsertWithoutMergingUnchecked(gap, r, val)
+	return true
+}
+
+// Insert inserts the given segment into the given gap. If the new segment can
+// be merged with adjacent segments, Insert will do so. Insert returns an
+// iterator to the segment containing the inserted value (which may have been
+// merged with other values). All existing iterators (including gap, but not
+// including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid, Insert panics.
+//
+// Insert is semantically equivalent to a InsertWithoutMerging followed by a
+// Merge, but may be more efficient. Note that there is no unchecked variant of
+// Insert since Insert must retrieve and inspect gap's predecessor and
+// successor segments regardless.
+func (s *addrSet) Insert(gap addrGapIterator, r addrRange, val *objectEncodeState) addrIterator {
+	if r.Length() <= 0 {
+		panic(fmt.Sprintf("invalid segment range %v", r))
+	}
+	prev, next := gap.PrevSegment(), gap.NextSegment()
+	if prev.Ok() && prev.End() > r.Start {
+		panic(fmt.Sprintf("new segment %v overlaps predecessor %v", r, prev.Range()))
+	}
+	if next.Ok() && next.Start() < r.End {
+		panic(fmt.Sprintf("new segment %v overlaps successor %v", r, next.Range()))
+	}
+	if prev.Ok() && prev.End() == r.Start {
+		if mval, ok := (addrSetFunctions{}).Merge(prev.Range(), prev.Value(), r, val); ok {
+			shrinkMaxGap := addrtrackGaps != 0 && gap.Range().Length() == gap.node.maxGap.Get()
+			prev.SetEndUnchecked(r.End)
+			prev.SetValue(mval)
+			if shrinkMaxGap {
+				gap.node.updateMaxGapLeaf()
+			}
+			if next.Ok() && next.Start() == r.End {
+				val = mval
+				if mval, ok := (addrSetFunctions{}).Merge(prev.Range(), val, next.Range(), next.Value()); ok {
+					prev.SetEndUnchecked(next.End())
+					prev.SetValue(mval)
+					return s.Remove(next).PrevSegment()
+				}
+			}
+			return prev
+		}
+	}
+	if next.Ok() && next.Start() == r.End {
+		if mval, ok := (addrSetFunctions{}).Merge(r, val, next.Range(), next.Value()); ok {
+			shrinkMaxGap := addrtrackGaps != 0 && gap.Range().Length() == gap.node.maxGap.Get()
+			next.SetStartUnchecked(r.Start)
+			next.SetValue(mval)
+			if shrinkMaxGap {
+				gap.node.updateMaxGapLeaf()
+			}
+			return next
+		}
+	}
+
+	return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMerging inserts the given segment into the given gap and
+// returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// If the gap cannot accommodate the segment, or if r is invalid,
+// InsertWithoutMerging panics.
+func (s *addrSet) InsertWithoutMerging(gap addrGapIterator, r addrRange, val *objectEncodeState) addrIterator {
+	if r.Length() <= 0 {
+		panic(fmt.Sprintf("invalid segment range %v", r))
+	}
+	if gr := gap.Range(); !gr.IsSupersetOf(r) {
+		panic(fmt.Sprintf("cannot insert segment range %v into gap range %v", r, gr))
+	}
+	return s.InsertWithoutMergingUnchecked(gap, r, val)
+}
+
+// InsertWithoutMergingUnchecked inserts the given segment into the given gap
+// and returns an iterator to the inserted segment. All existing iterators
+// (including gap, but not including the returned iterator) are invalidated.
+//
+// Preconditions:
+// * r.Start >= gap.Start().
+// * r.End <= gap.End().
+func (s *addrSet) InsertWithoutMergingUnchecked(gap addrGapIterator, r addrRange, val *objectEncodeState) addrIterator {
+	gap = gap.node.rebalanceBeforeInsert(gap)
+	splitMaxGap := addrtrackGaps != 0 && (gap.node.nrSegments == 0 || gap.Range().Length() == gap.node.maxGap.Get())
+	copy(gap.node.keys[gap.index+1:], gap.node.keys[gap.index:gap.node.nrSegments])
+	copy(gap.node.values[gap.index+1:], gap.node.values[gap.index:gap.node.nrSegments])
+	gap.node.keys[gap.index] = r
+	gap.node.values[gap.index] = val
+	gap.node.nrSegments++
+	if splitMaxGap {
+		gap.node.updateMaxGapLeaf()
+	}
+	return addrIterator{gap.node, gap.index}
+}
+
+// Remove removes the given segment and returns an iterator to the vacated gap.
+// All existing iterators (including seg, but not including the returned
+// iterator) are invalidated.
+func (s *addrSet) Remove(seg addrIterator) addrGapIterator {
+
+	if seg.node.hasChildren {
+
+		victim := seg.PrevSegment()
+
+		seg.SetRangeUnchecked(victim.Range())
+		seg.SetValue(victim.Value())
+
+		nextAdjacentNode := seg.NextSegment().node
+		if addrtrackGaps != 0 {
+			nextAdjacentNode.updateMaxGapLeaf()
+		}
+		return s.Remove(victim).NextGap()
+	}
+	copy(seg.node.keys[seg.index:], seg.node.keys[seg.index+1:seg.node.nrSegments])
+	copy(seg.node.values[seg.index:], seg.node.values[seg.index+1:seg.node.nrSegments])
+	addrSetFunctions{}.ClearValue(&seg.node.values[seg.node.nrSegments-1])
+	seg.node.nrSegments--
+	if addrtrackGaps != 0 {
+		seg.node.updateMaxGapLeaf()
+	}
+	return seg.node.rebalanceAfterRemove(addrGapIterator{seg.node, seg.index})
+}
+
+// RemoveAll removes all segments from the set. All existing iterators are
+// invalidated.
+func (s *addrSet) RemoveAll() {
+	s.root = addrnode{}
+}
+
+// RemoveRange removes all segments in the given range. An iterator to the
+// newly formed gap is returned, and all existing iterators are invalidated.
+func (s *addrSet) RemoveRange(r addrRange) addrGapIterator {
+	seg, gap := s.Find(r.Start)
+	if seg.Ok() {
+		seg = s.Isolate(seg, r)
+		gap = s.Remove(seg)
+	}
+	for seg = gap.NextSegment(); seg.Ok() && seg.Start() < r.End; seg = gap.NextSegment() {
+		seg = s.Isolate(seg, r)
+		gap = s.Remove(seg)
+	}
+	return gap
+}
+
+// Merge attempts to merge two neighboring segments. If successful, Merge
+// returns an iterator to the merged segment, and all existing iterators are
+// invalidated. Otherwise, Merge returns a terminal iterator.
+//
+// If first is not the predecessor of second, Merge panics.
+func (s *addrSet) Merge(first, second addrIterator) addrIterator {
+	if first.NextSegment() != second {
+		panic(fmt.Sprintf("attempt to merge non-neighboring segments %v, %v", first.Range(), second.Range()))
+	}
+	return s.MergeUnchecked(first, second)
+}
+
+// MergeUnchecked attempts to merge two neighboring segments. If successful,
+// MergeUnchecked returns an iterator to the merged segment, and all existing
+// iterators are invalidated. Otherwise, MergeUnchecked returns a terminal
+// iterator.
+//
+// Precondition: first is the predecessor of second: first.NextSegment() ==
+// second, first == second.PrevSegment().
+func (s *addrSet) MergeUnchecked(first, second addrIterator) addrIterator {
+	if first.End() == second.Start() {
+		if mval, ok := (addrSetFunctions{}).Merge(first.Range(), first.Value(), second.Range(), second.Value()); ok {
+
+			first.SetEndUnchecked(second.End())
+			first.SetValue(mval)
+
+			return s.Remove(second).PrevSegment()
+		}
+	}
+	return addrIterator{}
+}
+
+// MergeAll attempts to merge all adjacent segments in the set. All existing
+// iterators are invalidated.
+func (s *addrSet) MergeAll() {
+	seg := s.FirstSegment()
+	if !seg.Ok() {
+		return
+	}
+	next := seg.NextSegment()
+	for next.Ok() {
+		if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+			seg, next = mseg, mseg.NextSegment()
+		} else {
+			seg, next = next, next.NextSegment()
+		}
+	}
+}
+
+// MergeRange attempts to merge all adjacent segments that contain a key in the
+// specific range. All existing iterators are invalidated.
+func (s *addrSet) MergeRange(r addrRange) {
+	seg := s.LowerBoundSegment(r.Start)
+	if !seg.Ok() {
+		return
+	}
+	next := seg.NextSegment()
+	for next.Ok() && next.Range().Start < r.End {
+		if mseg := s.MergeUnchecked(seg, next); mseg.Ok() {
+			seg, next = mseg, mseg.NextSegment()
+		} else {
+			seg, next = next, next.NextSegment()
+		}
+	}
+}
+
+// MergeAdjacent attempts to merge the segment containing r.Start with its
+// predecessor, and the segment containing r.End-1 with its successor.
+func (s *addrSet) MergeAdjacent(r addrRange) {
+	first := s.FindSegment(r.Start)
+	if first.Ok() {
+		if prev := first.PrevSegment(); prev.Ok() {
+			s.Merge(prev, first)
+		}
+	}
+	last := s.FindSegment(r.End - 1)
+	if last.Ok() {
+		if next := last.NextSegment(); next.Ok() {
+			s.Merge(last, next)
+		}
+	}
+}
+
+// Split splits the given segment at the given key and returns iterators to the
+// two resulting segments. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+//
+// If the segment cannot be split at split (because split is at the start or
+// end of the segment's range, so splitting would produce a segment with zero
+// length, or because split falls outside the segment's range altogether),
+// Split panics.
+func (s *addrSet) Split(seg addrIterator, split uintptr) (addrIterator, addrIterator) {
+	if !seg.Range().CanSplitAt(split) {
+		panic(fmt.Sprintf("can't split %v at %v", seg.Range(), split))
+	}
+	return s.SplitUnchecked(seg, split)
+}
+
+// SplitUnchecked splits the given segment at the given key and returns
+// iterators to the two resulting segments. All existing iterators (including
+// seg, but not including the returned iterators) are invalidated.
+//
+// Preconditions: seg.Start() < key < seg.End().
+func (s *addrSet) SplitUnchecked(seg addrIterator, split uintptr) (addrIterator, addrIterator) {
+	val1, val2 := (addrSetFunctions{}).Split(seg.Range(), seg.Value(), split)
+	end2 := seg.End()
+	seg.SetEndUnchecked(split)
+	seg.SetValue(val1)
+	seg2 := s.InsertWithoutMergingUnchecked(seg.NextGap(), addrRange{split, end2}, val2)
+
+	return seg2.PrevSegment(), seg2
+}
+
+// SplitAt splits the segment straddling split, if one exists. SplitAt returns
+// true if a segment was split and false otherwise. If SplitAt splits a
+// segment, all existing iterators are invalidated.
+func (s *addrSet) SplitAt(split uintptr) bool {
+	if seg := s.FindSegment(split); seg.Ok() && seg.Range().CanSplitAt(split) {
+		s.SplitUnchecked(seg, split)
+		return true
+	}
+	return false
+}
+
+// Isolate ensures that the given segment's range does not escape r by
+// splitting at r.Start and r.End if necessary, and returns an updated iterator
+// to the bounded segment. All existing iterators (including seg, but not
+// including the returned iterators) are invalidated.
+func (s *addrSet) Isolate(seg addrIterator, r addrRange) addrIterator {
+	if seg.Range().CanSplitAt(r.Start) {
+		_, seg = s.SplitUnchecked(seg, r.Start)
+	}
+	if seg.Range().CanSplitAt(r.End) {
+		seg, _ = s.SplitUnchecked(seg, r.End)
+	}
+	return seg
+}
+
+// ApplyContiguous applies a function to a contiguous range of segments,
+// splitting if necessary. The function is applied until the first gap is
+// encountered, at which point the gap is returned. If the function is applied
+// across the entire range, a terminal gap is returned. All existing iterators
+// are invalidated.
+//
+// N.B. The Iterator must not be invalidated by the function.
+func (s *addrSet) ApplyContiguous(r addrRange, fn func(seg addrIterator)) addrGapIterator {
+	seg, gap := s.Find(r.Start)
+	if !seg.Ok() {
+		return gap
+	}
+	for {
+		seg = s.Isolate(seg, r)
+		fn(seg)
+		if seg.End() >= r.End {
+			return addrGapIterator{}
+		}
+		gap = seg.NextGap()
+		if !gap.IsEmpty() {
+			return gap
+		}
+		seg = gap.NextSegment()
+		if !seg.Ok() {
+
+			return addrGapIterator{}
+		}
+	}
+}
+
+// +stateify savable
+type addrnode struct {
+	// An internal binary tree node looks like:
+	//
+	//   K
+	//  / \
+	// Cl Cr
+	//
+	// where all keys in the subtree rooted by Cl (the left subtree) are less
+	// than K (the key of the parent node), and all keys in the subtree rooted
+	// by Cr (the right subtree) are greater than K.
+	//
+	// An internal B-tree node's indexes work out to look like:
+	//
+	//   K0 K1 K2  ...   Kn-1
+	//  / \/ \/ \  ...  /  \
+	// C0 C1 C2 C3 ... Cn-1 Cn
+	//
+	// where n is nrSegments.
+	nrSegments int
+
+	// parent is a pointer to this node's parent. If this node is root, parent
+	// is nil.
+	parent *addrnode
+
+	// parentIndex is the index of this node in parent.children.
+	parentIndex int
+
+	// Flag for internal nodes that is technically redundant with "children[0]
+	// != nil", but is stored in the first cache line. "hasChildren" rather
+	// than "isLeaf" because false must be the correct value for an empty root.
+	hasChildren bool
+
+	// The longest gap within this node. If the node is a leaf, it's simply the
+	// maximum gap among all the (nrSegments+1) gaps formed by its nrSegments keys
+	// including the 0th and nrSegments-th gap possibly shared with its upper-level
+	// nodes; if it's a non-leaf node, it's the max of all children's maxGap.
+	maxGap addrdynamicGap
+
+	// Nodes store keys and values in separate arrays to maximize locality in
+	// the common case (scanning keys for lookup).
+	keys     [addrmaxDegree - 1]addrRange
+	values   [addrmaxDegree - 1]*objectEncodeState
+	children [addrmaxDegree]*addrnode
+}
+
+// firstSegment returns the first segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *addrnode) firstSegment() addrIterator {
+	for n.hasChildren {
+		n = n.children[0]
+	}
+	return addrIterator{n, 0}
+}
+
+// lastSegment returns the last segment in the subtree rooted by n.
+//
+// Preconditions: n.nrSegments != 0.
+func (n *addrnode) lastSegment() addrIterator {
+	for n.hasChildren {
+		n = n.children[n.nrSegments]
+	}
+	return addrIterator{n, n.nrSegments - 1}
+}
+
+func (n *addrnode) prevSibling() *addrnode {
+	if n.parent == nil || n.parentIndex == 0 {
+		return nil
+	}
+	return n.parent.children[n.parentIndex-1]
+}
+
+func (n *addrnode) nextSibling() *addrnode {
+	if n.parent == nil || n.parentIndex == n.parent.nrSegments {
+		return nil
+	}
+	return n.parent.children[n.parentIndex+1]
+}
+
+// rebalanceBeforeInsert splits n and its ancestors if they are full, as
+// required for insertion, and returns an updated iterator to the position
+// represented by gap.
+func (n *addrnode) rebalanceBeforeInsert(gap addrGapIterator) addrGapIterator {
+	if n.nrSegments < addrmaxDegree-1 {
+		return gap
+	}
+	if n.parent != nil {
+		gap = n.parent.rebalanceBeforeInsert(gap)
+	}
+	if n.parent == nil {
+
+		left := &addrnode{
+			nrSegments:  addrminDegree - 1,
+			parent:      n,
+			parentIndex: 0,
+			hasChildren: n.hasChildren,
+		}
+		right := &addrnode{
+			nrSegments:  addrminDegree - 1,
+			parent:      n,
+			parentIndex: 1,
+			hasChildren: n.hasChildren,
+		}
+		copy(left.keys[:addrminDegree-1], n.keys[:addrminDegree-1])
+		copy(left.values[:addrminDegree-1], n.values[:addrminDegree-1])
+		copy(right.keys[:addrminDegree-1], n.keys[addrminDegree:])
+		copy(right.values[:addrminDegree-1], n.values[addrminDegree:])
+		n.keys[0], n.values[0] = n.keys[addrminDegree-1], n.values[addrminDegree-1]
+		addrzeroValueSlice(n.values[1:])
+		if n.hasChildren {
+			copy(left.children[:addrminDegree], n.children[:addrminDegree])
+			copy(right.children[:addrminDegree], n.children[addrminDegree:])
+			addrzeroNodeSlice(n.children[2:])
+			for i := 0; i < addrminDegree; i++ {
+				left.children[i].parent = left
+				left.children[i].parentIndex = i
+				right.children[i].parent = right
+				right.children[i].parentIndex = i
+			}
+		}
+		n.nrSegments = 1
+		n.hasChildren = true
+		n.children[0] = left
+		n.children[1] = right
+
+		if addrtrackGaps != 0 {
+			left.updateMaxGapLocal()
+			right.updateMaxGapLocal()
+		}
+		if gap.node != n {
+			return gap
+		}
+		if gap.index < addrminDegree {
+			return addrGapIterator{left, gap.index}
+		}
+		return addrGapIterator{right, gap.index - addrminDegree}
+	}
+
+	copy(n.parent.keys[n.parentIndex+1:], n.parent.keys[n.parentIndex:n.parent.nrSegments])
+	copy(n.parent.values[n.parentIndex+1:], n.parent.values[n.parentIndex:n.parent.nrSegments])
+	n.parent.keys[n.parentIndex], n.parent.values[n.parentIndex] = n.keys[addrminDegree-1], n.values[addrminDegree-1]
+	copy(n.parent.children[n.parentIndex+2:], n.parent.children[n.parentIndex+1:n.parent.nrSegments+1])
+	for i := n.parentIndex + 2; i < n.parent.nrSegments+2; i++ {
+		n.parent.children[i].parentIndex = i
+	}
+	sibling := &addrnode{
+		nrSegments:  addrminDegree - 1,
+		parent:      n.parent,
+		parentIndex: n.parentIndex + 1,
+		hasChildren: n.hasChildren,
+	}
+	n.parent.children[n.parentIndex+1] = sibling
+	n.parent.nrSegments++
+	copy(sibling.keys[:addrminDegree-1], n.keys[addrminDegree:])
+	copy(sibling.values[:addrminDegree-1], n.values[addrminDegree:])
+	addrzeroValueSlice(n.values[addrminDegree-1:])
+	if n.hasChildren {
+		copy(sibling.children[:addrminDegree], n.children[addrminDegree:])
+		addrzeroNodeSlice(n.children[addrminDegree:])
+		for i := 0; i < addrminDegree; i++ {
+			sibling.children[i].parent = sibling
+			sibling.children[i].parentIndex = i
+		}
+	}
+	n.nrSegments = addrminDegree - 1
+
+	if addrtrackGaps != 0 {
+		n.updateMaxGapLocal()
+		sibling.updateMaxGapLocal()
+	}
+
+	if gap.node != n {
+		return gap
+	}
+	if gap.index < addrminDegree {
+		return gap
+	}
+	return addrGapIterator{sibling, gap.index - addrminDegree}
+}
+
+// rebalanceAfterRemove "unsplits" n and its ancestors if they are deficient
+// (contain fewer segments than required by B-tree invariants), as required for
+// removal, and returns an updated iterator to the position represented by gap.
+//
+// Precondition: n is the only node in the tree that may currently violate a
+// B-tree invariant.
+func (n *addrnode) rebalanceAfterRemove(gap addrGapIterator) addrGapIterator {
+	for {
+		if n.nrSegments >= addrminDegree-1 {
+			return gap
+		}
+		if n.parent == nil {
+
+			return gap
+		}
+
+		if sibling := n.prevSibling(); sibling != nil && sibling.nrSegments >= addrminDegree {
+			copy(n.keys[1:], n.keys[:n.nrSegments])
+			copy(n.values[1:], n.values[:n.nrSegments])
+			n.keys[0] = n.parent.keys[n.parentIndex-1]
+			n.values[0] = n.parent.values[n.parentIndex-1]
+			n.parent.keys[n.parentIndex-1] = sibling.keys[sibling.nrSegments-1]
+			n.parent.values[n.parentIndex-1] = sibling.values[sibling.nrSegments-1]
+			addrSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+			if n.hasChildren {
+				copy(n.children[1:], n.children[:n.nrSegments+1])
+				n.children[0] = sibling.children[sibling.nrSegments]
+				sibling.children[sibling.nrSegments] = nil
+				n.children[0].parent = n
+				n.children[0].parentIndex = 0
+				for i := 1; i < n.nrSegments+2; i++ {
+					n.children[i].parentIndex = i
+				}
+			}
+			n.nrSegments++
+			sibling.nrSegments--
+
+			if addrtrackGaps != 0 {
+				n.updateMaxGapLocal()
+				sibling.updateMaxGapLocal()
+			}
+			if gap.node == sibling && gap.index == sibling.nrSegments {
+				return addrGapIterator{n, 0}
+			}
+			if gap.node == n {
+				return addrGapIterator{n, gap.index + 1}
+			}
+			return gap
+		}
+		if sibling := n.nextSibling(); sibling != nil && sibling.nrSegments >= addrminDegree {
+			n.keys[n.nrSegments] = n.parent.keys[n.parentIndex]
+			n.values[n.nrSegments] = n.parent.values[n.parentIndex]
+			n.parent.keys[n.parentIndex] = sibling.keys[0]
+			n.parent.values[n.parentIndex] = sibling.values[0]
+			copy(sibling.keys[:sibling.nrSegments-1], sibling.keys[1:])
+			copy(sibling.values[:sibling.nrSegments-1], sibling.values[1:])
+			addrSetFunctions{}.ClearValue(&sibling.values[sibling.nrSegments-1])
+			if n.hasChildren {
+				n.children[n.nrSegments+1] = sibling.children[0]
+				copy(sibling.children[:sibling.nrSegments], sibling.children[1:])
+				sibling.children[sibling.nrSegments] = nil
+				n.children[n.nrSegments+1].parent = n
+				n.children[n.nrSegments+1].parentIndex = n.nrSegments + 1
+				for i := 0; i < sibling.nrSegments; i++ {
+					sibling.children[i].parentIndex = i
+				}
+			}
+			n.nrSegments++
+			sibling.nrSegments--
+
+			if addrtrackGaps != 0 {
+				n.updateMaxGapLocal()
+				sibling.updateMaxGapLocal()
+			}
+			if gap.node == sibling {
+				if gap.index == 0 {
+					return addrGapIterator{n, n.nrSegments}
+				}
+				return addrGapIterator{sibling, gap.index - 1}
+			}
+			return gap
+		}
+
+		p := n.parent
+		if p.nrSegments == 1 {
+
+			left, right := p.children[0], p.children[1]
+			p.nrSegments = left.nrSegments + right.nrSegments + 1
+			p.hasChildren = left.hasChildren
+			p.keys[left.nrSegments] = p.keys[0]
+			p.values[left.nrSegments] = p.values[0]
+			copy(p.keys[:left.nrSegments], left.keys[:left.nrSegments])
+			copy(p.values[:left.nrSegments], left.values[:left.nrSegments])
+			copy(p.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+			copy(p.values[left.nrSegments+1:], right.values[:right.nrSegments])
+			if left.hasChildren {
+				copy(p.children[:left.nrSegments+1], left.children[:left.nrSegments+1])
+				copy(p.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+				for i := 0; i < p.nrSegments+1; i++ {
+					p.children[i].parent = p
+					p.children[i].parentIndex = i
+				}
+			} else {
+				p.children[0] = nil
+				p.children[1] = nil
+			}
+
+			if gap.node == left {
+				return addrGapIterator{p, gap.index}
+			}
+			if gap.node == right {
+				return addrGapIterator{p, gap.index + left.nrSegments + 1}
+			}
+			return gap
+		}
+		// Merge n and either sibling, along with the segment separating the
+		// two, into whichever of the two nodes comes first. This is the
+		// reverse of the non-root splitting case in
+		// node.rebalanceBeforeInsert.
+		var left, right *addrnode
+		if n.parentIndex > 0 {
+			left = n.prevSibling()
+			right = n
+		} else {
+			left = n
+			right = n.nextSibling()
+		}
+
+		if gap.node == right {
+			gap = addrGapIterator{left, gap.index + left.nrSegments + 1}
+		}
+		left.keys[left.nrSegments] = p.keys[left.parentIndex]
+		left.values[left.nrSegments] = p.values[left.parentIndex]
+		copy(left.keys[left.nrSegments+1:], right.keys[:right.nrSegments])
+		copy(left.values[left.nrSegments+1:], right.values[:right.nrSegments])
+		if left.hasChildren {
+			copy(left.children[left.nrSegments+1:], right.children[:right.nrSegments+1])
+			for i := left.nrSegments + 1; i < left.nrSegments+right.nrSegments+2; i++ {
+				left.children[i].parent = left
+				left.children[i].parentIndex = i
+			}
+		}
+		left.nrSegments += right.nrSegments + 1
+		copy(p.keys[left.parentIndex:], p.keys[left.parentIndex+1:p.nrSegments])
+		copy(p.values[left.parentIndex:], p.values[left.parentIndex+1:p.nrSegments])
+		addrSetFunctions{}.ClearValue(&p.values[p.nrSegments-1])
+		copy(p.children[left.parentIndex+1:], p.children[left.parentIndex+2:p.nrSegments+1])
+		for i := 0; i < p.nrSegments; i++ {
+			p.children[i].parentIndex = i
+		}
+		p.children[p.nrSegments] = nil
+		p.nrSegments--
+
+		if addrtrackGaps != 0 {
+			left.updateMaxGapLocal()
+		}
+
+		n = p
+	}
+}
+
+// updateMaxGapLeaf updates maxGap bottom-up from the calling leaf until no
+// necessary update.
+//
+// Preconditions: n must be a leaf node, trackGaps must be 1.
+func (n *addrnode) updateMaxGapLeaf() {
+	if n.hasChildren {
+		panic(fmt.Sprintf("updateMaxGapLeaf should always be called on leaf node: %v", n))
+	}
+	max := n.calculateMaxGapLeaf()
+	if max == n.maxGap.Get() {
+
+		return
+	}
+	oldMax := n.maxGap.Get()
+	n.maxGap.Set(max)
+	if max > oldMax {
+
+		for p := n.parent; p != nil; p = p.parent {
+			if p.maxGap.Get() >= max {
+
+				break
+			}
+
+			p.maxGap.Set(max)
+		}
+		return
+	}
+
+	for p := n.parent; p != nil; p = p.parent {
+		if p.maxGap.Get() > oldMax {
+
+			break
+		}
+
+		parentNewMax := p.calculateMaxGapInternal()
+		if p.maxGap.Get() == parentNewMax {
+
+			break
+		}
+
+		p.maxGap.Set(parentNewMax)
+	}
+}
+
+// updateMaxGapLocal updates maxGap of the calling node solely with no
+// propagation to ancestor nodes.
+//
+// Precondition: trackGaps must be 1.
+func (n *addrnode) updateMaxGapLocal() {
+	if !n.hasChildren {
+
+		n.maxGap.Set(n.calculateMaxGapLeaf())
+	} else {
+
+		n.maxGap.Set(n.calculateMaxGapInternal())
+	}
+}
+
+// calculateMaxGapLeaf iterates the gaps within a leaf node and calculate the
+// max.
+//
+// Preconditions: n must be a leaf node.
+func (n *addrnode) calculateMaxGapLeaf() uintptr {
+	max := addrGapIterator{n, 0}.Range().Length()
+	for i := 1; i <= n.nrSegments; i++ {
+		if current := (addrGapIterator{n, i}).Range().Length(); current > max {
+			max = current
+		}
+	}
+	return max
+}
+
+// calculateMaxGapInternal iterates children's maxGap within an internal node n
+// and calculate the max.
+//
+// Preconditions: n must be a non-leaf node.
+func (n *addrnode) calculateMaxGapInternal() uintptr {
+	max := n.children[0].maxGap.Get()
+	for i := 1; i <= n.nrSegments; i++ {
+		if current := n.children[i].maxGap.Get(); current > max {
+			max = current
+		}
+	}
+	return max
+}
+
+// searchFirstLargeEnoughGap returns the first gap having at least minSize length
+// in the subtree rooted by n. If not found, return a terminal gap iterator.
+func (n *addrnode) searchFirstLargeEnoughGap(minSize uintptr) addrGapIterator {
+	if n.maxGap.Get() < minSize {
+		return addrGapIterator{}
+	}
+	if n.hasChildren {
+		for i := 0; i <= n.nrSegments; i++ {
+			if largeEnoughGap := n.children[i].searchFirstLargeEnoughGap(minSize); largeEnoughGap.Ok() {
+				return largeEnoughGap
+			}
+		}
+	} else {
+		for i := 0; i <= n.nrSegments; i++ {
+			currentGap := addrGapIterator{n, i}
+			if currentGap.Range().Length() >= minSize {
+				return currentGap
+			}
+		}
+	}
+	panic(fmt.Sprintf("invalid maxGap in %v", n))
+}
+
+// searchLastLargeEnoughGap returns the last gap having at least minSize length
+// in the subtree rooted by n. If not found, return a terminal gap iterator.
+func (n *addrnode) searchLastLargeEnoughGap(minSize uintptr) addrGapIterator {
+	if n.maxGap.Get() < minSize {
+		return addrGapIterator{}
+	}
+	if n.hasChildren {
+		for i := n.nrSegments; i >= 0; i-- {
+			if largeEnoughGap := n.children[i].searchLastLargeEnoughGap(minSize); largeEnoughGap.Ok() {
+				return largeEnoughGap
+			}
+		}
+	} else {
+		for i := n.nrSegments; i >= 0; i-- {
+			currentGap := addrGapIterator{n, i}
+			if currentGap.Range().Length() >= minSize {
+				return currentGap
+			}
+		}
+	}
+	panic(fmt.Sprintf("invalid maxGap in %v", n))
+}
+
+// A Iterator is conceptually one of:
+//
+// - A pointer to a segment in a set; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Iterators are copyable values and are meaningfully equality-comparable. The
+// zero value of Iterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type addrIterator struct {
+	// node is the node containing the iterated segment. If the iterator is
+	// terminal, node is nil.
+	node *addrnode
+
+	// index is the index of the segment in node.keys/values.
+	index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (seg addrIterator) Ok() bool {
+	return seg.node != nil
+}
+
+// Range returns the iterated segment's range key.
+func (seg addrIterator) Range() addrRange {
+	return seg.node.keys[seg.index]
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (seg addrIterator) Start() uintptr {
+	return seg.node.keys[seg.index].Start
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (seg addrIterator) End() uintptr {
+	return seg.node.keys[seg.index].End
+}
+
+// SetRangeUnchecked mutates the iterated segment's range key. This operation
+// does not invalidate any iterators.
+//
+// Preconditions:
+// * r.Length() > 0.
+// * The new range must not overlap an existing one:
+//   * If seg.NextSegment().Ok(), then r.end <= seg.NextSegment().Start().
+//   * If seg.PrevSegment().Ok(), then r.start >= seg.PrevSegment().End().
+func (seg addrIterator) SetRangeUnchecked(r addrRange) {
+	seg.node.keys[seg.index] = r
+}
+
+// SetRange mutates the iterated segment's range key. If the new range would
+// cause the iterated segment to overlap another segment, or if the new range
+// is invalid, SetRange panics. This operation does not invalidate any
+// iterators.
+func (seg addrIterator) SetRange(r addrRange) {
+	if r.Length() <= 0 {
+		panic(fmt.Sprintf("invalid segment range %v", r))
+	}
+	if prev := seg.PrevSegment(); prev.Ok() && r.Start < prev.End() {
+		panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, prev.Range()))
+	}
+	if next := seg.NextSegment(); next.Ok() && r.End > next.Start() {
+		panic(fmt.Sprintf("new segment range %v overlaps segment range %v", r, next.Range()))
+	}
+	seg.SetRangeUnchecked(r)
+}
+
+// SetStartUnchecked mutates the iterated segment's start. This operation does
+// not invalidate any iterators.
+//
+// Preconditions: The new start must be valid:
+// * start < seg.End()
+// * If seg.PrevSegment().Ok(), then start >= seg.PrevSegment().End().
+func (seg addrIterator) SetStartUnchecked(start uintptr) {
+	seg.node.keys[seg.index].Start = start
+}
+
+// SetStart mutates the iterated segment's start. If the new start value would
+// cause the iterated segment to overlap another segment, or would result in an
+// invalid range, SetStart panics. This operation does not invalidate any
+// iterators.
+func (seg addrIterator) SetStart(start uintptr) {
+	if start >= seg.End() {
+		panic(fmt.Sprintf("new start %v would invalidate segment range %v", start, seg.Range()))
+	}
+	if prev := seg.PrevSegment(); prev.Ok() && start < prev.End() {
+		panic(fmt.Sprintf("new start %v would cause segment range %v to overlap segment range %v", start, seg.Range(), prev.Range()))
+	}
+	seg.SetStartUnchecked(start)
+}
+
+// SetEndUnchecked mutates the iterated segment's end. This operation does not
+// invalidate any iterators.
+//
+// Preconditions: The new end must be valid:
+// * end > seg.Start().
+// * If seg.NextSegment().Ok(), then end <= seg.NextSegment().Start().
+func (seg addrIterator) SetEndUnchecked(end uintptr) {
+	seg.node.keys[seg.index].End = end
+}
+
+// SetEnd mutates the iterated segment's end. If the new end value would cause
+// the iterated segment to overlap another segment, or would result in an
+// invalid range, SetEnd panics. This operation does not invalidate any
+// iterators.
+func (seg addrIterator) SetEnd(end uintptr) {
+	if end <= seg.Start() {
+		panic(fmt.Sprintf("new end %v would invalidate segment range %v", end, seg.Range()))
+	}
+	if next := seg.NextSegment(); next.Ok() && end > next.Start() {
+		panic(fmt.Sprintf("new end %v would cause segment range %v to overlap segment range %v", end, seg.Range(), next.Range()))
+	}
+	seg.SetEndUnchecked(end)
+}
+
+// Value returns a copy of the iterated segment's value.
+func (seg addrIterator) Value() *objectEncodeState {
+	return seg.node.values[seg.index]
+}
+
+// ValuePtr returns a pointer to the iterated segment's value. The pointer is
+// invalidated if the iterator is invalidated. This operation does not
+// invalidate any iterators.
+func (seg addrIterator) ValuePtr() **objectEncodeState {
+	return &seg.node.values[seg.index]
+}
+
+// SetValue mutates the iterated segment's value. This operation does not
+// invalidate any iterators.
+func (seg addrIterator) SetValue(val *objectEncodeState) {
+	seg.node.values[seg.index] = val
+}
+
+// PrevSegment returns the iterated segment's predecessor. If there is no
+// preceding segment, PrevSegment returns a terminal iterator.
+func (seg addrIterator) PrevSegment() addrIterator {
+	if seg.node.hasChildren {
+		return seg.node.children[seg.index].lastSegment()
+	}
+	if seg.index > 0 {
+		return addrIterator{seg.node, seg.index - 1}
+	}
+	if seg.node.parent == nil {
+		return addrIterator{}
+	}
+	return addrsegmentBeforePosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// NextSegment returns the iterated segment's successor. If there is no
+// succeeding segment, NextSegment returns a terminal iterator.
+func (seg addrIterator) NextSegment() addrIterator {
+	if seg.node.hasChildren {
+		return seg.node.children[seg.index+1].firstSegment()
+	}
+	if seg.index < seg.node.nrSegments-1 {
+		return addrIterator{seg.node, seg.index + 1}
+	}
+	if seg.node.parent == nil {
+		return addrIterator{}
+	}
+	return addrsegmentAfterPosition(seg.node.parent, seg.node.parentIndex)
+}
+
+// PrevGap returns the gap immediately before the iterated segment.
+func (seg addrIterator) PrevGap() addrGapIterator {
+	if seg.node.hasChildren {
+
+		return seg.node.children[seg.index].lastSegment().NextGap()
+	}
+	return addrGapIterator{seg.node, seg.index}
+}
+
+// NextGap returns the gap immediately after the iterated segment.
+func (seg addrIterator) NextGap() addrGapIterator {
+	if seg.node.hasChildren {
+		return seg.node.children[seg.index+1].firstSegment().PrevGap()
+	}
+	return addrGapIterator{seg.node, seg.index + 1}
+}
+
+// PrevNonEmpty returns the iterated segment's predecessor if it is adjacent,
+// or the gap before the iterated segment otherwise. If seg.Start() ==
+// Functions.MinKey(), PrevNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by PrevNonEmpty will be
+// non-terminal.
+func (seg addrIterator) PrevNonEmpty() (addrIterator, addrGapIterator) {
+	gap := seg.PrevGap()
+	if gap.Range().Length() != 0 {
+		return addrIterator{}, gap
+	}
+	return gap.PrevSegment(), addrGapIterator{}
+}
+
+// NextNonEmpty returns the iterated segment's successor if it is adjacent, or
+// the gap after the iterated segment otherwise. If seg.End() ==
+// Functions.MaxKey(), NextNonEmpty will return two terminal iterators.
+// Otherwise, exactly one of the iterators returned by NextNonEmpty will be
+// non-terminal.
+func (seg addrIterator) NextNonEmpty() (addrIterator, addrGapIterator) {
+	gap := seg.NextGap()
+	if gap.Range().Length() != 0 {
+		return addrIterator{}, gap
+	}
+	return gap.NextSegment(), addrGapIterator{}
+}
+
+// A GapIterator is conceptually one of:
+//
+// - A pointer to a position between two segments, before the first segment, or
+// after the last segment in a set, called a *gap*; or
+//
+// - A terminal iterator, which is a sentinel indicating that the end of
+// iteration has been reached.
+//
+// Note that the gap between two adjacent segments exists (iterators to it are
+// non-terminal), but has a length of zero. GapIterator.IsEmpty returns true
+// for such gaps. An empty set contains a single gap, spanning the entire range
+// of the set's keys.
+//
+// GapIterators are copyable values and are meaningfully equality-comparable.
+// The zero value of GapIterator is a terminal iterator.
+//
+// Unless otherwise specified, any mutation of a set invalidates all existing
+// iterators into the set.
+type addrGapIterator struct {
+	// The representation of a GapIterator is identical to that of an Iterator,
+	// except that index corresponds to positions between segments in the same
+	// way as for node.children (see comment for node.nrSegments).
+	node  *addrnode
+	index int
+}
+
+// Ok returns true if the iterator is not terminal. All other methods are only
+// valid for non-terminal iterators.
+func (gap addrGapIterator) Ok() bool {
+	return gap.node != nil
+}
+
+// Range returns the range spanned by the iterated gap.
+func (gap addrGapIterator) Range() addrRange {
+	return addrRange{gap.Start(), gap.End()}
+}
+
+// Start is equivalent to Range().Start, but should be preferred if only the
+// start of the range is needed.
+func (gap addrGapIterator) Start() uintptr {
+	if ps := gap.PrevSegment(); ps.Ok() {
+		return ps.End()
+	}
+	return addrSetFunctions{}.MinKey()
+}
+
+// End is equivalent to Range().End, but should be preferred if only the end of
+// the range is needed.
+func (gap addrGapIterator) End() uintptr {
+	if ns := gap.NextSegment(); ns.Ok() {
+		return ns.Start()
+	}
+	return addrSetFunctions{}.MaxKey()
+}
+
+// IsEmpty returns true if the iterated gap is empty (that is, the "gap" is
+// between two adjacent segments.)
+func (gap addrGapIterator) IsEmpty() bool {
+	return gap.Range().Length() == 0
+}
+
+// PrevSegment returns the segment immediately before the iterated gap. If no
+// such segment exists, PrevSegment returns a terminal iterator.
+func (gap addrGapIterator) PrevSegment() addrIterator {
+	return addrsegmentBeforePosition(gap.node, gap.index)
+}
+
+// NextSegment returns the segment immediately after the iterated gap. If no
+// such segment exists, NextSegment returns a terminal iterator.
+func (gap addrGapIterator) NextSegment() addrIterator {
+	return addrsegmentAfterPosition(gap.node, gap.index)
+}
+
+// PrevGap returns the iterated gap's predecessor. If no such gap exists,
+// PrevGap returns a terminal iterator.
+func (gap addrGapIterator) PrevGap() addrGapIterator {
+	seg := gap.PrevSegment()
+	if !seg.Ok() {
+		return addrGapIterator{}
+	}
+	return seg.PrevGap()
+}
+
+// NextGap returns the iterated gap's successor. If no such gap exists, NextGap
+// returns a terminal iterator.
+func (gap addrGapIterator) NextGap() addrGapIterator {
+	seg := gap.NextSegment()
+	if !seg.Ok() {
+		return addrGapIterator{}
+	}
+	return seg.NextGap()
+}
+
+// NextLargeEnoughGap returns the iterated gap's first next gap with larger
+// length than minSize.  If not found, return a terminal gap iterator (does NOT
+// include this gap itself).
+//
+// Precondition: trackGaps must be 1.
+func (gap addrGapIterator) NextLargeEnoughGap(minSize uintptr) addrGapIterator {
+	if addrtrackGaps != 1 {
+		panic("set is not tracking gaps")
+	}
+	if gap.node != nil && gap.node.hasChildren && gap.index == gap.node.nrSegments {
+
+		gap.node = gap.NextSegment().node
+		gap.index = 0
+		return gap.nextLargeEnoughGapHelper(minSize)
+	}
+	return gap.nextLargeEnoughGapHelper(minSize)
+}
+
+// nextLargeEnoughGapHelper is the helper function used by NextLargeEnoughGap
+// to do the real recursions.
+//
+// Preconditions: gap is NOT the trailing gap of a non-leaf node.
+func (gap addrGapIterator) nextLargeEnoughGapHelper(minSize uintptr) addrGapIterator {
+
+	for gap.node != nil &&
+		(gap.node.maxGap.Get() < minSize || (!gap.node.hasChildren && gap.index == gap.node.nrSegments)) {
+		gap.node, gap.index = gap.node.parent, gap.node.parentIndex
+	}
+
+	if gap.node == nil {
+		return addrGapIterator{}
+	}
+
+	gap.index++
+	for gap.index <= gap.node.nrSegments {
+		if gap.node.hasChildren {
+			if largeEnoughGap := gap.node.children[gap.index].searchFirstLargeEnoughGap(minSize); largeEnoughGap.Ok() {
+				return largeEnoughGap
+			}
+		} else {
+			if gap.Range().Length() >= minSize {
+				return gap
+			}
+		}
+		gap.index++
+	}
+	gap.node, gap.index = gap.node.parent, gap.node.parentIndex
+	if gap.node != nil && gap.index == gap.node.nrSegments {
+
+		gap.node, gap.index = gap.node.parent, gap.node.parentIndex
+	}
+	return gap.nextLargeEnoughGapHelper(minSize)
+}
+
+// PrevLargeEnoughGap returns the iterated gap's first prev gap with larger or
+// equal length than minSize.  If not found, return a terminal gap iterator
+// (does NOT include this gap itself).
+//
+// Precondition: trackGaps must be 1.
+func (gap addrGapIterator) PrevLargeEnoughGap(minSize uintptr) addrGapIterator {
+	if addrtrackGaps != 1 {
+		panic("set is not tracking gaps")
+	}
+	if gap.node != nil && gap.node.hasChildren && gap.index == 0 {
+
+		gap.node = gap.PrevSegment().node
+		gap.index = gap.node.nrSegments
+		return gap.prevLargeEnoughGapHelper(minSize)
+	}
+	return gap.prevLargeEnoughGapHelper(minSize)
+}
+
+// prevLargeEnoughGapHelper is the helper function used by PrevLargeEnoughGap
+// to do the real recursions.
+//
+// Preconditions: gap is NOT the first gap of a non-leaf node.
+func (gap addrGapIterator) prevLargeEnoughGapHelper(minSize uintptr) addrGapIterator {
+
+	for gap.node != nil &&
+		(gap.node.maxGap.Get() < minSize || (!gap.node.hasChildren && gap.index == 0)) {
+		gap.node, gap.index = gap.node.parent, gap.node.parentIndex
+	}
+
+	if gap.node == nil {
+		return addrGapIterator{}
+	}
+
+	gap.index--
+	for gap.index >= 0 {
+		if gap.node.hasChildren {
+			if largeEnoughGap := gap.node.children[gap.index].searchLastLargeEnoughGap(minSize); largeEnoughGap.Ok() {
+				return largeEnoughGap
+			}
+		} else {
+			if gap.Range().Length() >= minSize {
+				return gap
+			}
+		}
+		gap.index--
+	}
+	gap.node, gap.index = gap.node.parent, gap.node.parentIndex
+	if gap.node != nil && gap.index == 0 {
+
+		gap.node, gap.index = gap.node.parent, gap.node.parentIndex
+	}
+	return gap.prevLargeEnoughGapHelper(minSize)
+}
+
+// segmentBeforePosition returns the predecessor segment of the position given
+// by n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentBeforePosition returns a terminal iterator.
+func addrsegmentBeforePosition(n *addrnode, i int) addrIterator {
+	for i == 0 {
+		if n.parent == nil {
+			return addrIterator{}
+		}
+		n, i = n.parent, n.parentIndex
+	}
+	return addrIterator{n, i - 1}
+}
+
+// segmentAfterPosition returns the successor segment of the position given by
+// n.children[i], which may or may not contain a child. If no such segment
+// exists, segmentAfterPosition returns a terminal iterator.
+func addrsegmentAfterPosition(n *addrnode, i int) addrIterator {
+	for i == n.nrSegments {
+		if n.parent == nil {
+			return addrIterator{}
+		}
+		n, i = n.parent, n.parentIndex
+	}
+	return addrIterator{n, i}
+}
+
+func addrzeroValueSlice(slice []*objectEncodeState) {
+
+	for i := range slice {
+		addrSetFunctions{}.ClearValue(&slice[i])
+	}
+}
+
+func addrzeroNodeSlice(slice []*addrnode) {
+	for i := range slice {
+		slice[i] = nil
+	}
+}
+
+// String stringifies a Set for debugging.
+func (s *addrSet) String() string {
+	return s.root.String()
+}
+
+// String stringifies a node (and all of its children) for debugging.
+func (n *addrnode) String() string {
+	var buf bytes.Buffer
+	n.writeDebugString(&buf, "")
+	return buf.String()
+}
+
+func (n *addrnode) writeDebugString(buf *bytes.Buffer, prefix string) {
+	if n.hasChildren != (n.nrSegments > 0 && n.children[0] != nil) {
+		buf.WriteString(prefix)
+		buf.WriteString(fmt.Sprintf("WARNING: inconsistent value of hasChildren: got %v, want %v\n", n.hasChildren, !n.hasChildren))
+	}
+	for i := 0; i < n.nrSegments; i++ {
+		if child := n.children[i]; child != nil {
+			cprefix := fmt.Sprintf("%s- % 3d ", prefix, i)
+			if child.parent != n || child.parentIndex != i {
+				buf.WriteString(cprefix)
+				buf.WriteString(fmt.Sprintf("WARNING: inconsistent linkage to parent: got (%p, %d), want (%p, %d)\n", child.parent, child.parentIndex, n, i))
+			}
+			child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, i))
+		}
+		buf.WriteString(prefix)
+		if n.hasChildren {
+			if addrtrackGaps != 0 {
+				buf.WriteString(fmt.Sprintf("- % 3d: %v => %v, maxGap: %d\n", i, n.keys[i], n.values[i], n.maxGap.Get()))
+			} else {
+				buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+			}
+		} else {
+			buf.WriteString(fmt.Sprintf("- % 3d: %v => %v\n", i, n.keys[i], n.values[i]))
+		}
+	}
+	if child := n.children[n.nrSegments]; child != nil {
+		child.writeDebugString(buf, fmt.Sprintf("%s- % 3d ", prefix, n.nrSegments))
+	}
+}
+
+// SegmentDataSlices represents segments from a set as slices of start, end, and
+// values. SegmentDataSlices is primarily used as an intermediate representation
+// for save/restore and the layout here is optimized for that.
+//
+// +stateify savable
+type addrSegmentDataSlices struct {
+	Start  []uintptr
+	End    []uintptr
+	Values []*objectEncodeState
+}
+
+// ExportSortedSlices returns a copy of all segments in the given set, in
+// ascending key order.
+func (s *addrSet) ExportSortedSlices() *addrSegmentDataSlices {
+	var sds addrSegmentDataSlices
+	for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+		sds.Start = append(sds.Start, seg.Start())
+		sds.End = append(sds.End, seg.End())
+		sds.Values = append(sds.Values, seg.Value())
+	}
+	sds.Start = sds.Start[:len(sds.Start):len(sds.Start)]
+	sds.End = sds.End[:len(sds.End):len(sds.End)]
+	sds.Values = sds.Values[:len(sds.Values):len(sds.Values)]
+	return &sds
+}
+
+// ImportSortedSlices initializes the given set from the given slice.
+//
+// Preconditions:
+// * s must be empty.
+// * sds must represent a valid set (the segments in sds must have valid
+//   lengths that do not overlap).
+// * The segments in sds must be sorted in ascending key order.
+func (s *addrSet) ImportSortedSlices(sds *addrSegmentDataSlices) error {
+	if !s.IsEmpty() {
+		return fmt.Errorf("cannot import into non-empty set %v", s)
+	}
+	gap := s.FirstGap()
+	for i := range sds.Start {
+		r := addrRange{sds.Start[i], sds.End[i]}
+		if !gap.Range().IsSupersetOf(r) {
+			return fmt.Errorf("segment overlaps a preceding segment or is incorrectly sorted: [%d, %d) => %v", sds.Start[i], sds.End[i], sds.Values[i])
+		}
+		gap = s.InsertWithoutMerging(gap, r, sds.Values[i]).NextGap()
+	}
+	return nil
+}
+
+// segmentTestCheck returns an error if s is incorrectly sorted, does not
+// contain exactly expectedSegments segments, or contains a segment which
+// fails the passed check.
+//
+// This should be used only for testing, and has been added to this package for
+// templating convenience.
+func (s *addrSet) segmentTestCheck(expectedSegments int, segFunc func(int, addrRange, *objectEncodeState) error) error {
+	havePrev := false
+	prev := uintptr(0)
+	nrSegments := 0
+	for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+		next := seg.Start()
+		if havePrev && prev >= next {
+			return fmt.Errorf("incorrect order: key %d (segment %d) >= key %d (segment %d)", prev, nrSegments-1, next, nrSegments)
+		}
+		if segFunc != nil {
+			if err := segFunc(nrSegments, seg.Range(), seg.Value()); err != nil {
+				return err
+			}
+		}
+		prev = next
+		havePrev = true
+		nrSegments++
+	}
+	if nrSegments != expectedSegments {
+		return fmt.Errorf("incorrect number of segments: got %d, wanted %d", nrSegments, expectedSegments)
+	}
+	return nil
+}
+
+// countSegments counts the number of segments in the set.
+//
+// Similar to Check, this should only be used for testing.
+func (s *addrSet) countSegments() (segments int) {
+	for seg := s.FirstSegment(); seg.Ok(); seg = seg.NextSegment() {
+		segments++
+	}
+	return segments
+}
+func (s *addrSet) saveRoot() *addrSegmentDataSlices {
+	return s.ExportSortedSlices()
+}
+
+func (s *addrSet) loadRoot(sds *addrSegmentDataSlices) {
+	if err := s.ImportSortedSlices(sds); err != nil {
+		panic(err)
+	}
+}
diff --git a/pkg/state/complete_list.go b/pkg/state/complete_list.go
new file mode 100644
index 0000000..4d340a1
--- /dev/null
+++ b/pkg/state/complete_list.go
@@ -0,0 +1,221 @@
+package state
+
+// 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 completeElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (completeElementMapper) linkerFor(elem *objectDecodeState) *objectDecodeState { 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 completeList struct {
+	head *objectDecodeState
+	tail *objectDecodeState
+}
+
+// Reset resets list l to the empty state.
+func (l *completeList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *completeList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *completeList) Front() *objectDecodeState {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *completeList) Back() *objectDecodeState {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *completeList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (completeElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *completeList) PushFront(e *objectDecodeState) {
+	linker := completeElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		completeElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *completeList) PushBack(e *objectDecodeState) {
+	linker := completeElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		completeElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *completeList) PushBackList(m *completeList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		completeElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		completeElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *completeList) InsertAfter(b, e *objectDecodeState) {
+	bLinker := completeElementMapper{}.linkerFor(b)
+	eLinker := completeElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		completeElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *completeList) InsertBefore(a, e *objectDecodeState) {
+	aLinker := completeElementMapper{}.linkerFor(a)
+	eLinker := completeElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		completeElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *completeList) Remove(e *objectDecodeState) {
+	linker := completeElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		completeElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		completeElementMapper{}.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 completeEntry struct {
+	next *objectDecodeState
+	prev *objectDecodeState
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *completeEntry) Next() *objectDecodeState {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *completeEntry) Prev() *objectDecodeState {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *completeEntry) SetNext(elem *objectDecodeState) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *completeEntry) SetPrev(elem *objectDecodeState) {
+	e.prev = elem
+}
diff --git a/pkg/state/deferred_list.go b/pkg/state/deferred_list.go
new file mode 100644
index 0000000..2753ce4
--- /dev/null
+++ b/pkg/state/deferred_list.go
@@ -0,0 +1,206 @@
+package state
+
+// 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 deferredList struct {
+	head *objectEncodeState
+	tail *objectEncodeState
+}
+
+// Reset resets list l to the empty state.
+func (l *deferredList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *deferredList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *deferredList) Front() *objectEncodeState {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *deferredList) Back() *objectEncodeState {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *deferredList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (deferredMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *deferredList) PushFront(e *objectEncodeState) {
+	linker := deferredMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		deferredMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *deferredList) PushBack(e *objectEncodeState) {
+	linker := deferredMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		deferredMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *deferredList) PushBackList(m *deferredList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		deferredMapper{}.linkerFor(l.tail).SetNext(m.head)
+		deferredMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *deferredList) InsertAfter(b, e *objectEncodeState) {
+	bLinker := deferredMapper{}.linkerFor(b)
+	eLinker := deferredMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		deferredMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *deferredList) InsertBefore(a, e *objectEncodeState) {
+	aLinker := deferredMapper{}.linkerFor(a)
+	eLinker := deferredMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		deferredMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *deferredList) Remove(e *objectEncodeState) {
+	linker := deferredMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		deferredMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		deferredMapper{}.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 deferredEntry struct {
+	next *objectEncodeState
+	prev *objectEncodeState
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *deferredEntry) Next() *objectEncodeState {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *deferredEntry) Prev() *objectEncodeState {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *deferredEntry) SetNext(elem *objectEncodeState) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *deferredEntry) SetPrev(elem *objectEncodeState) {
+	e.prev = elem
+}
diff --git a/pkg/sync/gate_test.go b/pkg/sync/gate_test.go
deleted file mode 100644
index 82ce02b..0000000
--- a/pkg/sync/gate_test.go
+++ /dev/null
@@ -1,129 +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 sync
-
-import (
-	"context"
-	"runtime"
-	"sync/atomic"
-	"testing"
-	"time"
-)
-
-func TestGateBasic(t *testing.T) {
-	var g Gate
-
-	if !g.Enter() {
-		t.Fatalf("Enter failed before Close")
-	}
-	g.Leave()
-
-	g.Close()
-	if g.Enter() {
-		t.Fatalf("Enter succeeded after Close")
-	}
-}
-
-func TestGateConcurrent(t *testing.T) {
-	// Each call to testGateConcurrentOnce tests behavior around a single call
-	// to Gate.Close, so run many short tests to increase the probability of
-	// flushing out any issues.
-	totalTime := 5 * time.Second
-	timePerTest := 20 * time.Millisecond
-	numTests := int(totalTime / timePerTest)
-	for i := 0; i < numTests; i++ {
-		testGateConcurrentOnce(t, timePerTest)
-	}
-}
-
-func testGateConcurrentOnce(t *testing.T, d time.Duration) {
-	const numGoroutines = 1000
-
-	ctx, cancel := context.WithCancel(context.Background())
-	var wg WaitGroup
-	defer func() {
-		cancel()
-		wg.Wait()
-	}()
-
-	var g Gate
-	closeState := int32(0) // set to 1 before g.Close() and 2 after it returns
-
-	// Start a large number of goroutines that repeatedly attempt to enter the
-	// gate and get the expected result.
-	for i := 0; i < numGoroutines; i++ {
-		wg.Add(1)
-		go func() {
-			defer wg.Done()
-			for ctx.Err() == nil {
-				closedBeforeEnter := atomic.LoadInt32(&closeState) == 2
-				if g.Enter() {
-					closedBeforeLeave := atomic.LoadInt32(&closeState) == 2
-					g.Leave()
-					if closedBeforeEnter {
-						t.Errorf("Enter succeeded after Close")
-						return
-					}
-					if closedBeforeLeave {
-						t.Errorf("Close returned before Leave")
-						return
-					}
-				} else {
-					if atomic.LoadInt32(&closeState) == 0 {
-						t.Errorf("Enter failed before Close")
-						return
-					}
-				}
-				// Go does not preempt busy loops until Go 1.14.
-				runtime.Gosched()
-			}
-		}()
-	}
-
-	// Allow goroutines to enter the gate successfully for half of the test's
-	// duration, then close the gate and allow goroutines to fail to enter the
-	// gate for the remaining half.
-	time.Sleep(d / 2)
-	atomic.StoreInt32(&closeState, 1)
-	g.Close()
-	atomic.StoreInt32(&closeState, 2)
-	time.Sleep(d / 2)
-}
-
-func BenchmarkGateEnterLeave(b *testing.B) {
-	var g Gate
-	for i := 0; i < b.N; i++ {
-		g.Enter()
-		g.Leave()
-	}
-}
-
-func BenchmarkGateClose(b *testing.B) {
-	for i := 0; i < b.N; i++ {
-		var g Gate
-		g.Close()
-	}
-}
-
-func BenchmarkGateEnterLeaveAsyncClose(b *testing.B) {
-	for i := 0; i < b.N; i++ {
-		var g Gate
-		g.Enter()
-		go func() {
-			g.Leave()
-		}()
-		g.Close()
-	}
-}
diff --git a/pkg/sync/generic_atomicptr_unsafe.go b/pkg/sync/generic_atomicptr_unsafe.go
deleted file mode 100644
index 82b6df1..0000000
--- a/pkg/sync/generic_atomicptr_unsafe.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package seqatomic doesn't exist. This file must be instantiated using the
-// go_template_instance rule in tools/go_generics/defs.bzl.
-package seqatomic
-
-import (
-	"sync/atomic"
-	"unsafe"
-)
-
-// Value is a required type parameter.
-type Value struct{}
-
-// An AtomicPtr is a pointer to a value of type Value that can be atomically
-// loaded and stored. The zero value of an AtomicPtr represents nil.
-//
-// Note that copying AtomicPtr by value performs a non-atomic read of the
-// stored pointer, which is unsafe if Store() can be called concurrently; in
-// this case, do `dst.Store(src.Load())` instead.
-//
-// +stateify savable
-type AtomicPtr struct {
-	ptr unsafe.Pointer `state:".(*Value)"`
-}
-
-func (p *AtomicPtr) savePtr() *Value {
-	return p.Load()
-}
-
-func (p *AtomicPtr) loadPtr(v *Value) {
-	p.Store(v)
-}
-
-// Load returns the value set by the most recent Store. It returns nil if there
-// has been no previous call to Store.
-func (p *AtomicPtr) Load() *Value {
-	return (*Value)(atomic.LoadPointer(&p.ptr))
-}
-
-// Store sets the value returned by Load to x.
-func (p *AtomicPtr) Store(x *Value) {
-	atomic.StorePointer(&p.ptr, (unsafe.Pointer)(x))
-}
diff --git a/pkg/sync/generic_atomicptrmap_unsafe.go b/pkg/sync/generic_atomicptrmap_unsafe.go
deleted file mode 100644
index 3e98cb3..0000000
--- a/pkg/sync/generic_atomicptrmap_unsafe.go
+++ /dev/null
@@ -1,500 +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 atomicptrmap doesn't exist. This file must be instantiated using the
-// go_template_instance rule in tools/go_generics/defs.bzl.
-package atomicptrmap
-
-import (
-	"sync/atomic"
-	"unsafe"
-
-	"gvisor.dev/gvisor/pkg/gohacks"
-	"gvisor.dev/gvisor/pkg/sync"
-)
-
-// Key is a required type parameter.
-type Key struct{}
-
-// Value is a required type parameter.
-type Value struct{}
-
-const (
-	// ShardOrder is an optional parameter specifying the base-2 log of the
-	// number of shards per AtomicPtrMap. Higher values of ShardOrder reduce
-	// unnecessary synchronization between unrelated concurrent operations,
-	// improving performance for write-heavy workloads, but increase memory
-	// usage for small maps.
-	ShardOrder = 0
-)
-
-// Hasher is an optional type parameter. If Hasher is provided, it must define
-// the Init and Hash methods. One Hasher will be shared by all AtomicPtrMaps.
-type Hasher struct {
-	defaultHasher
-}
-
-// defaultHasher is the default Hasher. This indirection exists because
-// defaultHasher must exist even if a custom Hasher is provided, to prevent the
-// Go compiler from complaining about defaultHasher's unused imports.
-type defaultHasher struct {
-	fn   func(unsafe.Pointer, uintptr) uintptr
-	seed uintptr
-}
-
-// Init initializes the Hasher.
-func (h *defaultHasher) Init() {
-	h.fn = sync.MapKeyHasher(map[Key]*Value(nil))
-	h.seed = sync.RandUintptr()
-}
-
-// Hash returns the hash value for the given Key.
-func (h *defaultHasher) Hash(key Key) uintptr {
-	return h.fn(gohacks.Noescape(unsafe.Pointer(&key)), h.seed)
-}
-
-var hasher Hasher
-
-func init() {
-	hasher.Init()
-}
-
-// An AtomicPtrMap maps Keys to non-nil pointers to Values. AtomicPtrMap are
-// safe for concurrent use from multiple goroutines without additional
-// synchronization.
-//
-// The zero value of AtomicPtrMap is empty (maps all Keys to nil) and ready for
-// use. AtomicPtrMaps must not be copied after first use.
-//
-// sync.Map may be faster than AtomicPtrMap if most operations on the map are
-// concurrent writes to a fixed set of keys. AtomicPtrMap is usually faster in
-// other circumstances.
-type AtomicPtrMap struct {
-	// AtomicPtrMap is implemented as a hash table with the following
-	// properties:
-	//
-	// * Collisions are resolved with quadratic probing. Of the two major
-	// alternatives, Robin Hood linear probing makes it difficult for writers
-	// to execute in parallel, and bucketing is less effective in Go due to
-	// lack of SIMD.
-	//
-	// * The table is optionally divided into shards indexed by hash to further
-	// reduce unnecessary synchronization.
-
-	shards [1 << ShardOrder]apmShard
-}
-
-func (m *AtomicPtrMap) shard(hash uintptr) *apmShard {
-	// Go defines right shifts >= width of shifted unsigned operand as 0, so
-	// this is correct even if ShardOrder is 0 (although nogo complains because
-	// nogo is dumb).
-	const indexLSB = unsafe.Sizeof(uintptr(0))*8 - ShardOrder
-	index := hash >> indexLSB
-	return (*apmShard)(unsafe.Pointer(uintptr(unsafe.Pointer(&m.shards)) + (index * unsafe.Sizeof(apmShard{}))))
-}
-
-type apmShard struct {
-	apmShardMutationData
-	_ [apmShardMutationDataPadding]byte
-	apmShardLookupData
-	_ [apmShardLookupDataPadding]byte
-}
-
-type apmShardMutationData struct {
-	dirtyMu  sync.Mutex // serializes slot transitions out of empty
-	dirty    uintptr    // # slots with val != nil
-	count    uintptr    // # slots with val != nil and val != tombstone()
-	rehashMu sync.Mutex // serializes rehashing
-}
-
-type apmShardLookupData struct {
-	seq   sync.SeqCount  // allows atomic reads of slots+mask
-	slots unsafe.Pointer // [mask+1]slot or nil; protected by rehashMu/seq
-	mask  uintptr        // always (a power of 2) - 1; protected by rehashMu/seq
-}
-
-const (
-	cacheLineBytes = 64
-	// Cache line padding is enabled if sharding is.
-	apmEnablePadding = (ShardOrder + 63) >> 6 // 0 if ShardOrder == 0, 1 otherwise
-	// The -1 and +1 below are required to ensure that if unsafe.Sizeof(T) %
-	// cacheLineBytes == 0, then padding is 0 (rather than cacheLineBytes).
-	apmShardMutationDataRequiredPadding = cacheLineBytes - (((unsafe.Sizeof(apmShardMutationData{}) - 1) % cacheLineBytes) + 1)
-	apmShardMutationDataPadding         = apmEnablePadding * apmShardMutationDataRequiredPadding
-	apmShardLookupDataRequiredPadding   = cacheLineBytes - (((unsafe.Sizeof(apmShardLookupData{}) - 1) % cacheLineBytes) + 1)
-	apmShardLookupDataPadding           = apmEnablePadding * apmShardLookupDataRequiredPadding
-
-	// These define fractional thresholds for when apmShard.rehash() is called
-	// (i.e. the load factor) and when it rehases to a larger table
-	// respectively. They are chosen such that the rehash threshold = the
-	// expansion threshold + 1/2, so that when reuse of deleted slots is rare
-	// or non-existent, rehashing occurs after the insertion of at least 1/2
-	// the table's size in new entries, which is acceptably infrequent.
-	apmRehashThresholdNum    = 2
-	apmRehashThresholdDen    = 3
-	apmExpansionThresholdNum = 1
-	apmExpansionThresholdDen = 6
-)
-
-type apmSlot struct {
-	// slot states are indicated by val:
-	//
-	// * Empty: val == nil; key is meaningless. May transition to full or
-	// evacuated with dirtyMu locked.
-	//
-	// * Full: val != nil, tombstone(), or evacuated(); key is immutable. val
-	// is the Value mapped to key. May transition to deleted or evacuated.
-	//
-	// * Deleted: val == tombstone(); key is still immutable. key is mapped to
-	// no Value. May transition to full or evacuated.
-	//
-	// * Evacuated: val == evacuated(); key is immutable. Set by rehashing on
-	// slots that have already been moved, requiring readers to wait for
-	// rehashing to complete and use the new table. Terminal state.
-	//
-	// Note that once val is non-nil, it cannot become nil again. That is, the
-	// transition from empty to non-empty is irreversible for a given slot;
-	// the only way to create more empty slots is by rehashing.
-	val unsafe.Pointer
-	key Key
-}
-
-func apmSlotAt(slots unsafe.Pointer, pos uintptr) *apmSlot {
-	return (*apmSlot)(unsafe.Pointer(uintptr(slots) + pos*unsafe.Sizeof(apmSlot{})))
-}
-
-var tombstoneObj byte
-
-func tombstone() unsafe.Pointer {
-	return unsafe.Pointer(&tombstoneObj)
-}
-
-var evacuatedObj byte
-
-func evacuated() unsafe.Pointer {
-	return unsafe.Pointer(&evacuatedObj)
-}
-
-// Load returns the Value stored in m for key.
-func (m *AtomicPtrMap) Load(key Key) *Value {
-	hash := hasher.Hash(key)
-	shard := m.shard(hash)
-
-retry:
-	epoch := shard.seq.BeginRead()
-	slots := atomic.LoadPointer(&shard.slots)
-	mask := atomic.LoadUintptr(&shard.mask)
-	if !shard.seq.ReadOk(epoch) {
-		goto retry
-	}
-	if slots == nil {
-		return nil
-	}
-
-	i := hash & mask
-	inc := uintptr(1)
-	for {
-		slot := apmSlotAt(slots, i)
-		slotVal := atomic.LoadPointer(&slot.val)
-		if slotVal == nil {
-			// Empty slot; end of probe sequence.
-			return nil
-		}
-		if slotVal == evacuated() {
-			// Racing with rehashing.
-			goto retry
-		}
-		if slot.key == key {
-			if slotVal == tombstone() {
-				return nil
-			}
-			return (*Value)(slotVal)
-		}
-		i = (i + inc) & mask
-		inc++
-	}
-}
-
-// Store stores the Value val for key.
-func (m *AtomicPtrMap) Store(key Key, val *Value) {
-	m.maybeCompareAndSwap(key, false, nil, val)
-}
-
-// Swap stores the Value val for key and returns the previously-mapped Value.
-func (m *AtomicPtrMap) Swap(key Key, val *Value) *Value {
-	return m.maybeCompareAndSwap(key, false, nil, val)
-}
-
-// CompareAndSwap checks that the Value stored for key is oldVal; if it is, it
-// stores the Value newVal for key. CompareAndSwap returns the previous Value
-// stored for key, whether or not it stores newVal.
-func (m *AtomicPtrMap) CompareAndSwap(key Key, oldVal, newVal *Value) *Value {
-	return m.maybeCompareAndSwap(key, true, oldVal, newVal)
-}
-
-func (m *AtomicPtrMap) maybeCompareAndSwap(key Key, compare bool, typedOldVal, typedNewVal *Value) *Value {
-	hash := hasher.Hash(key)
-	shard := m.shard(hash)
-	oldVal := tombstone()
-	if typedOldVal != nil {
-		oldVal = unsafe.Pointer(typedOldVal)
-	}
-	newVal := tombstone()
-	if typedNewVal != nil {
-		newVal = unsafe.Pointer(typedNewVal)
-	}
-
-retry:
-	epoch := shard.seq.BeginRead()
-	slots := atomic.LoadPointer(&shard.slots)
-	mask := atomic.LoadUintptr(&shard.mask)
-	if !shard.seq.ReadOk(epoch) {
-		goto retry
-	}
-	if slots == nil {
-		if (compare && oldVal != tombstone()) || newVal == tombstone() {
-			return nil
-		}
-		// Need to allocate a table before insertion.
-		shard.rehash(nil)
-		goto retry
-	}
-
-	i := hash & mask
-	inc := uintptr(1)
-	for {
-		slot := apmSlotAt(slots, i)
-		slotVal := atomic.LoadPointer(&slot.val)
-		if slotVal == nil {
-			if (compare && oldVal != tombstone()) || newVal == tombstone() {
-				return nil
-			}
-			// Try to grab this slot for ourselves.
-			shard.dirtyMu.Lock()
-			slotVal = atomic.LoadPointer(&slot.val)
-			if slotVal == nil {
-				// Check if we need to rehash before dirtying a slot.
-				if dirty, capacity := shard.dirty+1, mask+1; dirty*apmRehashThresholdDen >= capacity*apmRehashThresholdNum {
-					shard.dirtyMu.Unlock()
-					shard.rehash(slots)
-					goto retry
-				}
-				slot.key = key
-				atomic.StorePointer(&slot.val, newVal) // transitions slot to full
-				shard.dirty++
-				atomic.AddUintptr(&shard.count, 1)
-				shard.dirtyMu.Unlock()
-				return nil
-			}
-			// Raced with another store; the slot is no longer empty. Continue
-			// with the new value of slotVal since we may have raced with
-			// another store of key.
-			shard.dirtyMu.Unlock()
-		}
-		if slotVal == evacuated() {
-			// Racing with rehashing.
-			goto retry
-		}
-		if slot.key == key {
-			// We're reusing an existing slot, so rehashing isn't necessary.
-			for {
-				if (compare && oldVal != slotVal) || newVal == slotVal {
-					if slotVal == tombstone() {
-						return nil
-					}
-					return (*Value)(slotVal)
-				}
-				if atomic.CompareAndSwapPointer(&slot.val, slotVal, newVal) {
-					if slotVal == tombstone() {
-						atomic.AddUintptr(&shard.count, 1)
-						return nil
-					}
-					if newVal == tombstone() {
-						atomic.AddUintptr(&shard.count, ^uintptr(0) /* -1 */)
-					}
-					return (*Value)(slotVal)
-				}
-				slotVal = atomic.LoadPointer(&slot.val)
-				if slotVal == evacuated() {
-					goto retry
-				}
-			}
-		}
-		// This produces a triangular number sequence of offsets from the
-		// initially-probed position.
-		i = (i + inc) & mask
-		inc++
-	}
-}
-
-// rehash is marked nosplit to avoid preemption during table copying.
-//go:nosplit
-func (shard *apmShard) rehash(oldSlots unsafe.Pointer) {
-	shard.rehashMu.Lock()
-	defer shard.rehashMu.Unlock()
-
-	if shard.slots != oldSlots {
-		// Raced with another call to rehash().
-		return
-	}
-
-	// Determine the size of the new table. Constraints:
-	//
-	// * The size of the table must be a power of two to ensure that every slot
-	// is visitable by every probe sequence under quadratic probing with
-	// triangular numbers.
-	//
-	// * The size of the table cannot decrease because even if shard.count is
-	// currently smaller than shard.dirty, concurrent stores that reuse
-	// existing slots can drive shard.count back up to a maximum of
-	// shard.dirty.
-	newSize := uintptr(8) // arbitrary initial size
-	if oldSlots != nil {
-		oldSize := shard.mask + 1
-		newSize = oldSize
-		if count := atomic.LoadUintptr(&shard.count) + 1; count*apmExpansionThresholdDen > oldSize*apmExpansionThresholdNum {
-			newSize *= 2
-		}
-	}
-
-	// Allocate the new table.
-	newSlotsSlice := make([]apmSlot, newSize)
-	newSlotsHeader := (*gohacks.SliceHeader)(unsafe.Pointer(&newSlotsSlice))
-	newSlots := newSlotsHeader.Data
-	newMask := newSize - 1
-
-	// Start a writer critical section now so that racing users of the old
-	// table that observe evacuated() wait for the new table. (But lock dirtyMu
-	// first since doing so may block, which we don't want to do during the
-	// writer critical section.)
-	shard.dirtyMu.Lock()
-	shard.seq.BeginWrite()
-
-	if oldSlots != nil {
-		realCount := uintptr(0)
-		// Copy old entries to the new table.
-		oldMask := shard.mask
-		for i := uintptr(0); i <= oldMask; i++ {
-			oldSlot := apmSlotAt(oldSlots, i)
-			val := atomic.SwapPointer(&oldSlot.val, evacuated())
-			if val == nil || val == tombstone() {
-				continue
-			}
-			hash := hasher.Hash(oldSlot.key)
-			j := hash & newMask
-			inc := uintptr(1)
-			for {
-				newSlot := apmSlotAt(newSlots, j)
-				if newSlot.val == nil {
-					newSlot.val = val
-					newSlot.key = oldSlot.key
-					break
-				}
-				j = (j + inc) & newMask
-				inc++
-			}
-			realCount++
-		}
-		// Update dirty to reflect that tombstones were not copied to the new
-		// table. Use realCount since a concurrent mutator may not have updated
-		// shard.count yet.
-		shard.dirty = realCount
-	}
-
-	// Switch to the new table.
-	atomic.StorePointer(&shard.slots, newSlots)
-	atomic.StoreUintptr(&shard.mask, newMask)
-
-	shard.seq.EndWrite()
-	shard.dirtyMu.Unlock()
-}
-
-// Range invokes f on each Key-Value pair stored in m. If any call to f returns
-// false, Range stops iteration and returns.
-//
-// Range does not necessarily correspond to any consistent snapshot of the
-// Map's contents: no Key will be visited more than once, but if the Value for
-// any Key is stored or deleted concurrently, Range may reflect any mapping for
-// that Key from any point during the Range call.
-//
-// f must not call other methods on m.
-func (m *AtomicPtrMap) Range(f func(key Key, val *Value) bool) {
-	for si := 0; si < len(m.shards); si++ {
-		shard := &m.shards[si]
-		if !shard.doRange(f) {
-			return
-		}
-	}
-}
-
-func (shard *apmShard) doRange(f func(key Key, val *Value) bool) bool {
-	// We have to lock rehashMu because if we handled races with rehashing by
-	// retrying, f could see the same key twice.
-	shard.rehashMu.Lock()
-	defer shard.rehashMu.Unlock()
-	slots := shard.slots
-	if slots == nil {
-		return true
-	}
-	mask := shard.mask
-	for i := uintptr(0); i <= mask; i++ {
-		slot := apmSlotAt(slots, i)
-		slotVal := atomic.LoadPointer(&slot.val)
-		if slotVal == nil || slotVal == tombstone() {
-			continue
-		}
-		if !f(slot.key, (*Value)(slotVal)) {
-			return false
-		}
-	}
-	return true
-}
-
-// RangeRepeatable is like Range, but:
-//
-// * RangeRepeatable may visit the same Key multiple times in the presence of
-// concurrent mutators, possibly passing different Values to f in different
-// calls.
-//
-// * It is safe for f to call other methods on m.
-func (m *AtomicPtrMap) RangeRepeatable(f func(key Key, val *Value) bool) {
-	for si := 0; si < len(m.shards); si++ {
-		shard := &m.shards[si]
-
-	retry:
-		epoch := shard.seq.BeginRead()
-		slots := atomic.LoadPointer(&shard.slots)
-		mask := atomic.LoadUintptr(&shard.mask)
-		if !shard.seq.ReadOk(epoch) {
-			goto retry
-		}
-		if slots == nil {
-			continue
-		}
-
-		for i := uintptr(0); i <= mask; i++ {
-			slot := apmSlotAt(slots, i)
-			slotVal := atomic.LoadPointer(&slot.val)
-			if slotVal == evacuated() {
-				goto retry
-			}
-			if slotVal == nil || slotVal == tombstone() {
-				continue
-			}
-			if !f(slot.key, (*Value)(slotVal)) {
-				return
-			}
-		}
-	}
-}
diff --git a/pkg/sync/generic_seqatomic_unsafe.go b/pkg/sync/generic_seqatomic_unsafe.go
deleted file mode 100644
index 82b676a..0000000
--- a/pkg/sync/generic_seqatomic_unsafe.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package seqatomic doesn't exist. This file must be instantiated using the
-// go_template_instance rule in tools/go_generics/defs.bzl.
-package seqatomic
-
-import (
-	"unsafe"
-
-	"gvisor.dev/gvisor/pkg/sync"
-)
-
-// Value is a required type parameter.
-type Value struct{}
-
-// SeqAtomicLoad returns a copy of *ptr, ensuring that the read does not race
-// with any writer critical sections in seq.
-//
-//go:nosplit
-func SeqAtomicLoad(seq *sync.SeqCount, ptr *Value) Value {
-	for {
-		if val, ok := SeqAtomicTryLoad(seq, seq.BeginRead(), ptr); ok {
-			return val
-		}
-	}
-}
-
-// SeqAtomicTryLoad returns a copy of *ptr while in a reader critical section
-// in seq initiated by a call to seq.BeginRead() that returned epoch. If the
-// read would race with a writer critical section, SeqAtomicTryLoad returns
-// (unspecified, false).
-//
-//go:nosplit
-func SeqAtomicTryLoad(seq *sync.SeqCount, epoch sync.SeqCountEpoch, ptr *Value) (val Value, ok bool) {
-	if sync.RaceEnabled {
-		// runtime.RaceDisable() doesn't actually stop the race detector, so it
-		// can't help us here. Instead, call runtime.memmove directly, which is
-		// not instrumented by the race detector.
-		sync.Memmove(unsafe.Pointer(&val), unsafe.Pointer(ptr), unsafe.Sizeof(val))
-	} else {
-		// This is ~40% faster for short reads than going through memmove.
-		val = *ptr
-	}
-	ok = seq.ReadOk(epoch)
-	return
-}
diff --git a/pkg/sync/mutex_test.go b/pkg/sync/mutex_test.go
deleted file mode 100644
index 4fb51a8..0000000
--- a/pkg/sync/mutex_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package sync
-
-import (
-	"sync"
-	"testing"
-	"unsafe"
-)
-
-// TestStructSize verifies that syncMutex's size hasn't drifted from the
-// standard library's version.
-//
-// The correctness of this package relies on these remaining in sync.
-func TestStructSize(t *testing.T) {
-	const (
-		got  = unsafe.Sizeof(syncMutex{})
-		want = unsafe.Sizeof(sync.Mutex{})
-	)
-	if got != want {
-		t.Errorf("got sizeof(syncMutex) = %d, want = sizeof(sync.Mutex) = %d", got, want)
-	}
-}
-
-// TestFieldValues verifies that the semantics of syncMutex.state from the
-// standard library's implementation.
-//
-// The correctness of this package relies on these remaining in sync.
-func TestFieldValues(t *testing.T) {
-	var m Mutex
-	m.Lock()
-	if got := *m.m.state(); got != mutexLocked {
-		t.Errorf("got locked sync.Mutex.state = %d, want = %d", got, mutexLocked)
-	}
-	m.Unlock()
-	if got := *m.m.state(); got != mutexUnlocked {
-		t.Errorf("got unlocked sync.Mutex.state = %d, want = %d", got, mutexUnlocked)
-	}
-}
-
-func TestDoubleTryLock(t *testing.T) {
-	var m Mutex
-	if !m.TryLock() {
-		t.Fatal("failed to aquire lock")
-	}
-	if m.TryLock() {
-		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
-	}
-}
-
-func TestTryLockAfterLock(t *testing.T) {
-	var m Mutex
-	m.Lock()
-	if m.TryLock() {
-		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
-	}
-}
-
-func TestTryLockUnlock(t *testing.T) {
-	var m Mutex
-	if !m.TryLock() {
-		t.Fatal("failed to aquire lock")
-	}
-	m.Unlock()
-	if !m.TryLock() {
-		t.Fatal("failed to aquire lock after unlock")
-	}
-}
diff --git a/pkg/sync/rwmutex_test.go b/pkg/sync/rwmutex_test.go
deleted file mode 100644
index 5ca96d1..0000000
--- a/pkg/sync/rwmutex_test.go
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Copyright 2019 The gVisor Authors.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// GOMAXPROCS=10 go test
-
-// Copy/pasted from the standard library's sync/rwmutex_test.go, except for the
-// addition of downgradingWriter and the renaming of num_iterations to
-// numIterations to shut up Golint.
-
-package sync
-
-import (
-	"fmt"
-	"runtime"
-	"sync/atomic"
-	"testing"
-)
-
-func parallelReader(m *RWMutex, clocked, cunlock, cdone chan bool) {
-	m.RLock()
-	clocked <- true
-	<-cunlock
-	m.RUnlock()
-	cdone <- true
-}
-
-func doTestParallelReaders(numReaders, gomaxprocs int) {
-	runtime.GOMAXPROCS(gomaxprocs)
-	var m RWMutex
-	clocked := make(chan bool)
-	cunlock := make(chan bool)
-	cdone := make(chan bool)
-	for i := 0; i < numReaders; i++ {
-		go parallelReader(&m, clocked, cunlock, cdone)
-	}
-	// Wait for all parallel RLock()s to succeed.
-	for i := 0; i < numReaders; i++ {
-		<-clocked
-	}
-	for i := 0; i < numReaders; i++ {
-		cunlock <- true
-	}
-	// Wait for the goroutines to finish.
-	for i := 0; i < numReaders; i++ {
-		<-cdone
-	}
-}
-
-func TestParallelReaders(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1))
-	doTestParallelReaders(1, 4)
-	doTestParallelReaders(3, 4)
-	doTestParallelReaders(4, 2)
-}
-
-func reader(rwm *RWMutex, numIterations int, activity *int32, cdone chan bool) {
-	for i := 0; i < numIterations; i++ {
-		rwm.RLock()
-		n := atomic.AddInt32(activity, 1)
-		if n < 1 || n >= 10000 {
-			panic(fmt.Sprintf("wlock(%d)\n", n))
-		}
-		for i := 0; i < 100; i++ {
-		}
-		atomic.AddInt32(activity, -1)
-		rwm.RUnlock()
-	}
-	cdone <- true
-}
-
-func writer(rwm *RWMutex, numIterations int, activity *int32, cdone chan bool) {
-	for i := 0; i < numIterations; i++ {
-		rwm.Lock()
-		n := atomic.AddInt32(activity, 10000)
-		if n != 10000 {
-			panic(fmt.Sprintf("wlock(%d)\n", n))
-		}
-		for i := 0; i < 100; i++ {
-		}
-		atomic.AddInt32(activity, -10000)
-		rwm.Unlock()
-	}
-	cdone <- true
-}
-
-func downgradingWriter(rwm *RWMutex, numIterations int, activity *int32, cdone chan bool) {
-	for i := 0; i < numIterations; i++ {
-		rwm.Lock()
-		n := atomic.AddInt32(activity, 10000)
-		if n != 10000 {
-			panic(fmt.Sprintf("wlock(%d)\n", n))
-		}
-		for i := 0; i < 100; i++ {
-		}
-		atomic.AddInt32(activity, -10000)
-		rwm.DowngradeLock()
-		n = atomic.AddInt32(activity, 1)
-		if n < 1 || n >= 10000 {
-			panic(fmt.Sprintf("wlock(%d)\n", n))
-		}
-		for i := 0; i < 100; i++ {
-		}
-		atomic.AddInt32(activity, -1)
-		rwm.RUnlock()
-	}
-	cdone <- true
-}
-
-func HammerDowngradableRWMutex(gomaxprocs, numReaders, numIterations int) {
-	runtime.GOMAXPROCS(gomaxprocs)
-	// Number of active readers + 10000 * number of active writers.
-	var activity int32
-	var rwm RWMutex
-	cdone := make(chan bool)
-	go writer(&rwm, numIterations, &activity, cdone)
-	go downgradingWriter(&rwm, numIterations, &activity, cdone)
-	var i int
-	for i = 0; i < numReaders/2; i++ {
-		go reader(&rwm, numIterations, &activity, cdone)
-	}
-	go writer(&rwm, numIterations, &activity, cdone)
-	go downgradingWriter(&rwm, numIterations, &activity, cdone)
-	for ; i < numReaders; i++ {
-		go reader(&rwm, numIterations, &activity, cdone)
-	}
-	// Wait for the 4 writers and all readers to finish.
-	for i := 0; i < 4+numReaders; i++ {
-		<-cdone
-	}
-}
-
-func TestDowngradableRWMutex(t *testing.T) {
-	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1))
-	n := 1000
-	if testing.Short() {
-		n = 5
-	}
-	HammerDowngradableRWMutex(1, 1, n)
-	HammerDowngradableRWMutex(1, 3, n)
-	HammerDowngradableRWMutex(1, 10, n)
-	HammerDowngradableRWMutex(4, 1, n)
-	HammerDowngradableRWMutex(4, 3, n)
-	HammerDowngradableRWMutex(4, 10, n)
-	HammerDowngradableRWMutex(10, 1, n)
-	HammerDowngradableRWMutex(10, 3, n)
-	HammerDowngradableRWMutex(10, 10, n)
-	HammerDowngradableRWMutex(10, 5, n)
-}
-
-func TestRWDoubleTryLock(t *testing.T) {
-	var rwm RWMutex
-	if !rwm.TryLock() {
-		t.Fatal("failed to aquire lock")
-	}
-	if rwm.TryLock() {
-		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
-	}
-}
-
-func TestRWTryLockAfterLock(t *testing.T) {
-	var rwm RWMutex
-	rwm.Lock()
-	if rwm.TryLock() {
-		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
-	}
-}
-
-func TestRWTryLockUnlock(t *testing.T) {
-	var rwm RWMutex
-	if !rwm.TryLock() {
-		t.Fatal("failed to aquire lock")
-	}
-	rwm.Unlock()
-	if !rwm.TryLock() {
-		t.Fatal("failed to aquire lock after unlock")
-	}
-}
-
-func TestTryRLockAfterLock(t *testing.T) {
-	var rwm RWMutex
-	rwm.Lock()
-	if rwm.TryRLock() {
-		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
-	}
-}
-
-func TestTryLockAfterRLock(t *testing.T) {
-	var rwm RWMutex
-	rwm.RLock()
-	if rwm.TryLock() {
-		t.Fatal("unexpectedly succeeded in aquiring locked mutex")
-	}
-}
-
-func TestDoubleTryRLock(t *testing.T) {
-	var rwm RWMutex
-	if !rwm.TryRLock() {
-		t.Fatal("failed to aquire lock")
-	}
-	if !rwm.TryRLock() {
-		t.Fatal("failed to read aquire read locked lock")
-	}
-}
diff --git a/pkg/sync/seqcount_test.go b/pkg/sync/seqcount_test.go
deleted file mode 100644
index 3f5592e..0000000
--- a/pkg/sync/seqcount_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2019 The gVisor Authors.
-//
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package sync
-
-import (
-	"testing"
-	"time"
-)
-
-func TestSeqCountWriteUncontended(t *testing.T) {
-	var seq SeqCount
-	seq.BeginWrite()
-	seq.EndWrite()
-}
-
-func TestSeqCountReadUncontended(t *testing.T) {
-	var seq SeqCount
-	epoch := seq.BeginRead()
-	if !seq.ReadOk(epoch) {
-		t.Errorf("ReadOk: got false, wanted true")
-	}
-}
-
-func TestSeqCountBeginReadAfterWrite(t *testing.T) {
-	var seq SeqCount
-	var data int32
-	const want = 1
-	seq.BeginWrite()
-	data = want
-	seq.EndWrite()
-	epoch := seq.BeginRead()
-	if data != want {
-		t.Errorf("Reader: got %v, wanted %v", data, want)
-	}
-	if !seq.ReadOk(epoch) {
-		t.Errorf("ReadOk: got false, wanted true")
-	}
-}
-
-func TestSeqCountBeginReadDuringWrite(t *testing.T) {
-	var seq SeqCount
-	var data int
-	const want = 1
-	seq.BeginWrite()
-	go func() {
-		time.Sleep(time.Second)
-		data = want
-		seq.EndWrite()
-	}()
-	epoch := seq.BeginRead()
-	if data != want {
-		t.Errorf("Reader: got %v, wanted %v", data, want)
-	}
-	if !seq.ReadOk(epoch) {
-		t.Errorf("ReadOk: got false, wanted true")
-	}
-}
-
-func TestSeqCountReadOkAfterWrite(t *testing.T) {
-	var seq SeqCount
-	epoch := seq.BeginRead()
-	seq.BeginWrite()
-	seq.EndWrite()
-	if seq.ReadOk(epoch) {
-		t.Errorf("ReadOk: got true, wanted false")
-	}
-}
-
-func TestSeqCountReadOkDuringWrite(t *testing.T) {
-	var seq SeqCount
-	epoch := seq.BeginRead()
-	seq.BeginWrite()
-	if seq.ReadOk(epoch) {
-		t.Errorf("ReadOk: got true, wanted false")
-	}
-	seq.EndWrite()
-}
-
-func BenchmarkSeqCountWriteUncontended(b *testing.B) {
-	var seq SeqCount
-	for i := 0; i < b.N; i++ {
-		seq.BeginWrite()
-		seq.EndWrite()
-	}
-}
-
-func BenchmarkSeqCountReadUncontended(b *testing.B) {
-	var seq SeqCount
-	b.RunParallel(func(pb *testing.PB) {
-		for pb.Next() {
-			epoch := seq.BeginRead()
-			if !seq.ReadOk(epoch) {
-				b.Fatalf("ReadOk: got false, wanted true")
-			}
-		}
-	})
-}
diff --git a/pkg/tcpip/buffer/buffer_state_autogen.go b/pkg/tcpip/buffer/buffer_state_autogen.go
new file mode 100644
index 0000000..51bfbff
--- /dev/null
+++ b/pkg/tcpip/buffer/buffer_state_autogen.go
@@ -0,0 +1,39 @@
+// automatically generated by stateify.
+
+package buffer
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (vv *VectorisedView) StateTypeName() string {
+	return "pkg/tcpip/buffer.VectorisedView"
+}
+
+func (vv *VectorisedView) StateFields() []string {
+	return []string{
+		"views",
+		"size",
+	}
+}
+
+func (vv *VectorisedView) beforeSave() {}
+
+// +checklocksignore
+func (vv *VectorisedView) StateSave(stateSinkObject state.Sink) {
+	vv.beforeSave()
+	stateSinkObject.Save(0, &vv.views)
+	stateSinkObject.Save(1, &vv.size)
+}
+
+func (vv *VectorisedView) afterLoad() {}
+
+// +checklocksignore
+func (vv *VectorisedView) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &vv.views)
+	stateSourceObject.Load(1, &vv.size)
+}
+
+func init() {
+	state.Register((*VectorisedView)(nil))
+}
diff --git a/pkg/tcpip/buffer/buffer_unsafe_state_autogen.go b/pkg/tcpip/buffer/buffer_unsafe_state_autogen.go
new file mode 100644
index 0000000..5a5c407
--- /dev/null
+++ b/pkg/tcpip/buffer/buffer_unsafe_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package buffer
diff --git a/pkg/tcpip/buffer/view_test.go b/pkg/tcpip/buffer/view_test.go
deleted file mode 100644
index d296d9c..0000000
--- a/pkg/tcpip/buffer/view_test.go
+++ /dev/null
@@ -1,629 +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 buffer_test contains tests for the buffer.VectorisedView type.
-package buffer_test
-
-import (
-	"bytes"
-	"io"
-	"reflect"
-	"testing"
-	"unsafe"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-)
-
-// copy returns a deep-copy of the vectorised view.
-func copyVV(vv buffer.VectorisedView) buffer.VectorisedView {
-	views := make([]buffer.View, 0, len(vv.Views()))
-	for _, v := range vv.Views() {
-		views = append(views, append(buffer.View(nil), v...))
-	}
-	return buffer.NewVectorisedView(vv.Size(), views)
-}
-
-// vv is an helper to build buffer.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)
-}
-
-// v returns a buffer.View containing piece.
-func v(piece string) buffer.View {
-	return buffer.View(piece)
-}
-
-var capLengthTestCases = []struct {
-	comment string
-	in      buffer.VectorisedView
-	length  int
-	want    buffer.VectorisedView
-}{
-	{
-		comment: "Simple case",
-		in:      vv(2, "12"),
-		length:  1,
-		want:    vv(1, "1"),
-	},
-	{
-		comment: "Case spanning across two Views",
-		in:      vv(4, "123", "4"),
-		length:  2,
-		want:    vv(2, "12"),
-	},
-	{
-		comment: "Corner case with negative length",
-		in:      vv(1, "1"),
-		length:  -1,
-		want:    vv(0),
-	},
-	{
-		comment: "Corner case with length = 0",
-		in:      vv(3, "12", "3"),
-		length:  0,
-		want:    vv(0),
-	},
-	{
-		comment: "Corner case with length = size",
-		in:      vv(1, "1"),
-		length:  1,
-		want:    vv(1, "1"),
-	},
-	{
-		comment: "Corner case with length > size",
-		in:      vv(1, "1"),
-		length:  2,
-		want:    vv(1, "1"),
-	},
-}
-
-func TestCapLength(t *testing.T) {
-	for _, c := range capLengthTestCases {
-		orig := copyVV(c.in)
-		c.in.CapLength(c.length)
-		if !reflect.DeepEqual(c.in, c.want) {
-			t.Errorf("Test \"%s\" failed when calling CapLength(%d) on %v. Got %v. Want %v",
-				c.comment, c.length, orig, c.in, c.want)
-		}
-	}
-}
-
-var trimFrontTestCases = []struct {
-	comment string
-	in      buffer.VectorisedView
-	count   int
-	want    buffer.VectorisedView
-}{
-	{
-		comment: "Simple case",
-		in:      vv(2, "12"),
-		count:   1,
-		want:    vv(1, "2"),
-	},
-	{
-		comment: "Case where we trim an entire View",
-		in:      vv(2, "1", "2"),
-		count:   1,
-		want:    vv(1, "2"),
-	},
-	{
-		comment: "Case spanning across two Views",
-		in:      vv(3, "1", "23"),
-		count:   2,
-		want:    vv(1, "3"),
-	},
-	{
-		comment: "Case with one empty Views",
-		in:      vv(3, "1", "", "23"),
-		count:   2,
-		want:    vv(1, "3"),
-	},
-	{
-		comment: "Corner case with negative count",
-		in:      vv(1, "1"),
-		count:   -1,
-		want:    vv(1, "1"),
-	},
-	{
-		comment: " Corner case with count = 0",
-		in:      vv(1, "1"),
-		count:   0,
-		want:    vv(1, "1"),
-	},
-	{
-		comment: "Corner case with count = size",
-		in:      vv(1, "1"),
-		count:   1,
-		want:    vv(0),
-	},
-	{
-		comment: "Corner case with count > size",
-		in:      vv(1, "1"),
-		count:   2,
-		want:    vv(0),
-	},
-}
-
-func TestTrimFront(t *testing.T) {
-	for _, c := range trimFrontTestCases {
-		orig := copyVV(c.in)
-		c.in.TrimFront(c.count)
-		if !reflect.DeepEqual(c.in, c.want) {
-			t.Errorf("Test \"%s\" failed when calling TrimFront(%d) on %v. Got %v. Want %v",
-				c.comment, c.count, orig, c.in, c.want)
-		}
-	}
-}
-
-var toViewCases = []struct {
-	comment string
-	in      buffer.VectorisedView
-	want    buffer.View
-}{
-	{
-		comment: "Simple case",
-		in:      vv(2, "12"),
-		want:    []byte("12"),
-	},
-	{
-		comment: "Case with multiple views",
-		in:      vv(2, "1", "2"),
-		want:    []byte("12"),
-	},
-	{
-		comment: "Empty case",
-		in:      vv(0),
-		want:    []byte(""),
-	},
-}
-
-func TestToView(t *testing.T) {
-	for _, c := range toViewCases {
-		got := c.in.ToView()
-		if !reflect.DeepEqual(got, c.want) {
-			t.Errorf("Test \"%s\" failed when calling ToView() on %v. Got %v. Want %v",
-				c.comment, c.in, got, c.want)
-		}
-	}
-}
-
-var toCloneCases = []struct {
-	comment  string
-	inView   buffer.VectorisedView
-	inBuffer []buffer.View
-}{
-	{
-		comment:  "Simple case",
-		inView:   vv(1, "1"),
-		inBuffer: make([]buffer.View, 1),
-	},
-	{
-		comment:  "Case with multiple views",
-		inView:   vv(2, "1", "2"),
-		inBuffer: make([]buffer.View, 2),
-	},
-	{
-		comment:  "Case with buffer too small",
-		inView:   vv(2, "1", "2"),
-		inBuffer: make([]buffer.View, 1),
-	},
-	{
-		comment:  "Case with buffer larger than needed",
-		inView:   vv(1, "1"),
-		inBuffer: make([]buffer.View, 2),
-	},
-	{
-		comment:  "Case with nil buffer",
-		inView:   vv(1, "1"),
-		inBuffer: nil,
-	},
-}
-
-func TestToClone(t *testing.T) {
-	for _, c := range toCloneCases {
-		t.Run(c.comment, func(t *testing.T) {
-			got := c.inView.Clone(c.inBuffer)
-			if !reflect.DeepEqual(got, c.inView) {
-				t.Fatalf("got (%+v).Clone(%+v) = %+v, want = %+v",
-					c.inView, c.inBuffer, got, c.inView)
-			}
-		})
-	}
-}
-
-type readToTestCases struct {
-	comment     string
-	vv          buffer.VectorisedView
-	bytesToRead int
-	wantBytes   string
-	leftVV      buffer.VectorisedView
-}
-
-func createReadToTestCases() []readToTestCases {
-	return []readToTestCases{
-		{
-			comment:     "large VV, short read",
-			vv:          vv(30, "012345678901234567890123456789"),
-			bytesToRead: 10,
-			wantBytes:   "0123456789",
-			leftVV:      vv(20, "01234567890123456789"),
-		},
-		{
-			comment:     "largeVV, multiple views, short read",
-			vv:          vv(13, "123", "345", "567", "8910"),
-			bytesToRead: 6,
-			wantBytes:   "123345",
-			leftVV:      vv(7, "567", "8910"),
-		},
-		{
-			comment:     "smallVV (multiple views), large read",
-			vv:          vv(3, "1", "2", "3"),
-			bytesToRead: 10,
-			wantBytes:   "123",
-			leftVV:      vv(0, ""),
-		},
-		{
-			comment:     "smallVV (single view), large read",
-			vv:          vv(1, "1"),
-			bytesToRead: 10,
-			wantBytes:   "1",
-			leftVV:      vv(0, ""),
-		},
-		{
-			comment:     "emptyVV, large read",
-			vv:          vv(0, ""),
-			bytesToRead: 10,
-			wantBytes:   "",
-			leftVV:      vv(0, ""),
-		},
-	}
-}
-
-func TestVVReadToVV(t *testing.T) {
-	for _, tc := range createReadToTestCases() {
-		t.Run(tc.comment, func(t *testing.T) {
-			var readTo buffer.VectorisedView
-			inSize := tc.vv.Size()
-			copied := tc.vv.ReadToVV(&readTo, tc.bytesToRead)
-			if got, want := copied, len(tc.wantBytes); got != want {
-				t.Errorf("incorrect number of bytes copied returned in ReadToVV got: %d, want: %d, tc: %+v", got, want, tc)
-			}
-			if got, want := string(readTo.ToView()), tc.wantBytes; got != want {
-				t.Errorf("unexpected content in readTo got: %s, want: %s", got, want)
-			}
-			if got, want := tc.vv.Size(), inSize-copied; got != want {
-				t.Errorf("test VV has incorrect size after reading got: %d, want: %d, tc.vv: %+v", got, want, tc.vv)
-			}
-			if got, want := string(tc.vv.ToView()), string(tc.leftVV.ToView()); got != want {
-				t.Errorf("unexpected data left in vv after read got: %+v, want: %+v", got, want)
-			}
-		})
-	}
-}
-
-func TestVVReadTo(t *testing.T) {
-	for _, tc := range createReadToTestCases() {
-		t.Run(tc.comment, func(t *testing.T) {
-			b := make([]byte, tc.bytesToRead)
-			dst := tcpip.SliceWriter(b)
-			origSize := tc.vv.Size()
-			copied, err := tc.vv.ReadTo(&dst, false /* peek */)
-			if err != nil && err != io.ErrShortWrite {
-				t.Errorf("got ReadTo(&dst, false) = (_, %s); want nil or io.ErrShortWrite", err)
-			}
-			if got, want := copied, len(tc.wantBytes); got != want {
-				t.Errorf("got ReadTo(&dst, false) = (%d, _); want %d", got, want)
-			}
-			if got, want := string(b[:copied]), tc.wantBytes; got != want {
-				t.Errorf("got dst = %q, want %q", got, want)
-			}
-			if got, want := tc.vv.Size(), origSize-copied; got != want {
-				t.Errorf("got after-read tc.vv.Size() = %d, want %d", got, want)
-			}
-			if got, want := string(tc.vv.ToView()), string(tc.leftVV.ToView()); got != want {
-				t.Errorf("got after-read data in tc.vv = %q, want %q", got, want)
-			}
-		})
-	}
-}
-
-func TestVVReadToPeek(t *testing.T) {
-	for _, tc := range createReadToTestCases() {
-		t.Run(tc.comment, func(t *testing.T) {
-			b := make([]byte, tc.bytesToRead)
-			dst := tcpip.SliceWriter(b)
-			origSize := tc.vv.Size()
-			origData := string(tc.vv.ToView())
-			copied, err := tc.vv.ReadTo(&dst, true /* peek */)
-			if err != nil && err != io.ErrShortWrite {
-				t.Errorf("got ReadTo(&dst, true) = (_, %s); want nil or io.ErrShortWrite", err)
-			}
-			if got, want := copied, len(tc.wantBytes); got != want {
-				t.Errorf("got ReadTo(&dst, true) = (%d, _); want %d", got, want)
-			}
-			if got, want := string(b[:copied]), tc.wantBytes; got != want {
-				t.Errorf("got dst = %q, want %q", got, want)
-			}
-			// Expect tc.vv is unchanged.
-			if got, want := tc.vv.Size(), origSize; got != want {
-				t.Errorf("got after-read tc.vv.Size() = %d, want %d", got, want)
-			}
-			if got, want := string(tc.vv.ToView()), origData; got != want {
-				t.Errorf("got after-read data in tc.vv = %q, want %q", got, want)
-			}
-		})
-	}
-}
-
-func TestVVRead(t *testing.T) {
-	testCases := []struct {
-		comment     string
-		vv          buffer.VectorisedView
-		bytesToRead int
-		readBytes   string
-		leftBytes   string
-		wantError   bool
-	}{
-		{
-			comment:     "large VV, short read",
-			vv:          vv(30, "012345678901234567890123456789"),
-			bytesToRead: 10,
-			readBytes:   "0123456789",
-			leftBytes:   "01234567890123456789",
-		},
-		{
-			comment:     "largeVV, multiple buffers, short read",
-			vv:          vv(13, "123", "345", "567", "8910"),
-			bytesToRead: 6,
-			readBytes:   "123345",
-			leftBytes:   "5678910",
-		},
-		{
-			comment:     "smallVV, large read",
-			vv:          vv(3, "1", "2", "3"),
-			bytesToRead: 10,
-			readBytes:   "123",
-			leftBytes:   "",
-		},
-		{
-			comment:     "smallVV, large read",
-			vv:          vv(1, "1"),
-			bytesToRead: 10,
-			readBytes:   "1",
-			leftBytes:   "",
-		},
-		{
-			comment:     "emptyVV, large read",
-			vv:          vv(0, ""),
-			bytesToRead: 10,
-			readBytes:   "",
-			wantError:   true,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.comment, func(t *testing.T) {
-			readTo := buffer.NewView(tc.bytesToRead)
-			inSize := tc.vv.Size()
-			copied, err := tc.vv.Read(readTo)
-			if !tc.wantError && err != nil {
-				t.Fatalf("unexpected error in tc.vv.Read(..) = %s", err)
-			}
-			readTo = readTo[:copied]
-			if got, want := copied, len(tc.readBytes); got != want {
-				t.Errorf("incorrect number of bytes copied returned in ReadToVV got: %d, want: %d, tc.vv: %+v", got, want, tc.vv)
-			}
-			if got, want := string(readTo), tc.readBytes; got != want {
-				t.Errorf("unexpected data in readTo got: %s, want: %s", got, want)
-			}
-			if got, want := tc.vv.Size(), inSize-copied; got != want {
-				t.Errorf("test VV has incorrect size after reading got: %d, want: %d, tc.vv: %+v", got, want, tc.vv)
-			}
-			if got, want := string(tc.vv.ToView()), tc.leftBytes; got != want {
-				t.Errorf("vv has incorrect data after Read got: %s, want: %s", got, want)
-			}
-		})
-	}
-}
-
-var pullUpTestCases = []struct {
-	comment string
-	in      buffer.VectorisedView
-	count   int
-	want    []byte
-	result  buffer.VectorisedView
-	ok      bool
-}{
-	{
-		comment: "simple case",
-		in:      vv(2, "12"),
-		count:   1,
-		want:    []byte("1"),
-		result:  vv(2, "12"),
-		ok:      true,
-	},
-	{
-		comment: "entire View",
-		in:      vv(2, "1", "2"),
-		count:   1,
-		want:    []byte("1"),
-		result:  vv(2, "1", "2"),
-		ok:      true,
-	},
-	{
-		comment: "spanning across two Views",
-		in:      vv(3, "1", "23"),
-		count:   2,
-		want:    []byte("12"),
-		result:  vv(3, "12", "3"),
-		ok:      true,
-	},
-	{
-		comment: "spanning across all Views",
-		in:      vv(5, "1", "23", "45"),
-		count:   5,
-		want:    []byte("12345"),
-		result:  vv(5, "12345"),
-		ok:      true,
-	},
-	{
-		comment: "count = 0",
-		in:      vv(1, "1"),
-		count:   0,
-		want:    []byte{},
-		result:  vv(1, "1"),
-		ok:      true,
-	},
-	{
-		comment: "count = size",
-		in:      vv(1, "1"),
-		count:   1,
-		want:    []byte("1"),
-		result:  vv(1, "1"),
-		ok:      true,
-	},
-	{
-		comment: "count too large",
-		in:      vv(3, "1", "23"),
-		count:   4,
-		want:    nil,
-		result:  vv(3, "1", "23"),
-		ok:      false,
-	},
-	{
-		comment: "empty vv",
-		in:      vv(0, ""),
-		count:   1,
-		want:    nil,
-		result:  vv(0, ""),
-		ok:      false,
-	},
-	{
-		comment: "empty vv, count = 0",
-		in:      vv(0, ""),
-		count:   0,
-		want:    nil,
-		result:  vv(0, ""),
-		ok:      true,
-	},
-	{
-		comment: "empty views",
-		in:      vv(3, "", "1", "", "23"),
-		count:   2,
-		want:    []byte("12"),
-		result:  vv(3, "12", "3"),
-		ok:      true,
-	},
-}
-
-func TestPullUp(t *testing.T) {
-	for _, c := range pullUpTestCases {
-		got, ok := c.in.PullUp(c.count)
-
-		// Is the return value right?
-		if ok != c.ok {
-			t.Errorf("Test %q failed when calling PullUp(%d) on %v. Got an ok of %t. Want %t",
-				c.comment, c.count, c.in, ok, c.ok)
-		}
-		if bytes.Compare(got, buffer.View(c.want)) != 0 {
-			t.Errorf("Test %q failed when calling PullUp(%d) on %v. Got %v. Want %v",
-				c.comment, c.count, c.in, got, c.want)
-		}
-
-		// Is the underlying structure right?
-		if !reflect.DeepEqual(c.in, c.result) {
-			t.Errorf("Test %q failed when calling PullUp(%d). Got vv with structure %v. Wanted %v",
-				c.comment, c.count, c.in, c.result)
-		}
-	}
-}
-
-func TestToVectorisedView(t *testing.T) {
-	testCases := []struct {
-		in   buffer.View
-		want buffer.VectorisedView
-	}{
-		{nil, buffer.VectorisedView{}},
-		{buffer.View{}, buffer.VectorisedView{}},
-		{buffer.View{'a'}, buffer.NewVectorisedView(1, []buffer.View{{'a'}})},
-	}
-	for _, tc := range testCases {
-		if got, want := tc.in.ToVectorisedView(), tc.want; !reflect.DeepEqual(got, want) {
-			t.Errorf("(%v).ToVectorisedView failed got: %+v, want: %+v", tc.in, got, want)
-		}
-	}
-}
-
-func TestAppendView(t *testing.T) {
-	testCases := []struct {
-		vv   buffer.VectorisedView
-		in   buffer.View
-		want buffer.VectorisedView
-	}{
-		{vv(0), nil, vv(0)},
-		{vv(0), v(""), vv(0)},
-		{vv(4, "abcd"), nil, vv(4, "abcd")},
-		{vv(4, "abcd"), v(""), vv(4, "abcd")},
-		{vv(4, "abcd"), v("e"), vv(5, "abcd", "e")},
-	}
-	for _, tc := range testCases {
-		tc.vv.AppendView(tc.in)
-		if got, want := tc.vv, tc.want; !reflect.DeepEqual(got, want) {
-			t.Errorf("(%v).ToVectorisedView failed got: %+v, want: %+v", tc.in, got, want)
-		}
-	}
-}
-
-func TestAppendViews(t *testing.T) {
-	testCases := []struct {
-		vv   buffer.VectorisedView
-		in   []buffer.View
-		want buffer.VectorisedView
-	}{
-		{vv(0), nil, vv(0)},
-		{vv(0), []buffer.View{}, vv(0)},
-		{vv(0), []buffer.View{v("")}, vv(0, "")},
-		{vv(4, "abcd"), nil, vv(4, "abcd")},
-		{vv(4, "abcd"), []buffer.View{}, vv(4, "abcd")},
-		{vv(4, "abcd"), []buffer.View{v("")}, vv(4, "abcd", "")},
-		{vv(4, "abcd"), []buffer.View{v("")}, vv(4, "abcd", "")},
-		{vv(4, "abcd"), []buffer.View{v("e")}, vv(5, "abcd", "e")},
-		{vv(4, "abcd"), []buffer.View{v("e"), v("fg")}, vv(7, "abcd", "e", "fg")},
-		{vv(4, "abcd"), []buffer.View{v(""), v("fg")}, vv(6, "abcd", "", "fg")},
-	}
-	for _, tc := range testCases {
-		tc.vv.AppendViews(tc.in)
-		if got, want := tc.vv, tc.want; !reflect.DeepEqual(got, want) {
-			t.Errorf("(%v).ToVectorisedView failed got: %+v, want: %+v", tc.in, got, want)
-		}
-	}
-}
-
-func TestMemSize(t *testing.T) {
-	const perViewCap = 128
-	views := make([]buffer.View, 2, 32)
-	views[0] = make(buffer.View, 10, perViewCap)
-	views[1] = make(buffer.View, 20, perViewCap)
-	vv := buffer.NewVectorisedView(30, views)
-	want := int(unsafe.Sizeof(vv)) + cap(views)*int(unsafe.Sizeof(views)) + 2*perViewCap
-	if got := vv.MemSize(); got != want {
-		t.Errorf("vv.MemSize() = %d, want %d", got, want)
-	}
-}
diff --git a/pkg/tcpip/faketime/faketime_state_autogen.go b/pkg/tcpip/faketime/faketime_state_autogen.go
new file mode 100644
index 0000000..3de72f2
--- /dev/null
+++ b/pkg/tcpip/faketime/faketime_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package faketime
diff --git a/pkg/tcpip/faketime/faketime_test.go b/pkg/tcpip/faketime/faketime_test.go
deleted file mode 100644
index c2704df..0000000
--- a/pkg/tcpip/faketime/faketime_test.go
+++ /dev/null
@@ -1,95 +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 faketime_test
-
-import (
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/tcpip/faketime"
-)
-
-func TestManualClockAdvance(t *testing.T) {
-	const timeout = time.Millisecond
-	clock := faketime.NewManualClock()
-	start := clock.NowMonotonic()
-	clock.Advance(timeout)
-	if got, want := time.Duration(clock.NowMonotonic()-start)*time.Nanosecond, timeout; got != want {
-		t.Errorf("got = %d, want = %d", got, want)
-	}
-}
-
-func TestManualClockAfterFunc(t *testing.T) {
-	const (
-		timeout1 = time.Millisecond     // timeout for counter1
-		timeout2 = 2 * time.Millisecond // timeout for counter2
-	)
-	tests := []struct {
-		name         string
-		advance      time.Duration
-		wantCounter1 int
-		wantCounter2 int
-	}{
-		{
-			name:         "before timeout1",
-			advance:      timeout1 - 1,
-			wantCounter1: 0,
-			wantCounter2: 0,
-		},
-		{
-			name:         "timeout1",
-			advance:      timeout1,
-			wantCounter1: 1,
-			wantCounter2: 0,
-		},
-		{
-			name:         "timeout2",
-			advance:      timeout2,
-			wantCounter1: 1,
-			wantCounter2: 1,
-		},
-		{
-			name:         "after timeout2",
-			advance:      timeout2 + 1,
-			wantCounter1: 1,
-			wantCounter2: 1,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			clock := faketime.NewManualClock()
-			counter1 := 0
-			counter2 := 0
-			clock.AfterFunc(timeout1, func() {
-				counter1++
-			})
-			clock.AfterFunc(timeout2, func() {
-				counter2++
-			})
-			start := clock.NowMonotonic()
-			clock.Advance(test.advance)
-			if got, want := counter1, test.wantCounter1; got != want {
-				t.Errorf("got counter1 = %d, want = %d", got, want)
-			}
-			if got, want := counter2, test.wantCounter2; got != want {
-				t.Errorf("got counter2 = %d, want = %d", got, want)
-			}
-			if got, want := time.Duration(clock.NowMonotonic()-start)*time.Nanosecond, test.advance; got != want {
-				t.Errorf("got elapsed = %d, want = %d", got, want)
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/hash/jenkins/jenkins_state_autogen.go b/pkg/tcpip/hash/jenkins/jenkins_state_autogen.go
new file mode 100644
index 0000000..216cc5a
--- /dev/null
+++ b/pkg/tcpip/hash/jenkins/jenkins_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package jenkins
diff --git a/pkg/tcpip/hash/jenkins/jenkins_test.go b/pkg/tcpip/hash/jenkins/jenkins_test.go
deleted file mode 100644
index 4c78b58..0000000
--- a/pkg/tcpip/hash/jenkins/jenkins_test.go
+++ /dev/null
@@ -1,176 +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 jenkins
-
-import (
-	"bytes"
-	"encoding/binary"
-	"hash"
-	"hash/fnv"
-	"math"
-	"testing"
-)
-
-func TestGolden32(t *testing.T) {
-	var golden32 = []struct {
-		out []byte
-		in  string
-	}{
-		{[]byte{0x00, 0x00, 0x00, 0x00}, ""},
-		{[]byte{0xca, 0x2e, 0x94, 0x42}, "a"},
-		{[]byte{0x45, 0xe6, 0x1e, 0x58}, "ab"},
-		{[]byte{0xed, 0x13, 0x1f, 0x5b}, "abc"},
-	}
-
-	hash := New32()
-
-	for _, g := range golden32 {
-		hash.Reset()
-		done, error := hash.Write([]byte(g.in))
-		if error != nil {
-			t.Fatalf("write error: %s", error)
-		}
-		if done != len(g.in) {
-			t.Fatalf("wrote only %d out of %d bytes", done, len(g.in))
-		}
-		if actual := hash.Sum(nil); !bytes.Equal(g.out, actual) {
-			t.Errorf("hash(%q) = 0x%x want 0x%x", g.in, actual, g.out)
-		}
-	}
-}
-
-func TestIntegrity32(t *testing.T) {
-	data := []byte{'1', '2', 3, 4, 5}
-
-	h := New32()
-	h.Write(data)
-	sum := h.Sum(nil)
-
-	if size := h.Size(); size != len(sum) {
-		t.Fatalf("Size()=%d but len(Sum())=%d", size, len(sum))
-	}
-
-	if a := h.Sum(nil); !bytes.Equal(sum, a) {
-		t.Fatalf("first Sum()=0x%x, second Sum()=0x%x", sum, a)
-	}
-
-	h.Reset()
-	h.Write(data)
-	if a := h.Sum(nil); !bytes.Equal(sum, a) {
-		t.Fatalf("Sum()=0x%x, but after Reset() Sum()=0x%x", sum, a)
-	}
-
-	h.Reset()
-	h.Write(data[:2])
-	h.Write(data[2:])
-	if a := h.Sum(nil); !bytes.Equal(sum, a) {
-		t.Fatalf("Sum()=0x%x, but with partial writes, Sum()=0x%x", sum, a)
-	}
-
-	sum32 := h.(hash.Hash32).Sum32()
-	if sum32 != binary.BigEndian.Uint32(sum) {
-		t.Fatalf("Sum()=0x%x, but Sum32()=0x%x", sum, sum32)
-	}
-}
-
-func BenchmarkJenkins32KB(b *testing.B) {
-	h := New32()
-
-	b.SetBytes(1024)
-	data := make([]byte, 1024)
-	for i := range data {
-		data[i] = byte(i)
-	}
-	in := make([]byte, 0, h.Size())
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		h.Reset()
-		h.Write(data)
-		h.Sum(in)
-	}
-}
-
-func BenchmarkFnv32(b *testing.B) {
-	arr := make([]int64, 1000)
-	for i := 0; i < b.N; i++ {
-		var payload [8]byte
-		binary.BigEndian.PutUint32(payload[:4], uint32(i))
-		binary.BigEndian.PutUint32(payload[4:], uint32(i))
-
-		h := fnv.New32()
-		h.Write(payload[:])
-		idx := int(h.Sum32()) % len(arr)
-		arr[idx]++
-	}
-	b.StopTimer()
-	c := 0
-	if b.N > 1000000 {
-		for i := 0; i < len(arr)-1; i++ {
-			if math.Abs(float64(arr[i]-arr[i+1]))/float64(arr[i]) > float64(0.1) {
-				if c == 0 {
-					b.Logf("i %d val[i] %d val[i+1] %d b.N %b\n", i, arr[i], arr[i+1], b.N)
-				}
-				c++
-			}
-		}
-		if c > 0 {
-			b.Logf("Unbalanced buckets: %d", c)
-		}
-	}
-}
-
-func BenchmarkSum32(b *testing.B) {
-	arr := make([]int64, 1000)
-	for i := 0; i < b.N; i++ {
-		var payload [8]byte
-		binary.BigEndian.PutUint32(payload[:4], uint32(i))
-		binary.BigEndian.PutUint32(payload[4:], uint32(i))
-		h := Sum32(0)
-		h.Write(payload[:])
-		idx := int(h.Sum32()) % len(arr)
-		arr[idx]++
-	}
-	b.StopTimer()
-	if b.N > 1000000 {
-		for i := 0; i < len(arr)-1; i++ {
-			if math.Abs(float64(arr[i]-arr[i+1]))/float64(arr[i]) > float64(0.1) {
-				b.Logf("val[%3d]=%8d\tval[%3d]=%8d\tb.N=%b\n", i, arr[i], i+1, arr[i+1], b.N)
-				break
-			}
-		}
-	}
-}
-
-func BenchmarkNew32(b *testing.B) {
-	arr := make([]int64, 1000)
-	for i := 0; i < b.N; i++ {
-		var payload [8]byte
-		binary.BigEndian.PutUint32(payload[:4], uint32(i))
-		binary.BigEndian.PutUint32(payload[4:], uint32(i))
-		h := New32()
-		h.Write(payload[:])
-		idx := int(h.Sum32()) % len(arr)
-		arr[idx]++
-	}
-	b.StopTimer()
-	if b.N > 1000000 {
-		for i := 0; i < len(arr)-1; i++ {
-			if math.Abs(float64(arr[i]-arr[i+1]))/float64(arr[i]) > float64(0.1) {
-				b.Logf("val[%3d]=%8d\tval[%3d]=%8d\tb.N=%b\n", i, arr[i], i+1, arr[i+1], b.N)
-				break
-			}
-		}
-	}
-}
diff --git a/pkg/tcpip/header/checksum_test.go b/pkg/tcpip/header/checksum_test.go
deleted file mode 100644
index d267dab..0000000
--- a/pkg/tcpip/header/checksum_test.go
+++ /dev/null
@@ -1,258 +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 header provides the implementation of the encoding and decoding of
-// network protocol headers.
-package header_test
-
-import (
-	"bytes"
-	"fmt"
-	"math/rand"
-	"sync"
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-func TestChecksumer(t *testing.T) {
-	testCases := []struct {
-		name string
-		data [][]byte
-		want uint16
-	}{
-		{
-			name: "empty",
-			want: 0,
-		},
-		{
-			name: "OneOddView",
-			data: [][]byte{
-				[]byte{1, 9, 0, 5, 4},
-			},
-			want: 1294,
-		},
-		{
-			name: "TwoOddViews",
-			data: [][]byte{
-				[]byte{1, 9, 0, 5, 4},
-				[]byte{4, 3, 7, 1, 2, 123},
-			},
-			want: 33819,
-		},
-		{
-			name: "OneEvenView",
-			data: [][]byte{
-				[]byte{1, 9, 0, 5},
-			},
-			want: 270,
-		},
-		{
-			name: "TwoEvenViews",
-			data: [][]byte{
-				buffer.NewViewFromBytes([]byte{98, 1, 9, 0}),
-				buffer.NewViewFromBytes([]byte{9, 0, 5, 4}),
-			},
-			want: 30981,
-		},
-		{
-			name: "ThreeViews",
-			data: [][]byte{
-				[]byte{77, 11, 33, 0, 55, 44},
-				[]byte{98, 1, 9, 0, 5, 4},
-				[]byte{4, 3, 7, 1, 2, 123, 99},
-			},
-			want: 34236,
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			var all bytes.Buffer
-			var c header.Checksumer
-			for _, b := range tc.data {
-				c.Add(b)
-				// Append to the buffer. We will check the checksum as a whole later.
-				if _, err := all.Write(b); err != nil {
-					t.Fatalf("all.Write(b) = _, %s; want _, nil", err)
-				}
-			}
-			if got, want := c.Checksum(), tc.want; got != want {
-				t.Errorf("c.Checksum() = %d, want %d", got, want)
-			}
-			if got, want := header.Checksum(all.Bytes(), 0 /* initial */), tc.want; got != want {
-				t.Errorf("Checksum(flatten tc.data) = %d, want %d", got, want)
-			}
-		})
-	}
-}
-
-func TestChecksum(t *testing.T) {
-	var bufSizes = []int{0, 1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128, 255, 256, 257, 1023, 1024}
-	type testCase struct {
-		buf      []byte
-		initial  uint16
-		csumOrig uint16
-		csumNew  uint16
-	}
-	testCases := make([]testCase, 100000)
-	// Ensure same buffer generation for test consistency.
-	rnd := rand.New(rand.NewSource(42))
-	for i := range testCases {
-		testCases[i].buf = make([]byte, bufSizes[i%len(bufSizes)])
-		testCases[i].initial = uint16(rnd.Intn(65536))
-		rnd.Read(testCases[i].buf)
-	}
-
-	for i := range testCases {
-		testCases[i].csumOrig = header.ChecksumOld(testCases[i].buf, testCases[i].initial)
-		testCases[i].csumNew = header.Checksum(testCases[i].buf, testCases[i].initial)
-		if got, want := testCases[i].csumNew, testCases[i].csumOrig; got != want {
-			t.Fatalf("new checksum for (buf = %x, initial = %d) does not match old got: %d, want: %d", testCases[i].buf, testCases[i].initial, got, want)
-		}
-	}
-}
-
-func BenchmarkChecksum(b *testing.B) {
-	var bufSizes = []int{64, 128, 256, 512, 1024, 1500, 2048, 4096, 8192, 16384, 32767, 32768, 65535, 65536}
-
-	checkSumImpls := []struct {
-		fn   func([]byte, uint16) uint16
-		name string
-	}{
-		{header.ChecksumOld, fmt.Sprintf("checksum_old")},
-		{header.Checksum, fmt.Sprintf("checksum")},
-	}
-
-	for _, csumImpl := range checkSumImpls {
-		// Ensure same buffer generation for test consistency.
-		rnd := rand.New(rand.NewSource(42))
-		for _, bufSz := range bufSizes {
-			b.Run(fmt.Sprintf("%s_%d", csumImpl.name, bufSz), func(b *testing.B) {
-				tc := struct {
-					buf     []byte
-					initial uint16
-					csum    uint16
-				}{
-					buf:     make([]byte, bufSz),
-					initial: uint16(rnd.Intn(65536)),
-				}
-				rnd.Read(tc.buf)
-				b.ResetTimer()
-				for i := 0; i < b.N; i++ {
-					tc.csum = csumImpl.fn(tc.buf, tc.initial)
-				}
-			})
-		}
-	}
-}
-
-func testICMPChecksum(t *testing.T, headerChecksum func() uint16, icmpChecksum func() uint16, want uint16, pktStr string) {
-	// icmpChecksum should not do any modifications of the header to
-	// calculate its checksum. Let's call it from a few go-routines and the
-	// race detector will trigger a warning if there are any concurrent
-	// read/write accesses.
-
-	const concurrency = 5
-	start := make(chan int)
-	ready := make(chan bool, concurrency)
-	var wg sync.WaitGroup
-	wg.Add(concurrency)
-	defer wg.Wait()
-
-	for i := 0; i < concurrency; i++ {
-		go func() {
-			defer wg.Done()
-
-			ready <- true
-			<-start
-
-			if got := headerChecksum(); want != got {
-				t.Errorf("new checksum for %s does not match old got: %x, want: %x", pktStr, got, want)
-			}
-			if got := icmpChecksum(); want != got {
-				t.Errorf("new checksum for %s does not match old got: %x, want: %x", pktStr, got, want)
-			}
-		}()
-	}
-	for i := 0; i < concurrency; i++ {
-		<-ready
-	}
-	close(start)
-}
-
-func TestICMPv4Checksum(t *testing.T) {
-	rnd := rand.New(rand.NewSource(42))
-
-	h := header.ICMPv4(make([]byte, header.ICMPv4MinimumSize))
-	if _, err := rnd.Read(h); err != nil {
-		t.Fatalf("rnd.Read failed: %v", err)
-	}
-	h.SetChecksum(0)
-
-	buf := make([]byte, 13)
-	if _, err := rnd.Read(buf); err != nil {
-		t.Fatalf("rnd.Read failed: %v", err)
-	}
-	vv := buffer.NewVectorisedView(len(buf), []buffer.View{
-		buffer.NewViewFromBytes(buf[:5]),
-		buffer.NewViewFromBytes(buf[5:]),
-	})
-
-	want := header.Checksum(vv.ToView(), 0)
-	want = ^header.Checksum(h, want)
-	h.SetChecksum(want)
-
-	testICMPChecksum(t, h.Checksum, func() uint16 {
-		return header.ICMPv4Checksum(h, header.ChecksumVV(vv, 0))
-	}, want, fmt.Sprintf("header: {% x} data {% x}", h, vv.ToView()))
-}
-
-func TestICMPv6Checksum(t *testing.T) {
-	rnd := rand.New(rand.NewSource(42))
-
-	h := header.ICMPv6(make([]byte, header.ICMPv6MinimumSize))
-	if _, err := rnd.Read(h); err != nil {
-		t.Fatalf("rnd.Read failed: %v", err)
-	}
-	h.SetChecksum(0)
-
-	buf := make([]byte, 13)
-	if _, err := rnd.Read(buf); err != nil {
-		t.Fatalf("rnd.Read failed: %v", err)
-	}
-	vv := buffer.NewVectorisedView(len(buf), []buffer.View{
-		buffer.NewViewFromBytes(buf[:7]),
-		buffer.NewViewFromBytes(buf[7:10]),
-		buffer.NewViewFromBytes(buf[10:]),
-	})
-
-	dst := header.IPv6Loopback
-	src := header.IPv6Loopback
-
-	want := header.PseudoHeaderChecksum(header.ICMPv6ProtocolNumber, src, dst, uint16(len(h)+vv.Size()))
-	want = header.Checksum(vv.ToView(), want)
-	want = ^header.Checksum(h, want)
-	h.SetChecksum(want)
-
-	testICMPChecksum(t, h.Checksum, func() uint16 {
-		return header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-			Header:      h,
-			Src:         src,
-			Dst:         dst,
-			PayloadCsum: header.ChecksumVV(vv, 0),
-			PayloadLen:  vv.Size(),
-		})
-	}, want, fmt.Sprintf("header: {% x} data {% x}", h, vv.ToView()))
-}
diff --git a/pkg/tcpip/header/eth_test.go b/pkg/tcpip/header/eth_test.go
deleted file mode 100644
index 3bc8b2b..0000000
--- a/pkg/tcpip/header/eth_test.go
+++ /dev/null
@@ -1,149 +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 header
-
-import (
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-)
-
-func TestIsValidUnicastEthernetAddress(t *testing.T) {
-	tests := []struct {
-		name     string
-		addr     tcpip.LinkAddress
-		expected bool
-	}{
-		{
-			"Nil",
-			tcpip.LinkAddress([]byte(nil)),
-			false,
-		},
-		{
-			"Empty",
-			tcpip.LinkAddress(""),
-			false,
-		},
-		{
-			"InvalidLength",
-			tcpip.LinkAddress("\x01\x02\x03"),
-			false,
-		},
-		{
-			"Unspecified",
-			unspecifiedEthernetAddress,
-			false,
-		},
-		{
-			"Multicast",
-			tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
-			false,
-		},
-		{
-			"Valid",
-			tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06"),
-			true,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			if got := IsValidUnicastEthernetAddress(test.addr); got != test.expected {
-				t.Fatalf("got IsValidUnicastEthernetAddress = %t, want = %t", got, test.expected)
-			}
-		})
-	}
-}
-
-func TestIsMulticastEthernetAddress(t *testing.T) {
-	tests := []struct {
-		name     string
-		addr     tcpip.LinkAddress
-		expected bool
-	}{
-		{
-			"Nil",
-			tcpip.LinkAddress([]byte(nil)),
-			false,
-		},
-		{
-			"Empty",
-			tcpip.LinkAddress(""),
-			false,
-		},
-		{
-			"InvalidLength",
-			tcpip.LinkAddress("\x01\x02\x03"),
-			false,
-		},
-		{
-			"Unspecified",
-			unspecifiedEthernetAddress,
-			false,
-		},
-		{
-			"Multicast",
-			tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
-			true,
-		},
-		{
-			"Unicast",
-			tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06"),
-			false,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			if got := IsMulticastEthernetAddress(test.addr); got != test.expected {
-				t.Fatalf("got IsMulticastEthernetAddress = %t, want = %t", got, test.expected)
-			}
-		})
-	}
-}
-
-func TestEthernetAddressFromMulticastIPv4Address(t *testing.T) {
-	tests := []struct {
-		name             string
-		addr             tcpip.Address
-		expectedLinkAddr tcpip.LinkAddress
-	}{
-		{
-			name:             "IPv4 Multicast without 24th bit set",
-			addr:             "\xe0\x7e\xdc\xba",
-			expectedLinkAddr: "\x01\x00\x5e\x7e\xdc\xba",
-		},
-		{
-			name:             "IPv4 Multicast with 24th bit set",
-			addr:             "\xe0\xfe\xdc\xba",
-			expectedLinkAddr: "\x01\x00\x5e\x7e\xdc\xba",
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			if got := EthernetAddressFromMulticastIPv4Address(test.addr); got != test.expectedLinkAddr {
-				t.Fatalf("got EthernetAddressFromMulticastIPv4Address(%s) = %s, want = %s", test.addr, got, test.expectedLinkAddr)
-			}
-		})
-	}
-}
-
-func TestEthernetAddressFromMulticastIPv6Address(t *testing.T) {
-	addr := tcpip.Address("\xff\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x1a")
-	if got, want := EthernetAddressFromMulticastIPv6Address(addr), tcpip.LinkAddress("\x33\x33\x0d\x0e\x0f\x1a"); got != want {
-		t.Fatalf("got EthernetAddressFromMulticastIPv6Address(%s) = %s, want = %s", addr, got, want)
-	}
-}
diff --git a/pkg/tcpip/header/header_state_autogen.go b/pkg/tcpip/header/header_state_autogen.go
new file mode 100644
index 0000000..d6dd588
--- /dev/null
+++ b/pkg/tcpip/header/header_state_autogen.go
@@ -0,0 +1,74 @@
+// automatically generated by stateify.
+
+package header
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (r *SACKBlock) StateTypeName() string {
+	return "pkg/tcpip/header.SACKBlock"
+}
+
+func (r *SACKBlock) StateFields() []string {
+	return []string{
+		"Start",
+		"End",
+	}
+}
+
+func (r *SACKBlock) beforeSave() {}
+
+// +checklocksignore
+func (r *SACKBlock) StateSave(stateSinkObject state.Sink) {
+	r.beforeSave()
+	stateSinkObject.Save(0, &r.Start)
+	stateSinkObject.Save(1, &r.End)
+}
+
+func (r *SACKBlock) afterLoad() {}
+
+// +checklocksignore
+func (r *SACKBlock) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &r.Start)
+	stateSourceObject.Load(1, &r.End)
+}
+
+func (t *TCPOptions) StateTypeName() string {
+	return "pkg/tcpip/header.TCPOptions"
+}
+
+func (t *TCPOptions) StateFields() []string {
+	return []string{
+		"TS",
+		"TSVal",
+		"TSEcr",
+		"SACKBlocks",
+	}
+}
+
+func (t *TCPOptions) beforeSave() {}
+
+// +checklocksignore
+func (t *TCPOptions) StateSave(stateSinkObject state.Sink) {
+	t.beforeSave()
+	stateSinkObject.Save(0, &t.TS)
+	stateSinkObject.Save(1, &t.TSVal)
+	stateSinkObject.Save(2, &t.TSEcr)
+	stateSinkObject.Save(3, &t.SACKBlocks)
+}
+
+func (t *TCPOptions) afterLoad() {}
+
+// +checklocksignore
+func (t *TCPOptions) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &t.TS)
+	stateSourceObject.Load(1, &t.TSVal)
+	stateSourceObject.Load(2, &t.TSEcr)
+	stateSourceObject.Load(3, &t.SACKBlocks)
+}
+
+func init() {
+	state.Register((*SACKBlock)(nil))
+	state.Register((*TCPOptions)(nil))
+}
diff --git a/pkg/tcpip/header/igmp_test.go b/pkg/tcpip/header/igmp_test.go
deleted file mode 100644
index b6126d2..0000000
--- a/pkg/tcpip/header/igmp_test.go
+++ /dev/null
@@ -1,110 +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 header_test
-
-import (
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-// TestIGMPHeader tests the functions within header.igmp
-func TestIGMPHeader(t *testing.T) {
-	const maxRespTimeTenthSec = 0xF0
-	b := []byte{
-		0x11,                // IGMP Type, Membership Query
-		maxRespTimeTenthSec, // Maximum Response Time
-		0xC0, 0xC0,          // Checksum
-		0x01, 0x02, 0x03, 0x04, // Group Address
-	}
-
-	igmpHeader := header.IGMP(b)
-
-	if got, want := igmpHeader.Type(), header.IGMPMembershipQuery; got != want {
-		t.Errorf("got igmpHeader.Type() = %x, want = %x", got, want)
-	}
-
-	if got, want := igmpHeader.MaxRespTime(), header.DecisecondToDuration(maxRespTimeTenthSec); got != want {
-		t.Errorf("got igmpHeader.MaxRespTime() = %s, want = %s", got, want)
-	}
-
-	if got, want := igmpHeader.Checksum(), uint16(0xC0C0); got != want {
-		t.Errorf("got igmpHeader.Checksum() = %x, want = %x", got, want)
-	}
-
-	if got, want := igmpHeader.GroupAddress(), tcpip.Address("\x01\x02\x03\x04"); got != want {
-		t.Errorf("got igmpHeader.GroupAddress() = %s, want = %s", got, want)
-	}
-
-	igmpType := header.IGMPv2MembershipReport
-	igmpHeader.SetType(igmpType)
-	if got := igmpHeader.Type(); got != igmpType {
-		t.Errorf("got igmpHeader.Type() = %x, want = %x", got, igmpType)
-	}
-	if got := header.IGMPType(b[0]); got != igmpType {
-		t.Errorf("got IGMPtype in backing buffer = %x, want %x", got, igmpType)
-	}
-
-	respTime := byte(0x02)
-	igmpHeader.SetMaxRespTime(respTime)
-	if got, want := igmpHeader.MaxRespTime(), header.DecisecondToDuration(respTime); got != want {
-		t.Errorf("got igmpHeader.MaxRespTime() = %s, want = %s", got, want)
-	}
-
-	checksum := uint16(0x0102)
-	igmpHeader.SetChecksum(checksum)
-	if got := igmpHeader.Checksum(); got != checksum {
-		t.Errorf("got igmpHeader.Checksum() = %x, want = %x", got, checksum)
-	}
-
-	groupAddress := tcpip.Address("\x04\x03\x02\x01")
-	igmpHeader.SetGroupAddress(groupAddress)
-	if got := igmpHeader.GroupAddress(); got != groupAddress {
-		t.Errorf("got igmpHeader.GroupAddress() = %s, want = %s", got, groupAddress)
-	}
-}
-
-// TestIGMPChecksum ensures that the checksum calculator produces the expected
-// checksum.
-func TestIGMPChecksum(t *testing.T) {
-	b := []byte{
-		0x11,       // IGMP Type, Membership Query
-		0xF0,       // Maximum Response Time
-		0xC0, 0xC0, // Checksum
-		0x01, 0x02, 0x03, 0x04, // Group Address
-	}
-
-	igmpHeader := header.IGMP(b)
-
-	// Calculate the initial checksum after setting the checksum temporarily to 0
-	// to avoid checksumming the checksum.
-	initialChecksum := igmpHeader.Checksum()
-	igmpHeader.SetChecksum(0)
-	checksum := ^header.Checksum(b, 0)
-	igmpHeader.SetChecksum(initialChecksum)
-
-	if got := header.IGMPCalculateChecksum(igmpHeader); got != checksum {
-		t.Errorf("got IGMPCalculateChecksum = %x, want %x", got, checksum)
-	}
-}
-
-func TestDecisecondToDuration(t *testing.T) {
-	const valueInDeciseconds = 5
-	if got, want := header.DecisecondToDuration(valueInDeciseconds), valueInDeciseconds*time.Second/10; got != want {
-		t.Fatalf("got header.DecisecondToDuration(%d) = %s, want = %s", valueInDeciseconds, got, want)
-	}
-}
diff --git a/pkg/tcpip/header/ipv4_test.go b/pkg/tcpip/header/ipv4_test.go
deleted file mode 100644
index 6475cd6..0000000
--- a/pkg/tcpip/header/ipv4_test.go
+++ /dev/null
@@ -1,179 +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 header_test
-
-import (
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-func TestIPv4OptionsSerializer(t *testing.T) {
-	optCases := []struct {
-		name   string
-		option []header.IPv4SerializableOption
-		expect []byte
-	}{
-		{
-			name: "NOP",
-			option: []header.IPv4SerializableOption{
-				&header.IPv4SerializableNOPOption{},
-			},
-			expect: []byte{1, 0, 0, 0},
-		},
-		{
-			name: "ListEnd",
-			option: []header.IPv4SerializableOption{
-				&header.IPv4SerializableListEndOption{},
-			},
-			expect: []byte{0, 0, 0, 0},
-		},
-		{
-			name: "RouterAlert",
-			option: []header.IPv4SerializableOption{
-				&header.IPv4SerializableRouterAlertOption{},
-			},
-			expect: []byte{148, 4, 0, 0},
-		}, {
-			name: "NOP and RouterAlert",
-			option: []header.IPv4SerializableOption{
-				&header.IPv4SerializableNOPOption{},
-				&header.IPv4SerializableRouterAlertOption{},
-			},
-			expect: []byte{1, 148, 4, 0, 0, 0, 0, 0},
-		},
-	}
-
-	for _, opt := range optCases {
-		t.Run(opt.name, func(t *testing.T) {
-			s := header.IPv4OptionsSerializer(opt.option)
-			l := s.Length()
-			if got := len(opt.expect); got != int(l) {
-				t.Fatalf("s.Length() = %d, want = %d", got, l)
-			}
-			b := make([]byte, l)
-			for i := range b {
-				// Fill the buffer with full bytes to ensure padding is being set
-				// correctly.
-				b[i] = 0xFF
-			}
-			if serializedLength := s.Serialize(b); serializedLength != l {
-				t.Fatalf("s.Serialize(_) = %d, want %d", serializedLength, l)
-			}
-			if diff := cmp.Diff(opt.expect, b); diff != "" {
-				t.Errorf("mismatched serialized option (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-// TestIPv4Encode checks that ipv4.Encode correctly fills out the requested
-// fields when options are supplied.
-func TestIPv4EncodeOptions(t *testing.T) {
-	tests := []struct {
-		name           string
-		numberOfNops   int
-		encodedOptions header.IPv4Options // reply should look like this
-		wantIHL        int
-	}{
-		{
-			name:    "valid no options",
-			wantIHL: header.IPv4MinimumSize,
-		},
-		{
-			name:           "one byte options",
-			numberOfNops:   1,
-			encodedOptions: header.IPv4Options{1, 0, 0, 0},
-			wantIHL:        header.IPv4MinimumSize + 4,
-		},
-		{
-			name:           "two byte options",
-			numberOfNops:   2,
-			encodedOptions: header.IPv4Options{1, 1, 0, 0},
-			wantIHL:        header.IPv4MinimumSize + 4,
-		},
-		{
-			name:           "three byte options",
-			numberOfNops:   3,
-			encodedOptions: header.IPv4Options{1, 1, 1, 0},
-			wantIHL:        header.IPv4MinimumSize + 4,
-		},
-		{
-			name:           "four byte options",
-			numberOfNops:   4,
-			encodedOptions: header.IPv4Options{1, 1, 1, 1},
-			wantIHL:        header.IPv4MinimumSize + 4,
-		},
-		{
-			name:           "five byte options",
-			numberOfNops:   5,
-			encodedOptions: header.IPv4Options{1, 1, 1, 1, 1, 0, 0, 0},
-			wantIHL:        header.IPv4MinimumSize + 8,
-		},
-		{
-			name:         "thirty nine byte options",
-			numberOfNops: 39,
-			encodedOptions: header.IPv4Options{
-				1, 1, 1, 1, 1, 1, 1, 1,
-				1, 1, 1, 1, 1, 1, 1, 1,
-				1, 1, 1, 1, 1, 1, 1, 1,
-				1, 1, 1, 1, 1, 1, 1, 1,
-				1, 1, 1, 1, 1, 1, 1, 0,
-			},
-			wantIHL: header.IPv4MinimumSize + 40,
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			serializeOpts := header.IPv4OptionsSerializer(make([]header.IPv4SerializableOption, test.numberOfNops))
-			for i := range serializeOpts {
-				serializeOpts[i] = &header.IPv4SerializableNOPOption{}
-			}
-			paddedOptionLength := serializeOpts.Length()
-			ipHeaderLength := int(header.IPv4MinimumSize + paddedOptionLength)
-			if ipHeaderLength > header.IPv4MaximumHeaderSize {
-				t.Fatalf("IP header length too large: got = %d, want <= %d ", ipHeaderLength, header.IPv4MaximumHeaderSize)
-			}
-			totalLen := uint16(ipHeaderLength)
-			hdr := buffer.NewPrependable(int(totalLen))
-			ip := header.IPv4(hdr.Prepend(ipHeaderLength))
-			// To check the padding works, poison the last byte of the options space.
-			if paddedOptionLength != serializeOpts.Length() {
-				ip.SetHeaderLength(uint8(ipHeaderLength))
-				ip.Options()[paddedOptionLength-1] = 0xff
-				ip.SetHeaderLength(0)
-			}
-			ip.Encode(&header.IPv4Fields{
-				Options: serializeOpts,
-			})
-			options := ip.Options()
-			wantOptions := test.encodedOptions
-			if got, want := int(ip.HeaderLength()), test.wantIHL; got != want {
-				t.Errorf("got IHL of %d, want %d", got, want)
-			}
-
-			// cmp.Diff does not consider nil slices equal to empty slices, but we do.
-			if len(wantOptions) == 0 && len(options) == 0 {
-				return
-			}
-
-			if diff := cmp.Diff(wantOptions, options); diff != "" {
-				t.Errorf("options mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/header/ipv6_extension_headers_test.go b/pkg/tcpip/header/ipv6_extension_headers_test.go
deleted file mode 100644
index 65adc62..0000000
--- a/pkg/tcpip/header/ipv6_extension_headers_test.go
+++ /dev/null
@@ -1,1346 +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 header
-
-import (
-	"bytes"
-	"errors"
-	"io"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-)
-
-// Equal returns true of a and b are equivalent.
-//
-// Note, Equal will return true if a and b hold the same Identifier value and
-// contain the same bytes in Buf, even if the bytes are split across views
-// differently.
-//
-// Needed to use cmp.Equal on IPv6RawPayloadHeader as it contains unexported
-// fields.
-func (a IPv6RawPayloadHeader) Equal(b IPv6RawPayloadHeader) bool {
-	return a.Identifier == b.Identifier && bytes.Equal(a.Buf.ToView(), b.Buf.ToView())
-}
-
-// Equal returns true of a and b are equivalent.
-//
-// Note, Equal will return true if a and b hold equivalent ipv6OptionsExtHdrs.
-//
-// Needed to use cmp.Equal on IPv6RawPayloadHeader as it contains unexported
-// fields.
-func (a IPv6HopByHopOptionsExtHdr) Equal(b IPv6HopByHopOptionsExtHdr) bool {
-	return bytes.Equal(a.ipv6OptionsExtHdr, b.ipv6OptionsExtHdr)
-}
-
-// Equal returns true of a and b are equivalent.
-//
-// Note, Equal will return true if a and b hold equivalent ipv6OptionsExtHdrs.
-//
-// Needed to use cmp.Equal on IPv6RawPayloadHeader as it contains unexported
-// fields.
-func (a IPv6DestinationOptionsExtHdr) Equal(b IPv6DestinationOptionsExtHdr) bool {
-	return bytes.Equal(a.ipv6OptionsExtHdr, b.ipv6OptionsExtHdr)
-}
-
-func TestIPv6UnknownExtHdrOption(t *testing.T) {
-	tests := []struct {
-		name                  string
-		identifier            IPv6ExtHdrOptionIdentifier
-		expectedUnknownAction IPv6OptionUnknownAction
-	}{
-		{
-			name:                  "Skip with zero LSBs",
-			identifier:            0,
-			expectedUnknownAction: IPv6OptionUnknownActionSkip,
-		},
-		{
-			name:                  "Discard with zero LSBs",
-			identifier:            64,
-			expectedUnknownAction: IPv6OptionUnknownActionDiscard,
-		},
-		{
-			name:                  "Discard and ICMP with zero LSBs",
-			identifier:            128,
-			expectedUnknownAction: IPv6OptionUnknownActionDiscardSendICMP,
-		},
-		{
-			name:                  "Discard and ICMP for non multicast destination with zero LSBs",
-			identifier:            192,
-			expectedUnknownAction: IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
-		},
-		{
-			name:                  "Skip with non-zero LSBs",
-			identifier:            63,
-			expectedUnknownAction: IPv6OptionUnknownActionSkip,
-		},
-		{
-			name:                  "Discard with non-zero LSBs",
-			identifier:            127,
-			expectedUnknownAction: IPv6OptionUnknownActionDiscard,
-		},
-		{
-			name:                  "Discard and ICMP with non-zero LSBs",
-			identifier:            191,
-			expectedUnknownAction: IPv6OptionUnknownActionDiscardSendICMP,
-		},
-		{
-			name:                  "Discard and ICMP for non multicast destination with non-zero LSBs",
-			identifier:            255,
-			expectedUnknownAction: IPv6OptionUnknownActionDiscardSendICMPNoMulticastDest,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			opt := &IPv6UnknownExtHdrOption{Identifier: test.identifier, Data: []byte{1, 2, 3, 4}}
-			if a := opt.UnknownAction(); a != test.expectedUnknownAction {
-				t.Fatalf("got UnknownAction() = %d, want = %d", a, test.expectedUnknownAction)
-			}
-		})
-	}
-
-}
-
-func TestIPv6OptionsExtHdrIterErr(t *testing.T) {
-	tests := []struct {
-		name  string
-		bytes []byte
-		err   error
-	}{
-		{
-			name:  "Single unknown with zero length",
-			bytes: []byte{255, 0},
-		},
-		{
-			name:  "Single unknown with non-zero length",
-			bytes: []byte{255, 3, 1, 2, 3},
-		},
-		{
-			name: "Two options",
-			bytes: []byte{
-				255, 0,
-				254, 1, 1,
-			},
-		},
-		{
-			name: "Three options",
-			bytes: []byte{
-				255, 0,
-				254, 1, 1,
-				253, 4, 2, 3, 4, 5,
-			},
-		},
-		{
-			name:  "Single unknown only identifier",
-			bytes: []byte{255},
-			err:   io.ErrUnexpectedEOF,
-		},
-		{
-			name:  "Single unknown too small with length = 1",
-			bytes: []byte{255, 1},
-			err:   io.ErrUnexpectedEOF,
-		},
-		{
-			name:  "Single unknown too small with length = 2",
-			bytes: []byte{255, 2, 1},
-			err:   io.ErrUnexpectedEOF,
-		},
-		{
-			name: "Valid first with second unknown only identifier",
-			bytes: []byte{
-				255, 0,
-				254,
-			},
-			err: io.ErrUnexpectedEOF,
-		},
-		{
-			name: "Valid first with second unknown missing data",
-			bytes: []byte{
-				255, 0,
-				254, 1,
-			},
-			err: io.ErrUnexpectedEOF,
-		},
-		{
-			name: "Valid first with second unknown too small",
-			bytes: []byte{
-				255, 0,
-				254, 2, 1,
-			},
-			err: io.ErrUnexpectedEOF,
-		},
-		{
-			name:  "One Pad1",
-			bytes: []byte{0},
-		},
-		{
-			name:  "Multiple Pad1",
-			bytes: []byte{0, 0, 0},
-		},
-		{
-			name: "Multiple PadN",
-			bytes: []byte{
-				// Pad3
-				1, 1, 1,
-
-				// Pad5
-				1, 3, 1, 2, 3,
-			},
-		},
-		{
-			name:  "Pad5 too small middle of data buffer",
-			bytes: []byte{1, 3, 1, 2},
-			err:   io.ErrUnexpectedEOF,
-		},
-		{
-			name:  "Pad5 no data",
-			bytes: []byte{1, 3},
-			err:   io.ErrUnexpectedEOF,
-		},
-		{
-			name:  "Router alert without data",
-			bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 0},
-			err:   ErrMalformedIPv6ExtHdrOption,
-		},
-		{
-			name:  "Router alert with partial data",
-			bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 1, 1},
-			err:   ErrMalformedIPv6ExtHdrOption,
-		},
-		{
-			name:  "Router alert with partial data and Pad1",
-			bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 1, 1, 0},
-			err:   ErrMalformedIPv6ExtHdrOption,
-		},
-		{
-			name:  "Router alert with extra data",
-			bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 3, 1, 2, 3},
-			err:   ErrMalformedIPv6ExtHdrOption,
-		},
-		{
-			name:  "Router alert with missing data",
-			bytes: []byte{byte(ipv6RouterAlertHopByHopOptionIdentifier), 1},
-			err:   io.ErrUnexpectedEOF,
-		},
-	}
-
-	check := func(t *testing.T, it IPv6OptionsExtHdrOptionsIterator, expectedErr error) {
-		for i := 0; ; i++ {
-			_, done, err := it.Next()
-			if err != nil {
-				// If we encountered a non-nil error while iterating, make sure it is
-				// is the same error as expectedErr.
-				if !errors.Is(err, expectedErr) {
-					t.Fatalf("got %d-th Next() = %v, want = %v", i, err, expectedErr)
-				}
-
-				return
-			}
-			if done {
-				// If we are done (without an error), make sure that we did not expect
-				// an error.
-				if expectedErr != nil {
-					t.Fatalf("expected error when iterating; want = %s", expectedErr)
-				}
-
-				return
-			}
-		}
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			t.Run("Hop By Hop", func(t *testing.T) {
-				extHdr := IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: test.bytes}
-				check(t, extHdr.Iter(), test.err)
-			})
-
-			t.Run("Destination", func(t *testing.T) {
-				extHdr := IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: test.bytes}
-				check(t, extHdr.Iter(), test.err)
-			})
-		})
-	}
-}
-
-func TestIPv6OptionsExtHdrIter(t *testing.T) {
-	tests := []struct {
-		name     string
-		bytes    []byte
-		expected []IPv6ExtHdrOption
-	}{
-		{
-			name:  "Single unknown with zero length",
-			bytes: []byte{255, 0},
-			expected: []IPv6ExtHdrOption{
-				&IPv6UnknownExtHdrOption{Identifier: 255, Data: []byte{}},
-			},
-		},
-		{
-			name:  "Single unknown with non-zero length",
-			bytes: []byte{255, 3, 1, 2, 3},
-			expected: []IPv6ExtHdrOption{
-				&IPv6UnknownExtHdrOption{Identifier: 255, Data: []byte{1, 2, 3}},
-			},
-		},
-		{
-			name:  "Single Pad1",
-			bytes: []byte{0},
-		},
-		{
-			name:  "Two Pad1",
-			bytes: []byte{0, 0},
-		},
-		{
-			name:  "Single Pad3",
-			bytes: []byte{1, 1, 1},
-		},
-		{
-			name:  "Single Pad5",
-			bytes: []byte{1, 3, 1, 2, 3},
-		},
-		{
-			name: "Multiple Pad",
-			bytes: []byte{
-				// Pad1
-				0,
-
-				// Pad2
-				1, 0,
-
-				// Pad3
-				1, 1, 1,
-
-				// Pad4
-				1, 2, 1, 2,
-
-				// Pad5
-				1, 3, 1, 2, 3,
-			},
-		},
-		{
-			name: "Multiple options",
-			bytes: []byte{
-				// Pad1
-				0,
-
-				// Unknown
-				255, 0,
-
-				// Pad2
-				1, 0,
-
-				// Unknown
-				254, 1, 1,
-
-				// Pad3
-				1, 1, 1,
-
-				// Unknown
-				253, 4, 2, 3, 4, 5,
-
-				// Pad4
-				1, 2, 1, 2,
-			},
-			expected: []IPv6ExtHdrOption{
-				&IPv6UnknownExtHdrOption{Identifier: 255, Data: []byte{}},
-				&IPv6UnknownExtHdrOption{Identifier: 254, Data: []byte{1}},
-				&IPv6UnknownExtHdrOption{Identifier: 253, Data: []byte{2, 3, 4, 5}},
-			},
-		},
-	}
-
-	checkIter := func(t *testing.T, it IPv6OptionsExtHdrOptionsIterator, expected []IPv6ExtHdrOption) {
-		for i, e := range expected {
-			opt, done, err := it.Next()
-			if err != nil {
-				t.Errorf("(i=%d) Next(): %s", i, err)
-			}
-			if done {
-				t.Errorf("(i=%d) unexpectedly done iterating", i)
-			}
-			if diff := cmp.Diff(e, opt); diff != "" {
-				t.Errorf("(i=%d) got option mismatch (-want +got):\n%s", i, diff)
-			}
-
-			if t.Failed() {
-				t.FailNow()
-			}
-		}
-
-		opt, done, err := it.Next()
-		if err != nil {
-			t.Errorf("(last) Next(): %s", err)
-		}
-		if !done {
-			t.Errorf("(last) iterator unexpectedly not done")
-		}
-		if opt != nil {
-			t.Errorf("(last) got Next() = %T, want = nil", opt)
-		}
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			t.Run("Hop By Hop", func(t *testing.T) {
-				extHdr := IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: test.bytes}
-				checkIter(t, extHdr.Iter(), test.expected)
-			})
-
-			t.Run("Destination", func(t *testing.T) {
-				extHdr := IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: test.bytes}
-				checkIter(t, extHdr.Iter(), test.expected)
-			})
-		})
-	}
-}
-
-func TestIPv6RoutingExtHdr(t *testing.T) {
-	tests := []struct {
-		name         string
-		bytes        []byte
-		segmentsLeft uint8
-	}{
-		{
-			name:         "Zeroes",
-			bytes:        []byte{0, 0, 0, 0, 0, 0},
-			segmentsLeft: 0,
-		},
-		{
-			name:         "Ones",
-			bytes:        []byte{1, 1, 1, 1, 1, 1},
-			segmentsLeft: 1,
-		},
-		{
-			name:         "Mixed",
-			bytes:        []byte{1, 2, 3, 4, 5, 6},
-			segmentsLeft: 2,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			extHdr := IPv6RoutingExtHdr(test.bytes)
-			if got := extHdr.SegmentsLeft(); got != test.segmentsLeft {
-				t.Errorf("got SegmentsLeft() = %d, want = %d", got, test.segmentsLeft)
-			}
-		})
-	}
-}
-
-func TestIPv6FragmentExtHdr(t *testing.T) {
-	tests := []struct {
-		name           string
-		bytes          [6]byte
-		fragmentOffset uint16
-		more           bool
-		id             uint32
-	}{
-		{
-			name:           "Zeroes",
-			bytes:          [6]byte{0, 0, 0, 0, 0, 0},
-			fragmentOffset: 0,
-			more:           false,
-			id:             0,
-		},
-		{
-			name:           "Ones",
-			bytes:          [6]byte{0, 9, 0, 0, 0, 1},
-			fragmentOffset: 1,
-			more:           true,
-			id:             1,
-		},
-		{
-			name:           "Mixed",
-			bytes:          [6]byte{68, 9, 128, 4, 2, 1},
-			fragmentOffset: 2177,
-			more:           true,
-			id:             2147746305,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			extHdr := IPv6FragmentExtHdr(test.bytes)
-			if got := extHdr.FragmentOffset(); got != test.fragmentOffset {
-				t.Errorf("got FragmentOffset() = %d, want = %d", got, test.fragmentOffset)
-			}
-			if got := extHdr.More(); got != test.more {
-				t.Errorf("got More() = %t, want = %t", got, test.more)
-			}
-			if got := extHdr.ID(); got != test.id {
-				t.Errorf("got ID() = %d, want = %d", got, test.id)
-			}
-		})
-	}
-}
-
-func makeVectorisedViewFromByteBuffers(bs ...[]byte) buffer.VectorisedView {
-	size := 0
-	var vs []buffer.View
-
-	for _, b := range bs {
-		vs = append(vs, buffer.View(b))
-		size += len(b)
-	}
-
-	return buffer.NewVectorisedView(size, vs)
-}
-
-func TestIPv6ExtHdrIterErr(t *testing.T) {
-	tests := []struct {
-		name         string
-		firstNextHdr IPv6ExtensionHeaderIdentifier
-		payload      buffer.VectorisedView
-		err          error
-	}{
-		{
-			name:         "Upper layer only without data",
-			firstNextHdr: 255,
-		},
-		{
-			name:         "Upper layer only with data",
-			firstNextHdr: 255,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{1, 2, 3, 4}),
-		},
-		{
-			name:         "No next header",
-			firstNextHdr: IPv6NoNextHeaderIdentifier,
-		},
-		{
-			name:         "No next header with data",
-			firstNextHdr: IPv6NoNextHeaderIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{1, 2, 3, 4}),
-		},
-		{
-			name:         "Valid single hop by hop",
-			firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 4, 1, 2, 3, 4}),
-		},
-		{
-			name:         "Hop by hop too small",
-			firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 4, 1, 2, 3}),
-			err:          io.ErrUnexpectedEOF,
-		},
-		{
-			name:         "Valid single fragment",
-			firstNextHdr: IPv6FragmentExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 68, 9, 128, 4, 2, 1}),
-		},
-		{
-			name:         "Fragment too small",
-			firstNextHdr: IPv6FragmentExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 68, 9, 128, 4, 2}),
-			err:          io.ErrUnexpectedEOF,
-		},
-		{
-			name:         "Valid single destination",
-			firstNextHdr: IPv6DestinationOptionsExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 4, 1, 2, 3, 4}),
-		},
-		{
-			name:         "Destination too small",
-			firstNextHdr: IPv6DestinationOptionsExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 4, 1, 2, 3}),
-			err:          io.ErrUnexpectedEOF,
-		},
-		{
-			name:         "Valid single routing",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 2, 3, 4, 5, 6}),
-		},
-		{
-			name:         "Valid single routing across views",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 2}, []byte{3, 4, 5, 6}),
-		},
-		{
-			name:         "Routing too small with zero length field",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 0, 1, 2, 3, 4, 5}),
-			err:          io.ErrUnexpectedEOF,
-		},
-		{
-			name:         "Valid routing with non-zero length field",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8}),
-		},
-		{
-			name:         "Valid routing with non-zero length field across views",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 1, 1, 2, 3, 4, 5, 6}, []byte{1, 2, 3, 4, 5, 6, 7, 8}),
-		},
-		{
-			name:         "Routing too small with non-zero length field",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 1, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7}),
-			err:          io.ErrUnexpectedEOF,
-		},
-		{
-			name:         "Routing too small with non-zero length field across views",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload:      makeVectorisedViewFromByteBuffers([]byte{255, 1, 1, 2, 3, 4, 5, 6}, []byte{1, 2, 3, 4, 5, 6, 7}),
-			err:          io.ErrUnexpectedEOF,
-		},
-		{
-			name:         "Mixed",
-			firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Hop By Hop Options extension header.
-				uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4,
-
-				// (Atomic) Fragment extension header.
-				//
-				// Reserved bits are 1 which should not affect anything.
-				uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6, 128, 4, 2, 1,
-
-				// Routing extension header.
-				uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6,
-
-				// Destination Options extension header.
-				255, 0, 255, 4, 1, 2, 3, 4,
-
-				// Upper layer data.
-				1, 2, 3, 4,
-			}),
-		},
-		{
-			name:         "Mixed without upper layer data",
-			firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Hop By Hop Options extension header.
-				uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4,
-
-				// (Atomic) Fragment extension header.
-				//
-				// Reserved bits are 1 which should not affect anything.
-				uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6, 128, 4, 2, 1,
-
-				// Routing extension header.
-				uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6,
-
-				// Destination Options extension header.
-				255, 0, 255, 4, 1, 2, 3, 4,
-			}),
-		},
-		{
-			name:         "Mixed without upper layer data but last ext hdr too small",
-			firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Hop By Hop Options extension header.
-				uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4,
-
-				// (Atomic) Fragment extension header.
-				//
-				// Reserved bits are 1 which should not affect anything.
-				uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6, 128, 4, 2, 1,
-
-				// Routing extension header.
-				uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6,
-
-				// Destination Options extension header.
-				255, 0, 255, 4, 1, 2, 3,
-			}),
-			err: io.ErrUnexpectedEOF,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			it := MakeIPv6PayloadIterator(test.firstNextHdr, test.payload)
-
-			for i := 0; ; i++ {
-				_, done, err := it.Next()
-				if err != nil {
-					// If we encountered a non-nil error while iterating, make sure it is
-					// is the same error as test.err.
-					if !errors.Is(err, test.err) {
-						t.Fatalf("got %d-th Next() = %v, want = %v", i, err, test.err)
-					}
-
-					return
-				}
-				if done {
-					// If we are done (without an error), make sure that we did not expect
-					// an error.
-					if test.err != nil {
-						t.Fatalf("expected error when iterating; want = %s", test.err)
-					}
-
-					return
-				}
-			}
-		})
-	}
-}
-
-func TestIPv6ExtHdrIter(t *testing.T) {
-	routingExtHdrWithUpperLayerData := buffer.View([]byte{255, 0, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4})
-	upperLayerData := buffer.View([]byte{1, 2, 3, 4})
-	tests := []struct {
-		name         string
-		firstNextHdr IPv6ExtensionHeaderIdentifier
-		payload      buffer.VectorisedView
-		expected     []IPv6PayloadHeader
-	}{
-		// With a non-atomic fragment that is not the first fragment, the payload
-		// after the fragment will not be parsed because the payload is expected to
-		// only hold upper layer data.
-		{
-			name:         "hopbyhop - fragment (not first) - routing - upper",
-			firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Hop By Hop extension header.
-				uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4,
-
-				// Fragment extension header.
-				//
-				// More = 1, Fragment Offset = 2117, ID = 2147746305
-				uint8(IPv6RoutingExtHdrIdentifier), 0, 68, 9, 128, 4, 2, 1,
-
-				// Routing extension header.
-				//
-				// Even though we have a routing ext header here, it should be
-				// be interpretted as raw bytes as only the first fragment is expected
-				// to hold headers.
-				255, 0, 1, 2, 3, 4, 5, 6,
-
-				// Upper layer data.
-				1, 2, 3, 4,
-			}),
-			expected: []IPv6PayloadHeader{
-				IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}},
-				IPv6FragmentExtHdr([6]byte{68, 9, 128, 4, 2, 1}),
-				IPv6RawPayloadHeader{
-					Identifier: IPv6RoutingExtHdrIdentifier,
-					Buf:        routingExtHdrWithUpperLayerData.ToVectorisedView(),
-				},
-			},
-		},
-		{
-			name:         "hopbyhop - fragment (first) - routing - upper",
-			firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Hop By Hop extension header.
-				uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4,
-
-				// Fragment extension header.
-				//
-				// More = 1, Fragment Offset = 0, ID = 2147746305
-				uint8(IPv6RoutingExtHdrIdentifier), 0, 0, 1, 128, 4, 2, 1,
-
-				// Routing extension header.
-				255, 0, 1, 2, 3, 4, 5, 6,
-
-				// Upper layer data.
-				1, 2, 3, 4,
-			}),
-			expected: []IPv6PayloadHeader{
-				IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}},
-				IPv6FragmentExtHdr([6]byte{0, 1, 128, 4, 2, 1}),
-				IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}),
-				IPv6RawPayloadHeader{
-					Identifier: 255,
-					Buf:        upperLayerData.ToVectorisedView(),
-				},
-			},
-		},
-		{
-			name:         "fragment - routing - upper (across views)",
-			firstNextHdr: IPv6FragmentExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Fragment extension header.
-				uint8(IPv6RoutingExtHdrIdentifier), 0, 68, 9, 128, 4, 2, 1,
-
-				// Routing extension header.
-				255, 0, 1, 2}, []byte{3, 4, 5, 6,
-
-				// Upper layer data.
-				1, 2, 3, 4,
-			}),
-			expected: []IPv6PayloadHeader{
-				IPv6FragmentExtHdr([6]byte{68, 9, 128, 4, 2, 1}),
-				IPv6RawPayloadHeader{
-					Identifier: IPv6RoutingExtHdrIdentifier,
-					Buf:        routingExtHdrWithUpperLayerData.ToVectorisedView(),
-				},
-			},
-		},
-
-		// If we have an atomic fragment, the payload following the fragment
-		// extension header should be parsed normally.
-		{
-			name:         "atomic fragment - routing - destination - upper",
-			firstNextHdr: IPv6FragmentExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Fragment extension header.
-				//
-				// Reserved bits are 1 which should not affect anything.
-				uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6, 128, 4, 2, 1,
-
-				// Routing extension header.
-				uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6,
-
-				// Destination Options extension header.
-				255, 0, 1, 4, 1, 2, 3, 4,
-
-				// Upper layer data.
-				1, 2, 3, 4,
-			}),
-			expected: []IPv6PayloadHeader{
-				IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}),
-				IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}),
-				IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}},
-				IPv6RawPayloadHeader{
-					Identifier: 255,
-					Buf:        upperLayerData.ToVectorisedView(),
-				},
-			},
-		},
-		{
-			name:         "atomic fragment - routing - upper (across views)",
-			firstNextHdr: IPv6FragmentExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Fragment extension header.
-				//
-				// Reserved bits are 1 which should not affect anything.
-				uint8(IPv6RoutingExtHdrIdentifier), 255, 0, 6}, []byte{128, 4, 2, 1,
-
-				// Routing extension header.
-				255, 0, 1, 2}, []byte{3, 4, 5, 6,
-
-				// Upper layer data.
-				1, 2}, []byte{3, 4}),
-			expected: []IPv6PayloadHeader{
-				IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}),
-				IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}),
-				IPv6RawPayloadHeader{
-					Identifier: 255,
-					Buf:        makeVectorisedViewFromByteBuffers(upperLayerData[:2], upperLayerData[2:]),
-				},
-			},
-		},
-		{
-			name:         "atomic fragment - destination - no next header",
-			firstNextHdr: IPv6FragmentExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Fragment extension header.
-				//
-				// Res (Reserved) bits are 1 which should not affect anything.
-				uint8(IPv6DestinationOptionsExtHdrIdentifier), 0, 0, 6, 128, 4, 2, 1,
-
-				// Destination Options extension header.
-				uint8(IPv6NoNextHeaderIdentifier), 0, 1, 4, 1, 2, 3, 4,
-
-				// Random data.
-				1, 2, 3, 4,
-			}),
-			expected: []IPv6PayloadHeader{
-				IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}),
-				IPv6DestinationOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}},
-			},
-		},
-		{
-			name:         "routing - atomic fragment - no next header",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Routing extension header.
-				uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6,
-
-				// Fragment extension header.
-				//
-				// Reserved bits are 1 which should not affect anything.
-				uint8(IPv6NoNextHeaderIdentifier), 0, 0, 6, 128, 4, 2, 1,
-
-				// Random data.
-				1, 2, 3, 4,
-			}),
-			expected: []IPv6PayloadHeader{
-				IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}),
-				IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}),
-			},
-		},
-		{
-			name:         "routing - atomic fragment - no next header (across views)",
-			firstNextHdr: IPv6RoutingExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Routing extension header.
-				uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6,
-
-				// Fragment extension header.
-				//
-				// Reserved bits are 1 which should not affect anything.
-				uint8(IPv6NoNextHeaderIdentifier), 255, 0, 6}, []byte{128, 4, 2, 1,
-
-				// Random data.
-				1, 2, 3, 4,
-			}),
-			expected: []IPv6PayloadHeader{
-				IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}),
-				IPv6FragmentExtHdr([6]byte{0, 6, 128, 4, 2, 1}),
-			},
-		},
-		{
-			name:         "hopbyhop - routing - fragment - no next header",
-			firstNextHdr: IPv6HopByHopOptionsExtHdrIdentifier,
-			payload: makeVectorisedViewFromByteBuffers([]byte{
-				// Hop By Hop Options extension header.
-				uint8(IPv6RoutingExtHdrIdentifier), 0, 1, 4, 1, 2, 3, 4,
-
-				// Routing extension header.
-				uint8(IPv6FragmentExtHdrIdentifier), 0, 1, 2, 3, 4, 5, 6,
-
-				// Fragment extension header.
-				//
-				// Fragment Offset = 32; Res = 6.
-				uint8(IPv6NoNextHeaderIdentifier), 0, 1, 6, 128, 4, 2, 1,
-
-				// Random data.
-				1, 2, 3, 4,
-			}),
-			expected: []IPv6PayloadHeader{
-				IPv6HopByHopOptionsExtHdr{ipv6OptionsExtHdr: []byte{1, 4, 1, 2, 3, 4}},
-				IPv6RoutingExtHdr([]byte{1, 2, 3, 4, 5, 6}),
-				IPv6FragmentExtHdr([6]byte{1, 6, 128, 4, 2, 1}),
-				IPv6RawPayloadHeader{
-					Identifier: IPv6NoNextHeaderIdentifier,
-					Buf:        upperLayerData.ToVectorisedView(),
-				},
-			},
-		},
-
-		// Test the raw payload for common transport layer protocol numbers.
-		{
-			name:         "TCP raw payload",
-			firstNextHdr: IPv6ExtensionHeaderIdentifier(TCPProtocolNumber),
-			payload:      makeVectorisedViewFromByteBuffers(upperLayerData),
-			expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{
-				Identifier: IPv6ExtensionHeaderIdentifier(TCPProtocolNumber),
-				Buf:        upperLayerData.ToVectorisedView(),
-			}},
-		},
-		{
-			name:         "UDP raw payload",
-			firstNextHdr: IPv6ExtensionHeaderIdentifier(UDPProtocolNumber),
-			payload:      makeVectorisedViewFromByteBuffers(upperLayerData),
-			expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{
-				Identifier: IPv6ExtensionHeaderIdentifier(UDPProtocolNumber),
-				Buf:        upperLayerData.ToVectorisedView(),
-			}},
-		},
-		{
-			name:         "ICMPv4 raw payload",
-			firstNextHdr: IPv6ExtensionHeaderIdentifier(ICMPv4ProtocolNumber),
-			payload:      makeVectorisedViewFromByteBuffers(upperLayerData),
-			expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{
-				Identifier: IPv6ExtensionHeaderIdentifier(ICMPv4ProtocolNumber),
-				Buf:        upperLayerData.ToVectorisedView(),
-			}},
-		},
-		{
-			name:         "ICMPv6 raw payload",
-			firstNextHdr: IPv6ExtensionHeaderIdentifier(ICMPv6ProtocolNumber),
-			payload:      makeVectorisedViewFromByteBuffers(upperLayerData),
-			expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{
-				Identifier: IPv6ExtensionHeaderIdentifier(ICMPv6ProtocolNumber),
-				Buf:        upperLayerData.ToVectorisedView(),
-			}},
-		},
-		{
-			name:         "Unknwon next header raw payload",
-			firstNextHdr: 255,
-			payload:      makeVectorisedViewFromByteBuffers(upperLayerData),
-			expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{
-				Identifier: 255,
-				Buf:        upperLayerData.ToVectorisedView(),
-			}},
-		},
-		{
-			name:         "Unknwon next header raw payload (across views)",
-			firstNextHdr: 255,
-			payload:      makeVectorisedViewFromByteBuffers(upperLayerData[:2], upperLayerData[2:]),
-			expected: []IPv6PayloadHeader{IPv6RawPayloadHeader{
-				Identifier: 255,
-				Buf:        makeVectorisedViewFromByteBuffers(upperLayerData[:2], upperLayerData[2:]),
-			}},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			it := MakeIPv6PayloadIterator(test.firstNextHdr, test.payload)
-
-			for i, e := range test.expected {
-				extHdr, done, err := it.Next()
-				if err != nil {
-					t.Errorf("(i=%d) Next(): %s", i, err)
-				}
-				if done {
-					t.Errorf("(i=%d) unexpectedly done iterating", i)
-				}
-				if diff := cmp.Diff(e, extHdr); diff != "" {
-					t.Errorf("(i=%d) got ext hdr mismatch (-want +got):\n%s", i, diff)
-				}
-
-				if t.Failed() {
-					t.FailNow()
-				}
-			}
-
-			extHdr, done, err := it.Next()
-			if err != nil {
-				t.Errorf("(last) Next(): %s", err)
-			}
-			if !done {
-				t.Errorf("(last) iterator unexpectedly not done")
-			}
-			if extHdr != nil {
-				t.Errorf("(last) got Next() = %T, want = nil", extHdr)
-			}
-		})
-	}
-}
-
-var _ IPv6SerializableHopByHopOption = (*dummyHbHOptionSerializer)(nil)
-
-// dummyHbHOptionSerializer provides a generic implementation of
-// IPv6SerializableHopByHopOption for use in tests.
-type dummyHbHOptionSerializer struct {
-	id          IPv6ExtHdrOptionIdentifier
-	payload     []byte
-	align       int
-	alignOffset int
-}
-
-// identifier implements IPv6SerializableHopByHopOption.
-func (s *dummyHbHOptionSerializer) identifier() IPv6ExtHdrOptionIdentifier {
-	return s.id
-}
-
-// length implements IPv6SerializableHopByHopOption.
-func (s *dummyHbHOptionSerializer) length() uint8 {
-	return uint8(len(s.payload))
-}
-
-// alignment implements IPv6SerializableHopByHopOption.
-func (s *dummyHbHOptionSerializer) alignment() (int, int) {
-	align := 1
-	if s.align != 0 {
-		align = s.align
-	}
-	return align, s.alignOffset
-}
-
-// serializeInto implements IPv6SerializableHopByHopOption.
-func (s *dummyHbHOptionSerializer) serializeInto(b []byte) uint8 {
-	return uint8(copy(b, s.payload))
-}
-
-func TestIPv6HopByHopSerializer(t *testing.T) {
-	validateDummies := func(t *testing.T, serializable IPv6SerializableHopByHopOption, deserialized IPv6ExtHdrOption) {
-		t.Helper()
-		dummy, ok := serializable.(*dummyHbHOptionSerializer)
-		if !ok {
-			t.Fatalf("got serializable = %T, want = *dummyHbHOptionSerializer", serializable)
-		}
-		unknown, ok := deserialized.(*IPv6UnknownExtHdrOption)
-		if !ok {
-			t.Fatalf("got deserialized = %T, want = %T", deserialized, &IPv6UnknownExtHdrOption{})
-		}
-		if dummy.id != unknown.Identifier {
-			t.Errorf("got deserialized identifier = %d, want = %d", unknown.Identifier, dummy.id)
-		}
-		if diff := cmp.Diff(dummy.payload, unknown.Data); diff != "" {
-			t.Errorf("option payload deserialization mismatch (-want +got):\n%s", diff)
-		}
-	}
-	tests := []struct {
-		name       string
-		nextHeader uint8
-		options    []IPv6SerializableHopByHopOption
-		expect     []byte
-		validate   func(*testing.T, IPv6SerializableHopByHopOption, IPv6ExtHdrOption)
-	}{
-		{
-			name:       "single option",
-			nextHeader: 13,
-			options: []IPv6SerializableHopByHopOption{
-				&dummyHbHOptionSerializer{
-					id:      15,
-					payload: []byte{9, 8, 7, 6},
-				},
-			},
-			expect:   []byte{13, 0, 15, 4, 9, 8, 7, 6},
-			validate: validateDummies,
-		},
-		{
-			name:       "short option padN zero",
-			nextHeader: 88,
-			options: []IPv6SerializableHopByHopOption{
-				&dummyHbHOptionSerializer{
-					id:      22,
-					payload: []byte{4, 5},
-				},
-			},
-			expect:   []byte{88, 0, 22, 2, 4, 5, 1, 0},
-			validate: validateDummies,
-		},
-		{
-			name:       "short option pad1",
-			nextHeader: 11,
-			options: []IPv6SerializableHopByHopOption{
-				&dummyHbHOptionSerializer{
-					id:      33,
-					payload: []byte{1, 2, 3},
-				},
-			},
-			expect:   []byte{11, 0, 33, 3, 1, 2, 3, 0},
-			validate: validateDummies,
-		},
-		{
-			name:       "long option padN",
-			nextHeader: 55,
-			options: []IPv6SerializableHopByHopOption{
-				&dummyHbHOptionSerializer{
-					id:      77,
-					payload: []byte{1, 2, 3, 4, 5, 6, 7, 8},
-				},
-			},
-			expect:   []byte{55, 1, 77, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 0, 0},
-			validate: validateDummies,
-		},
-		{
-			name:       "two options",
-			nextHeader: 33,
-			options: []IPv6SerializableHopByHopOption{
-				&dummyHbHOptionSerializer{
-					id:      11,
-					payload: []byte{1, 2, 3},
-				},
-				&dummyHbHOptionSerializer{
-					id:      22,
-					payload: []byte{4, 5, 6},
-				},
-			},
-			expect:   []byte{33, 1, 11, 3, 1, 2, 3, 22, 3, 4, 5, 6, 1, 2, 0, 0},
-			validate: validateDummies,
-		},
-		{
-			name:       "two options align 2n",
-			nextHeader: 33,
-			options: []IPv6SerializableHopByHopOption{
-				&dummyHbHOptionSerializer{
-					id:      11,
-					payload: []byte{1, 2, 3},
-				},
-				&dummyHbHOptionSerializer{
-					id:      22,
-					payload: []byte{4, 5, 6},
-					align:   2,
-				},
-			},
-			expect:   []byte{33, 1, 11, 3, 1, 2, 3, 0, 22, 3, 4, 5, 6, 1, 1, 0},
-			validate: validateDummies,
-		},
-		{
-			name:       "two options align 8n+1",
-			nextHeader: 33,
-			options: []IPv6SerializableHopByHopOption{
-				&dummyHbHOptionSerializer{
-					id:      11,
-					payload: []byte{1, 2},
-				},
-				&dummyHbHOptionSerializer{
-					id:          22,
-					payload:     []byte{4, 5, 6},
-					align:       8,
-					alignOffset: 1,
-				},
-			},
-			expect:   []byte{33, 1, 11, 2, 1, 2, 1, 1, 0, 22, 3, 4, 5, 6, 1, 0},
-			validate: validateDummies,
-		},
-		{
-			name:       "no options",
-			nextHeader: 33,
-			options:    []IPv6SerializableHopByHopOption{},
-			expect:     []byte{33, 0, 1, 4, 0, 0, 0, 0},
-		},
-		{
-			name:       "Router Alert",
-			nextHeader: 33,
-			options:    []IPv6SerializableHopByHopOption{&IPv6RouterAlertOption{Value: IPv6RouterAlertMLD}},
-			expect:     []byte{33, 0, 5, 2, 0, 0, 1, 0},
-			validate: func(t *testing.T, _ IPv6SerializableHopByHopOption, deserialized IPv6ExtHdrOption) {
-				t.Helper()
-				routerAlert, ok := deserialized.(*IPv6RouterAlertOption)
-				if !ok {
-					t.Fatalf("got deserialized = %T, want = *IPv6RouterAlertOption", deserialized)
-				}
-				if routerAlert.Value != IPv6RouterAlertMLD {
-					t.Errorf("got routerAlert.Value = %d, want = %d", routerAlert.Value, IPv6RouterAlertMLD)
-				}
-			},
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := IPv6SerializableHopByHopExtHdr(test.options)
-			length := s.length()
-			if length != len(test.expect) {
-				t.Fatalf("got s.length() = %d, want = %d", length, len(test.expect))
-			}
-			b := make([]byte, length)
-			for i := range b {
-				// Fill the buffer with ones to ensure all padding is correctly set.
-				b[i] = 0xFF
-			}
-			if got := s.serializeInto(test.nextHeader, b); got != length {
-				t.Fatalf("got s.serializeInto(..) = %d, want = %d", got, length)
-			}
-			if diff := cmp.Diff(test.expect, b); diff != "" {
-				t.Fatalf("serialization mismatch (-want +got):\n%s", diff)
-			}
-
-			// Deserialize the options and verify them.
-			optLen := (b[ipv6HopByHopExtHdrLengthOffset] + ipv6HopByHopExtHdrUnaccountedLenWords) * ipv6ExtHdrLenBytesPerUnit
-			iter := ipv6OptionsExtHdr(b[ipv6HopByHopExtHdrOptionsOffset:optLen]).Iter()
-			for _, testOpt := range test.options {
-				opt, done, err := iter.Next()
-				if err != nil {
-					t.Fatalf("iter.Next(): %s", err)
-				}
-				if done {
-					t.Fatalf("got iter.Next() = (%T, %t, _), want = (_, false, _)", opt, done)
-				}
-				test.validate(t, testOpt, opt)
-			}
-			opt, done, err := iter.Next()
-			if err != nil {
-				t.Fatalf("iter.Next(): %s", err)
-			}
-			if !done {
-				t.Fatalf("got iter.Next() = (%T, %t, _), want = (_, true, _)", opt, done)
-			}
-		})
-	}
-}
-
-var _ IPv6SerializableExtHdr = (*dummyIPv6ExtHdrSerializer)(nil)
-
-// dummyIPv6ExtHdrSerializer provides a generic implementation of
-// IPv6SerializableExtHdr for use in tests.
-//
-// The dummy header always carries the nextHeader value in the first byte.
-type dummyIPv6ExtHdrSerializer struct {
-	id             IPv6ExtensionHeaderIdentifier
-	headerContents []byte
-}
-
-// identifier implements IPv6SerializableExtHdr.
-func (s *dummyIPv6ExtHdrSerializer) identifier() IPv6ExtensionHeaderIdentifier {
-	return s.id
-}
-
-// length implements IPv6SerializableExtHdr.
-func (s *dummyIPv6ExtHdrSerializer) length() int {
-	return len(s.headerContents) + 1
-}
-
-// serializeInto implements IPv6SerializableExtHdr.
-func (s *dummyIPv6ExtHdrSerializer) serializeInto(nextHeader uint8, b []byte) int {
-	b[0] = nextHeader
-	return copy(b[1:], s.headerContents) + 1
-}
-
-func TestIPv6ExtHdrSerializer(t *testing.T) {
-	tests := []struct {
-		name             string
-		headers          []IPv6SerializableExtHdr
-		nextHeader       tcpip.TransportProtocolNumber
-		expectSerialized []byte
-		expectNextHeader uint8
-	}{
-		{
-			name: "one header",
-			headers: []IPv6SerializableExtHdr{
-				&dummyIPv6ExtHdrSerializer{
-					id:             15,
-					headerContents: []byte{1, 2, 3, 4},
-				},
-			},
-			nextHeader:       TCPProtocolNumber,
-			expectSerialized: []byte{byte(TCPProtocolNumber), 1, 2, 3, 4},
-			expectNextHeader: 15,
-		},
-		{
-			name: "two headers",
-			headers: []IPv6SerializableExtHdr{
-				&dummyIPv6ExtHdrSerializer{
-					id:             22,
-					headerContents: []byte{1, 2, 3},
-				},
-				&dummyIPv6ExtHdrSerializer{
-					id:             23,
-					headerContents: []byte{4, 5, 6},
-				},
-			},
-			nextHeader: ICMPv6ProtocolNumber,
-			expectSerialized: []byte{
-				23, 1, 2, 3,
-				byte(ICMPv6ProtocolNumber), 4, 5, 6,
-			},
-			expectNextHeader: 22,
-		},
-		{
-			name:             "no headers",
-			headers:          []IPv6SerializableExtHdr{},
-			nextHeader:       UDPProtocolNumber,
-			expectSerialized: []byte{},
-			expectNextHeader: byte(UDPProtocolNumber),
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := IPv6ExtHdrSerializer(test.headers)
-			l := s.Length()
-			if got, want := l, len(test.expectSerialized); got != want {
-				t.Fatalf("got serialized length = %d, want = %d", got, want)
-			}
-			b := make([]byte, l)
-			for i := range b {
-				// Fill the buffer with garbage to make sure we're writing to all bytes.
-				b[i] = 0xFF
-			}
-			nextHeader, serializedLen := s.Serialize(test.nextHeader, b)
-			if serializedLen != len(test.expectSerialized) || nextHeader != test.expectNextHeader {
-				t.Errorf(
-					"got s.Serialize(..) = (%d, %d), want = (%d, %d)",
-					nextHeader,
-					serializedLen,
-					test.expectNextHeader,
-					len(test.expectSerialized),
-				)
-			}
-			if diff := cmp.Diff(test.expectSerialized, b); diff != "" {
-				t.Errorf("serialization mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/header/ipv6_test.go b/pkg/tcpip/header/ipv6_test.go
deleted file mode 100644
index f10f446..0000000
--- a/pkg/tcpip/header/ipv6_test.go
+++ /dev/null
@@ -1,375 +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 header_test
-
-import (
-	"bytes"
-	"crypto/sha256"
-	"fmt"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/rand"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-const (
-	linkAddr               = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
-	linkLocalAddr          = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-	linkLocalMulticastAddr = tcpip.Address("\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-	uniqueLocalAddr1       = tcpip.Address("\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-	uniqueLocalAddr2       = tcpip.Address("\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-	globalAddr             = tcpip.Address("\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-)
-
-func TestEthernetAdddressToModifiedEUI64(t *testing.T) {
-	expectedIID := [header.IIDSize]byte{0, 2, 3, 255, 254, 4, 5, 6}
-
-	if diff := cmp.Diff(expectedIID, header.EthernetAddressToModifiedEUI64(linkAddr)); diff != "" {
-		t.Errorf("EthernetAddressToModifiedEUI64(%s) mismatch (-want +got):\n%s", linkAddr, diff)
-	}
-
-	var buf [header.IIDSize]byte
-	header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, buf[:])
-	if diff := cmp.Diff(expectedIID, buf); diff != "" {
-		t.Errorf("EthernetAddressToModifiedEUI64IntoBuf(%s, _) mismatch (-want +got):\n%s", linkAddr, diff)
-	}
-}
-
-func TestLinkLocalAddr(t *testing.T) {
-	if got, want := header.LinkLocalAddr(linkAddr), tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x02\x03\xff\xfe\x04\x05\x06"); got != want {
-		t.Errorf("got LinkLocalAddr(%s) = %s, want = %s", linkAddr, got, want)
-	}
-}
-
-func TestAppendOpaqueInterfaceIdentifier(t *testing.T) {
-	var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes * 2]byte
-	if n, err := rand.Read(secretKeyBuf[:]); err != nil {
-		t.Fatalf("rand.Read(_): %s", err)
-	} else if want := header.OpaqueIIDSecretKeyMinBytes * 2; n != want {
-		t.Fatalf("expected rand.Read to read %d bytes, read %d bytes", want, n)
-	}
-
-	tests := []struct {
-		name       string
-		prefix     tcpip.Subnet
-		nicName    string
-		dadCounter uint8
-		secretKey  []byte
-	}{
-		{
-			name:       "SecretKey of minimum size",
-			prefix:     header.IPv6LinkLocalPrefix.Subnet(),
-			nicName:    "eth0",
-			dadCounter: 0,
-			secretKey:  secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes],
-		},
-		{
-			name: "SecretKey of less than minimum size",
-			prefix: func() tcpip.Subnet {
-				addrWithPrefix := tcpip.AddressWithPrefix{
-					Address:   "\x01\x02\x03\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
-					PrefixLen: header.IIDOffsetInIPv6Address * 8,
-				}
-				return addrWithPrefix.Subnet()
-			}(),
-			nicName:    "eth10",
-			dadCounter: 1,
-			secretKey:  secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes/2],
-		},
-		{
-			name: "SecretKey of more than minimum size",
-			prefix: func() tcpip.Subnet {
-				addrWithPrefix := tcpip.AddressWithPrefix{
-					Address:   "\x01\x02\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
-					PrefixLen: header.IIDOffsetInIPv6Address * 8,
-				}
-				return addrWithPrefix.Subnet()
-			}(),
-			nicName:    "eth11",
-			dadCounter: 2,
-			secretKey:  secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes*2],
-		},
-		{
-			name: "Nil SecretKey and empty nicName",
-			prefix: func() tcpip.Subnet {
-				addrWithPrefix := tcpip.AddressWithPrefix{
-					Address:   "\x01\x02\x03\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
-					PrefixLen: header.IIDOffsetInIPv6Address * 8,
-				}
-				return addrWithPrefix.Subnet()
-			}(),
-			nicName:    "",
-			dadCounter: 3,
-			secretKey:  nil,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			h := sha256.New()
-			h.Write([]byte(test.prefix.ID()[:header.IIDOffsetInIPv6Address]))
-			h.Write([]byte(test.nicName))
-			h.Write([]byte{test.dadCounter})
-			if k := test.secretKey; k != nil {
-				h.Write(k)
-			}
-			var hashSum [sha256.Size]byte
-			h.Sum(hashSum[:0])
-			want := hashSum[:header.IIDSize]
-
-			// Passing a nil buffer should result in a new buffer returned with the
-			// IID.
-			if got := header.AppendOpaqueInterfaceIdentifier(nil, test.prefix, test.nicName, test.dadCounter, test.secretKey); !bytes.Equal(got, want) {
-				t.Errorf("got AppendOpaqueInterfaceIdentifier(nil, %s, %s, %d, %x) = %x, want = %x", test.prefix, test.nicName, test.dadCounter, test.secretKey, got, want)
-			}
-
-			// Passing a buffer with sufficient capacity for the IID should populate
-			// the buffer provided.
-			var iidBuf [header.IIDSize]byte
-			if got := header.AppendOpaqueInterfaceIdentifier(iidBuf[:0], test.prefix, test.nicName, test.dadCounter, test.secretKey); !bytes.Equal(got, want) {
-				t.Errorf("got AppendOpaqueInterfaceIdentifier(iidBuf[:0], %s, %s, %d, %x) = %x, want = %x", test.prefix, test.nicName, test.dadCounter, test.secretKey, got, want)
-			}
-			if got := iidBuf[:]; !bytes.Equal(got, want) {
-				t.Errorf("got iidBuf = %x, want = %x", got, want)
-			}
-		})
-	}
-}
-
-func TestLinkLocalAddrWithOpaqueIID(t *testing.T) {
-	var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes * 2]byte
-	if n, err := rand.Read(secretKeyBuf[:]); err != nil {
-		t.Fatalf("rand.Read(_): %s", err)
-	} else if want := header.OpaqueIIDSecretKeyMinBytes * 2; n != want {
-		t.Fatalf("expected rand.Read to read %d bytes, read %d bytes", want, n)
-	}
-
-	prefix := header.IPv6LinkLocalPrefix.Subnet()
-
-	tests := []struct {
-		name       string
-		prefix     tcpip.Subnet
-		nicName    string
-		dadCounter uint8
-		secretKey  []byte
-	}{
-		{
-			name:       "SecretKey of minimum size",
-			nicName:    "eth0",
-			dadCounter: 0,
-			secretKey:  secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes],
-		},
-		{
-			name:       "SecretKey of less than minimum size",
-			nicName:    "eth10",
-			dadCounter: 1,
-			secretKey:  secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes/2],
-		},
-		{
-			name:       "SecretKey of more than minimum size",
-			nicName:    "eth11",
-			dadCounter: 2,
-			secretKey:  secretKeyBuf[:header.OpaqueIIDSecretKeyMinBytes*2],
-		},
-		{
-			name:       "Nil SecretKey and empty nicName",
-			nicName:    "",
-			dadCounter: 3,
-			secretKey:  nil,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			addrBytes := [header.IPv6AddressSize]byte{
-				0: 0xFE,
-				1: 0x80,
-			}
-
-			want := tcpip.Address(header.AppendOpaqueInterfaceIdentifier(
-				addrBytes[:header.IIDOffsetInIPv6Address],
-				prefix,
-				test.nicName,
-				test.dadCounter,
-				test.secretKey,
-			))
-
-			if got := header.LinkLocalAddrWithOpaqueIID(test.nicName, test.dadCounter, test.secretKey); got != want {
-				t.Errorf("got LinkLocalAddrWithOpaqueIID(%s, %d, %x) = %s, want = %s", test.nicName, test.dadCounter, test.secretKey, got, want)
-			}
-		})
-	}
-}
-
-func TestIsV6LinkLocalMulticastAddress(t *testing.T) {
-	tests := []struct {
-		name     string
-		addr     tcpip.Address
-		expected bool
-	}{
-		{
-			name:     "Valid Link Local Multicast",
-			addr:     linkLocalMulticastAddr,
-			expected: true,
-		},
-		{
-			name:     "Valid Link Local Multicast with flags",
-			addr:     "\xff\xf2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
-			expected: true,
-		},
-		{
-			name:     "Link Local Unicast",
-			addr:     linkLocalAddr,
-			expected: false,
-		},
-		{
-			name:     "IPv4 Multicast",
-			addr:     "\xe0\x00\x00\x01",
-			expected: false,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			if got := header.IsV6LinkLocalMulticastAddress(test.addr); got != test.expected {
-				t.Errorf("got header.IsV6LinkLocalMulticastAddress(%s) = %t, want = %t", test.addr, got, test.expected)
-			}
-		})
-	}
-}
-
-func TestIsV6LinkLocalAddress(t *testing.T) {
-	tests := []struct {
-		name     string
-		addr     tcpip.Address
-		expected bool
-	}{
-		{
-			name:     "Valid Link Local Unicast",
-			addr:     linkLocalAddr,
-			expected: true,
-		},
-		{
-			name:     "Link Local Multicast",
-			addr:     linkLocalMulticastAddr,
-			expected: false,
-		},
-		{
-			name:     "Unique Local",
-			addr:     uniqueLocalAddr1,
-			expected: false,
-		},
-		{
-			name:     "Global",
-			addr:     globalAddr,
-			expected: false,
-		},
-		{
-			name:     "IPv4 Link Local",
-			addr:     "\xa9\xfe\x00\x01",
-			expected: false,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			if got := header.IsV6LinkLocalAddress(test.addr); got != test.expected {
-				t.Errorf("got header.IsV6LinkLocalAddress(%s) = %t, want = %t", test.addr, got, test.expected)
-			}
-		})
-	}
-}
-
-func TestScopeForIPv6Address(t *testing.T) {
-	tests := []struct {
-		name  string
-		addr  tcpip.Address
-		scope header.IPv6AddressScope
-		err   tcpip.Error
-	}{
-		{
-			name:  "Unique Local",
-			addr:  uniqueLocalAddr1,
-			scope: header.GlobalScope,
-			err:   nil,
-		},
-		{
-			name:  "Link Local Unicast",
-			addr:  linkLocalAddr,
-			scope: header.LinkLocalScope,
-			err:   nil,
-		},
-		{
-			name:  "Link Local Multicast",
-			addr:  linkLocalMulticastAddr,
-			scope: header.LinkLocalScope,
-			err:   nil,
-		},
-		{
-			name:  "Global",
-			addr:  globalAddr,
-			scope: header.GlobalScope,
-			err:   nil,
-		},
-		{
-			name:  "IPv4",
-			addr:  "\x01\x02\x03\x04",
-			scope: header.GlobalScope,
-			err:   &tcpip.ErrBadAddress{},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			got, err := header.ScopeForIPv6Address(test.addr)
-			if diff := cmp.Diff(test.err, err); diff != "" {
-				t.Errorf("unexpected error from header.IsV6UniqueLocalAddress(%s), (-want, +got):\n%s", test.addr, diff)
-			}
-			if got != test.scope {
-				t.Errorf("got header.IsV6UniqueLocalAddress(%s) = (%d, _), want = (%d, _)", test.addr, got, test.scope)
-			}
-		})
-	}
-}
-
-func TestSolicitedNodeAddr(t *testing.T) {
-	tests := []struct {
-		addr tcpip.Address
-		want tcpip.Address
-	}{
-		{
-			addr: "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\xa0",
-			want: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x0e\x0f\xa0",
-		},
-		{
-			addr: "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\xdd\x0e\x0f\xa0",
-			want: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x0e\x0f\xa0",
-		},
-		{
-			addr: "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\xdd\x01\x02\x03",
-			want: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x01\x02\x03",
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(fmt.Sprintf("%s", test.addr), func(t *testing.T) {
-			if got := header.SolicitedNodeAddr(test.addr); got != test.want {
-				t.Fatalf("got header.SolicitedNodeAddr(%s) = %s, want = %s", test.addr, got, test.want)
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/header/ipversion_test.go b/pkg/tcpip/header/ipversion_test.go
deleted file mode 100644
index b5540bf..0000000
--- a/pkg/tcpip/header/ipversion_test.go
+++ /dev/null
@@ -1,67 +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 header_test
-
-import (
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-func TestIPv4(t *testing.T) {
-	b := header.IPv4(make([]byte, header.IPv4MinimumSize))
-	b.Encode(&header.IPv4Fields{})
-
-	const want = header.IPv4Version
-	if v := header.IPVersion(b); v != want {
-		t.Fatalf("Bad version, want %v, got %v", want, v)
-	}
-}
-
-func TestIPv6(t *testing.T) {
-	b := header.IPv6(make([]byte, header.IPv6MinimumSize))
-	b.Encode(&header.IPv6Fields{})
-
-	const want = header.IPv6Version
-	if v := header.IPVersion(b); v != want {
-		t.Fatalf("Bad version, want %v, got %v", want, v)
-	}
-}
-
-func TestOtherVersion(t *testing.T) {
-	const want = header.IPv4Version + header.IPv6Version
-	b := make([]byte, 1)
-	b[0] = want << 4
-
-	if v := header.IPVersion(b); v != want {
-		t.Fatalf("Bad version, want %v, got %v", want, v)
-	}
-}
-
-func TestTooShort(t *testing.T) {
-	b := make([]byte, 1)
-	b[0] = (header.IPv4Version + header.IPv6Version) << 4
-
-	// Get the version of a zero-length slice.
-	const want = -1
-	if v := header.IPVersion(b[:0]); v != want {
-		t.Fatalf("Bad version, want %v, got %v", want, v)
-	}
-
-	// Get the version of a nil slice.
-	if v := header.IPVersion(nil); v != want {
-		t.Fatalf("Bad version, want %v, got %v", want, v)
-	}
-}
diff --git a/pkg/tcpip/header/mld_test.go b/pkg/tcpip/header/mld_test.go
deleted file mode 100644
index 0cecf10..0000000
--- a/pkg/tcpip/header/mld_test.go
+++ /dev/null
@@ -1,61 +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 header
-
-import (
-	"encoding/binary"
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-)
-
-func TestMLD(t *testing.T) {
-	b := []byte{
-		// Maximum Response Delay
-		0, 0,
-
-		// Reserved
-		0, 0,
-
-		// MulticastAddress
-		1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6,
-	}
-
-	const maxRespDelay = 513
-	binary.BigEndian.PutUint16(b, maxRespDelay)
-
-	mld := MLD(b)
-
-	if got, want := mld.MaximumResponseDelay(), maxRespDelay*time.Millisecond; got != want {
-		t.Errorf("got mld.MaximumResponseDelay() = %s, want = %s", got, want)
-	}
-
-	const newMaxRespDelay = 1234
-	mld.SetMaximumResponseDelay(newMaxRespDelay)
-	if got, want := mld.MaximumResponseDelay(), newMaxRespDelay*time.Millisecond; got != want {
-		t.Errorf("got mld.MaximumResponseDelay() = %s, want = %s", got, want)
-	}
-
-	if got, want := mld.MulticastAddress(), tcpip.Address([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}); got != want {
-		t.Errorf("got mld.MulticastAddress() = %s, want = %s", got, want)
-	}
-
-	multicastAddress := tcpip.Address([]byte{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0})
-	mld.SetMulticastAddress(multicastAddress)
-	if got := mld.MulticastAddress(); got != multicastAddress {
-		t.Errorf("got mld.MulticastAddress() = %s, want = %s", got, multicastAddress)
-	}
-}
diff --git a/pkg/tcpip/header/ndp_test.go b/pkg/tcpip/header/ndp_test.go
deleted file mode 100644
index d0a1a24..0000000
--- a/pkg/tcpip/header/ndp_test.go
+++ /dev/null
@@ -1,1452 +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 header
-
-import (
-	"bytes"
-	"encoding/binary"
-	"errors"
-	"fmt"
-	"io"
-	"regexp"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/tcpip"
-)
-
-// TestNDPNeighborSolicit tests the functions of NDPNeighborSolicit.
-func TestNDPNeighborSolicit(t *testing.T) {
-	b := []byte{
-		0, 0, 0, 0,
-		1, 2, 3, 4,
-		5, 6, 7, 8,
-		9, 10, 11, 12,
-		13, 14, 15, 16,
-	}
-
-	// Test getting the Target Address.
-	ns := NDPNeighborSolicit(b)
-	addr := tcpip.Address("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10")
-	if got := ns.TargetAddress(); got != addr {
-		t.Errorf("got ns.TargetAddress = %s, want %s", got, addr)
-	}
-
-	// Test updating the Target Address.
-	addr2 := tcpip.Address("\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x11")
-	ns.SetTargetAddress(addr2)
-	if got := ns.TargetAddress(); got != addr2 {
-		t.Errorf("got ns.TargetAddress = %s, want %s", got, addr2)
-	}
-	// Make sure the address got updated in the backing buffer.
-	if got := tcpip.Address(b[ndpNSTargetAddessOffset:][:IPv6AddressSize]); got != addr2 {
-		t.Errorf("got targetaddress buffer = %s, want %s", got, addr2)
-	}
-}
-
-// TestNDPNeighborAdvert tests the functions of NDPNeighborAdvert.
-func TestNDPNeighborAdvert(t *testing.T) {
-	b := []byte{
-		160, 0, 0, 0,
-		1, 2, 3, 4,
-		5, 6, 7, 8,
-		9, 10, 11, 12,
-		13, 14, 15, 16,
-	}
-
-	// Test getting the Target Address.
-	na := NDPNeighborAdvert(b)
-	addr := tcpip.Address("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10")
-	if got := na.TargetAddress(); got != addr {
-		t.Errorf("got TargetAddress = %s, want %s", got, addr)
-	}
-
-	// Test getting the Router Flag.
-	if got := na.RouterFlag(); !got {
-		t.Errorf("got RouterFlag = false, want = true")
-	}
-
-	// Test getting the Solicited Flag.
-	if got := na.SolicitedFlag(); got {
-		t.Errorf("got SolicitedFlag = true, want = false")
-	}
-
-	// Test getting the Override Flag.
-	if got := na.OverrideFlag(); !got {
-		t.Errorf("got OverrideFlag = false, want = true")
-	}
-
-	// Test updating the Target Address.
-	addr2 := tcpip.Address("\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x11")
-	na.SetTargetAddress(addr2)
-	if got := na.TargetAddress(); got != addr2 {
-		t.Errorf("got TargetAddress = %s, want %s", got, addr2)
-	}
-	// Make sure the address got updated in the backing buffer.
-	if got := tcpip.Address(b[ndpNATargetAddressOffset:][:IPv6AddressSize]); got != addr2 {
-		t.Errorf("got targetaddress buffer = %s, want %s", got, addr2)
-	}
-
-	// Test updating the Router Flag.
-	na.SetRouterFlag(false)
-	if got := na.RouterFlag(); got {
-		t.Errorf("got RouterFlag = true, want = false")
-	}
-
-	// Test updating the Solicited Flag.
-	na.SetSolicitedFlag(true)
-	if got := na.SolicitedFlag(); !got {
-		t.Errorf("got SolicitedFlag = false, want = true")
-	}
-
-	// Test updating the Override Flag.
-	na.SetOverrideFlag(false)
-	if got := na.OverrideFlag(); got {
-		t.Errorf("got OverrideFlag = true, want = false")
-	}
-
-	// Make sure flags got updated in the backing buffer.
-	if got := b[ndpNAFlagsOffset]; got != 64 {
-		t.Errorf("got flags byte = %d, want = 64", got)
-	}
-}
-
-func TestNDPRouterAdvert(t *testing.T) {
-	b := []byte{
-		64, 128, 1, 2,
-		3, 4, 5, 6,
-		7, 8, 9, 10,
-	}
-
-	ra := NDPRouterAdvert(b)
-
-	if got := ra.CurrHopLimit(); got != 64 {
-		t.Errorf("got ra.CurrHopLimit = %d, want = 64", got)
-	}
-
-	if got := ra.ManagedAddrConfFlag(); !got {
-		t.Errorf("got ManagedAddrConfFlag = false, want = true")
-	}
-
-	if got := ra.OtherConfFlag(); got {
-		t.Errorf("got OtherConfFlag = true, want = false")
-	}
-
-	if got, want := ra.RouterLifetime(), time.Second*258; got != want {
-		t.Errorf("got ra.RouterLifetime = %d, want = %d", got, want)
-	}
-
-	if got, want := ra.ReachableTime(), time.Millisecond*50595078; got != want {
-		t.Errorf("got ra.ReachableTime = %d, want = %d", got, want)
-	}
-
-	if got, want := ra.RetransTimer(), time.Millisecond*117967114; got != want {
-		t.Errorf("got ra.RetransTimer = %d, want = %d", got, want)
-	}
-}
-
-// TestNDPSourceLinkLayerAddressOptionEthernetAddress tests getting the
-// Ethernet address from an NDPSourceLinkLayerAddressOption.
-func TestNDPSourceLinkLayerAddressOptionEthernetAddress(t *testing.T) {
-	tests := []struct {
-		name     string
-		buf      []byte
-		expected tcpip.LinkAddress
-	}{
-		{
-			"ValidMAC",
-			[]byte{1, 2, 3, 4, 5, 6},
-			tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
-		},
-		{
-			"SLLBodyTooShort",
-			[]byte{1, 2, 3, 4, 5},
-			tcpip.LinkAddress([]byte(nil)),
-		},
-		{
-			"SLLBodyLargerThanNeeded",
-			[]byte{1, 2, 3, 4, 5, 6, 7, 8},
-			tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			sll := NDPSourceLinkLayerAddressOption(test.buf)
-			if got := sll.EthernetAddress(); got != test.expected {
-				t.Errorf("got sll.EthernetAddress = %s, want = %s", got, test.expected)
-			}
-		})
-	}
-}
-
-// TestNDPTargetLinkLayerAddressOptionEthernetAddress tests getting the
-// Ethernet address from an NDPTargetLinkLayerAddressOption.
-func TestNDPTargetLinkLayerAddressOptionEthernetAddress(t *testing.T) {
-	tests := []struct {
-		name     string
-		buf      []byte
-		expected tcpip.LinkAddress
-	}{
-		{
-			"ValidMAC",
-			[]byte{1, 2, 3, 4, 5, 6},
-			tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
-		},
-		{
-			"TLLBodyTooShort",
-			[]byte{1, 2, 3, 4, 5},
-			tcpip.LinkAddress([]byte(nil)),
-		},
-		{
-			"TLLBodyLargerThanNeeded",
-			[]byte{1, 2, 3, 4, 5, 6, 7, 8},
-			tcpip.LinkAddress("\x01\x02\x03\x04\x05\x06"),
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			tll := NDPTargetLinkLayerAddressOption(test.buf)
-			if got := tll.EthernetAddress(); got != test.expected {
-				t.Errorf("got tll.EthernetAddress = %s, want = %s", got, test.expected)
-			}
-		})
-	}
-}
-
-func TestOpts(t *testing.T) {
-	const optionHeaderLen = 2
-
-	checkNonce := func(expectedNonce []byte) func(*testing.T, NDPOption) {
-		return func(t *testing.T, opt NDPOption) {
-			if got := opt.kind(); got != ndpNonceOptionType {
-				t.Errorf("got kind() = %d, want = %d", got, ndpNonceOptionType)
-			}
-			nonce, ok := opt.(NDPNonceOption)
-			if !ok {
-				t.Fatalf("got nonce = %T, want = NDPNonceOption", opt)
-			}
-			if diff := cmp.Diff(expectedNonce, nonce.Nonce()); diff != "" {
-				t.Errorf("nonce mismatch (-want +got):\n%s", diff)
-			}
-		}
-	}
-
-	checkTLL := func(expectedAddr tcpip.LinkAddress) func(*testing.T, NDPOption) {
-		return func(t *testing.T, opt NDPOption) {
-			if got := opt.kind(); got != ndpTargetLinkLayerAddressOptionType {
-				t.Errorf("got kind() = %d, want = %d", got, ndpTargetLinkLayerAddressOptionType)
-			}
-			tll, ok := opt.(NDPTargetLinkLayerAddressOption)
-			if !ok {
-				t.Fatalf("got tll = %T, want = NDPTargetLinkLayerAddressOption", opt)
-			}
-			if got, want := tll.EthernetAddress(), expectedAddr; got != want {
-				t.Errorf("got tll.EthernetAddress = %s, want = %s", got, want)
-			}
-		}
-	}
-
-	checkSLL := func(expectedAddr tcpip.LinkAddress) func(*testing.T, NDPOption) {
-		return func(t *testing.T, opt NDPOption) {
-			if got := opt.kind(); got != ndpSourceLinkLayerAddressOptionType {
-				t.Errorf("got kind() = %d, want = %d", got, ndpSourceLinkLayerAddressOptionType)
-			}
-			sll, ok := opt.(NDPSourceLinkLayerAddressOption)
-			if !ok {
-				t.Fatalf("got sll = %T, want = NDPSourceLinkLayerAddressOption", opt)
-			}
-			if got, want := sll.EthernetAddress(), expectedAddr; got != want {
-				t.Errorf("got sll.EthernetAddress = %s, want = %s", got, want)
-			}
-		}
-	}
-
-	const validLifetimeSeconds = 16909060
-	const address = tcpip.Address("\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18")
-
-	expectedRDNSSBytes := [...]byte{
-		// Type, Length
-		25, 3,
-
-		// Reserved
-		0, 0,
-
-		// Lifetime
-		1, 2, 4, 8,
-
-		// Address
-		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
-	}
-	binary.BigEndian.PutUint32(expectedRDNSSBytes[4:], validLifetimeSeconds)
-	if n := copy(expectedRDNSSBytes[8:], address); n != IPv6AddressSize {
-		t.Fatalf("got copy(...) = %d, want = %d", n, IPv6AddressSize)
-	}
-	// Update reserved fields to non zero values to make sure serializing sets
-	// them to zero.
-	rdnssBytes := expectedRDNSSBytes
-	rdnssBytes[1] = 1
-	rdnssBytes[2] = 2
-
-	const searchListPaddingBytes = 3
-	const domainName = "abc.abcd.e"
-	expectedSearchListBytes := [...]byte{
-		// Type, Length
-		31, 3,
-
-		// Reserved
-		0, 0,
-
-		// Lifetime
-		1, 0, 0, 0,
-
-		// Domain names
-		3, 'a', 'b', 'c',
-		4, 'a', 'b', 'c', 'd',
-		1, 'e',
-		0,
-		0, 0, 0, 0,
-	}
-	binary.BigEndian.PutUint32(expectedSearchListBytes[4:], validLifetimeSeconds)
-	// Update reserved fields to non zero values to make sure serializing sets
-	// them to zero.
-	searchListBytes := expectedSearchListBytes
-	searchListBytes[2] = 1
-	searchListBytes[3] = 2
-
-	const prefixLength = 43
-	const onLinkFlag = false
-	const slaacFlag = true
-	const preferredLifetimeSeconds = 84281096
-	const onLinkFlagBit = 7
-	const slaacFlagBit = 6
-	boolToByte := func(v bool) byte {
-		if v {
-			return 1
-		}
-		return 0
-	}
-	flags := boolToByte(onLinkFlag)<<onLinkFlagBit | boolToByte(slaacFlag)<<slaacFlagBit
-	expectedPrefixInformationBytes := [...]byte{
-		// Type, Length
-		3, 4,
-
-		prefixLength, flags,
-
-		// Valid Lifetime
-		1, 2, 3, 4,
-
-		// Preferred Lifetime
-		5, 6, 7, 8,
-
-		// Reserved2
-		0, 0, 0, 0,
-
-		// Address
-		9, 10, 11, 12,
-		13, 14, 15, 16,
-		17, 18, 19, 20,
-		21, 22, 23, 24,
-	}
-	binary.BigEndian.PutUint32(expectedPrefixInformationBytes[4:], validLifetimeSeconds)
-	binary.BigEndian.PutUint32(expectedPrefixInformationBytes[8:], preferredLifetimeSeconds)
-	if n := copy(expectedPrefixInformationBytes[16:], address); n != IPv6AddressSize {
-		t.Fatalf("got copy(...) = %d, want = %d", n, IPv6AddressSize)
-	}
-	// Update reserved fields to non zero values to make sure serializing sets
-	// them to zero.
-	prefixInformationBytes := expectedPrefixInformationBytes
-	prefixInformationBytes[3] |= (1 << slaacFlagBit) - 1
-	binary.BigEndian.PutUint32(prefixInformationBytes[12:], validLifetimeSeconds+1)
-	tests := []struct {
-		name        string
-		buf         []byte
-		opt         NDPOption
-		expectedBuf []byte
-		check       func(*testing.T, NDPOption)
-	}{
-		{
-			name:        "Nonce",
-			buf:         make([]byte, 8),
-			opt:         NDPNonceOption([]byte{1, 2, 3, 4, 5, 6}),
-			expectedBuf: []byte{14, 1, 1, 2, 3, 4, 5, 6},
-			check:       checkNonce([]byte{1, 2, 3, 4, 5, 6}),
-		},
-		{
-			name:        "Nonce with padding",
-			buf:         []byte{1, 1, 1, 1, 1, 1, 1, 1},
-			opt:         NDPNonceOption([]byte{1, 2, 3, 4, 5}),
-			expectedBuf: []byte{14, 1, 1, 2, 3, 4, 5, 0},
-			check:       checkNonce([]byte{1, 2, 3, 4, 5, 0}),
-		},
-
-		{
-			name:        "TLL Ethernet",
-			buf:         make([]byte, 8),
-			opt:         NDPTargetLinkLayerAddressOption("\x01\x02\x03\x04\x05\x06"),
-			expectedBuf: []byte{2, 1, 1, 2, 3, 4, 5, 6},
-			check:       checkTLL("\x01\x02\x03\x04\x05\x06"),
-		},
-		{
-			name:        "TLL Padding",
-			buf:         []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-			opt:         NDPTargetLinkLayerAddressOption("\x01\x02\x03\x04\x05\x06\x07\x08"),
-			expectedBuf: []byte{2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0},
-			check:       checkTLL("\x01\x02\x03\x04\x05\x06"),
-		},
-		{
-			name:        "TLL Empty",
-			buf:         nil,
-			opt:         NDPTargetLinkLayerAddressOption(""),
-			expectedBuf: nil,
-		},
-
-		{
-			name:        "SLL Ethernet",
-			buf:         make([]byte, 8),
-			opt:         NDPSourceLinkLayerAddressOption("\x01\x02\x03\x04\x05\x06"),
-			expectedBuf: []byte{1, 1, 1, 2, 3, 4, 5, 6},
-			check:       checkSLL("\x01\x02\x03\x04\x05\x06"),
-		},
-		{
-			name:        "SLL Padding",
-			buf:         []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-			opt:         NDPSourceLinkLayerAddressOption("\x01\x02\x03\x04\x05\x06\x07\x08"),
-			expectedBuf: []byte{1, 2, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0},
-			check:       checkSLL("\x01\x02\x03\x04\x05\x06"),
-		},
-		{
-			name:        "SLL Empty",
-			buf:         nil,
-			opt:         NDPSourceLinkLayerAddressOption(""),
-			expectedBuf: nil,
-		},
-
-		{
-			name: "RDNSS",
-			buf:  []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-			// NDPRecursiveDNSServer holds the option after the header bytes.
-			opt:         NDPRecursiveDNSServer(rdnssBytes[optionHeaderLen:]),
-			expectedBuf: expectedRDNSSBytes[:],
-			check: func(t *testing.T, opt NDPOption) {
-				if got := opt.kind(); got != ndpRecursiveDNSServerOptionType {
-					t.Errorf("got kind() = %d, want = %d", got, ndpRecursiveDNSServerOptionType)
-				}
-				rdnss, ok := opt.(NDPRecursiveDNSServer)
-				if !ok {
-					t.Fatalf("got opt = %T, want = NDPRecursiveDNSServer", opt)
-				}
-				if got, want := rdnss.length(), len(expectedRDNSSBytes[optionHeaderLen:]); got != want {
-					t.Errorf("got length() = %d, want = %d", got, want)
-				}
-				if got, want := rdnss.Lifetime(), validLifetimeSeconds*time.Second; got != want {
-					t.Errorf("got Lifetime() = %s, want = %s", got, want)
-				}
-				if addrs, err := rdnss.Addresses(); err != nil {
-					t.Errorf("Addresses(): %s", err)
-				} else if diff := cmp.Diff([]tcpip.Address{address}, addrs); diff != "" {
-					t.Errorf("mismatched addresses (-want +got):\n%s", diff)
-				}
-			},
-		},
-
-		{
-			name:        "Search list",
-			buf:         []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-			opt:         NDPDNSSearchList(searchListBytes[optionHeaderLen:]),
-			expectedBuf: expectedSearchListBytes[:],
-			check: func(t *testing.T, opt NDPOption) {
-				if got := opt.kind(); got != ndpDNSSearchListOptionType {
-					t.Errorf("got kind() = %d, want = %d", got, ndpDNSSearchListOptionType)
-				}
-
-				dnssl, ok := opt.(NDPDNSSearchList)
-				if !ok {
-					t.Fatalf("got opt = %T, want = NDPDNSSearchList", opt)
-				}
-				if got, want := dnssl.length(), len(expectedRDNSSBytes[optionHeaderLen:]); got != want {
-					t.Errorf("got length() = %d, want = %d", got, want)
-				}
-				if got, want := dnssl.Lifetime(), validLifetimeSeconds*time.Second; got != want {
-					t.Errorf("got Lifetime() = %s, want = %s", got, want)
-				}
-
-				if domainNames, err := dnssl.DomainNames(); err != nil {
-					t.Errorf("DomainNames(): %s", err)
-				} else if diff := cmp.Diff([]string{domainName}, domainNames); diff != "" {
-					t.Errorf("domain names mismatch (-want +got):\n%s", diff)
-				}
-			},
-		},
-
-		{
-			name: "Prefix Information",
-			buf:  []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
-			// NDPPrefixInformation holds the option after the header bytes.
-			opt:         NDPPrefixInformation(prefixInformationBytes[optionHeaderLen:]),
-			expectedBuf: expectedPrefixInformationBytes[:],
-			check: func(t *testing.T, opt NDPOption) {
-				if got := opt.kind(); got != ndpPrefixInformationType {
-					t.Errorf("got kind() = %d, want = %d", got, ndpPrefixInformationType)
-				}
-
-				pi, ok := opt.(NDPPrefixInformation)
-				if !ok {
-					t.Fatalf("got opt = %T, want = NDPPrefixInformation", opt)
-				}
-
-				if got, want := pi.length(), len(expectedPrefixInformationBytes[optionHeaderLen:]); got != want {
-					t.Errorf("got length() = %d, want = %d", got, want)
-				}
-				if got := pi.PrefixLength(); got != prefixLength {
-					t.Errorf("got PrefixLength() = %d, want = %d", got, prefixLength)
-				}
-				if got := pi.OnLinkFlag(); got != onLinkFlag {
-					t.Errorf("got OnLinkFlag() = %t, want = %t", got, onLinkFlag)
-				}
-				if got := pi.AutonomousAddressConfigurationFlag(); got != slaacFlag {
-					t.Errorf("got AutonomousAddressConfigurationFlag() = %t, want = %t", got, slaacFlag)
-				}
-				if got, want := pi.ValidLifetime(), validLifetimeSeconds*time.Second; got != want {
-					t.Errorf("got ValidLifetime() = %s, want = %s", got, want)
-				}
-				if got, want := pi.PreferredLifetime(), preferredLifetimeSeconds*time.Second; got != want {
-					t.Errorf("got PreferredLifetime() = %s, want = %s", got, want)
-				}
-				if got := pi.Prefix(); got != address {
-					t.Errorf("got Prefix() = %s, want = %s", got, address)
-				}
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			opts := NDPOptions(test.buf)
-			serializer := NDPOptionsSerializer{
-				test.opt,
-			}
-			if got, want := int(serializer.Length()), len(test.expectedBuf); got != want {
-				t.Fatalf("got Length() = %d, want = %d", got, want)
-			}
-			opts.Serialize(serializer)
-			if diff := cmp.Diff(test.expectedBuf, test.buf); diff != "" {
-				t.Fatalf("serialized buffer mismatch (-want +got):\n%s", diff)
-			}
-
-			it, err := opts.Iter(true)
-			if err != nil {
-				t.Fatalf("got Iter(true) = (_, %s), want = (_, nil)", err)
-			}
-
-			if len(test.expectedBuf) > 0 {
-				next, done, err := it.Next()
-				if err != nil {
-					t.Fatalf("got Next() = (_, _, %s), want = (_, _, nil)", err)
-				}
-				if done {
-					t.Fatal("got Next() = (_, true, _), want = (_, false, _)")
-				}
-				test.check(t, next)
-			}
-
-			// Iterator should not return anything else.
-			next, done, err := it.Next()
-			if err != nil {
-				t.Errorf("got Next() = (_, _, %s), want = (_, _, nil)", err)
-			}
-			if !done {
-				t.Error("got Next() = (_, false, _), want = (_, true, _)")
-			}
-			if next != nil {
-				t.Errorf("got Next() = (%x, _, _), want = (nil, _, _)", next)
-			}
-		})
-	}
-}
-
-func TestNDPRecursiveDNSServerOption(t *testing.T) {
-	tests := []struct {
-		name     string
-		buf      []byte
-		lifetime time.Duration
-		addrs    []tcpip.Address
-	}{
-		{
-			"Valid1Addr",
-			[]byte{
-				25, 3, 0, 0,
-				0, 0, 0, 0,
-				0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
-			},
-			0,
-			[]tcpip.Address{
-				"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
-			},
-		},
-		{
-			"Valid2Addr",
-			[]byte{
-				25, 5, 0, 0,
-				0, 0, 0, 0,
-				0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
-				17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16,
-			},
-			0,
-			[]tcpip.Address{
-				"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
-				"\x11\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x10",
-			},
-		},
-		{
-			"Valid3Addr",
-			[]byte{
-				25, 7, 0, 0,
-				0, 0, 0, 0,
-				0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
-				17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16,
-				17, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17,
-			},
-			0,
-			[]tcpip.Address{
-				"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
-				"\x11\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x10",
-				"\x11\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x11",
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			opts := NDPOptions(test.buf)
-			it, err := opts.Iter(true)
-			if err != nil {
-				t.Fatalf("got Iter = (_, %s), want = (_, nil)", err)
-			}
-
-			// Iterator should get our option.
-			next, done, err := it.Next()
-			if err != nil {
-				t.Fatalf("got Next = (_, _, %s), want = (_, _, nil)", err)
-			}
-			if done {
-				t.Fatal("got Next = (_, true, _), want = (_, false, _)")
-			}
-			if got := next.kind(); got != ndpRecursiveDNSServerOptionType {
-				t.Fatalf("got Type = %d, want = %d", got, ndpRecursiveDNSServerOptionType)
-			}
-
-			opt, ok := next.(NDPRecursiveDNSServer)
-			if !ok {
-				t.Fatalf("next (type = %T) cannot be casted to an NDPRecursiveDNSServer", next)
-			}
-			if got := opt.Lifetime(); got != test.lifetime {
-				t.Errorf("got Lifetime = %d, want = %d", got, test.lifetime)
-			}
-			addrs, err := opt.Addresses()
-			if err != nil {
-				t.Errorf("opt.Addresses() = %s", err)
-			}
-			if diff := cmp.Diff(addrs, test.addrs); diff != "" {
-				t.Errorf("mismatched addresses (-want +got):\n%s", diff)
-			}
-
-			// Iterator should not return anything else.
-			next, done, err = it.Next()
-			if err != nil {
-				t.Errorf("got Next = (_, _, %s), want = (_, _, nil)", err)
-			}
-			if !done {
-				t.Error("got Next = (_, false, _), want = (_, true, _)")
-			}
-			if next != nil {
-				t.Errorf("got Next = (%x, _, _), want = (nil, _, _)", next)
-			}
-		})
-	}
-}
-
-// TestNDPDNSSearchListOption tests the getters of NDPDNSSearchList.
-func TestNDPDNSSearchListOption(t *testing.T) {
-	tests := []struct {
-		name        string
-		buf         []byte
-		lifetime    time.Duration
-		domainNames []string
-		err         error
-	}{
-		{
-			name: "Valid1Label",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 1,
-				3, 'a', 'b', 'c',
-				0,
-				0, 0, 0,
-			},
-			lifetime: time.Second,
-			domainNames: []string{
-				"abc",
-			},
-			err: nil,
-		},
-		{
-			name: "Valid2Label",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 5,
-				3, 'a', 'b', 'c',
-				4, 'a', 'b', 'c', 'd',
-				0,
-				0, 0, 0, 0, 0, 0,
-			},
-			lifetime: 5 * time.Second,
-			domainNames: []string{
-				"abc.abcd",
-			},
-			err: nil,
-		},
-		{
-			name: "Valid3Label",
-			buf: []byte{
-				0, 0,
-				1, 0, 0, 0,
-				3, 'a', 'b', 'c',
-				4, 'a', 'b', 'c', 'd',
-				1, 'e',
-				0,
-				0, 0, 0, 0,
-			},
-			lifetime: 16777216 * time.Second,
-			domainNames: []string{
-				"abc.abcd.e",
-			},
-			err: nil,
-		},
-		{
-			name: "Valid2Domains",
-			buf: []byte{
-				0, 0,
-				1, 2, 3, 4,
-				3, 'a', 'b', 'c',
-				0,
-				2, 'd', 'e',
-				3, 'x', 'y', 'z',
-				0,
-				0, 0, 0,
-			},
-			lifetime: 16909060 * time.Second,
-			domainNames: []string{
-				"abc",
-				"de.xyz",
-			},
-			err: nil,
-		},
-		{
-			name: "Valid3DomainsMixedCase",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				3, 'a', 'B', 'c',
-				0,
-				2, 'd', 'E',
-				3, 'X', 'y', 'z',
-				0,
-				1, 'J',
-				0,
-			},
-			lifetime: 0,
-			domainNames: []string{
-				"abc",
-				"de.xyz",
-				"j",
-			},
-			err: nil,
-		},
-		{
-			name: "ValidDomainAfterNULL",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				3, 'a', 'B', 'c',
-				0, 0, 0, 0,
-				2, 'd', 'E',
-				3, 'X', 'y', 'z',
-				0,
-			},
-			lifetime: 0,
-			domainNames: []string{
-				"abc",
-				"de.xyz",
-			},
-			err: nil,
-		},
-		{
-			name: "Valid0Domains",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				0,
-				0, 0, 0, 0, 0, 0, 0,
-			},
-			lifetime:    0,
-			domainNames: nil,
-			err:         nil,
-		},
-		{
-			name: "NoTrailingNull",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				7, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
-			},
-			lifetime:    0,
-			domainNames: nil,
-			err:         io.ErrUnexpectedEOF,
-		},
-		{
-			name: "IncorrectLength",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				8, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
-			},
-			lifetime:    0,
-			domainNames: nil,
-			err:         io.ErrUnexpectedEOF,
-		},
-		{
-			name: "IncorrectLengthWithNULL",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				7, 'a', 'b', 'c', 'd', 'e', 'f',
-				0,
-			},
-			lifetime:    0,
-			domainNames: nil,
-			err:         ErrNDPOptMalformedBody,
-		},
-		{
-			name: "LabelOfLength63",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				0,
-			},
-			lifetime: 0,
-			domainNames: []string{
-				"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk",
-			},
-			err: nil,
-		},
-		{
-			name: "LabelOfLength64",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				64, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l',
-				0,
-			},
-			lifetime:    0,
-			domainNames: nil,
-			err:         ErrNDPOptMalformedBody,
-		},
-		{
-			name: "DomainNameOfLength255",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				62, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j',
-				0,
-			},
-			lifetime: 0,
-			domainNames: []string{
-				"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij",
-			},
-			err: nil,
-		},
-		{
-			name: "DomainNameOfLength256",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 0,
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				0,
-			},
-			lifetime:    0,
-			domainNames: nil,
-			err:         ErrNDPOptMalformedBody,
-		},
-		{
-			name: "StartingDigitForLabel",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 1,
-				3, '9', 'b', 'c',
-				0,
-				0, 0, 0,
-			},
-			lifetime:    time.Second,
-			domainNames: nil,
-			err:         ErrNDPOptMalformedBody,
-		},
-		{
-			name: "StartingHyphenForLabel",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 1,
-				3, '-', 'b', 'c',
-				0,
-				0, 0, 0,
-			},
-			lifetime:    time.Second,
-			domainNames: nil,
-			err:         ErrNDPOptMalformedBody,
-		},
-		{
-			name: "EndingHyphenForLabel",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 1,
-				3, 'a', 'b', '-',
-				0,
-				0, 0, 0,
-			},
-			lifetime:    time.Second,
-			domainNames: nil,
-			err:         ErrNDPOptMalformedBody,
-		},
-		{
-			name: "EndingDigitForLabel",
-			buf: []byte{
-				0, 0,
-				0, 0, 0, 1,
-				3, 'a', 'b', '9',
-				0,
-				0, 0, 0,
-			},
-			lifetime: time.Second,
-			domainNames: []string{
-				"ab9",
-			},
-			err: nil,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			opt := NDPDNSSearchList(test.buf)
-
-			if got := opt.Lifetime(); got != test.lifetime {
-				t.Errorf("got Lifetime = %d, want = %d", got, test.lifetime)
-			}
-			domainNames, err := opt.DomainNames()
-			if !errors.Is(err, test.err) {
-				t.Errorf("opt.DomainNames() = %s", err)
-			}
-			if diff := cmp.Diff(domainNames, test.domainNames); diff != "" {
-				t.Errorf("mismatched domain names (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-func TestNDPSearchListOptionDomainNameLabelInvalidSymbols(t *testing.T) {
-	for r := rune(0); r <= 255; r++ {
-		t.Run(fmt.Sprintf("RuneVal=%d", r), func(t *testing.T) {
-			buf := []byte{
-				0, 0,
-				0, 0, 0, 0,
-				3, 'a', 0 /* will be replaced */, 'c',
-				0,
-				0, 0, 0,
-			}
-			buf[8] = uint8(r)
-			opt := NDPDNSSearchList(buf)
-
-			// As per RFC 1035 section 2.3.1, the label must only include ASCII
-			// letters, digits and hyphens (a-z, A-Z, 0-9, -).
-			var expectedErr error
-			re := regexp.MustCompile(`[a-zA-Z0-9-]`)
-			if !re.Match([]byte{byte(r)}) {
-				expectedErr = ErrNDPOptMalformedBody
-			}
-
-			if domainNames, err := opt.DomainNames(); !errors.Is(err, expectedErr) {
-				t.Errorf("got opt.DomainNames() = (%s, %v), want = (_, %v)", domainNames, err, ErrNDPOptMalformedBody)
-			}
-		})
-	}
-}
-
-// TestNDPOptionsIterCheck tests that Iter will return false if the NDPOptions
-// the iterator was returned for is malformed.
-func TestNDPOptionsIterCheck(t *testing.T) {
-	tests := []struct {
-		name        string
-		buf         []byte
-		expectedErr error
-	}{
-		{
-			name:        "ZeroLengthField",
-			buf:         []byte{0, 0, 0, 0, 0, 0, 0, 0},
-			expectedErr: ErrNDPOptMalformedHeader,
-		},
-		{
-			name:        "ValidSourceLinkLayerAddressOption",
-			buf:         []byte{1, 1, 1, 2, 3, 4, 5, 6},
-			expectedErr: nil,
-		},
-		{
-			name:        "TooSmallSourceLinkLayerAddressOption",
-			buf:         []byte{1, 1, 1, 2, 3, 4, 5},
-			expectedErr: io.ErrUnexpectedEOF,
-		},
-		{
-			name:        "ValidTargetLinkLayerAddressOption",
-			buf:         []byte{2, 1, 1, 2, 3, 4, 5, 6},
-			expectedErr: nil,
-		},
-		{
-			name:        "TooSmallTargetLinkLayerAddressOption",
-			buf:         []byte{2, 1, 1, 2, 3, 4, 5},
-			expectedErr: io.ErrUnexpectedEOF,
-		},
-		{
-			name: "ValidPrefixInformation",
-			buf: []byte{
-				3, 4, 43, 64,
-				1, 2, 3, 4,
-				5, 6, 7, 8,
-				0, 0, 0, 0,
-				9, 10, 11, 12,
-				13, 14, 15, 16,
-				17, 18, 19, 20,
-				21, 22, 23, 24,
-			},
-			expectedErr: nil,
-		},
-		{
-			name: "TooSmallPrefixInformation",
-			buf: []byte{
-				3, 4, 43, 64,
-				1, 2, 3, 4,
-				5, 6, 7, 8,
-				0, 0, 0, 0,
-				9, 10, 11, 12,
-				13, 14, 15, 16,
-				17, 18, 19, 20,
-				21, 22, 23,
-			},
-			expectedErr: io.ErrUnexpectedEOF,
-		},
-		{
-			name: "InvalidPrefixInformationLength",
-			buf: []byte{
-				3, 3, 43, 64,
-				1, 2, 3, 4,
-				5, 6, 7, 8,
-				0, 0, 0, 0,
-				9, 10, 11, 12,
-				13, 14, 15, 16,
-			},
-			expectedErr: ErrNDPOptMalformedBody,
-		},
-		{
-			name: "ValidSourceAndTargetLinkLayerAddressWithPrefixInformation",
-			buf: []byte{
-				// Source Link-Layer Address.
-				1, 1, 1, 2, 3, 4, 5, 6,
-
-				// Target Link-Layer Address.
-				2, 1, 7, 8, 9, 10, 11, 12,
-
-				// Prefix information.
-				3, 4, 43, 64,
-				1, 2, 3, 4,
-				5, 6, 7, 8,
-				0, 0, 0, 0,
-				9, 10, 11, 12,
-				13, 14, 15, 16,
-				17, 18, 19, 20,
-				21, 22, 23, 24,
-			},
-			expectedErr: nil,
-		},
-		{
-			name: "ValidSourceAndTargetLinkLayerAddressWithPrefixInformationWithUnrecognized",
-			buf: []byte{
-				// Source Link-Layer Address.
-				1, 1, 1, 2, 3, 4, 5, 6,
-
-				// Target Link-Layer Address.
-				2, 1, 7, 8, 9, 10, 11, 12,
-
-				// 255 is an unrecognized type. If 255 ends up
-				// being the type for some recognized type,
-				// update 255 to some other unrecognized value.
-				255, 2, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8,
-
-				// Prefix information.
-				3, 4, 43, 64,
-				1, 2, 3, 4,
-				5, 6, 7, 8,
-				0, 0, 0, 0,
-				9, 10, 11, 12,
-				13, 14, 15, 16,
-				17, 18, 19, 20,
-				21, 22, 23, 24,
-			},
-			expectedErr: nil,
-		},
-		{
-			name: "InvalidRecursiveDNSServerCutsOffAddress",
-			buf: []byte{
-				25, 4, 0, 0,
-				0, 0, 0, 0,
-				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
-				0, 1, 2, 3, 4, 5, 6, 7,
-			},
-			expectedErr: ErrNDPOptMalformedBody,
-		},
-		{
-			name: "InvalidRecursiveDNSServerInvalidLengthField",
-			buf: []byte{
-				25, 2, 0, 0,
-				0, 0, 0, 0,
-				0, 1, 2, 3, 4, 5, 6, 7, 8,
-			},
-			expectedErr: io.ErrUnexpectedEOF,
-		},
-		{
-			name: "RecursiveDNSServerTooSmall",
-			buf: []byte{
-				25, 1, 0, 0,
-				0, 0, 0,
-			},
-			expectedErr: io.ErrUnexpectedEOF,
-		},
-		{
-			name: "RecursiveDNSServerMulticast",
-			buf: []byte{
-				25, 3, 0, 0,
-				0, 0, 0, 0,
-				255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
-			},
-			expectedErr: ErrNDPOptMalformedBody,
-		},
-		{
-			name: "RecursiveDNSServerUnspecified",
-			buf: []byte{
-				25, 3, 0, 0,
-				0, 0, 0, 0,
-				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-			},
-			expectedErr: ErrNDPOptMalformedBody,
-		},
-		{
-			name: "DNSSearchListLargeCompliantRFC1035",
-			buf: []byte{
-				31, 33, 0, 0,
-				0, 0, 0, 0,
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				62, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j',
-				0,
-			},
-			expectedErr: nil,
-		},
-		{
-			name: "DNSSearchListNonCompliantRFC1035",
-			buf: []byte{
-				31, 33, 0, 0,
-				0, 0, 0, 0,
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				63, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
-				'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-				'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-				'i', 'j', 'k',
-				0,
-				0, 0, 0, 0, 0, 0, 0, 0,
-			},
-			expectedErr: ErrNDPOptMalformedBody,
-		},
-		{
-			name: "DNSSearchListValidSmall",
-			buf: []byte{
-				31, 2, 0, 0,
-				0, 0, 0, 0,
-				6, 'a', 'b', 'c', 'd', 'e', 'f',
-				0,
-			},
-			expectedErr: nil,
-		},
-		{
-			name: "DNSSearchListTooSmall",
-			buf: []byte{
-				31, 1, 0, 0,
-				0, 0, 0,
-			},
-			expectedErr: io.ErrUnexpectedEOF,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			opts := NDPOptions(test.buf)
-
-			if _, err := opts.Iter(true); !errors.Is(err, test.expectedErr) {
-				t.Fatalf("got Iter(true) = (_, %v), want = (_, %v)", err, test.expectedErr)
-			}
-
-			// test.buf may be malformed but we chose not to check
-			// the iterator so it must return true.
-			if _, err := opts.Iter(false); err != nil {
-				t.Fatalf("got Iter(false) = (_, %s), want = (_, nil)", err)
-			}
-		})
-	}
-}
-
-// TestNDPOptionsIter tests that we can iterator over a valid NDPOptions. Note,
-// this test does not actually check any of the option's getters, it simply
-// checks the option Type and Body. We have other tests that tests the option
-// field gettings given an option body and don't need to duplicate those tests
-// here.
-func TestNDPOptionsIter(t *testing.T) {
-	buf := []byte{
-		// Source Link-Layer Address.
-		1, 1, 1, 2, 3, 4, 5, 6,
-
-		// Target Link-Layer Address.
-		2, 1, 7, 8, 9, 10, 11, 12,
-
-		// 255 is an unrecognized type. If 255 ends up being the type
-		// for some recognized type, update 255 to some other
-		// unrecognized value. Note, this option should be skipped when
-		// iterating.
-		255, 2, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8,
-
-		// Prefix information.
-		3, 4, 43, 64,
-		1, 2, 3, 4,
-		5, 6, 7, 8,
-		0, 0, 0, 0,
-		9, 10, 11, 12,
-		13, 14, 15, 16,
-		17, 18, 19, 20,
-		21, 22, 23, 24,
-	}
-
-	opts := NDPOptions(buf)
-	it, err := opts.Iter(true)
-	if err != nil {
-		t.Fatalf("got Iter = (_, %s), want = (_, nil)", err)
-	}
-
-	// Test the first (Source Link-Layer) option.
-	next, done, err := it.Next()
-	if err != nil {
-		t.Fatalf("got Next = (_, _, %s), want = (_, _, nil)", err)
-	}
-	if done {
-		t.Fatal("got Next = (_, true, _), want = (_, false, _)")
-	}
-	if got, want := []byte(next.(NDPSourceLinkLayerAddressOption)), buf[2:][:6]; !bytes.Equal(got, want) {
-		t.Errorf("got Next = (%x, _, _), want = (%x, _, _)", got, want)
-	}
-	if got := next.kind(); got != ndpSourceLinkLayerAddressOptionType {
-		t.Errorf("got Type = %d, want = %d", got, ndpSourceLinkLayerAddressOptionType)
-	}
-
-	// Test the next (Target Link-Layer) option.
-	next, done, err = it.Next()
-	if err != nil {
-		t.Fatalf("got Next = (_, _, %s), want = (_, _, nil)", err)
-	}
-	if done {
-		t.Fatal("got Next = (_, true, _), want = (_, false, _)")
-	}
-	if got, want := []byte(next.(NDPTargetLinkLayerAddressOption)), buf[10:][:6]; !bytes.Equal(got, want) {
-		t.Errorf("got Next = (%x, _, _), want = (%x, _, _)", got, want)
-	}
-	if got := next.kind(); got != ndpTargetLinkLayerAddressOptionType {
-		t.Errorf("got Type = %d, want = %d", got, ndpTargetLinkLayerAddressOptionType)
-	}
-
-	// Test the next (Prefix Information) option.
-	// Note, the unrecognized option should be skipped.
-	next, done, err = it.Next()
-	if err != nil {
-		t.Fatalf("got Next = (_, _, %s), want = (_, _, nil)", err)
-	}
-	if done {
-		t.Fatal("got Next = (_, true, _), want = (_, false, _)")
-	}
-	if got, want := next.(NDPPrefixInformation), buf[34:][:30]; !bytes.Equal(got, want) {
-		t.Errorf("got Next = (%x, _, _), want = (%x, _, _)", got, want)
-	}
-	if got := next.kind(); got != ndpPrefixInformationType {
-		t.Errorf("got Type = %d, want = %d", got, ndpPrefixInformationType)
-	}
-
-	// Iterator should not return anything else.
-	next, done, err = it.Next()
-	if err != nil {
-		t.Errorf("got Next = (_, _, %s), want = (_, _, nil)", err)
-	}
-	if !done {
-		t.Error("got Next = (_, false, _), want = (_, true, _)")
-	}
-	if next != nil {
-		t.Errorf("got Next = (%x, _, _), want = (nil, _, _)", next)
-	}
-}
diff --git a/pkg/tcpip/header/parse/parse_state_autogen.go b/pkg/tcpip/header/parse/parse_state_autogen.go
new file mode 100644
index 0000000..ad047be
--- /dev/null
+++ b/pkg/tcpip/header/parse/parse_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package parse
diff --git a/pkg/tcpip/header/tcp_test.go b/pkg/tcpip/header/tcp_test.go
deleted file mode 100644
index 96db846..0000000
--- a/pkg/tcpip/header/tcp_test.go
+++ /dev/null
@@ -1,168 +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 header_test
-
-import (
-	"reflect"
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-func TestEncodeSACKBlocks(t *testing.T) {
-	testCases := []struct {
-		sackBlocks []header.SACKBlock
-		want       []header.SACKBlock
-		bufSize    int
-	}{
-		{
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}},
-			40,
-		},
-		{
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}},
-			30,
-		},
-		{
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
-			[]header.SACKBlock{{10, 20}, {22, 30}},
-			20,
-		},
-		{
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
-			[]header.SACKBlock{{10, 20}},
-			10,
-		},
-		{
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
-			nil,
-			8,
-		},
-		{
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}, {52, 60}, {62, 70}},
-			[]header.SACKBlock{{10, 20}, {22, 30}, {32, 40}, {42, 50}},
-			60,
-		},
-	}
-	for _, tc := range testCases {
-		b := make([]byte, tc.bufSize)
-		t.Logf("testing: %v", tc)
-		header.EncodeSACKBlocks(tc.sackBlocks, b)
-		opts := header.ParseTCPOptions(b)
-		if got, want := opts.SACKBlocks, tc.want; !reflect.DeepEqual(got, want) {
-			t.Errorf("header.EncodeSACKBlocks(%v, %v), encoded blocks got: %v, want: %v", tc.sackBlocks, b, got, want)
-		}
-	}
-}
-
-func TestTCPParseOptions(t *testing.T) {
-	type tsOption struct {
-		tsVal uint32
-		tsEcr uint32
-	}
-
-	generateOptions := func(tsOpt *tsOption, sackBlocks []header.SACKBlock) []byte {
-		l := 0
-		if tsOpt != nil {
-			l += 10
-		}
-		if len(sackBlocks) != 0 {
-			l += len(sackBlocks)*8 + 2
-		}
-		b := make([]byte, l)
-		offset := 0
-		if tsOpt != nil {
-			offset = header.EncodeTSOption(tsOpt.tsVal, tsOpt.tsEcr, b)
-		}
-		header.EncodeSACKBlocks(sackBlocks, b[offset:])
-		return b
-	}
-
-	testCases := []struct {
-		b    []byte
-		want header.TCPOptions
-	}{
-		// Trivial cases.
-		{nil, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionNOP}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionNOP, header.TCPOptionNOP}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionEOL}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionNOP, header.TCPOptionEOL, header.TCPOptionTS, 10, 1, 1}, header.TCPOptions{false, 0, 0, nil}},
-
-		// Test timestamp parsing.
-		{[]byte{header.TCPOptionNOP, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{true, 1, 1, nil}},
-		{[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{true, 1, 1, nil}},
-
-		// Test malformed timestamp option.
-		{[]byte{header.TCPOptionTS, 8, 1, 1}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionNOP, header.TCPOptionTS, 8, 1, 1}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionNOP, header.TCPOptionTS, 8, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, nil}},
-
-		// Test SACKBlock parsing.
-		{[]byte{header.TCPOptionSACK, 10, 0, 0, 0, 1, 0, 0, 0, 10}, header.TCPOptions{false, 0, 0, []header.SACKBlock{{1, 10}}}},
-		{[]byte{header.TCPOptionSACK, 18, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12}, header.TCPOptions{false, 0, 0, []header.SACKBlock{{1, 10}, {11, 12}}}},
-
-		// Test malformed SACK option.
-		{[]byte{header.TCPOptionSACK, 0}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionSACK, 8, 0, 0, 0, 1, 0, 0, 0, 10}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionSACK, 11, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionSACK, 17, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionSACK}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionSACK, 10}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionSACK, 10, 0, 0, 0, 1, 0, 0, 0}, header.TCPOptions{false, 0, 0, nil}},
-
-		// Test Timestamp + SACK block parsing.
-		{generateOptions(&tsOption{1, 1}, []header.SACKBlock{{1, 10}, {11, 12}}), header.TCPOptions{true, 1, 1, []header.SACKBlock{{1, 10}, {11, 12}}}},
-		{generateOptions(&tsOption{1, 2}, []header.SACKBlock{{1, 10}, {11, 12}}), header.TCPOptions{true, 1, 2, []header.SACKBlock{{1, 10}, {11, 12}}}},
-		{generateOptions(&tsOption{1, 3}, []header.SACKBlock{{1, 10}, {11, 12}, {13, 14}, {14, 15}, {15, 16}}), header.TCPOptions{true, 1, 3, []header.SACKBlock{{1, 10}, {11, 12}, {13, 14}, {14, 15}}}},
-
-		// Test valid timestamp + malformed SACK block parsing.
-		{[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionSACK}, header.TCPOptions{true, 1, 1, nil}},
-		{[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionSACK, 10}, header.TCPOptions{true, 1, 1, nil}},
-		{[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionSACK, 10, 0, 0, 0}, header.TCPOptions{true, 1, 1, nil}},
-		{[]byte{header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionSACK, 11, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{true, 1, 1, nil}},
-		{[]byte{header.TCPOptionSACK, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, nil}},
-		{[]byte{header.TCPOptionSACK, 10, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, []header.SACKBlock{{134873088, 65536}}}},
-		{[]byte{header.TCPOptionSACK, 10, 0, 0, 0, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, []header.SACKBlock{{8, 167772160}}}},
-		{[]byte{header.TCPOptionSACK, 11, 0, 0, 0, 1, 0, 0, 0, 1, header.TCPOptionTS, 10, 0, 0, 0, 1, 0, 0, 0, 1}, header.TCPOptions{false, 0, 0, nil}},
-	}
-	for _, tc := range testCases {
-		if got, want := header.ParseTCPOptions(tc.b), tc.want; !reflect.DeepEqual(got, want) {
-			t.Errorf("ParseTCPOptions(%v) = %v, want: %v", tc.b, got, tc.want)
-		}
-	}
-}
-
-func TestTCPFlags(t *testing.T) {
-	for _, tt := range []struct {
-		flags header.TCPFlags
-		want  string
-	}{
-		{header.TCPFlagFin, "F     "},
-		{header.TCPFlagSyn, " S    "},
-		{header.TCPFlagRst, "  R   "},
-		{header.TCPFlagPsh, "   P  "},
-		{header.TCPFlagAck, "    A "},
-		{header.TCPFlagUrg, "     U"},
-		{header.TCPFlagSyn | header.TCPFlagAck, " S  A "},
-		{header.TCPFlagFin | header.TCPFlagAck, "F   A "},
-	} {
-		if got := tt.flags.String(); got != tt.want {
-			t.Errorf("got TCPFlags(%#b).String() = %s, want = %s", tt.flags, got, tt.want)
-		}
-	}
-}
diff --git a/pkg/tcpip/link/ethernet/ethernet_state_autogen.go b/pkg/tcpip/link/ethernet/ethernet_state_autogen.go
new file mode 100644
index 0000000..71d255c
--- /dev/null
+++ b/pkg/tcpip/link/ethernet/ethernet_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package ethernet
diff --git a/pkg/tcpip/link/ethernet/ethernet_test.go b/pkg/tcpip/link/ethernet/ethernet_test.go
deleted file mode 100644
index 08a7f1c..0000000
--- a/pkg/tcpip/link/ethernet/ethernet_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2021 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 ethernet_test
-
-import (
-	"testing"
-
-	"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/ethernet"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-var _ stack.NetworkDispatcher = (*testNetworkDispatcher)(nil)
-
-type testNetworkDispatcher struct {
-	networkPackets int
-}
-
-func (t *testNetworkDispatcher) DeliverNetworkPacket(_, _ tcpip.LinkAddress, _ tcpip.NetworkProtocolNumber, _ *stack.PacketBuffer) {
-	t.networkPackets++
-}
-
-func (*testNetworkDispatcher) DeliverOutboundPacket(_, _ tcpip.LinkAddress, _ tcpip.NetworkProtocolNumber, _ *stack.PacketBuffer) {
-}
-
-func TestDeliverNetworkPacket(t *testing.T) {
-	const (
-		linkAddr       = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
-		otherLinkAddr1 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x07")
-		otherLinkAddr2 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x08")
-	)
-
-	e := ethernet.New(channel.New(0, 0, linkAddr))
-	var networkDispatcher testNetworkDispatcher
-	e.Attach(&networkDispatcher)
-
-	if networkDispatcher.networkPackets != 0 {
-		t.Fatalf("got networkDispatcher.networkPackets = %d, want = 0", networkDispatcher.networkPackets)
-	}
-
-	// An ethernet frame with a destination link address that is not assigned to
-	// our ethernet link endpoint should still be delivered to the network
-	// dispatcher since the ethernet endpoint is not expected to filter frames.
-	eth := buffer.NewView(header.EthernetMinimumSize)
-	header.Ethernet(eth).Encode(&header.EthernetFields{
-		SrcAddr: otherLinkAddr1,
-		DstAddr: otherLinkAddr2,
-		Type:    header.IPv4ProtocolNumber,
-	})
-	e.DeliverNetworkPacket("", "", 0, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: eth.ToVectorisedView(),
-	}))
-	if networkDispatcher.networkPackets != 1 {
-		t.Fatalf("got networkDispatcher.networkPackets = %d, want = 1", networkDispatcher.networkPackets)
-	}
-}
diff --git a/pkg/tcpip/link/loopback/loopback_state_autogen.go b/pkg/tcpip/link/loopback/loopback_state_autogen.go
new file mode 100644
index 0000000..c00fd9f
--- /dev/null
+++ b/pkg/tcpip/link/loopback/loopback_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package loopback
diff --git a/pkg/tcpip/link/nested/nested_state_autogen.go b/pkg/tcpip/link/nested/nested_state_autogen.go
new file mode 100644
index 0000000..9e1b5ca
--- /dev/null
+++ b/pkg/tcpip/link/nested/nested_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package nested
diff --git a/pkg/tcpip/link/nested/nested_test.go b/pkg/tcpip/link/nested/nested_test.go
deleted file mode 100644
index c1f9d30..0000000
--- a/pkg/tcpip/link/nested/nested_test.go
+++ /dev/null
@@ -1,109 +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 nested_test
-
-import (
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/link/nested"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-type parentEndpoint struct {
-	nested.Endpoint
-}
-
-var _ stack.LinkEndpoint = (*parentEndpoint)(nil)
-var _ stack.NetworkDispatcher = (*parentEndpoint)(nil)
-
-type childEndpoint struct {
-	stack.LinkEndpoint
-	dispatcher stack.NetworkDispatcher
-}
-
-var _ stack.LinkEndpoint = (*childEndpoint)(nil)
-
-func (c *childEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
-	c.dispatcher = dispatcher
-}
-
-func (c *childEndpoint) IsAttached() bool {
-	return c.dispatcher != nil
-}
-
-type counterDispatcher struct {
-	count int
-}
-
-var _ stack.NetworkDispatcher = (*counterDispatcher)(nil)
-
-func (d *counterDispatcher) DeliverNetworkPacket(tcpip.LinkAddress, tcpip.LinkAddress, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {
-	d.count++
-}
-
-func (d *counterDispatcher) DeliverOutboundPacket(tcpip.LinkAddress, tcpip.LinkAddress, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {
-	panic("unimplemented")
-}
-
-func TestNestedLinkEndpoint(t *testing.T) {
-	const emptyAddress = tcpip.LinkAddress("")
-
-	var (
-		childEP  childEndpoint
-		nestedEP parentEndpoint
-		disp     counterDispatcher
-	)
-	nestedEP.Endpoint.Init(&childEP, &nestedEP)
-
-	if childEP.IsAttached() {
-		t.Error("On init, childEP.IsAttached() = true, want = false")
-	}
-	if nestedEP.IsAttached() {
-		t.Error("On init, nestedEP.IsAttached() = true, want = false")
-	}
-
-	nestedEP.Attach(&disp)
-	if disp.count != 0 {
-		t.Fatalf("After attach, got disp.count = %d, want = 0", disp.count)
-	}
-	if !childEP.IsAttached() {
-		t.Error("After attach, childEP.IsAttached() = false, want = true")
-	}
-	if !nestedEP.IsAttached() {
-		t.Error("After attach, nestedEP.IsAttached() = false, want = true")
-	}
-
-	nestedEP.DeliverNetworkPacket(emptyAddress, emptyAddress, header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{}))
-	if disp.count != 1 {
-		t.Errorf("After first packet with dispatcher attached, got disp.count = %d, want = 1", disp.count)
-	}
-
-	nestedEP.Attach(nil)
-	if childEP.IsAttached() {
-		t.Error("After detach, childEP.IsAttached() = true, want = false")
-	}
-	if nestedEP.IsAttached() {
-		t.Error("After detach, nestedEP.IsAttached() = true, want = false")
-	}
-
-	disp.count = 0
-	nestedEP.DeliverNetworkPacket(emptyAddress, emptyAddress, header.IPv4ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{}))
-	if disp.count != 0 {
-		t.Errorf("After second packet with dispatcher detached, got disp.count = %d, want = 0", disp.count)
-	}
-
-}
diff --git a/pkg/tcpip/link/pipe/pipe_state_autogen.go b/pkg/tcpip/link/pipe/pipe_state_autogen.go
new file mode 100644
index 0000000..d3b40fe
--- /dev/null
+++ b/pkg/tcpip/link/pipe/pipe_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package pipe
diff --git a/pkg/tcpip/link/sniffer/sniffer_state_autogen.go b/pkg/tcpip/link/sniffer/sniffer_state_autogen.go
new file mode 100644
index 0000000..8d79def
--- /dev/null
+++ b/pkg/tcpip/link/sniffer/sniffer_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package sniffer
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 0000000..5cd8535
--- /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 018d6a5..0000000
--- a/pkg/tcpip/network/arp/arp_test.go
+++ /dev/null
@@ -1,711 +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"
-	"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(want, got, cmp.AllowUnexported(got), cmpopts.IgnoreFields(stack.NeighborEntry{}, "UpdatedAtNanos")); diff != "" {
-			return fmt.Errorf("got invalid event (-want +got):\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) *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,
-	})
-
-	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)
-	}
-
-	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 TestMalformedPacket(t *testing.T) {
-	c := newTestContext(t)
-	defer c.cleanup()
-
-	v := make(buffer.View, header.ARPSize)
-	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: v.ToVectorisedView(),
-	})
-
-	c.linkEP.InjectInbound(arp.ProtocolNumber, pkt)
-
-	if got := c.s.Stats().ARP.PacketsReceived.Value(); got != 1 {
-		t.Errorf("got c.s.Stats().ARP.PacketsReceived.Value() = %d, want = 1", got)
-	}
-	if got := c.s.Stats().ARP.MalformedPacketsReceived.Value(); got != 1 {
-		t.Errorf("got c.s.Stats().ARP.MalformedPacketsReceived.Value() = %d, want = 1", got)
-	}
-}
-
-func TestDisabledEndpoint(t *testing.T) {
-	c := newTestContext(t)
-	defer c.cleanup()
-
-	ep, err := c.s.GetNetworkEndpoint(nicID, header.ARPProtocolNumber)
-	if err != nil {
-		t.Fatalf("GetNetworkEndpoint(%d, header.ARPProtocolNumber) failed: %s", nicID, err)
-	}
-	ep.Disable()
-
-	v := make(buffer.View, header.ARPSize)
-	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: v.ToVectorisedView(),
-	})
-
-	c.linkEP.InjectInbound(arp.ProtocolNumber, pkt)
-
-	if got := c.s.Stats().ARP.PacketsReceived.Value(); got != 1 {
-		t.Errorf("got c.s.Stats().ARP.PacketsReceived.Value() = %d, want = 1", got)
-	}
-	if got := c.s.Stats().ARP.DisabledPacketsReceived.Value(); got != 1 {
-		t.Errorf("got c.s.Stats().ARP.DisabledPacketsReceived.Value() = %d, want = 1", got)
-	}
-}
-
-func TestDirectReply(t *testing.T) {
-	c := newTestContext(t)
-	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.ARPReply)
-
-	copy(h.HardwareAddressSender(), senderMAC)
-	copy(h.ProtocolAddressSender(), senderIPv4)
-	copy(h.HardwareAddressTarget(), stackLinkAddr)
-	copy(h.ProtocolAddressTarget(), stackAddr)
-
-	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: v.ToVectorisedView(),
-	})
-
-	c.linkEP.InjectInbound(arp.ProtocolNumber, pkt)
-
-	if got := c.s.Stats().ARP.PacketsReceived.Value(); got != 1 {
-		t.Errorf("got c.s.Stats().ARP.PacketsReceived.Value() = %d, want = 1", got)
-	}
-	if got := c.s.Stats().ARP.RepliesReceived.Value(); got != 1 {
-		t.Errorf("got c.s.Stats().ARP.PacketsReceived.Value() = %d, want = 1", got)
-	}
-}
-
-func TestDirectRequest(t *testing.T) {
-	c := newTestContext(t)
-	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) {
-			packetsRecv := c.s.Stats().ARP.PacketsReceived.Value()
-			requestsRecv := c.s.Stats().ARP.RequestsReceived.Value()
-			requestsRecvUnknownAddr := c.s.Stats().ARP.RequestsReceivedUnknownTargetAddress.Value()
-			outgoingReplies := c.s.Stats().ARP.OutgoingRepliesSent.Value()
-
-			// 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.NewPacketBuffer(stack.PacketBufferOptions{
-				Data: v.ToVectorisedView(),
-			}))
-
-			if got, want := c.s.Stats().ARP.PacketsReceived.Value(), packetsRecv+1; got != want {
-				t.Errorf("got c.s.Stats().ARP.PacketsReceived.Value() = %d, want = %d", got, want)
-			}
-			if got, want := c.s.Stats().ARP.RequestsReceived.Value(), requestsRecv+1; got != want {
-				t.Errorf("got c.s.Stats().ARP.PacketsReceived.Value() = %d, want = %d", got, want)
-			}
-
-			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)
-				}
-				if got, want := c.s.Stats().ARP.RequestsReceivedUnknownTargetAddress.Value(), requestsRecvUnknownAddr+1; got != want {
-					t.Errorf("got c.s.Stats().ARP.RequestsReceivedUnknownTargetAddress.Value() = %d, want = %d", got, want)
-				}
-				if got, want := c.s.Stats().ARP.OutgoingRepliesSent.Value(), outgoingReplies; got != want {
-					t.Errorf("got c.s.Stats().ARP.OutgoingRepliesSent.Value() = %d, want = %d", got, want)
-				}
-
-				return
-			}
-
-			if got, want := c.s.Stats().ARP.OutgoingRepliesSent.Value(), outgoingReplies+1; got != want {
-				t.Errorf("got c.s.Stats().ARP.OutgoingRepliesSent.Value() = %d, want = %d", got, want)
-			}
-
-			// 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, ipv4.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("c.s.Neighbors(%d, %d): %s", nicID, ipv4.ProtocolNumber, 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.LinkEndpoint = (*testLinkEndpoint)(nil)
-
-type testLinkEndpoint struct {
-	stack.LinkEndpoint
-
-	writeErr tcpip.Error
-}
-
-func (t *testLinkEndpoint) WritePacket(r stack.RouteInfo, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
-	if t.writeErr != nil {
-		return t.writeErr
-	}
-
-	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
-		linkErr                                         tcpip.Error
-		expectedErr                                     tcpip.Error
-		expectedLocalAddr                               tcpip.Address
-		expectedRemoteLinkAddr                          tcpip.LinkAddress
-		expectedRequestsSent                            uint64
-		expectedRequestBadLocalAddressErrors            uint64
-		expectedRequestInterfaceHasNoLocalAddressErrors uint64
-		expectedRequestDroppedErrors                    uint64
-	}{
-		{
-			name:                                 "Unicast",
-			nicAddr:                              stackAddr,
-			localAddr:                            stackAddr,
-			remoteLinkAddr:                       remoteLinkAddr,
-			expectedLocalAddr:                    stackAddr,
-			expectedRemoteLinkAddr:               remoteLinkAddr,
-			expectedRequestsSent:                 1,
-			expectedRequestBadLocalAddressErrors: 0,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 0,
-			expectedRequestDroppedErrors:                    0,
-		},
-		{
-			name:                                 "Multicast",
-			nicAddr:                              stackAddr,
-			localAddr:                            stackAddr,
-			remoteLinkAddr:                       "",
-			expectedLocalAddr:                    stackAddr,
-			expectedRemoteLinkAddr:               header.EthernetBroadcastAddress,
-			expectedRequestsSent:                 1,
-			expectedRequestBadLocalAddressErrors: 0,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 0,
-			expectedRequestDroppedErrors:                    0,
-		},
-		{
-			name:                                 "Unicast with unspecified source",
-			nicAddr:                              stackAddr,
-			localAddr:                            "",
-			remoteLinkAddr:                       remoteLinkAddr,
-			expectedLocalAddr:                    stackAddr,
-			expectedRemoteLinkAddr:               remoteLinkAddr,
-			expectedRequestsSent:                 1,
-			expectedRequestBadLocalAddressErrors: 0,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 0,
-			expectedRequestDroppedErrors:                    0,
-		},
-		{
-			name:                                 "Multicast with unspecified source",
-			nicAddr:                              stackAddr,
-			localAddr:                            "",
-			remoteLinkAddr:                       "",
-			expectedLocalAddr:                    stackAddr,
-			expectedRemoteLinkAddr:               header.EthernetBroadcastAddress,
-			expectedRequestsSent:                 1,
-			expectedRequestBadLocalAddressErrors: 0,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 0,
-			expectedRequestDroppedErrors:                    0,
-		},
-		{
-			name:                                 "Unicast with unassigned address",
-			nicAddr:                              stackAddr,
-			localAddr:                            testAddr,
-			remoteLinkAddr:                       remoteLinkAddr,
-			expectedErr:                          &tcpip.ErrBadLocalAddress{},
-			expectedRequestsSent:                 0,
-			expectedRequestBadLocalAddressErrors: 1,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 0,
-			expectedRequestDroppedErrors:                    0,
-		},
-		{
-			name:                                 "Multicast with unassigned address",
-			nicAddr:                              stackAddr,
-			localAddr:                            testAddr,
-			remoteLinkAddr:                       "",
-			expectedErr:                          &tcpip.ErrBadLocalAddress{},
-			expectedRequestsSent:                 0,
-			expectedRequestBadLocalAddressErrors: 1,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 0,
-			expectedRequestDroppedErrors:                    0,
-		},
-		{
-			name:                                 "Unicast with no local address available",
-			nicAddr:                              "",
-			localAddr:                            "",
-			remoteLinkAddr:                       remoteLinkAddr,
-			expectedErr:                          &tcpip.ErrNetworkUnreachable{},
-			expectedRequestsSent:                 0,
-			expectedRequestBadLocalAddressErrors: 0,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 1,
-			expectedRequestDroppedErrors:                    0,
-		},
-		{
-			name:                                 "Multicast with no local address available",
-			nicAddr:                              "",
-			localAddr:                            "",
-			remoteLinkAddr:                       "",
-			expectedErr:                          &tcpip.ErrNetworkUnreachable{},
-			expectedRequestsSent:                 0,
-			expectedRequestBadLocalAddressErrors: 0,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 1,
-			expectedRequestDroppedErrors:                    0,
-		},
-		{
-			name:                                 "Link error",
-			nicAddr:                              stackAddr,
-			localAddr:                            stackAddr,
-			remoteLinkAddr:                       remoteLinkAddr,
-			linkErr:                              &tcpip.ErrInvalidEndpointState{},
-			expectedErr:                          &tcpip.ErrInvalidEndpointState{},
-			expectedRequestsSent:                 0,
-			expectedRequestBadLocalAddressErrors: 0,
-			expectedRequestInterfaceHasNoLocalAddressErrors: 0,
-			expectedRequestDroppedErrors:                    1,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol},
-			})
-			linkEP := channel.New(defaultChannelSize, defaultMTU, stackLinkAddr)
-			if err := s.CreateNIC(nicID, &testLinkEndpoint{LinkEndpoint: linkEP, writeErr: test.linkErr}); err != nil {
-				t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
-			}
-
-			ep, err := s.GetNetworkEndpoint(nicID, arp.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, arp.ProtocolNumber, err)
-			}
-			linkRes, ok := ep.(stack.LinkAddressResolver)
-			if !ok {
-				t.Fatalf("expected %T to implement stack.LinkAddressResolver", ep)
-			}
-
-			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)
-				}
-			}
-
-			{
-				err := linkRes.LinkAddressRequest(remoteAddr, test.localAddr, test.remoteLinkAddr)
-				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
-					t.Fatalf("unexpected error from p.LinkAddressRequest(%s, %s, %s, _), (-want, +got):\n%s", remoteAddr, test.localAddr, test.remoteLinkAddr, diff)
-				}
-			}
-
-			if got := s.Stats().ARP.OutgoingRequestsSent.Value(); got != test.expectedRequestsSent {
-				t.Errorf("got s.Stats().ARP.OutgoingRequestsSent.Value() = %d, want = %d", got, test.expectedRequestsSent)
-			}
-			if got := s.Stats().ARP.OutgoingRequestInterfaceHasNoLocalAddressErrors.Value(); got != test.expectedRequestInterfaceHasNoLocalAddressErrors {
-				t.Errorf("got s.Stats().ARP.OutgoingRequestInterfaceHasNoLocalAddressErrors.Value() = %d, want = %d", got, test.expectedRequestInterfaceHasNoLocalAddressErrors)
-			}
-			if got := s.Stats().ARP.OutgoingRequestBadLocalAddressErrors.Value(); got != test.expectedRequestBadLocalAddressErrors {
-				t.Errorf("got s.Stats().ARP.OutgoingRequestBadLocalAddressErrors.Value() = %d, want = %d", got, test.expectedRequestBadLocalAddressErrors)
-			}
-			if got := s.Stats().ARP.OutgoingRequestsDropped.Value(); got != test.expectedRequestDroppedErrors {
-				t.Errorf("got s.Stats().ARP.OutgoingRequestsDropped.Value() = %d, want = %d", got, test.expectedRequestDroppedErrors)
-			}
-
-			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 := rep.Op(); got != header.ARPRequest {
-				t.Errorf("got Op = %d, want = %d", got, header.ARPRequest)
-			}
-			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)
-			}
-		})
-	}
-}
-
-func TestDADARPRequestPacket(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocolWithOptions(arp.Options{
-			DADConfigs: stack.DADConfigurations{
-				DupAddrDetectTransmits: 1,
-				RetransmitTimer:        time.Second,
-			},
-		}), ipv4.NewProtocol},
-	})
-	e := channel.New(1, defaultMTU, stackLinkAddr)
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
-	}
-
-	if res, err := s.CheckDuplicateAddress(nicID, header.IPv4ProtocolNumber, remoteAddr, func(stack.DADResult) {}); err != nil {
-		t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, header.IPv4ProtocolNumber, remoteAddr, err)
-	} else if res != stack.DADStarting {
-		t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, header.IPv4ProtocolNumber, remoteAddr, res, stack.DADStarting)
-	}
-
-	pkt, ok := e.ReadContext(context.Background())
-	if !ok {
-		t.Fatal("expected to send an ARP request")
-	}
-
-	if pkt.Route.RemoteLinkAddress != header.EthernetBroadcastAddress {
-		t.Errorf("got pkt.Route.RemoteLinkAddress = %s, want = %s", pkt.Route.RemoteLinkAddress, header.EthernetBroadcastAddress)
-	}
-
-	req := header.ARP(stack.PayloadSince(pkt.Pkt.NetworkHeader()))
-	if !req.IsValid() {
-		t.Errorf("got req.IsValid() = false, want = true")
-	}
-	if got := req.Op(); got != header.ARPRequest {
-		t.Errorf("got req.Op() = %d, want = %d", got, header.ARPRequest)
-	}
-	if got := tcpip.LinkAddress(req.HardwareAddressSender()); got != stackLinkAddr {
-		t.Errorf("got req.HardwareAddressSender() = %s, want = %s", got, stackLinkAddr)
-	}
-	if got := tcpip.Address(req.ProtocolAddressSender()); got != header.IPv4Any {
-		t.Errorf("got req.ProtocolAddressSender() = %s, want = %s", got, header.IPv4Any)
-	}
-	if got, want := tcpip.LinkAddress(req.HardwareAddressTarget()), tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"); got != want {
-		t.Errorf("got req.HardwareAddressTarget() = %s, want = %s", got, want)
-	}
-	if got := tcpip.Address(req.ProtocolAddressTarget()); got != remoteAddr {
-		t.Errorf("got req.ProtocolAddressTarget() = %s, want = %s", got, remoteAddr)
-	}
-}
diff --git a/pkg/tcpip/network/arp/stats_test.go b/pkg/tcpip/network/arp/stats_test.go
deleted file mode 100644
index e867b3c..0000000
--- a/pkg/tcpip/network/arp/stats_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 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
-
-import (
-	"reflect"
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/network/internal/testutil"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-var _ stack.NetworkInterface = (*testInterface)(nil)
-
-type testInterface struct {
-	stack.NetworkInterface
-	nicID tcpip.NICID
-}
-
-func (t *testInterface) ID() tcpip.NICID {
-	return t.nicID
-}
-
-func TestMultiCounterStatsInitialization(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
-	})
-	proto := s.NetworkProtocolInstance(ProtocolNumber).(*protocol)
-	var nic testInterface
-	ep := proto.NewEndpoint(&nic, nil).(*endpoint)
-	// At this point, the Stack's stats and the NetworkEndpoint's stats are
-	// expected to be bound by a MultiCounterStat.
-	refStack := s.Stats()
-	refEP := ep.stats.localStats
-	if err := testutil.ValidateMultiCounterStats(reflect.ValueOf(&ep.stats.arp).Elem(), []reflect.Value{reflect.ValueOf(&refEP.ARP).Elem(), reflect.ValueOf(&refStack.ARP).Elem()}); err != nil {
-		t.Error(err)
-	}
-}
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 0000000..9467fe2
--- /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/internal/fragmentation/fragmentation_state_autogen.go b/pkg/tcpip/network/internal/fragmentation/fragmentation_state_autogen.go
new file mode 100644
index 0000000..21c5774
--- /dev/null
+++ b/pkg/tcpip/network/internal/fragmentation/fragmentation_state_autogen.go
@@ -0,0 +1,68 @@
+// automatically generated by stateify.
+
+package fragmentation
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (l *reassemblerList) StateTypeName() string {
+	return "pkg/tcpip/network/internal/fragmentation.reassemblerList"
+}
+
+func (l *reassemblerList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *reassemblerList) beforeSave() {}
+
+// +checklocksignore
+func (l *reassemblerList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *reassemblerList) afterLoad() {}
+
+// +checklocksignore
+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/internal/fragmentation.reassemblerEntry"
+}
+
+func (e *reassemblerEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *reassemblerEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *reassemblerEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *reassemblerEntry) afterLoad() {}
+
+// +checklocksignore
+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/internal/fragmentation/fragmentation_test.go b/pkg/tcpip/network/internal/fragmentation/fragmentation_test.go
deleted file mode 100644
index 7daf64b..0000000
--- a/pkg/tcpip/network/internal/fragmentation/fragmentation_test.go
+++ /dev/null
@@ -1,636 +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"
-	"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/internal/testutil"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-// 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)
-}
-
-func pkt(size int, pieces ...string) *stack.PacketBuffer {
-	return stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: vv(size, pieces...),
-	})
-}
-
-type processInput struct {
-	id    FragmentID
-	first uint16
-	last  uint16
-	more  bool
-	proto uint8
-	pkt   *stack.PacketBuffer
-}
-
-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, pkt: pkt(2, "01")},
-			{id: FragmentID{ID: 0}, first: 2, last: 3, more: false, pkt: pkt(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, pkt: pkt(2, "01")},
-			{id: FragmentID{ID: 0}, first: 2, last: 3, more: false, proto: 17, pkt: pkt(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, pkt: pkt(2, "01")},
-			{id: FragmentID{ID: 1}, first: 0, last: 1, more: true, pkt: pkt(2, "ab")},
-			{id: FragmentID{ID: 1}, first: 2, last: 3, more: false, pkt: pkt(2, "cd")},
-			{id: FragmentID{ID: 0}, first: 2, last: 3, more: false, pkt: pkt(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{}, nil)
-			firstFragmentProto := c.in[0].proto
-			for i, in := range c.in {
-				resPkt, proto, done, err := f.Process(in.id, in.first, in.last, in.more, in.proto, in.pkt)
-				if err != nil {
-					t.Fatalf("f.Process(%+v, %d, %d, %t, %d, %#v) failed: %s",
-						in.id, in.first, in.last, in.more, in.proto, in.pkt, err)
-				}
-				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 diff := cmp.Diff(c.out[i].vv.ToOwnedView(), resPkt.Data().AsRange().ToOwnedView()); diff != "" {
-						t.Errorf("got Process(%+v, %d, %d, %t, %d, %#v) result mismatch (-want, +got):\n%s",
-							in.id, in.first, in.last, in.more, in.proto, in.pkt, diff)
-					}
-					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
-
-		// memSizeAfterEvent is the expected memory size of the fragmentation
-		// instance after the event.
-		memSizeAfterEvent int
-	}
-
-	memSizeOfFrags := func(frags ...*fragment) int {
-		var size int
-		for _, frag := range frags {
-			size += pkt(len(frag.data), frag.data).MemSize()
-		}
-		return size
-	}
-
-	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,
-					memSizeAfterEvent: memSizeOfFrags(half1),
-				},
-				{
-					name:              "half2",
-					fragment:          half2,
-					expectDone:        true,
-					memSizeAfterEvent: 0,
-				},
-			},
-		},
-		{
-			name: "half1 timeout, half2 timeout",
-			events: []event{
-				{
-					name:              "half1",
-					fragment:          half1,
-					expectDone:        false,
-					memSizeAfterEvent: memSizeOfFrags(half1),
-				},
-				{
-					name:              "half1 just before reassembly timeout",
-					clockAdvance:      reassemblyTimeout - 1,
-					memSizeAfterEvent: memSizeOfFrags(half1),
-				},
-				{
-					name:              "half1 reassembly timeout",
-					clockAdvance:      1,
-					memSizeAfterEvent: 0,
-				},
-				{
-					name:              "half2",
-					fragment:          half2,
-					expectDone:        false,
-					memSizeAfterEvent: memSizeOfFrags(half2),
-				},
-				{
-					name:              "half2 just before reassembly timeout",
-					clockAdvance:      reassemblyTimeout - 1,
-					memSizeAfterEvent: memSizeOfFrags(half2),
-				},
-				{
-					name:              "half2 reassembly timeout",
-					clockAdvance:      1,
-					memSizeAfterEvent: 0,
-				},
-			},
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			clock := faketime.NewManualClock()
-			f := NewFragmentation(minBlockSize, HighFragThreshold, LowFragThreshold, reassemblyTimeout, clock, nil)
-			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, pkt(len(frag.data), frag.data))
-					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.memSize, event.memSizeAfterEvent; got != want {
-					t.Errorf("%s: got f.memSize = %d, want = %d", event.name, got, want)
-				}
-			}
-		})
-	}
-}
-
-func TestMemoryLimits(t *testing.T) {
-	lowLimit := pkt(1, "0").MemSize()
-	highLimit := 3 * lowLimit // Allow at most 3 such packets.
-	f := NewFragmentation(minBlockSize, highLimit, lowLimit, reassembleTimeout, &faketime.NullClock{}, nil)
-	// Send first fragment with id = 0.
-	f.Process(FragmentID{ID: 0}, 0, 0, true, 0xFF, pkt(1, "0"))
-	// Send first fragment with id = 1.
-	f.Process(FragmentID{ID: 1}, 0, 0, true, 0xFF, pkt(1, "1"))
-	// Send first fragment with id = 2.
-	f.Process(FragmentID{ID: 2}, 0, 0, true, 0xFF, pkt(1, "2"))
-
-	// 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, pkt(1, "3"))
-
-	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) {
-	memSize := pkt(1, "0").MemSize()
-	f := NewFragmentation(minBlockSize, memSize, 0, reassembleTimeout, &faketime.NullClock{}, nil)
-	// Send first fragment with id = 0.
-	f.Process(FragmentID{}, 0, 0, true, 0xFF, pkt(1, "0"))
-	// Send the same packet again.
-	f.Process(FragmentID{}, 0, 0, true, 0xFF, pkt(1, "0"))
-
-	if got, want := f.memSize, memSize; 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",
-			err:       ErrInvalidArgs,
-		},
-		{
-			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{}, nil)
-			_, _, done, err := f.Process(FragmentID{}, test.first, test.last, test.more, 0, pkt(len(test.data), test.data))
-			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)
-			originalPayload := stack.PayloadSince(pkt.TransportHeader())
-			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.AppendViews(fragPkt.Data().Views())
-				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); diff != "" {
-				t.Errorf("reassembledPayload mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-type testTimeoutHandler struct {
-	pkt *stack.PacketBuffer
-}
-
-func (h *testTimeoutHandler) OnReassemblyTimeout(pkt *stack.PacketBuffer) {
-	h.pkt = pkt
-}
-
-func TestTimeoutHandler(t *testing.T) {
-	const (
-		proto = 99
-	)
-
-	pk1 := pkt(1, "1")
-	pk2 := pkt(1, "2")
-
-	type processParam struct {
-		first uint16
-		last  uint16
-		more  bool
-		pkt   *stack.PacketBuffer
-	}
-
-	tests := []struct {
-		name      string
-		params    []processParam
-		wantError bool
-		wantPkt   *stack.PacketBuffer
-	}{
-		{
-			name: "onTimeout runs",
-			params: []processParam{
-				{
-					first: 0,
-					last:  0,
-					more:  true,
-					pkt:   pk1,
-				},
-			},
-			wantError: false,
-			wantPkt:   pk1,
-		},
-		{
-			name: "no first fragment",
-			params: []processParam{
-				{
-					first: 1,
-					last:  1,
-					more:  true,
-					pkt:   pk1,
-				},
-			},
-			wantError: false,
-			wantPkt:   nil,
-		},
-		{
-			name: "second pkt is ignored",
-			params: []processParam{
-				{
-					first: 0,
-					last:  0,
-					more:  true,
-					pkt:   pk1,
-				},
-				{
-					first: 0,
-					last:  0,
-					more:  true,
-					pkt:   pk2,
-				},
-			},
-			wantError: false,
-			wantPkt:   pk1,
-		},
-		{
-			name: "invalid args - first is greater than last",
-			params: []processParam{
-				{
-					first: 1,
-					last:  0,
-					more:  true,
-					pkt:   pk1,
-				},
-			},
-			wantError: true,
-			wantPkt:   nil,
-		},
-	}
-
-	id := FragmentID{ID: 0}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			handler := &testTimeoutHandler{pkt: nil}
-
-			f := NewFragmentation(minBlockSize, HighFragThreshold, LowFragThreshold, reassembleTimeout, &faketime.NullClock{}, handler)
-
-			for _, p := range test.params {
-				if _, _, _, err := f.Process(id, p.first, p.last, p.more, proto, p.pkt); err != nil && !test.wantError {
-					t.Errorf("f.Process error = %s", err)
-				}
-			}
-			if !test.wantError {
-				r, ok := f.reassemblers[id]
-				if !ok {
-					t.Fatal("Reassembler not found")
-				}
-				f.release(r, true)
-			}
-			switch {
-			case handler.pkt != nil && test.wantPkt == nil:
-				t.Errorf("got handler.pkt = not nil (pkt.Data = %x), want = nil", handler.pkt.Data().AsRange().ToOwnedView())
-			case handler.pkt == nil && test.wantPkt != nil:
-				t.Errorf("got handler.pkt = nil, want = not nil (pkt.Data = %x)", test.wantPkt.Data().AsRange().ToOwnedView())
-			case handler.pkt != nil && test.wantPkt != nil:
-				if diff := cmp.Diff(test.wantPkt.Data().AsRange().ToOwnedView(), handler.pkt.Data().AsRange().ToOwnedView()); diff != "" {
-					t.Errorf("pkt.Data mismatch (-want, +got):\n%s", diff)
-				}
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/network/internal/fragmentation/reassembler_list.go b/pkg/tcpip/network/internal/fragmentation/reassembler_list.go
new file mode 100644
index 0000000..673bb11
--- /dev/null
+++ b/pkg/tcpip/network/internal/fragmentation/reassembler_list.go
@@ -0,0 +1,221 @@
+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.
+//
+//go:nosplit
+func (l *reassemblerList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *reassemblerList) Front() *reassembler {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *reassemblerList) Back() *reassembler {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+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.
+//
+//go:nosplit
+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.
+//
+//go:nosplit
+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.
+//
+//go:nosplit
+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.
+//
+//go:nosplit
+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.
+//
+//go:nosplit
+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.
+//
+//go:nosplit
+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.
+//
+//go:nosplit
+func (e *reassemblerEntry) Next() *reassembler {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *reassemblerEntry) Prev() *reassembler {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *reassemblerEntry) SetNext(elem *reassembler) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *reassemblerEntry) SetPrev(elem *reassembler) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/network/internal/fragmentation/reassembler_test.go b/pkg/tcpip/network/internal/fragmentation/reassembler_test.go
deleted file mode 100644
index cfd9f00..0000000
--- a/pkg/tcpip/network/internal/fragmentation/reassembler_test.go
+++ /dev/null
@@ -1,233 +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 (
-	"bytes"
-	"math"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-	"gvisor.dev/gvisor/pkg/tcpip/faketime"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-type processParams struct {
-	first     uint16
-	last      uint16
-	more      bool
-	pkt       *stack.PacketBuffer
-	wantDone  bool
-	wantError error
-}
-
-func TestReassemblerProcess(t *testing.T) {
-	const proto = 99
-
-	v := func(size int) buffer.View {
-		payload := buffer.NewView(size)
-		for i := 1; i < size; i++ {
-			payload[i] = uint8(i) * 3
-		}
-		return payload
-	}
-
-	pkt := func(sizes ...int) *stack.PacketBuffer {
-		var vv buffer.VectorisedView
-		for _, size := range sizes {
-			vv.AppendView(v(size))
-		}
-		return stack.NewPacketBuffer(stack.PacketBufferOptions{
-			Data: vv,
-		})
-	}
-
-	var tests = []struct {
-		name    string
-		params  []processParams
-		want    []hole
-		wantPkt *stack.PacketBuffer
-	}{
-		{
-			name:   "No fragments",
-			params: nil,
-			want:   []hole{{first: 0, last: math.MaxUint16, filled: false, final: true}},
-		},
-		{
-			name:   "One fragment at beginning",
-			params: []processParams{{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil}},
-			want: []hole{
-				{first: 0, last: 1, filled: true, final: false, pkt: pkt(2)},
-				{first: 2, last: math.MaxUint16, filled: false, final: true},
-			},
-		},
-		{
-			name:   "One fragment in the middle",
-			params: []processParams{{first: 1, last: 2, more: true, pkt: pkt(2), wantDone: false, wantError: nil}},
-			want: []hole{
-				{first: 1, last: 2, filled: true, final: false, pkt: pkt(2)},
-				{first: 0, last: 0, filled: false, final: false},
-				{first: 3, last: math.MaxUint16, filled: false, final: true},
-			},
-		},
-		{
-			name:   "One fragment at the end",
-			params: []processParams{{first: 1, last: 2, more: false, pkt: pkt(2), wantDone: false, wantError: nil}},
-			want: []hole{
-				{first: 1, last: 2, filled: true, final: true, pkt: pkt(2)},
-				{first: 0, last: 0, filled: false},
-			},
-		},
-		{
-			name:   "One fragment completing a packet",
-			params: []processParams{{first: 0, last: 1, more: false, pkt: pkt(2), wantDone: true, wantError: nil}},
-			want: []hole{
-				{first: 0, last: 1, filled: true, final: true},
-			},
-			wantPkt: pkt(2),
-		},
-		{
-			name: "Two fragments completing a packet",
-			params: []processParams{
-				{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
-				{first: 2, last: 3, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
-			},
-			want: []hole{
-				{first: 0, last: 1, filled: true, final: false},
-				{first: 2, last: 3, filled: true, final: true},
-			},
-			wantPkt: pkt(2, 2),
-		},
-		{
-			name: "Two fragments completing a packet with a duplicate",
-			params: []processParams{
-				{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
-				{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
-				{first: 2, last: 3, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
-			},
-			want: []hole{
-				{first: 0, last: 1, filled: true, final: false},
-				{first: 2, last: 3, filled: true, final: true},
-			},
-			wantPkt: pkt(2, 2),
-		},
-		{
-			name: "Two fragments completing a packet with a partial duplicate",
-			params: []processParams{
-				{first: 0, last: 3, more: true, pkt: pkt(4), wantDone: false, wantError: nil},
-				{first: 1, last: 2, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
-				{first: 4, last: 5, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
-			},
-			want: []hole{
-				{first: 0, last: 3, filled: true, final: false},
-				{first: 4, last: 5, filled: true, final: true},
-			},
-			wantPkt: pkt(4, 2),
-		},
-		{
-			name: "Two overlapping fragments",
-			params: []processParams{
-				{first: 0, last: 10, more: true, pkt: pkt(11), wantDone: false, wantError: nil},
-				{first: 5, last: 15, more: false, pkt: pkt(11), wantDone: false, wantError: ErrFragmentOverlap},
-			},
-			want: []hole{
-				{first: 0, last: 10, filled: true, final: false, pkt: pkt(11)},
-				{first: 11, last: math.MaxUint16, filled: false, final: true},
-			},
-		},
-		{
-			name: "Two final fragments with different ends",
-			params: []processParams{
-				{first: 10, last: 14, more: false, pkt: pkt(5), wantDone: false, wantError: nil},
-				{first: 0, last: 9, more: false, pkt: pkt(10), wantDone: false, wantError: ErrFragmentConflict},
-			},
-			want: []hole{
-				{first: 10, last: 14, filled: true, final: true, pkt: pkt(5)},
-				{first: 0, last: 9, filled: false, final: false},
-			},
-		},
-		{
-			name: "Two final fragments - duplicate",
-			params: []processParams{
-				{first: 5, last: 14, more: false, pkt: pkt(10), wantDone: false, wantError: nil},
-				{first: 10, last: 14, more: false, pkt: pkt(5), wantDone: false, wantError: nil},
-			},
-			want: []hole{
-				{first: 5, last: 14, filled: true, final: true, pkt: pkt(10)},
-				{first: 0, last: 4, filled: false, final: false},
-			},
-		},
-		{
-			name: "Two final fragments - duplicate, with different ends",
-			params: []processParams{
-				{first: 5, last: 14, more: false, pkt: pkt(10), wantDone: false, wantError: nil},
-				{first: 10, last: 13, more: false, pkt: pkt(4), wantDone: false, wantError: ErrFragmentConflict},
-			},
-			want: []hole{
-				{first: 5, last: 14, filled: true, final: true, pkt: pkt(10)},
-				{first: 0, last: 4, filled: false, final: false},
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			r := newReassembler(FragmentID{}, &faketime.NullClock{})
-			var resPkt *stack.PacketBuffer
-			var isDone bool
-			for _, param := range test.params {
-				pkt, _, done, _, err := r.process(param.first, param.last, param.more, proto, param.pkt)
-				if done != param.wantDone || err != param.wantError {
-					t.Errorf("got r.process(%d, %d, %t, %d, _) = (_, _, %t, _, %v), want = (%t, %v)", param.first, param.last, param.more, proto, done, err, param.wantDone, param.wantError)
-				}
-				if done {
-					resPkt = pkt
-					isDone = true
-				}
-			}
-
-			ignorePkt := func(a, b *stack.PacketBuffer) bool { return true }
-			cmpPktData := func(a, b *stack.PacketBuffer) bool {
-				if a == nil || b == nil {
-					return a == b
-				}
-				return bytes.Equal(a.Data().AsRange().ToOwnedView(), b.Data().AsRange().ToOwnedView())
-			}
-
-			if isDone {
-				if diff := cmp.Diff(
-					test.want, r.holes,
-					cmp.AllowUnexported(hole{}),
-					// Do not compare pkt in hole. Data will be altered.
-					cmp.Comparer(ignorePkt),
-				); diff != "" {
-					t.Errorf("r.holes mismatch (-want +got):\n%s", diff)
-				}
-				if diff := cmp.Diff(test.wantPkt, resPkt, cmp.Comparer(cmpPktData)); diff != "" {
-					t.Errorf("Reassembled pkt mismatch (-want +got):\n%s", diff)
-				}
-			} else {
-				if diff := cmp.Diff(
-					test.want, r.holes,
-					cmp.AllowUnexported(hole{}),
-					cmp.Comparer(cmpPktData),
-				); diff != "" {
-					t.Errorf("r.holes mismatch (-want +got):\n%s", diff)
-				}
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go b/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go
deleted file mode 100644
index a22b712..0000000
--- a/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go
+++ /dev/null
@@ -1,381 +0,0 @@
-// Copyright 2021 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 (
-	"bytes"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/sync"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/faketime"
-	"gvisor.dev/gvisor/pkg/tcpip/network/internal/ip"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-type mockDADProtocol struct {
-	t *testing.T
-
-	mu struct {
-		sync.Mutex
-
-		dad        ip.DAD
-		sentNonces map[tcpip.Address][][]byte
-	}
-}
-
-func (m *mockDADProtocol) init(t *testing.T, c stack.DADConfigurations, opts ip.DADOptions) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-
-	m.t = t
-	opts.Protocol = m
-	m.mu.dad.Init(&m.mu, c, opts)
-	m.initLocked()
-}
-
-func (m *mockDADProtocol) initLocked() {
-	m.mu.sentNonces = make(map[tcpip.Address][][]byte)
-}
-
-func (m *mockDADProtocol) SendDADMessage(addr tcpip.Address, nonce []byte) tcpip.Error {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.sentNonces[addr] = append(m.mu.sentNonces[addr], nonce)
-	return nil
-}
-
-func (m *mockDADProtocol) check(addrs []tcpip.Address) string {
-	sentNonces := make(map[tcpip.Address][][]byte)
-	for _, a := range addrs {
-		sentNonces[a] = append(sentNonces[a], nil)
-	}
-
-	return m.checkWithNonce(sentNonces)
-}
-
-func (m *mockDADProtocol) checkWithNonce(expectedSentNonces map[tcpip.Address][][]byte) string {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-
-	diff := cmp.Diff(expectedSentNonces, m.mu.sentNonces)
-	m.initLocked()
-	return diff
-}
-
-func (m *mockDADProtocol) checkDuplicateAddress(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	return m.mu.dad.CheckDuplicateAddressLocked(addr, h)
-}
-
-func (m *mockDADProtocol) stop(addr tcpip.Address, reason stack.DADResult) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.dad.StopLocked(addr, reason)
-}
-
-func (m *mockDADProtocol) extendIfNonceEqual(addr tcpip.Address, nonce []byte) ip.ExtendIfNonceEqualLockedDisposition {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	return m.mu.dad.ExtendIfNonceEqualLocked(addr, nonce)
-}
-
-func (m *mockDADProtocol) setConfigs(c stack.DADConfigurations) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.dad.SetConfigsLocked(c)
-}
-
-const (
-	addr1 = tcpip.Address("\x01")
-	addr2 = tcpip.Address("\x02")
-	addr3 = tcpip.Address("\x03")
-	addr4 = tcpip.Address("\x04")
-)
-
-type dadResult struct {
-	Addr tcpip.Address
-	R    stack.DADResult
-}
-
-func handler(ch chan<- dadResult, a tcpip.Address) func(stack.DADResult) {
-	return func(r stack.DADResult) {
-		ch <- dadResult{Addr: a, R: r}
-	}
-}
-
-func TestDADCheckDuplicateAddress(t *testing.T) {
-	var dad mockDADProtocol
-	clock := faketime.NewManualClock()
-	dad.init(t, stack.DADConfigurations{}, ip.DADOptions{
-		Clock: clock,
-	})
-
-	ch := make(chan dadResult, 2)
-
-	// DAD should initially be disabled.
-	if res := dad.checkDuplicateAddress(addr1, handler(nil, "")); res != stack.DADDisabled {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADDisabled)
-	}
-	// Wait for any initially fired timers to complete.
-	clock.Advance(0)
-	if diff := dad.check(nil); diff != "" {
-		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
-	}
-
-	// Enable and request DAD.
-	dadConfigs1 := stack.DADConfigurations{
-		DupAddrDetectTransmits: 1,
-		RetransmitTimer:        time.Second,
-	}
-	dad.setConfigs(dadConfigs1)
-	if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
-	}
-	clock.Advance(0)
-	if diff := dad.check([]tcpip.Address{addr1}); diff != "" {
-		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
-	}
-	// The second request for DAD on the same address should use the original
-	// request since it has not completed yet.
-	if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADAlreadyRunning {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADAlreadyRunning)
-	}
-	clock.Advance(0)
-	if diff := dad.check(nil); diff != "" {
-		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
-	}
-
-	dadConfigs2 := stack.DADConfigurations{
-		DupAddrDetectTransmits: 2,
-		RetransmitTimer:        time.Second,
-	}
-	dad.setConfigs(dadConfigs2)
-	// A new address should start a new DAD process.
-	if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
-	}
-	clock.Advance(0)
-	if diff := dad.check([]tcpip.Address{addr2}); diff != "" {
-		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
-	}
-
-	// Make sure DAD for addr1 only resolves after the expected timeout.
-	const delta = time.Nanosecond
-	dadConfig1Duration := time.Duration(dadConfigs1.DupAddrDetectTransmits) * dadConfigs1.RetransmitTimer
-	clock.Advance(dadConfig1Duration - delta)
-	select {
-	case r := <-ch:
-		t.Fatalf("unexpectedly got a DAD result before the expected timeout of %s; r = %#v", dadConfig1Duration, r)
-	default:
-	}
-	clock.Advance(delta)
-	for i := 0; i < 2; i++ {
-		if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
-			t.Errorf("(i=%d) dad result mismatch (-want +got):\n%s", i, diff)
-		}
-	}
-
-	// Make sure DAD for addr2 only resolves after the expected timeout.
-	dadConfig2Duration := time.Duration(dadConfigs2.DupAddrDetectTransmits) * dadConfigs2.RetransmitTimer
-	clock.Advance(dadConfig2Duration - dadConfig1Duration - delta)
-	select {
-	case r := <-ch:
-		t.Fatalf("unexpectedly got a DAD result before the expected timeout of %s; r = %#v", dadConfig2Duration, r)
-	default:
-	}
-	clock.Advance(delta)
-	if diff := cmp.Diff(dadResult{Addr: addr2, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
-		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should be able to restart DAD for addr2 after it resolved.
-	if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
-	}
-	clock.Advance(0)
-	if diff := dad.check([]tcpip.Address{addr2, addr2}); diff != "" {
-		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
-	}
-	clock.Advance(dadConfig2Duration)
-	if diff := cmp.Diff(dadResult{Addr: addr2, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
-		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should not have anymore results.
-	select {
-	case r := <-ch:
-		t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r)
-	default:
-	}
-}
-
-func TestDADStop(t *testing.T) {
-	var dad mockDADProtocol
-	clock := faketime.NewManualClock()
-	dadConfigs := stack.DADConfigurations{
-		DupAddrDetectTransmits: 1,
-		RetransmitTimer:        time.Second,
-	}
-	dad.init(t, dadConfigs, ip.DADOptions{
-		Clock: clock,
-	})
-
-	ch := make(chan dadResult, 1)
-
-	if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
-	}
-	if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
-	}
-	if res := dad.checkDuplicateAddress(addr3, handler(ch, addr3)); res != stack.DADStarting {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
-	}
-	clock.Advance(0)
-	if diff := dad.check([]tcpip.Address{addr1, addr2, addr3}); diff != "" {
-		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
-	}
-
-	dad.stop(addr1, &stack.DADAborted{})
-	if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADAborted{}}, <-ch); diff != "" {
-		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
-	}
-
-	dad.stop(addr2, &stack.DADDupAddrDetected{})
-	if diff := cmp.Diff(dadResult{Addr: addr2, R: &stack.DADDupAddrDetected{}}, <-ch); diff != "" {
-		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
-	}
-
-	dadResolutionDuration := time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer
-	clock.Advance(dadResolutionDuration)
-	if diff := cmp.Diff(dadResult{Addr: addr3, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
-		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should be able to restart DAD for an address we stopped DAD on.
-	if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
-		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
-	}
-	clock.Advance(0)
-	if diff := dad.check([]tcpip.Address{addr1}); diff != "" {
-		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
-	}
-	clock.Advance(dadResolutionDuration)
-	if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
-		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should not have anymore updates.
-	select {
-	case r := <-ch:
-		t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r)
-	default:
-	}
-}
-
-func TestNonce(t *testing.T) {
-	const (
-		nonceSize = 2
-
-		extendRequestAttempts = 2
-
-		dupAddrDetectTransmits = 2
-		extendTransmits        = 5
-	)
-
-	var secureRNGBytes [nonceSize * (dupAddrDetectTransmits + extendTransmits)]byte
-	for i := range secureRNGBytes {
-		secureRNGBytes[i] = byte(i)
-	}
-
-	tests := []struct {
-		name                string
-		mockedReceivedNonce []byte
-		expectedResults     [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition
-		expectedTransmits   int
-	}{
-		{
-			name:                "not matching",
-			mockedReceivedNonce: []byte{0, 0},
-			expectedResults:     [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition{ip.NonceNotEqual, ip.NonceNotEqual},
-			expectedTransmits:   dupAddrDetectTransmits,
-		},
-		{
-			name:                "matching nonce",
-			mockedReceivedNonce: secureRNGBytes[:nonceSize],
-			expectedResults:     [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition{ip.Extended, ip.AlreadyExtended},
-			expectedTransmits:   dupAddrDetectTransmits + extendTransmits,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			var dad mockDADProtocol
-			clock := faketime.NewManualClock()
-			dadConfigs := stack.DADConfigurations{
-				DupAddrDetectTransmits: dupAddrDetectTransmits,
-				RetransmitTimer:        time.Second,
-			}
-
-			var secureRNG bytes.Reader
-			secureRNG.Reset(secureRNGBytes[:])
-			dad.init(t, dadConfigs, ip.DADOptions{
-				Clock:              clock,
-				SecureRNG:          &secureRNG,
-				NonceSize:          nonceSize,
-				ExtendDADTransmits: extendTransmits,
-			})
-
-			ch := make(chan dadResult, 1)
-			if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
-				t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
-			}
-
-			clock.Advance(0)
-			for i, want := range test.expectedResults {
-				if got := dad.extendIfNonceEqual(addr1, test.mockedReceivedNonce); got != want {
-					t.Errorf("(i=%d) got dad.extendIfNonceEqual(%s, _) = %d, want = %d", i, addr1, got, want)
-				}
-			}
-
-			for i := 0; i < test.expectedTransmits; i++ {
-				if diff := dad.checkWithNonce(map[tcpip.Address][][]byte{
-					addr1: {
-						secureRNGBytes[nonceSize*i:][:nonceSize],
-					},
-				}); diff != "" {
-					t.Errorf("(i=%d) dad check mismatch (-want +got):\n%s", i, diff)
-				}
-
-				clock.Advance(dadConfigs.RetransmitTimer)
-			}
-
-			if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
-				t.Errorf("dad result mismatch (-want +got):\n%s", diff)
-			}
-
-			// Should not have anymore updates.
-			select {
-			case r := <-ch:
-				t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r)
-			default:
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/network/internal/ip/generic_multicast_protocol_test.go b/pkg/tcpip/network/internal/ip/generic_multicast_protocol_test.go
deleted file mode 100644
index 381460c..0000000
--- a/pkg/tcpip/network/internal/ip/generic_multicast_protocol_test.go
+++ /dev/null
@@ -1,805 +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 ip_test
-
-import (
-	"math/rand"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/sync"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/faketime"
-	"gvisor.dev/gvisor/pkg/tcpip/network/internal/ip"
-)
-
-const maxUnsolicitedReportDelay = time.Second
-
-var _ ip.MulticastGroupProtocol = (*mockMulticastGroupProtocol)(nil)
-
-type mockMulticastGroupProtocolProtectedFields struct {
-	sync.RWMutex
-
-	genericMulticastGroup    ip.GenericMulticastProtocolState
-	sendReportGroupAddrCount map[tcpip.Address]int
-	sendLeaveGroupAddrCount  map[tcpip.Address]int
-	makeQueuePackets         bool
-	disabled                 bool
-}
-
-type mockMulticastGroupProtocol struct {
-	t *testing.T
-
-	mu mockMulticastGroupProtocolProtectedFields
-}
-
-func (m *mockMulticastGroupProtocol) init(opts ip.GenericMulticastProtocolOptions) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.initLocked()
-	opts.Protocol = m
-	m.mu.genericMulticastGroup.Init(&m.mu.RWMutex, opts)
-}
-
-func (m *mockMulticastGroupProtocol) initLocked() {
-	m.mu.sendReportGroupAddrCount = make(map[tcpip.Address]int)
-	m.mu.sendLeaveGroupAddrCount = make(map[tcpip.Address]int)
-}
-
-func (m *mockMulticastGroupProtocol) setEnabled(v bool) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.disabled = !v
-}
-
-func (m *mockMulticastGroupProtocol) setQueuePackets(v bool) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.makeQueuePackets = v
-}
-
-func (m *mockMulticastGroupProtocol) joinGroup(addr tcpip.Address) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.genericMulticastGroup.JoinGroupLocked(addr)
-}
-
-func (m *mockMulticastGroupProtocol) leaveGroup(addr tcpip.Address) bool {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	return m.mu.genericMulticastGroup.LeaveGroupLocked(addr)
-}
-
-func (m *mockMulticastGroupProtocol) handleReport(addr tcpip.Address) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.genericMulticastGroup.HandleReportLocked(addr)
-}
-
-func (m *mockMulticastGroupProtocol) handleQuery(addr tcpip.Address, maxRespTime time.Duration) {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.genericMulticastGroup.HandleQueryLocked(addr, maxRespTime)
-}
-
-func (m *mockMulticastGroupProtocol) isLocallyJoined(addr tcpip.Address) bool {
-	m.mu.RLock()
-	defer m.mu.RUnlock()
-	return m.mu.genericMulticastGroup.IsLocallyJoinedRLocked(addr)
-}
-
-func (m *mockMulticastGroupProtocol) makeAllNonMember() {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.genericMulticastGroup.MakeAllNonMemberLocked()
-}
-
-func (m *mockMulticastGroupProtocol) initializeGroups() {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.genericMulticastGroup.InitializeGroupsLocked()
-}
-
-func (m *mockMulticastGroupProtocol) sendQueuedReports() {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-	m.mu.genericMulticastGroup.SendQueuedReportsLocked()
-}
-
-// Enabled implements ip.MulticastGroupProtocol.
-//
-// Precondition: m.mu must be read locked.
-func (m *mockMulticastGroupProtocol) Enabled() bool {
-	if m.mu.TryLock() {
-		m.mu.Unlock()
-		m.t.Fatal("got write lock, expected to not take the lock; generic multicast protocol must take the read or write lock before calling Enabled")
-	}
-
-	return !m.mu.disabled
-}
-
-// SendReport implements ip.MulticastGroupProtocol.
-//
-// Precondition: m.mu must be locked.
-func (m *mockMulticastGroupProtocol) SendReport(groupAddress tcpip.Address) (bool, tcpip.Error) {
-	if m.mu.TryLock() {
-		m.mu.Unlock()
-		m.t.Fatalf("got write lock, expected to not take the lock; generic multicast protocol must take the write lock before sending report for %s", groupAddress)
-	}
-	if m.mu.TryRLock() {
-		m.mu.RUnlock()
-		m.t.Fatalf("got read lock, expected to not take the lock; generic multicast protocol must take the write lock before sending report for %s", groupAddress)
-	}
-
-	m.mu.sendReportGroupAddrCount[groupAddress]++
-	return !m.mu.makeQueuePackets, nil
-}
-
-// SendLeave implements ip.MulticastGroupProtocol.
-//
-// Precondition: m.mu must be locked.
-func (m *mockMulticastGroupProtocol) SendLeave(groupAddress tcpip.Address) tcpip.Error {
-	if m.mu.TryLock() {
-		m.mu.Unlock()
-		m.t.Fatalf("got write lock, expected to not take the lock; generic multicast protocol must take the write lock before sending leave for %s", groupAddress)
-	}
-	if m.mu.TryRLock() {
-		m.mu.RUnlock()
-		m.t.Fatalf("got read lock, expected to not take the lock; generic multicast protocol must take the write lock before sending leave for %s", groupAddress)
-	}
-
-	m.mu.sendLeaveGroupAddrCount[groupAddress]++
-	return nil
-}
-
-func (m *mockMulticastGroupProtocol) check(sendReportGroupAddresses []tcpip.Address, sendLeaveGroupAddresses []tcpip.Address) string {
-	m.mu.Lock()
-	defer m.mu.Unlock()
-
-	sendReportGroupAddrCount := make(map[tcpip.Address]int)
-	for _, a := range sendReportGroupAddresses {
-		sendReportGroupAddrCount[a] = 1
-	}
-
-	sendLeaveGroupAddrCount := make(map[tcpip.Address]int)
-	for _, a := range sendLeaveGroupAddresses {
-		sendLeaveGroupAddrCount[a] = 1
-	}
-
-	diff := cmp.Diff(
-		&mockMulticastGroupProtocol{
-			mu: mockMulticastGroupProtocolProtectedFields{
-				sendReportGroupAddrCount: sendReportGroupAddrCount,
-				sendLeaveGroupAddrCount:  sendLeaveGroupAddrCount,
-			},
-		},
-		m,
-		cmp.AllowUnexported(mockMulticastGroupProtocol{}),
-		cmp.AllowUnexported(mockMulticastGroupProtocolProtectedFields{}),
-		// ignore mockMulticastGroupProtocol.mu and mockMulticastGroupProtocol.t
-		cmp.FilterPath(
-			func(p cmp.Path) bool {
-				switch p.Last().String() {
-				case ".RWMutex", ".t", ".makeQueuePackets", ".disabled", ".genericMulticastGroup":
-					return true
-				}
-				return false
-			},
-			cmp.Ignore(),
-		),
-	)
-	m.initLocked()
-	return diff
-}
-
-func TestJoinGroup(t *testing.T) {
-	tests := []struct {
-		name              string
-		addr              tcpip.Address
-		shouldSendReports bool
-	}{
-		{
-			name:              "Normal group",
-			addr:              addr1,
-			shouldSendReports: true,
-		},
-		{
-			name:              "All-nodes group",
-			addr:              addr2,
-			shouldSendReports: false,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			mgp := mockMulticastGroupProtocol{t: t}
-			clock := faketime.NewManualClock()
-
-			mgp.init(ip.GenericMulticastProtocolOptions{
-				Rand:                      rand.New(rand.NewSource(0)),
-				Clock:                     clock,
-				MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
-				AllNodesAddress:           addr2,
-			})
-
-			// Joining a group should send a report immediately and another after
-			// a random interval between 0 and the maximum unsolicited report delay.
-			mgp.joinGroup(test.addr)
-			if test.shouldSendReports {
-				if diff := mgp.check([]tcpip.Address{test.addr} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-					t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-				}
-
-				// Generic multicast protocol timers are expected to take the job mutex.
-				clock.Advance(maxUnsolicitedReportDelay)
-				if diff := mgp.check([]tcpip.Address{test.addr} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-					t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-				}
-			}
-
-			// Should have no more messages to send.
-			clock.Advance(time.Hour)
-			if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-func TestLeaveGroup(t *testing.T) {
-	tests := []struct {
-		name               string
-		addr               tcpip.Address
-		shouldSendMessages bool
-	}{
-		{
-			name:               "Normal group",
-			addr:               addr1,
-			shouldSendMessages: true,
-		},
-		{
-			name:               "All-nodes group",
-			addr:               addr2,
-			shouldSendMessages: false,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			mgp := mockMulticastGroupProtocol{t: t}
-			clock := faketime.NewManualClock()
-
-			mgp.init(ip.GenericMulticastProtocolOptions{
-				Rand:                      rand.New(rand.NewSource(1)),
-				Clock:                     clock,
-				MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
-				AllNodesAddress:           addr2,
-			})
-
-			mgp.joinGroup(test.addr)
-			if test.shouldSendMessages {
-				if diff := mgp.check([]tcpip.Address{test.addr} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-					t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-				}
-			}
-
-			// Leaving a group should send a leave report immediately and cancel any
-			// delayed reports.
-			{
-
-				if !mgp.leaveGroup(test.addr) {
-					t.Fatalf("got mgp.leaveGroup(%s) = false, want = true", test.addr)
-				}
-			}
-			if test.shouldSendMessages {
-				if diff := mgp.check(nil /* sendReportGroupAddresses */, []tcpip.Address{test.addr} /* sendLeaveGroupAddresses */); diff != "" {
-					t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-				}
-			}
-
-			// Should have no more messages to send.
-			//
-			// Generic multicast protocol timers are expected to take the job mutex.
-			clock.Advance(time.Hour)
-			if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-func TestHandleReport(t *testing.T) {
-	tests := []struct {
-		name             string
-		reportAddr       tcpip.Address
-		expectReportsFor []tcpip.Address
-	}{
-		{
-			name:             "Unpecified empty",
-			reportAddr:       "",
-			expectReportsFor: []tcpip.Address{addr1, addr2},
-		},
-		{
-			name:             "Unpecified any",
-			reportAddr:       "\x00",
-			expectReportsFor: []tcpip.Address{addr1, addr2},
-		},
-		{
-			name:             "Specified",
-			reportAddr:       addr1,
-			expectReportsFor: []tcpip.Address{addr2},
-		},
-		{
-			name:             "Specified all-nodes",
-			reportAddr:       addr3,
-			expectReportsFor: []tcpip.Address{addr1, addr2},
-		},
-		{
-			name:             "Specified other",
-			reportAddr:       addr4,
-			expectReportsFor: []tcpip.Address{addr1, addr2},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			mgp := mockMulticastGroupProtocol{t: t}
-			clock := faketime.NewManualClock()
-
-			mgp.init(ip.GenericMulticastProtocolOptions{
-				Rand:                      rand.New(rand.NewSource(2)),
-				Clock:                     clock,
-				MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
-				AllNodesAddress:           addr3,
-			})
-
-			mgp.joinGroup(addr1)
-			if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-			mgp.joinGroup(addr2)
-			if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-			mgp.joinGroup(addr3)
-			if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-
-			// Receiving a report for a group we have a timer scheduled for should
-			// cancel our delayed report timer for the group.
-			mgp.handleReport(test.reportAddr)
-			if len(test.expectReportsFor) != 0 {
-				// Generic multicast protocol timers are expected to take the job mutex.
-				clock.Advance(maxUnsolicitedReportDelay)
-				if diff := mgp.check(test.expectReportsFor /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-					t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-				}
-			}
-
-			// Should have no more messages to send.
-			clock.Advance(time.Hour)
-			if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-func TestHandleQuery(t *testing.T) {
-	tests := []struct {
-		name                    string
-		queryAddr               tcpip.Address
-		maxDelay                time.Duration
-		expectQueriedReportsFor []tcpip.Address
-		expectDelayedReportsFor []tcpip.Address
-	}{
-		{
-			name:                    "Unpecified empty",
-			queryAddr:               "",
-			maxDelay:                0,
-			expectQueriedReportsFor: []tcpip.Address{addr1, addr2},
-			expectDelayedReportsFor: nil,
-		},
-		{
-			name:                    "Unpecified any",
-			queryAddr:               "\x00",
-			maxDelay:                1,
-			expectQueriedReportsFor: []tcpip.Address{addr1, addr2},
-			expectDelayedReportsFor: nil,
-		},
-		{
-			name:                    "Specified",
-			queryAddr:               addr1,
-			maxDelay:                2,
-			expectQueriedReportsFor: []tcpip.Address{addr1},
-			expectDelayedReportsFor: []tcpip.Address{addr2},
-		},
-		{
-			name:                    "Specified all-nodes",
-			queryAddr:               addr3,
-			maxDelay:                3,
-			expectQueriedReportsFor: nil,
-			expectDelayedReportsFor: []tcpip.Address{addr1, addr2},
-		},
-		{
-			name:                    "Specified other",
-			queryAddr:               addr4,
-			maxDelay:                4,
-			expectQueriedReportsFor: nil,
-			expectDelayedReportsFor: []tcpip.Address{addr1, addr2},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			mgp := mockMulticastGroupProtocol{t: t}
-			clock := faketime.NewManualClock()
-
-			mgp.init(ip.GenericMulticastProtocolOptions{
-				Rand:                      rand.New(rand.NewSource(3)),
-				Clock:                     clock,
-				MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
-				AllNodesAddress:           addr3,
-			})
-
-			mgp.joinGroup(addr1)
-			if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-			mgp.joinGroup(addr2)
-			if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-			mgp.joinGroup(addr3)
-			if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-
-			// Receiving a query should make us reschedule our delayed report timer
-			// to some time within the new max response delay.
-			mgp.handleQuery(test.queryAddr, test.maxDelay)
-			clock.Advance(test.maxDelay)
-			if diff := mgp.check(test.expectQueriedReportsFor /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-
-			// The groups that were not affected by the query should still send a
-			// report after the max unsolicited report delay.
-			clock.Advance(maxUnsolicitedReportDelay)
-			if diff := mgp.check(test.expectDelayedReportsFor /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-
-			// Should have no more messages to send.
-			clock.Advance(time.Hour)
-			if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-				t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-func TestJoinCount(t *testing.T) {
-	mgp := mockMulticastGroupProtocol{t: t}
-	clock := faketime.NewManualClock()
-
-	mgp.init(ip.GenericMulticastProtocolOptions{
-		Rand:                      rand.New(rand.NewSource(4)),
-		Clock:                     clock,
-		MaxUnsolicitedReportDelay: time.Second,
-	})
-
-	// Set the join count to 2 for a group.
-	mgp.joinGroup(addr1)
-	if !mgp.isLocallyJoined(addr1) {
-		t.Fatalf("got mgp.isLocallyJoined(%s) = false, want = true", addr1)
-	}
-	// Only the first join should trigger a report to be sent.
-	if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	mgp.joinGroup(addr1)
-	if !mgp.isLocallyJoined(addr1) {
-		t.Errorf("got mgp.isLocallyJoined(%s) = false, want = true", addr1)
-	}
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	if t.Failed() {
-		t.FailNow()
-	}
-
-	// Group should still be considered joined after leaving once.
-	if !mgp.leaveGroup(addr1) {
-		t.Errorf("got mgp.leaveGroup(%s) = false, want = true", addr1)
-	}
-	if !mgp.isLocallyJoined(addr1) {
-		t.Errorf("got mgp.isLocallyJoined(%s) = false, want = true", addr1)
-	}
-	// A leave report should only be sent once the join count reaches 0.
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	if t.Failed() {
-		t.FailNow()
-	}
-
-	// Leaving once more should actually remove us from the group.
-	if !mgp.leaveGroup(addr1) {
-		t.Errorf("got mgp.leaveGroup(%s) = false, want = true", addr1)
-	}
-	if mgp.isLocallyJoined(addr1) {
-		t.Errorf("got mgp.isLocallyJoined(%s) = true, want = false", addr1)
-	}
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, []tcpip.Address{addr1} /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	if t.Failed() {
-		t.FailNow()
-	}
-
-	// Group should no longer be joined so we should not have anything to
-	// leave.
-	if mgp.leaveGroup(addr1) {
-		t.Errorf("got mgp.leaveGroup(%s) = true, want = false", addr1)
-	}
-	if mgp.isLocallyJoined(addr1) {
-		t.Errorf("got mgp.isLocallyJoined(%s) = true, want = false", addr1)
-	}
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should have no more messages to send.
-	//
-	// Generic multicast protocol timers are expected to take the job mutex.
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-}
-
-func TestMakeAllNonMemberAndInitialize(t *testing.T) {
-	mgp := mockMulticastGroupProtocol{t: t}
-	clock := faketime.NewManualClock()
-
-	mgp.init(ip.GenericMulticastProtocolOptions{
-		Rand:                      rand.New(rand.NewSource(3)),
-		Clock:                     clock,
-		MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
-		AllNodesAddress:           addr3,
-	})
-
-	mgp.joinGroup(addr1)
-	if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	mgp.joinGroup(addr2)
-	if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	mgp.joinGroup(addr3)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should send the leave reports for each but still consider them locally
-	// joined.
-	mgp.makeAllNonMember()
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, []tcpip.Address{addr1, addr2} /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	// Generic multicast protocol timers are expected to take the job mutex.
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	for _, group := range []tcpip.Address{addr1, addr2, addr3} {
-		if !mgp.isLocallyJoined(group) {
-			t.Fatalf("got mgp.isLocallyJoined(%s) = false, want = true", group)
-		}
-	}
-
-	// Should send the initial set of unsolcited reports.
-	mgp.initializeGroups()
-	if diff := mgp.check([]tcpip.Address{addr1, addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	clock.Advance(maxUnsolicitedReportDelay)
-	if diff := mgp.check([]tcpip.Address{addr1, addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should have no more messages to send.
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-}
-
-// TestGroupStateNonMember tests that groups do not send packets when in the
-// non-member state, but are still considered locally joined.
-func TestGroupStateNonMember(t *testing.T) {
-	mgp := mockMulticastGroupProtocol{t: t}
-	clock := faketime.NewManualClock()
-
-	mgp.init(ip.GenericMulticastProtocolOptions{
-		Rand:                      rand.New(rand.NewSource(3)),
-		Clock:                     clock,
-		MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
-	})
-	mgp.setEnabled(false)
-
-	// Joining groups should not send any reports.
-	mgp.joinGroup(addr1)
-	if !mgp.isLocallyJoined(addr1) {
-		t.Fatalf("got mgp.isLocallyJoined(%s) = false, want = true", addr1)
-	}
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	mgp.joinGroup(addr2)
-	if !mgp.isLocallyJoined(addr1) {
-		t.Fatalf("got mgp.isLocallyJoined(%s) = false, want = true", addr2)
-	}
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Receiving a query should not send any reports.
-	mgp.handleQuery(addr1, time.Nanosecond)
-	// Generic multicast protocol timers are expected to take the job mutex.
-	clock.Advance(time.Nanosecond)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Leaving groups should not send any leave messages.
-	if !mgp.leaveGroup(addr1) {
-		t.Errorf("got mgp.leaveGroup(%s) = false, want = true", addr2)
-	}
-	if mgp.isLocallyJoined(addr1) {
-		t.Errorf("got mgp.isLocallyJoined(%s) = true, want = false", addr2)
-	}
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-}
-
-func TestQueuedPackets(t *testing.T) {
-	clock := faketime.NewManualClock()
-	mgp := mockMulticastGroupProtocol{t: t}
-	mgp.init(ip.GenericMulticastProtocolOptions{
-		Rand:                      rand.New(rand.NewSource(4)),
-		Clock:                     clock,
-		MaxUnsolicitedReportDelay: maxUnsolicitedReportDelay,
-	})
-
-	// Joining should trigger a SendReport, but mgp should report that we did not
-	// send the packet.
-	mgp.setQueuePackets(true)
-	mgp.joinGroup(addr1)
-	if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// The delayed report timer should have been cancelled since we did not send
-	// the initial report earlier.
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Mock being able to successfully send the report.
-	mgp.setQueuePackets(false)
-	mgp.sendQueuedReports()
-	if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// The delayed report (sent after the initial report) should now be sent.
-	clock.Advance(maxUnsolicitedReportDelay)
-	if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should not have anything else to send (we should be idle).
-	mgp.sendQueuedReports()
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Receive a query but mock being unable to send reports again.
-	mgp.setQueuePackets(true)
-	mgp.handleQuery(addr1, time.Nanosecond)
-	clock.Advance(time.Nanosecond)
-	if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Mock being able to send reports again - we should have a packet queued to
-	// send.
-	mgp.setQueuePackets(false)
-	mgp.sendQueuedReports()
-	if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should not have anything else to send.
-	mgp.sendQueuedReports()
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Receive a query again, but mock being unable to send reports.
-	mgp.setQueuePackets(true)
-	mgp.handleQuery(addr1, time.Nanosecond)
-	clock.Advance(time.Nanosecond)
-	if diff := mgp.check([]tcpip.Address{addr1} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Receiving a report should should transition us into the idle member state,
-	// even if we had a packet queued. We should no longer have any packets to
-	// send.
-	mgp.handleReport(addr1)
-	mgp.sendQueuedReports()
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// When we fail to send the initial set of reports, incoming reports should
-	// not affect a newly joined group's reports from being sent.
-	mgp.setQueuePackets(true)
-	mgp.joinGroup(addr2)
-	if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	mgp.handleReport(addr2)
-	// Attempting to send queued reports while still unable to send reports should
-	// not change the host state.
-	mgp.sendQueuedReports()
-	if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	// Mock being able to successfully send the report.
-	mgp.setQueuePackets(false)
-	mgp.sendQueuedReports()
-	if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-	// The delayed report (sent after the initial report) should now be sent.
-	clock.Advance(maxUnsolicitedReportDelay)
-	if diff := mgp.check([]tcpip.Address{addr2} /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Errorf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-
-	// Should not have anything else to send.
-	mgp.sendQueuedReports()
-	clock.Advance(time.Hour)
-	if diff := mgp.check(nil /* sendReportGroupAddresses */, nil /* sendLeaveGroupAddresses */); diff != "" {
-		t.Fatalf("mockMulticastGroupProtocol mismatch (-want +got):\n%s", diff)
-	}
-}
diff --git a/pkg/tcpip/network/internal/ip/ip_state_autogen.go b/pkg/tcpip/network/internal/ip/ip_state_autogen.go
new file mode 100644
index 0000000..aee7704
--- /dev/null
+++ b/pkg/tcpip/network/internal/ip/ip_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package ip
diff --git a/pkg/tcpip/network/ipv4/igmp_test.go b/pkg/tcpip/network/ipv4/igmp_test.go
deleted file mode 100644
index e5e1b89..0000000
--- a/pkg/tcpip/network/ipv4/igmp_test.go
+++ /dev/null
@@ -1,383 +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 ipv4_test
-
-import (
-	"testing"
-	"time"
-
-	"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/ipv4"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-const (
-	linkAddr            = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
-	stackAddr           = tcpip.Address("\x0a\x00\x00\x01")
-	remoteAddr          = tcpip.Address("\x0a\x00\x00\x02")
-	multicastAddr       = tcpip.Address("\xe0\x00\x00\x03")
-	nicID               = 1
-	defaultTTL          = 1
-	defaultPrefixLength = 24
-)
-
-// validateIgmpPacket checks that a passed PacketInfo is an IPv4 IGMP packet
-// sent to the provided address with the passed fields set. Raises a t.Error if
-// any field does not match.
-func validateIgmpPacket(t *testing.T, p channel.PacketInfo, igmpType header.IGMPType, maxRespTime byte, srcAddr, dstAddr, groupAddress tcpip.Address) {
-	t.Helper()
-
-	payload := header.IPv4(stack.PayloadSince(p.Pkt.NetworkHeader()))
-	checker.IPv4(t, payload,
-		checker.SrcAddr(srcAddr),
-		checker.DstAddr(dstAddr),
-		// TTL for an IGMP message must be 1 as per RFC 2236 section 2.
-		checker.TTL(1),
-		checker.IPv4RouterAlert(),
-		checker.IGMP(
-			checker.IGMPType(igmpType),
-			checker.IGMPMaxRespTime(header.DecisecondToDuration(maxRespTime)),
-			checker.IGMPGroupAddress(groupAddress),
-		),
-	)
-}
-
-func createStack(t *testing.T, igmpEnabled bool) (*channel.Endpoint, *stack.Stack, *faketime.ManualClock) {
-	t.Helper()
-
-	// Create an endpoint of queue size 1, since no more than 1 packets are ever
-	// queued in the tests in this file.
-	e := channel.New(1, 1280, linkAddr)
-	clock := faketime.NewManualClock()
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocolWithOptions(ipv4.Options{
-			IGMP: ipv4.IGMPOptions{
-				Enabled: igmpEnabled,
-			},
-		})},
-		Clock: clock,
-	})
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-	return e, s, clock
-}
-
-func createAndInjectIGMPPacket(e *channel.Endpoint, igmpType header.IGMPType, maxRespTime byte, ttl uint8, srcAddr, dstAddr, groupAddress tcpip.Address, hasRouterAlertOption bool) {
-	var options header.IPv4OptionsSerializer
-	if hasRouterAlertOption {
-		options = header.IPv4OptionsSerializer{
-			&header.IPv4SerializableRouterAlertOption{},
-		}
-	}
-	buf := buffer.NewView(header.IPv4MinimumSize + int(options.Length()) + header.IGMPQueryMinimumSize)
-
-	ip := header.IPv4(buf)
-	ip.Encode(&header.IPv4Fields{
-		TotalLength: uint16(len(buf)),
-		TTL:         ttl,
-		Protocol:    uint8(header.IGMPProtocolNumber),
-		SrcAddr:     srcAddr,
-		DstAddr:     dstAddr,
-		Options:     options,
-	})
-	ip.SetChecksum(^ip.CalculateChecksum())
-
-	igmp := header.IGMP(ip.Payload())
-	igmp.SetType(igmpType)
-	igmp.SetMaxRespTime(maxRespTime)
-	igmp.SetGroupAddress(groupAddress)
-	igmp.SetChecksum(header.IGMPCalculateChecksum(igmp))
-
-	e.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-}
-
-// TestIGMPV1Present tests the node's ability to fallback to V1 when a V1
-// router is detected. V1 present status is expected to be reset when the NIC
-// cycles.
-func TestIGMPV1Present(t *testing.T) {
-	e, s, clock := createStack(t, true)
-	addr := tcpip.AddressWithPrefix{Address: stackAddr, PrefixLen: defaultPrefixLength}
-	if err := s.AddAddressWithPrefix(nicID, ipv4.ProtocolNumber, addr); err != nil {
-		t.Fatalf("AddAddressWithPrefix(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, addr, err)
-	}
-
-	if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
-		t.Fatalf("JoinGroup(ipv4, nic, %s) = %s", multicastAddr, err)
-	}
-
-	// This NIC will send an IGMPv2 report immediately, before this test can get
-	// the IGMPv1 General Membership Query in.
-	{
-		p, ok := e.Read()
-		if !ok {
-			t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
-		}
-		if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 1 {
-			t.Fatalf("got V2MembershipReport messages sent = %d, want = 1", got)
-		}
-		validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
-	}
-	if t.Failed() {
-		t.FailNow()
-	}
-
-	// Inject an IGMPv1 General Membership Query which is identical to a standard
-	// membership query except the Max Response Time is set to 0, which will tell
-	// the stack that this is a router using IGMPv1. Send it to the all systems
-	// group which is the only group this host belongs to.
-	createAndInjectIGMPPacket(e, header.IGMPMembershipQuery, 0, defaultTTL, remoteAddr, stackAddr, header.IPv4AllSystems, true /* hasRouterAlertOption */)
-	if got := s.Stats().IGMP.PacketsReceived.MembershipQuery.Value(); got != 1 {
-		t.Fatalf("got Membership Queries received = %d, want = 1", got)
-	}
-
-	// Before advancing the clock, verify that this host has not sent a
-	// V1MembershipReport yet.
-	if got := s.Stats().IGMP.PacketsSent.V1MembershipReport.Value(); got != 0 {
-		t.Fatalf("got V1MembershipReport messages sent = %d, want = 0", got)
-	}
-
-	// Verify the solicited Membership Report is sent. Now that this NIC has seen
-	// an IGMPv1 query, it should send an IGMPv1 Membership Report.
-	if p, ok := e.Read(); ok {
-		t.Fatalf("sent unexpected packet, expected V1MembershipReport only after advancing the clock = %+v", p.Pkt)
-	}
-	clock.Advance(ipv4.UnsolicitedReportIntervalMax)
-	{
-		p, ok := e.Read()
-		if !ok {
-			t.Fatal("unable to Read IGMP packet, expected V1MembershipReport")
-		}
-		if got := s.Stats().IGMP.PacketsSent.V1MembershipReport.Value(); got != 1 {
-			t.Fatalf("got V1MembershipReport messages sent = %d, want = 1", got)
-		}
-		validateIgmpPacket(t, p, header.IGMPv1MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
-	}
-
-	// Cycling the interface should reset the V1 present flag.
-	if err := s.DisableNIC(nicID); err != nil {
-		t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-	}
-	if err := s.EnableNIC(nicID); err != nil {
-		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-	}
-	{
-		p, ok := e.Read()
-		if !ok {
-			t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
-		}
-		if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 2 {
-			t.Fatalf("got V2MembershipReport messages sent = %d, want = 2", got)
-		}
-		validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
-	}
-}
-
-func TestSendQueuedIGMPReports(t *testing.T) {
-	e, s, clock := createStack(t, true)
-
-	// Joining a group without an assigned address should queue IGMP packets; none
-	// should be sent without an assigned address.
-	if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
-		t.Fatalf("JoinGroup(%d, %d, %s): %s", ipv4.ProtocolNumber, nicID, multicastAddr, err)
-	}
-	reportStat := s.Stats().IGMP.PacketsSent.V2MembershipReport
-	if got := reportStat.Value(); got != 0 {
-		t.Errorf("got reportStat.Value() = %d, want = 0", got)
-	}
-	clock.Advance(time.Hour)
-	if p, ok := e.Read(); ok {
-		t.Fatalf("got unexpected packet = %#v", p)
-	}
-
-	// The initial set of IGMP reports that were queued should be sent once an
-	// address is assigned.
-	if err := s.AddAddress(nicID, ipv4.ProtocolNumber, stackAddr); err != nil {
-		t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, stackAddr, err)
-	}
-	if got := reportStat.Value(); got != 1 {
-		t.Errorf("got reportStat.Value() = %d, want = 1", got)
-	}
-	if p, ok := e.Read(); !ok {
-		t.Error("expected to send an IGMP membership report")
-	} else {
-		validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
-	}
-	if t.Failed() {
-		t.FailNow()
-	}
-	clock.Advance(ipv4.UnsolicitedReportIntervalMax)
-	if got := reportStat.Value(); got != 2 {
-		t.Errorf("got reportStat.Value() = %d, want = 2", got)
-	}
-	if p, ok := e.Read(); !ok {
-		t.Error("expected to send an IGMP membership report")
-	} else {
-		validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
-	}
-	if t.Failed() {
-		t.FailNow()
-	}
-
-	// Should have no more packets to send after the initial set of unsolicited
-	// reports.
-	clock.Advance(time.Hour)
-	if p, ok := e.Read(); ok {
-		t.Fatalf("got unexpected packet = %#v", p)
-	}
-}
-
-func TestIGMPPacketValidation(t *testing.T) {
-	tests := []struct {
-		name                     string
-		messageType              header.IGMPType
-		stackAddresses           []tcpip.AddressWithPrefix
-		srcAddr                  tcpip.Address
-		includeRouterAlertOption bool
-		ttl                      uint8
-		expectValidIGMP          bool
-		getMessageTypeStatValue  func(tcpip.Stats) uint64
-	}{
-		{
-			name:                     "valid",
-			messageType:              header.IGMPLeaveGroup,
-			includeRouterAlertOption: true,
-			stackAddresses:           []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
-			srcAddr:                  remoteAddr,
-			ttl:                      1,
-			expectValidIGMP:          true,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.LeaveGroup.Value() },
-		},
-		{
-			name:                     "bad ttl",
-			messageType:              header.IGMPv1MembershipReport,
-			includeRouterAlertOption: true,
-			stackAddresses:           []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
-			srcAddr:                  remoteAddr,
-			ttl:                      2,
-			expectValidIGMP:          false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V1MembershipReport.Value() },
-		},
-		{
-			name:                     "missing router alert ip option",
-			messageType:              header.IGMPv2MembershipReport,
-			includeRouterAlertOption: false,
-			stackAddresses:           []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
-			srcAddr:                  remoteAddr,
-			ttl:                      1,
-			expectValidIGMP:          false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V2MembershipReport.Value() },
-		},
-		{
-			name:                     "igmp leave group and src ip does not belong to nic subnet",
-			messageType:              header.IGMPLeaveGroup,
-			includeRouterAlertOption: true,
-			stackAddresses:           []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
-			srcAddr:                  tcpip.Address("\x0a\x00\x01\x02"),
-			ttl:                      1,
-			expectValidIGMP:          false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.LeaveGroup.Value() },
-		},
-		{
-			name:                     "igmp query and src ip does not belong to nic subnet",
-			messageType:              header.IGMPMembershipQuery,
-			includeRouterAlertOption: true,
-			stackAddresses:           []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
-			srcAddr:                  tcpip.Address("\x0a\x00\x01\x02"),
-			ttl:                      1,
-			expectValidIGMP:          true,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.MembershipQuery.Value() },
-		},
-		{
-			name:                     "igmp report v1 and src ip does not belong to nic subnet",
-			messageType:              header.IGMPv1MembershipReport,
-			includeRouterAlertOption: true,
-			stackAddresses:           []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
-			srcAddr:                  tcpip.Address("\x0a\x00\x01\x02"),
-			ttl:                      1,
-			expectValidIGMP:          false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V1MembershipReport.Value() },
-		},
-		{
-			name:                     "igmp report v2 and src ip does not belong to nic subnet",
-			messageType:              header.IGMPv2MembershipReport,
-			includeRouterAlertOption: true,
-			stackAddresses:           []tcpip.AddressWithPrefix{{Address: stackAddr, PrefixLen: 24}},
-			srcAddr:                  tcpip.Address("\x0a\x00\x01\x02"),
-			ttl:                      1,
-			expectValidIGMP:          false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V2MembershipReport.Value() },
-		},
-		{
-			name:                     "src ip belongs to the subnet of the nic's second address",
-			messageType:              header.IGMPv2MembershipReport,
-			includeRouterAlertOption: true,
-			stackAddresses: []tcpip.AddressWithPrefix{
-				{Address: tcpip.Address("\x0a\x00\x0f\x01"), PrefixLen: 24},
-				{Address: stackAddr, PrefixLen: 24},
-			},
-			srcAddr:                 remoteAddr,
-			ttl:                     1,
-			expectValidIGMP:         true,
-			getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.IGMP.PacketsReceived.V2MembershipReport.Value() },
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			e, s, _ := createStack(t, true)
-			for _, address := range test.stackAddresses {
-				if err := s.AddAddressWithPrefix(nicID, ipv4.ProtocolNumber, address); err != nil {
-					t.Fatalf("AddAddressWithPrefix(%d, %d, %s): %s", nicID, ipv4.ProtocolNumber, address, err)
-				}
-			}
-			stats := s.Stats()
-			// Verify that every relevant stats is zero'd before we send a packet.
-			if got := test.getMessageTypeStatValue(s.Stats()); got != 0 {
-				t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 0", got)
-			}
-			if got := stats.IGMP.PacketsReceived.Invalid.Value(); got != 0 {
-				t.Errorf("got stats.IGMP.PacketsReceived.Invalid.Value() = %d, want = 0", got)
-			}
-			if got := stats.IP.PacketsDelivered.Value(); got != 0 {
-				t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 0", got)
-			}
-			createAndInjectIGMPPacket(e, test.messageType, 0, test.ttl, test.srcAddr, header.IPv4AllSystems, header.IPv4AllSystems, test.includeRouterAlertOption)
-			// We always expect the packet to pass IP validation.
-			if got := stats.IP.PacketsDelivered.Value(); got != 1 {
-				t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 1", got)
-			}
-			// Even when the IGMP-specific validation checks fail, we expect the
-			// corresponding IGMP counter to be incremented.
-			if got := test.getMessageTypeStatValue(s.Stats()); got != 1 {
-				t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 1", got)
-			}
-			var expectedInvalidCount uint64
-			if !test.expectValidIGMP {
-				expectedInvalidCount = 1
-			}
-			if got := stats.IGMP.PacketsReceived.Invalid.Value(); got != expectedInvalidCount {
-				t.Errorf("got stats.IGMP.PacketsReceived.Invalid.Value() = %d, want = %d", got, expectedInvalidCount)
-			}
-		})
-	}
-}
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 0000000..19b6722
--- /dev/null
+++ b/pkg/tcpip/network/ipv4/ipv4_state_autogen.go
@@ -0,0 +1,113 @@
+// automatically generated by stateify.
+
+package ipv4
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (i *icmpv4DestinationUnreachableSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv4.icmpv4DestinationUnreachableSockError"
+}
+
+func (i *icmpv4DestinationUnreachableSockError) StateFields() []string {
+	return []string{}
+}
+
+func (i *icmpv4DestinationUnreachableSockError) beforeSave() {}
+
+// +checklocksignore
+func (i *icmpv4DestinationUnreachableSockError) StateSave(stateSinkObject state.Sink) {
+	i.beforeSave()
+}
+
+func (i *icmpv4DestinationUnreachableSockError) afterLoad() {}
+
+// +checklocksignore
+func (i *icmpv4DestinationUnreachableSockError) StateLoad(stateSourceObject state.Source) {
+}
+
+func (i *icmpv4DestinationHostUnreachableSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv4.icmpv4DestinationHostUnreachableSockError"
+}
+
+func (i *icmpv4DestinationHostUnreachableSockError) StateFields() []string {
+	return []string{
+		"icmpv4DestinationUnreachableSockError",
+	}
+}
+
+func (i *icmpv4DestinationHostUnreachableSockError) beforeSave() {}
+
+// +checklocksignore
+func (i *icmpv4DestinationHostUnreachableSockError) StateSave(stateSinkObject state.Sink) {
+	i.beforeSave()
+	stateSinkObject.Save(0, &i.icmpv4DestinationUnreachableSockError)
+}
+
+func (i *icmpv4DestinationHostUnreachableSockError) afterLoad() {}
+
+// +checklocksignore
+func (i *icmpv4DestinationHostUnreachableSockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &i.icmpv4DestinationUnreachableSockError)
+}
+
+func (i *icmpv4DestinationPortUnreachableSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv4.icmpv4DestinationPortUnreachableSockError"
+}
+
+func (i *icmpv4DestinationPortUnreachableSockError) StateFields() []string {
+	return []string{
+		"icmpv4DestinationUnreachableSockError",
+	}
+}
+
+func (i *icmpv4DestinationPortUnreachableSockError) beforeSave() {}
+
+// +checklocksignore
+func (i *icmpv4DestinationPortUnreachableSockError) StateSave(stateSinkObject state.Sink) {
+	i.beforeSave()
+	stateSinkObject.Save(0, &i.icmpv4DestinationUnreachableSockError)
+}
+
+func (i *icmpv4DestinationPortUnreachableSockError) afterLoad() {}
+
+// +checklocksignore
+func (i *icmpv4DestinationPortUnreachableSockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &i.icmpv4DestinationUnreachableSockError)
+}
+
+func (e *icmpv4FragmentationNeededSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv4.icmpv4FragmentationNeededSockError"
+}
+
+func (e *icmpv4FragmentationNeededSockError) StateFields() []string {
+	return []string{
+		"icmpv4DestinationUnreachableSockError",
+		"mtu",
+	}
+}
+
+func (e *icmpv4FragmentationNeededSockError) beforeSave() {}
+
+// +checklocksignore
+func (e *icmpv4FragmentationNeededSockError) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.icmpv4DestinationUnreachableSockError)
+	stateSinkObject.Save(1, &e.mtu)
+}
+
+func (e *icmpv4FragmentationNeededSockError) afterLoad() {}
+
+// +checklocksignore
+func (e *icmpv4FragmentationNeededSockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.icmpv4DestinationUnreachableSockError)
+	stateSourceObject.Load(1, &e.mtu)
+}
+
+func init() {
+	state.Register((*icmpv4DestinationUnreachableSockError)(nil))
+	state.Register((*icmpv4DestinationHostUnreachableSockError)(nil))
+	state.Register((*icmpv4DestinationPortUnreachableSockError)(nil))
+	state.Register((*icmpv4FragmentationNeededSockError)(nil))
+}
diff --git a/pkg/tcpip/network/ipv4/ipv4_test.go b/pkg/tcpip/network/ipv4/ipv4_test.go
deleted file mode 100644
index cfed241..0000000
--- a/pkg/tcpip/network/ipv4/ipv4_test.go
+++ /dev/null
@@ -1,3106 +0,0 @@
-// Copyright 2021 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 (
-	"bytes"
-	"context"
-	"encoding/hex"
-	"fmt"
-	"io/ioutil"
-	"math"
-	"net"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"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/faketime"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/link/channel"
-	"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
-	"gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
-	"gvisor.dev/gvisor/pkg/tcpip/network/arp"
-	"gvisor.dev/gvisor/pkg/tcpip/network/internal/testutil"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/raw"
-	"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.
-		{
-			err := ep.Connect(randomAddr)
-			if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-				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)
-		}
-	})
-}
-
-func TestForwarding(t *testing.T) {
-	const (
-		nicID1           = 1
-		nicID2           = 2
-		randomSequence   = 123
-		randomIdent      = 42
-		randomTimeOffset = 0x10203040
-	)
-
-	ipv4Addr1 := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(net.ParseIP("10.0.0.1").To4()),
-		PrefixLen: 8,
-	}
-	ipv4Addr2 := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(net.ParseIP("11.0.0.1").To4()),
-		PrefixLen: 8,
-	}
-	remoteIPv4Addr1 := tcpip.Address(net.ParseIP("10.0.0.2").To4())
-	remoteIPv4Addr2 := tcpip.Address(net.ParseIP("11.0.0.2").To4())
-
-	tests := []struct {
-		name             string
-		TTL              uint8
-		expectErrorICMP  bool
-		options          header.IPv4Options
-		forwardedOptions header.IPv4Options
-		icmpType         header.ICMPv4Type
-		icmpCode         header.ICMPv4Code
-	}{
-		{
-			name:            "TTL of zero",
-			TTL:             0,
-			expectErrorICMP: true,
-			icmpType:        header.ICMPv4TimeExceeded,
-			icmpCode:        header.ICMPv4TTLExceeded,
-		},
-		{
-			name:            "TTL of one",
-			TTL:             1,
-			expectErrorICMP: false,
-		},
-		{
-			name:            "TTL of two",
-			TTL:             2,
-			expectErrorICMP: false,
-		},
-		{
-			name:            "Max TTL",
-			TTL:             math.MaxUint8,
-			expectErrorICMP: false,
-		},
-		{
-			name:             "four EOL options",
-			TTL:              2,
-			expectErrorICMP:  false,
-			options:          header.IPv4Options{0, 0, 0, 0},
-			forwardedOptions: header.IPv4Options{0, 0, 0, 0},
-		},
-		{
-			name: "TS type 1 full",
-			TTL:  2,
-			options: header.IPv4Options{
-				68, 12, 13, 0xF1,
-				192, 168, 1, 12,
-				1, 2, 3, 4,
-			},
-			expectErrorICMP: true,
-			icmpType:        header.ICMPv4ParamProblem,
-			icmpCode:        header.ICMPv4UnusedCode,
-		},
-		{
-			name: "TS type 0",
-			TTL:  2,
-			options: header.IPv4Options{
-				68, 24, 21, 0x00,
-				1, 2, 3, 4,
-				5, 6, 7, 8,
-				9, 10, 11, 12,
-				13, 14, 15, 16,
-				0, 0, 0, 0,
-			},
-			forwardedOptions: header.IPv4Options{
-				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
-			},
-		},
-		{
-			name: "end of options list",
-			TTL:  2,
-			options: header.IPv4Options{
-				68, 12, 13, 0x11,
-				192, 168, 1, 12,
-				1, 2, 3, 4,
-				0, 10, 3, 99, // EOL followed by junk
-				1, 2, 3, 4,
-			},
-			forwardedOptions: header.IPv4Options{
-				68, 12, 13, 0x21,
-				192, 168, 1, 12,
-				1, 2, 3, 4,
-				0,       // End of Options hides following bytes.
-				0, 0, 0, // 7 bytes unknown option removed.
-				0, 0, 0, 0,
-			},
-		},
-	}
-	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,
-			})
-
-			// Advance the clock by some unimportant amount to make
-			// it give a more recognisable signature than 00,00,00,00.
-			clock.Advance(time.Millisecond * randomTimeOffset)
-
-			// We expect at most a single packet in response to our ICMP Echo Request.
-			e1 := channel.New(1, ipv4.MaxTotalSize, "")
-			if err := s.CreateNIC(nicID1, e1); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
-			}
-			ipv4ProtoAddr1 := tcpip.ProtocolAddress{Protocol: header.IPv4ProtocolNumber, AddressWithPrefix: ipv4Addr1}
-			if err := s.AddProtocolAddress(nicID1, ipv4ProtoAddr1); err != nil {
-				t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID1, ipv4ProtoAddr1, err)
-			}
-
-			e2 := channel.New(1, ipv4.MaxTotalSize, "")
-			if err := s.CreateNIC(nicID2, e2); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID2, err)
-			}
-			ipv4ProtoAddr2 := tcpip.ProtocolAddress{Protocol: header.IPv4ProtocolNumber, AddressWithPrefix: ipv4Addr2}
-			if err := s.AddProtocolAddress(nicID2, ipv4ProtoAddr2); err != nil {
-				t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID2, ipv4ProtoAddr2, err)
-			}
-
-			s.SetRouteTable([]tcpip.Route{
-				{
-					Destination: ipv4Addr1.Subnet(),
-					NIC:         nicID1,
-				},
-				{
-					Destination: ipv4Addr2.Subnet(),
-					NIC:         nicID2,
-				},
-			})
-
-			if err := s.SetForwarding(header.IPv4ProtocolNumber, true); err != nil {
-				t.Fatalf("SetForwarding(%d, true): %s", header.IPv4ProtocolNumber, err)
-			}
-
-			ipHeaderLength := header.IPv4MinimumSize + len(test.options)
-			if ipHeaderLength > header.IPv4MaximumHeaderSize {
-				t.Fatalf("got ipHeaderLength = %d, want <= %d ", ipHeaderLength, header.IPv4MaximumHeaderSize)
-			}
-			totalLen := uint16(ipHeaderLength + header.ICMPv4MinimumSize)
-			hdr := buffer.NewPrependable(int(totalLen))
-			icmp := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
-			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))
-			ip.Encode(&header.IPv4Fields{
-				TotalLength: totalLen,
-				Protocol:    uint8(header.ICMPv4ProtocolNumber),
-				TTL:         test.TTL,
-				SrcAddr:     remoteIPv4Addr1,
-				DstAddr:     remoteIPv4Addr2,
-			})
-			if len(test.options) != 0 {
-				ip.SetHeaderLength(uint8(ipHeaderLength))
-				// Copy options manually. We do not use Encode for options so we can
-				// verify malformed options with handcrafted payloads.
-				if want, got := copy(ip.Options(), test.options), len(test.options); want != got {
-					t.Fatalf("got copy(ip.Options(), test.options) = %d, want = %d", got, want)
-				}
-			}
-			ip.SetChecksum(0)
-			ip.SetChecksum(^ip.CalculateChecksum())
-			requestPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-				Data: hdr.View().ToVectorisedView(),
-			})
-			e1.InjectInbound(header.IPv4ProtocolNumber, requestPkt)
-
-			if test.expectErrorICMP {
-				reply, ok := e1.Read()
-				if !ok {
-					t.Fatalf("expected ICMP packet type %d through incoming NIC", test.icmpType)
-				}
-
-				checker.IPv4(t, header.IPv4(stack.PayloadSince(reply.Pkt.NetworkHeader())),
-					checker.SrcAddr(ipv4Addr1.Address),
-					checker.DstAddr(remoteIPv4Addr1),
-					checker.TTL(ipv4.DefaultTTL),
-					checker.ICMPv4(
-						checker.ICMPv4Checksum(),
-						checker.ICMPv4Type(test.icmpType),
-						checker.ICMPv4Code(test.icmpCode),
-						checker.ICMPv4Payload([]byte(hdr.View())),
-					),
-				)
-
-				if n := e2.Drain(); n != 0 {
-					t.Fatalf("got e2.Drain() = %d, want = 0", n)
-				}
-			} else {
-				reply, ok := e2.Read()
-				if !ok {
-					t.Fatal("expected ICMP Echo packet through outgoing NIC")
-				}
-
-				checker.IPv4(t, header.IPv4(stack.PayloadSince(reply.Pkt.NetworkHeader())),
-					checker.SrcAddr(remoteIPv4Addr1),
-					checker.DstAddr(remoteIPv4Addr2),
-					checker.TTL(test.TTL-1),
-					checker.IPv4Options(test.forwardedOptions),
-					checker.ICMPv4(
-						checker.ICMPv4Checksum(),
-						checker.ICMPv4Type(header.ICMPv4Echo),
-						checker.ICMPv4Code(header.ICMPv4UnusedCode),
-						checker.ICMPv4Payload(nil),
-					),
-				)
-
-				if n := e1.Drain(); n != 0 {
-					t.Fatalf("got e1.Drain() = %d, want = 0", n)
-				}
-			}
-		})
-	}
-}
-
-// 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
-		// In some cases Linux sets the error pointer to the start of the option
-		// (offset 0) instead of the actual wrong value, which is the length byte
-		// (offset 1). For compatibility we must do the same. Use this constant
-		// to indicate where this happens.
-		pointerOffsetForInvalidLength = 0
-		randomTimeOffset              = 0x10203040
-	)
-	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             header.IPv4Options
-		replyOptions        header.IPv4Options // 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:           header.IPv4Options{0, 0, 0, 0},
-			replyOptions:      header.IPv4Options{0, 0, 0, 0},
-		},
-		{
-			name:              "NOP options",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options:           header.IPv4Options{1, 1, 1, 1},
-			replyOptions:      header.IPv4Options{1, 1, 1, 1},
-		},
-		{
-			name:              "NOP and End options",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options:           header.IPv4Options{1, 1, 0, 0},
-			replyOptions:      header.IPv4Options{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: header.IPv4Options{
-				68, 12, 13, 0x11,
-				192, 168, 1, 12,
-				1, 2, 3, 4,
-			},
-			replyOptions: header.IPv4Options{
-				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: header.IPv4Options{
-				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:        header.IPv4Options{},
-		},
-		{
-			name:              "unknown option",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options:           header.IPv4Options{10, 4, 9, 0},
-			//                        ^^
-			// The unknown option should be stripped out of the reply.
-			replyOptions: header.IPv4Options{},
-		},
-		{
-			name:              "bad option - no length",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				1, 1, 1, 68,
-				//        ^-start of timestamp.. but no length..
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + 3,
-		},
-		{
-			name:              "bad option - length 0",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				68, 0, 9, 0,
-				//  ^
-				1, 2, 3, 4,
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + pointerOffsetForInvalidLength,
-		},
-		{
-			name:              "bad option - length 1",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				68, 1, 9, 0,
-				//  ^
-				1, 2, 3, 4,
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + pointerOffsetForInvalidLength,
-		},
-		{
-			name:              "bad option - length big",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				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,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + pointerOffsetForInvalidLength,
-		},
-		{
-			// 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: header.IPv4Options{
-				68, 7, 5, 0,
-				//  ^  ^ Linux points here which is wrong.
-				//  | Not a multiple of 4
-				1, 2, 3, 0,
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + header.IPv4OptTSPointerOffset,
-		},
-		{
-			name:              "multiple type 0 with room",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				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: header.IPv4Options{
-				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: header.IPv4Options{
-				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: header.IPv4Options{
-				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: header.IPv4Options{
-				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: header.IPv4Options{
-				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
-			},
-		},
-		{
-			// Timestamp pointer uses one based counting so 0 is invalid.
-			name:              "timestamp pointer invalid",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				68, 8, 0, 0x00,
-				//      ^ 0 instead of 5 or more.
-				0, 0, 0, 0,
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + 2,
-		},
-		{
-			// Timestamp pointer cannot be less than 5. It must point past the header
-			// which is 4 bytes. (1 based counting)
-			name:              "timestamp pointer too small by 1",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				68, 8, header.IPv4OptionTimestampHdrLength, 0x00,
-				//          ^ header is 4 bytes, so 4 should fail.
-				0, 0, 0, 0,
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + header.IPv4OptTSPointerOffset,
-		},
-		{
-			name:              "valid timestamp pointer",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				68, 8, header.IPv4OptionTimestampHdrLength + 1, 0x00,
-				//          ^ header is 4 bytes, so 5 should succeed.
-				0, 0, 0, 0,
-			},
-			replyOptions: header.IPv4Options{
-				68, 8, 9, 0x00,
-				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: header.IPv4Options{
-				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 + header.IPv4OptTSPointerOffset,
-		},
-		// 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: header.IPv4Options{
-				68, 12, 13, 0x11,
-				192, 168, 1, 12,
-				1, 2, 3, 4,
-				0, 10, 3, 99, // EOL followed by junk
-			},
-			replyOptions: header.IPv4Options{
-				68, 12, 13, 0x21,
-				192, 168, 1, 12,
-				1, 2, 3, 4,
-				0,       // End of Options hides following bytes.
-				0, 0, 0, // 3 bytes unknown option removed.
-			},
-		},
-		{
-			// Timestamp with a size much too small.
-			name:              "timestamp truncated",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				68, 1, 0, 0,
-				//  ^ Smallest possible is 8. Linux points at the 68.
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + pointerOffsetForInvalidLength,
-		},
-		{
-			name:              "single record route with room",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				7, 7, 4, //  3 byte header
-				0, 0, 0, 0,
-				0,
-			},
-			replyOptions: header.IPv4Options{
-				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: header.IPv4Options{
-				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: header.IPv4Options{
-				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: header.IPv4Options{
-				7, 7, 8, // 3 byte header
-				1, 2, 3, 4,
-				0,
-			},
-			replyOptions: header.IPv4Options{
-				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: header.IPv4Options{
-				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: header.IPv4Options{
-				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.
-			},
-		},
-		{
-			// Pointer uses one based counting so 0 is invalid.
-			name:              "record route pointer zero",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				7, 8, 0, // 3 byte header
-				0, 0, 0, 0,
-				0,
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + header.IPv4OptRRPointerOffset,
-		},
-		{
-			// Pointer must be 4 or more as it must point past the 3 byte header
-			// using 1 based counting. 3 should fail.
-			name:              "record route pointer too small by 1",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				7, 8, header.IPv4OptionRecordRouteHdrLength, // 3 byte header
-				0, 0, 0, 0,
-				0,
-			},
-			shouldFail:          true,
-			expectErrorICMP:     true,
-			ICMPType:            header.ICMPv4ParamProblem,
-			ICMPCode:            header.ICMPv4UnusedCode,
-			paramProblemPointer: header.IPv4MinimumSize + header.IPv4OptRRPointerOffset,
-		},
-		{
-			// Pointer must be 4 or more as it must point past the 3 byte header
-			// using 1 based counting. Check 4 passes. (Duplicates "single
-			// record route with room")
-			name:              "valid record route pointer",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				7, 7, header.IPv4OptionRecordRouteHdrLength + 1, // 3 byte header
-				0, 0, 0, 0,
-				0,
-			},
-			replyOptions: header.IPv4Options{
-				7, 7, 8, // 3 byte header
-				192, 168, 1, 58, // New IP Address.
-				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: header.IPv4Options{
-				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 + header.IPv4OptRRPointerOffset,
-		},
-		{
-			name:              "duplicate record route",
-			maxTotalLength:    ipv4.MaxTotalSize,
-			transportProtocol: uint8(header.ICMPv4ProtocolNumber),
-			TTL:               ttl,
-			options: header.IPv4Options{
-				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,
-		},
-	}
-
-	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,
-			})
-			// Advance the clock by some unimportant amount to make
-			// it give a more recognisable signature than 00,00,00,00.
-			clock.Advance(time.Millisecond * randomTimeOffset)
-
-			// 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)
-			}
-
-			// 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,
-				},
-			})
-
-			if len(test.options)%4 != 0 {
-				t.Fatalf("options must be aligned to 32 bits, invalid test options: %x (len=%d)", test.options, len(test.options))
-			}
-			ipHeaderLength := header.IPv4MinimumSize + len(test.options)
-			if ipHeaderLength > header.IPv4MaximumHeaderSize {
-				t.Fatalf("IP header length too large: 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{
-				TotalLength: totalLen,
-				Protocol:    test.transportProtocol,
-				TTL:         test.TTL,
-				SrcAddr:     remoteIPv4Addr,
-				DstAddr:     ipv4Addr.Address,
-			})
-			if test.headerLength != 0 {
-				ip.SetHeaderLength(test.headerLength)
-			} else {
-				// Set the calculated header length, since we may manually add options.
-				ip.SetHeaderLength(uint8(ipHeaderLength))
-			}
-			if len(test.options) != 0 {
-				// Copy options manually. We do not use Encode for options so we can
-				// verify malformed options with handcrafted payloads.
-				if want, got := copy(ip.Options(), test.options), len(test.options); want != got {
-					t.Fatalf("got copy(ip.Options(), test.options) = %d, want = %d", got, want)
-				}
-			}
-			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.AppendView(packet.Data().AsRange().ToOwnedView())
-		// 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 diff := cmp.Diff(ft.wantError, err); diff != "" {
-				t.Fatalf("unexpected error from r.WritePacket(_, _, _), (-want, +got):\n%s", diff)
-			}
-			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
-		// 0 means insert the correct IHL. Non 0 means override the correct IHL.
-		overrideIHL  int // For 0 use 1 as it is an int and will be divided by 4.
-		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{
-						TOS:            tos,
-						TotalLength:    0,
-						ID:             ident,
-						Flags:          header.IPv4FlagDontFragment | header.IPv4FlagMoreFragments,
-						FragmentOffset: 59776,
-						TTL:            ttl,
-						Protocol:       protocol,
-						SrcAddr:        addr1,
-						DstAddr:        addr2,
-					},
-					overrideIHL:  1, // See note above.
-					payload:      payloadGen(12),
-					autoChecksum: true,
-				},
-			},
-			wantMalformedIPPackets: 1,
-			wantMalformedFragments: 0,
-		},
-		{
-			name: "IHL and TotalLength zero, FragmentOffset zero",
-			fragments: []fragmentData{
-				{
-					ipv4fields: header.IPv4Fields{
-						TOS:            tos,
-						TotalLength:    0,
-						ID:             ident,
-						Flags:          header.IPv4FlagMoreFragments,
-						FragmentOffset: 0,
-						TTL:            ttl,
-						Protocol:       protocol,
-						SrcAddr:        addr1,
-						DstAddr:        addr2,
-					},
-					overrideIHL:  1, // See note above.
-					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{
-						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{
-						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{
-						TOS:            tos,
-						TotalLength:    header.IPv4MinimumSize + 28,
-						ID:             ident,
-						Flags:          0,
-						FragmentOffset: 1944,
-						TTL:            ttl,
-						Protocol:       protocol,
-						SrcAddr:        addr1,
-						DstAddr:        addr2,
-					},
-					payload:      payloadGen(28),
-					overrideIHL:  header.IPv4MinimumSize - 12,
-					autoChecksum: true,
-				},
-				{
-					ipv4fields: header.IPv4Fields{
-						TOS:            tos,
-						TotalLength:    header.IPv4MinimumSize - 12,
-						ID:             ident,
-						Flags:          header.IPv4FlagMoreFragments,
-						FragmentOffset: 0,
-						TTL:            ttl,
-						Protocol:       protocol,
-						SrcAddr:        addr1,
-						DstAddr:        addr2,
-					},
-					payload:      payloadGen(28),
-					overrideIHL:  header.IPv4MinimumSize - 12,
-					autoChecksum: true,
-				},
-			},
-			wantMalformedIPPackets: 2,
-			wantMalformedFragments: 0,
-		},
-		{
-			name: "fragment with short TotalLength and extra payload",
-			fragments: []fragmentData{
-				{
-					ipv4fields: header.IPv4Fields{
-						TOS:            tos,
-						TotalLength:    header.IPv4MinimumSize + 28,
-						ID:             ident,
-						Flags:          0,
-						FragmentOffset: 28816,
-						TTL:            ttl,
-						Protocol:       protocol,
-						SrcAddr:        addr1,
-						DstAddr:        addr2,
-					},
-					payload:      payloadGen(28),
-					overrideIHL:  header.IPv4MinimumSize + 4,
-					autoChecksum: true,
-				},
-				{
-					ipv4fields: header.IPv4Fields{
-						TOS:            tos,
-						TotalLength:    header.IPv4MinimumSize + 4,
-						ID:             ident,
-						Flags:          header.IPv4FlagMoreFragments,
-						FragmentOffset: 0,
-						TTL:            ttl,
-						Protocol:       protocol,
-						SrcAddr:        addr1,
-						DstAddr:        addr2,
-					},
-					payload:      payloadGen(28),
-					overrideIHL:  header.IPv4MinimumSize + 4,
-					autoChecksum: true,
-				},
-			},
-			wantMalformedIPPackets: 1,
-			wantMalformedFragments: 1,
-		},
-		{
-			name: "multiple fragments with More Fragments flag set to false",
-			fragments: []fragmentData{
-				{
-					ipv4fields: header.IPv4Fields{
-						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{
-						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{
-						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)
-				if want, got := len(f.payload), copy(ip[header.IPv4MinimumSize:], f.payload); want != got {
-					t.Fatalf("copied %d bytes, expected %d bytes.", got, want)
-				}
-				// Encode sets this up correctly. If we want a different value for
-				// testing then we need to overwrite the good value.
-				if f.overrideIHL != 0 {
-					ip.SetHeaderLength(uint8(f.overrideIHL))
-					// If we are asked to add options (type not specified) then pad
-					// with 0 (EOL). RFC 791 page 23 says "The padding is zero".
-					for i := header.IPv4MinimumSize; i < f.overrideIHL; i++ {
-						ip[i] = byte(header.IPv4OptionListEndType)
-					}
-				}
-
-				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{
-						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{
-						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{
-						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{
-						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{
-						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{
-						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{
-						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{
-						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 IPv4 payload length.
-	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},
-		},
-		{
-			name: "Two fragments with MF flag 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:          header.IPv4FlagMoreFragments,
-					fragmentOffset: 65512,
-					payload:        ipv4Payload4Addr1ToAddr2[65512:],
-				},
-			},
-			expectedPayloads: nil,
-		},
-	}
-
-	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},
-				RawFactory:         raw.EndpointFactory{},
-			})
-			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)
-			}
-
-			// Bring up a raw endpoint so we can examine network headers.
-			epRaw, err := s.NewRawEndpoint(udp.ProtocolNumber, header.IPv4ProtocolNumber, &wq, true /* associated */)
-			if err != nil {
-				t.Fatalf("NewRawEndpoint(%d, %d, _, true): %s", udp.ProtocolNumber, header.IPv4ProtocolNumber, err)
-			}
-			defer epRaw.Close()
-
-			// 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{
-					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 {
-				// Check UDP payload delivered by UDP endpoint.
-				var buf bytes.Buffer
-				result, err := ep.Read(&buf, tcpip.ReadOptions{})
-				if err != nil {
-					t.Fatalf("(i=%d) ep.Read: %s", i, err)
-				}
-				if diff := cmp.Diff(tcpip.ReadResult{
-					Count: len(expectedPayload),
-					Total: len(expectedPayload),
-				}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
-					t.Errorf("(i=%d) ep.Read: unexpected result (-want +got):\n%s", i, diff)
-				}
-				if diff := cmp.Diff(expectedPayload, buf.Bytes()); diff != "" {
-					t.Errorf("(i=%d) ep.Read: UDP payload mismatch (-want +got):\n%s", i, diff)
-				}
-
-				// Check IPv4 header in packet delivered by raw endpoint.
-				buf.Reset()
-				result, err = epRaw.Read(&buf, tcpip.ReadOptions{})
-				if err != nil {
-					t.Fatalf("(i=%d) epRaw.Read: %s", i, err)
-				}
-				// Reassambly does not take care of checksum. Here we write our own
-				// check routine instead of using checker.IPv4.
-				ip := header.IPv4(buf.Bytes())
-				for _, check := range []checker.NetworkChecker{
-					checker.FragmentFlags(0),
-					checker.FragmentOffset(0),
-					checker.IPFullLength(uint16(header.IPv4MinimumSize + header.UDPMinimumSize + len(expectedPayload))),
-				} {
-					check(t, []header.Network{ip})
-				}
-			}
-
-			res, err := ep.Read(ioutil.Discard, tcpip.ReadOptions{})
-			if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-				t.Fatalf("(last) got Read = (%#v, %v), want = (_, %s)", res, 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, 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{
-					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{
-					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.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)
-		})
-	}
-}
-
-// TestCloseLocking test that lock ordering is followed when closing an
-// endpoint.
-func TestCloseLocking(t *testing.T) {
-	const (
-		nicID1 = 1
-		nicID2 = 2
-
-		src = tcpip.Address("\x10\x00\x00\x01")
-		dst = tcpip.Address("\x10\x00\x00\x02")
-
-		iterations = 1000
-	)
-
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
-	})
-
-	// Perform NAT so that the endoint tries to search for a sibling endpoint
-	// which ends up taking the protocol and endpoint lock (in that order).
-	table := stack.Table{
-		Rules: []stack.Rule{
-			{Target: &stack.AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
-			{Target: &stack.AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
-			{Target: &stack.RedirectTarget{Port: 5, NetworkProtocol: header.IPv4ProtocolNumber}},
-			{Target: &stack.AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
-			{Target: &stack.ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
-		},
-		BuiltinChains: [stack.NumHooks]int{
-			stack.Prerouting:  0,
-			stack.Input:       1,
-			stack.Forward:     stack.HookUnset,
-			stack.Output:      2,
-			stack.Postrouting: 3,
-		},
-		Underflows: [stack.NumHooks]int{
-			stack.Prerouting:  0,
-			stack.Input:       1,
-			stack.Forward:     stack.HookUnset,
-			stack.Output:      2,
-			stack.Postrouting: 3,
-		},
-	}
-	if err := s.IPTables().ReplaceTable(stack.NATID, table, false /* ipv6 */); err != nil {
-		t.Fatalf("s.IPTables().ReplaceTable(...): %s", err)
-	}
-
-	e := channel.New(0, defaultMTU, "")
-	if err := s.CreateNIC(nicID1, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
-	}
-
-	if err := s.AddAddress(nicID1, ipv4.ProtocolNumber, src); err != nil {
-		t.Fatalf("AddAddress(%d, %d, %s) failed: %s", nicID1, ipv4.ProtocolNumber, src, err)
-	}
-
-	s.SetRouteTable([]tcpip.Route{{
-		Destination: header.IPv4EmptySubnet,
-		NIC:         nicID1,
-	}})
-
-	var wq waiter.Queue
-	ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer ep.Close()
-
-	addr := tcpip.FullAddress{NIC: nicID1, Addr: dst, Port: 53}
-	if err := ep.Connect(addr); err != nil {
-		t.Errorf("ep.Connect(%#v): %s", addr, err)
-	}
-
-	var wg sync.WaitGroup
-	defer wg.Wait()
-
-	// Writing packets should trigger NAT which requires the stack to search the
-	// protocol for network endpoints with the destination address.
-	//
-	// Creating and removing interfaces should modify the protocol and endpoint
-	// which requires taking the locks of each.
-	//
-	// We expect the protocol > endpoint lock ordering to be followed here.
-	wg.Add(2)
-	go func() {
-		defer wg.Done()
-
-		data := []byte{1, 2, 3, 4}
-
-		for i := 0; i < iterations; i++ {
-			var r bytes.Reader
-			r.Reset(data)
-			if n, err := ep.Write(&r, tcpip.WriteOptions{}); err != nil {
-				t.Errorf("ep.Write(_, _): %s", err)
-				return
-			} else if want := int64(len(data)); n != want {
-				t.Errorf("got ep.Write(_, _) = (%d, _), want = (%d, _)", n, want)
-				return
-			}
-		}
-	}()
-	go func() {
-		defer wg.Done()
-
-		for i := 0; i < iterations; i++ {
-			if err := s.CreateNIC(nicID2, loopback.New()); err != nil {
-				t.Errorf("CreateNIC(%d, _): %s", nicID2, err)
-				return
-			}
-			if err := s.RemoveNIC(nicID2); err != nil {
-				t.Errorf("RemoveNIC(%d): %s", nicID2, err)
-				return
-			}
-		}
-	}()
-}
diff --git a/pkg/tcpip/network/ipv4/stats_test.go b/pkg/tcpip/network/ipv4/stats_test.go
deleted file mode 100644
index a637f9d..0000000
--- a/pkg/tcpip/network/ipv4/stats_test.go
+++ /dev/null
@@ -1,99 +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 ipv4
-
-import (
-	"reflect"
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/network/internal/testutil"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-var _ stack.NetworkInterface = (*testInterface)(nil)
-
-type testInterface struct {
-	stack.NetworkInterface
-	nicID tcpip.NICID
-}
-
-func (t *testInterface) ID() tcpip.NICID {
-	return t.nicID
-}
-
-func knownNICIDs(proto *protocol) []tcpip.NICID {
-	var nicIDs []tcpip.NICID
-
-	for k := range proto.mu.eps {
-		nicIDs = append(nicIDs, k)
-	}
-
-	return nicIDs
-}
-
-func TestClearEndpointFromProtocolOnClose(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
-	})
-	proto := s.NetworkProtocolInstance(ProtocolNumber).(*protocol)
-	nic := testInterface{nicID: 1}
-	ep := proto.NewEndpoint(&nic, nil).(*endpoint)
-	var nicIDs []tcpip.NICID
-
-	proto.mu.Lock()
-	foundEP, hasEndpointBeforeClose := proto.mu.eps[nic.ID()]
-	nicIDs = knownNICIDs(proto)
-	proto.mu.Unlock()
-
-	if !hasEndpointBeforeClose {
-		t.Fatalf("expected to find the nic id %d in the protocol's endpoint map (%v)", nic.ID(), nicIDs)
-	}
-	if foundEP != ep {
-		t.Fatalf("found an incorrect endpoint mapped to nic id %d", nic.ID())
-	}
-
-	ep.Close()
-
-	proto.mu.Lock()
-	_, hasEP := proto.mu.eps[nic.ID()]
-	nicIDs = knownNICIDs(proto)
-	proto.mu.Unlock()
-	if hasEP {
-		t.Fatalf("unexpectedly found an endpoint mapped to the nic id %d in the protocol's known nic ids (%v)", nic.ID(), nicIDs)
-	}
-}
-
-func TestMultiCounterStatsInitialization(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
-	})
-	proto := s.NetworkProtocolInstance(ProtocolNumber).(*protocol)
-	var nic testInterface
-	ep := proto.NewEndpoint(&nic, nil).(*endpoint)
-	// At this point, the Stack's stats and the NetworkEndpoint's stats are
-	// expected to be bound by a MultiCounterStat.
-	refStack := s.Stats()
-	refEP := ep.stats.localStats
-	if err := testutil.ValidateMultiCounterStats(reflect.ValueOf(&ep.stats.ip).Elem(), []reflect.Value{reflect.ValueOf(&refEP.IP).Elem(), reflect.ValueOf(&refStack.IP).Elem()}); err != nil {
-		t.Error(err)
-	}
-	if err := testutil.ValidateMultiCounterStats(reflect.ValueOf(&ep.stats.icmp).Elem(), []reflect.Value{reflect.ValueOf(&refEP.ICMP).Elem(), reflect.ValueOf(&refStack.ICMP.V4).Elem()}); err != nil {
-		t.Error(err)
-	}
-	if err := testutil.ValidateMultiCounterStats(reflect.ValueOf(&ep.stats.igmp).Elem(), []reflect.Value{reflect.ValueOf(&refEP.IGMP).Elem(), reflect.ValueOf(&refStack.IGMP).Elem()}); err != nil {
-		t.Error(err)
-	}
-}
diff --git a/pkg/tcpip/network/ipv6/icmp_test.go b/pkg/tcpip/network/ipv6/icmp_test.go
deleted file mode 100644
index 6a7705e..0000000
--- a/pkg/tcpip/network/ipv6/icmp_test.go
+++ /dev/null
@@ -1,1716 +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 (
-	"bytes"
-	"context"
-	"net"
-	"reflect"
-	"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/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
-
-	arbitraryHopLimit = 42
-)
-
-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.RouteInfo, *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
-}
-
-var _ stack.NetworkInterface = (*testInterface)(nil)
-
-type testInterface struct {
-	stack.LinkEndpoint
-
-	probeCount        int
-	confirmationCount int
-
-	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 (*testInterface) Promiscuous() bool {
-	return false
-}
-
-func (*testInterface) Spoofing() bool {
-	return false
-}
-
-func (t *testInterface) WritePacket(r *stack.Route, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
-	return t.LinkEndpoint.WritePacket(r.Fields(), gso, protocol, pkt)
-}
-
-func (t *testInterface) WritePackets(r *stack.Route, gso *stack.GSO, pkts stack.PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
-	return t.LinkEndpoint.WritePackets(r.Fields(), gso, pkts, protocol)
-}
-
-func (t *testInterface) WritePacketToRemote(remoteLinkAddr tcpip.LinkAddress, gso *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
-	var r stack.RouteInfo
-	r.NetProto = protocol
-	r.RemoteLinkAddress = remoteLinkAddr
-	return t.LinkEndpoint.WritePacket(r, gso, protocol, pkt)
-}
-
-func (t *testInterface) HandleNeighborProbe(tcpip.NetworkProtocolNumber, tcpip.Address, tcpip.LinkAddress) tcpip.Error {
-	t.probeCount++
-	return nil
-}
-
-func (t *testInterface) HandleNeighborConfirmation(tcpip.NetworkProtocolNumber, tcpip.Address, tcpip.LinkAddress, stack.ReachabilityConfirmationFlags) tcpip.Error {
-	t.confirmationCount++
-	return nil
-}
-
-func (*testInterface) PrimaryAddress(tcpip.NetworkProtocolNumber) (tcpip.AddressWithPrefix, tcpip.Error) {
-	return tcpip.AddressWithPrefix{}, nil
-}
-
-func (*testInterface) CheckLocalAddress(tcpip.NetworkProtocolNumber, tcpip.Address) bool {
-	return false
-}
-
-func handleICMPInIPv6(ep stack.NetworkEndpoint, src, dst tcpip.Address, icmp header.ICMPv6, hopLimit uint8, includeRouterAlert bool) {
-	var extensionHeaders header.IPv6ExtHdrSerializer
-	if includeRouterAlert {
-		extensionHeaders = header.IPv6ExtHdrSerializer{
-			header.IPv6SerializableHopByHopExtHdr{
-				&header.IPv6RouterAlertOption{Value: header.IPv6RouterAlertMLD},
-			},
-		}
-	}
-	ip := buffer.NewView(header.IPv6MinimumSize + extensionHeaders.Length())
-	header.IPv6(ip).Encode(&header.IPv6Fields{
-		PayloadLength:     uint16(len(icmp)),
-		TransportProtocol: header.ICMPv6ProtocolNumber,
-		HopLimit:          hopLimit,
-		SrcAddr:           src,
-		DstAddr:           dst,
-		ExtensionHeaders:  extensionHeaders,
-	})
-
-	vv := ip.ToVectorisedView()
-	vv.AppendView(buffer.View(icmp))
-	ep.HandlePacket(stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: vv,
-	}))
-}
-
-func TestICMPCounts(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
-	})
-	if err := s.CreateNIC(nicID, &stubLinkEndpoint{}); err != nil {
-		t.Fatalf("CreateNIC(_, _) = %s", 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{}, &stubDispatcher{})
-	defer ep.Close()
-
-	if err := ep.Enable(); err != nil {
-		t.Fatalf("ep.Enable(): %s", err)
-	}
-
-	addressableEndpoint, ok := ep.(stack.AddressableEndpoint)
-	if !ok {
-		t.Fatalf("expected network endpoint to implement stack.AddressableEndpoint")
-	}
-	addr := lladdr0.WithPrefix()
-	if ep, err := addressableEndpoint.AddAndAcquirePermanentAddress(addr, stack.CanBePrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */); err != nil {
-		t.Fatalf("addressableEndpoint.AddAndAcquirePermanentAddress(%s, CanBePrimaryEndpoint, AddressConfigStatic, false): %s", addr, err)
-	} else {
-		ep.DecRef()
-	}
-
-	var tllData [header.NDPLinkLayerAddressSize]byte
-	header.NDPOptions(tllData[:]).Serialize(header.NDPOptionsSerializer{
-		header.NDPTargetLinkLayerAddressOption(linkAddr1),
-	})
-
-	types := []struct {
-		typ                header.ICMPv6Type
-		hopLimit           uint8
-		includeRouterAlert bool
-		size               int
-		extraData          []byte
-	}{
-		{
-			typ:      header.ICMPv6DstUnreachable,
-			hopLimit: arbitraryHopLimit,
-			size:     header.ICMPv6DstUnreachableMinimumSize,
-		},
-		{
-			typ:      header.ICMPv6PacketTooBig,
-			hopLimit: arbitraryHopLimit,
-			size:     header.ICMPv6PacketTooBigMinimumSize,
-		},
-		{
-			typ:      header.ICMPv6TimeExceeded,
-			hopLimit: arbitraryHopLimit,
-			size:     header.ICMPv6MinimumSize,
-		},
-		{
-			typ:      header.ICMPv6ParamProblem,
-			hopLimit: arbitraryHopLimit,
-			size:     header.ICMPv6MinimumSize,
-		},
-		{
-			typ:      header.ICMPv6EchoRequest,
-			hopLimit: arbitraryHopLimit,
-			size:     header.ICMPv6EchoMinimumSize,
-		},
-		{
-			typ:      header.ICMPv6EchoReply,
-			hopLimit: arbitraryHopLimit,
-			size:     header.ICMPv6EchoMinimumSize,
-		},
-		{
-			typ:      header.ICMPv6RouterSolicit,
-			hopLimit: header.NDPHopLimit,
-			size:     header.ICMPv6MinimumSize,
-		},
-		{
-			typ:      header.ICMPv6RouterAdvert,
-			hopLimit: header.NDPHopLimit,
-			size:     header.ICMPv6HeaderSize + header.NDPRAMinimumSize,
-		},
-		{
-			typ:      header.ICMPv6NeighborSolicit,
-			hopLimit: header.NDPHopLimit,
-			size:     header.ICMPv6NeighborSolicitMinimumSize,
-		},
-		{
-			typ:       header.ICMPv6NeighborAdvert,
-			hopLimit:  header.NDPHopLimit,
-			size:      header.ICMPv6NeighborAdvertMinimumSize,
-			extraData: tllData[:],
-		},
-		{
-			typ:      header.ICMPv6RedirectMsg,
-			hopLimit: header.NDPHopLimit,
-			size:     header.ICMPv6MinimumSize,
-		},
-		{
-			typ:                header.ICMPv6MulticastListenerQuery,
-			hopLimit:           header.MLDHopLimit,
-			includeRouterAlert: true,
-			size:               header.MLDMinimumSize + header.ICMPv6HeaderSize,
-		},
-		{
-			typ:                header.ICMPv6MulticastListenerReport,
-			hopLimit:           header.MLDHopLimit,
-			includeRouterAlert: true,
-			size:               header.MLDMinimumSize + header.ICMPv6HeaderSize,
-		},
-		{
-			typ:                header.ICMPv6MulticastListenerDone,
-			hopLimit:           header.MLDHopLimit,
-			includeRouterAlert: true,
-			size:               header.MLDMinimumSize + header.ICMPv6HeaderSize,
-		},
-		{
-			typ:  255, /* Unrecognized */
-			size: 50,
-		},
-	}
-
-	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(header.ICMPv6ChecksumParams{
-			Header:      icmp[:typ.size],
-			Src:         lladdr0,
-			Dst:         lladdr1,
-			PayloadCsum: header.Checksum(typ.extraData, 0 /* initial */),
-			PayloadLen:  len(typ.extraData),
-		}))
-		handleICMPInIPv6(ep, lladdr1, lladdr0, icmp, typ.hopLimit, typ.includeRouterAlert)
-	}
-
-	// Construct an empty ICMP packet so that
-	// Stats().ICMP.ICMPv6ReceivedPacketStats.Invalid is incremented.
-	handleICMPInIPv6(ep, lladdr1, lladdr0, header.ICMPv6(buffer.NewView(header.IPv6MinimumSize)), arbitraryHopLimit, false)
-
-	icmpv6Stats := s.Stats().ICMP.V6.PacketsReceived
-	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,
-		}},
-	)
-
-	t.Cleanup(func() {
-		if err := c.s0.RemoveNIC(nicID); err != nil {
-			t.Errorf("c.s0.RemoveNIC(%d): %s", nicID, err)
-		}
-		if err := c.s1.RemoveNIC(nicID); err != nil {
-			t.Errorf("c.s1.RemoveNIC(%d): %s", nicID, err)
-		}
-
-		c.linkEP0.Close()
-		c.linkEP1.Close()
-	})
-
-	return c
-}
-
-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 && pi.Route.RemoteLinkAddress != args.remoteLinkAddr {
-		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)
-
-	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(header.ICMPv6ChecksumParams{
-		Header: pkt,
-		Src:    r.LocalAddress(),
-		Dst:    r.RemoteAddress(),
-	}))
-
-	// 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)
-	}
-
-	{
-		var r bytes.Reader
-		r.Reset(hdr.View())
-		if _, err := ep.Write(&r, tcpip.WriteOptions{To: &tcpip.FullAddress{NIC: nicID, Addr: lladdr1}}); err != nil {
-			t.Fatalf("ep.Write(_): %s", 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)
-			}
-		})
-	}
-
-	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
-			},
-		},
-	}
-
-	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},
-				})
-				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(header.ICMPv6ChecksumParams{
-							Header: icmp,
-							Src:    lladdr1,
-							Dst:    lladdr0,
-						}))
-					}
-					ip := header.IPv6(buffer.NewView(header.IPv6MinimumSize))
-					ip.Encode(&header.IPv6Fields{
-						PayloadLength:     uint16(len(icmp)),
-						TransportProtocol: 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.V6.PacketsReceived
-				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 {
-					// 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,
-			TransportProtocol: 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(header.ICMPv6ChecksumParams{
-						Header: icmpHdr,
-						Src:    lladdr1,
-						Dst:    lladdr0,
-					}))
-				}
-
-				ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-				ip.Encode(&header.IPv6Fields{
-					PayloadLength:     uint16(icmpSize),
-					TransportProtocol: 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.V6.PacketsReceived
-			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,
-			TransportProtocol: 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(header.ICMPv6ChecksumParams{
-						Header:      icmpHdr,
-						Src:         lladdr1,
-						Dst:         lladdr0,
-						PayloadCsum: header.Checksum(payload, 0 /* initial */),
-						PayloadLen:  len(payload),
-					}))
-				}
-
-				ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-				ip.Encode(&header.IPv6Fields{
-					PayloadLength:     uint16(size + payloadSize),
-					TransportProtocol: 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.V6.PacketsReceived
-			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.ErrBadLocalAddress{},
-		},
-		{
-			name:           "Multicast with unassigned address",
-			localAddr:      lladdr1,
-			remoteLinkAddr: "",
-			expectedErr:    &tcpip.ErrBadLocalAddress{},
-		},
-		{
-			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 {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
-			})
-
-			linkEP := channel.New(defaultChannelSize, defaultMTU, linkAddr0)
-			if err := s.CreateNIC(nicID, linkEP); 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)
-			}
-			linkRes, ok := ep.(stack.LinkAddressResolver)
-			if !ok {
-				t.Fatalf("expected %T to implement stack.LinkAddressResolver", ep)
-			}
-
-			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)
-				}
-			}
-
-			{
-				err := linkRes.LinkAddressRequest(lladdr0, test.localAddr, test.remoteLinkAddr)
-				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
-					t.Fatalf("unexpected error from p.LinkAddressRequest(%s, %s, %s, _), (-want, +got):\n%s", lladdr0, test.localAddr, test.remoteLinkAddr, diff)
-				}
-			}
-
-			if test.expectedErr != nil {
-				return
-			}
-
-			pkt, ok := linkEP.Read()
-			if !ok {
-				t.Fatal("expected to send a link address request")
-			}
-
-			var want stack.RouteInfo
-			want.NetProto = ProtocolNumber
-			want.RemoteLinkAddress = test.expectedRemoteLinkAddr
-			if diff := cmp.Diff(want, pkt.Route, cmp.AllowUnexported(want)); diff != "" {
-				t.Errorf("route info mismatch (-want +got):\n%s", diff)
-			}
-			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),
-					TransportProtocol: 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(header.ICMPv6ChecksumParams{
-					Header: pkt,
-					Src:    host2IPv6Addr.AddressWithPrefix.Address,
-					Dst:    host1IPv6Addr.AddressWithPrefix.Address,
-				}))
-				ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-				ip.Encode(&header.IPv6Fields{
-					PayloadLength:     header.ICMPv6MinimumSize,
-					TransportProtocol: 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{
-				{
-					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.MessageBody())
-				na.SetSolicitedFlag(true)
-				na.SetOverrideFlag(true)
-				na.SetTargetAddress(host2IPv6Addr.AddressWithPrefix.Address)
-				na.Options().Serialize(header.NDPOptionsSerializer{
-					header.NDPTargetLinkLayerAddressOption(host2NICLinkAddr),
-				})
-				pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-					Header: pkt,
-					Src:    host2IPv6Addr.AddressWithPrefix.Address,
-					Dst:    host1IPv6Addr.AddressWithPrefix.Address,
-				}))
-				payloadLength := hdr.UsedLength()
-				ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-				ip.Encode(&header.IPv6Fields{
-					PayloadLength:     uint16(payloadLength),
-					TransportProtocol: 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.MessageBody())
-				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.MessageBody())
-				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.MessageBody())
-				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.MessageBody())
-				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.MessageBody())
-				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.MessageBody())
-				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.MessageBody())
-				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.MessageBody())
-				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},
-			})
-			{
-				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)
-			}
-
-			testInterface := testInterface{LinkEndpoint: channel.New(0, header.IPv6MinimumMTU, linkAddr0)}
-			ep := netProto.NewEndpoint(&testInterface, &stubDispatcher{})
-			defer ep.Close()
-
-			if err := ep.Enable(); err != nil {
-				t.Fatalf("ep.Enable(): %s", err)
-			}
-
-			addressableEndpoint, ok := ep.(stack.AddressableEndpoint)
-			if !ok {
-				t.Fatalf("expected network endpoint to implement stack.AddressableEndpoint")
-			}
-			addr := lladdr0.WithPrefix()
-			if ep, err := addressableEndpoint.AddAndAcquirePermanentAddress(addr, stack.CanBePrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */); err != nil {
-				t.Fatalf("addressableEndpoint.AddAndAcquirePermanentAddress(%s, CanBePrimaryEndpoint, AddressConfigStatic, false): %s", addr, err)
-			} else {
-				ep.DecRef()
-			}
-
-			icmp := test.createPacket()
-			icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-				Header: icmp,
-				Src:    test.source,
-				Dst:    test.destination,
-			}))
-			handleICMPInIPv6(ep, test.source, test.destination, icmp, header.NDPHopLimit, false)
-
-			// Confirm the endpoint calls the correct NUDHandler method.
-			if testInterface.probeCount != test.wantProbeCount {
-				t.Errorf("got testInterface.probeCount = %d, want = %d", testInterface.probeCount, test.wantProbeCount)
-			}
-			if testInterface.confirmationCount != test.wantConfirmationCount {
-				t.Errorf("got testInterface.confirmationCount = %d, want = %d", testInterface.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 0000000..13d4278
--- /dev/null
+++ b/pkg/tcpip/network/ipv6/ipv6_state_autogen.go
@@ -0,0 +1,136 @@
+// automatically generated by stateify.
+
+package ipv6
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (i *icmpv6DestinationUnreachableSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv6.icmpv6DestinationUnreachableSockError"
+}
+
+func (i *icmpv6DestinationUnreachableSockError) StateFields() []string {
+	return []string{}
+}
+
+func (i *icmpv6DestinationUnreachableSockError) beforeSave() {}
+
+// +checklocksignore
+func (i *icmpv6DestinationUnreachableSockError) StateSave(stateSinkObject state.Sink) {
+	i.beforeSave()
+}
+
+func (i *icmpv6DestinationUnreachableSockError) afterLoad() {}
+
+// +checklocksignore
+func (i *icmpv6DestinationUnreachableSockError) StateLoad(stateSourceObject state.Source) {
+}
+
+func (i *icmpv6DestinationNetworkUnreachableSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv6.icmpv6DestinationNetworkUnreachableSockError"
+}
+
+func (i *icmpv6DestinationNetworkUnreachableSockError) StateFields() []string {
+	return []string{
+		"icmpv6DestinationUnreachableSockError",
+	}
+}
+
+func (i *icmpv6DestinationNetworkUnreachableSockError) beforeSave() {}
+
+// +checklocksignore
+func (i *icmpv6DestinationNetworkUnreachableSockError) StateSave(stateSinkObject state.Sink) {
+	i.beforeSave()
+	stateSinkObject.Save(0, &i.icmpv6DestinationUnreachableSockError)
+}
+
+func (i *icmpv6DestinationNetworkUnreachableSockError) afterLoad() {}
+
+// +checklocksignore
+func (i *icmpv6DestinationNetworkUnreachableSockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &i.icmpv6DestinationUnreachableSockError)
+}
+
+func (i *icmpv6DestinationPortUnreachableSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv6.icmpv6DestinationPortUnreachableSockError"
+}
+
+func (i *icmpv6DestinationPortUnreachableSockError) StateFields() []string {
+	return []string{
+		"icmpv6DestinationUnreachableSockError",
+	}
+}
+
+func (i *icmpv6DestinationPortUnreachableSockError) beforeSave() {}
+
+// +checklocksignore
+func (i *icmpv6DestinationPortUnreachableSockError) StateSave(stateSinkObject state.Sink) {
+	i.beforeSave()
+	stateSinkObject.Save(0, &i.icmpv6DestinationUnreachableSockError)
+}
+
+func (i *icmpv6DestinationPortUnreachableSockError) afterLoad() {}
+
+// +checklocksignore
+func (i *icmpv6DestinationPortUnreachableSockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &i.icmpv6DestinationUnreachableSockError)
+}
+
+func (i *icmpv6DestinationAddressUnreachableSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv6.icmpv6DestinationAddressUnreachableSockError"
+}
+
+func (i *icmpv6DestinationAddressUnreachableSockError) StateFields() []string {
+	return []string{
+		"icmpv6DestinationUnreachableSockError",
+	}
+}
+
+func (i *icmpv6DestinationAddressUnreachableSockError) beforeSave() {}
+
+// +checklocksignore
+func (i *icmpv6DestinationAddressUnreachableSockError) StateSave(stateSinkObject state.Sink) {
+	i.beforeSave()
+	stateSinkObject.Save(0, &i.icmpv6DestinationUnreachableSockError)
+}
+
+func (i *icmpv6DestinationAddressUnreachableSockError) afterLoad() {}
+
+// +checklocksignore
+func (i *icmpv6DestinationAddressUnreachableSockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &i.icmpv6DestinationUnreachableSockError)
+}
+
+func (e *icmpv6PacketTooBigSockError) StateTypeName() string {
+	return "pkg/tcpip/network/ipv6.icmpv6PacketTooBigSockError"
+}
+
+func (e *icmpv6PacketTooBigSockError) StateFields() []string {
+	return []string{
+		"mtu",
+	}
+}
+
+func (e *icmpv6PacketTooBigSockError) beforeSave() {}
+
+// +checklocksignore
+func (e *icmpv6PacketTooBigSockError) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.mtu)
+}
+
+func (e *icmpv6PacketTooBigSockError) afterLoad() {}
+
+// +checklocksignore
+func (e *icmpv6PacketTooBigSockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.mtu)
+}
+
+func init() {
+	state.Register((*icmpv6DestinationUnreachableSockError)(nil))
+	state.Register((*icmpv6DestinationNetworkUnreachableSockError)(nil))
+	state.Register((*icmpv6DestinationPortUnreachableSockError)(nil))
+	state.Register((*icmpv6DestinationAddressUnreachableSockError)(nil))
+	state.Register((*icmpv6PacketTooBigSockError)(nil))
+}
diff --git a/pkg/tcpip/network/ipv6/ipv6_test.go b/pkg/tcpip/network/ipv6/ipv6_test.go
deleted file mode 100644
index 81f5f23..0000000
--- a/pkg/tcpip/network/ipv6/ipv6_test.go
+++ /dev/null
@@ -1,3154 +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 (
-	"bytes"
-	"encoding/hex"
-	"fmt"
-	"io/ioutil"
-	"math"
-	"net"
-	"reflect"
-	"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/internal/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)
-	unknownHdrID        = uint8(header.IPv6UnknownExtHdrIdentifier)
-
-	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(header.ICMPv6ChecksumParams{
-		Header: pkt,
-		Src:    src,
-		Dst:    dst,
-	}))
-	payloadLength := hdr.UsedLength()
-	ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-	ip.Encode(&header.IPv6Fields{
-		PayloadLength:     uint16(payloadLength),
-		TransportProtocol: header.ICMPv6ProtocolNumber,
-		HopLimit:          255,
-		SrcAddr:           src,
-		DstAddr:           dst,
-	})
-
-	e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: hdr.View().ToVectorisedView(),
-	}))
-
-	stats := s.Stats().ICMP.V6.PacketsReceived
-
-	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),
-		TransportProtocol: 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.AppendView(fragment.Data().AsRange().ToOwnedView())
-	}
-
-	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) {
-	const nicID = 1
-
-	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(nicID, &stubLinkEndpoint{}); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			if err := s.AddAddress(nicID, ProtocolNumber, test.addr); err != nil {
-				t.Fatalf("AddAddress(%d, %d, nil) = %s", nicID, ProtocolNumber, err)
-			}
-
-			if addr, err := s.GetMainNICAddress(nicID, ProtocolNumber); err != nil {
-				t.Fatalf("stack.GetMainNICAddress(%d, %d): %s", nicID, ProtocolNumber, err)
-			} else if addr.Address != test.addr {
-				t.Fatalf("got stack.GetMainNICAddress(%d, %d) = %s, want = %s", nicID, ProtocolNumber, addr.Address, test.addr)
-			}
-		})
-	}
-}
-
-func TestReceiveIPv6ExtHdrs(t *testing.T) {
-	tests := []struct {
-		name                    string
-		extHdr                  func(nextHdr uint8) ([]byte, uint8)
-		shouldAccept            bool
-		countersToBeIncremented func(*tcpip.Stats) []*tcpip.StatCounter
-		// 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 router alert option",
-			extHdr: func(nextHdr uint8) ([]byte, uint8) {
-				return []byte{
-					nextHdr, 0,
-
-					// Router Alert option.
-					5, 2, 0, 0, 0, 0,
-				}, hopByHopExtHdrID
-			},
-			shouldAccept: true,
-			countersToBeIncremented: func(stats *tcpip.Stats) []*tcpip.StatCounter {
-				return []*tcpip.StatCounter{stats.IP.OptionRouterAlertReceived}
-			},
-		},
-		{
-			name: "hopbyhop with two router alert options",
-			extHdr: func(nextHdr uint8) ([]byte, uint8) {
-				return []byte{
-					nextHdr, 1,
-
-					// Router Alert option.
-					5, 2, 0, 0, 0, 0,
-
-					// Router Alert option.
-					5, 2, 0, 0, 0, 0, 0, 0,
-				}, hopByHopExtHdrID
-			},
-			shouldAccept: false,
-			countersToBeIncremented: func(stats *tcpip.Stats) []*tcpip.StatCounter {
-				return []*tcpip.StatCounter{
-					stats.IP.OptionRouterAlertReceived,
-					stats.IP.MalformedPacketsReceived,
-				}
-			},
-		},
-		{
-			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: "unknown next header (first)",
-			extHdr: func(nextHdr uint8) ([]byte, uint8) {
-				return []byte{
-					nextHdr, 0, 63, 4, 1, 2, 3, 4,
-				}, unknownHdrID
-			},
-			shouldAccept: false,
-			expectICMP:   true,
-			ICMPType:     header.ICMPv6ParamProblem,
-			ICMPCode:     header.ICMPv6UnknownHeader,
-			pointer:      header.IPv6NextHeaderOffset,
-		},
-		{
-			name: "unknown next header (not first)",
-			extHdr: func(nextHdr uint8) ([]byte, uint8) {
-				return []byte{
-					unknownHdrID, 0,
-					63, 4, 1, 2, 3, 4,
-				}, hopByHopExtHdrID
-			},
-			shouldAccept: false,
-			expectICMP:   true,
-			ICMPType:     header.ICMPv6ParamProblem,
-			ICMPCode:     header.ICMPv6UnknownHeader,
-			pointer:      header.IPv6FixedHeaderSize,
-		},
-		{
-			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: "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)
-
-			dstAddr := tcpip.Address(addr2)
-			if test.multicast {
-				dstAddr = header.IPv6AllNodesMulticastAddress
-			}
-
-			sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, addr1, dstAddr, 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))
-			ip.Encode(&header.IPv6Fields{
-				PayloadLength: uint16(payloadLength),
-				// We're lying about transport protocol here to be able to generate
-				// raw extension headers from the test definitions.
-				TransportProtocol: tcpip.TransportProtocolNumber(ipv6NextHdr),
-				HopLimit:          255,
-				SrcAddr:           addr1,
-				DstAddr:           dstAddr,
-			})
-
-			stats := s.Stats()
-			var counters []*tcpip.StatCounter
-			// Make sure that the counters we expect to be incremented are initially
-			// set to zero.
-			if fn := test.countersToBeIncremented; fn != nil {
-				counters = fn(&stats)
-			}
-			for i := range counters {
-				if got := counters[i].Value(); got != 0 {
-					t.Errorf("before writing packet: got test.countersToBeIncremented(&stats)[%d].Value() = %d, want = 0", i, got)
-				}
-			}
-
-			e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-				Data: hdr.View().ToVectorisedView(),
-			}))
-
-			for i := range counters {
-				if got := counters[i].Value(); got != 1 {
-					t.Errorf("after writing packet: got test.countersToBeIncremented(&stats)[%d].Value() = %d, want = 1", i, got)
-				}
-			}
-
-			udpReceiveStat := stats.UDP.PacketsReceived
-			if !test.shouldAccept {
-				if got := udpReceiveStat.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 := udpReceiveStat.Value(); got != 1 {
-				t.Errorf("got UDP Rx Packets = %d, want = 1", got)
-			}
-			var buf bytes.Buffer
-			result, err := ep.Read(&buf, tcpip.ReadOptions{})
-			if err != nil {
-				t.Fatalf("Read: %s", err)
-			}
-			if diff := cmp.Diff(tcpip.ReadResult{
-				Count: len(udpPayload),
-				Total: len(udpPayload),
-			}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
-				t.Errorf("Read: unexpected result (-want +got):\n%s", diff)
-			}
-			if diff := cmp.Diff(udpPayload, buf.Bytes()); diff != "" {
-				t.Errorf("got UDP payload mismatch (-want +got):\n%s", diff)
-			}
-
-			// Should not have any more UDP packets.
-			res, err := ep.Read(ioutil.Discard, tcpip.ReadOptions{})
-			if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-				t.Fatalf("got Read = (%v, %v), want = (_, %s)", res, 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
-		udpMaximumSizeMinus15 = header.UDPMaximumSize - 15
-		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+udpMaximumSizeMinus15,
-						[]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[:udpMaximumSizeMinus15],
-						},
-					),
-				},
-				{
-					srcAddr: addr1,
-					dstAddr: addr2,
-					nextHdr: fragmentExtHdrID,
-					data: buffer.NewVectorisedView(
-						fragmentExtHdrLen+len(ipv6Payload4Addr1ToAddr2)-udpMaximumSizeMinus15,
-						[]buffer.View{
-							// Fragment extension header.
-							//
-							// Fragment offset = udpMaximumSizeMinus15/8, More = false, ID = 1
-							buffer.View([]byte{uint8(header.UDPProtocolNumber), 0,
-								udpMaximumSizeMinus15 >> 8,
-								udpMaximumSizeMinus15 & 0xff,
-								0, 0, 0, 1}),
-
-							ipv6Payload4Addr1ToAddr2[udpMaximumSizeMinus15:],
-						},
-					),
-				},
-			},
-			expectedPayloads: [][]byte{udpPayload4Addr1ToAddr2},
-		},
-		{
-			name: "Two fragments with MF flag reassembled into a maximum UDP packet",
-			fragments: []fragmentData{
-				{
-					srcAddr: addr1,
-					dstAddr: addr2,
-					nextHdr: fragmentExtHdrID,
-					data: buffer.NewVectorisedView(
-						fragmentExtHdrLen+udpMaximumSizeMinus15,
-						[]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[:udpMaximumSizeMinus15],
-						},
-					),
-				},
-				{
-					srcAddr: addr1,
-					dstAddr: addr2,
-					nextHdr: fragmentExtHdrID,
-					data: buffer.NewVectorisedView(
-						fragmentExtHdrLen+len(ipv6Payload4Addr1ToAddr2)-udpMaximumSizeMinus15,
-						[]buffer.View{
-							// Fragment extension header.
-							//
-							// Fragment offset = udpMaximumSizeMinus15/8, More = true, ID = 1
-							buffer.View([]byte{uint8(header.UDPProtocolNumber), 0,
-								udpMaximumSizeMinus15 >> 8,
-								(udpMaximumSizeMinus15 & 0xff) + 1,
-								0, 0, 0, 1}),
-
-							ipv6Payload4Addr1ToAddr2[udpMaximumSizeMinus15:],
-						},
-					),
-				},
-			},
-			expectedPayloads: nil,
-		},
-		{
-			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()),
-					// We're lying about transport protocol here so that we can generate
-					// raw extension headers for the tests.
-					TransportProtocol: tcpip.TransportProtocolNumber(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 {
-				var buf bytes.Buffer
-				_, err := ep.Read(&buf, tcpip.ReadOptions{})
-				if err != nil {
-					t.Fatalf("(i=%d) Read: %s", i, err)
-				}
-				if diff := cmp.Diff(p, buf.Bytes()); diff != "" {
-					t.Errorf("(i=%d) got UDP payload mismatch (-want +got):\n%s", i, diff)
-				}
-			}
-
-			res, err := ep.Read(ioutil.Discard, tcpip.ReadOptions{})
-			if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-				t.Fatalf("(last) got Read = (%v, %v), want = (_, %s)", res, 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.IPv6SerializableFragmentExtHdr
-		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,
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						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,
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						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))
-				encodeArgs := f.ipv6Fields
-				encodeArgs.ExtensionHeaders = append(encodeArgs.ExtensionHeaders, &f.ipv6FragmentFields)
-				ip.Encode(&encodeArgs)
-
-				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.IPv6SerializableFragmentExtHdr
-		payload            []byte
-	}
-
-	tests := []struct {
-		name       string
-		fragments  []fragmentData
-		expectICMP bool
-	}{
-		{
-			name: "first fragment only",
-			fragments: []fragmentData{
-				{
-					ipv6Fields: header.IPv6Fields{
-						PayloadLength:     header.IPv6FragmentHeaderSize + 16,
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						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,
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						FragmentOffset: 0,
-						M:              true,
-						Identification: ident,
-					},
-					payload: []byte(data)[:16],
-				},
-				{
-					ipv6Fields: header.IPv6Fields{
-						PayloadLength:     header.IPv6FragmentHeaderSize + 16,
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						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),
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						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,
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						FragmentOffset: 0,
-						M:              true,
-						Identification: ident,
-					},
-					payload: []byte(data)[:16],
-				},
-				{
-					ipv6Fields: header.IPv6Fields{
-						PayloadLength:     uint16(header.IPv6FragmentHeaderSize + len(data) - 16),
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						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),
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						FragmentOffset: 8,
-						M:              false,
-						Identification: ident,
-					},
-					payload: []byte(data)[16:],
-				},
-				{
-					ipv6Fields: header.IPv6Fields{
-						PayloadLength:     header.IPv6FragmentHeaderSize + 16,
-						TransportProtocol: header.UDPProtocolNumber,
-						HopLimit:          hoplimit,
-						SrcAddr:           addr1,
-						DstAddr:           addr2,
-					},
-					ipv6FragmentFields: header.IPv6SerializableFragmentExtHdr{
-						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))
-				encodeArgs := f.ipv6Fields
-				encodeArgs.ExtensionHeaders = append(encodeArgs.ExtensionHeaders, &f.ipv6FragmentFields)
-				ip.Encode(&encodeArgs)
-
-				fragHDR := header.IPv6Fragment(hdr.View()[header.IPv6MinimumSize:])
-
-				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, string) (bool, bool) {
-	if lm.limit == 0 {
-		return true, false
-	}
-	lm.limit--
-	return false, false
-}
-
-func knownNICIDs(proto *protocol) []tcpip.NICID {
-	var nicIDs []tcpip.NICID
-
-	for k := range proto.mu.eps {
-		nicIDs = append(nicIDs, k)
-	}
-
-	return nicIDs
-}
-
-func TestClearEndpointFromProtocolOnClose(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
-	})
-	proto := s.NetworkProtocolInstance(ProtocolNumber).(*protocol)
-	var nic testInterface
-	ep := proto.NewEndpoint(&nic, nil).(*endpoint)
-	var nicIDs []tcpip.NICID
-
-	proto.mu.Lock()
-	foundEP, hasEndpointBeforeClose := proto.mu.eps[nic.ID()]
-	nicIDs = knownNICIDs(proto)
-	proto.mu.Unlock()
-	if !hasEndpointBeforeClose {
-		t.Fatalf("expected to find the nic id %d in the protocol's known nic ids (%v)", nic.ID(), nicIDs)
-	}
-	if foundEP != ep {
-		t.Fatalf("found an incorrect endpoint mapped to nic id %d", nic.ID())
-	}
-
-	ep.Close()
-
-	proto.mu.Lock()
-	_, hasEndpointAfterClose := proto.mu.eps[nic.ID()]
-	nicIDs = knownNICIDs(proto)
-	proto.mu.Unlock()
-	if hasEndpointAfterClose {
-		t.Fatalf("unexpectedly found an endpoint mapped to the nic id %d in the protocol's known nic ids (%v)", nic.ID(), nicIDs)
-	}
-}
-
-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 diff := cmp.Diff(ft.wantError, err); diff != "" {
-				t.Errorf("unexpected error from WritePacket(_, _, _), (-want, +got):\n%s", diff)
-			}
-			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 TestForwarding(t *testing.T) {
-	const (
-		nicID1         = 1
-		nicID2         = 2
-		randomSequence = 123
-		randomIdent    = 42
-	)
-
-	ipv6Addr1 := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(net.ParseIP("10::1").To16()),
-		PrefixLen: 64,
-	}
-	ipv6Addr2 := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(net.ParseIP("11::1").To16()),
-		PrefixLen: 64,
-	}
-	remoteIPv6Addr1 := tcpip.Address(net.ParseIP("10::2").To16())
-	remoteIPv6Addr2 := tcpip.Address(net.ParseIP("11::2").To16())
-
-	tests := []struct {
-		name            string
-		TTL             uint8
-		expectErrorICMP bool
-	}{
-		{
-			name:            "TTL of zero",
-			TTL:             0,
-			expectErrorICMP: true,
-		},
-		{
-			name:            "TTL of one",
-			TTL:             1,
-			expectErrorICMP: true,
-		},
-		{
-			name:            "TTL of two",
-			TTL:             2,
-			expectErrorICMP: false,
-		},
-		{
-			name:            "TTL of three",
-			TTL:             3,
-			expectErrorICMP: false,
-		},
-		{
-			name:            "Max TTL",
-			TTL:             math.MaxUint8,
-			expectErrorICMP: 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{icmp.NewProtocol6},
-			})
-			// We expect at most a single packet in response to our ICMP Echo Request.
-			e1 := channel.New(1, header.IPv6MinimumMTU, "")
-			if err := s.CreateNIC(nicID1, e1); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
-			}
-			ipv6ProtoAddr1 := tcpip.ProtocolAddress{Protocol: ProtocolNumber, AddressWithPrefix: ipv6Addr1}
-			if err := s.AddProtocolAddress(nicID1, ipv6ProtoAddr1); err != nil {
-				t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID1, ipv6ProtoAddr1, err)
-			}
-
-			e2 := channel.New(1, header.IPv6MinimumMTU, "")
-			if err := s.CreateNIC(nicID2, e2); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID2, err)
-			}
-			ipv6ProtoAddr2 := tcpip.ProtocolAddress{Protocol: ProtocolNumber, AddressWithPrefix: ipv6Addr2}
-			if err := s.AddProtocolAddress(nicID2, ipv6ProtoAddr2); err != nil {
-				t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID2, ipv6ProtoAddr2, err)
-			}
-
-			s.SetRouteTable([]tcpip.Route{
-				{
-					Destination: ipv6Addr1.Subnet(),
-					NIC:         nicID1,
-				},
-				{
-					Destination: ipv6Addr2.Subnet(),
-					NIC:         nicID2,
-				},
-			})
-
-			if err := s.SetForwarding(ProtocolNumber, true); err != nil {
-				t.Fatalf("SetForwarding(%d, true): %s", ProtocolNumber, err)
-			}
-
-			hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.ICMPv6MinimumSize)
-			icmp := header.ICMPv6(hdr.Prepend(header.ICMPv6MinimumSize))
-			icmp.SetIdent(randomIdent)
-			icmp.SetSequence(randomSequence)
-			icmp.SetType(header.ICMPv6EchoRequest)
-			icmp.SetCode(header.ICMPv6UnusedCode)
-			icmp.SetChecksum(0)
-			icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-				Header: icmp,
-				Src:    remoteIPv6Addr1,
-				Dst:    remoteIPv6Addr2,
-			}))
-			ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-			ip.Encode(&header.IPv6Fields{
-				PayloadLength:     header.ICMPv6MinimumSize,
-				TransportProtocol: header.ICMPv6ProtocolNumber,
-				HopLimit:          test.TTL,
-				SrcAddr:           remoteIPv6Addr1,
-				DstAddr:           remoteIPv6Addr2,
-			})
-			requestPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-				Data: hdr.View().ToVectorisedView(),
-			})
-			e1.InjectInbound(ProtocolNumber, requestPkt)
-
-			if test.expectErrorICMP {
-				reply, ok := e1.Read()
-				if !ok {
-					t.Fatal("expected ICMP Hop Limit Exceeded packet through incoming NIC")
-				}
-
-				checker.IPv6(t, header.IPv6(stack.PayloadSince(reply.Pkt.NetworkHeader())),
-					checker.SrcAddr(ipv6Addr1.Address),
-					checker.DstAddr(remoteIPv6Addr1),
-					checker.TTL(DefaultTTL),
-					checker.ICMPv6(
-						checker.ICMPv6Type(header.ICMPv6TimeExceeded),
-						checker.ICMPv6Code(header.ICMPv6HopLimitExceeded),
-						checker.ICMPv6Payload([]byte(hdr.View())),
-					),
-				)
-
-				if n := e2.Drain(); n != 0 {
-					t.Fatalf("got e2.Drain() = %d, want = 0", n)
-				}
-			} else {
-				reply, ok := e2.Read()
-				if !ok {
-					t.Fatal("expected ICMP Echo Request packet through outgoing NIC")
-				}
-
-				checker.IPv6(t, header.IPv6(stack.PayloadSince(reply.Pkt.NetworkHeader())),
-					checker.SrcAddr(remoteIPv6Addr1),
-					checker.DstAddr(remoteIPv6Addr2),
-					checker.TTL(test.TTL-1),
-					checker.ICMPv6(
-						checker.ICMPv6Type(header.ICMPv6EchoRequest),
-						checker.ICMPv6Code(header.ICMPv6UnusedCode),
-						checker.ICMPv6Payload(nil),
-					),
-				)
-
-				if n := e1.Drain(); n != 0 {
-					t.Fatalf("got e1.Drain() = %d, want = 0", n)
-				}
-			}
-		})
-	}
-}
-
-func TestMultiCounterStatsInitialization(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
-	})
-	proto := s.NetworkProtocolInstance(ProtocolNumber).(*protocol)
-	var nic testInterface
-	ep := proto.NewEndpoint(&nic, nil).(*endpoint)
-	// At this point, the Stack's stats and the NetworkEndpoint's stats are
-	// supposed to be bound.
-	refStack := s.Stats()
-	refEP := ep.stats.localStats
-	if err := testutil.ValidateMultiCounterStats(reflect.ValueOf(&ep.stats.ip).Elem(), []reflect.Value{reflect.ValueOf(&refStack.IP).Elem(), reflect.ValueOf(&refEP.IP).Elem()}); err != nil {
-		t.Error(err)
-	}
-	if err := testutil.ValidateMultiCounterStats(reflect.ValueOf(&ep.stats.icmp).Elem(), []reflect.Value{reflect.ValueOf(&refStack.ICMP.V6).Elem(), reflect.ValueOf(&refEP.ICMP).Elem()}); err != nil {
-		t.Error(err)
-	}
-}
diff --git a/pkg/tcpip/network/ipv6/mld_test.go b/pkg/tcpip/network/ipv6/mld_test.go
deleted file mode 100644
index 85a8f99..0000000
--- a/pkg/tcpip/network/ipv6/mld_test.go
+++ /dev/null
@@ -1,466 +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 ipv6_test
-
-import (
-	"bytes"
-	"testing"
-	"time"
-
-	"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/ipv6"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-const (
-	linkLocalAddr       = "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
-	globalAddr          = "\x0a\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
-	globalMulticastAddr = "\xff\x05\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
-)
-
-var (
-	linkLocalAddrSNMC = header.SolicitedNodeAddr(linkLocalAddr)
-	globalAddrSNMC    = header.SolicitedNodeAddr(globalAddr)
-)
-
-func validateMLDPacket(t *testing.T, p buffer.View, localAddress, remoteAddress tcpip.Address, mldType header.ICMPv6Type, groupAddress tcpip.Address) {
-	t.Helper()
-
-	checker.IPv6WithExtHdr(t, p,
-		checker.IPv6ExtHdr(
-			checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)),
-		),
-		checker.SrcAddr(localAddress),
-		checker.DstAddr(remoteAddress),
-		checker.TTL(header.MLDHopLimit),
-		checker.MLD(mldType, header.MLDMinimumSize,
-			checker.MLDMaxRespDelay(0),
-			checker.MLDMulticastAddress(groupAddress),
-		),
-	)
-}
-
-func TestIPv6JoinLeaveSolicitedNodeAddressPerformsMLD(t *testing.T) {
-	const nicID = 1
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			MLD: ipv6.MLDOptions{
-				Enabled: true,
-			},
-		})},
-	})
-	e := channel.New(1, header.IPv6MinimumMTU, "")
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-	}
-
-	// The stack will join an address's solicited node multicast address when
-	// an address is added. An MLD report message should be sent for the
-	// solicited-node group.
-	if err := s.AddAddress(nicID, ipv6.ProtocolNumber, linkLocalAddr); err != nil {
-		t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ipv6.ProtocolNumber, linkLocalAddr, err)
-	}
-	if p, ok := e.Read(); !ok {
-		t.Fatal("expected a report message to be sent")
-	} else {
-		validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), linkLocalAddr, linkLocalAddrSNMC, header.ICMPv6MulticastListenerReport, linkLocalAddrSNMC)
-	}
-
-	// The stack will leave an address's solicited node multicast address when
-	// an address is removed. An MLD done message should be sent for the
-	// solicited-node group.
-	if err := s.RemoveAddress(nicID, linkLocalAddr); err != nil {
-		t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, linkLocalAddr, err)
-	}
-	if p, ok := e.Read(); !ok {
-		t.Fatal("expected a done message to be sent")
-	} else {
-		validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), header.IPv6Any, header.IPv6AllRoutersMulticastAddress, header.ICMPv6MulticastListenerDone, linkLocalAddrSNMC)
-	}
-}
-
-func TestSendQueuedMLDReports(t *testing.T) {
-	const (
-		nicID      = 1
-		maxReports = 2
-	)
-
-	tests := []struct {
-		name            string
-		dadTransmits    uint8
-		retransmitTimer time.Duration
-	}{
-		{
-			name:            "DAD Disabled",
-			dadTransmits:    0,
-			retransmitTimer: 0,
-		},
-		{
-			name:            "DAD Enabled",
-			dadTransmits:    1,
-			retransmitTimer: time.Second,
-		},
-	}
-
-	nonce := [...]byte{
-		1, 2, 3, 4, 5, 6,
-	}
-
-	const maxNSMessages = 2
-	secureRNGBytes := make([]byte, len(nonce)*maxNSMessages)
-	for b := secureRNGBytes[:]; len(b) > 0; b = b[len(nonce):] {
-		if n := copy(b, nonce[:]); n != len(nonce) {
-			t.Fatalf("got copy(...) = %d, want = %d", n, len(nonce))
-		}
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			dadResolutionTime := test.retransmitTimer * time.Duration(test.dadTransmits)
-			clock := faketime.NewManualClock()
-			var secureRNG bytes.Reader
-			secureRNG.Reset(secureRNGBytes[:])
-			s := stack.New(stack.Options{
-				SecureRNG: &secureRNG,
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					DADConfigs: stack.DADConfigurations{
-						DupAddrDetectTransmits: test.dadTransmits,
-						RetransmitTimer:        test.retransmitTimer,
-					},
-					MLD: ipv6.MLDOptions{
-						Enabled: true,
-					},
-				})},
-				Clock: clock,
-			})
-
-			// Allow space for an extra packet so we can observe packets that were
-			// unexpectedly sent.
-			e := channel.New(maxReports+int(test.dadTransmits)+1 /* extra */, header.IPv6MinimumMTU, "")
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-			}
-
-			resolveDAD := func(addr, snmc tcpip.Address) {
-				clock.Advance(dadResolutionTime)
-				if p, ok := e.Read(); !ok {
-					t.Fatal("expected DAD packet")
-				} else {
-					checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
-						checker.SrcAddr(header.IPv6Any),
-						checker.DstAddr(snmc),
-						checker.TTL(header.NDPHopLimit),
-						checker.NDPNS(
-							checker.NDPNSTargetAddress(addr),
-							checker.NDPNSOptions([]header.NDPOption{header.NDPNonceOption(nonce[:])}),
-						))
-				}
-			}
-
-			var reportCounter uint64
-			reportStat := s.Stats().ICMP.V6.PacketsSent.MulticastListenerReport
-			if got := reportStat.Value(); got != reportCounter {
-				t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter)
-			}
-			var doneCounter uint64
-			doneStat := s.Stats().ICMP.V6.PacketsSent.MulticastListenerDone
-			if got := doneStat.Value(); got != doneCounter {
-				t.Errorf("got doneStat.Value() = %d, want = %d", got, doneCounter)
-			}
-
-			// Joining a group without an assigned address should send an MLD report
-			// with the unspecified address.
-			if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, globalMulticastAddr); err != nil {
-				t.Fatalf("JoinGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, globalMulticastAddr, err)
-			}
-			reportCounter++
-			if got := reportStat.Value(); got != reportCounter {
-				t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter)
-			}
-			if p, ok := e.Read(); !ok {
-				t.Errorf("expected MLD report for %s", globalMulticastAddr)
-			} else {
-				validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), header.IPv6Any, globalMulticastAddr, header.ICMPv6MulticastListenerReport, globalMulticastAddr)
-			}
-			clock.Advance(time.Hour)
-			if p, ok := e.Read(); ok {
-				t.Errorf("got unexpected packet = %#v", p)
-			}
-			if t.Failed() {
-				t.FailNow()
-			}
-
-			// Adding a global address should not send reports for the already joined
-			// group since we should only send queued reports when a link-local
-			// address is assigned.
-			//
-			// Note, we will still expect to send a report for the global address's
-			// solicited node address from the unspecified address as per  RFC 3590
-			// section 4.
-			if err := s.AddAddressWithOptions(nicID, ipv6.ProtocolNumber, globalAddr, stack.FirstPrimaryEndpoint); err != nil {
-				t.Fatalf("AddAddressWithOptions(%d, %d, %s, %d): %s", nicID, ipv6.ProtocolNumber, globalAddr, stack.FirstPrimaryEndpoint, err)
-			}
-			reportCounter++
-			if got := reportStat.Value(); got != reportCounter {
-				t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter)
-			}
-			if p, ok := e.Read(); !ok {
-				t.Errorf("expected MLD report for %s", globalAddrSNMC)
-			} else {
-				validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), header.IPv6Any, globalAddrSNMC, header.ICMPv6MulticastListenerReport, globalAddrSNMC)
-			}
-			if dadResolutionTime != 0 {
-				// Reports should not be sent when the address resolves.
-				resolveDAD(globalAddr, globalAddrSNMC)
-				if got := reportStat.Value(); got != reportCounter {
-					t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter)
-				}
-			}
-			// Leave the group since we don't care about the global address's
-			// solicited node multicast group membership.
-			if err := s.LeaveGroup(ipv6.ProtocolNumber, nicID, globalAddrSNMC); err != nil {
-				t.Fatalf("LeaveGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, globalAddrSNMC, err)
-			}
-			if got := doneStat.Value(); got != doneCounter {
-				t.Errorf("got doneStat.Value() = %d, want = %d", got, doneCounter)
-			}
-			if p, ok := e.Read(); ok {
-				t.Errorf("got unexpected packet = %#v", p)
-			}
-			if t.Failed() {
-				t.FailNow()
-			}
-
-			// Adding a link-local address should send a report for its solicited node
-			// address and globalMulticastAddr.
-			if err := s.AddAddressWithOptions(nicID, ipv6.ProtocolNumber, linkLocalAddr, stack.CanBePrimaryEndpoint); err != nil {
-				t.Fatalf("AddAddressWithOptions(%d, %d, %s, %d): %s", nicID, ipv6.ProtocolNumber, linkLocalAddr, stack.CanBePrimaryEndpoint, err)
-			}
-			if dadResolutionTime != 0 {
-				reportCounter++
-				if got := reportStat.Value(); got != reportCounter {
-					t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter)
-				}
-				if p, ok := e.Read(); !ok {
-					t.Errorf("expected MLD report for %s", linkLocalAddrSNMC)
-				} else {
-					validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), header.IPv6Any, linkLocalAddrSNMC, header.ICMPv6MulticastListenerReport, linkLocalAddrSNMC)
-				}
-				resolveDAD(linkLocalAddr, linkLocalAddrSNMC)
-			}
-
-			// We expect two batches of reports to be sent (1 batch when the
-			// link-local address is assigned, and another after the maximum
-			// unsolicited report interval.
-			for i := 0; i < 2; i++ {
-				// We expect reports to be sent (one for globalMulticastAddr and another
-				// for linkLocalAddrSNMC).
-				reportCounter += maxReports
-				if got := reportStat.Value(); got != reportCounter {
-					t.Errorf("got reportStat.Value() = %d, want = %d", got, reportCounter)
-				}
-
-				addrs := map[tcpip.Address]bool{
-					globalMulticastAddr: false,
-					linkLocalAddrSNMC:   false,
-				}
-				for range addrs {
-					p, ok := e.Read()
-					if !ok {
-						t.Fatalf("expected MLD report for %s and %s; addrs = %#v", globalMulticastAddr, linkLocalAddrSNMC, addrs)
-					}
-
-					addr := header.IPv6(stack.PayloadSince(p.Pkt.NetworkHeader())).DestinationAddress()
-					if seen, ok := addrs[addr]; !ok {
-						t.Fatalf("got unexpected packet destined to %s", addr)
-					} else if seen {
-						t.Fatalf("got another packet destined to %s", addr)
-					}
-
-					addrs[addr] = true
-					validateMLDPacket(t, stack.PayloadSince(p.Pkt.NetworkHeader()), linkLocalAddr, addr, header.ICMPv6MulticastListenerReport, addr)
-
-					clock.Advance(ipv6.UnsolicitedReportIntervalMax)
-				}
-			}
-
-			// Should not send any more reports.
-			clock.Advance(time.Hour)
-			if p, ok := e.Read(); ok {
-				t.Errorf("got unexpected packet = %#v", p)
-			}
-		})
-	}
-}
-
-// createAndInjectMLDPacket creates and injects an MLD packet with the
-// specified fields.
-func createAndInjectMLDPacket(e *channel.Endpoint, mldType header.ICMPv6Type, hopLimit uint8, srcAddress tcpip.Address, withRouterAlertOption bool, routerAlertValue header.IPv6RouterAlertValue) {
-	var extensionHeaders header.IPv6ExtHdrSerializer
-	if withRouterAlertOption {
-		extensionHeaders = header.IPv6ExtHdrSerializer{
-			header.IPv6SerializableHopByHopExtHdr{
-				&header.IPv6RouterAlertOption{Value: routerAlertValue},
-			},
-		}
-	}
-
-	extensionHeadersLength := extensionHeaders.Length()
-	payloadLength := extensionHeadersLength + header.ICMPv6HeaderSize + header.MLDMinimumSize
-	buf := buffer.NewView(header.IPv6MinimumSize + payloadLength)
-
-	ip := header.IPv6(buf)
-	ip.Encode(&header.IPv6Fields{
-		PayloadLength:     uint16(payloadLength),
-		HopLimit:          hopLimit,
-		TransportProtocol: header.ICMPv6ProtocolNumber,
-		SrcAddr:           srcAddress,
-		DstAddr:           header.IPv6AllNodesMulticastAddress,
-		ExtensionHeaders:  extensionHeaders,
-	})
-
-	icmp := header.ICMPv6(ip.Payload()[extensionHeadersLength:])
-	icmp.SetType(mldType)
-	mld := header.MLD(icmp.MessageBody())
-	mld.SetMaximumResponseDelay(0)
-	mld.SetMulticastAddress(header.IPv6Any)
-	icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-		Header: icmp,
-		Src:    srcAddress,
-		Dst:    header.IPv6AllNodesMulticastAddress,
-	}))
-
-	e.InjectInbound(ipv6.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-}
-
-func TestMLDPacketValidation(t *testing.T) {
-	const (
-		nicID          = 1
-		linkLocalAddr2 = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-	)
-
-	tests := []struct {
-		name                     string
-		messageType              header.ICMPv6Type
-		srcAddr                  tcpip.Address
-		includeRouterAlertOption bool
-		routerAlertValue         header.IPv6RouterAlertValue
-		hopLimit                 uint8
-		expectValidMLD           bool
-		getMessageTypeStatValue  func(tcpip.Stats) uint64
-	}{
-		{
-			name:                     "valid",
-			messageType:              header.ICMPv6MulticastListenerQuery,
-			includeRouterAlertOption: true,
-			routerAlertValue:         header.IPv6RouterAlertMLD,
-			srcAddr:                  linkLocalAddr2,
-			hopLimit:                 header.MLDHopLimit,
-			expectValidMLD:           true,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerQuery.Value() },
-		},
-		{
-			name:                     "bad hop limit",
-			messageType:              header.ICMPv6MulticastListenerReport,
-			includeRouterAlertOption: true,
-			routerAlertValue:         header.IPv6RouterAlertMLD,
-			srcAddr:                  linkLocalAddr2,
-			hopLimit:                 header.MLDHopLimit + 1,
-			expectValidMLD:           false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerReport.Value() },
-		},
-		{
-			name:                     "src ip not link local",
-			messageType:              header.ICMPv6MulticastListenerReport,
-			includeRouterAlertOption: true,
-			routerAlertValue:         header.IPv6RouterAlertMLD,
-			srcAddr:                  globalAddr,
-			hopLimit:                 header.MLDHopLimit,
-			expectValidMLD:           false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerReport.Value() },
-		},
-		{
-			name:                     "missing router alert ip option",
-			messageType:              header.ICMPv6MulticastListenerDone,
-			includeRouterAlertOption: false,
-			srcAddr:                  linkLocalAddr2,
-			hopLimit:                 header.MLDHopLimit,
-			expectValidMLD:           false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerDone.Value() },
-		},
-		{
-			name:                     "incorrect router alert value",
-			messageType:              header.ICMPv6MulticastListenerDone,
-			includeRouterAlertOption: true,
-			routerAlertValue:         header.IPv6RouterAlertRSVP,
-			srcAddr:                  linkLocalAddr2,
-			hopLimit:                 header.MLDHopLimit,
-			expectValidMLD:           false,
-			getMessageTypeStatValue:  func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerDone.Value() },
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					MLD: ipv6.MLDOptions{
-						Enabled: true,
-					},
-				})},
-			})
-			e := channel.New(nicID, header.IPv6MinimumMTU, "")
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-			}
-			stats := s.Stats()
-			// Verify that every relevant stats is zero'd before we send a packet.
-			if got := test.getMessageTypeStatValue(s.Stats()); got != 0 {
-				t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 0", got)
-			}
-			if got := stats.ICMP.V6.PacketsReceived.Invalid.Value(); got != 0 {
-				t.Errorf("got stats.ICMP.V6.PacketsReceived.Invalid.Value() = %d, want = 0", got)
-			}
-			if got := stats.IP.PacketsDelivered.Value(); got != 0 {
-				t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 0", got)
-			}
-			createAndInjectMLDPacket(e, test.messageType, test.hopLimit, test.srcAddr, test.includeRouterAlertOption, test.routerAlertValue)
-			// We always expect the packet to pass IP validation.
-			if got := stats.IP.PacketsDelivered.Value(); got != 1 {
-				t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 1", got)
-			}
-			// Even when the MLD-specific validation checks fail, we expect the
-			// corresponding MLD counter to be incremented.
-			if got := test.getMessageTypeStatValue(s.Stats()); got != 1 {
-				t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 1", got)
-			}
-			var expectedInvalidCount uint64
-			if !test.expectValidMLD {
-				expectedInvalidCount = 1
-			}
-			if got := stats.ICMP.V6.PacketsReceived.Invalid.Value(); got != expectedInvalidCount {
-				t.Errorf("got stats.ICMP.V6.PacketsReceived.Invalid.Value() = %d, want = %d", got, expectedInvalidCount)
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/network/ipv6/ndp_test.go b/pkg/tcpip/network/ipv6/ndp_test.go
deleted file mode 100644
index 52b9a20..0000000
--- a/pkg/tcpip/network/ipv6/ndp_test.go
+++ /dev/null
@@ -1,1376 +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 (
-	"bytes"
-	"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/faketime"
-	"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) (*stack.Stack, stack.NetworkEndpoint) {
-	t.Helper()
-
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol6},
-	})
-
-	if err := s.CreateNIC(1, &stubLinkEndpoint{}); err != nil {
-		t.Fatalf("CreateNIC(_) = %s", 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{}, &stubDispatcher{})
-	if err := ep.Enable(); err != nil {
-		t.Fatalf("ep.Enable(): %s", err)
-	}
-	t.Cleanup(ep.Close)
-
-	addressableEndpoint, ok := ep.(stack.AddressableEndpoint)
-	if !ok {
-		t.Fatalf("expected network endpoint to implement stack.AddressableEndpoint")
-	}
-	addr := llladdr.WithPrefix()
-	if addressEP, err := addressableEndpoint.AddAndAcquirePermanentAddress(addr, stack.CanBePrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */); err != nil {
-		t.Fatalf("addressableEndpoint.AddAndAcquirePermanentAddress(%s, CanBePrimaryEndpoint, AddressConfigStatic, false): %s", addr, err)
-	} else {
-		addressEP.DecRef()
-	}
-
-	return s, ep
-}
-
-var _ NDPDispatcher = (*testNDPDispatcher)(nil)
-
-// testNDPDispatcher is an NDPDispatcher only allows default router discovery.
-type testNDPDispatcher struct {
-	addr tcpip.Address
-}
-
-func (*testNDPDispatcher) OnDuplicateAddressDetectionResult(tcpip.NICID, tcpip.Address, stack.DADResult) {
-}
-
-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)
-	}
-}
-
-type linkResolutionResult struct {
-	linkAddr tcpip.LinkAddress
-	ok       bool
-}
-
-// TestNeighborSolicitationWithSourceLinkLayerOption 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 TestNeighborSolicitationWithSourceLinkLayerOption(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)
-			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.MessageBody())
-			ns.SetTargetAddress(lladdr0)
-			opts := ns.Options()
-			copy(opts, test.optsBuf)
-			pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-				Header: pkt,
-				Src:    lladdr1,
-				Dst:    lladdr0,
-			}))
-			payloadLength := hdr.UsedLength()
-			ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-			ip.Encode(&header.IPv6Fields{
-				PayloadLength:     uint16(payloadLength),
-				TransportProtocol: header.ICMPv6ProtocolNumber,
-				HopLimit:          255,
-				SrcAddr:           lladdr1,
-				DstAddr:           lladdr0,
-			})
-
-			invalid := s.Stats().ICMP.V6.PacketsReceived.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(),
-			}))
-
-			neighbors, err := s.Neighbors(nicID, ProtocolNumber)
-			if err != nil {
-				t.Fatalf("s.Neighbors(%d, %d): %s", nicID, ProtocolNumber, 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, %d) returned unexpected duplicate neighbor entry (-existing +got):\n%s", nicID, ProtocolNumber, diff)
-					}
-					t.Fatalf("s.Neighbors(%d, %d) returned unexpected duplicate neighbor entry: %#v", nicID, ProtocolNumber, 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 TestNeighborSolicitationResponse(t *testing.T) {
-	const nicID = 1
-	nicAddr := lladdr0
-	remoteAddr := lladdr1
-	nicAddrSNMC := header.SolicitedNodeAddr(nicAddr)
-	nicLinkAddr := linkAddr0
-	remoteLinkAddr0 := linkAddr1
-	remoteLinkAddr1 := linkAddr2
-
-	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 _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocol},
-			})
-			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{
-				{
-					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.MessageBody())
-			ns.SetTargetAddress(nicAddr)
-			opts := ns.Options()
-			opts.Serialize(test.nsOpts)
-			pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-				Header: pkt,
-				Src:    test.nsSrc,
-				Dst:    test.nsDst,
-			}))
-			payloadLength := hdr.UsedLength()
-			ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-			ip.Encode(&header.IPv6Fields{
-				PayloadLength:     uint16(payloadLength),
-				TransportProtocol: header.ICMPv6ProtocolNumber,
-				HopLimit:          255,
-				SrcAddr:           test.nsSrc,
-				DstAddr:           test.nsDst,
-			})
-
-			invalid := s.Stats().ICMP.V6.PacketsReceived.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")
-				}
-
-				respNSDst := header.SolicitedNodeAddr(test.nsSrc)
-				var want stack.RouteInfo
-				want.NetProto = ProtocolNumber
-				want.RemoteLinkAddress = header.EthernetAddressFromMulticastIPv6Address(respNSDst)
-				if diff := cmp.Diff(want, p.Route, cmp.AllowUnexported(want)); diff != "" {
-					t.Errorf("route info mismatch (-want +got):\n%s", diff)
-				}
-
-				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.MessageBody())
-				na.SetSolicitedFlag(true)
-				na.SetOverrideFlag(true)
-				na.SetTargetAddress(test.nsSrc)
-				na.Options().Serialize(ser)
-				pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-					Header: pkt,
-					Src:    test.nsSrc,
-					Dst:    nicAddr,
-				}))
-				payloadLength := hdr.UsedLength()
-				ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-				ip.Encode(&header.IPv6Fields{
-					PayloadLength:     uint16(payloadLength),
-					TransportProtocol: 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[:]),
-					}),
-				))
-		})
-	}
-}
-
-// TestNeighborAdvertisementWithTargetLinkLayerOption 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 TestNeighborAdvertisementWithTargetLinkLayerOption(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},
-			})
-			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.MessageBody())
-			ns.SetTargetAddress(lladdr1)
-			opts := ns.Options()
-			copy(opts, test.optsBuf)
-			pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-				Header: pkt,
-				Src:    lladdr1,
-				Dst:    lladdr0,
-			}))
-			payloadLength := hdr.UsedLength()
-			ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-			ip.Encode(&header.IPv6Fields{
-				PayloadLength:     uint16(payloadLength),
-				TransportProtocol: header.ICMPv6ProtocolNumber,
-				HopLimit:          255,
-				SrcAddr:           lladdr1,
-				DstAddr:           lladdr0,
-			})
-
-			invalid := s.Stats().ICMP.V6.PacketsReceived.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(),
-			}))
-
-			neighbors, err := s.Neighbors(nicID, ProtocolNumber)
-			if err != nil {
-				t.Fatalf("s.Neighbors(%d, %d): %s", nicID, ProtocolNumber, 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, %d) returned unexpected duplicate neighbor entry (-existing +got):\n%s", nicID, ProtocolNumber, diff)
-					}
-					t.Fatalf("s.Neighbors(%d, %d) returned unexpected duplicate neighbor entry: %#v", nicID, ProtocolNumber, 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) {
-	setup := func(t *testing.T) (*stack.Stack, stack.NetworkEndpoint) {
-		t.Helper()
-
-		// Create a stack with the assigned link-local address lladdr0
-		// and an endpoint to lladdr1.
-		s, ep := setupStackAndEndpoint(t, lladdr0, lladdr1)
-
-		return s, ep
-	}
-
-	handleIPv6Payload := func(payload buffer.View, hopLimit uint8, atomicFragment bool, ep stack.NetworkEndpoint) {
-		var extHdrs header.IPv6ExtHdrSerializer
-		if atomicFragment {
-			extHdrs = append(extHdrs, &header.IPv6SerializableFragmentExtHdr{})
-		}
-		extHdrsLen := extHdrs.Length()
-
-		ip := buffer.NewView(header.IPv6MinimumSize + extHdrsLen)
-		header.IPv6(ip).Encode(&header.IPv6Fields{
-			PayloadLength:     uint16(len(payload) + extHdrsLen),
-			TransportProtocol: header.ICMPv6ProtocolNumber,
-			HopLimit:          hopLimit,
-			SrcAddr:           lladdr1,
-			DstAddr:           lladdr0,
-			ExtensionHeaders:  extHdrs,
-		})
-		vv := ip.ToVectorisedView()
-		vv.AppendView(payload)
-		ep.HandlePacket(stack.NewPacketBuffer(stack.PacketBufferOptions{
-			Data: vv,
-		}))
-	}
-
-	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 := setup(t)
-
-						if isRouter {
-							// Enabling forwarding makes the stack act as a router.
-							s.SetForwarding(ProtocolNumber, true)
-						}
-
-						stats := s.Stats().ICMP.V6.PacketsReceived
-						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(header.ICMPv6ChecksumParams{
-							Header:      icmp[:typ.size],
-							Src:         lladdr0,
-							Dst:         lladdr1,
-							PayloadCsum: header.Checksum(typ.extraData /* initial */, 0),
-							PayloadLen:  len(typ.extraData),
-						}))
-
-						// 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)
-
-						// 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)
-						}
-
-					})
-				}
-			})
-		}
-	}
-}
-
-// TestNeighborAdvertisementValidation tests that the NIC validates received
-// Neighbor Advertisements.
-//
-// In particular, if the IP Destination Address is a multicast address, and the
-// Solicited flag is not zero, the Neighbor Advertisement is invalid and should
-// be discarded.
-func TestNeighborAdvertisementValidation(t *testing.T) {
-	tests := []struct {
-		name          string
-		ipDstAddr     tcpip.Address
-		solicitedFlag bool
-		valid         bool
-	}{
-		{
-			name:          "Multicast IP destination address with Solicited flag set",
-			ipDstAddr:     header.IPv6AllNodesMulticastAddress,
-			solicitedFlag: true,
-			valid:         false,
-		},
-		{
-			name:          "Multicast IP destination address with Solicited flag unset",
-			ipDstAddr:     header.IPv6AllNodesMulticastAddress,
-			solicitedFlag: false,
-			valid:         true,
-		},
-		{
-			name:          "Unicast IP destination address with Solicited flag set",
-			ipDstAddr:     lladdr0,
-			solicitedFlag: true,
-			valid:         true,
-		},
-		{
-			name:          "Unicast IP destination address with Solicited flag unset",
-			ipDstAddr:     lladdr0,
-			solicitedFlag: false,
-			valid:         true,
-		},
-	}
-
-	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, header.IPv6MinimumMTU, 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
-			hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
-			pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
-			pkt.SetType(header.ICMPv6NeighborAdvert)
-			na := header.NDPNeighborAdvert(pkt.MessageBody())
-			na.SetTargetAddress(lladdr1)
-			na.SetSolicitedFlag(test.solicitedFlag)
-			pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-				Header: pkt,
-				Src:    lladdr1,
-				Dst:    test.ipDstAddr,
-			}))
-			payloadLength := hdr.UsedLength()
-			ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-			ip.Encode(&header.IPv6Fields{
-				PayloadLength:     uint16(payloadLength),
-				TransportProtocol: header.ICMPv6ProtocolNumber,
-				HopLimit:          255,
-				SrcAddr:           lladdr1,
-				DstAddr:           test.ipDstAddr,
-			})
-
-			stats := s.Stats().ICMP.V6.PacketsReceived
-			invalid := stats.Invalid
-			rxNA := stats.NeighborAdvert
-
-			if got := rxNA.Value(); got != 0 {
-				t.Fatalf("got rxNA = %d, want = 0", got)
-			}
-			if got := invalid.Value(); got != 0 {
-				t.Fatalf("got invalid = %d, want = 0", got)
-			}
-
-			e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-				Data: hdr.View().ToVectorisedView(),
-			}))
-
-			if got := rxNA.Value(); got != 1 {
-				t.Fatalf("got rxNA = %d, want = 1", got)
-			}
-			var wantInvalid uint64 = 1
-			if test.valid {
-				wantInvalid = 0
-			}
-			if got := invalid.Value(); got != wantInvalid {
-				t.Fatalf("got invalid = %d, want = %d", got, wantInvalid)
-			}
-			// As per RFC 4861 section 7.2.5:
-			//   When a valid Neighbor Advertisement is received ...
-			//   If no entry exists, the advertisement SHOULD be silently discarded.
-			//   There is no need to create an entry if none exists, since the
-			//   recipient has apparently not initiated any communication with the
-			//   target.
-			if neighbors, err := s.Neighbors(nicID, ProtocolNumber); err != nil {
-				t.Fatalf("s.Neighbors(%d, %d): %s", nicID, ProtocolNumber, err)
-			} else if len(neighbors) != 0 {
-				t.Fatalf("got len(neighbors) = %d, want = 0; neighbors = %#v", len(neighbors), neighbors)
-			}
-		})
-	}
-}
-
-// 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) {
-	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 _, 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},
-			})
-
-			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.MessageBody(), test.ndpPayload)
-			payloadLength := hdr.UsedLength()
-			pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-				Header: pkt,
-				Src:    test.src,
-				Dst:    header.IPv6AllNodesMulticastAddress,
-			}))
-			ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-			ip.Encode(&header.IPv6Fields{
-				PayloadLength:     uint16(payloadLength),
-				TransportProtocol: icmp.ProtocolNumber6,
-				HopLimit:          test.hopLimit,
-				SrcAddr:           test.src,
-				DstAddr:           header.IPv6AllNodesMulticastAddress,
-			})
-
-			stats := s.Stats().ICMP.V6.PacketsReceived
-			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)
-				}
-			}
-		})
-	}
-}
-
-// TestCheckDuplicateAddress checks that calls to CheckDuplicateAddress and DAD
-// performed when adding new addresses do not interfere with each other.
-func TestCheckDuplicateAddress(t *testing.T) {
-	const nicID = 1
-
-	clock := faketime.NewManualClock()
-	dadConfigs := stack.DADConfigurations{
-		DupAddrDetectTransmits: 1,
-		RetransmitTimer:        time.Second,
-	}
-
-	nonces := [...][]byte{
-		{1, 2, 3, 4, 5, 6},
-		{7, 8, 9, 10, 11, 12},
-	}
-
-	var secureRNGBytes []byte
-	for _, n := range nonces {
-		secureRNGBytes = append(secureRNGBytes, n...)
-	}
-	var secureRNG bytes.Reader
-	secureRNG.Reset(secureRNGBytes[:])
-	s := stack.New(stack.Options{
-		SecureRNG: &secureRNG,
-		Clock:     clock,
-		NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocolWithOptions(Options{
-			DADConfigs: dadConfigs,
-		})},
-	})
-	// This test is expected to send at max 2 DAD messages. We allow an extra
-	// packet to be stored to catch unexpected packets.
-	e := channel.New(3, header.IPv6MinimumMTU, linkAddr0)
-	e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	dadPacketsSent := 0
-	snmc := header.SolicitedNodeAddr(lladdr0)
-	remoteLinkAddr := header.EthernetAddressFromMulticastIPv6Address(snmc)
-	checkDADMsg := func() {
-		p, ok := e.ReadContext(context.Background())
-		if !ok {
-			t.Fatalf("expected %d-th DAD message", dadPacketsSent)
-		}
-
-		if p.Proto != header.IPv6ProtocolNumber {
-			t.Errorf("(i=%d) got p.Proto = %d, want = %d", dadPacketsSent, p.Proto, header.IPv6ProtocolNumber)
-		}
-
-		if p.Route.RemoteLinkAddress != remoteLinkAddr {
-			t.Errorf("(i=%d) got p.Route.RemoteLinkAddress = %s, want = %s", dadPacketsSent, p.Route.RemoteLinkAddress, remoteLinkAddr)
-		}
-
-		checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
-			checker.SrcAddr(header.IPv6Any),
-			checker.DstAddr(snmc),
-			checker.TTL(header.NDPHopLimit),
-			checker.NDPNS(
-				checker.NDPNSTargetAddress(lladdr0),
-				checker.NDPNSOptions([]header.NDPOption{header.NDPNonceOption(nonces[dadPacketsSent])}),
-			))
-	}
-	if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
-		t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
-	}
-	checkDADMsg()
-
-	// Start DAD for the address we just added.
-	//
-	// Even though the stack will perform DAD before the added address transitions
-	// from tentative to assigned, this DAD request should be independent of that.
-	ch := make(chan stack.DADResult, 3)
-	dadRequestsMade := 1
-	dadPacketsSent++
-	if res, err := s.CheckDuplicateAddress(nicID, ProtocolNumber, lladdr0, func(r stack.DADResult) {
-		ch <- r
-	}); err != nil {
-		t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, ProtocolNumber, lladdr0, err)
-	} else if res != stack.DADStarting {
-		t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADStarting)
-	}
-	checkDADMsg()
-
-	// Remove the address and make sure our DAD request was not stopped.
-	if err := s.RemoveAddress(nicID, lladdr0); err != nil {
-		t.Fatalf("RemoveAddress(%d, %s): %s", nicID, lladdr0, err)
-	}
-	// Should not restart DAD since we already requested DAD above - the handler
-	// should be called when the original request compeletes so we should not send
-	// an extra DAD message here.
-	dadRequestsMade++
-	if res, err := s.CheckDuplicateAddress(nicID, ProtocolNumber, lladdr0, func(r stack.DADResult) {
-		ch <- r
-	}); err != nil {
-		t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, ProtocolNumber, lladdr0, err)
-	} else if res != stack.DADAlreadyRunning {
-		t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADAlreadyRunning)
-	}
-
-	// Wait for DAD to complete.
-	clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer)
-	for i := 0; i < dadRequestsMade; i++ {
-		if diff := cmp.Diff(&stack.DADSucceeded{}, <-ch); diff != "" {
-			t.Errorf("(i=%d) DAD result mismatch (-want +got):\n%s", i, diff)
-		}
-	}
-	// Should have no more results.
-	select {
-	case r := <-ch:
-		t.Errorf("unexpectedly got an extra DAD result; r = %#v", r)
-	default:
-	}
-
-	// Should have no more packets.
-	if p, ok := e.Read(); ok {
-		t.Errorf("got unexpected packet = %#v", p)
-	}
-}
diff --git a/pkg/tcpip/ports/ports_state_autogen.go b/pkg/tcpip/ports/ports_state_autogen.go
new file mode 100644
index 0000000..2719f6c
--- /dev/null
+++ b/pkg/tcpip/ports/ports_state_autogen.go
@@ -0,0 +1,42 @@
+// automatically generated by stateify.
+
+package ports
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (f *Flags) StateTypeName() string {
+	return "pkg/tcpip/ports.Flags"
+}
+
+func (f *Flags) StateFields() []string {
+	return []string{
+		"MostRecent",
+		"LoadBalanced",
+		"TupleOnly",
+	}
+}
+
+func (f *Flags) beforeSave() {}
+
+// +checklocksignore
+func (f *Flags) StateSave(stateSinkObject state.Sink) {
+	f.beforeSave()
+	stateSinkObject.Save(0, &f.MostRecent)
+	stateSinkObject.Save(1, &f.LoadBalanced)
+	stateSinkObject.Save(2, &f.TupleOnly)
+}
+
+func (f *Flags) afterLoad() {}
+
+// +checklocksignore
+func (f *Flags) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &f.MostRecent)
+	stateSourceObject.Load(1, &f.LoadBalanced)
+	stateSourceObject.Load(2, &f.TupleOnly)
+}
+
+func init() {
+	state.Register((*Flags)(nil))
+}
diff --git a/pkg/tcpip/ports/ports_test.go b/pkg/tcpip/ports/ports_test.go
deleted file mode 100644
index 0f43dc8..0000000
--- a/pkg/tcpip/ports/ports_test.go
+++ /dev/null
@@ -1,492 +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 ports
-
-import (
-	"math/rand"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/tcpip"
-)
-
-const (
-	fakeTransNumber   tcpip.TransportProtocolNumber = 1
-	fakeNetworkNumber tcpip.NetworkProtocolNumber   = 2
-
-	fakeIPAddress  = tcpip.Address("\x08\x08\x08\x08")
-	fakeIPAddress1 = tcpip.Address("\x08\x08\x08\x09")
-)
-
-type portReserveTestAction struct {
-	port    uint16
-	ip      tcpip.Address
-	want    tcpip.Error
-	flags   Flags
-	release bool
-	device  tcpip.NICID
-	dest    tcpip.FullAddress
-}
-
-func TestPortReservation(t *testing.T) {
-	for _, test := range []struct {
-		tname   string
-		actions []portReserveTestAction
-	}{
-		{
-			tname: "bind to ip",
-			actions: []portReserveTestAction{
-				{port: 80, ip: fakeIPAddress, want: nil},
-				{port: 80, ip: fakeIPAddress1, want: nil},
-				/* N.B. Order of tests matters! */
-				{port: 80, ip: anyIPAddress, want: &tcpip.ErrPortInUse{}},
-				{port: 80, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}, flags: Flags{LoadBalanced: true}},
-			},
-		},
-		{
-			tname: "bind to inaddr any",
-			actions: []portReserveTestAction{
-				{port: 22, ip: anyIPAddress, want: nil},
-				{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
-				/* release fakeIPAddress, but anyIPAddress is still inuse */
-				{port: 22, ip: fakeIPAddress, release: true},
-				{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
-				{port: 22, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}, flags: Flags{LoadBalanced: true}},
-				/* Release port 22 from any IP address, then try to reserve fake IP address on 22 */
-				{port: 22, ip: anyIPAddress, want: nil, release: true},
-				{port: 22, ip: fakeIPAddress, want: nil},
-			},
-		}, {
-			tname: "bind to zero port",
-			actions: []portReserveTestAction{
-				{port: 00, ip: fakeIPAddress, want: nil},
-				{port: 00, ip: fakeIPAddress, want: nil},
-				{port: 00, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-			},
-		}, {
-			tname: "bind to ip with reuseport",
-			actions: []portReserveTestAction{
-				{port: 25, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 25, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-
-				{port: 25, ip: fakeIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
-				{port: 25, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
-
-				{port: 25, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-			},
-		}, {
-			tname: "bind to inaddr any with reuseport",
-			actions: []portReserveTestAction{
-				{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-
-				{port: 24, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
-
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, release: true, want: nil},
-
-				{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, release: true},
-				{port: 24, ip: anyIPAddress, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
-
-				{port: 24, ip: anyIPAddress, flags: Flags{LoadBalanced: true}, release: true},
-				{port: 24, ip: anyIPAddress, flags: Flags{}, want: nil},
-			},
-		}, {
-			tname: "bind twice with device fails",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 3, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 3, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind to device",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 1, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 2, want: nil},
-			},
-		}, {
-			tname: "bind to device and then without device",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind without device",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind with device",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 789, want: nil},
-				{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind with reuseport",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
-			},
-		}, {
-			tname: "binding with reuseport and device",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 999, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "mixing reuseport and not reuseport by binding to device",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 456, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 999, want: nil},
-			},
-		}, {
-			tname: "can't bind to 0 after mixing reuseport and not reuseport",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 456, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind and release",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 789, flags: Flags{LoadBalanced: true}, want: nil},
-
-				// Release the bind to device 0 and try again.
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: nil, release: true},
-				{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: nil},
-			},
-		}, {
-			tname: "bind twice with reuseport once",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "release an unreserved device",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{}, want: nil},
-				// The below don't exist.
-				{port: 24, ip: fakeIPAddress, device: 345, flags: Flags{}, want: nil, release: true},
-				{port: 9999, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil, release: true},
-				// Release all.
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil, release: true},
-				{port: 24, ip: fakeIPAddress, device: 456, flags: Flags{}, want: nil, release: true},
-			},
-		}, {
-			tname: "bind with reuseaddr",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 123, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{MostRecent: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, want: &tcpip.ErrPortInUse{}},
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{MostRecent: true}, want: nil},
-			},
-		}, {
-			tname: "bind twice with reuseaddr once",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, device: 123, flags: Flags{}, want: nil},
-				{port: 24, ip: fakeIPAddress, device: 0, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind with reuseaddr and reuseport",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-			},
-		}, {
-			tname: "bind with reuseaddr and reuseport, and then reuseaddr",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind with reuseaddr and reuseport, and then reuseport",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind with reuseaddr and reuseport twice, and then reuseaddr",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
-			},
-		}, {
-			tname: "bind with reuseaddr and reuseport twice, and then reuseport",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-			},
-		}, {
-			tname: "bind with reuseaddr, and then reuseaddr and reuseport",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind with reuseport, and then reuseaddr and reuseport",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true, LoadBalanced: true}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{MostRecent: true}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind tuple with reuseaddr, and then wildcard with reuseaddr",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{}, want: nil},
-			},
-		}, {
-			tname: "bind tuple with reuseaddr, and then wildcard",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
-				{port: 24, ip: fakeIPAddress, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind wildcard with reuseaddr, and then tuple with reuseaddr",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
-			},
-		}, {
-			tname: "bind tuple with reuseaddr, and then wildcard",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind two tuples with reuseaddr",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 25}, want: nil},
-			},
-		}, {
-			tname: "bind two tuples",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: nil},
-				{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 25}, want: nil},
-			},
-		}, {
-			tname: "bind wildcard, and then tuple with reuseaddr",
-			actions: []portReserveTestAction{
-				{port: 24, ip: fakeIPAddress, dest: tcpip.FullAddress{}, want: nil},
-				{port: 24, ip: fakeIPAddress, flags: Flags{TupleOnly: true}, dest: tcpip.FullAddress{Addr: fakeIPAddress, Port: 24}, want: &tcpip.ErrPortInUse{}},
-			},
-		}, {
-			tname: "bind wildcard twice with reuseaddr",
-			actions: []portReserveTestAction{
-				{port: 24, ip: anyIPAddress, flags: Flags{TupleOnly: true}, want: nil},
-				{port: 24, ip: anyIPAddress, flags: Flags{TupleOnly: true}, want: nil},
-			},
-		},
-	} {
-		t.Run(test.tname, func(t *testing.T) {
-			pm := NewPortManager()
-			net := []tcpip.NetworkProtocolNumber{fakeNetworkNumber}
-
-			for _, test := range test.actions {
-				first, _ := pm.PortRange()
-				if test.release {
-					portRes := Reservation{
-						Networks:     net,
-						Transport:    fakeTransNumber,
-						Addr:         test.ip,
-						Port:         test.port,
-						Flags:        test.flags,
-						BindToDevice: test.device,
-						Dest:         test.dest,
-					}
-					pm.ReleasePort(portRes)
-					continue
-				}
-				portRes := Reservation{
-					Networks:     net,
-					Transport:    fakeTransNumber,
-					Addr:         test.ip,
-					Port:         test.port,
-					Flags:        test.flags,
-					BindToDevice: test.device,
-					Dest:         test.dest,
-				}
-				gotPort, err := pm.ReservePort(portRes, nil /* testPort */)
-				if diff := cmp.Diff(test.want, err); diff != "" {
-					t.Fatalf("unexpected error from ReservePort(%+v, _), (-want, +got):\n%s", portRes, diff)
-				}
-				if test.port == 0 && (gotPort == 0 || gotPort < first) {
-					t.Fatalf("ReservePort(%+v, _) = %d, want port number >= %d to be picked", portRes, gotPort, first)
-				}
-			}
-		})
-	}
-}
-
-func TestPickEphemeralPort(t *testing.T) {
-	const (
-		firstEphemeral    = 32000
-		numEphemeralPorts = 1000
-	)
-
-	for _, test := range []struct {
-		name     string
-		f        func(port uint16) (bool, tcpip.Error)
-		wantErr  tcpip.Error
-		wantPort uint16
-	}{
-		{
-			name: "no-port-available",
-			f: func(port uint16) (bool, tcpip.Error) {
-				return false, nil
-			},
-			wantErr: &tcpip.ErrNoPortAvailable{},
-		},
-		{
-			name: "port-tester-error",
-			f: func(port uint16) (bool, tcpip.Error) {
-				return false, &tcpip.ErrBadBuffer{}
-			},
-			wantErr: &tcpip.ErrBadBuffer{},
-		},
-		{
-			name: "only-port-16042-available",
-			f: func(port uint16) (bool, tcpip.Error) {
-				if port == firstEphemeral+42 {
-					return true, nil
-				}
-				return false, nil
-			},
-			wantPort: firstEphemeral + 42,
-		},
-		{
-			name: "only-port-under-16000-available",
-			f: func(port uint16) (bool, tcpip.Error) {
-				if port < firstEphemeral {
-					return true, nil
-				}
-				return false, nil
-			},
-			wantErr: &tcpip.ErrNoPortAvailable{},
-		},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			pm := NewPortManager()
-			if err := pm.SetPortRange(firstEphemeral, firstEphemeral+numEphemeralPorts); err != nil {
-				t.Fatalf("failed to set ephemeral port range: %s", err)
-			}
-			port, err := pm.PickEphemeralPort(test.f)
-			if diff := cmp.Diff(test.wantErr, err); diff != "" {
-				t.Fatalf("unexpected error from PickEphemeralPort(..), (-want, +got):\n%s", diff)
-			}
-			if port != test.wantPort {
-				t.Errorf("got PickEphemeralPort(..) = (%d, nil); want (%d, nil)", port, test.wantPort)
-			}
-		})
-	}
-}
-
-func TestPickEphemeralPortStable(t *testing.T) {
-	const (
-		firstEphemeral    = 32000
-		numEphemeralPorts = 1000
-	)
-
-	for _, test := range []struct {
-		name     string
-		f        func(port uint16) (bool, tcpip.Error)
-		wantErr  tcpip.Error
-		wantPort uint16
-	}{
-		{
-			name: "no-port-available",
-			f: func(port uint16) (bool, tcpip.Error) {
-				return false, nil
-			},
-			wantErr: &tcpip.ErrNoPortAvailable{},
-		},
-		{
-			name: "port-tester-error",
-			f: func(port uint16) (bool, tcpip.Error) {
-				return false, &tcpip.ErrBadBuffer{}
-			},
-			wantErr: &tcpip.ErrBadBuffer{},
-		},
-		{
-			name: "only-port-16042-available",
-			f: func(port uint16) (bool, tcpip.Error) {
-				if port == firstEphemeral+42 {
-					return true, nil
-				}
-				return false, nil
-			},
-			wantPort: firstEphemeral + 42,
-		},
-		{
-			name: "only-port-under-16000-available",
-			f: func(port uint16) (bool, tcpip.Error) {
-				if port < firstEphemeral {
-					return true, nil
-				}
-				return false, nil
-			},
-			wantErr: &tcpip.ErrNoPortAvailable{},
-		},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			pm := NewPortManager()
-			if err := pm.SetPortRange(firstEphemeral, firstEphemeral+numEphemeralPorts); err != nil {
-				t.Fatalf("failed to set ephemeral port range: %s", err)
-			}
-			portOffset := uint16(rand.Int31n(int32(numEphemeralPorts)))
-			port, err := pm.PickEphemeralPortStable(portOffset, test.f)
-			if diff := cmp.Diff(test.wantErr, err); diff != "" {
-				t.Fatalf("unexpected error from PickEphemeralPort(..), (-want, +got):\n%s", diff)
-			}
-			if port != test.wantPort {
-				t.Errorf("got PickEphemeralPort(..) = (%d, nil); want (%d, nil)", port, test.wantPort)
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/seqnum/seqnum_state_autogen.go b/pkg/tcpip/seqnum/seqnum_state_autogen.go
new file mode 100644
index 0000000..23e7981
--- /dev/null
+++ b/pkg/tcpip/seqnum/seqnum_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package seqnum
diff --git a/pkg/tcpip/sock_err_list.go b/pkg/tcpip/sock_err_list.go
new file mode 100644
index 0000000..0be1993
--- /dev/null
+++ b/pkg/tcpip/sock_err_list.go
@@ -0,0 +1,221 @@
+package tcpip
+
+// 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 sockErrorElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (sockErrorElementMapper) linkerFor(elem *SockError) *SockError { 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 sockErrorList struct {
+	head *SockError
+	tail *SockError
+}
+
+// Reset resets list l to the empty state.
+func (l *sockErrorList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *sockErrorList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *sockErrorList) Front() *SockError {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *sockErrorList) Back() *SockError {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *sockErrorList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (sockErrorElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *sockErrorList) PushFront(e *SockError) {
+	linker := sockErrorElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		sockErrorElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *sockErrorList) PushBack(e *SockError) {
+	linker := sockErrorElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		sockErrorElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *sockErrorList) PushBackList(m *sockErrorList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		sockErrorElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		sockErrorElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *sockErrorList) InsertAfter(b, e *SockError) {
+	bLinker := sockErrorElementMapper{}.linkerFor(b)
+	eLinker := sockErrorElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		sockErrorElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *sockErrorList) InsertBefore(a, e *SockError) {
+	aLinker := sockErrorElementMapper{}.linkerFor(a)
+	eLinker := sockErrorElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		sockErrorElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *sockErrorList) Remove(e *SockError) {
+	linker := sockErrorElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		sockErrorElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		sockErrorElementMapper{}.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 sockErrorEntry struct {
+	next *SockError
+	prev *SockError
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *sockErrorEntry) Next() *SockError {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *sockErrorEntry) Prev() *SockError {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *sockErrorEntry) SetNext(elem *SockError) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *sockErrorEntry) SetPrev(elem *SockError) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/stack/addressable_endpoint_state_test.go b/pkg/tcpip/stack/addressable_endpoint_state_test.go
deleted file mode 100644
index 140f146..0000000
--- a/pkg/tcpip/stack/addressable_endpoint_state_test.go
+++ /dev/null
@@ -1,61 +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 stack_test
-
-import (
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-// TestAddressableEndpointStateCleanup tests that cleaning up an addressable
-// endpoint state removes permanent addresses and leaves groups.
-func TestAddressableEndpointStateCleanup(t *testing.T) {
-	var ep fakeNetworkEndpoint
-	if err := ep.Enable(); err != nil {
-		t.Fatalf("ep.Enable(): %s", err)
-	}
-
-	var s stack.AddressableEndpointState
-	s.Init(&ep)
-
-	addr := tcpip.AddressWithPrefix{
-		Address:   "\x01",
-		PrefixLen: 8,
-	}
-
-	{
-		ep, err := s.AddAndAcquirePermanentAddress(addr, stack.NeverPrimaryEndpoint, stack.AddressConfigStatic, false /* deprecated */)
-		if err != nil {
-			t.Fatalf("s.AddAndAcquirePermanentAddress(%s, %d, %d, false): %s", addr, stack.NeverPrimaryEndpoint, stack.AddressConfigStatic, err)
-		}
-		// We don't need the address endpoint.
-		ep.DecRef()
-	}
-	{
-		ep := s.AcquireAssignedAddress(addr.Address, false /* allowTemp */, stack.NeverPrimaryEndpoint)
-		if ep == nil {
-			t.Fatalf("got s.AcquireAssignedAddress(%s, false, NeverPrimaryEndpoint) = nil, want = non-nil", addr.Address)
-		}
-		ep.DecRef()
-	}
-
-	s.Cleanup()
-	if ep := s.AcquireAssignedAddress(addr.Address, false /* allowTemp */, stack.NeverPrimaryEndpoint); ep != nil {
-		ep.DecRef()
-		t.Fatalf("got s.AcquireAssignedAddress(%s, false, NeverPrimaryEndpoint) = %s, want = nil", addr.Address, ep.AddressWithPrefix())
-	}
-}
diff --git a/pkg/tcpip/stack/forwarding_test.go b/pkg/tcpip/stack/forwarding_test.go
deleted file mode 100644
index 16ee75b..0000000
--- a/pkg/tcpip/stack/forwarding_test.go
+++ /dev/null
@@ -1,785 +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 stack
-
-import (
-	"encoding/binary"
-	"math"
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/sync"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-const (
-	fwdTestNetNumber           tcpip.NetworkProtocolNumber = math.MaxUint32
-	fwdTestNetHeaderLen                                    = 12
-	fwdTestNetDefaultPrefixLen                             = 8
-
-	// fwdTestNetDefaultMTU is the MTU, in bytes, used throughout the tests,
-	// except where another value is explicitly used. It is chosen to match
-	// the MTU of loopback interfaces on linux systems.
-	fwdTestNetDefaultMTU = 65536
-
-	dstAddrOffset        = 0
-	srcAddrOffset        = 1
-	protocolNumberOffset = 2
-)
-
-var _ LinkAddressResolver = (*fwdTestNetworkEndpoint)(nil)
-var _ NetworkEndpoint = (*fwdTestNetworkEndpoint)(nil)
-
-// fwdTestNetworkEndpoint is a network-layer protocol endpoint.
-// Headers of this protocol are fwdTestNetHeaderLen bytes, but we currently only
-// use the first three: destination address, source address, and transport
-// protocol. They're all one byte fields to simplify parsing.
-type fwdTestNetworkEndpoint struct {
-	AddressableEndpointState
-
-	nic        NetworkInterface
-	proto      *fwdTestNetworkProtocol
-	dispatcher TransportDispatcher
-}
-
-func (*fwdTestNetworkEndpoint) Enable() tcpip.Error {
-	return nil
-}
-
-func (*fwdTestNetworkEndpoint) Enabled() bool {
-	return true
-}
-
-func (*fwdTestNetworkEndpoint) Disable() {}
-
-func (f *fwdTestNetworkEndpoint) MTU() uint32 {
-	return f.nic.MTU() - uint32(f.MaxHeaderLength())
-}
-
-func (*fwdTestNetworkEndpoint) DefaultTTL() uint8 {
-	return 123
-}
-
-func (f *fwdTestNetworkEndpoint) HandlePacket(pkt *PacketBuffer) {
-	if _, _, ok := f.proto.Parse(pkt); !ok {
-		return
-	}
-
-	netHdr := pkt.NetworkHeader().View()
-	_, dst := f.proto.ParseAddresses(netHdr)
-
-	addressEndpoint := f.AcquireAssignedAddress(dst, f.nic.Promiscuous(), CanBePrimaryEndpoint)
-	if addressEndpoint != nil {
-		addressEndpoint.DecRef()
-		// Dispatch the packet to the transport protocol.
-		f.dispatcher.DeliverTransportPacket(tcpip.TransportProtocolNumber(netHdr[protocolNumberOffset]), pkt)
-		return
-	}
-
-	r, err := f.proto.stack.FindRoute(0, "", dst, fwdTestNetNumber, false /* multicastLoop */)
-	if err != nil {
-		return
-	}
-	defer r.Release()
-
-	vv := buffer.NewVectorisedView(pkt.Size(), pkt.Views())
-	pkt = NewPacketBuffer(PacketBufferOptions{
-		ReserveHeaderBytes: int(r.MaxHeaderLength()),
-		Data:               vv.ToView().ToVectorisedView(),
-	})
-	// TODO(b/143425874) Decrease the TTL field in forwarded packets.
-	_ = r.WriteHeaderIncludedPacket(pkt)
-}
-
-func (f *fwdTestNetworkEndpoint) MaxHeaderLength() uint16 {
-	return f.nic.MaxHeaderLength() + fwdTestNetHeaderLen
-}
-
-func (*fwdTestNetworkEndpoint) PseudoHeaderChecksum(protocol tcpip.TransportProtocolNumber, dstAddr tcpip.Address) uint16 {
-	return 0
-}
-
-func (f *fwdTestNetworkEndpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber {
-	return f.proto.Number()
-}
-
-func (f *fwdTestNetworkEndpoint) WritePacket(r *Route, gso *GSO, params NetworkHeaderParams, pkt *PacketBuffer) tcpip.Error {
-	// Add the protocol's header to the packet and send it to the link
-	// endpoint.
-	b := pkt.NetworkHeader().Push(fwdTestNetHeaderLen)
-	b[dstAddrOffset] = r.RemoteAddress()[0]
-	b[srcAddrOffset] = r.LocalAddress()[0]
-	b[protocolNumberOffset] = byte(params.Protocol)
-
-	return f.nic.WritePacket(r, gso, fwdTestNetNumber, pkt)
-}
-
-// WritePackets implements LinkEndpoint.WritePackets.
-func (*fwdTestNetworkEndpoint) WritePackets(r *Route, gso *GSO, pkts PacketBufferList, params NetworkHeaderParams) (int, tcpip.Error) {
-	panic("not implemented")
-}
-
-func (f *fwdTestNetworkEndpoint) WriteHeaderIncludedPacket(r *Route, pkt *PacketBuffer) tcpip.Error {
-	// The network header should not already be populated.
-	if _, ok := pkt.NetworkHeader().Consume(fwdTestNetHeaderLen); !ok {
-		return &tcpip.ErrMalformedHeader{}
-	}
-
-	return f.nic.WritePacket(r, nil /* gso */, fwdTestNetNumber, pkt)
-}
-
-func (f *fwdTestNetworkEndpoint) Close() {
-	f.AddressableEndpointState.Cleanup()
-}
-
-// Stats implements stack.NetworkEndpoint.
-func (*fwdTestNetworkEndpoint) Stats() NetworkEndpointStats {
-	return &fwdTestNetworkEndpointStats{}
-}
-
-var _ NetworkEndpointStats = (*fwdTestNetworkEndpointStats)(nil)
-
-type fwdTestNetworkEndpointStats struct{}
-
-// IsNetworkEndpointStats implements stack.NetworkEndpointStats.
-func (*fwdTestNetworkEndpointStats) IsNetworkEndpointStats() {}
-
-var _ NetworkProtocol = (*fwdTestNetworkProtocol)(nil)
-
-// fwdTestNetworkProtocol is a network-layer protocol that implements Address
-// resolution.
-type fwdTestNetworkProtocol struct {
-	stack *Stack
-
-	neigh                  *neighborCache
-	addrResolveDelay       time.Duration
-	onLinkAddressResolved  func(*neighborCache, tcpip.Address, tcpip.LinkAddress)
-	onResolveStaticAddress func(tcpip.Address) (tcpip.LinkAddress, bool)
-
-	mu struct {
-		sync.RWMutex
-		forwarding bool
-	}
-}
-
-func (*fwdTestNetworkProtocol) Number() tcpip.NetworkProtocolNumber {
-	return fwdTestNetNumber
-}
-
-func (*fwdTestNetworkProtocol) MinimumPacketSize() int {
-	return fwdTestNetHeaderLen
-}
-
-func (*fwdTestNetworkProtocol) DefaultPrefixLen() int {
-	return fwdTestNetDefaultPrefixLen
-}
-
-func (*fwdTestNetworkProtocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) {
-	return tcpip.Address(v[srcAddrOffset : srcAddrOffset+1]), tcpip.Address(v[dstAddrOffset : dstAddrOffset+1])
-}
-
-func (*fwdTestNetworkProtocol) Parse(pkt *PacketBuffer) (tcpip.TransportProtocolNumber, bool, bool) {
-	netHeader, ok := pkt.NetworkHeader().Consume(fwdTestNetHeaderLen)
-	if !ok {
-		return 0, false, false
-	}
-	return tcpip.TransportProtocolNumber(netHeader[protocolNumberOffset]), true, true
-}
-
-func (f *fwdTestNetworkProtocol) NewEndpoint(nic NetworkInterface, dispatcher TransportDispatcher) NetworkEndpoint {
-	e := &fwdTestNetworkEndpoint{
-		nic:        nic,
-		proto:      f,
-		dispatcher: dispatcher,
-	}
-	e.AddressableEndpointState.Init(e)
-	return e
-}
-
-func (*fwdTestNetworkProtocol) SetOption(tcpip.SettableNetworkProtocolOption) tcpip.Error {
-	return &tcpip.ErrUnknownProtocolOption{}
-}
-
-func (*fwdTestNetworkProtocol) Option(tcpip.GettableNetworkProtocolOption) tcpip.Error {
-	return &tcpip.ErrUnknownProtocolOption{}
-}
-
-func (*fwdTestNetworkProtocol) Close() {}
-
-func (*fwdTestNetworkProtocol) Wait() {}
-
-func (f *fwdTestNetworkEndpoint) LinkAddressRequest(addr, _ tcpip.Address, remoteLinkAddr tcpip.LinkAddress) tcpip.Error {
-	if fn := f.proto.onLinkAddressResolved; fn != nil {
-		time.AfterFunc(f.proto.addrResolveDelay, func() {
-			fn(f.proto.neigh, addr, remoteLinkAddr)
-		})
-	}
-	return nil
-}
-
-func (f *fwdTestNetworkEndpoint) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) {
-	if fn := f.proto.onResolveStaticAddress; fn != nil {
-		return fn(addr)
-	}
-	return "", false
-}
-
-func (*fwdTestNetworkEndpoint) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
-	return fwdTestNetNumber
-}
-
-// Forwarding implements stack.ForwardingNetworkProtocol.
-func (f *fwdTestNetworkProtocol) Forwarding() bool {
-	f.mu.RLock()
-	defer f.mu.RUnlock()
-	return f.mu.forwarding
-
-}
-
-// SetForwarding implements stack.ForwardingNetworkProtocol.
-func (f *fwdTestNetworkProtocol) SetForwarding(v bool) {
-	f.mu.Lock()
-	defer f.mu.Unlock()
-	f.mu.forwarding = v
-}
-
-// fwdTestPacketInfo holds all the information about an outbound packet.
-type fwdTestPacketInfo struct {
-	RemoteLinkAddress tcpip.LinkAddress
-	LocalLinkAddress  tcpip.LinkAddress
-	Pkt               *PacketBuffer
-}
-
-type fwdTestLinkEndpoint struct {
-	dispatcher NetworkDispatcher
-	mtu        uint32
-	linkAddr   tcpip.LinkAddress
-
-	// C is where outbound packets are queued.
-	C chan fwdTestPacketInfo
-}
-
-// InjectInbound injects an inbound packet.
-func (e *fwdTestLinkEndpoint) InjectInbound(protocol tcpip.NetworkProtocolNumber, pkt *PacketBuffer) {
-	e.InjectLinkAddr(protocol, "", pkt)
-}
-
-// InjectLinkAddr injects an inbound packet with a remote link address.
-func (e *fwdTestLinkEndpoint) InjectLinkAddr(protocol tcpip.NetworkProtocolNumber, remote tcpip.LinkAddress, pkt *PacketBuffer) {
-	e.dispatcher.DeliverNetworkPacket(remote, "" /* local */, protocol, pkt)
-}
-
-// Attach saves the stack network-layer dispatcher for use later when packets
-// are injected.
-func (e *fwdTestLinkEndpoint) Attach(dispatcher NetworkDispatcher) {
-	e.dispatcher = dispatcher
-}
-
-// IsAttached implements stack.LinkEndpoint.IsAttached.
-func (e *fwdTestLinkEndpoint) IsAttached() bool {
-	return e.dispatcher != nil
-}
-
-// MTU implements stack.LinkEndpoint.MTU. It returns the value initialized
-// during construction.
-func (e *fwdTestLinkEndpoint) MTU() uint32 {
-	return e.mtu
-}
-
-// Capabilities implements stack.LinkEndpoint.Capabilities.
-func (e fwdTestLinkEndpoint) Capabilities() LinkEndpointCapabilities {
-	caps := LinkEndpointCapabilities(0)
-	return caps | CapabilityResolutionRequired
-}
-
-// GSOMaxSize returns the maximum GSO packet size.
-func (*fwdTestLinkEndpoint) GSOMaxSize() uint32 {
-	return 1 << 15
-}
-
-// MaxHeaderLength returns the maximum size of the link layer header. Given it
-// doesn't have a header, it just returns 0.
-func (*fwdTestLinkEndpoint) MaxHeaderLength() uint16 {
-	return 0
-}
-
-// LinkAddress returns the link address of this endpoint.
-func (e *fwdTestLinkEndpoint) LinkAddress() tcpip.LinkAddress {
-	return e.linkAddr
-}
-
-func (e fwdTestLinkEndpoint) WritePacket(r RouteInfo, gso *GSO, protocol tcpip.NetworkProtocolNumber, pkt *PacketBuffer) tcpip.Error {
-	p := fwdTestPacketInfo{
-		RemoteLinkAddress: r.RemoteLinkAddress,
-		LocalLinkAddress:  r.LocalLinkAddress,
-		Pkt:               pkt,
-	}
-
-	select {
-	case e.C <- p:
-	default:
-	}
-
-	return nil
-}
-
-// WritePackets stores outbound packets into the channel.
-func (e *fwdTestLinkEndpoint) WritePackets(r RouteInfo, gso *GSO, pkts PacketBufferList, protocol tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
-	n := 0
-	for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
-		e.WritePacket(r, gso, protocol, pkt)
-		n++
-	}
-
-	return n, nil
-}
-
-// Wait implements stack.LinkEndpoint.Wait.
-func (*fwdTestLinkEndpoint) Wait() {}
-
-// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
-func (*fwdTestLinkEndpoint) ARPHardwareType() header.ARPHardwareType {
-	panic("not implemented")
-}
-
-// AddHeader implements stack.LinkEndpoint.AddHeader.
-func (e *fwdTestLinkEndpoint) AddHeader(local, remote tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *PacketBuffer) {
-	panic("not implemented")
-}
-
-func fwdTestNetFactory(t *testing.T, proto *fwdTestNetworkProtocol) (ep1, ep2 *fwdTestLinkEndpoint) {
-	// Create a stack with the network protocol and two NICs.
-	s := New(Options{
-		NetworkProtocols: []NetworkProtocolFactory{func(s *Stack) NetworkProtocol {
-			proto.stack = s
-			return proto
-		}},
-	})
-
-	// Enable forwarding.
-	s.SetForwarding(proto.Number(), true)
-
-	// NIC 1 has the link address "a", and added the network address 1.
-	ep1 = &fwdTestLinkEndpoint{
-		C:        make(chan fwdTestPacketInfo, 300),
-		mtu:      fwdTestNetDefaultMTU,
-		linkAddr: "a",
-	}
-	if err := s.CreateNIC(1, ep1); err != nil {
-		t.Fatal("CreateNIC #1 failed:", err)
-	}
-	if err := s.AddAddress(1, fwdTestNetNumber, "\x01"); err != nil {
-		t.Fatal("AddAddress #1 failed:", err)
-	}
-
-	// NIC 2 has the link address "b", and added the network address 2.
-	ep2 = &fwdTestLinkEndpoint{
-		C:        make(chan fwdTestPacketInfo, 300),
-		mtu:      fwdTestNetDefaultMTU,
-		linkAddr: "b",
-	}
-	if err := s.CreateNIC(2, ep2); err != nil {
-		t.Fatal("CreateNIC #2 failed:", err)
-	}
-	if err := s.AddAddress(2, fwdTestNetNumber, "\x02"); err != nil {
-		t.Fatal("AddAddress #2 failed:", err)
-	}
-
-	nic, ok := s.nics[2]
-	if !ok {
-		t.Fatal("NIC 2 does not exist")
-	}
-
-	if l, ok := nic.linkAddrResolvers[fwdTestNetNumber]; ok {
-		proto.neigh = &l.neigh
-	}
-
-	// Route all packets to NIC 2.
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, NIC: 2}})
-	}
-
-	return ep1, ep2
-}
-
-func TestForwardingWithStaticResolver(t *testing.T) {
-	// Create a network protocol with a static resolver.
-	proto := &fwdTestNetworkProtocol{
-		onResolveStaticAddress:
-		// The network address 3 is resolved to the link address "c".
-		func(addr tcpip.Address) (tcpip.LinkAddress, bool) {
-			if addr == "\x03" {
-				return "c", true
-			}
-			return "", false
-		},
-	}
-
-	ep1, ep2 := fwdTestNetFactory(t, proto)
-
-	// Inject an inbound packet to address 3 on NIC 1, and see if it is
-	// forwarded to NIC 2.
-	buf := buffer.NewView(30)
-	buf[dstAddrOffset] = 3
-	ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	var p fwdTestPacketInfo
-
-	select {
-	case p = <-ep2.C:
-	default:
-		t.Fatal("packet not forwarded")
-	}
-
-	// Test that the static address resolution happened correctly.
-	if p.RemoteLinkAddress != "c" {
-		t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
-	}
-	if p.LocalLinkAddress != "b" {
-		t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
-	}
-}
-
-func TestForwardingWithFakeResolver(t *testing.T) {
-	proto := fwdTestNetworkProtocol{
-		addrResolveDelay: 500 * time.Millisecond,
-		onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
-			t.Helper()
-			if len(linkAddr) != 0 {
-				t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
-			}
-			// Any address will be resolved to the link address "c".
-			neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
-				Solicited: true,
-				Override:  false,
-				IsRouter:  false,
-			})
-		},
-	}
-	ep1, ep2 := fwdTestNetFactory(t, &proto)
-
-	// Inject an inbound packet to address 3 on NIC 1, and see if it is
-	// forwarded to NIC 2.
-	buf := buffer.NewView(30)
-	buf[dstAddrOffset] = 3
-	ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	var p fwdTestPacketInfo
-
-	select {
-	case p = <-ep2.C:
-	case <-time.After(time.Second):
-		t.Fatal("packet not forwarded")
-	}
-
-	// Test that the address resolution happened correctly.
-	if p.RemoteLinkAddress != "c" {
-		t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
-	}
-	if p.LocalLinkAddress != "b" {
-		t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
-	}
-}
-
-func TestForwardingWithNoResolver(t *testing.T) {
-	// Create a network protocol without a resolver.
-	proto := &fwdTestNetworkProtocol{}
-
-	// Whether or not we use the neighbor cache here does not matter since
-	// neither linkAddrCache nor neighborCache will be used.
-	ep1, ep2 := fwdTestNetFactory(t, proto)
-
-	// inject an inbound packet to address 3 on NIC 1, and see if it is
-	// forwarded to NIC 2.
-	buf := buffer.NewView(30)
-	buf[dstAddrOffset] = 3
-	ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	select {
-	case <-ep2.C:
-		t.Fatal("Packet should not be forwarded")
-	case <-time.After(time.Second):
-	}
-}
-
-func TestForwardingResolutionFailsForQueuedPackets(t *testing.T) {
-	proto := &fwdTestNetworkProtocol{
-		addrResolveDelay: 50 * time.Millisecond,
-		onLinkAddressResolved: func(*neighborCache, tcpip.Address, tcpip.LinkAddress) {
-			// Don't resolve the link address.
-		},
-	}
-
-	ep1, ep2 := fwdTestNetFactory(t, proto)
-
-	const numPackets int = 5
-	// These packets will all be enqueued in the packet queue to wait for link
-	// address resolution.
-	for i := 0; i < numPackets; i++ {
-		buf := buffer.NewView(30)
-		buf[dstAddrOffset] = 3
-		ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-			Data: buf.ToVectorisedView(),
-		}))
-	}
-
-	// All packets should fail resolution.
-	// TODO(gvisor.dev/issue/5141): Use a fake clock.
-	for i := 0; i < numPackets; i++ {
-		select {
-		case got := <-ep2.C:
-			t.Fatalf("got %#v; packets should have failed resolution and not been forwarded", got)
-		case <-time.After(100 * time.Millisecond):
-		}
-	}
-}
-
-func TestForwardingWithFakeResolverPartialTimeout(t *testing.T) {
-	proto := fwdTestNetworkProtocol{
-		addrResolveDelay: 500 * time.Millisecond,
-		onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
-			t.Helper()
-			if len(linkAddr) != 0 {
-				t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
-			}
-			// Only packets to address 3 will be resolved to the
-			// link address "c".
-			if addr == "\x03" {
-				neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
-					Solicited: true,
-					Override:  false,
-					IsRouter:  false,
-				})
-			}
-		},
-	}
-	ep1, ep2 := fwdTestNetFactory(t, &proto)
-
-	// Inject an inbound packet to address 4 on NIC 1. This packet should
-	// not be forwarded.
-	buf := buffer.NewView(30)
-	buf[dstAddrOffset] = 4
-	ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	// Inject an inbound packet to address 3 on NIC 1, and see if it is
-	// forwarded to NIC 2.
-	buf = buffer.NewView(30)
-	buf[dstAddrOffset] = 3
-	ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	var p fwdTestPacketInfo
-
-	select {
-	case p = <-ep2.C:
-	case <-time.After(time.Second):
-		t.Fatal("packet not forwarded")
-	}
-
-	if nh := PayloadSince(p.Pkt.NetworkHeader()); nh[dstAddrOffset] != 3 {
-		t.Fatalf("got p.Pkt.NetworkHeader[dstAddrOffset] = %d, want = 3", nh[dstAddrOffset])
-	}
-
-	// Test that the address resolution happened correctly.
-	if p.RemoteLinkAddress != "c" {
-		t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
-	}
-	if p.LocalLinkAddress != "b" {
-		t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
-	}
-}
-
-func TestForwardingWithFakeResolverTwoPackets(t *testing.T) {
-	proto := fwdTestNetworkProtocol{
-		addrResolveDelay: 500 * time.Millisecond,
-		onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
-			t.Helper()
-			if len(linkAddr) != 0 {
-				t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
-			}
-			// Any packets will be resolved to the link address "c".
-			neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
-				Solicited: true,
-				Override:  false,
-				IsRouter:  false,
-			})
-		},
-	}
-	ep1, ep2 := fwdTestNetFactory(t, &proto)
-
-	// Inject two inbound packets to address 3 on NIC 1.
-	for i := 0; i < 2; i++ {
-		buf := buffer.NewView(30)
-		buf[dstAddrOffset] = 3
-		ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-			Data: buf.ToVectorisedView(),
-		}))
-	}
-
-	for i := 0; i < 2; i++ {
-		var p fwdTestPacketInfo
-
-		select {
-		case p = <-ep2.C:
-		case <-time.After(time.Second):
-			t.Fatal("packet not forwarded")
-		}
-
-		if nh := PayloadSince(p.Pkt.NetworkHeader()); nh[dstAddrOffset] != 3 {
-			t.Fatalf("got p.Pkt.NetworkHeader[dstAddrOffset] = %d, want = 3", nh[dstAddrOffset])
-		}
-
-		// Test that the address resolution happened correctly.
-		if p.RemoteLinkAddress != "c" {
-			t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
-		}
-		if p.LocalLinkAddress != "b" {
-			t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
-		}
-	}
-}
-
-func TestForwardingWithFakeResolverManyPackets(t *testing.T) {
-	proto := fwdTestNetworkProtocol{
-		addrResolveDelay: 500 * time.Millisecond,
-		onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
-			t.Helper()
-			if len(linkAddr) != 0 {
-				t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
-			}
-			// Any packets will be resolved to the link address "c".
-			neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
-				Solicited: true,
-				Override:  false,
-				IsRouter:  false,
-			})
-		},
-	}
-	ep1, ep2 := fwdTestNetFactory(t, &proto)
-
-	for i := 0; i < maxPendingPacketsPerResolution+5; i++ {
-		// Inject inbound 'maxPendingPacketsPerResolution + 5' packets on NIC 1.
-		buf := buffer.NewView(30)
-		buf[dstAddrOffset] = 3
-		// Set the packet sequence number.
-		binary.BigEndian.PutUint16(buf[fwdTestNetHeaderLen:], uint16(i))
-		ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-			Data: buf.ToVectorisedView(),
-		}))
-	}
-
-	for i := 0; i < maxPendingPacketsPerResolution; i++ {
-		var p fwdTestPacketInfo
-
-		select {
-		case p = <-ep2.C:
-		case <-time.After(time.Second):
-			t.Fatal("packet not forwarded")
-		}
-
-		b := PayloadSince(p.Pkt.NetworkHeader())
-		if b[dstAddrOffset] != 3 {
-			t.Fatalf("got b[dstAddrOffset] = %d, want = 3", b[dstAddrOffset])
-		}
-		if len(b) < fwdTestNetHeaderLen+2 {
-			t.Fatalf("packet is too short to hold a sequence number: len(b) = %d", b)
-		}
-		seqNumBuf := b[fwdTestNetHeaderLen:]
-
-		// The first 5 packets should not be forwarded so the sequence number should
-		// start with 5.
-		want := uint16(i + 5)
-		if n := binary.BigEndian.Uint16(seqNumBuf); n != want {
-			t.Fatalf("got the packet #%d, want = #%d", n, want)
-		}
-
-		// Test that the address resolution happened correctly.
-		if p.RemoteLinkAddress != "c" {
-			t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
-		}
-		if p.LocalLinkAddress != "b" {
-			t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
-		}
-	}
-}
-
-func TestForwardingWithFakeResolverManyResolutions(t *testing.T) {
-	proto := fwdTestNetworkProtocol{
-		addrResolveDelay: 500 * time.Millisecond,
-		onLinkAddressResolved: func(neigh *neighborCache, addr tcpip.Address, linkAddr tcpip.LinkAddress) {
-			t.Helper()
-			if len(linkAddr) != 0 {
-				t.Fatalf("got linkAddr=%q, want unspecified", linkAddr)
-			}
-			// Any packets will be resolved to the link address "c".
-			neigh.handleConfirmation(addr, "c", ReachabilityConfirmationFlags{
-				Solicited: true,
-				Override:  false,
-				IsRouter:  false,
-			})
-		},
-	}
-	ep1, ep2 := fwdTestNetFactory(t, &proto)
-
-	for i := 0; i < maxPendingResolutions+5; i++ {
-		// Inject inbound 'maxPendingResolutions + 5' packets on NIC 1.
-		// Each packet has a different destination address (3 to
-		// maxPendingResolutions + 7).
-		buf := buffer.NewView(30)
-		buf[dstAddrOffset] = byte(3 + i)
-		ep1.InjectInbound(fwdTestNetNumber, NewPacketBuffer(PacketBufferOptions{
-			Data: buf.ToVectorisedView(),
-		}))
-	}
-
-	for i := 0; i < maxPendingResolutions; i++ {
-		var p fwdTestPacketInfo
-
-		select {
-		case p = <-ep2.C:
-		case <-time.After(time.Second):
-			t.Fatal("packet not forwarded")
-		}
-
-		// The first 5 packets (address 3 to 7) should not be forwarded
-		// because their address resolutions are interrupted.
-		if nh := PayloadSince(p.Pkt.NetworkHeader()); nh[dstAddrOffset] < 8 {
-			t.Fatalf("got p.Pkt.NetworkHeader[dstAddrOffset] = %d, want p.Pkt.NetworkHeader[dstAddrOffset] >= 8", nh[dstAddrOffset])
-		}
-
-		// Test that the address resolution happened correctly.
-		if p.RemoteLinkAddress != "c" {
-			t.Fatalf("got p.RemoteLinkAddress = %s, want = c", p.RemoteLinkAddress)
-		}
-		if p.LocalLinkAddress != "b" {
-			t.Fatalf("got p.LocalLinkAddress = %s, want = b", p.LocalLinkAddress)
-		}
-	}
-}
diff --git a/pkg/tcpip/stack/ndp_test.go b/pkg/tcpip/stack/ndp_test.go
deleted file mode 100644
index 0725e02..0000000
--- a/pkg/tcpip/stack/ndp_test.go
+++ /dev/null
@@ -1,5430 +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 stack_test
-
-import (
-	"bytes"
-	"context"
-	"encoding/binary"
-	"fmt"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/rand"
-	"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/loopback"
-	"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/udp"
-	"gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
-	addr1     = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-	addr2     = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-	addr3     = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03")
-	linkAddr1 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
-	linkAddr2 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x07")
-	linkAddr3 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x08")
-	linkAddr4 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x09")
-
-	defaultPrefixLen = 128
-
-	// Extra time to use when waiting for an async event to occur.
-	defaultAsyncPositiveEventTimeout = 10 * time.Second
-
-	// Extra time to use when waiting for an async event to not occur.
-	//
-	// Since a negative check is used to make sure an event did not happen, it is
-	// okay to use a smaller timeout compared to the positive case since execution
-	// stall in regards to the monotonic clock will not affect the expected
-	// outcome.
-	defaultAsyncNegativeEventTimeout = time.Second
-)
-
-var (
-	llAddr1 = header.LinkLocalAddr(linkAddr1)
-	llAddr2 = header.LinkLocalAddr(linkAddr2)
-	llAddr3 = header.LinkLocalAddr(linkAddr3)
-	llAddr4 = header.LinkLocalAddr(linkAddr4)
-	dstAddr = tcpip.FullAddress{
-		Addr: "\x0a\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
-		Port: 25,
-	}
-)
-
-func addrForSubnet(subnet tcpip.Subnet, linkAddr tcpip.LinkAddress) tcpip.AddressWithPrefix {
-	if !header.IsValidUnicastEthernetAddress(linkAddr) {
-		return tcpip.AddressWithPrefix{}
-	}
-
-	addrBytes := []byte(subnet.ID())
-	header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, addrBytes[header.IIDOffsetInIPv6Address:])
-	return tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(addrBytes),
-		PrefixLen: 64,
-	}
-}
-
-// prefixSubnetAddr returns a prefix (Address + Length), the prefix's equivalent
-// tcpip.Subnet, and an address where the lower half of the address is composed
-// of the EUI-64 of linkAddr if it is a valid unicast ethernet address.
-func prefixSubnetAddr(offset uint8, linkAddr tcpip.LinkAddress) (tcpip.AddressWithPrefix, tcpip.Subnet, tcpip.AddressWithPrefix) {
-	prefixBytes := []byte{1, 2, 3, 4, 5, 6, 7, 8 + offset, 0, 0, 0, 0, 0, 0, 0, 0}
-	prefix := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(prefixBytes),
-		PrefixLen: 64,
-	}
-
-	subnet := prefix.Subnet()
-
-	return prefix, subnet, addrForSubnet(subnet, linkAddr)
-}
-
-// ndpDADEvent is a set of parameters that was passed to
-// ndpDispatcher.OnDuplicateAddressDetectionResult.
-type ndpDADEvent struct {
-	nicID tcpip.NICID
-	addr  tcpip.Address
-	res   stack.DADResult
-}
-
-type ndpRouterEvent struct {
-	nicID tcpip.NICID
-	addr  tcpip.Address
-	// true if router was discovered, false if invalidated.
-	discovered bool
-}
-
-type ndpPrefixEvent struct {
-	nicID  tcpip.NICID
-	prefix tcpip.Subnet
-	// true if prefix was discovered, false if invalidated.
-	discovered bool
-}
-
-type ndpAutoGenAddrEventType int
-
-const (
-	newAddr ndpAutoGenAddrEventType = iota
-	deprecatedAddr
-	invalidatedAddr
-)
-
-type ndpAutoGenAddrEvent struct {
-	nicID     tcpip.NICID
-	addr      tcpip.AddressWithPrefix
-	eventType ndpAutoGenAddrEventType
-}
-
-type ndpRDNSS struct {
-	addrs    []tcpip.Address
-	lifetime time.Duration
-}
-
-type ndpRDNSSEvent struct {
-	nicID tcpip.NICID
-	rdnss ndpRDNSS
-}
-
-type ndpDNSSLEvent struct {
-	nicID       tcpip.NICID
-	domainNames []string
-	lifetime    time.Duration
-}
-
-type ndpDHCPv6Event struct {
-	nicID         tcpip.NICID
-	configuration ipv6.DHCPv6ConfigurationFromNDPRA
-}
-
-var _ ipv6.NDPDispatcher = (*ndpDispatcher)(nil)
-
-// ndpDispatcher implements NDPDispatcher so tests can know when various NDP
-// related events happen for test purposes.
-type ndpDispatcher struct {
-	dadC                 chan ndpDADEvent
-	routerC              chan ndpRouterEvent
-	rememberRouter       bool
-	prefixC              chan ndpPrefixEvent
-	rememberPrefix       bool
-	autoGenAddrC         chan ndpAutoGenAddrEvent
-	rdnssC               chan ndpRDNSSEvent
-	dnsslC               chan ndpDNSSLEvent
-	routeTable           []tcpip.Route
-	dhcpv6ConfigurationC chan ndpDHCPv6Event
-}
-
-// Implements ipv6.NDPDispatcher.OnDuplicateAddressDetectionResult.
-func (n *ndpDispatcher) OnDuplicateAddressDetectionResult(nicID tcpip.NICID, addr tcpip.Address, res stack.DADResult) {
-	if n.dadC != nil {
-		n.dadC <- ndpDADEvent{
-			nicID,
-			addr,
-			res,
-		}
-	}
-}
-
-// Implements ipv6.NDPDispatcher.OnDefaultRouterDiscovered.
-func (n *ndpDispatcher) OnDefaultRouterDiscovered(nicID tcpip.NICID, addr tcpip.Address) bool {
-	if c := n.routerC; c != nil {
-		c <- ndpRouterEvent{
-			nicID,
-			addr,
-			true,
-		}
-	}
-
-	return n.rememberRouter
-}
-
-// Implements ipv6.NDPDispatcher.OnDefaultRouterInvalidated.
-func (n *ndpDispatcher) OnDefaultRouterInvalidated(nicID tcpip.NICID, addr tcpip.Address) {
-	if c := n.routerC; c != nil {
-		c <- ndpRouterEvent{
-			nicID,
-			addr,
-			false,
-		}
-	}
-}
-
-// Implements ipv6.NDPDispatcher.OnOnLinkPrefixDiscovered.
-func (n *ndpDispatcher) OnOnLinkPrefixDiscovered(nicID tcpip.NICID, prefix tcpip.Subnet) bool {
-	if c := n.prefixC; c != nil {
-		c <- ndpPrefixEvent{
-			nicID,
-			prefix,
-			true,
-		}
-	}
-
-	return n.rememberPrefix
-}
-
-// Implements ipv6.NDPDispatcher.OnOnLinkPrefixInvalidated.
-func (n *ndpDispatcher) OnOnLinkPrefixInvalidated(nicID tcpip.NICID, prefix tcpip.Subnet) {
-	if c := n.prefixC; c != nil {
-		c <- ndpPrefixEvent{
-			nicID,
-			prefix,
-			false,
-		}
-	}
-}
-
-func (n *ndpDispatcher) OnAutoGenAddress(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) bool {
-	if c := n.autoGenAddrC; c != nil {
-		c <- ndpAutoGenAddrEvent{
-			nicID,
-			addr,
-			newAddr,
-		}
-	}
-	return true
-}
-
-func (n *ndpDispatcher) OnAutoGenAddressDeprecated(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) {
-	if c := n.autoGenAddrC; c != nil {
-		c <- ndpAutoGenAddrEvent{
-			nicID,
-			addr,
-			deprecatedAddr,
-		}
-	}
-}
-
-func (n *ndpDispatcher) OnAutoGenAddressInvalidated(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) {
-	if c := n.autoGenAddrC; c != nil {
-		c <- ndpAutoGenAddrEvent{
-			nicID,
-			addr,
-			invalidatedAddr,
-		}
-	}
-}
-
-// Implements ipv6.NDPDispatcher.OnRecursiveDNSServerOption.
-func (n *ndpDispatcher) OnRecursiveDNSServerOption(nicID tcpip.NICID, addrs []tcpip.Address, lifetime time.Duration) {
-	if c := n.rdnssC; c != nil {
-		c <- ndpRDNSSEvent{
-			nicID,
-			ndpRDNSS{
-				addrs,
-				lifetime,
-			},
-		}
-	}
-}
-
-// Implements ipv6.NDPDispatcher.OnDNSSearchListOption.
-func (n *ndpDispatcher) OnDNSSearchListOption(nicID tcpip.NICID, domainNames []string, lifetime time.Duration) {
-	if n.dnsslC != nil {
-		n.dnsslC <- ndpDNSSLEvent{
-			nicID,
-			domainNames,
-			lifetime,
-		}
-	}
-}
-
-// Implements ipv6.NDPDispatcher.OnDHCPv6Configuration.
-func (n *ndpDispatcher) OnDHCPv6Configuration(nicID tcpip.NICID, configuration ipv6.DHCPv6ConfigurationFromNDPRA) {
-	if c := n.dhcpv6ConfigurationC; c != nil {
-		c <- ndpDHCPv6Event{
-			nicID,
-			configuration,
-		}
-	}
-}
-
-// channelLinkWithHeaderLength is a channel.Endpoint with a configurable
-// header length.
-type channelLinkWithHeaderLength struct {
-	*channel.Endpoint
-	headerLength uint16
-}
-
-func (l *channelLinkWithHeaderLength) MaxHeaderLength() uint16 {
-	return l.headerLength
-}
-
-// Check e to make sure that the event is for addr on nic with ID 1, and the
-// resolved flag set to resolved with the specified err.
-func checkDADEvent(e ndpDADEvent, nicID tcpip.NICID, addr tcpip.Address, res stack.DADResult) string {
-	return cmp.Diff(ndpDADEvent{nicID: nicID, addr: addr, res: res}, e, cmp.AllowUnexported(e))
-}
-
-// TestDADDisabled tests that an address successfully resolves immediately
-// when DAD is not enabled (the default for an empty stack.Options).
-func TestDADDisabled(t *testing.T) {
-	const nicID = 1
-	ndpDisp := ndpDispatcher{
-		dadC: make(chan ndpDADEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPDisp: &ndpDisp,
-		})},
-	})
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	addrWithPrefix := tcpip.AddressWithPrefix{
-		Address:   addr1,
-		PrefixLen: defaultPrefixLen,
-	}
-	if err := s.AddAddressWithPrefix(nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil {
-		t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addrWithPrefix, err)
-	}
-
-	// Should get the address immediately since we should not have performed
-	// DAD on it.
-	select {
-	case e := <-ndpDisp.dadC:
-		if diff := checkDADEvent(e, nicID, addr1, &stack.DADSucceeded{}); diff != "" {
-			t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-		}
-	default:
-		t.Fatal("expected DAD event")
-	}
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil {
-		t.Fatal(err)
-	}
-
-	// We should not have sent any NDP NS messages.
-	if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got != 0 {
-		t.Fatalf("got NeighborSolicit = %d, want = 0", got)
-	}
-}
-
-func TestDADResolveLoopback(t *testing.T) {
-	const nicID = 1
-	ndpDisp := ndpDispatcher{
-		dadC: make(chan ndpDADEvent, 1),
-	}
-
-	dadConfigs := stack.DADConfigurations{
-		RetransmitTimer:        time.Second,
-		DupAddrDetectTransmits: 1,
-	}
-	clock := faketime.NewManualClock()
-	s := stack.New(stack.Options{
-		Clock: clock,
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPDisp:    &ndpDisp,
-			DADConfigs: dadConfigs,
-		})},
-	})
-	if err := s.CreateNIC(nicID, loopback.New()); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	addrWithPrefix := tcpip.AddressWithPrefix{
-		Address:   addr1,
-		PrefixLen: defaultPrefixLen,
-	}
-	if err := s.AddAddressWithPrefix(nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil {
-		t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addrWithPrefix, err)
-	}
-
-	// Address should not be considered bound to the NIC yet (DAD ongoing).
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-		t.Fatal(err)
-	}
-
-	// DAD should not resolve after the normal resolution time since our DAD
-	// message was looped back - we should extend our DAD process.
-	dadResolutionTime := time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer
-	clock.Advance(dadResolutionTime)
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-		t.Error(err)
-	}
-
-	// Make sure the address does not resolve before the extended resolution time
-	// has passed.
-	const delta = time.Nanosecond
-	// DAD will send extra NS probes if an NS message is looped back.
-	const extraTransmits = 3
-	clock.Advance(dadResolutionTime*extraTransmits - delta)
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-		t.Error(err)
-	}
-
-	// DAD should now resolve.
-	clock.Advance(delta)
-	if diff := checkDADEvent(<-ndpDisp.dadC, nicID, addr1, &stack.DADSucceeded{}); diff != "" {
-		t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-	}
-}
-
-// TestDADResolve tests that an address successfully resolves after performing
-// DAD for various values of DupAddrDetectTransmits and RetransmitTimer.
-// Included in the subtests is a test to make sure that an invalid
-// RetransmitTimer (<1ms) values get fixed to the default RetransmitTimer of 1s.
-// This tests also validates the NDP NS packet that is transmitted.
-func TestDADResolve(t *testing.T) {
-	const nicID = 1
-
-	tests := []struct {
-		name                    string
-		linkHeaderLen           uint16
-		dupAddrDetectTransmits  uint8
-		retransTimer            time.Duration
-		expectedRetransmitTimer time.Duration
-	}{
-		{
-			name:                    "1:1s:1s",
-			dupAddrDetectTransmits:  1,
-			retransTimer:            time.Second,
-			expectedRetransmitTimer: time.Second,
-		},
-		{
-			name:                    "2:1s:1s",
-			linkHeaderLen:           1,
-			dupAddrDetectTransmits:  2,
-			retransTimer:            time.Second,
-			expectedRetransmitTimer: time.Second,
-		},
-		{
-			name:                    "1:2s:2s",
-			linkHeaderLen:           2,
-			dupAddrDetectTransmits:  1,
-			retransTimer:            2 * time.Second,
-			expectedRetransmitTimer: 2 * time.Second,
-		},
-		// 0s is an invalid RetransmitTimer timer and will be fixed to
-		// the default RetransmitTimer value of 1s.
-		{
-			name:                    "1:0s:1s",
-			linkHeaderLen:           3,
-			dupAddrDetectTransmits:  1,
-			retransTimer:            0,
-			expectedRetransmitTimer: time.Second,
-		},
-	}
-
-	nonces := [][]byte{
-		{1, 2, 3, 4, 5, 6},
-		{7, 8, 9, 10, 11, 12},
-	}
-
-	var secureRNGBytes []byte
-	for _, n := range nonces {
-		secureRNGBytes = append(secureRNGBytes, n...)
-	}
-
-	for _, test := range tests {
-		test := test
-
-		t.Run(test.name, func(t *testing.T) {
-			t.Parallel()
-
-			ndpDisp := ndpDispatcher{
-				dadC: make(chan ndpDADEvent),
-			}
-
-			e := channelLinkWithHeaderLength{
-				Endpoint:     channel.New(int(test.dupAddrDetectTransmits), 1280, linkAddr1),
-				headerLength: test.linkHeaderLen,
-			}
-			e.Endpoint.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-			var secureRNG bytes.Reader
-			secureRNG.Reset(secureRNGBytes)
-
-			s := stack.New(stack.Options{
-				SecureRNG: &secureRNG,
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPDisp: &ndpDisp,
-					DADConfigs: stack.DADConfigurations{
-						RetransmitTimer:        test.retransTimer,
-						DupAddrDetectTransmits: test.dupAddrDetectTransmits,
-					},
-				})},
-			})
-			if err := s.CreateNIC(nicID, &e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			// We add a default route so the call to FindRoute below will succeed
-			// once we have an assigned address.
-			s.SetRouteTable([]tcpip.Route{{
-				Destination: header.IPv6EmptySubnet,
-				Gateway:     addr3,
-				NIC:         nicID,
-			}})
-
-			addrWithPrefix := tcpip.AddressWithPrefix{
-				Address:   addr1,
-				PrefixLen: defaultPrefixLen,
-			}
-			if err := s.AddAddressWithPrefix(nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil {
-				t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addrWithPrefix, err)
-			}
-
-			// Address should not be considered bound to the NIC yet (DAD ongoing).
-			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-
-			// Make sure the address does not resolve before the resolution time has
-			// passed.
-			time.Sleep(test.expectedRetransmitTimer*time.Duration(test.dupAddrDetectTransmits) - defaultAsyncNegativeEventTimeout)
-			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Error(err)
-			}
-			// Should not get a route even if we specify the local address as the
-			// tentative address.
-			{
-				r, err := s.FindRoute(nicID, "", addr2, header.IPv6ProtocolNumber, false)
-				if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-					t.Errorf("got FindRoute(%d, '', %s, %d, false) = (%+v, %v), want = (_, %s)", nicID, addr2, header.IPv6ProtocolNumber, r, err, &tcpip.ErrNoRoute{})
-				}
-				if r != nil {
-					r.Release()
-				}
-			}
-			{
-				r, err := s.FindRoute(nicID, addr1, addr2, header.IPv6ProtocolNumber, false)
-				if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-					t.Errorf("got FindRoute(%d, %s, %s, %d, false) = (%+v, %v), want = (_, %s)", nicID, addr1, addr2, header.IPv6ProtocolNumber, r, err, &tcpip.ErrNoRoute{})
-				}
-				if r != nil {
-					r.Release()
-				}
-			}
-
-			if t.Failed() {
-				t.FailNow()
-			}
-
-			// Wait for DAD to resolve.
-			select {
-			case <-time.After(defaultAsyncPositiveEventTimeout):
-				t.Fatal("timed out waiting for DAD resolution")
-			case e := <-ndpDisp.dadC:
-				if diff := checkDADEvent(e, nicID, addr1, &stack.DADSucceeded{}); diff != "" {
-					t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-				}
-			}
-			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil {
-				t.Error(err)
-			}
-			// Should get a route using the address now that it is resolved.
-			{
-				r, err := s.FindRoute(nicID, "", addr2, header.IPv6ProtocolNumber, false)
-				if err != nil {
-					t.Errorf("got FindRoute(%d, '', %s, %d, false): %s", nicID, addr2, header.IPv6ProtocolNumber, err)
-				} else if r.LocalAddress() != addr1 {
-					t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), addr1)
-				}
-				r.Release()
-			}
-			{
-				r, err := s.FindRoute(nicID, addr1, addr2, header.IPv6ProtocolNumber, false)
-				if err != nil {
-					t.Errorf("got FindRoute(%d, %s, %s, %d, false): %s", nicID, addr1, addr2, header.IPv6ProtocolNumber, err)
-				} else if r.LocalAddress() != addr1 {
-					t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), addr1)
-				}
-				if r != nil {
-					r.Release()
-				}
-			}
-
-			if t.Failed() {
-				t.FailNow()
-			}
-
-			// Should not have sent any more NS messages.
-			if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got != uint64(test.dupAddrDetectTransmits) {
-				t.Fatalf("got NeighborSolicit = %d, want = %d", got, test.dupAddrDetectTransmits)
-			}
-
-			// Validate the sent Neighbor Solicitation messages.
-			for i := uint8(0); i < test.dupAddrDetectTransmits; i++ {
-				p, _ := e.ReadContext(context.Background())
-
-				// Make sure its an IPv6 packet.
-				if p.Proto != header.IPv6ProtocolNumber {
-					t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber)
-				}
-
-				// Make sure the right remote link address is used.
-				snmc := header.SolicitedNodeAddr(addr1)
-				if want := header.EthernetAddressFromMulticastIPv6Address(snmc); p.Route.RemoteLinkAddress != want {
-					t.Errorf("got remote link address = %s, want = %s", p.Route.RemoteLinkAddress, want)
-				}
-
-				// Check NDP NS packet.
-				//
-				// As per RFC 4861 section 4.3, a possible option is the Source Link
-				// Layer option, but this option MUST NOT be included when the source
-				// address of the packet is the unspecified address.
-				checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
-					checker.SrcAddr(header.IPv6Any),
-					checker.DstAddr(snmc),
-					checker.TTL(header.NDPHopLimit),
-					checker.NDPNS(
-						checker.NDPNSTargetAddress(addr1),
-						checker.NDPNSOptions([]header.NDPOption{header.NDPNonceOption(nonces[i])}),
-					))
-
-				if l, want := p.Pkt.AvailableHeaderBytes(), int(test.linkHeaderLen); l != want {
-					t.Errorf("got p.Pkt.AvailableHeaderBytes() = %d; want = %d", l, want)
-				}
-			}
-		})
-	}
-}
-
-func rxNDPSolicit(e *channel.Endpoint, tgt tcpip.Address) {
-	hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.ICMPv6NeighborSolicitMinimumSize)
-	pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborSolicitMinimumSize))
-	pkt.SetType(header.ICMPv6NeighborSolicit)
-	ns := header.NDPNeighborSolicit(pkt.MessageBody())
-	ns.SetTargetAddress(tgt)
-	snmc := header.SolicitedNodeAddr(tgt)
-	pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-		Header: pkt,
-		Src:    header.IPv6Any,
-		Dst:    snmc,
-	}))
-	payloadLength := hdr.UsedLength()
-	ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-	ip.Encode(&header.IPv6Fields{
-		PayloadLength:     uint16(payloadLength),
-		TransportProtocol: icmp.ProtocolNumber6,
-		HopLimit:          255,
-		SrcAddr:           header.IPv6Any,
-		DstAddr:           snmc,
-	})
-	e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{Data: hdr.View().ToVectorisedView()}))
-}
-
-// TestDADFail tests to make sure that the DAD process fails if another node is
-// detected to be performing DAD on the same address (receive an NS message from
-// a node doing DAD for the same address), or if another node is detected to own
-// the address already (receive an NA message for the tentative address).
-func TestDADFail(t *testing.T) {
-	const nicID = 1
-
-	tests := []struct {
-		name                      string
-		rxPkt                     func(e *channel.Endpoint, tgt tcpip.Address)
-		getStat                   func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter
-		expectedHolderLinkAddress tcpip.LinkAddress
-	}{
-		{
-			name:  "RxSolicit",
-			rxPkt: rxNDPSolicit,
-			getStat: func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
-				return s.NeighborSolicit
-			},
-			expectedHolderLinkAddress: "",
-		},
-		{
-			name: "RxAdvert",
-			rxPkt: func(e *channel.Endpoint, tgt tcpip.Address) {
-				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.MessageBody())
-				na.SetSolicitedFlag(true)
-				na.SetOverrideFlag(true)
-				na.SetTargetAddress(tgt)
-				na.Options().Serialize(header.NDPOptionsSerializer{
-					header.NDPTargetLinkLayerAddressOption(linkAddr1),
-				})
-				pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-					Header: pkt,
-					Src:    tgt,
-					Dst:    header.IPv6AllNodesMulticastAddress,
-				}))
-				payloadLength := hdr.UsedLength()
-				ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-				ip.Encode(&header.IPv6Fields{
-					PayloadLength:     uint16(payloadLength),
-					TransportProtocol: icmp.ProtocolNumber6,
-					HopLimit:          255,
-					SrcAddr:           tgt,
-					DstAddr:           header.IPv6AllNodesMulticastAddress,
-				})
-				e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{Data: hdr.View().ToVectorisedView()}))
-			},
-			getStat: func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
-				return s.NeighborAdvert
-			},
-			expectedHolderLinkAddress: linkAddr1,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				dadC: make(chan ndpDADEvent, 1),
-			}
-			dadConfigs := stack.DefaultDADConfigurations()
-			dadConfigs.RetransmitTimer = time.Second * 2
-
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPDisp:    &ndpDisp,
-					DADConfigs: dadConfigs,
-				})},
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr1); err != nil {
-				t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr1, err)
-			}
-
-			// Address should not be considered bound to the NIC yet
-			// (DAD ongoing).
-			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-
-			// Receive a packet to simulate an address conflict.
-			test.rxPkt(e, addr1)
-
-			stat := test.getStat(s.Stats().ICMP.V6.PacketsReceived)
-			if got := stat.Value(); got != 1 {
-				t.Fatalf("got stat = %d, want = 1", got)
-			}
-
-			// Wait for DAD to fail and make sure the address did
-			// not get resolved.
-			select {
-			case <-time.After(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer + time.Second):
-				// If we don't get a failure event after the
-				// expected resolution time + extra 1s buffer,
-				// something is wrong.
-				t.Fatal("timed out waiting for DAD failure")
-			case e := <-ndpDisp.dadC:
-				if diff := checkDADEvent(e, nicID, addr1, &stack.DADDupAddrDetected{HolderLinkAddress: test.expectedHolderLinkAddress}); diff != "" {
-					t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-				}
-			}
-			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-
-			// Attempting to add the address again should not fail if the address's
-			// state was cleaned up when DAD failed.
-			if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr1); err != nil {
-				t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr1, err)
-			}
-		})
-	}
-}
-
-func TestDADStop(t *testing.T) {
-	const nicID = 1
-
-	tests := []struct {
-		name               string
-		stopFn             func(t *testing.T, s *stack.Stack)
-		skipFinalAddrCheck bool
-	}{
-		// Tests to make sure that DAD stops when an address is removed.
-		{
-			name: "Remove address",
-			stopFn: func(t *testing.T, s *stack.Stack) {
-				if err := s.RemoveAddress(nicID, addr1); err != nil {
-					t.Fatalf("RemoveAddress(%d, %s): %s", nicID, addr1, err)
-				}
-			},
-		},
-
-		// Tests to make sure that DAD stops when the NIC is disabled.
-		{
-			name: "Disable NIC",
-			stopFn: func(t *testing.T, s *stack.Stack) {
-				if err := s.DisableNIC(nicID); err != nil {
-					t.Fatalf("DisableNIC(%d): %s", nicID, err)
-				}
-			},
-		},
-
-		// Tests to make sure that DAD stops when the NIC is removed.
-		{
-			name: "Remove NIC",
-			stopFn: func(t *testing.T, s *stack.Stack) {
-				if err := s.RemoveNIC(nicID); err != nil {
-					t.Fatalf("RemoveNIC(%d): %s", nicID, err)
-				}
-			},
-			// The NIC is removed so we can't check its addresses after calling
-			// stopFn.
-			skipFinalAddrCheck: true,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				dadC: make(chan ndpDADEvent, 1),
-			}
-
-			dadConfigs := stack.DADConfigurations{
-				RetransmitTimer:        time.Second,
-				DupAddrDetectTransmits: 2,
-			}
-
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPDisp:    &ndpDisp,
-					DADConfigs: dadConfigs,
-				})},
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-			}
-
-			if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr1); err != nil {
-				t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, header.IPv6ProtocolNumber, addr1, err)
-			}
-
-			// Address should not be considered bound to the NIC yet (DAD ongoing).
-			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-
-			test.stopFn(t, s)
-
-			// Wait for DAD to fail (since the address was removed during DAD).
-			select {
-			case <-time.After(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer + time.Second):
-				// If we don't get a failure event after the expected resolution
-				// time + extra 1s buffer, something is wrong.
-				t.Fatal("timed out waiting for DAD failure")
-			case e := <-ndpDisp.dadC:
-				if diff := checkDADEvent(e, nicID, addr1, &stack.DADAborted{}); diff != "" {
-					t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-				}
-			}
-
-			if !test.skipFinalAddrCheck {
-				if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-					t.Fatal(err)
-				}
-			}
-
-			// Should not have sent more than 1 NS message.
-			if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got > 1 {
-				t.Errorf("got NeighborSolicit = %d, want <= 1", got)
-			}
-		})
-	}
-}
-
-// TestSetNDPConfigurations tests that we can update and use per-interface NDP
-// configurations without affecting the default NDP configurations or other
-// interfaces' configurations.
-func TestSetNDPConfigurations(t *testing.T) {
-	const nicID1 = 1
-	const nicID2 = 2
-	const nicID3 = 3
-
-	tests := []struct {
-		name                    string
-		dupAddrDetectTransmits  uint8
-		retransmitTimer         time.Duration
-		expectedRetransmitTimer time.Duration
-	}{
-		{
-			"OK",
-			1,
-			time.Second,
-			time.Second,
-		},
-		{
-			"Invalid Retransmit Timer",
-			1,
-			0,
-			time.Second,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				dadC: make(chan ndpDADEvent, 1),
-			}
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPDisp: &ndpDisp,
-				})},
-			})
-
-			expectDADEvent := func(nicID tcpip.NICID, addr tcpip.Address) {
-				select {
-				case e := <-ndpDisp.dadC:
-					if diff := checkDADEvent(e, nicID, addr, &stack.DADSucceeded{}); diff != "" {
-						t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-					}
-				default:
-					t.Fatalf("expected DAD event for %s", addr)
-				}
-			}
-
-			// This NIC(1)'s NDP configurations will be updated to
-			// be different from the default.
-			if err := s.CreateNIC(nicID1, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID1, err)
-			}
-
-			// Created before updating NIC(1)'s NDP configurations
-			// but updating NIC(1)'s NDP configurations should not
-			// affect other existing NICs.
-			if err := s.CreateNIC(nicID2, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err)
-			}
-
-			// Update the configurations on NIC(1) to use DAD.
-			if ipv6Ep, err := s.GetNetworkEndpoint(nicID1, header.IPv6ProtocolNumber); err != nil {
-				t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID1, header.IPv6ProtocolNumber, err)
-			} else {
-				dad := ipv6Ep.(stack.DuplicateAddressDetector)
-				dad.SetDADConfigurations(stack.DADConfigurations{
-					DupAddrDetectTransmits: test.dupAddrDetectTransmits,
-					RetransmitTimer:        test.retransmitTimer,
-				})
-			}
-
-			// Created after updating NIC(1)'s NDP configurations
-			// but the stack's default NDP configurations should not
-			// have been updated.
-			if err := s.CreateNIC(nicID3, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID3, err)
-			}
-
-			// Add addresses for each NIC.
-			addrWithPrefix1 := tcpip.AddressWithPrefix{Address: addr1, PrefixLen: defaultPrefixLen}
-			if err := s.AddAddressWithPrefix(nicID1, header.IPv6ProtocolNumber, addrWithPrefix1); err != nil {
-				t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID1, header.IPv6ProtocolNumber, addrWithPrefix1, err)
-			}
-			addrWithPrefix2 := tcpip.AddressWithPrefix{Address: addr2, PrefixLen: defaultPrefixLen}
-			if err := s.AddAddressWithPrefix(nicID2, header.IPv6ProtocolNumber, addrWithPrefix2); err != nil {
-				t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID2, header.IPv6ProtocolNumber, addrWithPrefix2, err)
-			}
-			expectDADEvent(nicID2, addr2)
-			addrWithPrefix3 := tcpip.AddressWithPrefix{Address: addr3, PrefixLen: defaultPrefixLen}
-			if err := s.AddAddressWithPrefix(nicID3, header.IPv6ProtocolNumber, addrWithPrefix3); err != nil {
-				t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID3, header.IPv6ProtocolNumber, addrWithPrefix3, err)
-			}
-			expectDADEvent(nicID3, addr3)
-
-			// Address should not be considered bound to NIC(1) yet
-			// (DAD ongoing).
-			if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-
-			// Should get the address on NIC(2) and NIC(3)
-			// immediately since we should not have performed DAD on
-			// it as the stack was configured to not do DAD by
-			// default and we only updated the NDP configurations on
-			// NIC(1).
-			if err := checkGetMainNICAddress(s, nicID2, header.IPv6ProtocolNumber, addrWithPrefix2); err != nil {
-				t.Fatal(err)
-			}
-			if err := checkGetMainNICAddress(s, nicID3, header.IPv6ProtocolNumber, addrWithPrefix3); err != nil {
-				t.Fatal(err)
-			}
-
-			// Sleep until right (500ms before) before resolution to
-			// make sure the address didn't resolve on NIC(1) yet.
-			const delta = 500 * time.Millisecond
-			time.Sleep(time.Duration(test.dupAddrDetectTransmits)*test.expectedRetransmitTimer - delta)
-			if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-
-			// Wait for DAD to resolve.
-			select {
-			case <-time.After(2 * delta):
-				// We should get a resolution event after 500ms
-				// (delta) since we wait for 500ms less than the
-				// expected resolution time above to make sure
-				// that the address did not yet resolve. Waiting
-				// for 1s (2x delta) without a resolution event
-				// means something is wrong.
-				t.Fatal("timed out waiting for DAD resolution")
-			case e := <-ndpDisp.dadC:
-				if diff := checkDADEvent(e, nicID1, addr1, &stack.DADSucceeded{}); diff != "" {
-					t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-				}
-			}
-			if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, addrWithPrefix1); err != nil {
-				t.Fatal(err)
-			}
-		})
-	}
-}
-
-// raBufWithOptsAndDHCPv6 returns a valid NDP Router Advertisement with options
-// and DHCPv6 configurations specified.
-func raBufWithOptsAndDHCPv6(ip tcpip.Address, rl uint16, managedAddress, otherConfigurations bool, optSer header.NDPOptionsSerializer) *stack.PacketBuffer {
-	icmpSize := header.ICMPv6HeaderSize + header.NDPRAMinimumSize + int(optSer.Length())
-	hdr := buffer.NewPrependable(header.IPv6MinimumSize + icmpSize)
-	pkt := header.ICMPv6(hdr.Prepend(icmpSize))
-	pkt.SetType(header.ICMPv6RouterAdvert)
-	pkt.SetCode(0)
-	raPayload := pkt.MessageBody()
-	ra := header.NDPRouterAdvert(raPayload)
-	// Populate the Router Lifetime.
-	binary.BigEndian.PutUint16(raPayload[2:], rl)
-	// Populate the Managed Address flag field.
-	if managedAddress {
-		// The Managed Addresses flag field is the 7th bit of byte #1 (0-indexing)
-		// of the RA payload.
-		raPayload[1] |= (1 << 7)
-	}
-	// Populate the Other Configurations flag field.
-	if otherConfigurations {
-		// The Other Configurations flag field is the 6th bit of byte #1
-		// (0-indexing) of the RA payload.
-		raPayload[1] |= (1 << 6)
-	}
-	opts := ra.Options()
-	opts.Serialize(optSer)
-	pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
-		Header: pkt,
-		Src:    ip,
-		Dst:    header.IPv6AllNodesMulticastAddress,
-	}))
-	payloadLength := hdr.UsedLength()
-	iph := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
-	iph.Encode(&header.IPv6Fields{
-		PayloadLength:     uint16(payloadLength),
-		TransportProtocol: icmp.ProtocolNumber6,
-		HopLimit:          header.NDPHopLimit,
-		SrcAddr:           ip,
-		DstAddr:           header.IPv6AllNodesMulticastAddress,
-	})
-
-	return stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: hdr.View().ToVectorisedView(),
-	})
-}
-
-// raBufWithOpts returns a valid NDP Router Advertisement with options.
-//
-// Note, raBufWithOpts does not populate any of the RA fields other than the
-// Router Lifetime.
-func raBufWithOpts(ip tcpip.Address, rl uint16, optSer header.NDPOptionsSerializer) *stack.PacketBuffer {
-	return raBufWithOptsAndDHCPv6(ip, rl, false, false, optSer)
-}
-
-// raBufWithDHCPv6 returns a valid NDP Router Advertisement with DHCPv6 related
-// fields set.
-//
-// Note, raBufWithDHCPv6 does not populate any of the RA fields other than the
-// DHCPv6 related ones.
-func raBufWithDHCPv6(ip tcpip.Address, managedAddresses, otherConfiguratiosns bool) *stack.PacketBuffer {
-	return raBufWithOptsAndDHCPv6(ip, 0, managedAddresses, otherConfiguratiosns, header.NDPOptionsSerializer{})
-}
-
-// raBuf returns a valid NDP Router Advertisement.
-//
-// Note, raBuf does not populate any of the RA fields other than the
-// Router Lifetime.
-func raBuf(ip tcpip.Address, rl uint16) *stack.PacketBuffer {
-	return raBufWithOpts(ip, rl, header.NDPOptionsSerializer{})
-}
-
-// raBufWithPI returns a valid NDP Router Advertisement with a single Prefix
-// Information option.
-//
-// Note, raBufWithPI does not populate any of the RA fields other than the
-// Router Lifetime.
-func raBufWithPI(ip tcpip.Address, rl uint16, prefix tcpip.AddressWithPrefix, onLink, auto bool, vl, pl uint32) *stack.PacketBuffer {
-	flags := uint8(0)
-	if onLink {
-		// The OnLink flag is the 7th bit in the flags byte.
-		flags |= 1 << 7
-	}
-	if auto {
-		// The Address Auto-Configuration flag is the 6th bit in the
-		// flags byte.
-		flags |= 1 << 6
-	}
-
-	// A valid header.NDPPrefixInformation must be 30 bytes.
-	buf := [30]byte{}
-	// The first byte in a header.NDPPrefixInformation is the Prefix Length
-	// field.
-	buf[0] = uint8(prefix.PrefixLen)
-	// The 2nd byte within a header.NDPPrefixInformation is the Flags field.
-	buf[1] = flags
-	// The Valid Lifetime field starts after the 2nd byte within a
-	// header.NDPPrefixInformation.
-	binary.BigEndian.PutUint32(buf[2:], vl)
-	// The Preferred Lifetime field starts after the 6th byte within a
-	// header.NDPPrefixInformation.
-	binary.BigEndian.PutUint32(buf[6:], pl)
-	// The Prefix Address field starts after the 14th byte within a
-	// header.NDPPrefixInformation.
-	copy(buf[14:], prefix.Address)
-	return raBufWithOpts(ip, rl, header.NDPOptionsSerializer{
-		header.NDPPrefixInformation(buf[:]),
-	})
-}
-
-// TestNoRouterDiscovery tests that router discovery will not be performed if
-// configured not to.
-func TestNoRouterDiscovery(t *testing.T) {
-	// Being configured to discover routers means handle and
-	// discover are set to true and forwarding is set to false.
-	// This tests all possible combinations of the configurations,
-	// except for the configuration where handle = true, discover =
-	// true and forwarding = false (the required configuration to do
-	// router discovery) - that will done in other tests.
-	for i := 0; i < 7; i++ {
-		handle := i&1 != 0
-		discover := i&2 != 0
-		forwarding := i&4 == 0
-
-		t.Run(fmt.Sprintf("HandleRAs(%t), DiscoverDefaultRouters(%t), Forwarding(%t)", handle, discover, forwarding), func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				routerC: make(chan ndpRouterEvent, 1),
-			}
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPConfigs: ipv6.NDPConfigurations{
-						HandleRAs:              handle,
-						DiscoverDefaultRouters: discover,
-					},
-					NDPDisp: &ndpDisp,
-				})},
-			})
-			s.SetForwarding(ipv6.ProtocolNumber, forwarding)
-
-			if err := s.CreateNIC(1, e); err != nil {
-				t.Fatalf("CreateNIC(1) = %s", err)
-			}
-
-			// Rx an RA with non-zero lifetime.
-			e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 1000))
-			select {
-			case <-ndpDisp.routerC:
-				t.Fatal("unexpectedly discovered a router when configured not to")
-			default:
-			}
-		})
-	}
-}
-
-// Check e to make sure that the event is for addr on nic with ID 1, and the
-// discovered flag set to discovered.
-func checkRouterEvent(e ndpRouterEvent, addr tcpip.Address, discovered bool) string {
-	return cmp.Diff(ndpRouterEvent{nicID: 1, addr: addr, discovered: discovered}, e, cmp.AllowUnexported(e))
-}
-
-// TestRouterDiscoveryDispatcherNoRemember tests that the stack does not
-// remember a discovered router when the dispatcher asks it not to.
-func TestRouterDiscoveryDispatcherNoRemember(t *testing.T) {
-	ndpDisp := ndpDispatcher{
-		routerC: make(chan ndpRouterEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				DiscoverDefaultRouters: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	// Receive an RA for a router we should not remember.
-	const lifetimeSeconds = 1
-	e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, lifetimeSeconds))
-	select {
-	case e := <-ndpDisp.routerC:
-		if diff := checkRouterEvent(e, llAddr2, true); diff != "" {
-			t.Errorf("router event mismatch (-want +got):\n%s", diff)
-		}
-	default:
-		t.Fatal("expected router discovery event")
-	}
-
-	// Wait for the invalidation time plus some buffer to make sure we do
-	// not actually receive any invalidation events as we should not have
-	// remembered the router in the first place.
-	select {
-	case <-ndpDisp.routerC:
-		t.Fatal("should not have received any router events")
-	case <-time.After(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout):
-	}
-}
-
-func TestRouterDiscovery(t *testing.T) {
-	ndpDisp := ndpDispatcher{
-		routerC:        make(chan ndpRouterEvent, 1),
-		rememberRouter: true,
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				DiscoverDefaultRouters: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	expectRouterEvent := func(addr tcpip.Address, discovered bool) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.routerC:
-			if diff := checkRouterEvent(e, addr, discovered); diff != "" {
-				t.Errorf("router event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected router discovery event")
-		}
-	}
-
-	expectAsyncRouterInvalidationEvent := func(addr tcpip.Address, timeout time.Duration) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.routerC:
-			if diff := checkRouterEvent(e, addr, false); diff != "" {
-				t.Errorf("router event mismatch (-want +got):\n%s", diff)
-			}
-		case <-time.After(timeout):
-			t.Fatal("timed out waiting for router discovery event")
-		}
-	}
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	// Rx an RA from lladdr2 with zero lifetime. It should not be
-	// remembered.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 0))
-	select {
-	case <-ndpDisp.routerC:
-		t.Fatal("unexpectedly discovered a router with 0 lifetime")
-	default:
-	}
-
-	// Rx an RA from lladdr2 with a huge lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 1000))
-	expectRouterEvent(llAddr2, true)
-
-	// Rx an RA from another router (lladdr3) with non-zero lifetime.
-	const l3LifetimeSeconds = 6
-	e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr3, l3LifetimeSeconds))
-	expectRouterEvent(llAddr3, true)
-
-	// Rx an RA from lladdr2 with lesser lifetime.
-	const l2LifetimeSeconds = 2
-	e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, l2LifetimeSeconds))
-	select {
-	case <-ndpDisp.routerC:
-		t.Fatal("Should not receive a router event when updating lifetimes for known routers")
-	default:
-	}
-
-	// Wait for lladdr2's router invalidation job to execute. The lifetime
-	// of the router should have been updated to the most recent (smaller)
-	// lifetime.
-	//
-	// Wait for the normal lifetime plus an extra bit for the
-	// router to get invalidated. If we don't get an invalidation
-	// event after this time, then something is wrong.
-	expectAsyncRouterInvalidationEvent(llAddr2, l2LifetimeSeconds*time.Second+defaultAsyncPositiveEventTimeout)
-
-	// Rx an RA from lladdr2 with huge lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 1000))
-	expectRouterEvent(llAddr2, true)
-
-	// Rx an RA from lladdr2 with zero lifetime. It should be invalidated.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 0))
-	expectRouterEvent(llAddr2, false)
-
-	// Wait for lladdr3's router invalidation job to execute. The lifetime
-	// of the router should have been updated to the most recent (smaller)
-	// lifetime.
-	//
-	// Wait for the normal lifetime plus an extra bit for the
-	// router to get invalidated. If we don't get an invalidation
-	// event after this time, then something is wrong.
-	expectAsyncRouterInvalidationEvent(llAddr3, l3LifetimeSeconds*time.Second+defaultAsyncPositiveEventTimeout)
-}
-
-// TestRouterDiscoveryMaxRouters tests that only
-// ipv6.MaxDiscoveredDefaultRouters discovered routers are remembered.
-func TestRouterDiscoveryMaxRouters(t *testing.T) {
-	ndpDisp := ndpDispatcher{
-		routerC:        make(chan ndpRouterEvent, 1),
-		rememberRouter: true,
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				DiscoverDefaultRouters: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	// Receive an RA from 2 more than the max number of discovered routers.
-	for i := 1; i <= ipv6.MaxDiscoveredDefaultRouters+2; i++ {
-		linkAddr := []byte{2, 2, 3, 4, 5, 0}
-		linkAddr[5] = byte(i)
-		llAddr := header.LinkLocalAddr(tcpip.LinkAddress(linkAddr))
-
-		e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr, 5))
-
-		if i <= ipv6.MaxDiscoveredDefaultRouters {
-			select {
-			case e := <-ndpDisp.routerC:
-				if diff := checkRouterEvent(e, llAddr, true); diff != "" {
-					t.Errorf("router event mismatch (-want +got):\n%s", diff)
-				}
-			default:
-				t.Fatal("expected router discovery event")
-			}
-
-		} else {
-			select {
-			case <-ndpDisp.routerC:
-				t.Fatal("should not have discovered a new router after we already discovered the max number of routers")
-			default:
-			}
-		}
-	}
-}
-
-// TestNoPrefixDiscovery tests that prefix discovery will not be performed if
-// configured not to.
-func TestNoPrefixDiscovery(t *testing.T) {
-	prefix := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address("\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00"),
-		PrefixLen: 64,
-	}
-
-	// Being configured to discover prefixes means handle and
-	// discover are set to true and forwarding is set to false.
-	// This tests all possible combinations of the configurations,
-	// except for the configuration where handle = true, discover =
-	// true and forwarding = false (the required configuration to do
-	// prefix discovery) - that will done in other tests.
-	for i := 0; i < 7; i++ {
-		handle := i&1 != 0
-		discover := i&2 != 0
-		forwarding := i&4 == 0
-
-		t.Run(fmt.Sprintf("HandleRAs(%t), DiscoverOnLinkPrefixes(%t), Forwarding(%t)", handle, discover, forwarding), func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				prefixC: make(chan ndpPrefixEvent, 1),
-			}
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPConfigs: ipv6.NDPConfigurations{
-						HandleRAs:              handle,
-						DiscoverOnLinkPrefixes: discover,
-					},
-					NDPDisp: &ndpDisp,
-				})},
-			})
-			s.SetForwarding(ipv6.ProtocolNumber, forwarding)
-
-			if err := s.CreateNIC(1, e); err != nil {
-				t.Fatalf("CreateNIC(1) = %s", err)
-			}
-
-			// Rx an RA with prefix with non-zero lifetime.
-			e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, 10, 0))
-
-			select {
-			case <-ndpDisp.prefixC:
-				t.Fatal("unexpectedly discovered a prefix when configured not to")
-			default:
-			}
-		})
-	}
-}
-
-// Check e to make sure that the event is for prefix on nic with ID 1, and the
-// discovered flag set to discovered.
-func checkPrefixEvent(e ndpPrefixEvent, prefix tcpip.Subnet, discovered bool) string {
-	return cmp.Diff(ndpPrefixEvent{nicID: 1, prefix: prefix, discovered: discovered}, e, cmp.AllowUnexported(e))
-}
-
-// TestPrefixDiscoveryDispatcherNoRemember tests that the stack does not
-// remember a discovered on-link prefix when the dispatcher asks it not to.
-func TestPrefixDiscoveryDispatcherNoRemember(t *testing.T) {
-	prefix, subnet, _ := prefixSubnetAddr(0, "")
-
-	ndpDisp := ndpDispatcher{
-		prefixC: make(chan ndpPrefixEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				DiscoverDefaultRouters: false,
-				DiscoverOnLinkPrefixes: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	// Receive an RA with prefix that we should not remember.
-	const lifetimeSeconds = 1
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, lifetimeSeconds, 0))
-	select {
-	case e := <-ndpDisp.prefixC:
-		if diff := checkPrefixEvent(e, subnet, true); diff != "" {
-			t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
-		}
-	default:
-		t.Fatal("expected prefix discovery event")
-	}
-
-	// Wait for the invalidation time plus some buffer to make sure we do
-	// not actually receive any invalidation events as we should not have
-	// remembered the prefix in the first place.
-	select {
-	case <-ndpDisp.prefixC:
-		t.Fatal("should not have received any prefix events")
-	case <-time.After(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout):
-	}
-}
-
-func TestPrefixDiscovery(t *testing.T) {
-	prefix1, subnet1, _ := prefixSubnetAddr(0, "")
-	prefix2, subnet2, _ := prefixSubnetAddr(1, "")
-	prefix3, subnet3, _ := prefixSubnetAddr(2, "")
-
-	ndpDisp := ndpDispatcher{
-		prefixC:        make(chan ndpPrefixEvent, 1),
-		rememberPrefix: true,
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				DiscoverOnLinkPrefixes: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	expectPrefixEvent := func(prefix tcpip.Subnet, discovered bool) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.prefixC:
-			if diff := checkPrefixEvent(e, prefix, discovered); diff != "" {
-				t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected prefix discovery event")
-		}
-	}
-
-	// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-	// with zero valid lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 0, 0))
-	select {
-	case <-ndpDisp.prefixC:
-		t.Fatal("unexpectedly discovered a prefix with 0 lifetime")
-	default:
-	}
-
-	// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-	// with non-zero lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 100, 0))
-	expectPrefixEvent(subnet1, true)
-
-	// Receive an RA with prefix2 in a PI.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, false, 100, 0))
-	expectPrefixEvent(subnet2, true)
-
-	// Receive an RA with prefix3 in a PI.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix3, true, false, 100, 0))
-	expectPrefixEvent(subnet3, true)
-
-	// Receive an RA with prefix1 in a PI with lifetime = 0.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 0, 0))
-	expectPrefixEvent(subnet1, false)
-
-	// Receive an RA with prefix2 in a PI with lesser lifetime.
-	lifetime := uint32(2)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, false, lifetime, 0))
-	select {
-	case <-ndpDisp.prefixC:
-		t.Fatal("unexpectedly received prefix event when updating lifetime")
-	default:
-	}
-
-	// Wait for prefix2's most recent invalidation job plus some buffer to
-	// expire.
-	select {
-	case e := <-ndpDisp.prefixC:
-		if diff := checkPrefixEvent(e, subnet2, false); diff != "" {
-			t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
-		}
-	case <-time.After(time.Duration(lifetime)*time.Second + defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for prefix discovery event")
-	}
-
-	// Receive RA to invalidate prefix3.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix3, true, false, 0, 0))
-	expectPrefixEvent(subnet3, false)
-}
-
-func TestPrefixDiscoveryWithInfiniteLifetime(t *testing.T) {
-	// Update the infinite lifetime value to a smaller value so we can test
-	// that when we receive a PI with such a lifetime value, we do not
-	// invalidate the prefix.
-	const testInfiniteLifetimeSeconds = 2
-	const testInfiniteLifetime = testInfiniteLifetimeSeconds * time.Second
-	saved := header.NDPInfiniteLifetime
-	header.NDPInfiniteLifetime = testInfiniteLifetime
-	defer func() {
-		header.NDPInfiniteLifetime = saved
-	}()
-
-	prefix := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address("\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00"),
-		PrefixLen: 64,
-	}
-	subnet := prefix.Subnet()
-
-	ndpDisp := ndpDispatcher{
-		prefixC:        make(chan ndpPrefixEvent, 1),
-		rememberPrefix: true,
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				DiscoverOnLinkPrefixes: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	expectPrefixEvent := func(prefix tcpip.Subnet, discovered bool) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.prefixC:
-			if diff := checkPrefixEvent(e, prefix, discovered); diff != "" {
-				t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected prefix discovery event")
-		}
-	}
-
-	// Receive an RA with prefix in an NDP Prefix Information option (PI)
-	// with infinite valid lifetime which should not get invalidated.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds, 0))
-	expectPrefixEvent(subnet, true)
-	select {
-	case <-ndpDisp.prefixC:
-		t.Fatal("unexpectedly invalidated a prefix with infinite lifetime")
-	case <-time.After(testInfiniteLifetime + defaultAsyncNegativeEventTimeout):
-	}
-
-	// Receive an RA with finite lifetime.
-	// The prefix should get invalidated after 1s.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds-1, 0))
-	select {
-	case e := <-ndpDisp.prefixC:
-		if diff := checkPrefixEvent(e, subnet, false); diff != "" {
-			t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
-		}
-	case <-time.After(testInfiniteLifetime):
-		t.Fatal("timed out waiting for prefix discovery event")
-	}
-
-	// Receive an RA with finite lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds-1, 0))
-	expectPrefixEvent(subnet, true)
-
-	// Receive an RA with prefix with an infinite lifetime.
-	// The prefix should not be invalidated.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds, 0))
-	select {
-	case <-ndpDisp.prefixC:
-		t.Fatal("unexpectedly invalidated a prefix with infinite lifetime")
-	case <-time.After(testInfiniteLifetime + defaultAsyncNegativeEventTimeout):
-	}
-
-	// Receive an RA with a prefix with a lifetime value greater than the
-	// set infinite lifetime value.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds+1, 0))
-	select {
-	case <-ndpDisp.prefixC:
-		t.Fatal("unexpectedly invalidated a prefix with infinite lifetime")
-	case <-time.After((testInfiniteLifetimeSeconds+1)*time.Second + defaultAsyncNegativeEventTimeout):
-	}
-
-	// Receive an RA with 0 lifetime.
-	// The prefix should get invalidated.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, 0, 0))
-	expectPrefixEvent(subnet, false)
-}
-
-// TestPrefixDiscoveryMaxRouters tests that only
-// ipv6.MaxDiscoveredOnLinkPrefixes discovered on-link prefixes are remembered.
-func TestPrefixDiscoveryMaxOnLinkPrefixes(t *testing.T) {
-	ndpDisp := ndpDispatcher{
-		prefixC:        make(chan ndpPrefixEvent, ipv6.MaxDiscoveredOnLinkPrefixes+3),
-		rememberPrefix: true,
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				DiscoverDefaultRouters: false,
-				DiscoverOnLinkPrefixes: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	optSer := make(header.NDPOptionsSerializer, ipv6.MaxDiscoveredOnLinkPrefixes+2)
-	prefixes := [ipv6.MaxDiscoveredOnLinkPrefixes + 2]tcpip.Subnet{}
-
-	// Receive an RA with 2 more than the max number of discovered on-link
-	// prefixes.
-	for i := 0; i < ipv6.MaxDiscoveredOnLinkPrefixes+2; i++ {
-		prefixAddr := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0}
-		prefixAddr[7] = byte(i)
-		prefix := tcpip.AddressWithPrefix{
-			Address:   tcpip.Address(prefixAddr[:]),
-			PrefixLen: 64,
-		}
-		prefixes[i] = prefix.Subnet()
-		buf := [30]byte{}
-		buf[0] = uint8(prefix.PrefixLen)
-		buf[1] = 128
-		binary.BigEndian.PutUint32(buf[2:], 10)
-		copy(buf[14:], prefix.Address)
-
-		optSer[i] = header.NDPPrefixInformation(buf[:])
-	}
-
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, optSer))
-	for i := 0; i < ipv6.MaxDiscoveredOnLinkPrefixes+2; i++ {
-		if i < ipv6.MaxDiscoveredOnLinkPrefixes {
-			select {
-			case e := <-ndpDisp.prefixC:
-				if diff := checkPrefixEvent(e, prefixes[i], true); diff != "" {
-					t.Errorf("prefix event mismatch (-want +got):\n%s", diff)
-				}
-			default:
-				t.Fatal("expected prefix discovery event")
-			}
-		} else {
-			select {
-			case <-ndpDisp.prefixC:
-				t.Fatal("should not have discovered a new prefix after we already discovered the max number of prefixes")
-			default:
-			}
-		}
-	}
-}
-
-// Checks to see if list contains an IPv6 address, item.
-func containsV6Addr(list []tcpip.ProtocolAddress, item tcpip.AddressWithPrefix) bool {
-	protocolAddress := tcpip.ProtocolAddress{
-		Protocol:          header.IPv6ProtocolNumber,
-		AddressWithPrefix: item,
-	}
-
-	return containsAddr(list, protocolAddress)
-}
-
-// TestNoAutoGenAddr tests that SLAAC is not performed when configured not to.
-func TestNoAutoGenAddr(t *testing.T) {
-	prefix, _, _ := prefixSubnetAddr(0, "")
-
-	// Being configured to auto-generate addresses means handle and
-	// autogen are set to true and forwarding is set to false.
-	// This tests all possible combinations of the configurations,
-	// except for the configuration where handle = true, autogen =
-	// true and forwarding = false (the required configuration to do
-	// SLAAC) - that will done in other tests.
-	for i := 0; i < 7; i++ {
-		handle := i&1 != 0
-		autogen := i&2 != 0
-		forwarding := i&4 == 0
-
-		t.Run(fmt.Sprintf("HandleRAs(%t), AutoGenAddr(%t), Forwarding(%t)", handle, autogen, forwarding), func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-			}
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPConfigs: ipv6.NDPConfigurations{
-						HandleRAs:              handle,
-						AutoGenGlobalAddresses: autogen,
-					},
-					NDPDisp: &ndpDisp,
-				})},
-			})
-			s.SetForwarding(ipv6.ProtocolNumber, forwarding)
-
-			if err := s.CreateNIC(1, e); err != nil {
-				t.Fatalf("CreateNIC(1) = %s", err)
-			}
-
-			// Rx an RA with prefix with non-zero lifetime.
-			e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, false, true, 10, 0))
-
-			select {
-			case <-ndpDisp.autoGenAddrC:
-				t.Fatal("unexpectedly auto-generated an address when configured not to")
-			default:
-			}
-		})
-	}
-}
-
-// Check e to make sure that the event is for addr on nic with ID 1, and the
-// event type is set to eventType.
-func checkAutoGenAddrEvent(e ndpAutoGenAddrEvent, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) string {
-	return cmp.Diff(ndpAutoGenAddrEvent{nicID: 1, addr: addr, eventType: eventType}, e, cmp.AllowUnexported(e))
-}
-
-// TestAutoGenAddr tests that an address is properly generated and invalidated
-// when configured to do so.
-func TestAutoGenAddr2(t *testing.T) {
-	const newMinVL = 2
-	newMinVLDuration := newMinVL * time.Second
-	saved := ipv6.MinPrefixInformationValidLifetimeForUpdate
-	defer func() {
-		ipv6.MinPrefixInformationValidLifetimeForUpdate = saved
-	}()
-	ipv6.MinPrefixInformationValidLifetimeForUpdate = newMinVLDuration
-
-	prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
-	prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-
-	ndpDisp := ndpDispatcher{
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				AutoGenGlobalAddresses: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-	// with zero valid lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 0, 0))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly auto-generated an address with 0 lifetime")
-	default:
-	}
-
-	// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-	// with non-zero lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
-	expectAutoGenAddrEvent(addr1, newAddr)
-	if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) {
-		t.Fatalf("Should have %s in the list of addresses", addr1)
-	}
-
-	// Receive an RA with prefix2 in an NDP Prefix Information option (PI)
-	// with preferred lifetime > valid lifetime
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 5, 6))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly auto-generated an address with preferred lifetime > valid lifetime")
-	default:
-	}
-
-	// Receive an RA with prefix2 in a PI.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0))
-	expectAutoGenAddrEvent(addr2, newAddr)
-	if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) {
-		t.Fatalf("Should have %s in the list of addresses", addr1)
-	}
-	if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr2) {
-		t.Fatalf("Should have %s in the list of addresses", addr2)
-	}
-
-	// Refresh valid lifetime for addr of prefix1.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, 0))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly auto-generated an address when we already have an address for a prefix")
-	default:
-	}
-
-	// Wait for addr of prefix1 to be invalidated.
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" {
-			t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-		}
-	case <-time.After(newMinVLDuration + defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for addr auto gen event")
-	}
-	if containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) {
-		t.Fatalf("Should not have %s in the list of addresses", addr1)
-	}
-	if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr2) {
-		t.Fatalf("Should have %s in the list of addresses", addr2)
-	}
-}
-
-func addressCheck(addrs []tcpip.ProtocolAddress, containList, notContainList []tcpip.AddressWithPrefix) string {
-	ret := ""
-	for _, c := range containList {
-		if !containsV6Addr(addrs, c) {
-			ret += fmt.Sprintf("should have %s in the list of addresses\n", c)
-		}
-	}
-	for _, c := range notContainList {
-		if containsV6Addr(addrs, c) {
-			ret += fmt.Sprintf("should not have %s in the list of addresses\n", c)
-		}
-	}
-	return ret
-}
-
-// TestAutoGenTempAddr tests that temporary SLAAC addresses are generated when
-// configured to do so as part of IPv6 Privacy Extensions.
-func TestAutoGenTempAddr(t *testing.T) {
-	const (
-		nicID            = 1
-		newMinVL         = 5
-		newMinVLDuration = newMinVL * time.Second
-	)
-
-	savedMinPrefixInformationValidLifetimeForUpdate := ipv6.MinPrefixInformationValidLifetimeForUpdate
-	savedMaxDesync := ipv6.MaxDesyncFactor
-	defer func() {
-		ipv6.MinPrefixInformationValidLifetimeForUpdate = savedMinPrefixInformationValidLifetimeForUpdate
-		ipv6.MaxDesyncFactor = savedMaxDesync
-	}()
-	ipv6.MinPrefixInformationValidLifetimeForUpdate = newMinVLDuration
-	ipv6.MaxDesyncFactor = time.Nanosecond
-
-	prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
-	prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-
-	tests := []struct {
-		name             string
-		dupAddrTransmits uint8
-		retransmitTimer  time.Duration
-	}{
-		{
-			name: "DAD disabled",
-		},
-		{
-			name:             "DAD enabled",
-			dupAddrTransmits: 1,
-			retransmitTimer:  time.Second,
-		},
-	}
-
-	// This Run will not return until the parallel tests finish.
-	//
-	// We need this because we need to do some teardown work after the
-	// parallel tests complete.
-	//
-	// See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for
-	// more details.
-	t.Run("group", func(t *testing.T) {
-		for i, test := range tests {
-			i := i
-			test := test
-
-			t.Run(test.name, func(t *testing.T) {
-				t.Parallel()
-
-				seed := []byte{uint8(i)}
-				var tempIIDHistory [header.IIDSize]byte
-				header.InitialTempIID(tempIIDHistory[:], seed, nicID)
-				newTempAddr := func(stableAddr tcpip.Address) tcpip.AddressWithPrefix {
-					return header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], stableAddr)
-				}
-
-				ndpDisp := ndpDispatcher{
-					dadC:         make(chan ndpDADEvent, 2),
-					autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
-				}
-				e := channel.New(0, 1280, linkAddr1)
-				s := stack.New(stack.Options{
-					NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-						DADConfigs: stack.DADConfigurations{
-							DupAddrDetectTransmits: test.dupAddrTransmits,
-							RetransmitTimer:        test.retransmitTimer,
-						},
-						NDPConfigs: ipv6.NDPConfigurations{
-							HandleRAs:                  true,
-							AutoGenGlobalAddresses:     true,
-							AutoGenTempGlobalAddresses: true,
-						},
-						NDPDisp:     &ndpDisp,
-						TempIIDSeed: seed,
-					})},
-				})
-
-				if err := s.CreateNIC(nicID, e); err != nil {
-					t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-				}
-
-				expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-					t.Helper()
-
-					select {
-					case e := <-ndpDisp.autoGenAddrC:
-						if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-							t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-						}
-					default:
-						t.Fatal("expected addr auto gen event")
-					}
-				}
-
-				expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-					t.Helper()
-
-					select {
-					case e := <-ndpDisp.autoGenAddrC:
-						if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-							t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-						}
-					case <-time.After(defaultAsyncPositiveEventTimeout):
-						t.Fatal("timed out waiting for addr auto gen event")
-					}
-				}
-
-				expectDADEventAsync := func(addr tcpip.Address) {
-					t.Helper()
-
-					select {
-					case e := <-ndpDisp.dadC:
-						if diff := checkDADEvent(e, nicID, addr, &stack.DADSucceeded{}); diff != "" {
-							t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-						}
-					case <-time.After(time.Duration(test.dupAddrTransmits)*test.retransmitTimer + defaultAsyncPositiveEventTimeout):
-						t.Fatal("timed out waiting for DAD event")
-					}
-				}
-
-				// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-				// with zero valid lifetime.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 0, 0))
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					t.Fatalf("unexpectedly auto-generated an address with 0 lifetime; event = %+v", e)
-				default:
-				}
-
-				// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-				// with non-zero valid lifetime.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
-				expectAutoGenAddrEvent(addr1, newAddr)
-				expectDADEventAsync(addr1.Address)
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					t.Fatalf("unexpectedly got an auto gen addr event = %+v", e)
-				default:
-				}
-				if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1}, nil); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-
-				// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-				// with non-zero valid & preferred lifetimes.
-				tempAddr1 := newTempAddr(addr1.Address)
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100))
-				expectAutoGenAddrEvent(tempAddr1, newAddr)
-				expectDADEventAsync(tempAddr1.Address)
-				if mismatch := addressCheck(s.NICInfo()[1].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1}, nil); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-
-				// Receive an RA with prefix2 in an NDP Prefix Information option (PI)
-				// with preferred lifetime > valid lifetime
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 5, 6))
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					t.Fatalf("unexpectedly auto-generated an address with preferred lifetime > valid lifetime; event = %+v", e)
-				default:
-				}
-				if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1}, nil); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-
-				// Receive an RA with prefix2 in a PI w/ non-zero valid and preferred
-				// lifetimes.
-				tempAddr2 := newTempAddr(addr2.Address)
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100))
-				expectAutoGenAddrEvent(addr2, newAddr)
-				expectDADEventAsync(addr2.Address)
-				expectAutoGenAddrEventAsync(tempAddr2, newAddr)
-				expectDADEventAsync(tempAddr2.Address)
-				if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-
-				// Deprecate prefix1.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
-				expectAutoGenAddrEvent(addr1, deprecatedAddr)
-				expectAutoGenAddrEvent(tempAddr1, deprecatedAddr)
-				if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-
-				// Refresh lifetimes for prefix1.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100))
-				if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-
-				// Reduce valid lifetime and deprecate addresses of prefix1.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, 0))
-				expectAutoGenAddrEvent(addr1, deprecatedAddr)
-				expectAutoGenAddrEvent(tempAddr1, deprecatedAddr)
-				if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-
-				// Wait for addrs of prefix1 to be invalidated. They should be
-				// invalidated at the same time.
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					var nextAddr tcpip.AddressWithPrefix
-					if e.addr == addr1 {
-						if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" {
-							t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-						}
-						nextAddr = tempAddr1
-					} else {
-						if diff := checkAutoGenAddrEvent(e, tempAddr1, invalidatedAddr); diff != "" {
-							t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-						}
-						nextAddr = addr1
-					}
-
-					select {
-					case e := <-ndpDisp.autoGenAddrC:
-						if diff := checkAutoGenAddrEvent(e, nextAddr, invalidatedAddr); diff != "" {
-							t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-						}
-					case <-time.After(defaultAsyncPositiveEventTimeout):
-						t.Fatal("timed out waiting for addr auto gen event")
-					}
-				case <-time.After(newMinVLDuration + defaultAsyncPositiveEventTimeout):
-					t.Fatal("timed out waiting for addr auto gen event")
-				}
-				if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr2, tempAddr2}, []tcpip.AddressWithPrefix{addr1, tempAddr1}); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-
-				// Receive an RA with prefix2 in a PI w/ 0 lifetimes.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 0, 0))
-				expectAutoGenAddrEvent(addr2, deprecatedAddr)
-				expectAutoGenAddrEvent(tempAddr2, deprecatedAddr)
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					t.Errorf("got unexpected auto gen addr event = %+v", e)
-				default:
-				}
-				if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr2, tempAddr2}, []tcpip.AddressWithPrefix{addr1, tempAddr1}); mismatch != "" {
-					t.Fatal(mismatch)
-				}
-			})
-		}
-	})
-}
-
-// TestNoAutoGenTempAddrForLinkLocal test that temporary SLAAC addresses are not
-// generated for auto generated link-local addresses.
-func TestNoAutoGenTempAddrForLinkLocal(t *testing.T) {
-	const nicID = 1
-
-	savedMaxDesyncFactor := ipv6.MaxDesyncFactor
-	defer func() {
-		ipv6.MaxDesyncFactor = savedMaxDesyncFactor
-	}()
-	ipv6.MaxDesyncFactor = time.Nanosecond
-
-	tests := []struct {
-		name             string
-		dupAddrTransmits uint8
-		retransmitTimer  time.Duration
-	}{
-		{
-			name: "DAD disabled",
-		},
-		{
-			name:             "DAD enabled",
-			dupAddrTransmits: 1,
-			retransmitTimer:  time.Second,
-		},
-	}
-
-	// This Run will not return until the parallel tests finish.
-	//
-	// We need this because we need to do some teardown work after the
-	// parallel tests complete.
-	//
-	// See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for
-	// more details.
-	t.Run("group", func(t *testing.T) {
-		for _, test := range tests {
-			test := test
-
-			t.Run(test.name, func(t *testing.T) {
-				t.Parallel()
-
-				ndpDisp := ndpDispatcher{
-					dadC:         make(chan ndpDADEvent, 1),
-					autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-				}
-				e := channel.New(0, 1280, linkAddr1)
-				s := stack.New(stack.Options{
-					NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-						NDPConfigs: ipv6.NDPConfigurations{
-							AutoGenTempGlobalAddresses: true,
-						},
-						NDPDisp:          &ndpDisp,
-						AutoGenLinkLocal: true,
-					})},
-				})
-
-				if err := s.CreateNIC(nicID, e); err != nil {
-					t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-				}
-
-				// The stable link-local address should auto-generate and resolve DAD.
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, tcpip.AddressWithPrefix{Address: llAddr1, PrefixLen: header.IIDOffsetInIPv6Address * 8}, newAddr); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-				default:
-					t.Fatal("expected addr auto gen event")
-				}
-				select {
-				case e := <-ndpDisp.dadC:
-					if diff := checkDADEvent(e, nicID, llAddr1, &stack.DADSucceeded{}); diff != "" {
-						t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-					}
-				case <-time.After(time.Duration(test.dupAddrTransmits)*test.retransmitTimer + defaultAsyncPositiveEventTimeout):
-					t.Fatal("timed out waiting for DAD event")
-				}
-
-				// No new addresses should be generated.
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					t.Errorf("got unxpected auto gen addr event = %+v", e)
-				case <-time.After(defaultAsyncNegativeEventTimeout):
-				}
-			})
-		}
-	})
-}
-
-// TestNoAutoGenTempAddrWithoutStableAddr tests that a temporary SLAAC address
-// will not be generated until after DAD completes, even if a new Router
-// Advertisement is received to refresh lifetimes.
-func TestNoAutoGenTempAddrWithoutStableAddr(t *testing.T) {
-	const (
-		nicID           = 1
-		dadTransmits    = 1
-		retransmitTimer = 2 * time.Second
-	)
-
-	savedMaxDesyncFactor := ipv6.MaxDesyncFactor
-	defer func() {
-		ipv6.MaxDesyncFactor = savedMaxDesyncFactor
-	}()
-	ipv6.MaxDesyncFactor = 0
-
-	prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-	var tempIIDHistory [header.IIDSize]byte
-	header.InitialTempIID(tempIIDHistory[:], nil, nicID)
-	tempAddr := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
-
-	ndpDisp := ndpDispatcher{
-		dadC:         make(chan ndpDADEvent, 1),
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			DADConfigs: stack.DADConfigurations{
-				DupAddrDetectTransmits: dadTransmits,
-				RetransmitTimer:        retransmitTimer,
-			},
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:                  true,
-				AutoGenGlobalAddresses:     true,
-				AutoGenTempGlobalAddresses: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	// Receive an RA to trigger SLAAC for prefix.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" {
-			t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-		}
-	default:
-		t.Fatal("expected addr auto gen event")
-	}
-
-	// DAD on the stable address for prefix has not yet completed. Receiving a new
-	// RA that would refresh lifetimes should not generate a temporary SLAAC
-	// address for the prefix.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		t.Fatalf("unexpected auto gen addr event = %+v", e)
-	default:
-	}
-
-	// Wait for DAD to complete for the stable address then expect the temporary
-	// address to be generated.
-	select {
-	case e := <-ndpDisp.dadC:
-		if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADSucceeded{}); diff != "" {
-			t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-		}
-	case <-time.After(dadTransmits*retransmitTimer + defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for DAD event")
-	}
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		if diff := checkAutoGenAddrEvent(e, tempAddr, newAddr); diff != "" {
-			t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-		}
-	case <-time.After(defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for addr auto gen event")
-	}
-}
-
-// TestAutoGenTempAddrRegen tests that temporary SLAAC addresses are
-// regenerated.
-func TestAutoGenTempAddrRegen(t *testing.T) {
-	const (
-		nicID            = 1
-		regenAfter       = 2 * time.Second
-		newMinVL         = 10
-		newMinVLDuration = newMinVL * time.Second
-	)
-
-	savedMaxDesyncFactor := ipv6.MaxDesyncFactor
-	savedMinMaxTempAddrPreferredLifetime := ipv6.MinMaxTempAddrPreferredLifetime
-	savedMinMaxTempAddrValidLifetime := ipv6.MinMaxTempAddrValidLifetime
-	defer func() {
-		ipv6.MaxDesyncFactor = savedMaxDesyncFactor
-		ipv6.MinMaxTempAddrPreferredLifetime = savedMinMaxTempAddrPreferredLifetime
-		ipv6.MinMaxTempAddrValidLifetime = savedMinMaxTempAddrValidLifetime
-	}()
-	ipv6.MaxDesyncFactor = 0
-	ipv6.MinMaxTempAddrPreferredLifetime = newMinVLDuration
-	ipv6.MinMaxTempAddrValidLifetime = newMinVLDuration
-
-	prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-	var tempIIDHistory [header.IIDSize]byte
-	header.InitialTempIID(tempIIDHistory[:], nil, nicID)
-	tempAddr1 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
-	tempAddr2 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
-	tempAddr3 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
-
-	ndpDisp := ndpDispatcher{
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	ndpConfigs := ipv6.NDPConfigurations{
-		HandleRAs:                  true,
-		AutoGenGlobalAddresses:     true,
-		AutoGenTempGlobalAddresses: true,
-		RegenAdvanceDuration:       newMinVLDuration - regenAfter,
-	}
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ndpConfigs,
-			NDPDisp:    &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		case <-time.After(timeout):
-			t.Fatal("timed out waiting for addr auto gen event")
-		}
-	}
-
-	// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-	// with non-zero valid & preferred lifetimes.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
-	expectAutoGenAddrEvent(addr, newAddr)
-	expectAutoGenAddrEvent(tempAddr1, newAddr)
-	if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddr1}, nil); mismatch != "" {
-		t.Fatal(mismatch)
-	}
-
-	// Wait for regeneration
-	expectAutoGenAddrEventAsync(tempAddr2, newAddr, regenAfter+defaultAsyncPositiveEventTimeout)
-	if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddr1, tempAddr2}, nil); mismatch != "" {
-		t.Fatal(mismatch)
-	}
-
-	// Wait for regeneration
-	expectAutoGenAddrEventAsync(tempAddr3, newAddr, regenAfter+defaultAsyncPositiveEventTimeout)
-	if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddr1, tempAddr2, tempAddr3}, nil); mismatch != "" {
-		t.Fatal(mismatch)
-	}
-
-	// Stop generating temporary addresses
-	ndpConfigs.AutoGenTempGlobalAddresses = false
-	if ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber); err != nil {
-		t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
-	} else {
-		ndpEP := ipv6Ep.(ipv6.NDPEndpoint)
-		ndpEP.SetNDPConfigurations(ndpConfigs)
-	}
-
-	// Wait for all the temporary addresses to get invalidated.
-	tempAddrs := []tcpip.AddressWithPrefix{tempAddr1, tempAddr2, tempAddr3}
-	invalidateAfter := newMinVLDuration - 2*regenAfter
-	for _, addr := range tempAddrs {
-		// Wait for a deprecation then invalidation event, or just an invalidation
-		// event. We need to cover both cases but cannot deterministically hit both
-		// cases because the deprecation and invalidation jobs could execute in any
-		// order.
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, deprecatedAddr); diff == "" {
-				// If we get a deprecation event first, we should get an invalidation
-				// event almost immediately after.
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-				case <-time.After(defaultAsyncPositiveEventTimeout):
-					t.Fatal("timed out waiting for addr auto gen event")
-				}
-			} else if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff == "" {
-				// If we get an invalidation event first, we shouldn't get a deprecation
-				// event after.
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					t.Fatalf("unexpectedly got an auto-generated event = %+v", e)
-				case <-time.After(defaultAsyncNegativeEventTimeout):
-				}
-			} else {
-				t.Fatalf("got unexpected auto-generated event = %+v", e)
-			}
-		case <-time.After(invalidateAfter + defaultAsyncPositiveEventTimeout):
-			t.Fatal("timed out waiting for addr auto gen event")
-		}
-
-		invalidateAfter = regenAfter
-	}
-	if mismatch := addressCheck(s.NICInfo()[1].ProtocolAddresses, []tcpip.AddressWithPrefix{addr}, tempAddrs); mismatch != "" {
-		t.Fatal(mismatch)
-	}
-}
-
-// TestAutoGenTempAddrRegenJobUpdates tests that a temporary address's
-// regeneration job gets updated when refreshing the address's lifetimes.
-func TestAutoGenTempAddrRegenJobUpdates(t *testing.T) {
-	const (
-		nicID            = 1
-		regenAfter       = 2 * time.Second
-		newMinVL         = 10
-		newMinVLDuration = newMinVL * time.Second
-	)
-
-	savedMaxDesyncFactor := ipv6.MaxDesyncFactor
-	savedMinMaxTempAddrPreferredLifetime := ipv6.MinMaxTempAddrPreferredLifetime
-	savedMinMaxTempAddrValidLifetime := ipv6.MinMaxTempAddrValidLifetime
-	defer func() {
-		ipv6.MaxDesyncFactor = savedMaxDesyncFactor
-		ipv6.MinMaxTempAddrPreferredLifetime = savedMinMaxTempAddrPreferredLifetime
-		ipv6.MinMaxTempAddrValidLifetime = savedMinMaxTempAddrValidLifetime
-	}()
-	ipv6.MaxDesyncFactor = 0
-	ipv6.MinMaxTempAddrPreferredLifetime = newMinVLDuration
-	ipv6.MinMaxTempAddrValidLifetime = newMinVLDuration
-
-	prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-	var tempIIDHistory [header.IIDSize]byte
-	header.InitialTempIID(tempIIDHistory[:], nil, nicID)
-	tempAddr1 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
-	tempAddr2 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
-	tempAddr3 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address)
-
-	ndpDisp := ndpDispatcher{
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	ndpConfigs := ipv6.NDPConfigurations{
-		HandleRAs:                  true,
-		AutoGenGlobalAddresses:     true,
-		AutoGenTempGlobalAddresses: true,
-		RegenAdvanceDuration:       newMinVLDuration - regenAfter,
-	}
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ndpConfigs,
-			NDPDisp:    &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		case <-time.After(timeout):
-			t.Fatal("timed out waiting for addr auto gen event")
-		}
-	}
-
-	// Receive an RA with prefix1 in an NDP Prefix Information option (PI)
-	// with non-zero valid & preferred lifetimes.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
-	expectAutoGenAddrEvent(addr, newAddr)
-	expectAutoGenAddrEvent(tempAddr1, newAddr)
-	if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddr1}, nil); mismatch != "" {
-		t.Fatal(mismatch)
-	}
-
-	// Deprecate the prefix.
-	//
-	// A new temporary address should be generated after the regeneration
-	// time has passed since the prefix is deprecated.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 0))
-	expectAutoGenAddrEvent(addr, deprecatedAddr)
-	expectAutoGenAddrEvent(tempAddr1, deprecatedAddr)
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		t.Fatalf("unexpected auto gen addr event = %+v", e)
-	case <-time.After(regenAfter + defaultAsyncNegativeEventTimeout):
-	}
-
-	// Prefer the prefix again.
-	//
-	// A new temporary address should immediately be generated since the
-	// regeneration time has already passed since the last address was generated
-	// - this regeneration does not depend on a job.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
-	expectAutoGenAddrEvent(tempAddr2, newAddr)
-
-	// Increase the maximum lifetimes for temporary addresses to large values
-	// then refresh the lifetimes of the prefix.
-	//
-	// A new address should not be generated after the regeneration time that was
-	// expected for the previous check. This is because the preferred lifetime for
-	// the temporary addresses has increased, so it will take more time to
-	// regenerate a new temporary address. Note, new addresses are only
-	// regenerated after the preferred lifetime - the regenerate advance duration
-	// as paased.
-	ndpConfigs.MaxTempAddrValidLifetime = 100 * time.Second
-	ndpConfigs.MaxTempAddrPreferredLifetime = 100 * time.Second
-	ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber)
-	if err != nil {
-		t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
-	}
-	ndpEP := ipv6Ep.(ipv6.NDPEndpoint)
-	ndpEP.SetNDPConfigurations(ndpConfigs)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		t.Fatalf("unexpected auto gen addr event = %+v", e)
-	case <-time.After(regenAfter + defaultAsyncNegativeEventTimeout):
-	}
-
-	// Set the maximum lifetimes for temporary addresses such that on the next
-	// RA, the regeneration job gets scheduled again.
-	//
-	// The maximum lifetime is the sum of the minimum lifetimes for temporary
-	// addresses + the time that has already passed since the last address was
-	// generated so that the regeneration job is needed to generate the next
-	// address.
-	newLifetimes := newMinVLDuration + regenAfter + defaultAsyncNegativeEventTimeout
-	ndpConfigs.MaxTempAddrValidLifetime = newLifetimes
-	ndpConfigs.MaxTempAddrPreferredLifetime = newLifetimes
-	ndpEP.SetNDPConfigurations(ndpConfigs)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
-	expectAutoGenAddrEventAsync(tempAddr3, newAddr, regenAfter+defaultAsyncPositiveEventTimeout)
-}
-
-// TestMixedSLAACAddrConflictRegen tests SLAAC address regeneration in response
-// to a mix of DAD conflicts and NIC-local conflicts.
-func TestMixedSLAACAddrConflictRegen(t *testing.T) {
-	const (
-		nicID           = 1
-		nicName         = "nic"
-		lifetimeSeconds = 9999
-		// From stack.maxSLAACAddrLocalRegenAttempts
-		maxSLAACAddrLocalRegenAttempts = 10
-		// We use 2 more addreses than the maximum local regeneration attempts
-		// because we want to also trigger regeneration in response to a DAD
-		// conflicts for this test.
-		maxAddrs         = maxSLAACAddrLocalRegenAttempts + 2
-		dupAddrTransmits = 1
-		retransmitTimer  = time.Second
-	)
-
-	var tempIIDHistoryWithModifiedEUI64 [header.IIDSize]byte
-	header.InitialTempIID(tempIIDHistoryWithModifiedEUI64[:], nil, nicID)
-
-	var tempIIDHistoryWithOpaqueIID [header.IIDSize]byte
-	header.InitialTempIID(tempIIDHistoryWithOpaqueIID[:], nil, nicID)
-
-	prefix, subnet, stableAddrWithModifiedEUI64 := prefixSubnetAddr(0, linkAddr1)
-	var stableAddrsWithOpaqueIID [maxAddrs]tcpip.AddressWithPrefix
-	var tempAddrsWithOpaqueIID [maxAddrs]tcpip.AddressWithPrefix
-	var tempAddrsWithModifiedEUI64 [maxAddrs]tcpip.AddressWithPrefix
-	addrBytes := []byte(subnet.ID())
-	for i := 0; i < maxAddrs; i++ {
-		stableAddrsWithOpaqueIID[i] = tcpip.AddressWithPrefix{
-			Address:   tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, uint8(i), nil)),
-			PrefixLen: header.IIDOffsetInIPv6Address * 8,
-		}
-		// When generating temporary addresses, the resolved stable address for the
-		// SLAAC prefix will be the first address stable address generated for the
-		// prefix as we will not simulate address conflicts for the stable addresses
-		// in tests involving temporary addresses. Address conflicts for stable
-		// addresses will be done in their own tests.
-		tempAddrsWithOpaqueIID[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistoryWithOpaqueIID[:], stableAddrsWithOpaqueIID[0].Address)
-		tempAddrsWithModifiedEUI64[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistoryWithModifiedEUI64[:], stableAddrWithModifiedEUI64.Address)
-	}
-
-	tests := []struct {
-		name          string
-		addrs         []tcpip.AddressWithPrefix
-		tempAddrs     bool
-		initialExpect tcpip.AddressWithPrefix
-		maxAddrs      int
-		nicNameFromID func(tcpip.NICID, string) string
-	}{
-		{
-			name:     "Stable addresses with opaque IIDs",
-			addrs:    stableAddrsWithOpaqueIID[:],
-			maxAddrs: 1,
-			nicNameFromID: func(tcpip.NICID, string) string {
-				return nicName
-			},
-		},
-		{
-			name:          "Temporary addresses with opaque IIDs",
-			addrs:         tempAddrsWithOpaqueIID[:],
-			tempAddrs:     true,
-			initialExpect: stableAddrsWithOpaqueIID[0],
-			maxAddrs:      1 /* initial (stable) address */ + maxSLAACAddrLocalRegenAttempts,
-			nicNameFromID: func(tcpip.NICID, string) string {
-				return nicName
-			},
-		},
-		{
-			name:          "Temporary addresses with modified EUI64",
-			addrs:         tempAddrsWithModifiedEUI64[:],
-			tempAddrs:     true,
-			maxAddrs:      1 /* initial (stable) address */ + maxSLAACAddrLocalRegenAttempts,
-			initialExpect: stableAddrWithModifiedEUI64,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				// We may receive a deprecated and invalidated event for each SLAAC
-				// address that is assigned.
-				autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAddrs*2),
-			}
-			e := channel.New(0, 1280, linkAddr1)
-			clock := faketime.NewManualClock()
-			s := stack.New(stack.Options{
-				Clock: clock,
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPConfigs: ipv6.NDPConfigurations{
-						HandleRAs:                     true,
-						AutoGenGlobalAddresses:        true,
-						AutoGenTempGlobalAddresses:    test.tempAddrs,
-						AutoGenAddressConflictRetries: 1,
-					},
-					NDPDisp: &ndpDisp,
-					OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-						NICNameFromID: test.nicNameFromID,
-					},
-				})},
-				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
-			})
-
-			s.SetRouteTable([]tcpip.Route{{
-				Destination: header.IPv6EmptySubnet,
-				Gateway:     llAddr2,
-				NIC:         nicID,
-			}})
-
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			manuallyAssignedAddresses := make(map[tcpip.Address]struct{})
-			for j := 0; j < len(test.addrs)-1; j++ {
-				// The NIC will not attempt to generate an address in response to a
-				// NIC-local conflict after some maximum number of attempts. We skip
-				// creating a conflict for the address that would be generated as part
-				// of the last attempt so we can simulate a DAD conflict for this
-				// address and restart the NIC-local generation process.
-				if j == maxSLAACAddrLocalRegenAttempts-1 {
-					continue
-				}
-
-				if err := s.AddAddress(nicID, ipv6.ProtocolNumber, test.addrs[j].Address); err != nil {
-					t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, test.addrs[j].Address, err)
-				}
-
-				manuallyAssignedAddresses[test.addrs[j].Address] = struct{}{}
-			}
-
-			expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-				t.Helper()
-
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-				default:
-					t.Fatal("expected addr auto gen event")
-				}
-			}
-
-			expectAutoGenAddrAsyncEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-				t.Helper()
-
-				if diff := checkAutoGenAddrEvent(<-ndpDisp.autoGenAddrC, addr, eventType); diff != "" {
-					t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-				}
-			}
-
-			expectDADEventAsync := func(addr tcpip.Address) {
-				t.Helper()
-
-				clock.Advance(dupAddrTransmits * retransmitTimer)
-				if diff := checkDADEvent(<-ndpDisp.dadC, nicID, addr, &stack.DADSucceeded{}); diff != "" {
-					t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-				}
-			}
-
-			// Enable DAD.
-			ndpDisp.dadC = make(chan ndpDADEvent, 2)
-			if ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber); err != nil {
-				t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
-			} else {
-				ndpEP := ipv6Ep.(stack.DuplicateAddressDetector)
-				ndpEP.SetDADConfigurations(stack.DADConfigurations{
-					DupAddrDetectTransmits: dupAddrTransmits,
-					RetransmitTimer:        retransmitTimer,
-				})
-			}
-
-			// Do SLAAC for prefix.
-			e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
-			if test.initialExpect != (tcpip.AddressWithPrefix{}) {
-				expectAutoGenAddrEvent(test.initialExpect, newAddr)
-				expectDADEventAsync(test.initialExpect.Address)
-			}
-
-			// The last local generation attempt should succeed, but we introduce a
-			// DAD failure to restart the local generation process.
-			addr := test.addrs[maxSLAACAddrLocalRegenAttempts-1]
-			expectAutoGenAddrAsyncEvent(addr, newAddr)
-			rxNDPSolicit(e, addr.Address)
-			select {
-			case e := <-ndpDisp.dadC:
-				if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" {
-					t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-				}
-			default:
-				t.Fatal("expected DAD event")
-			}
-			expectAutoGenAddrEvent(addr, invalidatedAddr)
-
-			// The last address generated should resolve DAD.
-			addr = test.addrs[len(test.addrs)-1]
-			expectAutoGenAddrAsyncEvent(addr, newAddr)
-			expectDADEventAsync(addr.Address)
-
-			select {
-			case e := <-ndpDisp.autoGenAddrC:
-				t.Fatalf("unexpected auto gen addr event = %+v", e)
-			default:
-			}
-
-			// Wait for all the SLAAC addresses to be invalidated.
-			clock.Advance(lifetimeSeconds * time.Second)
-			gotAddresses := make(map[tcpip.Address]struct{})
-			for _, a := range s.NICInfo()[nicID].ProtocolAddresses {
-				gotAddresses[a.AddressWithPrefix.Address] = struct{}{}
-			}
-			if diff := cmp.Diff(manuallyAssignedAddresses, gotAddresses); diff != "" {
-				t.Fatalf("assigned addresses mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
-
-// stackAndNdpDispatcherWithDefaultRoute returns an ndpDispatcher,
-// channel.Endpoint and stack.Stack.
-//
-// stack.Stack will have a default route through the router (llAddr3) installed
-// and a static link-address (linkAddr3) added to the link address cache for the
-// router.
-func stackAndNdpDispatcherWithDefaultRoute(t *testing.T, nicID tcpip.NICID) (*ndpDispatcher, *channel.Endpoint, *stack.Stack) {
-	t.Helper()
-	ndpDisp := &ndpDispatcher{
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				AutoGenGlobalAddresses: true,
-			},
-			NDPDisp: ndpDisp,
-		})},
-		TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
-	})
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-	s.SetRouteTable([]tcpip.Route{{
-		Destination: header.IPv6EmptySubnet,
-		Gateway:     llAddr3,
-		NIC:         nicID,
-	}})
-
-	if err := s.AddStaticNeighbor(nicID, ipv6.ProtocolNumber, llAddr3, linkAddr3); err != nil {
-		t.Fatalf("s.AddStaticNeighbor(%d, %d, %s, %s): %s", nicID, ipv6.ProtocolNumber, llAddr3, linkAddr3, err)
-	}
-	return ndpDisp, e, s
-}
-
-// addrForNewConnectionTo returns the local address used when creating a new
-// connection to addr.
-func addrForNewConnectionTo(t *testing.T, s *stack.Stack, addr tcpip.FullAddress) tcpip.Address {
-	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(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq)
-	if err != nil {
-		t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err)
-	}
-	defer ep.Close()
-	ep.SocketOptions().SetV6Only(true)
-	if err := ep.Connect(addr); err != nil {
-		t.Fatalf("ep.Connect(%+v): %s", addr, err)
-	}
-	got, err := ep.GetLocalAddress()
-	if err != nil {
-		t.Fatalf("ep.GetLocalAddress(): %s", err)
-	}
-	return got.Addr
-}
-
-// addrForNewConnection returns the local address used when creating a new
-// connection.
-func addrForNewConnection(t *testing.T, s *stack.Stack) tcpip.Address {
-	t.Helper()
-
-	return addrForNewConnectionTo(t, s, dstAddr)
-}
-
-// addrForNewConnectionWithAddr returns the local address used when creating a
-// new connection with a specific local address.
-func addrForNewConnectionWithAddr(t *testing.T, s *stack.Stack, addr tcpip.FullAddress) tcpip.Address {
-	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(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq)
-	if err != nil {
-		t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err)
-	}
-	defer ep.Close()
-	ep.SocketOptions().SetV6Only(true)
-	if err := ep.Bind(addr); err != nil {
-		t.Fatalf("ep.Bind(%+v): %s", addr, err)
-	}
-	if err := ep.Connect(dstAddr); err != nil {
-		t.Fatalf("ep.Connect(%+v): %s", dstAddr, err)
-	}
-	got, err := ep.GetLocalAddress()
-	if err != nil {
-		t.Fatalf("ep.GetLocalAddress(): %s", err)
-	}
-	return got.Addr
-}
-
-// TestAutoGenAddrDeprecateFromPI tests deprecating a SLAAC address when
-// receiving a PI with 0 preferred lifetime.
-func TestAutoGenAddrDeprecateFromPI(t *testing.T) {
-	const nicID = 1
-
-	prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
-	prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-
-	ndpDisp, e, s := stackAndNdpDispatcherWithDefaultRoute(t, nicID)
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) {
-		t.Helper()
-
-		if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil {
-			t.Fatal(err)
-		}
-
-		if got := addrForNewConnection(t, s); got != addr.Address {
-			t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address)
-		}
-	}
-
-	// Receive PI for prefix1.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100))
-	expectAutoGenAddrEvent(addr1, newAddr)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should have %s in the list of addresses", addr1)
-	}
-	expectPrimaryAddr(addr1)
-
-	// Deprecate addr for prefix1 immedaitely.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0))
-	expectAutoGenAddrEvent(addr1, deprecatedAddr)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should have %s in the list of addresses", addr1)
-	}
-	// addr should still be the primary endpoint as there are no other addresses.
-	expectPrimaryAddr(addr1)
-
-	// Refresh lifetimes of addr generated from prefix1.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly got an auto-generated event")
-	default:
-	}
-	expectPrimaryAddr(addr1)
-
-	// Receive PI for prefix2.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100))
-	expectAutoGenAddrEvent(addr2, newAddr)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-	expectPrimaryAddr(addr2)
-
-	// Deprecate addr for prefix2 immedaitely.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0))
-	expectAutoGenAddrEvent(addr2, deprecatedAddr)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-	// addr1 should be the primary endpoint now since addr2 is deprecated but
-	// addr1 is not.
-	expectPrimaryAddr(addr1)
-	// addr2 is deprecated but if explicitly requested, it should be used.
-	fullAddr2 := tcpip.FullAddress{Addr: addr2.Address, NIC: nicID}
-	if got := addrForNewConnectionWithAddr(t, s, fullAddr2); got != addr2.Address {
-		t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr2, got, addr2.Address)
-	}
-
-	// Another PI w/ 0 preferred lifetime should not result in a deprecation
-	// event.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly got an auto-generated event")
-	default:
-	}
-	expectPrimaryAddr(addr1)
-	if got := addrForNewConnectionWithAddr(t, s, fullAddr2); got != addr2.Address {
-		t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr2, got, addr2.Address)
-	}
-
-	// Refresh lifetimes of addr generated from prefix2.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly got an auto-generated event")
-	default:
-	}
-	expectPrimaryAddr(addr2)
-}
-
-// TestAutoGenAddrJobDeprecation tests that an address is properly deprecated
-// when its preferred lifetime expires.
-func TestAutoGenAddrJobDeprecation(t *testing.T) {
-	const nicID = 1
-	const newMinVL = 2
-	newMinVLDuration := newMinVL * time.Second
-
-	saved := ipv6.MinPrefixInformationValidLifetimeForUpdate
-	defer func() {
-		ipv6.MinPrefixInformationValidLifetimeForUpdate = saved
-	}()
-	ipv6.MinPrefixInformationValidLifetimeForUpdate = newMinVLDuration
-
-	prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
-	prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-
-	ndpDisp, e, s := stackAndNdpDispatcherWithDefaultRoute(t, nicID)
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	expectAutoGenAddrEventAfter := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		case <-time.After(timeout):
-			t.Fatal("timed out waiting for addr auto gen event")
-		}
-	}
-
-	expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) {
-		t.Helper()
-
-		if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil {
-			t.Fatal(err)
-		}
-
-		if got := addrForNewConnection(t, s); got != addr.Address {
-			t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address)
-		}
-	}
-
-	// Receive PI for prefix2.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100))
-	expectAutoGenAddrEvent(addr2, newAddr)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-	expectPrimaryAddr(addr2)
-
-	// Receive a PI for prefix1.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 90))
-	expectAutoGenAddrEvent(addr1, newAddr)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should have %s in the list of addresses", addr1)
-	}
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-	expectPrimaryAddr(addr1)
-
-	// Refresh lifetime for addr of prefix1.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, newMinVL-1))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly got an auto-generated event")
-	default:
-	}
-	expectPrimaryAddr(addr1)
-
-	// Wait for addr of prefix1 to be deprecated.
-	expectAutoGenAddrEventAfter(addr1, deprecatedAddr, newMinVLDuration-time.Second+defaultAsyncPositiveEventTimeout)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should not have %s in the list of addresses", addr1)
-	}
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-	// addr2 should be the primary endpoint now since addr1 is deprecated but
-	// addr2 is not.
-	expectPrimaryAddr(addr2)
-	// addr1 is deprecated but if explicitly requested, it should be used.
-	fullAddr1 := tcpip.FullAddress{Addr: addr1.Address, NIC: nicID}
-	if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address {
-		t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address)
-	}
-
-	// Refresh valid lifetime for addr of prefix1, w/ 0 preferred lifetime to make
-	// sure we do not get a deprecation event again.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, 0))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly got an auto-generated event")
-	default:
-	}
-	expectPrimaryAddr(addr2)
-	if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address {
-		t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address)
-	}
-
-	// Refresh lifetimes for addr of prefix1.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, newMinVL-1))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly got an auto-generated event")
-	default:
-	}
-	// addr1 is the primary endpoint again since it is non-deprecated now.
-	expectPrimaryAddr(addr1)
-
-	// Wait for addr of prefix1 to be deprecated.
-	expectAutoGenAddrEventAfter(addr1, deprecatedAddr, newMinVLDuration-time.Second+defaultAsyncPositiveEventTimeout)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should not have %s in the list of addresses", addr1)
-	}
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-	// addr2 should be the primary endpoint now since it is not deprecated.
-	expectPrimaryAddr(addr2)
-	if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address {
-		t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address)
-	}
-
-	// Wait for addr of prefix1 to be invalidated.
-	expectAutoGenAddrEventAfter(addr1, invalidatedAddr, time.Second+defaultAsyncPositiveEventTimeout)
-	if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should not have %s in the list of addresses", addr1)
-	}
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-	expectPrimaryAddr(addr2)
-
-	// Refresh both lifetimes for addr of prefix2 to the same value.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, newMinVL, newMinVL))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly got an auto-generated event")
-	default:
-	}
-
-	// Wait for a deprecation then invalidation events, or just an invalidation
-	// event. We need to cover both cases but cannot deterministically hit both
-	// cases because the deprecation and invalidation handlers could be handled in
-	// either deprecation then invalidation, or invalidation then deprecation
-	// (which should be cancelled by the invalidation handler).
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		if diff := checkAutoGenAddrEvent(e, addr2, deprecatedAddr); diff == "" {
-			// If we get a deprecation event first, we should get an invalidation
-			// event almost immediately after.
-			select {
-			case e := <-ndpDisp.autoGenAddrC:
-				if diff := checkAutoGenAddrEvent(e, addr2, invalidatedAddr); diff != "" {
-					t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-				}
-			case <-time.After(defaultAsyncPositiveEventTimeout):
-				t.Fatal("timed out waiting for addr auto gen event")
-			}
-		} else if diff := checkAutoGenAddrEvent(e, addr2, invalidatedAddr); diff == "" {
-			// If we get an invalidation  event first, we should not get a deprecation
-			// event after.
-			select {
-			case <-ndpDisp.autoGenAddrC:
-				t.Fatal("unexpectedly got an auto-generated event")
-			case <-time.After(defaultAsyncNegativeEventTimeout):
-			}
-		} else {
-			t.Fatalf("got unexpected auto-generated event")
-		}
-	case <-time.After(newMinVLDuration + defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for addr auto gen event")
-	}
-	if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should not have %s in the list of addresses", addr1)
-	}
-	if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should not have %s in the list of addresses", addr2)
-	}
-	// Should not have any primary endpoints.
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-		t.Fatal(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(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq)
-	if err != nil {
-		t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err)
-	}
-	defer ep.Close()
-	ep.SocketOptions().SetV6Only(true)
-
-	{
-		err := ep.Connect(dstAddr)
-		if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-			t.Errorf("got ep.Connect(%+v) = %s, want = %s", dstAddr, err, &tcpip.ErrNoRoute{})
-		}
-	}
-}
-
-// Tests transitioning a SLAAC address's valid lifetime between finite and
-// infinite values.
-func TestAutoGenAddrFiniteToInfiniteToFiniteVL(t *testing.T) {
-	const infiniteVLSeconds = 2
-	const minVLSeconds = 1
-	savedIL := header.NDPInfiniteLifetime
-	savedMinVL := ipv6.MinPrefixInformationValidLifetimeForUpdate
-	defer func() {
-		ipv6.MinPrefixInformationValidLifetimeForUpdate = savedMinVL
-		header.NDPInfiniteLifetime = savedIL
-	}()
-	ipv6.MinPrefixInformationValidLifetimeForUpdate = minVLSeconds * time.Second
-	header.NDPInfiniteLifetime = infiniteVLSeconds * time.Second
-
-	prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-
-	tests := []struct {
-		name       string
-		infiniteVL uint32
-	}{
-		{
-			name:       "EqualToInfiniteVL",
-			infiniteVL: infiniteVLSeconds,
-		},
-		// Our implementation supports changing header.NDPInfiniteLifetime for tests
-		// such that a packet can be received where the lifetime field has a value
-		// greater than header.NDPInfiniteLifetime. Because of this, we test to make
-		// sure that receiving a value greater than header.NDPInfiniteLifetime is
-		// handled the same as when receiving a value equal to
-		// header.NDPInfiniteLifetime.
-		{
-			name:       "MoreThanInfiniteVL",
-			infiniteVL: infiniteVLSeconds + 1,
-		},
-	}
-
-	// This Run will not return until the parallel tests finish.
-	//
-	// We need this because we need to do some teardown work after the
-	// parallel tests complete.
-	//
-	// See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for
-	// more details.
-	t.Run("group", func(t *testing.T) {
-		for _, test := range tests {
-			test := test
-
-			t.Run(test.name, func(t *testing.T) {
-				t.Parallel()
-
-				ndpDisp := ndpDispatcher{
-					autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-				}
-				e := channel.New(0, 1280, linkAddr1)
-				s := stack.New(stack.Options{
-					NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-						NDPConfigs: ipv6.NDPConfigurations{
-							HandleRAs:              true,
-							AutoGenGlobalAddresses: true,
-						},
-						NDPDisp: &ndpDisp,
-					})},
-				})
-
-				if err := s.CreateNIC(1, e); err != nil {
-					t.Fatalf("CreateNIC(1) = %s", err)
-				}
-
-				// Receive an RA with finite prefix.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0))
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-
-				default:
-					t.Fatal("expected addr auto gen event")
-				}
-
-				// Receive an new RA with prefix with infinite VL.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.infiniteVL, 0))
-
-				// Receive a new RA with prefix with finite VL.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0))
-
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-
-				case <-time.After(minVLSeconds*time.Second + defaultAsyncPositiveEventTimeout):
-					t.Fatal("timeout waiting for addr auto gen event")
-				}
-			})
-		}
-	})
-}
-
-// TestAutoGenAddrValidLifetimeUpdates tests that the valid lifetime of an
-// auto-generated address only gets updated when required to, as specified in
-// RFC 4862 section 5.5.3.e.
-func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) {
-	const infiniteVL = 4294967295
-	const newMinVL = 4
-	saved := ipv6.MinPrefixInformationValidLifetimeForUpdate
-	defer func() {
-		ipv6.MinPrefixInformationValidLifetimeForUpdate = saved
-	}()
-	ipv6.MinPrefixInformationValidLifetimeForUpdate = newMinVL * time.Second
-
-	prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-
-	tests := []struct {
-		name string
-		ovl  uint32
-		nvl  uint32
-		evl  uint32
-	}{
-		// Should update the VL to the minimum VL for updating if the
-		// new VL is less than newMinVL but was originally greater than
-		// it.
-		{
-			"LargeVLToVLLessThanMinVLForUpdate",
-			9999,
-			1,
-			newMinVL,
-		},
-		{
-			"LargeVLTo0",
-			9999,
-			0,
-			newMinVL,
-		},
-		{
-			"InfiniteVLToVLLessThanMinVLForUpdate",
-			infiniteVL,
-			1,
-			newMinVL,
-		},
-		{
-			"InfiniteVLTo0",
-			infiniteVL,
-			0,
-			newMinVL,
-		},
-
-		// Should not update VL if original VL was less than newMinVL
-		// and the new VL is also less than newMinVL.
-		{
-			"ShouldNotUpdateWhenBothOldAndNewAreLessThanMinVLForUpdate",
-			newMinVL - 1,
-			newMinVL - 3,
-			newMinVL - 1,
-		},
-
-		// Should take the new VL if the new VL is greater than the
-		// remaining time or is greater than newMinVL.
-		{
-			"MorethanMinVLToLesserButStillMoreThanMinVLForUpdate",
-			newMinVL + 5,
-			newMinVL + 3,
-			newMinVL + 3,
-		},
-		{
-			"SmallVLToGreaterVLButStillLessThanMinVLForUpdate",
-			newMinVL - 3,
-			newMinVL - 1,
-			newMinVL - 1,
-		},
-		{
-			"SmallVLToGreaterVLThatIsMoreThaMinVLForUpdate",
-			newMinVL - 3,
-			newMinVL + 1,
-			newMinVL + 1,
-		},
-	}
-
-	// This Run will not return until the parallel tests finish.
-	//
-	// We need this because we need to do some teardown work after the
-	// parallel tests complete.
-	//
-	// See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for
-	// more details.
-	t.Run("group", func(t *testing.T) {
-		for _, test := range tests {
-			test := test
-
-			t.Run(test.name, func(t *testing.T) {
-				t.Parallel()
-
-				ndpDisp := ndpDispatcher{
-					autoGenAddrC: make(chan ndpAutoGenAddrEvent, 10),
-				}
-				e := channel.New(10, 1280, linkAddr1)
-				s := stack.New(stack.Options{
-					NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-						NDPConfigs: ipv6.NDPConfigurations{
-							HandleRAs:              true,
-							AutoGenGlobalAddresses: true,
-						},
-						NDPDisp: &ndpDisp,
-					})},
-				})
-
-				if err := s.CreateNIC(1, e); err != nil {
-					t.Fatalf("CreateNIC(1) = %s", err)
-				}
-
-				// Receive an RA with prefix with initial VL,
-				// test.ovl.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.ovl, 0))
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-				default:
-					t.Fatal("expected addr auto gen event")
-				}
-
-				// Receive an new RA with prefix with new VL,
-				// test.nvl.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.nvl, 0))
-
-				//
-				// Validate that the VL for the address got set
-				// to test.evl.
-				//
-
-				// The address should not be invalidated until the effective valid
-				// lifetime has passed.
-				select {
-				case <-ndpDisp.autoGenAddrC:
-					t.Fatal("unexpectedly received an auto gen addr event")
-				case <-time.After(time.Duration(test.evl)*time.Second - defaultAsyncNegativeEventTimeout):
-				}
-
-				// Wait for the invalidation event.
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-				case <-time.After(defaultAsyncPositiveEventTimeout):
-					t.Fatal("timeout waiting for addr auto gen event")
-				}
-			})
-		}
-	})
-}
-
-// TestAutoGenAddrRemoval tests that when auto-generated addresses are removed
-// by the user, its resources will be cleaned up and an invalidation event will
-// be sent to the integrator.
-func TestAutoGenAddrRemoval(t *testing.T) {
-	prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-
-	ndpDisp := ndpDispatcher{
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				AutoGenGlobalAddresses: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	// Receive a PI to auto-generate an address.
-	const lifetimeSeconds = 1
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, 0))
-	expectAutoGenAddrEvent(addr, newAddr)
-
-	// Removing the address should result in an invalidation event
-	// immediately.
-	if err := s.RemoveAddress(1, addr.Address); err != nil {
-		t.Fatalf("RemoveAddress(_, %s) = %s", addr.Address, err)
-	}
-	expectAutoGenAddrEvent(addr, invalidatedAddr)
-
-	// Wait for the original valid lifetime to make sure the original job got
-	// cancelled/cleaned up.
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly received an auto gen addr event")
-	case <-time.After(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout):
-	}
-}
-
-// TestAutoGenAddrAfterRemoval tests adding a SLAAC address that was previously
-// assigned to the NIC but is in the permanentExpired state.
-func TestAutoGenAddrAfterRemoval(t *testing.T) {
-	const nicID = 1
-
-	prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
-	prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
-	ndpDisp, e, s := stackAndNdpDispatcherWithDefaultRoute(t, nicID)
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) {
-		t.Helper()
-
-		if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil {
-			t.Fatal(err)
-		}
-
-		if got := addrForNewConnection(t, s); got != addr.Address {
-			t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address)
-		}
-	}
-
-	// Receive a PI to auto-generate addr1 with a large valid and preferred
-	// lifetime.
-	const largeLifetimeSeconds = 999
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix1, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
-	expectAutoGenAddrEvent(addr1, newAddr)
-	expectPrimaryAddr(addr1)
-
-	// Add addr2 as a static address.
-	protoAddr2 := tcpip.ProtocolAddress{
-		Protocol:          header.IPv6ProtocolNumber,
-		AddressWithPrefix: addr2,
-	}
-	if err := s.AddProtocolAddressWithOptions(nicID, protoAddr2, stack.FirstPrimaryEndpoint); err != nil {
-		t.Fatalf("AddProtocolAddressWithOptions(%d, %+v, %d) = %s", nicID, protoAddr2, stack.FirstPrimaryEndpoint, err)
-	}
-	// addr2 should be more preferred now since it is at the front of the primary
-	// list.
-	expectPrimaryAddr(addr2)
-
-	// Get a route using addr2 to increment its reference count then remove it
-	// to leave it in the permanentExpired state.
-	r, err := s.FindRoute(nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, false)
-	if err != nil {
-		t.Fatalf("FindRoute(%d, %s, %s, %d, false): %s", nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, err)
-	}
-	defer r.Release()
-	if err := s.RemoveAddress(nicID, addr2.Address); err != nil {
-		t.Fatalf("s.RemoveAddress(%d, %s): %s", nicID, addr2.Address, err)
-	}
-	// addr1 should be preferred again since addr2 is in the expired state.
-	expectPrimaryAddr(addr1)
-
-	// Receive a PI to auto-generate addr2 as valid and preferred.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
-	expectAutoGenAddrEvent(addr2, newAddr)
-	// addr2 should be more preferred now that it is closer to the front of the
-	// primary list and not deprecated.
-	expectPrimaryAddr(addr2)
-
-	// Removing the address should result in an invalidation event immediately.
-	// It should still be in the permanentExpired state because r is still held.
-	//
-	// We remove addr2 here to make sure addr2 was marked as a SLAAC address
-	// (it was previously marked as a static address).
-	if err := s.RemoveAddress(1, addr2.Address); err != nil {
-		t.Fatalf("RemoveAddress(_, %s) = %s", addr2.Address, err)
-	}
-	expectAutoGenAddrEvent(addr2, invalidatedAddr)
-	// addr1 should be more preferred since addr2 is in the expired state.
-	expectPrimaryAddr(addr1)
-
-	// Receive a PI to auto-generate addr2 as valid and deprecated.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, 0))
-	expectAutoGenAddrEvent(addr2, newAddr)
-	// addr1 should still be more preferred since addr2 is deprecated, even though
-	// it is closer to the front of the primary list.
-	expectPrimaryAddr(addr1)
-
-	// Receive a PI to refresh addr2's preferred lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly got an auto gen addr event")
-	default:
-	}
-	// addr2 should be more preferred now that it is not deprecated.
-	expectPrimaryAddr(addr2)
-
-	if err := s.RemoveAddress(1, addr2.Address); err != nil {
-		t.Fatalf("RemoveAddress(_, %s) = %s", addr2.Address, err)
-	}
-	expectAutoGenAddrEvent(addr2, invalidatedAddr)
-	expectPrimaryAddr(addr1)
-}
-
-// TestAutoGenAddrStaticConflict tests that if SLAAC generates an address that
-// is already assigned to the NIC, the static address remains.
-func TestAutoGenAddrStaticConflict(t *testing.T) {
-	prefix, _, addr := prefixSubnetAddr(0, linkAddr1)
-
-	ndpDisp := ndpDispatcher{
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				AutoGenGlobalAddresses: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(1) = %s", err)
-	}
-
-	// Add the address as a static address before SLAAC tries to add it.
-	if err := s.AddProtocolAddress(1, tcpip.ProtocolAddress{Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr}); err != nil {
-		t.Fatalf("AddAddress(_, %d, %s) = %s", header.IPv6ProtocolNumber, addr.Address, err)
-	}
-	if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) {
-		t.Fatalf("Should have %s in the list of addresses", addr1)
-	}
-
-	// Receive a PI where the generated address will be the same as the one
-	// that we already have assigned statically.
-	const lifetimeSeconds = 1
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, 0))
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly received an auto gen addr event for an address we already have statically")
-	default:
-	}
-	if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) {
-		t.Fatalf("Should have %s in the list of addresses", addr1)
-	}
-
-	// Should not get an invalidation event after the PI's invalidation
-	// time.
-	select {
-	case <-ndpDisp.autoGenAddrC:
-		t.Fatal("unexpectedly received an auto gen addr event")
-	case <-time.After(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout):
-	}
-	if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) {
-		t.Fatalf("Should have %s in the list of addresses", addr1)
-	}
-}
-
-// TestAutoGenAddrWithOpaqueIID tests that SLAAC generated addresses will use
-// opaque interface identifiers when configured to do so.
-func TestAutoGenAddrWithOpaqueIID(t *testing.T) {
-	const nicID = 1
-	const nicName = "nic1"
-	var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes]byte
-	secretKey := secretKeyBuf[:]
-	n, err := rand.Read(secretKey)
-	if err != nil {
-		t.Fatalf("rand.Read(_): %s", err)
-	}
-	if n != header.OpaqueIIDSecretKeyMinBytes {
-		t.Fatalf("got rand.Read(_) = (%d, _), want = (%d, _)", n, header.OpaqueIIDSecretKeyMinBytes)
-	}
-
-	prefix1, subnet1, _ := prefixSubnetAddr(0, linkAddr1)
-	prefix2, subnet2, _ := prefixSubnetAddr(1, linkAddr1)
-	// addr1 and addr2 are the addresses that are expected to be generated when
-	// stack.Stack is configured to generate opaque interface identifiers as
-	// defined by RFC 7217.
-	addrBytes := []byte(subnet1.ID())
-	addr1 := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet1, nicName, 0, secretKey)),
-		PrefixLen: 64,
-	}
-	addrBytes = []byte(subnet2.ID())
-	addr2 := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet2, nicName, 0, secretKey)),
-		PrefixLen: 64,
-	}
-
-	ndpDisp := ndpDispatcher{
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				AutoGenGlobalAddresses: true,
-			},
-			NDPDisp: &ndpDisp,
-			OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: func(_ tcpip.NICID, nicName string) string {
-					return nicName
-				},
-				SecretKey: secretKey,
-			},
-		})},
-	})
-	opts := stack.NICOptions{Name: nicName}
-	if err := s.CreateNICWithOptions(nicID, e, opts); err != nil {
-		t.Fatalf("CreateNICWithOptions(%d, _, %+v, _) = %s", nicID, opts, err)
-	}
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	// Receive an RA with prefix1 in a PI.
-	const validLifetimeSecondPrefix1 = 1
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, validLifetimeSecondPrefix1, 0))
-	expectAutoGenAddrEvent(addr1, newAddr)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should have %s in the list of addresses", addr1)
-	}
-
-	// Receive an RA with prefix2 in a PI with a large valid lifetime.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0))
-	expectAutoGenAddrEvent(addr2, newAddr)
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should have %s in the list of addresses", addr1)
-	}
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-
-	// Wait for addr of prefix1 to be invalidated.
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" {
-			t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-		}
-	case <-time.After(validLifetimeSecondPrefix1*time.Second + defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for addr auto gen event")
-	}
-	if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) {
-		t.Fatalf("should not have %s in the list of addresses", addr1)
-	}
-	if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) {
-		t.Fatalf("should have %s in the list of addresses", addr2)
-	}
-}
-
-func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
-	const nicID = 1
-	const nicName = "nic"
-	const dadTransmits = 1
-	const retransmitTimer = time.Second
-	const maxMaxRetries = 3
-	const lifetimeSeconds = 10
-
-	// Needed for the temporary address sub test.
-	savedMaxDesync := ipv6.MaxDesyncFactor
-	defer func() {
-		ipv6.MaxDesyncFactor = savedMaxDesync
-	}()
-	ipv6.MaxDesyncFactor = time.Nanosecond
-
-	var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes]byte
-	secretKey := secretKeyBuf[:]
-	n, err := rand.Read(secretKey)
-	if err != nil {
-		t.Fatalf("rand.Read(_): %s", err)
-	}
-	if n != header.OpaqueIIDSecretKeyMinBytes {
-		t.Fatalf("got rand.Read(_) = (%d, _), want = (%d, _)", n, header.OpaqueIIDSecretKeyMinBytes)
-	}
-
-	prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
-
-	addrForSubnet := func(subnet tcpip.Subnet, dadCounter uint8) tcpip.AddressWithPrefix {
-		addrBytes := []byte(subnet.ID())
-		return tcpip.AddressWithPrefix{
-			Address:   tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, dadCounter, secretKey)),
-			PrefixLen: 64,
-		}
-	}
-
-	expectAutoGenAddrEvent := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	expectAutoGenAddrEventAsync := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		case <-time.After(defaultAsyncPositiveEventTimeout):
-			t.Fatal("timed out waiting for addr auto gen event")
-		}
-	}
-
-	expectDADEvent := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.dadC:
-			if diff := checkDADEvent(e, nicID, addr, res); diff != "" {
-				t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected DAD event")
-		}
-	}
-
-	expectDADEventAsync := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.dadC:
-			if diff := checkDADEvent(e, nicID, addr, res); diff != "" {
-				t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-			}
-		case <-time.After(dadTransmits*retransmitTimer + defaultAsyncPositiveEventTimeout):
-			t.Fatal("timed out waiting for DAD event")
-		}
-	}
-
-	stableAddrForTempAddrTest := addrForSubnet(subnet, 0)
-
-	addrTypes := []struct {
-		name             string
-		ndpConfigs       ipv6.NDPConfigurations
-		autoGenLinkLocal bool
-		prepareFn        func(t *testing.T, ndpDisp *ndpDispatcher, e *channel.Endpoint, tempIIDHistory []byte) []tcpip.AddressWithPrefix
-		addrGenFn        func(dadCounter uint8, tempIIDHistory []byte) tcpip.AddressWithPrefix
-	}{
-		{
-			name: "Global address",
-			ndpConfigs: ipv6.NDPConfigurations{
-				HandleRAs:              true,
-				AutoGenGlobalAddresses: true,
-			},
-			prepareFn: func(_ *testing.T, _ *ndpDispatcher, e *channel.Endpoint, _ []byte) []tcpip.AddressWithPrefix {
-				// Receive an RA with prefix1 in a PI.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
-				return nil
-
-			},
-			addrGenFn: func(dadCounter uint8, _ []byte) tcpip.AddressWithPrefix {
-				return addrForSubnet(subnet, dadCounter)
-			},
-		},
-		{
-			name:             "LinkLocal address",
-			ndpConfigs:       ipv6.NDPConfigurations{},
-			autoGenLinkLocal: true,
-			prepareFn: func(*testing.T, *ndpDispatcher, *channel.Endpoint, []byte) []tcpip.AddressWithPrefix {
-				return nil
-			},
-			addrGenFn: func(dadCounter uint8, _ []byte) tcpip.AddressWithPrefix {
-				return addrForSubnet(header.IPv6LinkLocalPrefix.Subnet(), dadCounter)
-			},
-		},
-		{
-			name: "Temporary address",
-			ndpConfigs: ipv6.NDPConfigurations{
-				HandleRAs:                  true,
-				AutoGenGlobalAddresses:     true,
-				AutoGenTempGlobalAddresses: true,
-			},
-			prepareFn: func(t *testing.T, ndpDisp *ndpDispatcher, e *channel.Endpoint, tempIIDHistory []byte) []tcpip.AddressWithPrefix {
-				header.InitialTempIID(tempIIDHistory, nil, nicID)
-
-				// Generate a stable SLAAC address so temporary addresses will be
-				// generated.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100))
-				expectAutoGenAddrEvent(t, ndpDisp, stableAddrForTempAddrTest, newAddr)
-				expectDADEventAsync(t, ndpDisp, stableAddrForTempAddrTest.Address, &stack.DADSucceeded{})
-
-				// The stable address will be assigned throughout the test.
-				return []tcpip.AddressWithPrefix{stableAddrForTempAddrTest}
-			},
-			addrGenFn: func(_ uint8, tempIIDHistory []byte) tcpip.AddressWithPrefix {
-				return header.GenerateTempIPv6SLAACAddr(tempIIDHistory, stableAddrForTempAddrTest.Address)
-			},
-		},
-	}
-
-	for _, addrType := range addrTypes {
-		// This Run will not return until the parallel tests finish.
-		//
-		// We need this because we need to do some teardown work after the parallel
-		// tests complete and limit the number of parallel tests running at the same
-		// time to reduce flakes.
-		//
-		// See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for
-		// more details.
-		t.Run(addrType.name, func(t *testing.T) {
-			for maxRetries := uint8(0); maxRetries <= maxMaxRetries; maxRetries++ {
-				for numFailures := uint8(0); numFailures <= maxRetries+1; numFailures++ {
-					maxRetries := maxRetries
-					numFailures := numFailures
-					addrType := addrType
-
-					t.Run(fmt.Sprintf("%d max retries and %d failures", maxRetries, numFailures), func(t *testing.T) {
-						t.Parallel()
-
-						ndpDisp := ndpDispatcher{
-							dadC:         make(chan ndpDADEvent, 1),
-							autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
-						}
-						e := channel.New(0, 1280, linkAddr1)
-						ndpConfigs := addrType.ndpConfigs
-						ndpConfigs.AutoGenAddressConflictRetries = maxRetries
-						s := stack.New(stack.Options{
-							NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-								AutoGenLinkLocal: addrType.autoGenLinkLocal,
-								DADConfigs: stack.DADConfigurations{
-									DupAddrDetectTransmits: dadTransmits,
-									RetransmitTimer:        retransmitTimer,
-								},
-								NDPConfigs: ndpConfigs,
-								NDPDisp:    &ndpDisp,
-								OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-									NICNameFromID: func(_ tcpip.NICID, nicName string) string {
-										return nicName
-									},
-									SecretKey: secretKey,
-								},
-							})},
-						})
-						opts := stack.NICOptions{Name: nicName}
-						if err := s.CreateNICWithOptions(nicID, e, opts); err != nil {
-							t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, err)
-						}
-
-						var tempIIDHistory [header.IIDSize]byte
-						stableAddrs := addrType.prepareFn(t, &ndpDisp, e, tempIIDHistory[:])
-
-						// Simulate DAD conflicts so the address is regenerated.
-						for i := uint8(0); i < numFailures; i++ {
-							addr := addrType.addrGenFn(i, tempIIDHistory[:])
-							expectAutoGenAddrEventAsync(t, &ndpDisp, addr, newAddr)
-
-							// Should not have any new addresses assigned to the NIC.
-							if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, stableAddrs, nil); mismatch != "" {
-								t.Fatal(mismatch)
-							}
-
-							// Simulate a DAD conflict.
-							rxNDPSolicit(e, addr.Address)
-							expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr)
-							expectDADEvent(t, &ndpDisp, addr.Address, &stack.DADDupAddrDetected{})
-
-							// Attempting to add the address manually should not fail if the
-							// address's state was cleaned up when DAD failed.
-							if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr.Address); err != nil {
-								t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr.Address, err)
-							}
-							if err := s.RemoveAddress(nicID, addr.Address); err != nil {
-								t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr.Address, err)
-							}
-							expectDADEvent(t, &ndpDisp, addr.Address, &stack.DADAborted{})
-						}
-
-						// Should not have any new addresses assigned to the NIC.
-						if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, stableAddrs, nil); mismatch != "" {
-							t.Fatal(mismatch)
-						}
-
-						// If we had less failures than generation attempts, we should have
-						// an address after DAD resolves.
-						if maxRetries+1 > numFailures {
-							addr := addrType.addrGenFn(numFailures, tempIIDHistory[:])
-							expectAutoGenAddrEventAsync(t, &ndpDisp, addr, newAddr)
-							expectDADEventAsync(t, &ndpDisp, addr.Address, &stack.DADSucceeded{})
-							if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, append(stableAddrs, addr), nil); mismatch != "" {
-								t.Fatal(mismatch)
-							}
-						}
-
-						// Should not attempt address generation again.
-						select {
-						case e := <-ndpDisp.autoGenAddrC:
-							t.Fatalf("unexpectedly got an auto-generated address event = %+v", e)
-						case <-time.After(defaultAsyncNegativeEventTimeout):
-						}
-					})
-				}
-			}
-		})
-	}
-}
-
-// TestAutoGenAddrWithEUI64IIDNoDADRetries tests that a regeneration attempt is
-// not made for SLAAC addresses generated with an IID based on the NIC's link
-// address.
-func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) {
-	const nicID = 1
-	const dadTransmits = 1
-	const retransmitTimer = time.Second
-	const maxRetries = 3
-	const lifetimeSeconds = 10
-
-	prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
-
-	addrTypes := []struct {
-		name             string
-		ndpConfigs       ipv6.NDPConfigurations
-		autoGenLinkLocal bool
-		subnet           tcpip.Subnet
-		triggerSLAACFn   func(e *channel.Endpoint)
-	}{
-		{
-			name: "Global address",
-			ndpConfigs: ipv6.NDPConfigurations{
-				HandleRAs:                     true,
-				AutoGenGlobalAddresses:        true,
-				AutoGenAddressConflictRetries: maxRetries,
-			},
-			subnet: subnet,
-			triggerSLAACFn: func(e *channel.Endpoint) {
-				// Receive an RA with prefix1 in a PI.
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
-
-			},
-		},
-		{
-			name: "LinkLocal address",
-			ndpConfigs: ipv6.NDPConfigurations{
-				AutoGenAddressConflictRetries: maxRetries,
-			},
-			autoGenLinkLocal: true,
-			subnet:           header.IPv6LinkLocalPrefix.Subnet(),
-			triggerSLAACFn:   func(e *channel.Endpoint) {},
-		},
-	}
-
-	for _, addrType := range addrTypes {
-		addrType := addrType
-
-		t.Run(addrType.name, func(t *testing.T) {
-			t.Parallel()
-
-			ndpDisp := ndpDispatcher{
-				dadC:         make(chan ndpDADEvent, 1),
-				autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
-			}
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					AutoGenLinkLocal: addrType.autoGenLinkLocal,
-					NDPConfigs:       addrType.ndpConfigs,
-					NDPDisp:          &ndpDisp,
-					DADConfigs: stack.DADConfigurations{
-						DupAddrDetectTransmits: dadTransmits,
-						RetransmitTimer:        retransmitTimer,
-					},
-				})},
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-				t.Helper()
-
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-				default:
-					t.Fatal("expected addr auto gen event")
-				}
-			}
-
-			addrType.triggerSLAACFn(e)
-
-			addrBytes := []byte(addrType.subnet.ID())
-			header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr1, addrBytes[header.IIDOffsetInIPv6Address:])
-			addr := tcpip.AddressWithPrefix{
-				Address:   tcpip.Address(addrBytes),
-				PrefixLen: 64,
-			}
-			expectAutoGenAddrEvent(addr, newAddr)
-
-			// Simulate a DAD conflict.
-			rxNDPSolicit(e, addr.Address)
-			expectAutoGenAddrEvent(addr, invalidatedAddr)
-			select {
-			case e := <-ndpDisp.dadC:
-				if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" {
-					t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-				}
-			default:
-				t.Fatal("expected DAD event")
-			}
-
-			// Should not attempt address regeneration.
-			select {
-			case e := <-ndpDisp.autoGenAddrC:
-				t.Fatalf("unexpectedly got an auto-generated address event = %+v", e)
-			case <-time.After(defaultAsyncNegativeEventTimeout):
-			}
-		})
-	}
-}
-
-// TestAutoGenAddrContinuesLifetimesAfterRetry tests that retrying address
-// generation in response to DAD conflicts does not refresh the lifetimes.
-func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) {
-	const nicID = 1
-	const nicName = "nic"
-	const dadTransmits = 1
-	const retransmitTimer = 2 * time.Second
-	const failureTimer = time.Second
-	const maxRetries = 1
-	const lifetimeSeconds = 5
-
-	var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes]byte
-	secretKey := secretKeyBuf[:]
-	n, err := rand.Read(secretKey)
-	if err != nil {
-		t.Fatalf("rand.Read(_): %s", err)
-	}
-	if n != header.OpaqueIIDSecretKeyMinBytes {
-		t.Fatalf("got rand.Read(_) = (%d, _), want = (%d, _)", n, header.OpaqueIIDSecretKeyMinBytes)
-	}
-
-	prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
-
-	ndpDisp := ndpDispatcher{
-		dadC:         make(chan ndpDADEvent, 1),
-		autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			DADConfigs: stack.DADConfigurations{
-				DupAddrDetectTransmits: dadTransmits,
-				RetransmitTimer:        retransmitTimer,
-			},
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs:                     true,
-				AutoGenGlobalAddresses:        true,
-				AutoGenAddressConflictRetries: maxRetries,
-			},
-			NDPDisp: &ndpDisp,
-			OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: func(_ tcpip.NICID, nicName string) string {
-					return nicName
-				},
-				SecretKey: secretKey,
-			},
-		})},
-	})
-	opts := stack.NICOptions{Name: nicName}
-	if err := s.CreateNICWithOptions(nicID, e, opts); err != nil {
-		t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, err)
-	}
-
-	expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
-		t.Helper()
-
-		select {
-		case e := <-ndpDisp.autoGenAddrC:
-			if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected addr auto gen event")
-		}
-	}
-
-	// Receive an RA with prefix in a PI.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
-
-	addrBytes := []byte(subnet.ID())
-	addr := tcpip.AddressWithPrefix{
-		Address:   tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, 0, secretKey)),
-		PrefixLen: 64,
-	}
-	expectAutoGenAddrEvent(addr, newAddr)
-
-	// Simulate a DAD conflict after some time has passed.
-	time.Sleep(failureTimer)
-	rxNDPSolicit(e, addr.Address)
-	expectAutoGenAddrEvent(addr, invalidatedAddr)
-	select {
-	case e := <-ndpDisp.dadC:
-		if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" {
-			t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-		}
-	default:
-		t.Fatal("expected DAD event")
-	}
-
-	// Let the next address resolve.
-	addr.Address = tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, 1, secretKey))
-	expectAutoGenAddrEvent(addr, newAddr)
-	select {
-	case e := <-ndpDisp.dadC:
-		if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADSucceeded{}); diff != "" {
-			t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
-		}
-	case <-time.After(dadTransmits*retransmitTimer + defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for DAD event")
-	}
-
-	// Address should be deprecated/invalidated after the lifetime expires.
-	//
-	// Note, the remaining lifetime is calculated from when the PI was first
-	// processed. Since we wait for some time before simulating a DAD conflict
-	// and more time for the new address to resolve, the new address is only
-	// expected to be valid for the remaining time. The DAD conflict should
-	// not have reset the lifetimes.
-	//
-	// We expect either just the invalidation event or the deprecation event
-	// followed by the invalidation event.
-	select {
-	case e := <-ndpDisp.autoGenAddrC:
-		if e.eventType == deprecatedAddr {
-			if diff := checkAutoGenAddrEvent(e, addr, deprecatedAddr); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-
-			select {
-			case e := <-ndpDisp.autoGenAddrC:
-				if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
-					t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-				}
-			case <-time.After(defaultAsyncPositiveEventTimeout):
-				t.Fatal("timed out waiting for invalidated auto gen addr event after deprecation")
-			}
-		} else {
-			if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
-				t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-			}
-		}
-	case <-time.After(lifetimeSeconds*time.Second - failureTimer - dadTransmits*retransmitTimer + defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for auto gen addr event")
-	}
-}
-
-// TestNDPRecursiveDNSServerDispatch tests that we properly dispatch an event
-// to the integrator when an RA is received with the NDP Recursive DNS Server
-// option with at least one valid address.
-func TestNDPRecursiveDNSServerDispatch(t *testing.T) {
-	tests := []struct {
-		name     string
-		opt      header.NDPRecursiveDNSServer
-		expected *ndpRDNSS
-	}{
-		{
-			"Unspecified",
-			header.NDPRecursiveDNSServer([]byte{
-				0, 0,
-				0, 0, 0, 2,
-				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-			}),
-			nil,
-		},
-		{
-			"Multicast",
-			header.NDPRecursiveDNSServer([]byte{
-				0, 0,
-				0, 0, 0, 2,
-				255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
-			}),
-			nil,
-		},
-		{
-			"OptionTooSmall",
-			header.NDPRecursiveDNSServer([]byte{
-				0, 0,
-				0, 0, 0, 2,
-				1, 2, 3, 4, 5, 6, 7, 8,
-			}),
-			nil,
-		},
-		{
-			"0Addresses",
-			header.NDPRecursiveDNSServer([]byte{
-				0, 0,
-				0, 0, 0, 2,
-			}),
-			nil,
-		},
-		{
-			"Valid1Address",
-			header.NDPRecursiveDNSServer([]byte{
-				0, 0,
-				0, 0, 0, 2,
-				1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1,
-			}),
-			&ndpRDNSS{
-				[]tcpip.Address{
-					"\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01",
-				},
-				2 * time.Second,
-			},
-		},
-		{
-			"Valid2Addresses",
-			header.NDPRecursiveDNSServer([]byte{
-				0, 0,
-				0, 0, 0, 1,
-				1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1,
-				1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 2,
-			}),
-			&ndpRDNSS{
-				[]tcpip.Address{
-					"\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01",
-					"\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x02",
-				},
-				time.Second,
-			},
-		},
-		{
-			"Valid3Addresses",
-			header.NDPRecursiveDNSServer([]byte{
-				0, 0,
-				0, 0, 0, 0,
-				1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1,
-				1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 2,
-				1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 3,
-			}),
-			&ndpRDNSS{
-				[]tcpip.Address{
-					"\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01",
-					"\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x02",
-					"\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x03",
-				},
-				0,
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				// We do not expect more than a single RDNSS
-				// event at any time for this test.
-				rdnssC: make(chan ndpRDNSSEvent, 1),
-			}
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPConfigs: ipv6.NDPConfigurations{
-						HandleRAs: true,
-					},
-					NDPDisp: &ndpDisp,
-				})},
-			})
-			if err := s.CreateNIC(1, e); err != nil {
-				t.Fatalf("CreateNIC(1) = %s", err)
-			}
-
-			e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, header.NDPOptionsSerializer{test.opt}))
-
-			if test.expected != nil {
-				select {
-				case e := <-ndpDisp.rdnssC:
-					if e.nicID != 1 {
-						t.Errorf("got rdnss nicID = %d, want = 1", e.nicID)
-					}
-					if diff := cmp.Diff(e.rdnss.addrs, test.expected.addrs); diff != "" {
-						t.Errorf("rdnss addrs mismatch (-want +got):\n%s", diff)
-					}
-					if e.rdnss.lifetime != test.expected.lifetime {
-						t.Errorf("got rdnss lifetime = %s, want = %s", e.rdnss.lifetime, test.expected.lifetime)
-					}
-				default:
-					t.Fatal("expected an RDNSS option event")
-				}
-			}
-
-			// Should have no more RDNSS options.
-			select {
-			case e := <-ndpDisp.rdnssC:
-				t.Fatalf("unexpectedly got a new RDNSS option event: %+v", e)
-			default:
-			}
-		})
-	}
-}
-
-// TestNDPDNSSearchListDispatch tests that the integrator is informed when an
-// NDP DNS Search List option is received with at least one domain name in the
-// search list.
-func TestNDPDNSSearchListDispatch(t *testing.T) {
-	const nicID = 1
-
-	ndpDisp := ndpDispatcher{
-		dnsslC: make(chan ndpDNSSLEvent, 3),
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	optSer := header.NDPOptionsSerializer{
-		header.NDPDNSSearchList([]byte{
-			0, 0,
-			0, 0, 0, 0,
-			2, 'h', 'i',
-			0,
-		}),
-		header.NDPDNSSearchList([]byte{
-			0, 0,
-			0, 0, 0, 1,
-			1, 'i',
-			0,
-			2, 'a', 'm',
-			2, 'm', 'e',
-			0,
-		}),
-		header.NDPDNSSearchList([]byte{
-			0, 0,
-			0, 0, 1, 0,
-			3, 'x', 'y', 'z',
-			0,
-			5, 'h', 'e', 'l', 'l', 'o',
-			5, 'w', 'o', 'r', 'l', 'd',
-			0,
-			4, 't', 'h', 'i', 's',
-			2, 'i', 's',
-			1, 'a',
-			4, 't', 'e', 's', 't',
-			0,
-		}),
-	}
-	expected := []struct {
-		domainNames []string
-		lifetime    time.Duration
-	}{
-		{
-			domainNames: []string{
-				"hi",
-			},
-			lifetime: 0,
-		},
-		{
-			domainNames: []string{
-				"i",
-				"am.me",
-			},
-			lifetime: time.Second,
-		},
-		{
-			domainNames: []string{
-				"xyz",
-				"hello.world",
-				"this.is.a.test",
-			},
-			lifetime: 256 * time.Second,
-		},
-	}
-
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, optSer))
-
-	for i, expected := range expected {
-		select {
-		case dnssl := <-ndpDisp.dnsslC:
-			if dnssl.nicID != nicID {
-				t.Errorf("got %d-th dnssl nicID = %d, want = %d", i, dnssl.nicID, nicID)
-			}
-			if diff := cmp.Diff(dnssl.domainNames, expected.domainNames); diff != "" {
-				t.Errorf("%d-th dnssl domain names mismatch (-want +got):\n%s", i, diff)
-			}
-			if dnssl.lifetime != expected.lifetime {
-				t.Errorf("got %d-th dnssl lifetime = %s, want = %s", i, dnssl.lifetime, expected.lifetime)
-			}
-		default:
-			t.Fatal("expected a DNSSL event")
-		}
-	}
-
-	// Should have no more DNSSL options.
-	select {
-	case <-ndpDisp.dnsslC:
-		t.Fatal("unexpectedly got a DNSSL event")
-	default:
-	}
-}
-
-// TestCleanupNDPState tests that all discovered routers and prefixes, and
-// auto-generated addresses are invalidated when a NIC becomes a router.
-func TestCleanupNDPState(t *testing.T) {
-	const (
-		lifetimeSeconds          = 5
-		maxRouterAndPrefixEvents = 4
-		nicID1                   = 1
-		nicID2                   = 2
-	)
-
-	prefix1, subnet1, e1Addr1 := prefixSubnetAddr(0, linkAddr1)
-	prefix2, subnet2, e1Addr2 := prefixSubnetAddr(1, linkAddr1)
-	e2Addr1 := addrForSubnet(subnet1, linkAddr2)
-	e2Addr2 := addrForSubnet(subnet2, linkAddr2)
-	llAddrWithPrefix1 := tcpip.AddressWithPrefix{
-		Address:   llAddr1,
-		PrefixLen: 64,
-	}
-	llAddrWithPrefix2 := tcpip.AddressWithPrefix{
-		Address:   llAddr2,
-		PrefixLen: 64,
-	}
-
-	tests := []struct {
-		name                 string
-		cleanupFn            func(t *testing.T, s *stack.Stack)
-		keepAutoGenLinkLocal bool
-		maxAutoGenAddrEvents int
-		skipFinalAddrCheck   bool
-	}{
-		// A NIC should still keep its auto-generated link-local address when
-		// becoming a router.
-		{
-			name: "Enable forwarding",
-			cleanupFn: func(t *testing.T, s *stack.Stack) {
-				t.Helper()
-				s.SetForwarding(ipv6.ProtocolNumber, true)
-			},
-			keepAutoGenLinkLocal: true,
-			maxAutoGenAddrEvents: 4,
-		},
-
-		// A NIC should cleanup all NDP state when it is disabled.
-		{
-			name: "Disable NIC",
-			cleanupFn: func(t *testing.T, s *stack.Stack) {
-				t.Helper()
-
-				if err := s.DisableNIC(nicID1); err != nil {
-					t.Fatalf("s.DisableNIC(%d): %s", nicID1, err)
-				}
-				if err := s.DisableNIC(nicID2); err != nil {
-					t.Fatalf("s.DisableNIC(%d): %s", nicID2, err)
-				}
-			},
-			keepAutoGenLinkLocal: false,
-			maxAutoGenAddrEvents: 6,
-		},
-
-		// A NIC should cleanup all NDP state when it is removed.
-		{
-			name: "Remove NIC",
-			cleanupFn: func(t *testing.T, s *stack.Stack) {
-				t.Helper()
-
-				if err := s.RemoveNIC(nicID1); err != nil {
-					t.Fatalf("s.RemoveNIC(%d): %s", nicID1, err)
-				}
-				if err := s.RemoveNIC(nicID2); err != nil {
-					t.Fatalf("s.RemoveNIC(%d): %s", nicID2, err)
-				}
-			},
-			keepAutoGenLinkLocal: false,
-			maxAutoGenAddrEvents: 6,
-			// The NICs are removed so we can't check their addresses after calling
-			// stopFn.
-			skipFinalAddrCheck: true,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				routerC:        make(chan ndpRouterEvent, maxRouterAndPrefixEvents),
-				rememberRouter: true,
-				prefixC:        make(chan ndpPrefixEvent, maxRouterAndPrefixEvents),
-				rememberPrefix: true,
-				autoGenAddrC:   make(chan ndpAutoGenAddrEvent, test.maxAutoGenAddrEvents),
-			}
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					AutoGenLinkLocal: true,
-					NDPConfigs: ipv6.NDPConfigurations{
-						HandleRAs:              true,
-						DiscoverDefaultRouters: true,
-						DiscoverOnLinkPrefixes: true,
-						AutoGenGlobalAddresses: true,
-					},
-					NDPDisp: &ndpDisp,
-				})},
-			})
-
-			expectRouterEvent := func() (bool, ndpRouterEvent) {
-				select {
-				case e := <-ndpDisp.routerC:
-					return true, e
-				default:
-				}
-
-				return false, ndpRouterEvent{}
-			}
-
-			expectPrefixEvent := func() (bool, ndpPrefixEvent) {
-				select {
-				case e := <-ndpDisp.prefixC:
-					return true, e
-				default:
-				}
-
-				return false, ndpPrefixEvent{}
-			}
-
-			expectAutoGenAddrEvent := func() (bool, ndpAutoGenAddrEvent) {
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					return true, e
-				default:
-				}
-
-				return false, ndpAutoGenAddrEvent{}
-			}
-
-			e1 := channel.New(0, 1280, linkAddr1)
-			if err := s.CreateNIC(nicID1, e1); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID1, err)
-			}
-			// We have other tests that make sure we receive the *correct* events
-			// on normal discovery of routers/prefixes, and auto-generated
-			// addresses. Here we just make sure we get an event and let other tests
-			// handle the correctness check.
-			expectAutoGenAddrEvent()
-
-			e2 := channel.New(0, 1280, linkAddr2)
-			if err := s.CreateNIC(nicID2, e2); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err)
-			}
-			expectAutoGenAddrEvent()
-
-			// Receive RAs on NIC(1) and NIC(2) from default routers (llAddr3 and
-			// llAddr4) w/ PI (for prefix1 in RA from llAddr3 and prefix2 in RA from
-			// llAddr4) to discover multiple routers and prefixes, and auto-gen
-			// multiple addresses.
-
-			e1.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, lifetimeSeconds, prefix1, true, true, lifetimeSeconds, lifetimeSeconds))
-			if ok, _ := expectRouterEvent(); !ok {
-				t.Errorf("expected router event for %s on NIC(%d)", llAddr3, nicID1)
-			}
-			if ok, _ := expectPrefixEvent(); !ok {
-				t.Errorf("expected prefix event for %s on NIC(%d)", prefix1, nicID1)
-			}
-			if ok, _ := expectAutoGenAddrEvent(); !ok {
-				t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr1, nicID1)
-			}
-
-			e1.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr4, lifetimeSeconds, prefix2, true, true, lifetimeSeconds, lifetimeSeconds))
-			if ok, _ := expectRouterEvent(); !ok {
-				t.Errorf("expected router event for %s on NIC(%d)", llAddr4, nicID1)
-			}
-			if ok, _ := expectPrefixEvent(); !ok {
-				t.Errorf("expected prefix event for %s on NIC(%d)", prefix2, nicID1)
-			}
-			if ok, _ := expectAutoGenAddrEvent(); !ok {
-				t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr2, nicID1)
-			}
-
-			e2.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, lifetimeSeconds, prefix1, true, true, lifetimeSeconds, lifetimeSeconds))
-			if ok, _ := expectRouterEvent(); !ok {
-				t.Errorf("expected router event for %s on NIC(%d)", llAddr3, nicID2)
-			}
-			if ok, _ := expectPrefixEvent(); !ok {
-				t.Errorf("expected prefix event for %s on NIC(%d)", prefix1, nicID2)
-			}
-			if ok, _ := expectAutoGenAddrEvent(); !ok {
-				t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr2, nicID2)
-			}
-
-			e2.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr4, lifetimeSeconds, prefix2, true, true, lifetimeSeconds, lifetimeSeconds))
-			if ok, _ := expectRouterEvent(); !ok {
-				t.Errorf("expected router event for %s on NIC(%d)", llAddr4, nicID2)
-			}
-			if ok, _ := expectPrefixEvent(); !ok {
-				t.Errorf("expected prefix event for %s on NIC(%d)", prefix2, nicID2)
-			}
-			if ok, _ := expectAutoGenAddrEvent(); !ok {
-				t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e2Addr2, nicID2)
-			}
-
-			// We should have the auto-generated addresses added.
-			nicinfo := s.NICInfo()
-			nic1Addrs := nicinfo[nicID1].ProtocolAddresses
-			nic2Addrs := nicinfo[nicID2].ProtocolAddresses
-			if !containsV6Addr(nic1Addrs, llAddrWithPrefix1) {
-				t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs)
-			}
-			if !containsV6Addr(nic1Addrs, e1Addr1) {
-				t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e1Addr1, nicID1, nic1Addrs)
-			}
-			if !containsV6Addr(nic1Addrs, e1Addr2) {
-				t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e1Addr2, nicID1, nic1Addrs)
-			}
-			if !containsV6Addr(nic2Addrs, llAddrWithPrefix2) {
-				t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs)
-			}
-			if !containsV6Addr(nic2Addrs, e2Addr1) {
-				t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e2Addr1, nicID2, nic2Addrs)
-			}
-			if !containsV6Addr(nic2Addrs, e2Addr2) {
-				t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e2Addr2, nicID2, nic2Addrs)
-			}
-
-			// We can't proceed any further if we already failed the test (missing
-			// some discovery/auto-generated address events or addresses).
-			if t.Failed() {
-				t.FailNow()
-			}
-
-			test.cleanupFn(t, s)
-
-			// Collect invalidation events after having NDP state cleaned up.
-			gotRouterEvents := make(map[ndpRouterEvent]int)
-			for i := 0; i < maxRouterAndPrefixEvents; i++ {
-				ok, e := expectRouterEvent()
-				if !ok {
-					t.Errorf("expected %d router events after becoming a router; got = %d", maxRouterAndPrefixEvents, i)
-					break
-				}
-				gotRouterEvents[e]++
-			}
-			gotPrefixEvents := make(map[ndpPrefixEvent]int)
-			for i := 0; i < maxRouterAndPrefixEvents; i++ {
-				ok, e := expectPrefixEvent()
-				if !ok {
-					t.Errorf("expected %d prefix events after becoming a router; got = %d", maxRouterAndPrefixEvents, i)
-					break
-				}
-				gotPrefixEvents[e]++
-			}
-			gotAutoGenAddrEvents := make(map[ndpAutoGenAddrEvent]int)
-			for i := 0; i < test.maxAutoGenAddrEvents; i++ {
-				ok, e := expectAutoGenAddrEvent()
-				if !ok {
-					t.Errorf("expected %d auto-generated address events after becoming a router; got = %d", test.maxAutoGenAddrEvents, i)
-					break
-				}
-				gotAutoGenAddrEvents[e]++
-			}
-
-			// No need to proceed any further if we already failed the test (missing
-			// some invalidation events).
-			if t.Failed() {
-				t.FailNow()
-			}
-
-			expectedRouterEvents := map[ndpRouterEvent]int{
-				{nicID: nicID1, addr: llAddr3, discovered: false}: 1,
-				{nicID: nicID1, addr: llAddr4, discovered: false}: 1,
-				{nicID: nicID2, addr: llAddr3, discovered: false}: 1,
-				{nicID: nicID2, addr: llAddr4, discovered: false}: 1,
-			}
-			if diff := cmp.Diff(expectedRouterEvents, gotRouterEvents); diff != "" {
-				t.Errorf("router events mismatch (-want +got):\n%s", diff)
-			}
-			expectedPrefixEvents := map[ndpPrefixEvent]int{
-				{nicID: nicID1, prefix: subnet1, discovered: false}: 1,
-				{nicID: nicID1, prefix: subnet2, discovered: false}: 1,
-				{nicID: nicID2, prefix: subnet1, discovered: false}: 1,
-				{nicID: nicID2, prefix: subnet2, discovered: false}: 1,
-			}
-			if diff := cmp.Diff(expectedPrefixEvents, gotPrefixEvents); diff != "" {
-				t.Errorf("prefix events mismatch (-want +got):\n%s", diff)
-			}
-			expectedAutoGenAddrEvents := map[ndpAutoGenAddrEvent]int{
-				{nicID: nicID1, addr: e1Addr1, eventType: invalidatedAddr}: 1,
-				{nicID: nicID1, addr: e1Addr2, eventType: invalidatedAddr}: 1,
-				{nicID: nicID2, addr: e2Addr1, eventType: invalidatedAddr}: 1,
-				{nicID: nicID2, addr: e2Addr2, eventType: invalidatedAddr}: 1,
-			}
-
-			if !test.keepAutoGenLinkLocal {
-				expectedAutoGenAddrEvents[ndpAutoGenAddrEvent{nicID: nicID1, addr: llAddrWithPrefix1, eventType: invalidatedAddr}] = 1
-				expectedAutoGenAddrEvents[ndpAutoGenAddrEvent{nicID: nicID2, addr: llAddrWithPrefix2, eventType: invalidatedAddr}] = 1
-			}
-
-			if diff := cmp.Diff(expectedAutoGenAddrEvents, gotAutoGenAddrEvents); diff != "" {
-				t.Errorf("auto-generated address events mismatch (-want +got):\n%s", diff)
-			}
-
-			if !test.skipFinalAddrCheck {
-				// Make sure the auto-generated addresses got removed.
-				nicinfo = s.NICInfo()
-				nic1Addrs = nicinfo[nicID1].ProtocolAddresses
-				nic2Addrs = nicinfo[nicID2].ProtocolAddresses
-				if containsV6Addr(nic1Addrs, llAddrWithPrefix1) != test.keepAutoGenLinkLocal {
-					if test.keepAutoGenLinkLocal {
-						t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs)
-					} else {
-						t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs)
-					}
-				}
-				if containsV6Addr(nic1Addrs, e1Addr1) {
-					t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e1Addr1, nicID1, nic1Addrs)
-				}
-				if containsV6Addr(nic1Addrs, e1Addr2) {
-					t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e1Addr2, nicID1, nic1Addrs)
-				}
-				if containsV6Addr(nic2Addrs, llAddrWithPrefix2) != test.keepAutoGenLinkLocal {
-					if test.keepAutoGenLinkLocal {
-						t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs)
-					} else {
-						t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs)
-					}
-				}
-				if containsV6Addr(nic2Addrs, e2Addr1) {
-					t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e2Addr1, nicID2, nic2Addrs)
-				}
-				if containsV6Addr(nic2Addrs, e2Addr2) {
-					t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e2Addr2, nicID2, nic2Addrs)
-				}
-			}
-
-			// Should not get any more events (invalidation timers should have been
-			// cancelled when the NDP state was cleaned up).
-			time.Sleep(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout)
-			select {
-			case <-ndpDisp.routerC:
-				t.Error("unexpected router event")
-			default:
-			}
-			select {
-			case <-ndpDisp.prefixC:
-				t.Error("unexpected prefix event")
-			default:
-			}
-			select {
-			case <-ndpDisp.autoGenAddrC:
-				t.Error("unexpected auto-generated address event")
-			default:
-			}
-		})
-	}
-}
-
-// TestDHCPv6ConfigurationFromNDPDA tests that the NDPDispatcher is properly
-// informed when new information about what configurations are available via
-// DHCPv6 is learned.
-func TestDHCPv6ConfigurationFromNDPDA(t *testing.T) {
-	const nicID = 1
-
-	ndpDisp := ndpDispatcher{
-		dhcpv6ConfigurationC: make(chan ndpDHCPv6Event, 1),
-		rememberRouter:       true,
-	}
-	e := channel.New(0, 1280, linkAddr1)
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			NDPConfigs: ipv6.NDPConfigurations{
-				HandleRAs: true,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	})
-
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	expectDHCPv6Event := func(configuration ipv6.DHCPv6ConfigurationFromNDPRA) {
-		t.Helper()
-		select {
-		case e := <-ndpDisp.dhcpv6ConfigurationC:
-			if diff := cmp.Diff(ndpDHCPv6Event{nicID: nicID, configuration: configuration}, e, cmp.AllowUnexported(e)); diff != "" {
-				t.Errorf("dhcpv6 event mismatch (-want +got):\n%s", diff)
-			}
-		default:
-			t.Fatal("expected DHCPv6 configuration event")
-		}
-	}
-
-	expectNoDHCPv6Event := func() {
-		t.Helper()
-		select {
-		case <-ndpDisp.dhcpv6ConfigurationC:
-			t.Fatal("unexpected DHCPv6 configuration event")
-		default:
-		}
-	}
-
-	// Even if the first RA reports no DHCPv6 configurations are available, the
-	// dispatcher should get an event.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false))
-	expectDHCPv6Event(ipv6.DHCPv6NoConfiguration)
-	// Receiving the same update again should not result in an event to the
-	// dispatcher.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false))
-	expectNoDHCPv6Event()
-
-	// Receive an RA that updates the DHCPv6 configuration to Other
-	// Configurations.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
-	expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
-	expectNoDHCPv6Event()
-
-	// Receive an RA that updates the DHCPv6 configuration to Managed Address.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false))
-	expectDHCPv6Event(ipv6.DHCPv6ManagedAddress)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false))
-	expectNoDHCPv6Event()
-
-	// Receive an RA that updates the DHCPv6 configuration to none.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false))
-	expectDHCPv6Event(ipv6.DHCPv6NoConfiguration)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false))
-	expectNoDHCPv6Event()
-
-	// Receive an RA that updates the DHCPv6 configuration to Managed Address.
-	//
-	// Note, when the M flag is set, the O flag is redundant.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true))
-	expectDHCPv6Event(ipv6.DHCPv6ManagedAddress)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true))
-	expectNoDHCPv6Event()
-	// Even though the DHCPv6 flags are different, the effective configuration is
-	// the same so we should not receive a new event.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false))
-	expectNoDHCPv6Event()
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true))
-	expectNoDHCPv6Event()
-
-	// Receive an RA that updates the DHCPv6 configuration to Other
-	// Configurations.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
-	expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
-	expectNoDHCPv6Event()
-
-	// Cycling the NIC should cause the last DHCPv6 configuration to be cleared.
-	if err := s.DisableNIC(nicID); err != nil {
-		t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-	}
-	if err := s.EnableNIC(nicID); err != nil {
-		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-	}
-
-	// Receive an RA that updates the DHCPv6 configuration to Other
-	// Configurations.
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
-	expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations)
-	e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true))
-	expectNoDHCPv6Event()
-}
-
-// TestRouterSolicitation tests the initial Router Solicitations that are sent
-// when a NIC newly becomes enabled.
-func TestRouterSolicitation(t *testing.T) {
-	const nicID = 1
-
-	tests := []struct {
-		name                        string
-		linkHeaderLen               uint16
-		linkAddr                    tcpip.LinkAddress
-		nicAddr                     tcpip.Address
-		expectedSrcAddr             tcpip.Address
-		expectedNDPOpts             []header.NDPOption
-		maxRtrSolicit               uint8
-		rtrSolicitInt               time.Duration
-		effectiveRtrSolicitInt      time.Duration
-		maxRtrSolicitDelay          time.Duration
-		effectiveMaxRtrSolicitDelay time.Duration
-	}{
-		{
-			name:                        "Single RS with 2s delay and interval",
-			expectedSrcAddr:             header.IPv6Any,
-			maxRtrSolicit:               1,
-			rtrSolicitInt:               2 * time.Second,
-			effectiveRtrSolicitInt:      2 * time.Second,
-			maxRtrSolicitDelay:          2 * time.Second,
-			effectiveMaxRtrSolicitDelay: 2 * time.Second,
-		},
-		{
-			name:                        "Single RS with 4s delay and interval",
-			expectedSrcAddr:             header.IPv6Any,
-			maxRtrSolicit:               1,
-			rtrSolicitInt:               4 * time.Second,
-			effectiveRtrSolicitInt:      4 * time.Second,
-			maxRtrSolicitDelay:          4 * time.Second,
-			effectiveMaxRtrSolicitDelay: 4 * time.Second,
-		},
-		{
-			name:                        "Two RS with delay",
-			linkHeaderLen:               1,
-			nicAddr:                     llAddr1,
-			expectedSrcAddr:             llAddr1,
-			maxRtrSolicit:               2,
-			rtrSolicitInt:               2 * time.Second,
-			effectiveRtrSolicitInt:      2 * time.Second,
-			maxRtrSolicitDelay:          500 * time.Millisecond,
-			effectiveMaxRtrSolicitDelay: 500 * time.Millisecond,
-		},
-		{
-			name:            "Single RS without delay",
-			linkHeaderLen:   2,
-			linkAddr:        linkAddr1,
-			nicAddr:         llAddr1,
-			expectedSrcAddr: llAddr1,
-			expectedNDPOpts: []header.NDPOption{
-				header.NDPSourceLinkLayerAddressOption(linkAddr1),
-			},
-			maxRtrSolicit:               1,
-			rtrSolicitInt:               2 * time.Second,
-			effectiveRtrSolicitInt:      2 * time.Second,
-			maxRtrSolicitDelay:          0,
-			effectiveMaxRtrSolicitDelay: 0,
-		},
-		{
-			name:                        "Two RS without delay and invalid zero interval",
-			linkHeaderLen:               3,
-			linkAddr:                    linkAddr1,
-			expectedSrcAddr:             header.IPv6Any,
-			maxRtrSolicit:               2,
-			rtrSolicitInt:               0,
-			effectiveRtrSolicitInt:      4 * time.Second,
-			maxRtrSolicitDelay:          0,
-			effectiveMaxRtrSolicitDelay: 0,
-		},
-		{
-			name:                        "Three RS without delay",
-			linkAddr:                    linkAddr1,
-			expectedSrcAddr:             header.IPv6Any,
-			maxRtrSolicit:               3,
-			rtrSolicitInt:               500 * time.Millisecond,
-			effectiveRtrSolicitInt:      500 * time.Millisecond,
-			maxRtrSolicitDelay:          0,
-			effectiveMaxRtrSolicitDelay: 0,
-		},
-		{
-			name:                        "Two RS with invalid negative delay",
-			linkAddr:                    linkAddr1,
-			expectedSrcAddr:             header.IPv6Any,
-			maxRtrSolicit:               2,
-			rtrSolicitInt:               time.Second,
-			effectiveRtrSolicitInt:      time.Second,
-			maxRtrSolicitDelay:          -3 * time.Second,
-			effectiveMaxRtrSolicitDelay: time.Second,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			clock := faketime.NewManualClock()
-			e := channelLinkWithHeaderLength{
-				Endpoint:     channel.New(int(test.maxRtrSolicit), 1280, test.linkAddr),
-				headerLength: test.linkHeaderLen,
-			}
-			e.Endpoint.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-			waitForPkt := func(timeout time.Duration) {
-				t.Helper()
-
-				clock.Advance(timeout)
-				p, ok := e.Read()
-				if !ok {
-					t.Fatal("expected router solicitation packet")
-				}
-
-				if p.Proto != header.IPv6ProtocolNumber {
-					t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber)
-				}
-
-				// Make sure the right remote link address is used.
-				if want := header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersMulticastAddress); p.Route.RemoteLinkAddress != want {
-					t.Errorf("got remote link address = %s, want = %s", p.Route.RemoteLinkAddress, want)
-				}
-
-				checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
-					checker.SrcAddr(test.expectedSrcAddr),
-					checker.DstAddr(header.IPv6AllRoutersMulticastAddress),
-					checker.TTL(header.NDPHopLimit),
-					checker.NDPRS(checker.NDPRSOptions(test.expectedNDPOpts)),
-				)
-
-				if l, want := p.Pkt.AvailableHeaderBytes(), int(test.linkHeaderLen); l != want {
-					t.Errorf("got p.Pkt.AvailableHeaderBytes() = %d; want = %d", l, want)
-				}
-			}
-			waitForNothing := func(timeout time.Duration) {
-				t.Helper()
-
-				clock.Advance(timeout)
-				if p, ok := e.Read(); ok {
-					t.Fatalf("unexpectedly got a packet = %#v", p)
-				}
-			}
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPConfigs: ipv6.NDPConfigurations{
-						MaxRtrSolicitations:     test.maxRtrSolicit,
-						RtrSolicitationInterval: test.rtrSolicitInt,
-						MaxRtrSolicitationDelay: test.maxRtrSolicitDelay,
-					},
-				})},
-				Clock: clock,
-			})
-			if err := s.CreateNIC(nicID, &e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			if addr := test.nicAddr; addr != "" {
-				if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr); err != nil {
-					t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr, err)
-				}
-			}
-
-			// Make sure each RS is sent at the right time.
-			remaining := test.maxRtrSolicit
-			if remaining > 0 {
-				waitForPkt(test.effectiveMaxRtrSolicitDelay)
-				remaining--
-			}
-
-			for ; remaining > 0; remaining-- {
-				if test.effectiveRtrSolicitInt > defaultAsyncPositiveEventTimeout {
-					waitForNothing(test.effectiveRtrSolicitInt - time.Nanosecond)
-					waitForPkt(time.Nanosecond)
-				} else {
-					waitForPkt(test.effectiveRtrSolicitInt)
-				}
-			}
-
-			// Make sure no more RS.
-			if test.effectiveRtrSolicitInt > test.effectiveMaxRtrSolicitDelay {
-				waitForNothing(test.effectiveRtrSolicitInt)
-			} else {
-				waitForNothing(test.effectiveMaxRtrSolicitDelay)
-			}
-
-			if got, want := s.Stats().ICMP.V6.PacketsSent.RouterSolicit.Value(), uint64(test.maxRtrSolicit); got != want {
-				t.Fatalf("got sent RouterSolicit = %d, want = %d", got, want)
-			}
-		})
-	}
-}
-
-func TestStopStartSolicitingRouters(t *testing.T) {
-	const nicID = 1
-	const delay = 0
-	const interval = 500 * time.Millisecond
-	const maxRtrSolicitations = 3
-
-	tests := []struct {
-		name    string
-		startFn func(t *testing.T, s *stack.Stack)
-		// first is used to tell stopFn that it is being called for the first time
-		// after router solicitations were last enabled.
-		stopFn func(t *testing.T, s *stack.Stack, first bool)
-	}{
-		// Tests that when forwarding is enabled or disabled, router solicitations
-		// are stopped or started, respectively.
-		{
-			name: "Enable and disable forwarding",
-			startFn: func(t *testing.T, s *stack.Stack) {
-				t.Helper()
-				s.SetForwarding(ipv6.ProtocolNumber, false)
-			},
-			stopFn: func(t *testing.T, s *stack.Stack, _ bool) {
-				t.Helper()
-				s.SetForwarding(ipv6.ProtocolNumber, true)
-			},
-		},
-
-		// Tests that when a NIC is enabled or disabled, router solicitations
-		// are started or stopped, respectively.
-		{
-			name: "Enable and disable NIC",
-			startFn: func(t *testing.T, s *stack.Stack) {
-				t.Helper()
-
-				if err := s.EnableNIC(nicID); err != nil {
-					t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-				}
-			},
-			stopFn: func(t *testing.T, s *stack.Stack, _ bool) {
-				t.Helper()
-
-				if err := s.DisableNIC(nicID); err != nil {
-					t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-				}
-			},
-		},
-
-		// Tests that when a NIC is removed, router solicitations are stopped. We
-		// cannot start router solications on a removed NIC.
-		{
-			name: "Remove NIC",
-			stopFn: func(t *testing.T, s *stack.Stack, first bool) {
-				t.Helper()
-
-				// Only try to remove the NIC the first time stopFn is called since it's
-				// impossible to remove an already removed NIC.
-				if !first {
-					return
-				}
-
-				if err := s.RemoveNIC(nicID); err != nil {
-					t.Fatalf("s.RemoveNIC(%d): %s", nicID, err)
-				}
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			e := channel.New(maxRtrSolicitations, 1280, linkAddr1)
-			waitForPkt := func(timeout time.Duration) {
-				t.Helper()
-
-				ctx, cancel := context.WithTimeout(context.Background(), timeout)
-				defer cancel()
-				p, ok := e.ReadContext(ctx)
-				if !ok {
-					t.Fatal("timed out waiting for packet")
-				}
-
-				if p.Proto != header.IPv6ProtocolNumber {
-					t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber)
-				}
-				checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
-					checker.SrcAddr(header.IPv6Any),
-					checker.DstAddr(header.IPv6AllRoutersMulticastAddress),
-					checker.TTL(header.NDPHopLimit),
-					checker.NDPRS())
-			}
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPConfigs: ipv6.NDPConfigurations{
-						MaxRtrSolicitations:     maxRtrSolicitations,
-						RtrSolicitationInterval: interval,
-						MaxRtrSolicitationDelay: delay,
-					},
-				})},
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			// Stop soliciting routers.
-			test.stopFn(t, s, true /* first */)
-			ctx, cancel := context.WithTimeout(context.Background(), delay+defaultAsyncNegativeEventTimeout)
-			defer cancel()
-			if _, ok := e.ReadContext(ctx); ok {
-				// A single RS may have been sent before solicitations were stopped.
-				ctx, cancel := context.WithTimeout(context.Background(), interval+defaultAsyncNegativeEventTimeout)
-				defer cancel()
-				if _, ok = e.ReadContext(ctx); ok {
-					t.Fatal("should not have sent more than one RS message")
-				}
-			}
-
-			// Stopping router solicitations after it has already been stopped should
-			// do nothing.
-			test.stopFn(t, s, false /* first */)
-			ctx, cancel = context.WithTimeout(context.Background(), delay+defaultAsyncNegativeEventTimeout)
-			defer cancel()
-			if _, ok := e.ReadContext(ctx); ok {
-				t.Fatal("unexpectedly got a packet after router solicitation has been stopepd")
-			}
-
-			// If test.startFn is nil, there is no way to restart router solications.
-			if test.startFn == nil {
-				return
-			}
-
-			// Start soliciting routers.
-			test.startFn(t, s)
-			waitForPkt(delay + defaultAsyncPositiveEventTimeout)
-			waitForPkt(interval + defaultAsyncPositiveEventTimeout)
-			waitForPkt(interval + defaultAsyncPositiveEventTimeout)
-			ctx, cancel = context.WithTimeout(context.Background(), interval+defaultAsyncNegativeEventTimeout)
-			defer cancel()
-			if _, ok := e.ReadContext(ctx); ok {
-				t.Fatal("unexpectedly got an extra packet after sending out the expected RSs")
-			}
-
-			// Starting router solicitations after it has already completed should do
-			// nothing.
-			test.startFn(t, s)
-			ctx, cancel = context.WithTimeout(context.Background(), delay+defaultAsyncNegativeEventTimeout)
-			defer cancel()
-			if _, ok := e.ReadContext(ctx); ok {
-				t.Fatal("unexpectedly got a packet after finishing router solicitations")
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/stack/neighbor_cache_test.go b/pkg/tcpip/stack/neighbor_cache_test.go
deleted file mode 100644
index 48bb75e..0000000
--- a/pkg/tcpip/stack/neighbor_cache_test.go
+++ /dev/null
@@ -1,1592 +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 stack
-
-import (
-	"bytes"
-	"encoding/binary"
-	"fmt"
-	"math"
-	"math/rand"
-	"strings"
-	"sync"
-	"sync/atomic"
-	"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/faketime"
-)
-
-const (
-	// entryStoreSize is the default number of entries that will be generated and
-	// added to the entry store. This number needs to be larger than the size of
-	// the neighbor cache to give ample opportunity for verifying behavior during
-	// cache overflows. Four times the size of the neighbor cache allows for
-	// three complete cache overflows.
-	entryStoreSize = 4 * neighborCacheSize
-
-	// typicalLatency is the typical latency for an ARP or NDP packet to travel
-	// to a router and back.
-	typicalLatency = time.Millisecond
-
-	// testEntryBroadcastAddr is a special address that indicates a packet should
-	// be sent to all nodes.
-	testEntryBroadcastAddr = tcpip.Address("broadcast")
-
-	// testEntryLocalAddr is the source address of neighbor probes.
-	testEntryLocalAddr = tcpip.Address("local_addr")
-
-	// testEntryBroadcastLinkAddr is a special link address sent back to
-	// multicast neighbor probes.
-	testEntryBroadcastLinkAddr = tcpip.LinkAddress("mac_broadcast")
-
-	// infiniteDuration indicates that a task will not occur in our lifetime.
-	infiniteDuration = time.Duration(math.MaxInt64)
-)
-
-// unorderedEventsDiffOpts returns options passed to cmp.Diff to sort slices of
-// events for cases where ordering must be ignored.
-func unorderedEventsDiffOpts() []cmp.Option {
-	return []cmp.Option{
-		cmpopts.SortSlices(func(a, b testEntryEventInfo) bool {
-			return strings.Compare(string(a.Entry.Addr), string(b.Entry.Addr)) < 0
-		}),
-	}
-}
-
-// unorderedEntriesDiffOpts returns options passed to cmp.Diff to sort slices of
-// entries for cases where ordering must be ignored.
-func unorderedEntriesDiffOpts() []cmp.Option {
-	return []cmp.Option{
-		cmpopts.SortSlices(func(a, b NeighborEntry) bool {
-			return strings.Compare(string(a.Addr), string(b.Addr)) < 0
-		}),
-	}
-}
-
-func newTestNeighborResolver(nudDisp NUDDispatcher, config NUDConfigurations, clock tcpip.Clock) *testNeighborResolver {
-	config.resetInvalidFields()
-	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
-	linkRes := &testNeighborResolver{
-		clock:   clock,
-		entries: newTestEntryStore(),
-		delay:   typicalLatency,
-	}
-	linkRes.neigh.init(&nic{
-		stack: &Stack{
-			clock:           clock,
-			nudDisp:         nudDisp,
-			nudConfigs:      config,
-			randomGenerator: rng,
-		},
-		id:    1,
-		stats: makeNICStats(),
-	}, linkRes)
-	return linkRes
-}
-
-// testEntryStore contains a set of IP to NeighborEntry mappings.
-type testEntryStore struct {
-	mu         sync.RWMutex
-	entriesMap map[tcpip.Address]NeighborEntry
-}
-
-func toAddress(i int) tcpip.Address {
-	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.BigEndian, uint8(1))
-	binary.Write(buf, binary.BigEndian, uint8(0))
-	binary.Write(buf, binary.BigEndian, uint16(i))
-	return tcpip.Address(buf.String())
-}
-
-func toLinkAddress(i int) tcpip.LinkAddress {
-	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.BigEndian, uint8(1))
-	binary.Write(buf, binary.BigEndian, uint8(0))
-	binary.Write(buf, binary.BigEndian, uint32(i))
-	return tcpip.LinkAddress(buf.String())
-}
-
-// newTestEntryStore returns a testEntryStore pre-populated with entries.
-func newTestEntryStore() *testEntryStore {
-	store := &testEntryStore{
-		entriesMap: make(map[tcpip.Address]NeighborEntry),
-	}
-	for i := 0; i < entryStoreSize; i++ {
-		addr := toAddress(i)
-		linkAddr := toLinkAddress(i)
-
-		store.entriesMap[addr] = NeighborEntry{
-			Addr:     addr,
-			LinkAddr: linkAddr,
-		}
-	}
-	return store
-}
-
-// size returns the number of entries in the store.
-func (s *testEntryStore) size() int {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-	return len(s.entriesMap)
-}
-
-// entry returns the entry at index i. Returns an empty entry and false if i is
-// out of bounds.
-func (s *testEntryStore) entry(i int) (NeighborEntry, bool) {
-	return s.entryByAddr(toAddress(i))
-}
-
-// entryByAddr returns the entry matching addr for situations when the index is
-// not available. Returns an empty entry and false if no entries match addr.
-func (s *testEntryStore) entryByAddr(addr tcpip.Address) (NeighborEntry, bool) {
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-	entry, ok := s.entriesMap[addr]
-	return entry, ok
-}
-
-// entries returns all entries in the store.
-func (s *testEntryStore) entries() []NeighborEntry {
-	entries := make([]NeighborEntry, 0, len(s.entriesMap))
-	s.mu.RLock()
-	defer s.mu.RUnlock()
-	for i := 0; i < entryStoreSize; i++ {
-		addr := toAddress(i)
-		if entry, ok := s.entriesMap[addr]; ok {
-			entries = append(entries, entry)
-		}
-	}
-	return entries
-}
-
-// set modifies the link addresses of an entry.
-func (s *testEntryStore) set(i int, linkAddr tcpip.LinkAddress) {
-	addr := toAddress(i)
-	s.mu.Lock()
-	defer s.mu.Unlock()
-	if entry, ok := s.entriesMap[addr]; ok {
-		entry.LinkAddr = linkAddr
-		s.entriesMap[addr] = entry
-	}
-}
-
-// testNeighborResolver implements LinkAddressResolver to emulate sending a
-// neighbor probe.
-type testNeighborResolver struct {
-	clock                tcpip.Clock
-	neigh                neighborCache
-	entries              *testEntryStore
-	delay                time.Duration
-	onLinkAddressRequest func()
-	dropReplies          bool
-}
-
-var _ LinkAddressResolver = (*testNeighborResolver)(nil)
-
-func (r *testNeighborResolver) LinkAddressRequest(targetAddr, _ tcpip.Address, _ tcpip.LinkAddress) tcpip.Error {
-	if !r.dropReplies {
-		// Delay handling the request to emulate network latency.
-		r.clock.AfterFunc(r.delay, func() {
-			r.fakeRequest(targetAddr)
-		})
-	}
-
-	// Execute post address resolution action, if available.
-	if f := r.onLinkAddressRequest; f != nil {
-		f()
-	}
-	return nil
-}
-
-// fakeRequest emulates handling a response for a link address request.
-func (r *testNeighborResolver) fakeRequest(addr tcpip.Address) {
-	if entry, ok := r.entries.entryByAddr(addr); ok {
-		r.neigh.handleConfirmation(addr, entry.LinkAddr, ReachabilityConfirmationFlags{
-			Solicited: true,
-			Override:  false,
-			IsRouter:  false,
-		})
-	}
-}
-
-func (*testNeighborResolver) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) {
-	if addr == testEntryBroadcastAddr {
-		return testEntryBroadcastLinkAddr, true
-	}
-	return "", false
-}
-
-func (*testNeighborResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
-	return 0
-}
-
-type entryEvent struct {
-	nicID    tcpip.NICID
-	address  tcpip.Address
-	linkAddr tcpip.LinkAddress
-	state    NeighborState
-}
-
-func TestNeighborCacheGetConfig(t *testing.T) {
-	nudDisp := testNUDDispatcher{}
-	c := DefaultNUDConfigurations()
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, c, clock)
-
-	if got, want := linkRes.neigh.config(), c; got != want {
-		t.Errorf("got linkRes.neigh.config() = %+v, want = %+v", got, want)
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	defer nudDisp.mu.Unlock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-func TestNeighborCacheSetConfig(t *testing.T) {
-	nudDisp := testNUDDispatcher{}
-	c := DefaultNUDConfigurations()
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, c, clock)
-
-	c.MinRandomFactor = 1
-	c.MaxRandomFactor = 1
-	linkRes.neigh.setConfig(c)
-
-	if got, want := linkRes.neigh.config(), c; got != want {
-		t.Errorf("got linkRes.neigh.config() = %+v, want = %+v", got, want)
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	defer nudDisp.mu.Unlock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-func addReachableEntryWithRemoved(nudDisp *testNUDDispatcher, clock *faketime.ManualClock, linkRes *testNeighborResolver, entry NeighborEntry, removed []NeighborEntry) error {
-	var gotLinkResolutionResult LinkResolutionResult
-
-	_, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
-		gotLinkResolutionResult = r
-	})
-	if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-		return fmt.Errorf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
-	}
-
-	{
-		var wantEvents []testEntryEventInfo
-
-		for _, removedEntry := range removed {
-			wantEvents = append(wantEvents, testEntryEventInfo{
-				EventType: entryTestRemoved,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           removedEntry.Addr,
-					LinkAddr:       removedEntry.LinkAddr,
-					State:          Reachable,
-					UpdatedAtNanos: clock.NowNanoseconds(),
-				},
-			})
-		}
-
-		wantEvents = append(wantEvents, testEntryEventInfo{
-			EventType: entryTestAdded,
-			NICID:     1,
-			Entry: NeighborEntry{
-				Addr:           entry.Addr,
-				LinkAddr:       "",
-				State:          Incomplete,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		})
-
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	clock.Advance(typicalLatency)
-
-	select {
-	case <-ch:
-	default:
-		return fmt.Errorf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
-	}
-	wantLinkResolutionResult := LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil}
-	if diff := cmp.Diff(wantLinkResolutionResult, gotLinkResolutionResult); diff != "" {
-		return fmt.Errorf("got link resolution result mismatch (-want +got):\n%s", diff)
-	}
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestChanged,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       entry.LinkAddr,
-					State:          Reachable,
-					UpdatedAtNanos: clock.NowNanoseconds(),
-				},
-			},
-		}
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	return nil
-}
-
-func addReachableEntry(nudDisp *testNUDDispatcher, clock *faketime.ManualClock, linkRes *testNeighborResolver, entry NeighborEntry) error {
-	return addReachableEntryWithRemoved(nudDisp, clock, linkRes, entry, nil /* removed */)
-}
-
-func TestNeighborCacheEntry(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	nudDisp := testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, c, clock)
-
-	entry, ok := linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
-		t.Fatalf("addReachableEntry(...) = %s", err)
-	}
-
-	if _, _, err := linkRes.neigh.entry(entry.Addr, "", nil); err != nil {
-		t.Fatalf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err)
-	}
-
-	// No more events should have been dispatched.
-	nudDisp.mu.Lock()
-	defer nudDisp.mu.Unlock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-func TestNeighborCacheRemoveEntry(t *testing.T) {
-	config := DefaultNUDConfigurations()
-
-	nudDisp := testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
-	entry, ok := linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
-		t.Fatalf("addReachableEntry(...) = %s", err)
-	}
-
-	linkRes.neigh.removeEntry(entry.Addr)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestRemoved,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       entry.LinkAddr,
-					State:          Reachable,
-					UpdatedAtNanos: clock.NowNanoseconds(),
-				},
-			},
-		}
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	{
-		_, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
-		if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-			t.Errorf("got linkRes.neigh.entry(%s, '', nil) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
-		}
-	}
-}
-
-type testContext struct {
-	clock   *faketime.ManualClock
-	linkRes *testNeighborResolver
-	nudDisp *testNUDDispatcher
-}
-
-func newTestContext(c NUDConfigurations) testContext {
-	nudDisp := &testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(nudDisp, c, clock)
-
-	return testContext{
-		clock:   clock,
-		linkRes: linkRes,
-		nudDisp: nudDisp,
-	}
-}
-
-type overflowOptions struct {
-	startAtEntryIndex int
-	wantStaticEntries []NeighborEntry
-}
-
-func (c *testContext) overflowCache(opts overflowOptions) error {
-	// Fill the neighbor cache to capacity to verify the LRU eviction strategy is
-	// working properly after the entry removal.
-	for i := opts.startAtEntryIndex; i < c.linkRes.entries.size(); i++ {
-		var removedEntries []NeighborEntry
-
-		// When beyond the full capacity, the cache will evict an entry as per the
-		// LRU eviction strategy. Note that the number of static entries should not
-		// affect the total number of dynamic entries that can be added.
-		if i >= neighborCacheSize+opts.startAtEntryIndex {
-			removedEntry, ok := c.linkRes.entries.entry(i - neighborCacheSize)
-			if !ok {
-				return fmt.Errorf("got linkRes.entries.entry(%d) = _, false, want = true", i-neighborCacheSize)
-			}
-			removedEntries = append(removedEntries, removedEntry)
-		}
-
-		entry, ok := c.linkRes.entries.entry(i)
-		if !ok {
-			return fmt.Errorf("got c.linkRes.entries.entry(%d) = _, false, want = true", i)
-		}
-		if err := addReachableEntryWithRemoved(c.nudDisp, c.clock, c.linkRes, entry, removedEntries); err != nil {
-			return fmt.Errorf("addReachableEntryWithRemoved(...) = %s", err)
-		}
-	}
-
-	// Expect to find only the most recent entries. The order of entries reported
-	// by entries() is nondeterministic, so entries have to be sorted before
-	// comparison.
-	wantUnorderedEntries := opts.wantStaticEntries
-	for i := c.linkRes.entries.size() - neighborCacheSize; i < c.linkRes.entries.size(); i++ {
-		entry, ok := c.linkRes.entries.entry(i)
-		if !ok {
-			return fmt.Errorf("got c.linkRes.entries.entry(%d) = _, false, want = true", i)
-		}
-		durationReachableNanos := int64(c.linkRes.entries.size()-i-1) * typicalLatency.Nanoseconds()
-		wantEntry := NeighborEntry{
-			Addr:           entry.Addr,
-			LinkAddr:       entry.LinkAddr,
-			State:          Reachable,
-			UpdatedAtNanos: c.clock.NowNanoseconds() - durationReachableNanos,
-		}
-		wantUnorderedEntries = append(wantUnorderedEntries, wantEntry)
-	}
-
-	if diff := cmp.Diff(wantUnorderedEntries, c.linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" {
-		return fmt.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff)
-	}
-
-	// No more events should have been dispatched.
-	c.nudDisp.mu.Lock()
-	defer c.nudDisp.mu.Unlock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), c.nudDisp.mu.events); diff != "" {
-		return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-
-	return nil
-}
-
-// TestNeighborCacheOverflow verifies that the LRU cache eviction strategy
-// respects the dynamic entry count.
-func TestNeighborCacheOverflow(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	// Stay in Reachable so the cache can overflow
-	config.BaseReachableTime = infiniteDuration
-	config.MinRandomFactor = 1
-	config.MaxRandomFactor = 1
-
-	c := newTestContext(config)
-	opts := overflowOptions{
-		startAtEntryIndex: 0,
-	}
-	if err := c.overflowCache(opts); err != nil {
-		t.Errorf("c.overflowCache(%+v): %s", opts, err)
-	}
-}
-
-// TestNeighborCacheRemoveEntryThenOverflow verifies that the LRU cache
-// eviction strategy respects the dynamic entry count when an entry is removed.
-func TestNeighborCacheRemoveEntryThenOverflow(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	// Stay in Reachable so the cache can overflow
-	config.BaseReachableTime = infiniteDuration
-	config.MinRandomFactor = 1
-	config.MaxRandomFactor = 1
-
-	c := newTestContext(config)
-
-	// Add a dynamic entry
-	entry, ok := c.linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil {
-		t.Fatalf("addReachableEntry(...) = %s", err)
-	}
-
-	// Remove the entry
-	c.linkRes.neigh.removeEntry(entry.Addr)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestRemoved,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       entry.LinkAddr,
-					State:          Reachable,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	opts := overflowOptions{
-		startAtEntryIndex: 0,
-	}
-	if err := c.overflowCache(opts); err != nil {
-		t.Errorf("c.overflowCache(%+v): %s", opts, err)
-	}
-}
-
-// TestNeighborCacheDuplicateStaticEntryWithSameLinkAddress verifies that
-// adding a duplicate static entry with the same link address does not dispatch
-// any events.
-func TestNeighborCacheDuplicateStaticEntryWithSameLinkAddress(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	c := newTestContext(config)
-
-	// Add a static entry
-	entry, ok := c.linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	staticLinkAddr := entry.LinkAddr + "static"
-	c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestAdded,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       staticLinkAddr,
-					State:          Static,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// Add a duplicate static entry with the same link address.
-	c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
-	c.nudDisp.mu.Lock()
-	defer c.nudDisp.mu.Unlock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), c.nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-// TestNeighborCacheDuplicateStaticEntryWithDifferentLinkAddress verifies that
-// adding a duplicate static entry with a different link address dispatches a
-// change event.
-func TestNeighborCacheDuplicateStaticEntryWithDifferentLinkAddress(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	c := newTestContext(config)
-
-	// Add a static entry
-	entry, ok := c.linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	staticLinkAddr := entry.LinkAddr + "static"
-	c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestAdded,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       staticLinkAddr,
-					State:          Static,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// Add a duplicate entry with a different link address
-	staticLinkAddr += "duplicate"
-	c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestChanged,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       staticLinkAddr,
-					State:          Static,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-}
-
-// TestNeighborCacheRemoveStaticEntryThenOverflow verifies that the LRU cache
-// eviction strategy respects the dynamic entry count when a static entry is
-// added then removed. In this case, the dynamic entry count shouldn't have
-// been touched.
-func TestNeighborCacheRemoveStaticEntryThenOverflow(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	// Stay in Reachable so the cache can overflow
-	config.BaseReachableTime = infiniteDuration
-	config.MinRandomFactor = 1
-	config.MaxRandomFactor = 1
-
-	c := newTestContext(config)
-
-	// Add a static entry
-	entry, ok := c.linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	staticLinkAddr := entry.LinkAddr + "static"
-	c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestAdded,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       staticLinkAddr,
-					State:          Static,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// Remove the static entry that was just added
-	c.linkRes.neigh.removeEntry(entry.Addr)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestRemoved,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       staticLinkAddr,
-					State:          Static,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	opts := overflowOptions{
-		startAtEntryIndex: 0,
-	}
-	if err := c.overflowCache(opts); err != nil {
-		t.Errorf("c.overflowCache(%+v): %s", opts, err)
-	}
-}
-
-// TestNeighborCacheOverwriteWithStaticEntryThenOverflow verifies that the LRU
-// cache eviction strategy keeps count of the dynamic entry count when an entry
-// is overwritten by a static entry. Static entries should not count towards
-// the size of the LRU cache.
-func TestNeighborCacheOverwriteWithStaticEntryThenOverflow(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	// Stay in Reachable so the cache can overflow
-	config.BaseReachableTime = infiniteDuration
-	config.MinRandomFactor = 1
-	config.MaxRandomFactor = 1
-
-	c := newTestContext(config)
-
-	// Add a dynamic entry
-	entry, ok := c.linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil {
-		t.Fatalf("addReachableEntry(...) = %s", err)
-	}
-
-	// Override the entry with a static one using the same address
-	staticLinkAddr := entry.LinkAddr + "static"
-	c.linkRes.neigh.addStaticEntry(entry.Addr, staticLinkAddr)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestRemoved,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       entry.LinkAddr,
-					State:          Reachable,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-			{
-				EventType: entryTestAdded,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       staticLinkAddr,
-					State:          Static,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	opts := overflowOptions{
-		startAtEntryIndex: 1,
-		wantStaticEntries: []NeighborEntry{
-			{
-				Addr:           entry.Addr,
-				LinkAddr:       staticLinkAddr,
-				State:          Static,
-				UpdatedAtNanos: c.clock.NowNanoseconds(),
-			},
-		},
-	}
-	if err := c.overflowCache(opts); err != nil {
-		t.Errorf("c.overflowCache(%+v): %s", opts, err)
-	}
-}
-
-func TestNeighborCacheAddStaticEntryThenOverflow(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	// Stay in Reachable so the cache can overflow
-	config.BaseReachableTime = infiniteDuration
-	config.MinRandomFactor = 1
-	config.MaxRandomFactor = 1
-
-	c := newTestContext(config)
-
-	entry, ok := c.linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	c.linkRes.neigh.addStaticEntry(entry.Addr, entry.LinkAddr)
-	e, _, err := c.linkRes.neigh.entry(entry.Addr, "", nil)
-	if err != nil {
-		t.Errorf("unexpected error from c.linkRes.neigh.entry(%s, \"\", nil): %s", entry.Addr, err)
-	}
-	want := NeighborEntry{
-		Addr:           entry.Addr,
-		LinkAddr:       entry.LinkAddr,
-		State:          Static,
-		UpdatedAtNanos: c.clock.NowNanoseconds(),
-	}
-	if diff := cmp.Diff(want, e); diff != "" {
-		t.Errorf("c.linkRes.neigh.entry(%s, \"\", nil) mismatch (-want, +got):\n%s", entry.Addr, diff)
-	}
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestAdded,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       entry.LinkAddr,
-					State:          Static,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	opts := overflowOptions{
-		startAtEntryIndex: 1,
-		wantStaticEntries: []NeighborEntry{
-			{
-				Addr:           entry.Addr,
-				LinkAddr:       entry.LinkAddr,
-				State:          Static,
-				UpdatedAtNanos: c.clock.NowNanoseconds(),
-			},
-		},
-	}
-	if err := c.overflowCache(opts); err != nil {
-		t.Errorf("c.overflowCache(%+v): %s", opts, err)
-	}
-}
-
-func TestNeighborCacheClear(t *testing.T) {
-	config := DefaultNUDConfigurations()
-
-	nudDisp := testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
-	// Add a dynamic entry.
-	entry, ok := linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
-		t.Fatalf("addReachableEntry(...) = %s", err)
-	}
-
-	// Add a static entry.
-	linkRes.neigh.addStaticEntry(entryTestAddr1, entryTestLinkAddr1)
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestAdded,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entryTestAddr1,
-					LinkAddr:       entryTestLinkAddr1,
-					State:          Static,
-					UpdatedAtNanos: clock.NowNanoseconds(),
-				},
-			},
-		}
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// Clear should remove both dynamic and static entries.
-	linkRes.neigh.clear()
-
-	// Remove events dispatched from clear() have no deterministic order so they
-	// need to be sorted before comparison.
-	wantUnorderedEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestRemoved,
-			NICID:     1,
-			Entry: NeighborEntry{
-				Addr:           entry.Addr,
-				LinkAddr:       entry.LinkAddr,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-		{
-			EventType: entryTestRemoved,
-			NICID:     1,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Static,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	defer nudDisp.mu.Unlock()
-	if diff := cmp.Diff(wantUnorderedEvents, nudDisp.mu.events, unorderedEventsDiffOpts()...); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-// TestNeighborCacheClearThenOverflow verifies that the LRU cache eviction
-// strategy keeps count of the dynamic entry count when all entries are
-// cleared.
-func TestNeighborCacheClearThenOverflow(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	// Stay in Reachable so the cache can overflow
-	config.BaseReachableTime = infiniteDuration
-	config.MinRandomFactor = 1
-	config.MaxRandomFactor = 1
-
-	c := newTestContext(config)
-
-	// Add a dynamic entry
-	entry, ok := c.linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got c.linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	if err := addReachableEntry(c.nudDisp, c.clock, c.linkRes, entry); err != nil {
-		t.Fatalf("addReachableEntry(...) = %s", err)
-	}
-
-	// Clear the cache.
-	c.linkRes.neigh.clear()
-
-	{
-		wantEvents := []testEntryEventInfo{
-			{
-				EventType: entryTestRemoved,
-				NICID:     1,
-				Entry: NeighborEntry{
-					Addr:           entry.Addr,
-					LinkAddr:       entry.LinkAddr,
-					State:          Reachable,
-					UpdatedAtNanos: c.clock.NowNanoseconds(),
-				},
-			},
-		}
-		c.nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, c.nudDisp.mu.events)
-		c.nudDisp.mu.events = nil
-		c.nudDisp.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	opts := overflowOptions{
-		startAtEntryIndex: 0,
-	}
-	if err := c.overflowCache(opts); err != nil {
-		t.Errorf("c.overflowCache(%+v): %s", opts, err)
-	}
-}
-
-func TestNeighborCacheKeepFrequentlyUsed(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	// Stay in Reachable so the cache can overflow
-	config.BaseReachableTime = infiniteDuration
-	config.MinRandomFactor = 1
-	config.MaxRandomFactor = 1
-
-	nudDisp := testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
-	startedAt := clock.NowNanoseconds()
-
-	// The following logic is very similar to overflowCache, but
-	// periodically refreshes the frequently used entry.
-
-	// Fill the neighbor cache to capacity
-	for i := 0; i < neighborCacheSize; i++ {
-		entry, ok := linkRes.entries.entry(i)
-		if !ok {
-			t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i)
-		}
-		if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
-			t.Fatalf("addReachableEntry(...) = %s", err)
-		}
-	}
-
-	frequentlyUsedEntry, ok := linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
-	}
-
-	// Keep adding more entries
-	for i := neighborCacheSize; i < linkRes.entries.size(); i++ {
-		// Periodically refresh the frequently used entry
-		if i%(neighborCacheSize/2) == 0 {
-			if _, _, err := linkRes.neigh.entry(frequentlyUsedEntry.Addr, "", nil); err != nil {
-				t.Errorf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", frequentlyUsedEntry.Addr, err)
-			}
-		}
-
-		entry, ok := linkRes.entries.entry(i)
-		if !ok {
-			t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i)
-		}
-
-		// An entry should have been removed, as per the LRU eviction strategy
-		removedEntry, ok := linkRes.entries.entry(i - neighborCacheSize + 1)
-		if !ok {
-			t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i-neighborCacheSize+1)
-		}
-
-		if err := addReachableEntryWithRemoved(&nudDisp, clock, linkRes, entry, []NeighborEntry{removedEntry}); err != nil {
-			t.Fatalf("addReachableEntryWithRemoved(...) = %s", err)
-		}
-	}
-
-	// Expect to find only the frequently used entry and the most recent entries.
-	// The order of entries reported by entries() is nondeterministic, so entries
-	// have to be sorted before comparison.
-	wantUnsortedEntries := []NeighborEntry{
-		{
-			Addr:     frequentlyUsedEntry.Addr,
-			LinkAddr: frequentlyUsedEntry.LinkAddr,
-			State:    Reachable,
-			// Can be inferred since the frequently used entry is the first to
-			// be created and transitioned to Reachable.
-			UpdatedAtNanos: startedAt + typicalLatency.Nanoseconds(),
-		},
-	}
-
-	for i := linkRes.entries.size() - neighborCacheSize + 1; i < linkRes.entries.size(); i++ {
-		entry, ok := linkRes.entries.entry(i)
-		if !ok {
-			t.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i)
-		}
-		durationReachableNanos := int64(linkRes.entries.size()-i-1) * typicalLatency.Nanoseconds()
-		wantUnsortedEntries = append(wantUnsortedEntries, NeighborEntry{
-			Addr:           entry.Addr,
-			LinkAddr:       entry.LinkAddr,
-			State:          Reachable,
-			UpdatedAtNanos: clock.NowNanoseconds() - durationReachableNanos,
-		})
-	}
-
-	if diff := cmp.Diff(wantUnsortedEntries, linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" {
-		t.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff)
-	}
-
-	// No more events should have been dispatched.
-	nudDisp.mu.Lock()
-	defer nudDisp.mu.Unlock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-func TestNeighborCacheConcurrent(t *testing.T) {
-	const concurrentProcesses = 16
-
-	config := DefaultNUDConfigurations()
-
-	nudDisp := testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
-	storeEntries := linkRes.entries.entries()
-	for _, entry := range storeEntries {
-		var wg sync.WaitGroup
-		for r := 0; r < concurrentProcesses; r++ {
-			wg.Add(1)
-			go func(entry NeighborEntry) {
-				defer wg.Done()
-				switch e, _, err := linkRes.neigh.entry(entry.Addr, "", nil); err.(type) {
-				case nil, *tcpip.ErrWouldBlock:
-				default:
-					t.Errorf("got linkRes.neigh.entry(%s, '', nil) = (%+v, _, %s), want (_, _, nil) or (_, _, %s)", entry.Addr, e, err, &tcpip.ErrWouldBlock{})
-				}
-			}(entry)
-		}
-
-		// Wait for all goroutines to send a request
-		wg.Wait()
-
-		// Process all the requests for a single entry concurrently
-		clock.Advance(typicalLatency)
-	}
-
-	// All goroutines add in the same order and add more values than can fit in
-	// the cache. Our eviction strategy requires that the last entries are
-	// present, up to the size of the neighbor cache, and the rest are missing.
-	// The order of entries reported by entries() is nondeterministic, so entries
-	// have to be sorted before comparison.
-	var wantUnsortedEntries []NeighborEntry
-	for i := linkRes.entries.size() - neighborCacheSize; i < linkRes.entries.size(); i++ {
-		entry, ok := linkRes.entries.entry(i)
-		if !ok {
-			t.Errorf("got linkRes.entries.entry(%d) = _, false, want = true", i)
-		}
-		durationReachableNanos := int64(linkRes.entries.size()-i-1) * typicalLatency.Nanoseconds()
-		wantUnsortedEntries = append(wantUnsortedEntries, NeighborEntry{
-			Addr:           entry.Addr,
-			LinkAddr:       entry.LinkAddr,
-			State:          Reachable,
-			UpdatedAtNanos: clock.NowNanoseconds() - durationReachableNanos,
-		})
-	}
-
-	if diff := cmp.Diff(wantUnsortedEntries, linkRes.neigh.entries(), unorderedEntriesDiffOpts()...); diff != "" {
-		t.Errorf("neighbor entries mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-func TestNeighborCacheReplace(t *testing.T) {
-	config := DefaultNUDConfigurations()
-
-	nudDisp := testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
-	entry, ok := linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
-	}
-	if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
-		t.Fatalf("addReachableEntry(...) = %s", err)
-	}
-
-	// Notify of a link address change
-	var updatedLinkAddr tcpip.LinkAddress
-	{
-		entry, ok := linkRes.entries.entry(1)
-		if !ok {
-			t.Fatal("got linkRes.entries.entry(1) = _, false, want = true")
-		}
-		updatedLinkAddr = entry.LinkAddr
-	}
-	linkRes.entries.set(0, updatedLinkAddr)
-	linkRes.neigh.handleConfirmation(entry.Addr, updatedLinkAddr, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  true,
-		IsRouter:  false,
-	})
-
-	// Requesting the entry again should start neighbor reachability confirmation.
-	//
-	// Verify the entry's new link address and the new state.
-	{
-		e, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
-		if err != nil {
-			t.Fatalf("linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err)
-		}
-		want := NeighborEntry{
-			Addr:           entry.Addr,
-			LinkAddr:       updatedLinkAddr,
-			State:          Delay,
-			UpdatedAtNanos: clock.NowNanoseconds(),
-		}
-		if diff := cmp.Diff(want, e); diff != "" {
-			t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff)
-		}
-	}
-
-	clock.Advance(config.DelayFirstProbeTime + typicalLatency)
-
-	// Verify that the neighbor is now reachable.
-	{
-		e, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
-		if err != nil {
-			t.Errorf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err)
-		}
-		want := NeighborEntry{
-			Addr:           entry.Addr,
-			LinkAddr:       updatedLinkAddr,
-			State:          Reachable,
-			UpdatedAtNanos: clock.NowNanoseconds(),
-		}
-		if diff := cmp.Diff(want, e); diff != "" {
-			t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff)
-		}
-	}
-}
-
-func TestNeighborCacheResolutionFailed(t *testing.T) {
-	config := DefaultNUDConfigurations()
-
-	nudDisp := testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-
-	var requestCount uint32
-	linkRes.onLinkAddressRequest = func() {
-		atomic.AddUint32(&requestCount, 1)
-	}
-
-	entry, ok := linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
-	}
-
-	// First, sanity check that resolution is working
-	if err := addReachableEntry(&nudDisp, clock, linkRes, entry); err != nil {
-		t.Fatalf("addReachableEntry(...) = %s", err)
-	}
-
-	got, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
-	if err != nil {
-		t.Fatalf("unexpected error from linkRes.neigh.entry(%s, '', nil): %s", entry.Addr, err)
-	}
-	want := NeighborEntry{
-		Addr:           entry.Addr,
-		LinkAddr:       entry.LinkAddr,
-		State:          Reachable,
-		UpdatedAtNanos: clock.NowNanoseconds(),
-	}
-	if diff := cmp.Diff(want, got); diff != "" {
-		t.Errorf("linkRes.neigh.entry(%s, '', nil) mismatch (-want, +got):\n%s", entry.Addr, diff)
-	}
-
-	// Verify address resolution fails for an unknown address.
-	before := atomic.LoadUint32(&requestCount)
-
-	entry.Addr += "2"
-	{
-		_, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
-			if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" {
-				t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
-			}
-		})
-		if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-			t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
-		}
-		waitFor := config.DelayFirstProbeTime + typicalLatency*time.Duration(config.MaxMulticastProbes)
-		clock.Advance(waitFor)
-		select {
-		case <-ch:
-		default:
-			t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
-		}
-	}
-
-	maxAttempts := linkRes.neigh.config().MaxUnicastProbes
-	if got, want := atomic.LoadUint32(&requestCount)-before, maxAttempts; got != want {
-		t.Errorf("got link address request count = %d, want = %d", got, want)
-	}
-}
-
-// TestNeighborCacheResolutionTimeout simulates sending MaxMulticastProbes
-// probes and not retrieving a confirmation before the duration defined by
-// MaxMulticastProbes * RetransmitTimer.
-func TestNeighborCacheResolutionTimeout(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	config.RetransmitTimer = time.Millisecond // small enough to cause timeout
-
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(nil, config, clock)
-	// large enough to cause timeout
-	linkRes.delay = time.Minute
-
-	entry, ok := linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
-	}
-
-	_, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
-		if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" {
-			t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
-		}
-	})
-	if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-		t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
-	}
-	waitFor := config.RetransmitTimer * time.Duration(config.MaxMulticastProbes)
-	clock.Advance(waitFor)
-
-	select {
-	case <-ch:
-	default:
-		t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
-	}
-}
-
-// TestNeighborCacheRetryResolution simulates retrying communication after
-// failing to perform address resolution.
-func TestNeighborCacheRetryResolution(t *testing.T) {
-	config := DefaultNUDConfigurations()
-	nudDisp := testNUDDispatcher{}
-	clock := faketime.NewManualClock()
-	linkRes := newTestNeighborResolver(&nudDisp, config, clock)
-	// Simulate a faulty link.
-	linkRes.dropReplies = true
-
-	entry, ok := linkRes.entries.entry(0)
-	if !ok {
-		t.Fatal("got linkRes.entries.entry(0) = _, false, want = true ")
-	}
-
-	// Perform address resolution with a faulty link, which will fail.
-	{
-		_, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
-			if diff := cmp.Diff(LinkResolutionResult{Err: &tcpip.ErrTimeout{}}, r); diff != "" {
-				t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
-			}
-		})
-		if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-			t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
-		}
-
-		{
-			wantEvents := []testEntryEventInfo{
-				{
-					EventType: entryTestAdded,
-					NICID:     1,
-					Entry: NeighborEntry{
-						Addr:           entry.Addr,
-						LinkAddr:       "",
-						State:          Incomplete,
-						UpdatedAtNanos: clock.NowNanoseconds(),
-					},
-				},
-			}
-			nudDisp.mu.Lock()
-			diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-			nudDisp.mu.events = nil
-			nudDisp.mu.Unlock()
-			if diff != "" {
-				t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-			}
-		}
-
-		waitFor := config.RetransmitTimer * time.Duration(config.MaxMulticastProbes)
-		clock.Advance(waitFor)
-
-		select {
-		case <-ch:
-		default:
-			t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
-		}
-
-		{
-			wantEvents := []testEntryEventInfo{
-				{
-					EventType: entryTestChanged,
-					NICID:     1,
-					Entry: NeighborEntry{
-						Addr:           entry.Addr,
-						LinkAddr:       "",
-						State:          Unreachable,
-						UpdatedAtNanos: clock.NowNanoseconds(),
-					},
-				},
-			}
-			nudDisp.mu.Lock()
-			diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-			nudDisp.mu.events = nil
-			nudDisp.mu.Unlock()
-			if diff != "" {
-				t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-			}
-		}
-
-		{
-			wantEntries := []NeighborEntry{
-				{
-					Addr:           entry.Addr,
-					LinkAddr:       "",
-					State:          Unreachable,
-					UpdatedAtNanos: clock.NowNanoseconds(),
-				},
-			}
-			if diff := cmp.Diff(linkRes.neigh.entries(), wantEntries, unorderedEntriesDiffOpts()...); diff != "" {
-				t.Fatalf("neighbor entries mismatch (-got, +want):\n%s", diff)
-			}
-		}
-	}
-
-	// Retry address resolution with a working link.
-	linkRes.dropReplies = false
-	{
-		incompleteEntry, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
-			if diff := cmp.Diff(LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil}, r); diff != "" {
-				t.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
-			}
-		})
-		if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-			t.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
-		}
-		if incompleteEntry.State != Incomplete {
-			t.Fatalf("got entry.State = %s, want = %s", incompleteEntry.State, Incomplete)
-		}
-
-		{
-			wantEvents := []testEntryEventInfo{
-				{
-					EventType: entryTestChanged,
-					NICID:     1,
-					Entry: NeighborEntry{
-						Addr:           entry.Addr,
-						LinkAddr:       "",
-						State:          Incomplete,
-						UpdatedAtNanos: clock.NowNanoseconds(),
-					},
-				},
-			}
-			nudDisp.mu.Lock()
-			diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-			nudDisp.mu.events = nil
-			nudDisp.mu.Unlock()
-			if diff != "" {
-				t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-			}
-		}
-
-		clock.Advance(typicalLatency)
-
-		select {
-		case <-ch:
-		default:
-			t.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
-		}
-
-		{
-			wantEvents := []testEntryEventInfo{
-				{
-					EventType: entryTestChanged,
-					NICID:     1,
-					Entry: NeighborEntry{
-						Addr:           entry.Addr,
-						LinkAddr:       entry.LinkAddr,
-						State:          Reachable,
-						UpdatedAtNanos: clock.NowNanoseconds(),
-					},
-				},
-			}
-			nudDisp.mu.Lock()
-			diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-			nudDisp.mu.events = nil
-			nudDisp.mu.Unlock()
-			if diff != "" {
-				t.Fatalf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-			}
-		}
-
-		{
-			gotEntry, _, err := linkRes.neigh.entry(entry.Addr, "", nil)
-			if err != nil {
-				t.Fatalf("linkRes.neigh.entry(%s, '', _): %s", entry.Addr, err)
-			}
-
-			wantEntry := NeighborEntry{
-				Addr:           entry.Addr,
-				LinkAddr:       entry.LinkAddr,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			}
-			if diff := cmp.Diff(gotEntry, wantEntry); diff != "" {
-				t.Fatalf("neighbor entry mismatch (-got, +want):\n%s", diff)
-			}
-		}
-	}
-}
-
-func BenchmarkCacheClear(b *testing.B) {
-	b.StopTimer()
-	config := DefaultNUDConfigurations()
-	clock := &tcpip.StdClock{}
-	linkRes := newTestNeighborResolver(nil, config, clock)
-	linkRes.delay = 0
-
-	// Clear for every possible size of the cache
-	for cacheSize := 0; cacheSize < neighborCacheSize; cacheSize++ {
-		// Fill the neighbor cache to capacity.
-		for i := 0; i < cacheSize; i++ {
-			entry, ok := linkRes.entries.entry(i)
-			if !ok {
-				b.Fatalf("got linkRes.entries.entry(%d) = _, false, want = true", i)
-			}
-
-			_, ch, err := linkRes.neigh.entry(entry.Addr, "", func(r LinkResolutionResult) {
-				if diff := cmp.Diff(LinkResolutionResult{LinkAddress: entry.LinkAddr, Err: nil}, r); diff != "" {
-					b.Fatalf("got link resolution result mismatch (-want +got):\n%s", diff)
-				}
-			})
-			if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-				b.Fatalf("got linkRes.neigh.entry(%s, '', _) = %v, want = %s", entry.Addr, err, &tcpip.ErrWouldBlock{})
-			}
-
-			select {
-			case <-ch:
-			default:
-				b.Fatalf("expected notification from done channel returned by linkRes.neigh.entry(%s, '', _)", entry.Addr)
-			}
-		}
-
-		b.StartTimer()
-		linkRes.neigh.clear()
-		b.StopTimer()
-	}
-}
diff --git a/pkg/tcpip/stack/neighbor_entry_list.go b/pkg/tcpip/stack/neighbor_entry_list.go
new file mode 100644
index 0000000..d784300
--- /dev/null
+++ b/pkg/tcpip/stack/neighbor_entry_list.go
@@ -0,0 +1,221 @@
+package stack
+
+// 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 neighborEntryElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (neighborEntryElementMapper) linkerFor(elem *neighborEntry) *neighborEntry { 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 neighborEntryList struct {
+	head *neighborEntry
+	tail *neighborEntry
+}
+
+// Reset resets list l to the empty state.
+func (l *neighborEntryList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *neighborEntryList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *neighborEntryList) Front() *neighborEntry {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *neighborEntryList) Back() *neighborEntry {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *neighborEntryList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (neighborEntryElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *neighborEntryList) PushFront(e *neighborEntry) {
+	linker := neighborEntryElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		neighborEntryElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *neighborEntryList) PushBack(e *neighborEntry) {
+	linker := neighborEntryElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		neighborEntryElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *neighborEntryList) PushBackList(m *neighborEntryList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		neighborEntryElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		neighborEntryElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *neighborEntryList) InsertAfter(b, e *neighborEntry) {
+	bLinker := neighborEntryElementMapper{}.linkerFor(b)
+	eLinker := neighborEntryElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		neighborEntryElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *neighborEntryList) InsertBefore(a, e *neighborEntry) {
+	aLinker := neighborEntryElementMapper{}.linkerFor(a)
+	eLinker := neighborEntryElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		neighborEntryElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *neighborEntryList) Remove(e *neighborEntry) {
+	linker := neighborEntryElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		neighborEntryElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		neighborEntryElementMapper{}.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 neighborEntryEntry struct {
+	next *neighborEntry
+	prev *neighborEntry
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *neighborEntryEntry) Next() *neighborEntry {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *neighborEntryEntry) Prev() *neighborEntry {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *neighborEntryEntry) SetNext(elem *neighborEntry) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *neighborEntryEntry) SetPrev(elem *neighborEntry) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/stack/neighbor_entry_test.go b/pkg/tcpip/stack/neighbor_entry_test.go
deleted file mode 100644
index bb2b2d7..0000000
--- a/pkg/tcpip/stack/neighbor_entry_test.go
+++ /dev/null
@@ -1,2270 +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 stack
-
-import (
-	"fmt"
-	"math"
-	"math/rand"
-	"sync"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/faketime"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-const (
-	entryTestNetNumber tcpip.NetworkProtocolNumber = math.MaxUint32
-
-	entryTestNICID tcpip.NICID = 1
-	entryTestAddr1             = tcpip.Address("\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-	entryTestAddr2             = tcpip.Address("\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-
-	entryTestLinkAddr1 = tcpip.LinkAddress("\x0a\x00\x00\x00\x00\x01")
-	entryTestLinkAddr2 = tcpip.LinkAddress("\x0a\x00\x00\x00\x00\x02")
-
-	// entryTestNetDefaultMTU is the MTU, in bytes, used throughout the tests,
-	// except where another value is explicitly used. It is chosen to match the
-	// MTU of loopback interfaces on Linux systems.
-	entryTestNetDefaultMTU = 65536
-)
-
-// runImmediatelyScheduledJobs runs all jobs scheduled to run at the current
-// time.
-func runImmediatelyScheduledJobs(clock *faketime.ManualClock) {
-	clock.Advance(immediateDuration)
-}
-
-// The following unit tests exercise every state transition and verify its
-// behavior with RFC 4681 and RFC 7048.
-//
-// | From        | To          | Cause                                      | Update   | Action     | Event   |
-// | =========== | =========== | ========================================== | ======== | ===========| ======= |
-// | Unknown     | Unknown     | Confirmation w/ unknown address            |          |            | Added   |
-// | Unknown     | Incomplete  | Packet queued to unknown address           |          | Send probe | Added   |
-// | Unknown     | Stale       | Probe                                      |          |            | Added   |
-// | Incomplete  | Incomplete  | Retransmit timer expired                   |          | Send probe | Changed |
-// | Incomplete  | Reachable   | Solicited confirmation                     | LinkAddr | Notify     | Changed |
-// | Incomplete  | Stale       | Unsolicited confirmation                   | LinkAddr | Notify     | Changed |
-// | Incomplete  | Stale       | Probe                                      | LinkAddr | Notify     | Changed |
-// | Incomplete  | Unreachable | Max probes sent without reply              |          | Notify     | Changed |
-// | Reachable   | Reachable   | Confirmation w/ different isRouter flag    | IsRouter |            |         |
-// | Reachable   | Stale       | Reachable timer expired                    |          |            | Changed |
-// | Reachable   | Stale       | Probe or confirmation w/ different address |          |            | Changed |
-// | Stale       | Reachable   | Solicited override confirmation            | LinkAddr |            | Changed |
-// | Stale       | Reachable   | Solicited confirmation w/o address         |          | Notify     | Changed |
-// | Stale       | Stale       | Override confirmation                      | LinkAddr |            | Changed |
-// | Stale       | Stale       | Probe w/ different address                 | LinkAddr |            | Changed |
-// | Stale       | Delay       | Packet sent                                |          |            | Changed |
-// | Delay       | Reachable   | Upper-layer confirmation                   |          |            | Changed |
-// | Delay       | Reachable   | Solicited override confirmation            | LinkAddr |            | Changed |
-// | Delay       | Reachable   | Solicited confirmation w/o address         |          | Notify     | Changed |
-// | Delay       | Stale       | Probe or confirmation w/ different address |          |            | Changed |
-// | Delay       | Probe       | Delay timer expired                        |          | Send probe | Changed |
-// | Probe       | Reachable   | Solicited override confirmation            | LinkAddr |            | Changed |
-// | Probe       | Reachable   | Solicited confirmation w/ same address     |          | Notify     | Changed |
-// | Probe       | Reachable   | Solicited confirmation w/o address         |          | Notify     | Changed |
-// | Probe       | Stale       | Probe or confirmation w/ different address |          |            | Changed |
-// | Probe       | Probe       | Retransmit timer expired                   |          |            | Changed |
-// | Probe       | Unreachable | Max probes sent without reply              |          | Notify     | Changed |
-// | Unreachable | Incomplete  | Packet queued                              |          | Send probe | Changed |
-// | Unreachable | Stale       | Probe w/ different address                 | LinkAddr |            | Changed |
-
-type testEntryEventType uint8
-
-const (
-	entryTestAdded testEntryEventType = iota
-	entryTestChanged
-	entryTestRemoved
-)
-
-func (t testEntryEventType) String() string {
-	switch t {
-	case entryTestAdded:
-		return "add"
-	case entryTestChanged:
-		return "change"
-	case entryTestRemoved:
-		return "remove"
-	default:
-		return fmt.Sprintf("unknown (%d)", t)
-	}
-}
-
-// Fields are exported for use with cmp.Diff.
-type testEntryEventInfo struct {
-	EventType testEntryEventType
-	NICID     tcpip.NICID
-	Entry     NeighborEntry
-}
-
-func (e testEntryEventInfo) String() string {
-	return fmt.Sprintf("%s event for NIC #%d, %#v", e.EventType, e.NICID, e.Entry)
-}
-
-// testNUDDispatcher implements NUDDispatcher to validate the dispatching of
-// events upon certain NUD state machine events.
-type testNUDDispatcher struct {
-	mu struct {
-		sync.Mutex
-		events []testEntryEventInfo
-	}
-}
-
-var _ NUDDispatcher = (*testNUDDispatcher)(nil)
-
-func (d *testNUDDispatcher) queueEvent(e testEntryEventInfo) {
-	d.mu.Lock()
-	defer d.mu.Unlock()
-	d.mu.events = append(d.mu.events, e)
-}
-
-func (d *testNUDDispatcher) OnNeighborAdded(nicID tcpip.NICID, entry NeighborEntry) {
-	d.queueEvent(testEntryEventInfo{
-		EventType: entryTestAdded,
-		NICID:     nicID,
-		Entry:     entry,
-	})
-}
-
-func (d *testNUDDispatcher) OnNeighborChanged(nicID tcpip.NICID, entry NeighborEntry) {
-	d.queueEvent(testEntryEventInfo{
-		EventType: entryTestChanged,
-		NICID:     nicID,
-		Entry:     entry,
-	})
-}
-
-func (d *testNUDDispatcher) OnNeighborRemoved(nicID tcpip.NICID, entry NeighborEntry) {
-	d.queueEvent(testEntryEventInfo{
-		EventType: entryTestRemoved,
-		NICID:     nicID,
-		Entry:     entry,
-	})
-}
-
-type entryTestLinkResolver struct {
-	mu struct {
-		sync.Mutex
-		probes []entryTestProbeInfo
-	}
-}
-
-var _ LinkAddressResolver = (*entryTestLinkResolver)(nil)
-
-type entryTestProbeInfo struct {
-	RemoteAddress     tcpip.Address
-	RemoteLinkAddress tcpip.LinkAddress
-	LocalAddress      tcpip.Address
-}
-
-func (p entryTestProbeInfo) String() string {
-	return fmt.Sprintf("probe with RemoteAddress=%q, RemoteLinkAddress=%q, LocalAddress=%q", p.RemoteAddress, p.RemoteLinkAddress, p.LocalAddress)
-}
-
-// LinkAddressRequest sends a request for the LinkAddress of addr. Broadcasts
-// to the local network if linkAddr is the zero value.
-func (r *entryTestLinkResolver) LinkAddressRequest(targetAddr, localAddr tcpip.Address, linkAddr tcpip.LinkAddress) tcpip.Error {
-	r.mu.Lock()
-	defer r.mu.Unlock()
-	r.mu.probes = append(r.mu.probes, entryTestProbeInfo{
-		RemoteAddress:     targetAddr,
-		RemoteLinkAddress: linkAddr,
-		LocalAddress:      localAddr,
-	})
-	return nil
-}
-
-// ResolveStaticAddress attempts to resolve address without sending requests.
-// It either resolves the name immediately or returns the empty LinkAddress.
-func (r *entryTestLinkResolver) ResolveStaticAddress(addr tcpip.Address) (tcpip.LinkAddress, bool) {
-	return "", false
-}
-
-// LinkAddressProtocol returns the network protocol of the addresses this
-// resolver can resolve.
-func (r *entryTestLinkResolver) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
-	return entryTestNetNumber
-}
-
-func entryTestSetup(c NUDConfigurations) (*neighborEntry, *testNUDDispatcher, *entryTestLinkResolver, *faketime.ManualClock) {
-	clock := faketime.NewManualClock()
-	disp := testNUDDispatcher{}
-	nic := nic{
-		LinkEndpoint: nil, // entryTestLinkResolver doesn't use a LinkEndpoint
-
-		id: entryTestNICID,
-		stack: &Stack{
-			clock:           clock,
-			nudDisp:         &disp,
-			nudConfigs:      c,
-			randomGenerator: rand.New(rand.NewSource(time.Now().UnixNano())),
-		},
-		stats: makeNICStats(),
-	}
-	netEP := (&testIPv6Protocol{}).NewEndpoint(&nic, nil)
-	nic.networkEndpoints = map[tcpip.NetworkProtocolNumber]NetworkEndpoint{
-		header.IPv6ProtocolNumber: netEP,
-	}
-
-	var linkRes entryTestLinkResolver
-	// Stub out the neighbor cache to verify deletion from the cache.
-	l := &linkResolver{
-		resolver: &linkRes,
-	}
-	l.neigh.init(&nic, &linkRes)
-
-	entry := newNeighborEntry(&l.neigh, entryTestAddr1 /* remoteAddr */, l.neigh.state)
-	l.neigh.mu.Lock()
-	l.neigh.mu.cache[entryTestAddr1] = entry
-	l.neigh.mu.Unlock()
-	nic.linkAddrResolvers = map[tcpip.NetworkProtocolNumber]*linkResolver{
-		header.IPv6ProtocolNumber: l,
-	}
-
-	return entry, &disp, &linkRes, clock
-}
-
-// TestEntryInitiallyUnknown verifies that the state of a newly created
-// neighborEntry is Unknown.
-func TestEntryInitiallyUnknown(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	e.mu.Lock()
-	if e.mu.neigh.State != Unknown {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
-	}
-	e.mu.Unlock()
-
-	clock.Advance(c.RetransmitTimer)
-
-	// No probes should have been sent.
-	linkRes.mu.Lock()
-	diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-	linkRes.mu.Unlock()
-	if diff != "" {
-		t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryUnknownToUnknownWhenConfirmationWithUnknownAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  false,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Unknown {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
-	}
-	e.mu.Unlock()
-
-	clock.Advance(time.Hour)
-
-	// No probes should have been sent.
-	linkRes.mu.Lock()
-	diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-	linkRes.mu.Unlock()
-	if diff != "" {
-		t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryUnknownToIncomplete(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-}
-
-func unknownToIncomplete(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	if err := func() error {
-		e.mu.Lock()
-		defer e.mu.Unlock()
-
-		if e.mu.neigh.State != Unknown {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
-		}
-		e.handlePacketQueuedLocked(entryTestAddr2)
-		if e.mu.neigh.State != Incomplete {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
-		}
-		return nil
-	}(); err != nil {
-		return err
-	}
-
-	runImmediatelyScheduledJobs(clock)
-	wantProbes := []entryTestProbeInfo{
-		{
-			RemoteAddress:     entryTestAddr1,
-			RemoteLinkAddress: tcpip.LinkAddress(""),
-			LocalAddress:      entryTestAddr2,
-		},
-	}
-	linkRes.mu.Lock()
-	diff := cmp.Diff(wantProbes, linkRes.mu.probes)
-	linkRes.mu.probes = nil
-	linkRes.mu.Unlock()
-	if diff != "" {
-		return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestAdded,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       tcpip.LinkAddress(""),
-				State:          Incomplete,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	{
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-	return nil
-}
-
-func TestEntryUnknownToStale(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-}
-
-func unknownToStale(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	if err := func() error {
-		e.mu.Lock()
-		defer e.mu.Unlock()
-
-		if e.mu.neigh.State != Unknown {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unknown)
-		}
-		e.handleProbeLocked(entryTestLinkAddr1)
-		if e.mu.neigh.State != Stale {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-		}
-		return nil
-	}(); err != nil {
-		return err
-	}
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestAdded,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	{
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	return nil
-}
-
-func TestEntryIncompleteToIncompleteDoesNotChangeUpdatedAt(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	c.MaxMulticastProbes = 3
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-
-	// UpdatedAt should remain the same during address resolution.
-	e.mu.Lock()
-	startedAt := e.mu.neigh.UpdatedAtNanos
-	e.mu.Unlock()
-
-	// Wait for the rest of the reachability probe transmissions, signifying
-	// Incomplete to Incomplete transitions.
-	for i := uint32(1); i < c.MaxMulticastProbes; i++ {
-		clock.Advance(c.RetransmitTimer)
-
-		wantProbes := []entryTestProbeInfo{
-			{
-				RemoteAddress:     entryTestAddr1,
-				RemoteLinkAddress: tcpip.LinkAddress(""),
-				LocalAddress:      entryTestAddr2,
-			},
-		}
-		linkRes.mu.Lock()
-		diff := cmp.Diff(wantProbes, linkRes.mu.probes)
-		linkRes.mu.probes = nil
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Fatalf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-
-		e.mu.Lock()
-		if got, want := e.mu.neigh.UpdatedAtNanos, startedAt; got != want {
-			t.Errorf("got e.mu.neigh.UpdatedAt = %q, want = %q", got, want)
-		}
-		e.mu.Unlock()
-	}
-
-	// UpdatedAt should change after failing address resolution. Timing out after
-	// sending the last probe transitions the entry to Unreachable.
-	clock.Advance(c.RetransmitTimer)
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       tcpip.LinkAddress(""),
-				State:          Unreachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryIncompleteToReachable(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachable(...) = %s", err)
-	}
-}
-
-func incompleteToReachableWithFlags(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, flags ReachabilityConfirmationFlags) error {
-	if err := func() error {
-		e.mu.Lock()
-		defer e.mu.Unlock()
-
-		if e.mu.neigh.State != Incomplete {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
-		}
-		e.handleConfirmationLocked(entryTestLinkAddr1, flags)
-		if e.mu.neigh.State != Reachable {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-		}
-		if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
-			return fmt.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
-		}
-		return nil
-	}(); err != nil {
-		return err
-	}
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	{
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	return nil
-}
-
-func incompleteToReachable(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	if err := incompleteToReachableWithFlags(e, nudDisp, linkRes, clock, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  false,
-		IsRouter:  false,
-	}); err != nil {
-		return err
-	}
-
-	e.mu.Lock()
-	isRouter := e.mu.isRouter
-	e.mu.Unlock()
-	if isRouter {
-		return fmt.Errorf("got e.mu.isRouter = %t, want = false", isRouter)
-	}
-
-	return nil
-}
-
-func TestEntryIncompleteToReachableWithRouterFlag(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachableWithRouterFlag(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachableWithRouterFlag(...) = %s", err)
-	}
-}
-
-func incompleteToReachableWithRouterFlag(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	if err := incompleteToReachableWithFlags(e, nudDisp, linkRes, clock, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  false,
-		IsRouter:  true,
-	}); err != nil {
-		return err
-	}
-
-	e.mu.Lock()
-	isRouter := e.mu.isRouter
-	e.mu.Unlock()
-	if !isRouter {
-		return fmt.Errorf("got e.mu.isRouter = %t, want = true", isRouter)
-	}
-
-	return nil
-}
-
-func TestEntryIncompleteToStaleWhenUnsolicitedConfirmation(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  false,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	if e.mu.isRouter {
-		t.Errorf("got e.mu.isRouter = %t, want = false", e.mu.isRouter)
-	}
-	e.mu.Unlock()
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryIncompleteToStaleWhenProbe(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleProbeLocked(entryTestLinkAddr1)
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	e.mu.Unlock()
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryIncompleteToUnreachable(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	c.MaxMulticastProbes = 3
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToUnreachable(...) = %s", err)
-	}
-}
-
-func incompleteToUnreachable(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	{
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Incomplete {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Incomplete)
-		}
-	}
-
-	// The first probe was sent in the transition from Unknown to Incomplete.
-	clock.Advance(c.RetransmitTimer)
-
-	// Observe each subsequent multicast probe transmitted.
-	for i := uint32(1); i < c.MaxMulticastProbes; i++ {
-		wantProbes := []entryTestProbeInfo{{
-			RemoteAddress:     entryTestAddr1,
-			RemoteLinkAddress: "",
-			LocalAddress:      entryTestAddr2,
-		}}
-		linkRes.mu.Lock()
-		diff := cmp.Diff(wantProbes, linkRes.mu.probes)
-		linkRes.mu.probes = nil
-		linkRes.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("link address resolver probe #%d mismatch (-want, +got):\n%s", i+1, diff)
-		}
-
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Incomplete {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Incomplete)
-		}
-
-		clock.Advance(c.RetransmitTimer)
-	}
-
-	{
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Unreachable {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Unreachable)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       tcpip.LinkAddress(""),
-				State:          Unreachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-	nudDisp.mu.events = nil
-	nudDisp.mu.Unlock()
-	if diff != "" {
-		return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-
-	return nil
-}
-
-type testLocker struct{}
-
-var _ sync.Locker = (*testLocker)(nil)
-
-func (*testLocker) Lock()   {}
-func (*testLocker) Unlock() {}
-
-func TestEntryReachableToReachableClearsRouterWhenConfirmationWithoutRouter(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachableWithRouterFlag(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachableWithRouterFlag(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  false,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Reachable {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-	}
-	if got, want := e.mu.isRouter, false; got != want {
-		t.Errorf("got e.mu.isRouter = %t, want = %t", got, want)
-	}
-	ipv6EP := e.cache.nic.networkEndpoints[header.IPv6ProtocolNumber].(*testIPv6Endpoint)
-	if ipv6EP.invalidatedRtr != e.mu.neigh.Addr {
-		t.Errorf("got ipv6EP.invalidatedRtr = %s, want = %s", ipv6EP.invalidatedRtr, e.mu.neigh.Addr)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
-	nudDisp.mu.Unlock()
-	if diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-func TestEntryReachableToReachableWhenProbeWithSameAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachable(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleProbeLocked(entryTestLinkAddr1)
-	if e.mu.neigh.State != Reachable {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
-	nudDisp.mu.Unlock()
-	if diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-func TestEntryReachableToStaleWhenTimeout(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	// Eliminate random factors from ReachableTime computation so the transition
-	// from Stale to Reachable will only take BaseReachableTime duration.
-	c.MinRandomFactor = 1
-	c.MaxRandomFactor = 1
-
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachable(...) = %s", err)
-	}
-	if err := reachableToStale(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("reachableToStale(...) = %s", err)
-	}
-}
-
-// reachableToStale transitions a neighborEntry in Reachable state to Stale
-// state. Depends on the elimination of random factors in the ReachableTime
-// computation.
-//
-//	c.MinRandomFactor = 1
-//	c.MaxRandomFactor = 1
-func reachableToStale(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	// Ensure there are no random factors in the ReachableTime computation.
-	if c.MinRandomFactor != 1 {
-		return fmt.Errorf("got c.MinRandomFactor = %f, want = 1", c.MinRandomFactor)
-	}
-	if c.MaxRandomFactor != 1 {
-		return fmt.Errorf("got c.MaxRandomFactor = %f, want = 1", c.MaxRandomFactor)
-	}
-
-	{
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Reachable {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Reachable)
-		}
-	}
-
-	clock.Advance(c.BaseReachableTime)
-
-	{
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Stale {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Stale)
-		}
-	}
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	{
-
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	return nil
-}
-
-func TestEntryReachableToStaleWhenProbeWithDifferentAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachable(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleProbeLocked(entryTestLinkAddr2)
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryReachableToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachable(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  false,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryReachableToStaleWhenConfirmationWithDifferentAddressAndOverride(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachable(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  true,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToStaleWhenProbeWithSameAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleProbeLocked(entryTestLinkAddr1)
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  true,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Reachable {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked("" /* linkAddr */, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  false,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Reachable {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToStaleWhenOverrideConfirmation(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  true,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
-	}
-	e.mu.Unlock()
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToStaleWhenProbeUpdateAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleProbeLocked(entryTestLinkAddr2)
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryStaleToDelay(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-}
-
-func staleToDelay(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	if err := func() error {
-		e.mu.Lock()
-		defer e.mu.Unlock()
-
-		if e.mu.neigh.State != Stale {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-		}
-		e.handlePacketQueuedLocked(entryTestAddr2)
-		if e.mu.neigh.State != Delay {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Delay)
-		}
-		return nil
-	}(); err != nil {
-		return err
-	}
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Delay,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-	nudDisp.mu.events = nil
-	nudDisp.mu.Unlock()
-	if diff != "" {
-		return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-
-	return nil
-}
-
-func TestEntryDelayToReachableWhenUpperLevelConfirmation(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleUpperLevelConfirmationLocked()
-	if e.mu.neigh.State != Reachable {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  true,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Reachable {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr2 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr2)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked("" /* linkAddr */, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  false,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Reachable {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToDelayWhenOverrideConfirmationWithSameAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  true,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Delay {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Delay)
-	}
-	if e.mu.neigh.LinkAddr != entryTestLinkAddr1 {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, entryTestLinkAddr1)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToStaleWhenProbeWithDifferentAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleProbeLocked(entryTestLinkAddr2)
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  true,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryDelayToProbe(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-	if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("delayToProbe(...) = %s", err)
-	}
-}
-
-func delayToProbe(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	{
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Delay {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Delay)
-		}
-	}
-
-	// Wait for the first unicast probe to be transmitted, marking the
-	// transition from Delay to Probe.
-	clock.Advance(c.DelayFirstProbeTime)
-
-	{
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Probe {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
-		}
-	}
-
-	wantProbes := []entryTestProbeInfo{
-		{
-			RemoteAddress:     entryTestAddr1,
-			RemoteLinkAddress: entryTestLinkAddr1,
-		},
-	}
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff(wantProbes, linkRes.mu.probes)
-		linkRes.mu.probes = nil
-		linkRes.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Probe,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	{
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	return nil
-}
-
-func TestEntryProbeToStaleWhenProbeWithDifferentAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-	if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("delayToProbe(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleProbeLocked(entryTestLinkAddr2)
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryProbeToStaleWhenConfirmationWithDifferentAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-	if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("delayToProbe(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr2, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  true,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Stale {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Stale)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr2,
-				State:          Stale,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	if diff := cmp.Diff(wantEvents, nudDisp.mu.events); diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-	nudDisp.mu.Unlock()
-}
-
-func TestEntryProbeToProbeWhenOverrideConfirmationWithSameAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-	if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("delayToProbe(...) = %s", err)
-	}
-
-	e.mu.Lock()
-	e.handleConfirmationLocked(entryTestLinkAddr1, ReachabilityConfirmationFlags{
-		Solicited: false,
-		Override:  true,
-		IsRouter:  false,
-	})
-	if e.mu.neigh.State != Probe {
-		t.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Probe)
-	}
-	if got, want := e.mu.neigh.LinkAddr, entryTestLinkAddr1; got != want {
-		t.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", got, want)
-	}
-	e.mu.Unlock()
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			t.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	// No events should have been dispatched.
-	nudDisp.mu.Lock()
-	diff := cmp.Diff([]testEntryEventInfo(nil), nudDisp.mu.events)
-	nudDisp.mu.Unlock()
-	if diff != "" {
-		t.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-}
-
-func TestEntryProbeToReachableWhenSolicitedOverrideConfirmation(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-	if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("delayToProbe(...) = %s", err)
-	}
-	if err := probeToReachableWithOverride(e, nudDisp, linkRes, clock, entryTestLinkAddr2); err != nil {
-		t.Fatalf("probeToReachableWithOverride(...) = %s", err)
-	}
-}
-
-func probeToReachableWithFlags(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) error {
-	if err := func() error {
-		e.mu.Lock()
-		defer e.mu.Unlock()
-
-		prevLinkAddr := e.mu.neigh.LinkAddr
-		if e.mu.neigh.State != Probe {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Probe)
-		}
-		e.handleConfirmationLocked(linkAddr, flags)
-
-		if e.mu.neigh.State != Reachable {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Reachable)
-		}
-		if linkAddr == "" {
-			linkAddr = prevLinkAddr
-		}
-		if e.mu.neigh.LinkAddr != linkAddr {
-			return fmt.Errorf("got e.mu.neigh.LinkAddr = %q, want = %q", e.mu.neigh.LinkAddr, linkAddr)
-		}
-		return nil
-	}(); err != nil {
-		return err
-	}
-
-	// No probes should have been sent.
-	runImmediatelyScheduledJobs(clock)
-	{
-		linkRes.mu.Lock()
-		diff := cmp.Diff([]entryTestProbeInfo(nil), linkRes.mu.probes)
-		linkRes.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       linkAddr,
-				State:          Reachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	{
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-
-	return nil
-}
-
-func probeToReachableWithOverride(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock, linkAddr tcpip.LinkAddress) error {
-	return probeToReachableWithFlags(e, nudDisp, linkRes, clock, linkAddr, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  true,
-		IsRouter:  false,
-	})
-}
-
-func TestEntryProbeToReachableWhenSolicitedConfirmationWithSameAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-	if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("delayToProbe(...) = %s", err)
-	}
-	if err := probeToReachable(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("probeToReachable(...) = %s", err)
-	}
-}
-
-func probeToReachable(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	return probeToReachableWithFlags(e, nudDisp, linkRes, clock, entryTestLinkAddr1, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  false,
-		IsRouter:  false,
-	})
-}
-
-func TestEntryProbeToReachableWhenSolicitedConfirmationWithoutAddress(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-	if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("delayToProbe(...) = %s", err)
-	}
-	if err := probeToReachableWithoutAddress(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("probeToReachableWithoutAddress(...) = %s", err)
-	}
-}
-
-func probeToReachableWithoutAddress(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	return probeToReachableWithFlags(e, nudDisp, linkRes, clock, "" /* linkAddr */, ReachabilityConfirmationFlags{
-		Solicited: true,
-		Override:  false,
-		IsRouter:  false,
-	})
-}
-
-func TestEntryProbeToUnreachable(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	c.MaxMulticastProbes = 3
-	c.MaxUnicastProbes = 3
-	c.DelayFirstProbeTime = c.RetransmitTimer
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToStale(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToStale(...) = %s", err)
-	}
-	if err := staleToDelay(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("staleToDelay(...) = %s", err)
-	}
-	if err := delayToProbe(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("delayToProbe(...) = %s", err)
-	}
-	if err := probeToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("probeToUnreachable(...) = %s", err)
-	}
-}
-
-func probeToUnreachable(c NUDConfigurations, e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	{
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Probe {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
-		}
-	}
-
-	// The first probe was sent in the transition from Delay to Probe.
-	clock.Advance(c.RetransmitTimer)
-
-	// Observe each subsequent unicast probe transmitted.
-	for i := uint32(1); i < c.MaxUnicastProbes; i++ {
-		wantProbes := []entryTestProbeInfo{{
-			RemoteAddress:     entryTestAddr1,
-			RemoteLinkAddress: entryTestLinkAddr1,
-		}}
-		linkRes.mu.Lock()
-		diff := cmp.Diff(wantProbes, linkRes.mu.probes)
-		linkRes.mu.probes = nil
-		linkRes.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("link address resolver probe #%d mismatch (-want, +got):\n%s", i+1, diff)
-		}
-
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Probe {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Probe)
-		}
-
-		clock.Advance(c.RetransmitTimer)
-	}
-
-	{
-		e.mu.Lock()
-		state := e.mu.neigh.State
-		e.mu.Unlock()
-		if state != Unreachable {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", state, Unreachable)
-		}
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       entryTestLinkAddr1,
-				State:          Unreachable,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	nudDisp.mu.Lock()
-	diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-	nudDisp.mu.events = nil
-	nudDisp.mu.Unlock()
-	if diff != "" {
-		return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-	}
-
-	return nil
-}
-
-func TestEntryUnreachableToIncomplete(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	c.MaxMulticastProbes = 3
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToUnreachable(...) = %s", err)
-	}
-	if err := unreachableToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unreachableToIncomplete(...) = %s", err)
-	}
-}
-
-func unreachableToIncomplete(e *neighborEntry, nudDisp *testNUDDispatcher, linkRes *entryTestLinkResolver, clock *faketime.ManualClock) error {
-	if err := func() error {
-		e.mu.Lock()
-		defer e.mu.Unlock()
-
-		if e.mu.neigh.State != Unreachable {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Unreachable)
-		}
-		e.handlePacketQueuedLocked(entryTestAddr2)
-		if e.mu.neigh.State != Incomplete {
-			return fmt.Errorf("got e.mu.neigh.State = %q, want = %q", e.mu.neigh.State, Incomplete)
-		}
-		return nil
-	}(); err != nil {
-		return err
-	}
-
-	runImmediatelyScheduledJobs(clock)
-	wantProbes := []entryTestProbeInfo{
-		{
-			RemoteAddress:     entryTestAddr1,
-			RemoteLinkAddress: tcpip.LinkAddress(""),
-			LocalAddress:      entryTestAddr2,
-		},
-	}
-	linkRes.mu.Lock()
-	diff := cmp.Diff(wantProbes, linkRes.mu.probes)
-	linkRes.mu.probes = nil
-	linkRes.mu.Unlock()
-	if diff != "" {
-		return fmt.Errorf("link address resolver probes mismatch (-want, +got):\n%s", diff)
-	}
-
-	wantEvents := []testEntryEventInfo{
-		{
-			EventType: entryTestChanged,
-			NICID:     entryTestNICID,
-			Entry: NeighborEntry{
-				Addr:           entryTestAddr1,
-				LinkAddr:       tcpip.LinkAddress(""),
-				State:          Incomplete,
-				UpdatedAtNanos: clock.NowNanoseconds(),
-			},
-		},
-	}
-	{
-		nudDisp.mu.Lock()
-		diff := cmp.Diff(wantEvents, nudDisp.mu.events)
-		nudDisp.mu.events = nil
-		nudDisp.mu.Unlock()
-		if diff != "" {
-			return fmt.Errorf("nud dispatcher events mismatch (-want, +got):\n%s", diff)
-		}
-	}
-	return nil
-}
-
-func TestEntryUnreachableToStale(t *testing.T) {
-	c := DefaultNUDConfigurations()
-	c.MaxMulticastProbes = 3
-	// Eliminate random factors from ReachableTime computation so the transition
-	// from Stale to Reachable will only take BaseReachableTime duration.
-	c.MinRandomFactor = 1
-	c.MaxRandomFactor = 1
-
-	e, nudDisp, linkRes, clock := entryTestSetup(c)
-
-	if err := unknownToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unknownToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToUnreachable(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToUnreachable(...) = %s", err)
-	}
-	if err := unreachableToIncomplete(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("unreachableToIncomplete(...) = %s", err)
-	}
-	if err := incompleteToReachable(e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("incompleteToReachable(...) = %s", err)
-	}
-	if err := reachableToStale(c, e, nudDisp, linkRes, clock); err != nil {
-		t.Fatalf("reachableToStale(...) = %s", err)
-	}
-}
diff --git a/pkg/tcpip/stack/nic_test.go b/pkg/tcpip/stack/nic_test.go
deleted file mode 100644
index c0f956e..0000000
--- a/pkg/tcpip/stack/nic_test.go
+++ /dev/null
@@ -1,210 +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 stack
-
-import (
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-var _ AddressableEndpoint = (*testIPv6Endpoint)(nil)
-var _ NetworkEndpoint = (*testIPv6Endpoint)(nil)
-var _ NDPEndpoint = (*testIPv6Endpoint)(nil)
-
-// An IPv6 NetworkEndpoint that throws away outgoing packets.
-//
-// We use this instead of ipv6.endpoint because the ipv6 package depends on
-// the stack package which this test lives in, causing a cyclic dependency.
-type testIPv6Endpoint struct {
-	AddressableEndpointState
-
-	nic      NetworkInterface
-	protocol *testIPv6Protocol
-
-	invalidatedRtr tcpip.Address
-}
-
-func (*testIPv6Endpoint) Enable() tcpip.Error {
-	return nil
-}
-
-func (*testIPv6Endpoint) Enabled() bool {
-	return true
-}
-
-func (*testIPv6Endpoint) Disable() {}
-
-// DefaultTTL implements NetworkEndpoint.DefaultTTL.
-func (*testIPv6Endpoint) DefaultTTL() uint8 {
-	return 0
-}
-
-// MTU implements NetworkEndpoint.MTU.
-func (e *testIPv6Endpoint) MTU() uint32 {
-	return e.nic.MTU() - header.IPv6MinimumSize
-}
-
-// MaxHeaderLength implements NetworkEndpoint.MaxHeaderLength.
-func (e *testIPv6Endpoint) MaxHeaderLength() uint16 {
-	return e.nic.MaxHeaderLength() + header.IPv6MinimumSize
-}
-
-// WritePacket implements NetworkEndpoint.WritePacket.
-func (*testIPv6Endpoint) WritePacket(*Route, *GSO, NetworkHeaderParams, *PacketBuffer) tcpip.Error {
-	return nil
-}
-
-// WritePackets implements NetworkEndpoint.WritePackets.
-func (*testIPv6Endpoint) WritePackets(*Route, *GSO, PacketBufferList, NetworkHeaderParams) (int, tcpip.Error) {
-	// Our tests don't use this so we don't support it.
-	return 0, &tcpip.ErrNotSupported{}
-}
-
-// WriteHeaderIncludedPacket implements
-// NetworkEndpoint.WriteHeaderIncludedPacket.
-func (*testIPv6Endpoint) WriteHeaderIncludedPacket(*Route, *PacketBuffer) tcpip.Error {
-	// Our tests don't use this so we don't support it.
-	return &tcpip.ErrNotSupported{}
-}
-
-// HandlePacket implements NetworkEndpoint.HandlePacket.
-func (*testIPv6Endpoint) HandlePacket(*PacketBuffer) {}
-
-// Close implements NetworkEndpoint.Close.
-func (e *testIPv6Endpoint) Close() {
-	e.AddressableEndpointState.Cleanup()
-}
-
-// NetworkProtocolNumber implements NetworkEndpoint.NetworkProtocolNumber.
-func (*testIPv6Endpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber {
-	return header.IPv6ProtocolNumber
-}
-
-func (e *testIPv6Endpoint) InvalidateDefaultRouter(rtr tcpip.Address) {
-	e.invalidatedRtr = rtr
-}
-
-// Stats implements NetworkEndpoint.
-func (*testIPv6Endpoint) Stats() NetworkEndpointStats {
-	return &testIPv6EndpointStats{}
-}
-
-var _ NetworkEndpointStats = (*testIPv6EndpointStats)(nil)
-
-type testIPv6EndpointStats struct{}
-
-// IsNetworkEndpointStats implements stack.NetworkEndpointStats.
-func (*testIPv6EndpointStats) IsNetworkEndpointStats() {}
-
-// We use this instead of ipv6.protocol because the ipv6 package depends on
-// the stack package which this test lives in, causing a cyclic dependency.
-type testIPv6Protocol struct{}
-
-// Number implements NetworkProtocol.Number.
-func (*testIPv6Protocol) Number() tcpip.NetworkProtocolNumber {
-	return header.IPv6ProtocolNumber
-}
-
-// MinimumPacketSize implements NetworkProtocol.MinimumPacketSize.
-func (*testIPv6Protocol) MinimumPacketSize() int {
-	return header.IPv6MinimumSize
-}
-
-// DefaultPrefixLen implements NetworkProtocol.DefaultPrefixLen.
-func (*testIPv6Protocol) DefaultPrefixLen() int {
-	return header.IPv6AddressSize * 8
-}
-
-// ParseAddresses implements NetworkProtocol.ParseAddresses.
-func (*testIPv6Protocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) {
-	h := header.IPv6(v)
-	return h.SourceAddress(), h.DestinationAddress()
-}
-
-// NewEndpoint implements NetworkProtocol.NewEndpoint.
-func (p *testIPv6Protocol) NewEndpoint(nic NetworkInterface, _ TransportDispatcher) NetworkEndpoint {
-	e := &testIPv6Endpoint{
-		nic:      nic,
-		protocol: p,
-	}
-	e.AddressableEndpointState.Init(e)
-	return e
-}
-
-// SetOption implements NetworkProtocol.SetOption.
-func (*testIPv6Protocol) SetOption(tcpip.SettableNetworkProtocolOption) tcpip.Error {
-	return nil
-}
-
-// Option implements NetworkProtocol.Option.
-func (*testIPv6Protocol) Option(tcpip.GettableNetworkProtocolOption) tcpip.Error {
-	return nil
-}
-
-// Close implements NetworkProtocol.Close.
-func (*testIPv6Protocol) Close() {}
-
-// Wait implements NetworkProtocol.Wait.
-func (*testIPv6Protocol) Wait() {}
-
-// Parse implements NetworkProtocol.Parse.
-func (*testIPv6Protocol) Parse(*PacketBuffer) (tcpip.TransportProtocolNumber, bool, bool) {
-	return 0, false, false
-}
-
-func TestDisabledRxStatsWhenNICDisabled(t *testing.T) {
-	// When the NIC is disabled, the only field that matters is the stats field.
-	// This test is limited to stats counter checks.
-	nic := nic{
-		stats: makeNICStats(),
-	}
-
-	if got := nic.stats.DisabledRx.Packets.Value(); got != 0 {
-		t.Errorf("got DisabledRx.Packets = %d, want = 0", got)
-	}
-	if got := nic.stats.DisabledRx.Bytes.Value(); got != 0 {
-		t.Errorf("got DisabledRx.Bytes = %d, want = 0", got)
-	}
-	if got := nic.stats.Rx.Packets.Value(); got != 0 {
-		t.Errorf("got Rx.Packets = %d, want = 0", got)
-	}
-	if got := nic.stats.Rx.Bytes.Value(); got != 0 {
-		t.Errorf("got Rx.Bytes = %d, want = 0", got)
-	}
-
-	if t.Failed() {
-		t.FailNow()
-	}
-
-	nic.DeliverNetworkPacket("", "", 0, NewPacketBuffer(PacketBufferOptions{
-		Data: buffer.View([]byte{1, 2, 3, 4}).ToVectorisedView(),
-	}))
-
-	if got := nic.stats.DisabledRx.Packets.Value(); got != 1 {
-		t.Errorf("got DisabledRx.Packets = %d, want = 1", got)
-	}
-	if got := nic.stats.DisabledRx.Bytes.Value(); got != 4 {
-		t.Errorf("got DisabledRx.Bytes = %d, want = 4", got)
-	}
-	if got := nic.stats.Rx.Packets.Value(); got != 0 {
-		t.Errorf("got Rx.Packets = %d, want = 0", got)
-	}
-	if got := nic.stats.Rx.Bytes.Value(); got != 0 {
-		t.Errorf("got Rx.Bytes = %d, want = 0", got)
-	}
-}
diff --git a/pkg/tcpip/stack/nud_test.go b/pkg/tcpip/stack/nud_test.go
deleted file mode 100644
index e1253f3..0000000
--- a/pkg/tcpip/stack/nud_test.go
+++ /dev/null
@@ -1,813 +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 stack_test
-
-import (
-	"math"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/faketime"
-	"gvisor.dev/gvisor/pkg/tcpip/link/channel"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-)
-
-const (
-	defaultBaseReachableTime           = 30 * time.Second
-	minimumBaseReachableTime           = time.Millisecond
-	defaultMinRandomFactor             = 0.5
-	defaultMaxRandomFactor             = 1.5
-	defaultRetransmitTimer             = time.Second
-	minimumRetransmitTimer             = time.Millisecond
-	defaultDelayFirstProbeTime         = 5 * time.Second
-	defaultMaxMulticastProbes          = 3
-	defaultMaxUnicastProbes            = 3
-	defaultMaxAnycastDelayTime         = time.Second
-	defaultMaxReachbilityConfirmations = 3
-
-	defaultFakeRandomNum = 0.5
-)
-
-// fakeRand is a deterministic random number generator.
-type fakeRand struct {
-	num float32
-}
-
-var _ stack.Rand = (*fakeRand)(nil)
-
-func (f *fakeRand) Float32() float32 {
-	return f.num
-}
-
-func TestNUDFunctions(t *testing.T) {
-	const nicID = 1
-
-	tests := []struct {
-		name                  string
-		nicID                 tcpip.NICID
-		netProtoFactory       []stack.NetworkProtocolFactory
-		extraLinkCapabilities stack.LinkEndpointCapabilities
-		expectedErr           tcpip.Error
-	}{
-		{
-			name:                  "Invalid NICID",
-			nicID:                 nicID + 1,
-			netProtoFactory:       []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-			extraLinkCapabilities: stack.CapabilityResolutionRequired,
-			expectedErr:           &tcpip.ErrUnknownNICID{},
-		},
-		{
-			name:        "No network protocol",
-			nicID:       nicID,
-			expectedErr: &tcpip.ErrNotSupported{},
-		},
-		{
-			name:            "With IPv6",
-			nicID:           nicID,
-			netProtoFactory: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-			expectedErr:     &tcpip.ErrNotSupported{},
-		},
-		{
-			name:                  "With resolution capability",
-			nicID:                 nicID,
-			extraLinkCapabilities: stack.CapabilityResolutionRequired,
-			expectedErr:           &tcpip.ErrNotSupported{},
-		},
-		{
-			name:                  "With IPv6 and resolution capability",
-			nicID:                 nicID,
-			netProtoFactory:       []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-			extraLinkCapabilities: stack.CapabilityResolutionRequired,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			clock := faketime.NewManualClock()
-			s := stack.New(stack.Options{
-				NUDConfigs:       stack.DefaultNUDConfigurations(),
-				NetworkProtocols: test.netProtoFactory,
-				Clock:            clock,
-			})
-
-			e := channel.New(0, 0, linkAddr1)
-			e.LinkEPCapabilities &^= stack.CapabilityResolutionRequired
-			e.LinkEPCapabilities |= test.extraLinkCapabilities
-
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			configs := stack.DefaultNUDConfigurations()
-			configs.BaseReachableTime = time.Hour
-
-			{
-				err := s.SetNUDConfigurations(test.nicID, ipv6.ProtocolNumber, configs)
-				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
-					t.Errorf("s.SetNUDConfigurations(%d, %d, _) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
-				}
-			}
-
-			{
-				gotConfigs, err := s.NUDConfigurations(test.nicID, ipv6.ProtocolNumber)
-				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
-					t.Errorf("s.NUDConfigurations(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
-				} else if test.expectedErr == nil {
-					if diff := cmp.Diff(configs, gotConfigs); diff != "" {
-						t.Errorf("got configs mismatch (-want +got):\n%s", diff)
-					}
-				}
-			}
-
-			for _, addr := range []tcpip.Address{llAddr1, llAddr2} {
-				{
-					err := s.AddStaticNeighbor(test.nicID, ipv6.ProtocolNumber, addr, linkAddr1)
-					if diff := cmp.Diff(test.expectedErr, err); diff != "" {
-						t.Errorf("s.AddStaticNeighbor(%d, %d, %s, %s) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, addr, linkAddr1, diff)
-					}
-				}
-			}
-
-			{
-				wantErr := test.expectedErr
-				for i := 0; i < 2; i++ {
-					{
-						err := s.RemoveNeighbor(test.nicID, ipv6.ProtocolNumber, llAddr1)
-						if diff := cmp.Diff(wantErr, err); diff != "" {
-							t.Errorf("s.RemoveNeighbor(%d, %d, '') error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
-						}
-					}
-
-					if test.expectedErr != nil {
-						break
-					}
-
-					// Removing a neighbor that does not exist should give us a bad address
-					// error.
-					wantErr = &tcpip.ErrBadAddress{}
-				}
-			}
-
-			{
-				neighbors, err := s.Neighbors(test.nicID, ipv6.ProtocolNumber)
-				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
-					t.Errorf("s.Neigbors(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
-				} else if test.expectedErr == nil {
-					if diff := cmp.Diff(
-						[]stack.NeighborEntry{{Addr: llAddr2, LinkAddr: linkAddr1, State: stack.Static, UpdatedAtNanos: clock.NowNanoseconds()}},
-						neighbors,
-					); diff != "" {
-						t.Errorf("neighbors mismatch (-want +got):\n%s", diff)
-					}
-				}
-			}
-
-			{
-				err := s.ClearNeighbors(test.nicID, ipv6.ProtocolNumber)
-				if diff := cmp.Diff(test.expectedErr, err); diff != "" {
-					t.Errorf("s.ClearNeigbors(%d, %d) error mismatch (-want +got):\n%s", test.nicID, ipv6.ProtocolNumber, diff)
-				} else if test.expectedErr == nil {
-					if neighbors, err := s.Neighbors(test.nicID, ipv6.ProtocolNumber); err != nil {
-						t.Errorf("s.Neighbors(%d, %d): %s", test.nicID, ipv6.ProtocolNumber, err)
-					} else if len(neighbors) != 0 {
-						t.Errorf("got len(neighbors) = %d, want = 0; neighbors = %#v", len(neighbors), neighbors)
-					}
-				}
-			}
-		})
-	}
-}
-
-func TestDefaultNUDConfigurations(t *testing.T) {
-	const nicID = 1
-
-	e := channel.New(0, 1280, linkAddr1)
-	e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-	s := stack.New(stack.Options{
-		// A neighbor cache is required to store NUDConfigurations. The networking
-		// stack will only allocate neighbor caches if a protocol providing link
-		// address resolution is specified (e.g. ARP or IPv6).
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-		NUDConfigs:       stack.DefaultNUDConfigurations(),
-	})
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-	c, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
-	if err != nil {
-		t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
-	}
-	if got, want := c, stack.DefaultNUDConfigurations(); got != want {
-		t.Errorf("got stack.NUDConfigurations(%d, %d) = %+v, want = %+v", nicID, ipv6.ProtocolNumber, got, want)
-	}
-}
-
-func TestNUDConfigurationsBaseReachableTime(t *testing.T) {
-	tests := []struct {
-		name              string
-		baseReachableTime time.Duration
-		want              time.Duration
-	}{
-		// Invalid cases
-		{
-			name:              "EqualToZero",
-			baseReachableTime: 0,
-			want:              defaultBaseReachableTime,
-		},
-		// Valid cases
-		{
-			name:              "MoreThanZero",
-			baseReachableTime: time.Millisecond,
-			want:              time.Millisecond,
-		},
-		{
-			name:              "MoreThanDefaultBaseReachableTime",
-			baseReachableTime: 2 * defaultBaseReachableTime,
-			want:              2 * defaultBaseReachableTime,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			const nicID = 1
-
-			c := stack.DefaultNUDConfigurations()
-			c.BaseReachableTime = test.baseReachableTime
-
-			e := channel.New(0, 1280, linkAddr1)
-			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-			s := stack.New(stack.Options{
-				// A neighbor cache is required to store NUDConfigurations. The
-				// networking stack will only allocate neighbor caches if a protocol
-				// providing link address resolution is specified (e.g. ARP or IPv6).
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-				NUDConfigs:       c,
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
-			}
-			if got := sc.BaseReachableTime; got != test.want {
-				t.Errorf("got BaseReachableTime = %q, want = %q", got, test.want)
-			}
-		})
-	}
-}
-
-func TestNUDConfigurationsMinRandomFactor(t *testing.T) {
-	tests := []struct {
-		name            string
-		minRandomFactor float32
-		want            float32
-	}{
-		// Invalid cases
-		{
-			name:            "LessThanZero",
-			minRandomFactor: -1,
-			want:            defaultMinRandomFactor,
-		},
-		{
-			name:            "EqualToZero",
-			minRandomFactor: 0,
-			want:            defaultMinRandomFactor,
-		},
-		// Valid cases
-		{
-			name:            "MoreThanZero",
-			minRandomFactor: 1,
-			want:            1,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			const nicID = 1
-
-			c := stack.DefaultNUDConfigurations()
-			c.MinRandomFactor = test.minRandomFactor
-
-			e := channel.New(0, 1280, linkAddr1)
-			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-			s := stack.New(stack.Options{
-				// A neighbor cache is required to store NUDConfigurations. The
-				// networking stack will only allocate neighbor caches if a protocol
-				// providing link address resolution is specified (e.g. ARP or IPv6).
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-				NUDConfigs:       c,
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
-			}
-			if got := sc.MinRandomFactor; got != test.want {
-				t.Errorf("got MinRandomFactor = %f, want = %f", got, test.want)
-			}
-		})
-	}
-}
-
-func TestNUDConfigurationsMaxRandomFactor(t *testing.T) {
-	tests := []struct {
-		name            string
-		minRandomFactor float32
-		maxRandomFactor float32
-		want            float32
-	}{
-		// Invalid cases
-		{
-			name:            "LessThanZero",
-			minRandomFactor: defaultMinRandomFactor,
-			maxRandomFactor: -1,
-			want:            defaultMaxRandomFactor,
-		},
-		{
-			name:            "EqualToZero",
-			minRandomFactor: defaultMinRandomFactor,
-			maxRandomFactor: 0,
-			want:            defaultMaxRandomFactor,
-		},
-		{
-			name:            "LessThanMinRandomFactor",
-			minRandomFactor: defaultMinRandomFactor,
-			maxRandomFactor: defaultMinRandomFactor * 0.99,
-			want:            defaultMaxRandomFactor,
-		},
-		{
-			name:            "MoreThanMinRandomFactorWhenMinRandomFactorIsLargerThanMaxRandomFactorDefault",
-			minRandomFactor: defaultMaxRandomFactor * 2,
-			maxRandomFactor: defaultMaxRandomFactor,
-			want:            defaultMaxRandomFactor * 6,
-		},
-		// Valid cases
-		{
-			name:            "EqualToMinRandomFactor",
-			minRandomFactor: defaultMinRandomFactor,
-			maxRandomFactor: defaultMinRandomFactor,
-			want:            defaultMinRandomFactor,
-		},
-		{
-			name:            "MoreThanMinRandomFactor",
-			minRandomFactor: defaultMinRandomFactor,
-			maxRandomFactor: defaultMinRandomFactor * 1.1,
-			want:            defaultMinRandomFactor * 1.1,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			const nicID = 1
-
-			c := stack.DefaultNUDConfigurations()
-			c.MinRandomFactor = test.minRandomFactor
-			c.MaxRandomFactor = test.maxRandomFactor
-
-			e := channel.New(0, 1280, linkAddr1)
-			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-			s := stack.New(stack.Options{
-				// A neighbor cache is required to store NUDConfigurations. The
-				// networking stack will only allocate neighbor caches if a protocol
-				// providing link address resolution is specified (e.g. ARP or IPv6).
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-				NUDConfigs:       c,
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
-			}
-			if got := sc.MaxRandomFactor; got != test.want {
-				t.Errorf("got MaxRandomFactor = %f, want = %f", got, test.want)
-			}
-		})
-	}
-}
-
-func TestNUDConfigurationsRetransmitTimer(t *testing.T) {
-	tests := []struct {
-		name            string
-		retransmitTimer time.Duration
-		want            time.Duration
-	}{
-		// Invalid cases
-		{
-			name:            "EqualToZero",
-			retransmitTimer: 0,
-			want:            defaultRetransmitTimer,
-		},
-		{
-			name:            "LessThanMinimumRetransmitTimer",
-			retransmitTimer: minimumRetransmitTimer - time.Nanosecond,
-			want:            defaultRetransmitTimer,
-		},
-		// Valid cases
-		{
-			name:            "EqualToMinimumRetransmitTimer",
-			retransmitTimer: minimumRetransmitTimer,
-			want:            minimumBaseReachableTime,
-		},
-		{
-			name:            "LargetThanMinimumRetransmitTimer",
-			retransmitTimer: 2 * minimumBaseReachableTime,
-			want:            2 * minimumBaseReachableTime,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			const nicID = 1
-
-			c := stack.DefaultNUDConfigurations()
-			c.RetransmitTimer = test.retransmitTimer
-
-			e := channel.New(0, 1280, linkAddr1)
-			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-			s := stack.New(stack.Options{
-				// A neighbor cache is required to store NUDConfigurations. The
-				// networking stack will only allocate neighbor caches if a protocol
-				// providing link address resolution is specified (e.g. ARP or IPv6).
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-				NUDConfigs:       c,
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
-			}
-			if got := sc.RetransmitTimer; got != test.want {
-				t.Errorf("got RetransmitTimer = %q, want = %q", got, test.want)
-			}
-		})
-	}
-}
-
-func TestNUDConfigurationsDelayFirstProbeTime(t *testing.T) {
-	tests := []struct {
-		name                string
-		delayFirstProbeTime time.Duration
-		want                time.Duration
-	}{
-		// Invalid cases
-		{
-			name:                "EqualToZero",
-			delayFirstProbeTime: 0,
-			want:                defaultDelayFirstProbeTime,
-		},
-		// Valid cases
-		{
-			name:                "MoreThanZero",
-			delayFirstProbeTime: time.Millisecond,
-			want:                time.Millisecond,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			const nicID = 1
-
-			c := stack.DefaultNUDConfigurations()
-			c.DelayFirstProbeTime = test.delayFirstProbeTime
-
-			e := channel.New(0, 1280, linkAddr1)
-			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-			s := stack.New(stack.Options{
-				// A neighbor cache is required to store NUDConfigurations. The
-				// networking stack will only allocate neighbor caches if a protocol
-				// providing link address resolution is specified (e.g. ARP or IPv6).
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-				NUDConfigs:       c,
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
-			}
-			if got := sc.DelayFirstProbeTime; got != test.want {
-				t.Errorf("got DelayFirstProbeTime = %q, want = %q", got, test.want)
-			}
-		})
-	}
-}
-
-func TestNUDConfigurationsMaxMulticastProbes(t *testing.T) {
-	tests := []struct {
-		name               string
-		maxMulticastProbes uint32
-		want               uint32
-	}{
-		// Invalid cases
-		{
-			name:               "EqualToZero",
-			maxMulticastProbes: 0,
-			want:               defaultMaxMulticastProbes,
-		},
-		// Valid cases
-		{
-			name:               "MoreThanZero",
-			maxMulticastProbes: 1,
-			want:               1,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			const nicID = 1
-
-			c := stack.DefaultNUDConfigurations()
-			c.MaxMulticastProbes = test.maxMulticastProbes
-
-			e := channel.New(0, 1280, linkAddr1)
-			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-			s := stack.New(stack.Options{
-				// A neighbor cache is required to store NUDConfigurations. The
-				// networking stack will only allocate neighbor caches if a protocol
-				// providing link address resolution is specified (e.g. ARP or IPv6).
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-				NUDConfigs:       c,
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
-			}
-			if got := sc.MaxMulticastProbes; got != test.want {
-				t.Errorf("got MaxMulticastProbes = %q, want = %q", got, test.want)
-			}
-		})
-	}
-}
-
-func TestNUDConfigurationsMaxUnicastProbes(t *testing.T) {
-	tests := []struct {
-		name             string
-		maxUnicastProbes uint32
-		want             uint32
-	}{
-		// Invalid cases
-		{
-			name:             "EqualToZero",
-			maxUnicastProbes: 0,
-			want:             defaultMaxUnicastProbes,
-		},
-		// Valid cases
-		{
-			name:             "MoreThanZero",
-			maxUnicastProbes: 1,
-			want:             1,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			const nicID = 1
-
-			c := stack.DefaultNUDConfigurations()
-			c.MaxUnicastProbes = test.maxUnicastProbes
-
-			e := channel.New(0, 1280, linkAddr1)
-			e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-
-			s := stack.New(stack.Options{
-				// A neighbor cache is required to store NUDConfigurations. The
-				// networking stack will only allocate neighbor caches if a protocol
-				// providing link address resolution is specified (e.g. ARP or IPv6).
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-				NUDConfigs:       c,
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-			sc, err := s.NUDConfigurations(nicID, ipv6.ProtocolNumber)
-			if err != nil {
-				t.Fatalf("got stack.NUDConfigurations(%d, %d) = %s", nicID, ipv6.ProtocolNumber, err)
-			}
-			if got := sc.MaxUnicastProbes; got != test.want {
-				t.Errorf("got MaxUnicastProbes = %q, want = %q", got, test.want)
-			}
-		})
-	}
-}
-
-// TestNUDStateReachableTime verifies the correctness of the ReachableTime
-// computation.
-func TestNUDStateReachableTime(t *testing.T) {
-	tests := []struct {
-		name              string
-		baseReachableTime time.Duration
-		minRandomFactor   float32
-		maxRandomFactor   float32
-		want              time.Duration
-	}{
-		{
-			name:              "AllZeros",
-			baseReachableTime: 0,
-			minRandomFactor:   0,
-			maxRandomFactor:   0,
-			want:              0,
-		},
-		{
-			name:              "ZeroMaxRandomFactor",
-			baseReachableTime: time.Second,
-			minRandomFactor:   0,
-			maxRandomFactor:   0,
-			want:              0,
-		},
-		{
-			name:              "ZeroMinRandomFactor",
-			baseReachableTime: time.Second,
-			minRandomFactor:   0,
-			maxRandomFactor:   1,
-			want:              time.Duration(defaultFakeRandomNum * float32(time.Second)),
-		},
-		{
-			name:              "FractionalRandomFactor",
-			baseReachableTime: time.Duration(math.MaxInt64),
-			minRandomFactor:   0.001,
-			maxRandomFactor:   0.002,
-			want:              time.Duration((0.001 + (0.001 * defaultFakeRandomNum)) * float32(math.MaxInt64)),
-		},
-		{
-			name:              "MinAndMaxRandomFactorsEqual",
-			baseReachableTime: time.Second,
-			minRandomFactor:   1,
-			maxRandomFactor:   1,
-			want:              time.Second,
-		},
-		{
-			name:              "MinAndMaxRandomFactorsDifferent",
-			baseReachableTime: time.Second,
-			minRandomFactor:   1,
-			maxRandomFactor:   2,
-			want:              time.Duration((1.0 + defaultFakeRandomNum) * float32(time.Second)),
-		},
-		{
-			name:              "MaxInt64",
-			baseReachableTime: time.Duration(math.MaxInt64),
-			minRandomFactor:   1,
-			maxRandomFactor:   1,
-			want:              time.Duration(math.MaxInt64),
-		},
-		{
-			name:              "Overflow",
-			baseReachableTime: time.Duration(math.MaxInt64),
-			minRandomFactor:   1.5,
-			maxRandomFactor:   1.5,
-			want:              time.Duration(math.MaxInt64),
-		},
-		{
-			name:              "DoubleOverflow",
-			baseReachableTime: time.Duration(math.MaxInt64),
-			minRandomFactor:   2.5,
-			maxRandomFactor:   2.5,
-			want:              time.Duration(math.MaxInt64),
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			c := stack.NUDConfigurations{
-				BaseReachableTime: test.baseReachableTime,
-				MinRandomFactor:   test.minRandomFactor,
-				MaxRandomFactor:   test.maxRandomFactor,
-			}
-			// A fake random number generator is used to ensure deterministic
-			// results.
-			rng := fakeRand{
-				num: defaultFakeRandomNum,
-			}
-			s := stack.NewNUDState(c, &rng)
-			if got, want := s.ReachableTime(), test.want; got != want {
-				t.Errorf("got ReachableTime = %q, want = %q", got, want)
-			}
-		})
-	}
-}
-
-// TestNUDStateRecomputeReachableTime exercises the ReachableTime function
-// twice to verify recomputation of reachable time when the min random factor,
-// max random factor, or base reachable time changes.
-func TestNUDStateRecomputeReachableTime(t *testing.T) {
-	const defaultBase = time.Second
-	const defaultMin = 2.0 * defaultMaxRandomFactor
-	const defaultMax = 3.0 * defaultMaxRandomFactor
-
-	tests := []struct {
-		name              string
-		baseReachableTime time.Duration
-		minRandomFactor   float32
-		maxRandomFactor   float32
-		want              time.Duration
-	}{
-		{
-			name:              "BaseReachableTime",
-			baseReachableTime: 2 * defaultBase,
-			minRandomFactor:   defaultMin,
-			maxRandomFactor:   defaultMax,
-			want:              time.Duration((defaultMin + (defaultMax-defaultMin)*defaultFakeRandomNum) * float32(2*defaultBase)),
-		},
-		{
-			name:              "MinRandomFactor",
-			baseReachableTime: defaultBase,
-			minRandomFactor:   defaultMax,
-			maxRandomFactor:   defaultMax,
-			want:              time.Duration(defaultMax * float32(defaultBase)),
-		},
-		{
-			name:              "MaxRandomFactor",
-			baseReachableTime: defaultBase,
-			minRandomFactor:   defaultMin,
-			maxRandomFactor:   defaultMin,
-			want:              time.Duration(defaultMin * float32(defaultBase)),
-		},
-		{
-			name:              "BothRandomFactor",
-			baseReachableTime: defaultBase,
-			minRandomFactor:   2 * defaultMin,
-			maxRandomFactor:   2 * defaultMax,
-			want:              time.Duration((2*defaultMin + (2*defaultMax-2*defaultMin)*defaultFakeRandomNum) * float32(defaultBase)),
-		},
-		{
-			name:              "BaseReachableTimeAndBothRandomFactors",
-			baseReachableTime: 2 * defaultBase,
-			minRandomFactor:   2 * defaultMin,
-			maxRandomFactor:   2 * defaultMax,
-			want:              time.Duration((2*defaultMin + (2*defaultMax-2*defaultMin)*defaultFakeRandomNum) * float32(2*defaultBase)),
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			c := stack.DefaultNUDConfigurations()
-			c.BaseReachableTime = defaultBase
-			c.MinRandomFactor = defaultMin
-			c.MaxRandomFactor = defaultMax
-
-			// A fake random number generator is used to ensure deterministic
-			// results.
-			rng := fakeRand{
-				num: defaultFakeRandomNum,
-			}
-			s := stack.NewNUDState(c, &rng)
-			old := s.ReachableTime()
-
-			if got, want := s.ReachableTime(), old; got != want {
-				t.Errorf("got ReachableTime = %q, want = %q", got, want)
-			}
-
-			// Check for recomputation when changing the min random factor, the max
-			// random factor, the base reachability time, or any permutation of those
-			// three options.
-			c.BaseReachableTime = test.baseReachableTime
-			c.MinRandomFactor = test.minRandomFactor
-			c.MaxRandomFactor = test.maxRandomFactor
-			s.SetConfig(c)
-
-			if got, want := s.ReachableTime(), test.want; got != want {
-				t.Errorf("got ReachableTime = %q, want = %q", got, want)
-			}
-
-			// Verify that ReachableTime isn't recomputed when none of the
-			// configuration options change. The random factor is changed so that if
-			// a recompution were to occur, ReachableTime would change.
-			rng.num = defaultFakeRandomNum / 2.0
-			if got, want := s.ReachableTime(), test.want; got != want {
-				t.Errorf("got ReachableTime = %q, want = %q", got, want)
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/stack/packet_buffer_list.go b/pkg/tcpip/stack/packet_buffer_list.go
new file mode 100644
index 0000000..ce7057d
--- /dev/null
+++ b/pkg/tcpip/stack/packet_buffer_list.go
@@ -0,0 +1,221 @@
+package stack
+
+// 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 PacketBufferElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (PacketBufferElementMapper) linkerFor(elem *PacketBuffer) *PacketBuffer { 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 PacketBufferList struct {
+	head *PacketBuffer
+	tail *PacketBuffer
+}
+
+// Reset resets list l to the empty state.
+func (l *PacketBufferList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *PacketBufferList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *PacketBufferList) Front() *PacketBuffer {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *PacketBufferList) Back() *PacketBuffer {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *PacketBufferList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (PacketBufferElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *PacketBufferList) PushFront(e *PacketBuffer) {
+	linker := PacketBufferElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		PacketBufferElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *PacketBufferList) PushBack(e *PacketBuffer) {
+	linker := PacketBufferElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		PacketBufferElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *PacketBufferList) PushBackList(m *PacketBufferList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		PacketBufferElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		PacketBufferElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *PacketBufferList) InsertAfter(b, e *PacketBuffer) {
+	bLinker := PacketBufferElementMapper{}.linkerFor(b)
+	eLinker := PacketBufferElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		PacketBufferElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *PacketBufferList) InsertBefore(a, e *PacketBuffer) {
+	aLinker := PacketBufferElementMapper{}.linkerFor(a)
+	eLinker := PacketBufferElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		PacketBufferElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *PacketBufferList) Remove(e *PacketBuffer) {
+	linker := PacketBufferElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		PacketBufferElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		PacketBufferElementMapper{}.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 PacketBufferEntry struct {
+	next *PacketBuffer
+	prev *PacketBuffer
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *PacketBufferEntry) Next() *PacketBuffer {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *PacketBufferEntry) Prev() *PacketBuffer {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *PacketBufferEntry) SetNext(elem *PacketBuffer) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *PacketBufferEntry) SetPrev(elem *PacketBuffer) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/stack/packet_buffer_test.go b/pkg/tcpip/stack/packet_buffer_test.go
deleted file mode 100644
index 6728370..0000000
--- a/pkg/tcpip/stack/packet_buffer_test.go
+++ /dev/null
@@ -1,615 +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 stack
-
-import (
-	"bytes"
-	"fmt"
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-)
-
-func TestPacketHeaderPush(t *testing.T) {
-	for _, test := range []struct {
-		name      string
-		reserved  int
-		link      []byte
-		network   []byte
-		transport []byte
-		data      []byte
-	}{
-		{
-			name: "construct empty packet",
-		},
-		{
-			name:     "construct link header only packet",
-			reserved: 60,
-			link:     makeView(10),
-		},
-		{
-			name:     "construct link and network header only packet",
-			reserved: 60,
-			link:     makeView(10),
-			network:  makeView(20),
-		},
-		{
-			name:      "construct header only packet",
-			reserved:  60,
-			link:      makeView(10),
-			network:   makeView(20),
-			transport: makeView(30),
-		},
-		{
-			name: "construct data only packet",
-			data: makeView(40),
-		},
-		{
-			name:      "construct L3 packet",
-			reserved:  60,
-			network:   makeView(20),
-			transport: makeView(30),
-			data:      makeView(40),
-		},
-		{
-			name:      "construct L2 packet",
-			reserved:  60,
-			link:      makeView(10),
-			network:   makeView(20),
-			transport: makeView(30),
-			data:      makeView(40),
-		},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			pk := NewPacketBuffer(PacketBufferOptions{
-				ReserveHeaderBytes: test.reserved,
-				// Make a copy of data to make sure our truth data won't be taint by
-				// PacketBuffer.
-				Data: buffer.NewViewFromBytes(test.data).ToVectorisedView(),
-			})
-
-			allHdrSize := len(test.link) + len(test.network) + len(test.transport)
-
-			// Check the initial values for packet.
-			checkInitialPacketBuffer(t, pk, PacketBufferOptions{
-				ReserveHeaderBytes: test.reserved,
-				Data:               buffer.View(test.data).ToVectorisedView(),
-			})
-
-			// Push headers.
-			if v := test.transport; len(v) > 0 {
-				copy(pk.TransportHeader().Push(len(v)), v)
-			}
-			if v := test.network; len(v) > 0 {
-				copy(pk.NetworkHeader().Push(len(v)), v)
-			}
-			if v := test.link; len(v) > 0 {
-				copy(pk.LinkHeader().Push(len(v)), v)
-			}
-
-			// Check the after values for packet.
-			if got, want := pk.ReservedHeaderBytes(), test.reserved; got != want {
-				t.Errorf("After pk.ReservedHeaderBytes() = %d, want %d", got, want)
-			}
-			if got, want := pk.AvailableHeaderBytes(), test.reserved-allHdrSize; got != want {
-				t.Errorf("After pk.AvailableHeaderBytes() = %d, want %d", got, want)
-			}
-			if got, want := pk.HeaderSize(), allHdrSize; got != want {
-				t.Errorf("After pk.HeaderSize() = %d, want %d", got, want)
-			}
-			if got, want := pk.Size(), allHdrSize+len(test.data); got != want {
-				t.Errorf("After pk.Size() = %d, want %d", got, want)
-			}
-			checkData(t, pk, test.data)
-			checkViewEqual(t, "After pk.Views()", concatViews(pk.Views()...),
-				concatViews(test.link, test.network, test.transport, test.data))
-			// Check the after values for each header.
-			checkPacketHeader(t, "After pk.LinkHeader", pk.LinkHeader(), test.link)
-			checkPacketHeader(t, "After pk.NetworkHeader", pk.NetworkHeader(), test.network)
-			checkPacketHeader(t, "After pk.TransportHeader", pk.TransportHeader(), test.transport)
-			// Check the after values for PayloadSince.
-			checkViewEqual(t, "After PayloadSince(LinkHeader)",
-				PayloadSince(pk.LinkHeader()),
-				concatViews(test.link, test.network, test.transport, test.data))
-			checkViewEqual(t, "After PayloadSince(NetworkHeader)",
-				PayloadSince(pk.NetworkHeader()),
-				concatViews(test.network, test.transport, test.data))
-			checkViewEqual(t, "After PayloadSince(TransportHeader)",
-				PayloadSince(pk.TransportHeader()),
-				concatViews(test.transport, test.data))
-		})
-	}
-}
-
-func TestPacketHeaderConsume(t *testing.T) {
-	for _, test := range []struct {
-		name      string
-		data      []byte
-		link      int
-		network   int
-		transport int
-	}{
-		{
-			name:      "parse L2 packet",
-			data:      concatViews(makeView(10), makeView(20), makeView(30), makeView(40)),
-			link:      10,
-			network:   20,
-			transport: 30,
-		},
-		{
-			name:      "parse L3 packet",
-			data:      concatViews(makeView(20), makeView(30), makeView(40)),
-			network:   20,
-			transport: 30,
-		},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			pk := NewPacketBuffer(PacketBufferOptions{
-				// Make a copy of data to make sure our truth data won't be taint by
-				// PacketBuffer.
-				Data: buffer.NewViewFromBytes(test.data).ToVectorisedView(),
-			})
-
-			// Check the initial values for packet.
-			checkInitialPacketBuffer(t, pk, PacketBufferOptions{
-				Data: buffer.View(test.data).ToVectorisedView(),
-			})
-
-			// Consume headers.
-			if size := test.link; size > 0 {
-				if _, ok := pk.LinkHeader().Consume(size); !ok {
-					t.Fatalf("pk.LinkHeader().Consume() = false, want true")
-				}
-			}
-			if size := test.network; size > 0 {
-				if _, ok := pk.NetworkHeader().Consume(size); !ok {
-					t.Fatalf("pk.NetworkHeader().Consume() = false, want true")
-				}
-			}
-			if size := test.transport; size > 0 {
-				if _, ok := pk.TransportHeader().Consume(size); !ok {
-					t.Fatalf("pk.TransportHeader().Consume() = false, want true")
-				}
-			}
-
-			allHdrSize := test.link + test.network + test.transport
-
-			// Check the after values for packet.
-			if got, want := pk.ReservedHeaderBytes(), 0; got != want {
-				t.Errorf("After pk.ReservedHeaderBytes() = %d, want %d", got, want)
-			}
-			if got, want := pk.AvailableHeaderBytes(), 0; got != want {
-				t.Errorf("After pk.AvailableHeaderBytes() = %d, want %d", got, want)
-			}
-			if got, want := pk.HeaderSize(), allHdrSize; got != want {
-				t.Errorf("After pk.HeaderSize() = %d, want %d", got, want)
-			}
-			if got, want := pk.Size(), len(test.data); got != want {
-				t.Errorf("After pk.Size() = %d, want %d", got, want)
-			}
-			// After state of pk.
-			var (
-				link      = test.data[:test.link]
-				network   = test.data[test.link:][:test.network]
-				transport = test.data[test.link+test.network:][:test.transport]
-				payload   = test.data[allHdrSize:]
-			)
-			checkData(t, pk, payload)
-			checkViewEqual(t, "After pk.Views()", concatViews(pk.Views()...), test.data)
-			// Check the after values for each header.
-			checkPacketHeader(t, "After pk.LinkHeader", pk.LinkHeader(), link)
-			checkPacketHeader(t, "After pk.NetworkHeader", pk.NetworkHeader(), network)
-			checkPacketHeader(t, "After pk.TransportHeader", pk.TransportHeader(), transport)
-			// Check the after values for PayloadSince.
-			checkViewEqual(t, "After PayloadSince(LinkHeader)",
-				PayloadSince(pk.LinkHeader()),
-				concatViews(link, network, transport, payload))
-			checkViewEqual(t, "After PayloadSince(NetworkHeader)",
-				PayloadSince(pk.NetworkHeader()),
-				concatViews(network, transport, payload))
-			checkViewEqual(t, "After PayloadSince(TransportHeader)",
-				PayloadSince(pk.TransportHeader()),
-				concatViews(transport, payload))
-		})
-	}
-}
-
-func TestPacketHeaderConsumeDataTooShort(t *testing.T) {
-	data := makeView(10)
-
-	pk := NewPacketBuffer(PacketBufferOptions{
-		// Make a copy of data to make sure our truth data won't be taint by
-		// PacketBuffer.
-		Data: buffer.NewViewFromBytes(data).ToVectorisedView(),
-	})
-
-	// Consume should fail if pkt.Data is too short.
-	if _, ok := pk.LinkHeader().Consume(11); ok {
-		t.Fatalf("pk.LinkHeader().Consume() = _, true; want _, false")
-	}
-	if _, ok := pk.NetworkHeader().Consume(11); ok {
-		t.Fatalf("pk.NetworkHeader().Consume() = _, true; want _, false")
-	}
-	if _, ok := pk.TransportHeader().Consume(11); ok {
-		t.Fatalf("pk.TransportHeader().Consume() = _, true; want _, false")
-	}
-
-	// Check packet should look the same as initial packet.
-	checkInitialPacketBuffer(t, pk, PacketBufferOptions{
-		Data: buffer.View(data).ToVectorisedView(),
-	})
-}
-
-func TestPacketHeaderPushCalledAtMostOnce(t *testing.T) {
-	const headerSize = 10
-
-	pk := NewPacketBuffer(PacketBufferOptions{
-		ReserveHeaderBytes: headerSize * int(numHeaderType),
-	})
-
-	for _, h := range []PacketHeader{
-		pk.TransportHeader(),
-		pk.NetworkHeader(),
-		pk.LinkHeader(),
-	} {
-		t.Run("PushedTwice/"+h.typ.String(), func(t *testing.T) {
-			h.Push(headerSize)
-
-			defer func() { recover() }()
-			h.Push(headerSize)
-			t.Fatal("Second push should have panicked")
-		})
-	}
-}
-
-func TestPacketHeaderConsumeCalledAtMostOnce(t *testing.T) {
-	const headerSize = 10
-
-	pk := NewPacketBuffer(PacketBufferOptions{
-		Data: makeView(headerSize * int(numHeaderType)).ToVectorisedView(),
-	})
-
-	for _, h := range []PacketHeader{
-		pk.LinkHeader(),
-		pk.NetworkHeader(),
-		pk.TransportHeader(),
-	} {
-		t.Run("ConsumedTwice/"+h.typ.String(), func(t *testing.T) {
-			if _, ok := h.Consume(headerSize); !ok {
-				t.Fatal("First consume should succeed")
-			}
-
-			defer func() { recover() }()
-			h.Consume(headerSize)
-			t.Fatal("Second consume should have panicked")
-		})
-	}
-}
-
-func TestPacketHeaderPushThenConsumePanics(t *testing.T) {
-	const headerSize = 10
-
-	pk := NewPacketBuffer(PacketBufferOptions{
-		ReserveHeaderBytes: headerSize * int(numHeaderType),
-	})
-
-	for _, h := range []PacketHeader{
-		pk.TransportHeader(),
-		pk.NetworkHeader(),
-		pk.LinkHeader(),
-	} {
-		t.Run(h.typ.String(), func(t *testing.T) {
-			h.Push(headerSize)
-
-			defer func() { recover() }()
-			h.Consume(headerSize)
-			t.Fatal("Consume should have panicked")
-		})
-	}
-}
-
-func TestPacketHeaderConsumeThenPushPanics(t *testing.T) {
-	const headerSize = 10
-
-	pk := NewPacketBuffer(PacketBufferOptions{
-		Data: makeView(headerSize * int(numHeaderType)).ToVectorisedView(),
-	})
-
-	for _, h := range []PacketHeader{
-		pk.LinkHeader(),
-		pk.NetworkHeader(),
-		pk.TransportHeader(),
-	} {
-		t.Run(h.typ.String(), func(t *testing.T) {
-			h.Consume(headerSize)
-
-			defer func() { recover() }()
-			h.Push(headerSize)
-			t.Fatal("Push should have panicked")
-		})
-	}
-}
-
-func TestPacketBufferData(t *testing.T) {
-	for _, tc := range []struct {
-		name    string
-		makePkt func(*testing.T) *PacketBuffer
-		data    string
-	}{
-		{
-			name: "inbound packet",
-			makePkt: func(*testing.T) *PacketBuffer {
-				pkt := NewPacketBuffer(PacketBufferOptions{
-					Data: vv("aabbbbccccccDATA"),
-				})
-				pkt.LinkHeader().Consume(2)
-				pkt.NetworkHeader().Consume(4)
-				pkt.TransportHeader().Consume(6)
-				return pkt
-			},
-			data: "DATA",
-		},
-		{
-			name: "outbound packet",
-			makePkt: func(*testing.T) *PacketBuffer {
-				pkt := NewPacketBuffer(PacketBufferOptions{
-					ReserveHeaderBytes: 12,
-					Data:               vv("DATA"),
-				})
-				copy(pkt.TransportHeader().Push(6), []byte("cccccc"))
-				copy(pkt.NetworkHeader().Push(4), []byte("bbbb"))
-				copy(pkt.LinkHeader().Push(2), []byte("aa"))
-				return pkt
-			},
-			data: "DATA",
-		},
-	} {
-		t.Run(tc.name, func(t *testing.T) {
-			// PullUp
-			for _, n := range []int{1, len(tc.data)} {
-				t.Run(fmt.Sprintf("PullUp%d", n), func(t *testing.T) {
-					pkt := tc.makePkt(t)
-					v, ok := pkt.Data().PullUp(n)
-					wantV := []byte(tc.data)[:n]
-					if !ok || !bytes.Equal(v, wantV) {
-						t.Errorf("pkt.Data().PullUp(%d) = %q, %t; want %q, true", n, v, ok, wantV)
-					}
-				})
-			}
-			t.Run("PullUpOutOfBounds", func(t *testing.T) {
-				n := len(tc.data) + 1
-				pkt := tc.makePkt(t)
-				v, ok := pkt.Data().PullUp(n)
-				if ok || v != nil {
-					t.Errorf("pkt.Data().PullUp(%d) = %q, %t; want nil, false", n, v, ok)
-				}
-			})
-
-			// TrimFront
-			for _, n := range []int{1, len(tc.data)} {
-				t.Run(fmt.Sprintf("TrimFront%d", n), func(t *testing.T) {
-					pkt := tc.makePkt(t)
-					pkt.Data().TrimFront(n)
-
-					checkData(t, pkt, []byte(tc.data)[n:])
-				})
-			}
-
-			// CapLength
-			for _, n := range []int{0, 1, len(tc.data)} {
-				t.Run(fmt.Sprintf("CapLength%d", n), func(t *testing.T) {
-					pkt := tc.makePkt(t)
-					pkt.Data().CapLength(n)
-
-					want := []byte(tc.data)
-					if n < len(want) {
-						want = want[:n]
-					}
-					checkData(t, pkt, want)
-				})
-			}
-
-			// Views
-			t.Run("Views", func(t *testing.T) {
-				pkt := tc.makePkt(t)
-				checkData(t, pkt, []byte(tc.data))
-			})
-
-			// AppendView
-			t.Run("AppendView", func(t *testing.T) {
-				s := "APPEND"
-
-				pkt := tc.makePkt(t)
-				pkt.Data().AppendView(buffer.View(s))
-
-				checkData(t, pkt, []byte(tc.data+s))
-			})
-
-			// ReadFromData/VV
-			for _, n := range []int{0, 1, 2, 7, 10, 14, 20} {
-				t.Run(fmt.Sprintf("ReadFromData%d", n), func(t *testing.T) {
-					s := "TO READ"
-					otherPkt := NewPacketBuffer(PacketBufferOptions{
-						Data: vv(s, s),
-					})
-					s += s
-
-					pkt := tc.makePkt(t)
-					pkt.Data().ReadFromData(otherPkt.Data(), n)
-
-					if n < len(s) {
-						s = s[:n]
-					}
-					checkData(t, pkt, []byte(tc.data+s))
-				})
-				t.Run(fmt.Sprintf("ReadFromVV%d", n), func(t *testing.T) {
-					s := "TO READ"
-					srcVV := vv(s, s)
-					s += s
-
-					pkt := tc.makePkt(t)
-					pkt.Data().ReadFromVV(&srcVV, n)
-
-					if n < len(s) {
-						s = s[:n]
-					}
-					checkData(t, pkt, []byte(tc.data+s))
-				})
-			}
-
-			// ExtractVV
-			t.Run("ExtractVV", func(t *testing.T) {
-				pkt := tc.makePkt(t)
-				extractedVV := pkt.Data().ExtractVV()
-
-				got := extractedVV.ToOwnedView()
-				want := []byte(tc.data)
-				if !bytes.Equal(got, want) {
-					t.Errorf("pkt.Data().ExtractVV().ToOwnedView() = %q, want %q", got, want)
-				}
-			})
-
-			// Replace
-			t.Run("Replace", func(t *testing.T) {
-				s := "REPLACED"
-
-				pkt := tc.makePkt(t)
-				pkt.Data().Replace(vv(s))
-
-				checkData(t, pkt, []byte(s))
-			})
-		})
-	}
-}
-
-func checkInitialPacketBuffer(t *testing.T, pk *PacketBuffer, opts PacketBufferOptions) {
-	t.Helper()
-	reserved := opts.ReserveHeaderBytes
-	if got, want := pk.ReservedHeaderBytes(), reserved; got != want {
-		t.Errorf("Initial pk.ReservedHeaderBytes() = %d, want %d", got, want)
-	}
-	if got, want := pk.AvailableHeaderBytes(), reserved; got != want {
-		t.Errorf("Initial pk.AvailableHeaderBytes() = %d, want %d", got, want)
-	}
-	if got, want := pk.HeaderSize(), 0; got != want {
-		t.Errorf("Initial pk.HeaderSize() = %d, want %d", got, want)
-	}
-	data := opts.Data.ToView()
-	if got, want := pk.Size(), len(data); got != want {
-		t.Errorf("Initial pk.Size() = %d, want %d", got, want)
-	}
-	checkData(t, pk, data)
-	checkViewEqual(t, "Initial pk.Views()", concatViews(pk.Views()...), data)
-	// Check the initial values for each header.
-	checkPacketHeader(t, "Initial pk.LinkHeader", pk.LinkHeader(), nil)
-	checkPacketHeader(t, "Initial pk.NetworkHeader", pk.NetworkHeader(), nil)
-	checkPacketHeader(t, "Initial pk.TransportHeader", pk.TransportHeader(), nil)
-	// Check the initial valies for PayloadSince.
-	checkViewEqual(t, "Initial PayloadSince(LinkHeader)",
-		PayloadSince(pk.LinkHeader()), data)
-	checkViewEqual(t, "Initial PayloadSince(NetworkHeader)",
-		PayloadSince(pk.NetworkHeader()), data)
-	checkViewEqual(t, "Initial PayloadSince(TransportHeader)",
-		PayloadSince(pk.TransportHeader()), data)
-}
-
-func checkPacketHeader(t *testing.T, name string, h PacketHeader, want []byte) {
-	t.Helper()
-	checkViewEqual(t, name+".View()", h.View(), want)
-}
-
-func checkViewEqual(t *testing.T, what string, got, want buffer.View) {
-	t.Helper()
-	if !bytes.Equal(got, want) {
-		t.Errorf("%s = %x, want %x", what, got, want)
-	}
-}
-
-func checkData(t *testing.T, pkt *PacketBuffer, want []byte) {
-	t.Helper()
-	if got := concatViews(pkt.Data().Views()...); !bytes.Equal(got, want) {
-		t.Errorf("pkt.Data().Views() = %x, want %x", got, want)
-	}
-	if got := pkt.Data().Size(); got != len(want) {
-		t.Errorf("pkt.Data().Size() = %d, want %d", got, len(want))
-	}
-
-	t.Run("AsRange", func(t *testing.T) {
-		// Full range
-		checkRange(t, pkt.Data().AsRange(), want)
-
-		// SubRange
-		for _, off := range []int{0, 1, len(want), len(want) + 1} {
-			t.Run(fmt.Sprintf("SubRange%d", off), func(t *testing.T) {
-				// Empty when off is greater than the size of range.
-				var sub []byte
-				if off < len(want) {
-					sub = want[off:]
-				}
-				checkRange(t, pkt.Data().AsRange().SubRange(off), sub)
-			})
-		}
-
-		// Capped
-		for _, n := range []int{0, 1, len(want), len(want) + 1} {
-			t.Run(fmt.Sprintf("Capped%d", n), func(t *testing.T) {
-				sub := want
-				if n < len(sub) {
-					sub = sub[:n]
-				}
-				checkRange(t, pkt.Data().AsRange().Capped(n), sub)
-			})
-		}
-	})
-}
-
-func checkRange(t *testing.T, r Range, data []byte) {
-	if got, want := r.Size(), len(data); got != want {
-		t.Errorf("r.Size() = %d, want %d", got, want)
-	}
-	if got := r.AsView(); !bytes.Equal(got, data) {
-		t.Errorf("r.AsView() = %x, want %x", got, data)
-	}
-	if got := r.ToOwnedView(); !bytes.Equal(got, data) {
-		t.Errorf("r.ToOwnedView() = %x, want %x", got, data)
-	}
-	if got, want := r.Checksum(), header.Checksum(data, 0 /* initial */); got != want {
-		t.Errorf("r.Checksum() = %x, want %x", got, want)
-	}
-}
-
-func vv(pieces ...string) buffer.VectorisedView {
-	var views []buffer.View
-	var size int
-	for _, p := range pieces {
-		v := buffer.View([]byte(p))
-		size += len(v)
-		views = append(views, v)
-	}
-	return buffer.NewVectorisedView(size, views)
-}
-
-func makeView(size int) buffer.View {
-	b := byte(size)
-	return bytes.Repeat([]byte{b}, size)
-}
-
-func concatViews(views ...buffer.View) buffer.View {
-	var all buffer.View
-	for _, v := range views {
-		all = append(all, v...)
-	}
-	return all
-}
diff --git a/pkg/tcpip/stack/stack_state_autogen.go b/pkg/tcpip/stack/stack_state_autogen.go
new file mode 100644
index 0000000..6b10c1e
--- /dev/null
+++ b/pkg/tcpip/stack/stack_state_autogen.go
@@ -0,0 +1,718 @@
+// automatically generated by stateify.
+
+package stack
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (t *tuple) StateTypeName() string {
+	return "pkg/tcpip/stack.tuple"
+}
+
+func (t *tuple) StateFields() []string {
+	return []string{
+		"tupleEntry",
+		"tupleID",
+		"conn",
+		"direction",
+	}
+}
+
+func (t *tuple) beforeSave() {}
+
+// +checklocksignore
+func (t *tuple) StateSave(stateSinkObject state.Sink) {
+	t.beforeSave()
+	stateSinkObject.Save(0, &t.tupleEntry)
+	stateSinkObject.Save(1, &t.tupleID)
+	stateSinkObject.Save(2, &t.conn)
+	stateSinkObject.Save(3, &t.direction)
+}
+
+func (t *tuple) afterLoad() {}
+
+// +checklocksignore
+func (t *tuple) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &t.tupleEntry)
+	stateSourceObject.Load(1, &t.tupleID)
+	stateSourceObject.Load(2, &t.conn)
+	stateSourceObject.Load(3, &t.direction)
+}
+
+func (ti *tupleID) StateTypeName() string {
+	return "pkg/tcpip/stack.tupleID"
+}
+
+func (ti *tupleID) StateFields() []string {
+	return []string{
+		"srcAddr",
+		"srcPort",
+		"dstAddr",
+		"dstPort",
+		"transProto",
+		"netProto",
+	}
+}
+
+func (ti *tupleID) beforeSave() {}
+
+// +checklocksignore
+func (ti *tupleID) StateSave(stateSinkObject state.Sink) {
+	ti.beforeSave()
+	stateSinkObject.Save(0, &ti.srcAddr)
+	stateSinkObject.Save(1, &ti.srcPort)
+	stateSinkObject.Save(2, &ti.dstAddr)
+	stateSinkObject.Save(3, &ti.dstPort)
+	stateSinkObject.Save(4, &ti.transProto)
+	stateSinkObject.Save(5, &ti.netProto)
+}
+
+func (ti *tupleID) afterLoad() {}
+
+// +checklocksignore
+func (ti *tupleID) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &ti.srcAddr)
+	stateSourceObject.Load(1, &ti.srcPort)
+	stateSourceObject.Load(2, &ti.dstAddr)
+	stateSourceObject.Load(3, &ti.dstPort)
+	stateSourceObject.Load(4, &ti.transProto)
+	stateSourceObject.Load(5, &ti.netProto)
+}
+
+func (cn *conn) StateTypeName() string {
+	return "pkg/tcpip/stack.conn"
+}
+
+func (cn *conn) StateFields() []string {
+	return []string{
+		"original",
+		"reply",
+		"manip",
+		"tcbHook",
+		"tcb",
+		"lastUsed",
+	}
+}
+
+func (cn *conn) beforeSave() {}
+
+// +checklocksignore
+func (cn *conn) StateSave(stateSinkObject state.Sink) {
+	cn.beforeSave()
+	var lastUsedValue unixTime = cn.saveLastUsed()
+	stateSinkObject.SaveValue(5, lastUsedValue)
+	stateSinkObject.Save(0, &cn.original)
+	stateSinkObject.Save(1, &cn.reply)
+	stateSinkObject.Save(2, &cn.manip)
+	stateSinkObject.Save(3, &cn.tcbHook)
+	stateSinkObject.Save(4, &cn.tcb)
+}
+
+func (cn *conn) afterLoad() {}
+
+// +checklocksignore
+func (cn *conn) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &cn.original)
+	stateSourceObject.Load(1, &cn.reply)
+	stateSourceObject.Load(2, &cn.manip)
+	stateSourceObject.Load(3, &cn.tcbHook)
+	stateSourceObject.Load(4, &cn.tcb)
+	stateSourceObject.LoadValue(5, new(unixTime), func(y interface{}) { cn.loadLastUsed(y.(unixTime)) })
+}
+
+func (ct *ConnTrack) StateTypeName() string {
+	return "pkg/tcpip/stack.ConnTrack"
+}
+
+func (ct *ConnTrack) StateFields() []string {
+	return []string{
+		"seed",
+		"buckets",
+	}
+}
+
+// +checklocksignore
+func (ct *ConnTrack) StateSave(stateSinkObject state.Sink) {
+	ct.beforeSave()
+	stateSinkObject.Save(0, &ct.seed)
+	stateSinkObject.Save(1, &ct.buckets)
+}
+
+func (ct *ConnTrack) afterLoad() {}
+
+// +checklocksignore
+func (ct *ConnTrack) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &ct.seed)
+	stateSourceObject.Load(1, &ct.buckets)
+}
+
+func (b *bucket) StateTypeName() string {
+	return "pkg/tcpip/stack.bucket"
+}
+
+func (b *bucket) StateFields() []string {
+	return []string{
+		"tuples",
+	}
+}
+
+func (b *bucket) beforeSave() {}
+
+// +checklocksignore
+func (b *bucket) StateSave(stateSinkObject state.Sink) {
+	b.beforeSave()
+	stateSinkObject.Save(0, &b.tuples)
+}
+
+func (b *bucket) afterLoad() {}
+
+// +checklocksignore
+func (b *bucket) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &b.tuples)
+}
+
+func (u *unixTime) StateTypeName() string {
+	return "pkg/tcpip/stack.unixTime"
+}
+
+func (u *unixTime) StateFields() []string {
+	return []string{
+		"second",
+		"nano",
+	}
+}
+
+func (u *unixTime) beforeSave() {}
+
+// +checklocksignore
+func (u *unixTime) StateSave(stateSinkObject state.Sink) {
+	u.beforeSave()
+	stateSinkObject.Save(0, &u.second)
+	stateSinkObject.Save(1, &u.nano)
+}
+
+func (u *unixTime) afterLoad() {}
+
+// +checklocksignore
+func (u *unixTime) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &u.second)
+	stateSourceObject.Load(1, &u.nano)
+}
+
+func (it *IPTables) StateTypeName() string {
+	return "pkg/tcpip/stack.IPTables"
+}
+
+func (it *IPTables) StateFields() []string {
+	return []string{
+		"mu",
+		"v4Tables",
+		"v6Tables",
+		"modified",
+		"priorities",
+		"connections",
+		"reaperDone",
+	}
+}
+
+// +checklocksignore
+func (it *IPTables) StateSave(stateSinkObject state.Sink) {
+	it.beforeSave()
+	stateSinkObject.Save(0, &it.mu)
+	stateSinkObject.Save(1, &it.v4Tables)
+	stateSinkObject.Save(2, &it.v6Tables)
+	stateSinkObject.Save(3, &it.modified)
+	stateSinkObject.Save(4, &it.priorities)
+	stateSinkObject.Save(5, &it.connections)
+	stateSinkObject.Save(6, &it.reaperDone)
+}
+
+// +checklocksignore
+func (it *IPTables) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &it.mu)
+	stateSourceObject.Load(1, &it.v4Tables)
+	stateSourceObject.Load(2, &it.v6Tables)
+	stateSourceObject.Load(3, &it.modified)
+	stateSourceObject.Load(4, &it.priorities)
+	stateSourceObject.Load(5, &it.connections)
+	stateSourceObject.Load(6, &it.reaperDone)
+	stateSourceObject.AfterLoad(it.afterLoad)
+}
+
+func (table *Table) StateTypeName() string {
+	return "pkg/tcpip/stack.Table"
+}
+
+func (table *Table) StateFields() []string {
+	return []string{
+		"Rules",
+		"BuiltinChains",
+		"Underflows",
+	}
+}
+
+func (table *Table) beforeSave() {}
+
+// +checklocksignore
+func (table *Table) StateSave(stateSinkObject state.Sink) {
+	table.beforeSave()
+	stateSinkObject.Save(0, &table.Rules)
+	stateSinkObject.Save(1, &table.BuiltinChains)
+	stateSinkObject.Save(2, &table.Underflows)
+}
+
+func (table *Table) afterLoad() {}
+
+// +checklocksignore
+func (table *Table) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &table.Rules)
+	stateSourceObject.Load(1, &table.BuiltinChains)
+	stateSourceObject.Load(2, &table.Underflows)
+}
+
+func (r *Rule) StateTypeName() string {
+	return "pkg/tcpip/stack.Rule"
+}
+
+func (r *Rule) StateFields() []string {
+	return []string{
+		"Filter",
+		"Matchers",
+		"Target",
+	}
+}
+
+func (r *Rule) beforeSave() {}
+
+// +checklocksignore
+func (r *Rule) StateSave(stateSinkObject state.Sink) {
+	r.beforeSave()
+	stateSinkObject.Save(0, &r.Filter)
+	stateSinkObject.Save(1, &r.Matchers)
+	stateSinkObject.Save(2, &r.Target)
+}
+
+func (r *Rule) afterLoad() {}
+
+// +checklocksignore
+func (r *Rule) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &r.Filter)
+	stateSourceObject.Load(1, &r.Matchers)
+	stateSourceObject.Load(2, &r.Target)
+}
+
+func (fl *IPHeaderFilter) StateTypeName() string {
+	return "pkg/tcpip/stack.IPHeaderFilter"
+}
+
+func (fl *IPHeaderFilter) StateFields() []string {
+	return []string{
+		"Protocol",
+		"CheckProtocol",
+		"Dst",
+		"DstMask",
+		"DstInvert",
+		"Src",
+		"SrcMask",
+		"SrcInvert",
+		"InputInterface",
+		"InputInterfaceMask",
+		"InputInterfaceInvert",
+		"OutputInterface",
+		"OutputInterfaceMask",
+		"OutputInterfaceInvert",
+	}
+}
+
+func (fl *IPHeaderFilter) beforeSave() {}
+
+// +checklocksignore
+func (fl *IPHeaderFilter) StateSave(stateSinkObject state.Sink) {
+	fl.beforeSave()
+	stateSinkObject.Save(0, &fl.Protocol)
+	stateSinkObject.Save(1, &fl.CheckProtocol)
+	stateSinkObject.Save(2, &fl.Dst)
+	stateSinkObject.Save(3, &fl.DstMask)
+	stateSinkObject.Save(4, &fl.DstInvert)
+	stateSinkObject.Save(5, &fl.Src)
+	stateSinkObject.Save(6, &fl.SrcMask)
+	stateSinkObject.Save(7, &fl.SrcInvert)
+	stateSinkObject.Save(8, &fl.InputInterface)
+	stateSinkObject.Save(9, &fl.InputInterfaceMask)
+	stateSinkObject.Save(10, &fl.InputInterfaceInvert)
+	stateSinkObject.Save(11, &fl.OutputInterface)
+	stateSinkObject.Save(12, &fl.OutputInterfaceMask)
+	stateSinkObject.Save(13, &fl.OutputInterfaceInvert)
+}
+
+func (fl *IPHeaderFilter) afterLoad() {}
+
+// +checklocksignore
+func (fl *IPHeaderFilter) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &fl.Protocol)
+	stateSourceObject.Load(1, &fl.CheckProtocol)
+	stateSourceObject.Load(2, &fl.Dst)
+	stateSourceObject.Load(3, &fl.DstMask)
+	stateSourceObject.Load(4, &fl.DstInvert)
+	stateSourceObject.Load(5, &fl.Src)
+	stateSourceObject.Load(6, &fl.SrcMask)
+	stateSourceObject.Load(7, &fl.SrcInvert)
+	stateSourceObject.Load(8, &fl.InputInterface)
+	stateSourceObject.Load(9, &fl.InputInterfaceMask)
+	stateSourceObject.Load(10, &fl.InputInterfaceInvert)
+	stateSourceObject.Load(11, &fl.OutputInterface)
+	stateSourceObject.Load(12, &fl.OutputInterfaceMask)
+	stateSourceObject.Load(13, &fl.OutputInterfaceInvert)
+}
+
+func (l *neighborEntryList) StateTypeName() string {
+	return "pkg/tcpip/stack.neighborEntryList"
+}
+
+func (l *neighborEntryList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *neighborEntryList) beforeSave() {}
+
+// +checklocksignore
+func (l *neighborEntryList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *neighborEntryList) afterLoad() {}
+
+// +checklocksignore
+func (l *neighborEntryList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *neighborEntryEntry) StateTypeName() string {
+	return "pkg/tcpip/stack.neighborEntryEntry"
+}
+
+func (e *neighborEntryEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *neighborEntryEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *neighborEntryEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *neighborEntryEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *neighborEntryEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func (p *PacketBufferList) StateTypeName() string {
+	return "pkg/tcpip/stack.PacketBufferList"
+}
+
+func (p *PacketBufferList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (p *PacketBufferList) beforeSave() {}
+
+// +checklocksignore
+func (p *PacketBufferList) StateSave(stateSinkObject state.Sink) {
+	p.beforeSave()
+	stateSinkObject.Save(0, &p.head)
+	stateSinkObject.Save(1, &p.tail)
+}
+
+func (p *PacketBufferList) afterLoad() {}
+
+// +checklocksignore
+func (p *PacketBufferList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &p.head)
+	stateSourceObject.Load(1, &p.tail)
+}
+
+func (e *PacketBufferEntry) StateTypeName() string {
+	return "pkg/tcpip/stack.PacketBufferEntry"
+}
+
+func (e *PacketBufferEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *PacketBufferEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *PacketBufferEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *PacketBufferEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *PacketBufferEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func (t *TransportEndpointID) StateTypeName() string {
+	return "pkg/tcpip/stack.TransportEndpointID"
+}
+
+func (t *TransportEndpointID) StateFields() []string {
+	return []string{
+		"LocalPort",
+		"LocalAddress",
+		"RemotePort",
+		"RemoteAddress",
+	}
+}
+
+func (t *TransportEndpointID) beforeSave() {}
+
+// +checklocksignore
+func (t *TransportEndpointID) StateSave(stateSinkObject state.Sink) {
+	t.beforeSave()
+	stateSinkObject.Save(0, &t.LocalPort)
+	stateSinkObject.Save(1, &t.LocalAddress)
+	stateSinkObject.Save(2, &t.RemotePort)
+	stateSinkObject.Save(3, &t.RemoteAddress)
+}
+
+func (t *TransportEndpointID) afterLoad() {}
+
+// +checklocksignore
+func (t *TransportEndpointID) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &t.LocalPort)
+	stateSourceObject.Load(1, &t.LocalAddress)
+	stateSourceObject.Load(2, &t.RemotePort)
+	stateSourceObject.Load(3, &t.RemoteAddress)
+}
+
+func (g *GSOType) StateTypeName() string {
+	return "pkg/tcpip/stack.GSOType"
+}
+
+func (g *GSOType) StateFields() []string {
+	return nil
+}
+
+func (g *GSO) StateTypeName() string {
+	return "pkg/tcpip/stack.GSO"
+}
+
+func (g *GSO) StateFields() []string {
+	return []string{
+		"Type",
+		"NeedsCsum",
+		"CsumOffset",
+		"MSS",
+		"L3HdrLen",
+		"MaxSize",
+	}
+}
+
+func (g *GSO) beforeSave() {}
+
+// +checklocksignore
+func (g *GSO) StateSave(stateSinkObject state.Sink) {
+	g.beforeSave()
+	stateSinkObject.Save(0, &g.Type)
+	stateSinkObject.Save(1, &g.NeedsCsum)
+	stateSinkObject.Save(2, &g.CsumOffset)
+	stateSinkObject.Save(3, &g.MSS)
+	stateSinkObject.Save(4, &g.L3HdrLen)
+	stateSinkObject.Save(5, &g.MaxSize)
+}
+
+func (g *GSO) afterLoad() {}
+
+// +checklocksignore
+func (g *GSO) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &g.Type)
+	stateSourceObject.Load(1, &g.NeedsCsum)
+	stateSourceObject.Load(2, &g.CsumOffset)
+	stateSourceObject.Load(3, &g.MSS)
+	stateSourceObject.Load(4, &g.L3HdrLen)
+	stateSourceObject.Load(5, &g.MaxSize)
+}
+
+func (t *TransportEndpointInfo) StateTypeName() string {
+	return "pkg/tcpip/stack.TransportEndpointInfo"
+}
+
+func (t *TransportEndpointInfo) StateFields() []string {
+	return []string{
+		"NetProto",
+		"TransProto",
+		"ID",
+		"BindNICID",
+		"BindAddr",
+		"RegisterNICID",
+	}
+}
+
+func (t *TransportEndpointInfo) beforeSave() {}
+
+// +checklocksignore
+func (t *TransportEndpointInfo) StateSave(stateSinkObject state.Sink) {
+	t.beforeSave()
+	stateSinkObject.Save(0, &t.NetProto)
+	stateSinkObject.Save(1, &t.TransProto)
+	stateSinkObject.Save(2, &t.ID)
+	stateSinkObject.Save(3, &t.BindNICID)
+	stateSinkObject.Save(4, &t.BindAddr)
+	stateSinkObject.Save(5, &t.RegisterNICID)
+}
+
+func (t *TransportEndpointInfo) afterLoad() {}
+
+// +checklocksignore
+func (t *TransportEndpointInfo) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &t.NetProto)
+	stateSourceObject.Load(1, &t.TransProto)
+	stateSourceObject.Load(2, &t.ID)
+	stateSourceObject.Load(3, &t.BindNICID)
+	stateSourceObject.Load(4, &t.BindAddr)
+	stateSourceObject.Load(5, &t.RegisterNICID)
+}
+
+func (ep *multiPortEndpoint) StateTypeName() string {
+	return "pkg/tcpip/stack.multiPortEndpoint"
+}
+
+func (ep *multiPortEndpoint) StateFields() []string {
+	return []string{
+		"demux",
+		"netProto",
+		"transProto",
+		"endpoints",
+		"flags",
+	}
+}
+
+func (ep *multiPortEndpoint) beforeSave() {}
+
+// +checklocksignore
+func (ep *multiPortEndpoint) StateSave(stateSinkObject state.Sink) {
+	ep.beforeSave()
+	stateSinkObject.Save(0, &ep.demux)
+	stateSinkObject.Save(1, &ep.netProto)
+	stateSinkObject.Save(2, &ep.transProto)
+	stateSinkObject.Save(3, &ep.endpoints)
+	stateSinkObject.Save(4, &ep.flags)
+}
+
+func (ep *multiPortEndpoint) afterLoad() {}
+
+// +checklocksignore
+func (ep *multiPortEndpoint) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &ep.demux)
+	stateSourceObject.Load(1, &ep.netProto)
+	stateSourceObject.Load(2, &ep.transProto)
+	stateSourceObject.Load(3, &ep.endpoints)
+	stateSourceObject.Load(4, &ep.flags)
+}
+
+func (l *tupleList) StateTypeName() string {
+	return "pkg/tcpip/stack.tupleList"
+}
+
+func (l *tupleList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *tupleList) beforeSave() {}
+
+// +checklocksignore
+func (l *tupleList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *tupleList) afterLoad() {}
+
+// +checklocksignore
+func (l *tupleList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *tupleEntry) StateTypeName() string {
+	return "pkg/tcpip/stack.tupleEntry"
+}
+
+func (e *tupleEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *tupleEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *tupleEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *tupleEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *tupleEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+	state.Register((*tuple)(nil))
+	state.Register((*tupleID)(nil))
+	state.Register((*conn)(nil))
+	state.Register((*ConnTrack)(nil))
+	state.Register((*bucket)(nil))
+	state.Register((*unixTime)(nil))
+	state.Register((*IPTables)(nil))
+	state.Register((*Table)(nil))
+	state.Register((*Rule)(nil))
+	state.Register((*IPHeaderFilter)(nil))
+	state.Register((*neighborEntryList)(nil))
+	state.Register((*neighborEntryEntry)(nil))
+	state.Register((*PacketBufferList)(nil))
+	state.Register((*PacketBufferEntry)(nil))
+	state.Register((*TransportEndpointID)(nil))
+	state.Register((*GSOType)(nil))
+	state.Register((*GSO)(nil))
+	state.Register((*TransportEndpointInfo)(nil))
+	state.Register((*multiPortEndpoint)(nil))
+	state.Register((*tupleList)(nil))
+	state.Register((*tupleEntry)(nil))
+}
diff --git a/pkg/tcpip/stack/stack_test.go b/pkg/tcpip/stack/stack_test.go
deleted file mode 100644
index 2814b94..0000000
--- a/pkg/tcpip/stack/stack_test.go
+++ /dev/null
@@ -1,4501 +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 stack_test contains tests for the stack. It is in its own package so
-// that the tests can also validate that all definitions needed to implement
-// transport and network protocols are properly exported by the stack package.
-package stack_test
-
-import (
-	"bytes"
-	"fmt"
-	"math"
-	"net"
-	"sort"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/rand"
-	"gvisor.dev/gvisor/pkg/sync"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-	"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/loopback"
-	"gvisor.dev/gvisor/pkg/tcpip/network/arp"
-	"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/udp"
-)
-
-const (
-	fakeNetNumber        tcpip.NetworkProtocolNumber = math.MaxUint32
-	fakeNetHeaderLen                                 = 12
-	fakeDefaultPrefixLen                             = 8
-
-	// fakeControlProtocol is used for control packets that represent
-	// destination port unreachable.
-	fakeControlProtocol tcpip.TransportProtocolNumber = 2
-
-	// defaultMTU is the MTU, in bytes, used throughout the tests, except
-	// where another value is explicitly used. It is chosen to match the MTU
-	// of loopback interfaces on linux systems.
-	defaultMTU = 65536
-
-	dstAddrOffset        = 0
-	srcAddrOffset        = 1
-	protocolNumberOffset = 2
-)
-
-func checkGetMainNICAddress(s *stack.Stack, nicID tcpip.NICID, proto tcpip.NetworkProtocolNumber, want tcpip.AddressWithPrefix) error {
-	if addr, err := s.GetMainNICAddress(nicID, proto); err != nil {
-		return fmt.Errorf("stack.GetMainNICAddress(%d, %d): %s", nicID, proto, err)
-	} else if addr != want {
-		return fmt.Errorf("got stack.GetMainNICAddress(%d, %d) = %s, want = %s", nicID, proto, addr, want)
-	}
-	return nil
-}
-
-// fakeNetworkEndpoint is a network-layer protocol endpoint. It counts sent and
-// received packets; the counts of all endpoints are aggregated in the protocol
-// descriptor.
-//
-// Headers of this protocol are fakeNetHeaderLen bytes, but we currently only
-// use the first three: destination address, source address, and transport
-// protocol. They're all one byte fields to simplify parsing.
-type fakeNetworkEndpoint struct {
-	stack.AddressableEndpointState
-
-	mu struct {
-		sync.RWMutex
-
-		enabled bool
-	}
-
-	nic        stack.NetworkInterface
-	proto      *fakeNetworkProtocol
-	dispatcher stack.TransportDispatcher
-}
-
-func (f *fakeNetworkEndpoint) Enable() tcpip.Error {
-	f.mu.Lock()
-	defer f.mu.Unlock()
-	f.mu.enabled = true
-	return nil
-}
-
-func (f *fakeNetworkEndpoint) Enabled() bool {
-	f.mu.RLock()
-	defer f.mu.RUnlock()
-	return f.mu.enabled
-}
-
-func (f *fakeNetworkEndpoint) Disable() {
-	f.mu.Lock()
-	defer f.mu.Unlock()
-	f.mu.enabled = false
-}
-
-func (f *fakeNetworkEndpoint) MTU() uint32 {
-	return f.nic.MTU() - uint32(f.MaxHeaderLength())
-}
-
-func (*fakeNetworkEndpoint) DefaultTTL() uint8 {
-	return 123
-}
-
-func (f *fakeNetworkEndpoint) HandlePacket(pkt *stack.PacketBuffer) {
-	if _, _, ok := f.proto.Parse(pkt); !ok {
-		return
-	}
-
-	// Increment the received packet count in the protocol descriptor.
-	netHdr := pkt.NetworkHeader().View()
-
-	dst := tcpip.Address(netHdr[dstAddrOffset:][:1])
-	addressEndpoint := f.AcquireAssignedAddress(dst, f.nic.Promiscuous(), stack.CanBePrimaryEndpoint)
-	if addressEndpoint == nil {
-		return
-	}
-	addressEndpoint.DecRef()
-
-	f.proto.packetCount[int(dst[0])%len(f.proto.packetCount)]++
-
-	// Handle control packets.
-	if netHdr[protocolNumberOffset] == uint8(fakeControlProtocol) {
-		nb, ok := pkt.Data().PullUp(fakeNetHeaderLen)
-		if !ok {
-			return
-		}
-		pkt.Data().TrimFront(fakeNetHeaderLen)
-		f.dispatcher.DeliverTransportError(
-			tcpip.Address(nb[srcAddrOffset:srcAddrOffset+1]),
-			tcpip.Address(nb[dstAddrOffset:dstAddrOffset+1]),
-			fakeNetNumber,
-			tcpip.TransportProtocolNumber(nb[protocolNumberOffset]),
-			// Nothing checks the error.
-			nil, /* transport error */
-			pkt,
-		)
-		return
-	}
-
-	// Dispatch the packet to the transport protocol.
-	f.dispatcher.DeliverTransportPacket(tcpip.TransportProtocolNumber(pkt.NetworkHeader().View()[protocolNumberOffset]), pkt)
-}
-
-func (f *fakeNetworkEndpoint) MaxHeaderLength() uint16 {
-	return f.nic.MaxHeaderLength() + fakeNetHeaderLen
-}
-
-func (*fakeNetworkEndpoint) PseudoHeaderChecksum(protocol tcpip.TransportProtocolNumber, dstAddr tcpip.Address) uint16 {
-	return 0
-}
-
-func (f *fakeNetworkEndpoint) NetworkProtocolNumber() tcpip.NetworkProtocolNumber {
-	return f.proto.Number()
-}
-
-func (f *fakeNetworkEndpoint) WritePacket(r *stack.Route, gso *stack.GSO, params stack.NetworkHeaderParams, pkt *stack.PacketBuffer) tcpip.Error {
-	// Increment the sent packet count in the protocol descriptor.
-	f.proto.sendPacketCount[int(r.RemoteAddress()[0])%len(f.proto.sendPacketCount)]++
-
-	// Add the protocol's header to the packet and send it to the link
-	// endpoint.
-	hdr := pkt.NetworkHeader().Push(fakeNetHeaderLen)
-	pkt.NetworkProtocolNumber = fakeNetNumber
-	hdr[dstAddrOffset] = r.RemoteAddress()[0]
-	hdr[srcAddrOffset] = r.LocalAddress()[0]
-	hdr[protocolNumberOffset] = byte(params.Protocol)
-
-	if r.Loop()&stack.PacketLoop != 0 {
-		f.HandlePacket(pkt.Clone())
-	}
-	if r.Loop()&stack.PacketOut == 0 {
-		return nil
-	}
-
-	return f.nic.WritePacket(r, gso, fakeNetNumber, pkt)
-}
-
-// WritePackets implements stack.LinkEndpoint.WritePackets.
-func (*fakeNetworkEndpoint) WritePackets(r *stack.Route, gso *stack.GSO, pkts stack.PacketBufferList, params stack.NetworkHeaderParams) (int, tcpip.Error) {
-	panic("not implemented")
-}
-
-func (*fakeNetworkEndpoint) WriteHeaderIncludedPacket(r *stack.Route, pkt *stack.PacketBuffer) tcpip.Error {
-	return &tcpip.ErrNotSupported{}
-}
-
-func (f *fakeNetworkEndpoint) Close() {
-	f.AddressableEndpointState.Cleanup()
-}
-
-// Stats implements NetworkEndpoint.
-func (*fakeNetworkEndpoint) Stats() stack.NetworkEndpointStats {
-	return &fakeNetworkEndpointStats{}
-}
-
-var _ stack.NetworkEndpointStats = (*fakeNetworkEndpointStats)(nil)
-
-type fakeNetworkEndpointStats struct{}
-
-// IsNetworkEndpointStats implements stack.NetworkEndpointStats.
-func (*fakeNetworkEndpointStats) IsNetworkEndpointStats() {}
-
-// fakeNetworkProtocol is a network-layer protocol descriptor. It aggregates the
-// number of packets sent and received via endpoints of this protocol. The index
-// where packets are added is given by the packet's destination address MOD 10.
-type fakeNetworkProtocol struct {
-	packetCount     [10]int
-	sendPacketCount [10]int
-	defaultTTL      uint8
-
-	mu struct {
-		sync.RWMutex
-		forwarding bool
-	}
-}
-
-func (*fakeNetworkProtocol) Number() tcpip.NetworkProtocolNumber {
-	return fakeNetNumber
-}
-
-func (*fakeNetworkProtocol) MinimumPacketSize() int {
-	return fakeNetHeaderLen
-}
-
-func (*fakeNetworkProtocol) DefaultPrefixLen() int {
-	return fakeDefaultPrefixLen
-}
-
-func (f *fakeNetworkProtocol) PacketCount(intfAddr byte) int {
-	return f.packetCount[int(intfAddr)%len(f.packetCount)]
-}
-
-func (*fakeNetworkProtocol) ParseAddresses(v buffer.View) (src, dst tcpip.Address) {
-	return tcpip.Address(v[srcAddrOffset : srcAddrOffset+1]), tcpip.Address(v[dstAddrOffset : dstAddrOffset+1])
-}
-
-func (f *fakeNetworkProtocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.TransportDispatcher) stack.NetworkEndpoint {
-	e := &fakeNetworkEndpoint{
-		nic:        nic,
-		proto:      f,
-		dispatcher: dispatcher,
-	}
-	e.AddressableEndpointState.Init(e)
-	return e
-}
-
-func (f *fakeNetworkProtocol) SetOption(option tcpip.SettableNetworkProtocolOption) tcpip.Error {
-	switch v := option.(type) {
-	case *tcpip.DefaultTTLOption:
-		f.defaultTTL = uint8(*v)
-		return nil
-	default:
-		return &tcpip.ErrUnknownProtocolOption{}
-	}
-}
-
-func (f *fakeNetworkProtocol) Option(option tcpip.GettableNetworkProtocolOption) tcpip.Error {
-	switch v := option.(type) {
-	case *tcpip.DefaultTTLOption:
-		*v = tcpip.DefaultTTLOption(f.defaultTTL)
-		return nil
-	default:
-		return &tcpip.ErrUnknownProtocolOption{}
-	}
-}
-
-// Close implements NetworkProtocol.Close.
-func (*fakeNetworkProtocol) Close() {}
-
-// Wait implements NetworkProtocol.Wait.
-func (*fakeNetworkProtocol) Wait() {}
-
-// Parse implements NetworkProtocol.Parse.
-func (*fakeNetworkProtocol) Parse(pkt *stack.PacketBuffer) (tcpip.TransportProtocolNumber, bool, bool) {
-	hdr, ok := pkt.NetworkHeader().Consume(fakeNetHeaderLen)
-	if !ok {
-		return 0, false, false
-	}
-	pkt.NetworkProtocolNumber = fakeNetNumber
-	return tcpip.TransportProtocolNumber(hdr[protocolNumberOffset]), true, true
-}
-
-// Forwarding implements stack.ForwardingNetworkProtocol.
-func (f *fakeNetworkProtocol) Forwarding() bool {
-	f.mu.RLock()
-	defer f.mu.RUnlock()
-	return f.mu.forwarding
-}
-
-// SetForwarding implements stack.ForwardingNetworkProtocol.
-func (f *fakeNetworkProtocol) SetForwarding(v bool) {
-	f.mu.Lock()
-	defer f.mu.Unlock()
-	f.mu.forwarding = v
-}
-
-func fakeNetFactory(*stack.Stack) stack.NetworkProtocol {
-	return &fakeNetworkProtocol{}
-}
-
-// linkEPWithMockedAttach is a stack.LinkEndpoint that tests can use to verify
-// that LinkEndpoint.Attach was called.
-type linkEPWithMockedAttach struct {
-	stack.LinkEndpoint
-	attached bool
-}
-
-// Attach implements stack.LinkEndpoint.Attach.
-func (l *linkEPWithMockedAttach) Attach(d stack.NetworkDispatcher) {
-	l.LinkEndpoint.Attach(d)
-	l.attached = d != nil
-}
-
-func (l *linkEPWithMockedAttach) isAttached() bool {
-	return l.attached
-}
-
-// Checks to see if list contains an address.
-func containsAddr(list []tcpip.ProtocolAddress, item tcpip.ProtocolAddress) bool {
-	for _, i := range list {
-		if i == item {
-			return true
-		}
-	}
-
-	return false
-}
-
-func TestNetworkReceive(t *testing.T) {
-	// Create a stack with the fake network protocol, one nic, and two
-	// addresses attached to it: 1 & 2.
-	ep := channel.New(10, defaultMTU, "")
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x02"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
-	buf := buffer.NewView(30)
-
-	// Make sure packet with wrong address is not delivered.
-	buf[dstAddrOffset] = 3
-	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeNet.packetCount[1] != 0 {
-		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 0)
-	}
-	if fakeNet.packetCount[2] != 0 {
-		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 0)
-	}
-
-	// Make sure packet is delivered to first endpoint.
-	buf[dstAddrOffset] = 1
-	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeNet.packetCount[1] != 1 {
-		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
-	}
-	if fakeNet.packetCount[2] != 0 {
-		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 0)
-	}
-
-	// Make sure packet is delivered to second endpoint.
-	buf[dstAddrOffset] = 2
-	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeNet.packetCount[1] != 1 {
-		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
-	}
-	if fakeNet.packetCount[2] != 1 {
-		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
-	}
-
-	// Make sure packet is not delivered if protocol number is wrong.
-	ep.InjectInbound(fakeNetNumber-1, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeNet.packetCount[1] != 1 {
-		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
-	}
-	if fakeNet.packetCount[2] != 1 {
-		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
-	}
-
-	// Make sure packet that is too small is dropped.
-	buf.CapLength(2)
-	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeNet.packetCount[1] != 1 {
-		t.Errorf("packetCount[1] = %d, want %d", fakeNet.packetCount[1], 1)
-	}
-	if fakeNet.packetCount[2] != 1 {
-		t.Errorf("packetCount[2] = %d, want %d", fakeNet.packetCount[2], 1)
-	}
-}
-
-func sendTo(s *stack.Stack, addr tcpip.Address, payload buffer.View) tcpip.Error {
-	r, err := s.FindRoute(0, "", addr, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		return err
-	}
-	defer r.Release()
-	return send(r, payload)
-}
-
-func send(r *stack.Route, payload buffer.View) tcpip.Error {
-	return r.WritePacket(nil /* gso */, stack.NetworkHeaderParams{Protocol: fakeTransNumber, TTL: 123, TOS: stack.DefaultTOS}, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		ReserveHeaderBytes: int(r.MaxHeaderLength()),
-		Data:               payload.ToVectorisedView(),
-	}))
-}
-
-func testSendTo(t *testing.T, s *stack.Stack, addr tcpip.Address, ep *channel.Endpoint, payload buffer.View) {
-	t.Helper()
-	ep.Drain()
-	if err := sendTo(s, addr, payload); err != nil {
-		t.Error("sendTo failed:", err)
-	}
-	if got, want := ep.Drain(), 1; got != want {
-		t.Errorf("sendTo packet count: got = %d, want %d", got, want)
-	}
-}
-
-func testSend(t *testing.T, r *stack.Route, ep *channel.Endpoint, payload buffer.View) {
-	t.Helper()
-	ep.Drain()
-	if err := send(r, payload); err != nil {
-		t.Error("send failed:", err)
-	}
-	if got, want := ep.Drain(), 1; got != want {
-		t.Errorf("send packet count: got = %d, want %d", got, want)
-	}
-}
-
-func testFailingSend(t *testing.T, r *stack.Route, ep *channel.Endpoint, payload buffer.View, wantErr tcpip.Error) {
-	t.Helper()
-	if gotErr := send(r, payload); gotErr != wantErr {
-		t.Errorf("send failed: got = %s, want = %s ", gotErr, wantErr)
-	}
-}
-
-func testFailingSendTo(t *testing.T, s *stack.Stack, addr tcpip.Address, ep *channel.Endpoint, payload buffer.View, wantErr tcpip.Error) {
-	t.Helper()
-	if gotErr := sendTo(s, addr, payload); gotErr != wantErr {
-		t.Errorf("sendto failed: got = %s, want = %s ", gotErr, wantErr)
-	}
-}
-
-func testRecv(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf buffer.View) {
-	t.Helper()
-	// testRecvInternal injects one packet, and we expect to receive it.
-	want := fakeNet.PacketCount(localAddrByte) + 1
-	testRecvInternal(t, fakeNet, localAddrByte, ep, buf, want)
-}
-
-func testFailingRecv(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf buffer.View) {
-	t.Helper()
-	// testRecvInternal injects one packet, and we do NOT expect to receive it.
-	want := fakeNet.PacketCount(localAddrByte)
-	testRecvInternal(t, fakeNet, localAddrByte, ep, buf, want)
-}
-
-func testRecvInternal(t *testing.T, fakeNet *fakeNetworkProtocol, localAddrByte byte, ep *channel.Endpoint, buf buffer.View, want int) {
-	t.Helper()
-	ep.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if got := fakeNet.PacketCount(localAddrByte); got != want {
-		t.Errorf("receive packet count: got = %d, want %d", got, want)
-	}
-}
-
-func TestNetworkSend(t *testing.T) {
-	// Create a stack with the fake network protocol, one nic, and one
-	// address: 1. The route table sends all packets through the only
-	// existing nic.
-	ep := channel.New(10, defaultMTU, "")
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatal("NewNIC failed:", err)
-	}
-
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	// Make sure that the link-layer endpoint received the outbound packet.
-	testSendTo(t, s, "\x03", ep, nil)
-}
-
-func TestNetworkSendMultiRoute(t *testing.T) {
-	// Create a stack with the fake network protocol, two nics, and two
-	// addresses per nic, the first nic has odd address, the second one has
-	// even addresses.
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep1 := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep1); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x03"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	ep2 := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(2, ep2); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	if err := s.AddAddress(2, fakeNetNumber, "\x02"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	if err := s.AddAddress(2, fakeNetNumber, "\x04"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	// Set a route table that sends all packets with odd destination
-	// addresses through the first NIC, and all even destination address
-	// through the second one.
-	{
-		subnet0, err := tcpip.NewSubnet("\x00", "\x01")
-		if err != nil {
-			t.Fatal(err)
-		}
-		subnet1, err := tcpip.NewSubnet("\x01", "\x01")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{
-			{Destination: subnet1, Gateway: "\x00", NIC: 1},
-			{Destination: subnet0, Gateway: "\x00", NIC: 2},
-		})
-	}
-
-	// Send a packet to an odd destination.
-	testSendTo(t, s, "\x05", ep1, nil)
-
-	// Send a packet to an even destination.
-	testSendTo(t, s, "\x06", ep2, nil)
-}
-
-func testRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr, expectedSrcAddr tcpip.Address) {
-	r, err := s.FindRoute(nic, srcAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatal("FindRoute failed:", err)
-	}
-
-	defer r.Release()
-
-	if r.LocalAddress() != expectedSrcAddr {
-		t.Fatalf("got Route.LocalAddress() = %s, want = %s", expectedSrcAddr, r.LocalAddress())
-	}
-
-	if r.RemoteAddress() != dstAddr {
-		t.Fatalf("got Route.RemoteAddress() = %s, want = %s", dstAddr, r.RemoteAddress())
-	}
-}
-
-func testNoRoute(t *testing.T, s *stack.Stack, nic tcpip.NICID, srcAddr, dstAddr tcpip.Address) {
-	_, err := s.FindRoute(nic, srcAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
-	if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-		t.Fatalf("FindRoute returned unexpected error, got = %v, want = %s", err, &tcpip.ErrNoRoute{})
-	}
-}
-
-// TestAttachToLinkEndpointImmediately tests that a LinkEndpoint is attached to
-// a NetworkDispatcher when the NIC is created.
-func TestAttachToLinkEndpointImmediately(t *testing.T) {
-	const nicID = 1
-
-	tests := []struct {
-		name    string
-		nicOpts stack.NICOptions
-	}{
-		{
-			name:    "Create enabled NIC",
-			nicOpts: stack.NICOptions{Disabled: false},
-		},
-		{
-			name:    "Create disabled NIC",
-			nicOpts: stack.NICOptions{Disabled: true},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-			})
-
-			e := linkEPWithMockedAttach{
-				LinkEndpoint: loopback.New(),
-			}
-
-			if err := s.CreateNICWithOptions(nicID, &e, test.nicOpts); err != nil {
-				t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, test.nicOpts, err)
-			}
-			if !e.isAttached() {
-				t.Fatal("link endpoint not attached to a network dispatcher")
-			}
-		})
-	}
-}
-
-func TestDisableUnknownNIC(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	err := s.DisableNIC(1)
-	if _, ok := err.(*tcpip.ErrUnknownNICID); !ok {
-		t.Fatalf("got s.DisableNIC(1) = %v, want = %s", err, &tcpip.ErrUnknownNICID{})
-	}
-}
-
-func TestDisabledNICsNICInfoAndCheckNIC(t *testing.T) {
-	const nicID = 1
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	e := loopback.New()
-	nicOpts := stack.NICOptions{Disabled: true}
-	if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
-		t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, nicOpts, err)
-	}
-
-	checkNIC := func(enabled bool) {
-		t.Helper()
-
-		allNICInfo := s.NICInfo()
-		nicInfo, ok := allNICInfo[nicID]
-		if !ok {
-			t.Errorf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo)
-		} else if nicInfo.Flags.Running != enabled {
-			t.Errorf("got nicInfo.Flags.Running = %t, want = %t", nicInfo.Flags.Running, enabled)
-		}
-
-		if got := s.CheckNIC(nicID); got != enabled {
-			t.Errorf("got s.CheckNIC(%d) = %t, want = %t", nicID, got, enabled)
-		}
-	}
-
-	// NIC should initially report itself as disabled.
-	checkNIC(false)
-
-	if err := s.EnableNIC(nicID); err != nil {
-		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-	}
-	checkNIC(true)
-
-	// If the NIC is not reporting a correct enabled status, we cannot trust the
-	// next check so end the test here.
-	if t.Failed() {
-		t.FailNow()
-	}
-
-	if err := s.DisableNIC(nicID); err != nil {
-		t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-	}
-	checkNIC(false)
-}
-
-func TestRemoveUnknownNIC(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	err := s.RemoveNIC(1)
-	if _, ok := err.(*tcpip.ErrUnknownNICID); !ok {
-		t.Fatalf("got s.RemoveNIC(1) = %v, want = %s", err, &tcpip.ErrUnknownNICID{})
-	}
-}
-
-func TestRemoveNIC(t *testing.T) {
-	const nicID = 1
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	e := linkEPWithMockedAttach{
-		LinkEndpoint: loopback.New(),
-	}
-	if err := s.CreateNIC(nicID, &e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	// NIC should be present in NICInfo and attached to a NetworkDispatcher.
-	allNICInfo := s.NICInfo()
-	if _, ok := allNICInfo[nicID]; !ok {
-		t.Errorf("entry for %d missing from allNICInfo = %+v", nicID, allNICInfo)
-	}
-	if !e.isAttached() {
-		t.Fatal("link endpoint not attached to a network dispatcher")
-	}
-
-	// Removing a NIC should remove it from NICInfo and e should be detached from
-	// the NetworkDispatcher.
-	if err := s.RemoveNIC(nicID); err != nil {
-		t.Fatalf("s.RemoveNIC(%d): %s", nicID, err)
-	}
-	if nicInfo, ok := s.NICInfo()[nicID]; ok {
-		t.Errorf("got unexpected NICInfo entry for deleted NIC %d = %+v", nicID, nicInfo)
-	}
-	if e.isAttached() {
-		t.Error("link endpoint for removed NIC still attached to a network dispatcher")
-	}
-}
-
-func TestRouteWithDownNIC(t *testing.T) {
-	tests := []struct {
-		name   string
-		downFn func(s *stack.Stack, nicID tcpip.NICID) tcpip.Error
-		upFn   func(s *stack.Stack, nicID tcpip.NICID) tcpip.Error
-	}{
-		{
-			name:   "Disabled NIC",
-			downFn: (*stack.Stack).DisableNIC,
-			upFn:   (*stack.Stack).EnableNIC,
-		},
-
-		// Once a NIC is removed, it cannot be brought up.
-		{
-			name:   "Removed NIC",
-			downFn: (*stack.Stack).RemoveNIC,
-		},
-	}
-
-	const unspecifiedNIC = 0
-	const nicID1 = 1
-	const nicID2 = 2
-	const addr1 = tcpip.Address("\x01")
-	const addr2 = tcpip.Address("\x02")
-	const nic1Dst = tcpip.Address("\x05")
-	const nic2Dst = tcpip.Address("\x06")
-
-	setup := func(t *testing.T) (*stack.Stack, *channel.Endpoint, *channel.Endpoint) {
-		s := stack.New(stack.Options{
-			NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-		})
-
-		ep1 := channel.New(1, defaultMTU, "")
-		if err := s.CreateNIC(nicID1, ep1); err != nil {
-			t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
-		}
-
-		if err := s.AddAddress(nicID1, fakeNetNumber, addr1); err != nil {
-			t.Fatalf("AddAddress(%d, %d, %s): %s", nicID1, fakeNetNumber, addr1, err)
-		}
-
-		ep2 := channel.New(1, defaultMTU, "")
-		if err := s.CreateNIC(nicID2, ep2); err != nil {
-			t.Fatalf("CreateNIC(%d, _): %s", nicID2, err)
-		}
-
-		if err := s.AddAddress(nicID2, fakeNetNumber, addr2); err != nil {
-			t.Fatalf("AddAddress(%d, %d, %s): %s", nicID2, fakeNetNumber, addr2, err)
-		}
-
-		// Set a route table that sends all packets with odd destination
-		// addresses through the first NIC, and all even destination address
-		// through the second one.
-		{
-			subnet0, err := tcpip.NewSubnet("\x00", "\x01")
-			if err != nil {
-				t.Fatal(err)
-			}
-			subnet1, err := tcpip.NewSubnet("\x01", "\x01")
-			if err != nil {
-				t.Fatal(err)
-			}
-			s.SetRouteTable([]tcpip.Route{
-				{Destination: subnet1, Gateway: "\x00", NIC: nicID1},
-				{Destination: subnet0, Gateway: "\x00", NIC: nicID2},
-			})
-		}
-
-		return s, ep1, ep2
-	}
-
-	// Tests that routes through a down NIC are not used when looking up a route
-	// for a destination.
-	t.Run("Find", func(t *testing.T) {
-		for _, test := range tests {
-			t.Run(test.name, func(t *testing.T) {
-				s, _, _ := setup(t)
-
-				// Test routes to odd address.
-				testRoute(t, s, unspecifiedNIC, "", "\x05", addr1)
-				testRoute(t, s, unspecifiedNIC, addr1, "\x05", addr1)
-				testRoute(t, s, nicID1, addr1, "\x05", addr1)
-
-				// Test routes to even address.
-				testRoute(t, s, unspecifiedNIC, "", "\x06", addr2)
-				testRoute(t, s, unspecifiedNIC, addr2, "\x06", addr2)
-				testRoute(t, s, nicID2, addr2, "\x06", addr2)
-
-				// Bringing NIC1 down should result in no routes to odd addresses. Routes to
-				// even addresses should continue to be available as NIC2 is still up.
-				if err := test.downFn(s, nicID1); err != nil {
-					t.Fatalf("test.downFn(_, %d): %s", nicID1, err)
-				}
-				testNoRoute(t, s, unspecifiedNIC, "", nic1Dst)
-				testNoRoute(t, s, unspecifiedNIC, addr1, nic1Dst)
-				testNoRoute(t, s, nicID1, addr1, nic1Dst)
-				testRoute(t, s, unspecifiedNIC, "", nic2Dst, addr2)
-				testRoute(t, s, unspecifiedNIC, addr2, nic2Dst, addr2)
-				testRoute(t, s, nicID2, addr2, nic2Dst, addr2)
-
-				// Bringing NIC2 down should result in no routes to even addresses. No
-				// route should be available to any address as routes to odd addresses
-				// were made unavailable by bringing NIC1 down above.
-				if err := test.downFn(s, nicID2); err != nil {
-					t.Fatalf("test.downFn(_, %d): %s", nicID2, err)
-				}
-				testNoRoute(t, s, unspecifiedNIC, "", nic1Dst)
-				testNoRoute(t, s, unspecifiedNIC, addr1, nic1Dst)
-				testNoRoute(t, s, nicID1, addr1, nic1Dst)
-				testNoRoute(t, s, unspecifiedNIC, "", nic2Dst)
-				testNoRoute(t, s, unspecifiedNIC, addr2, nic2Dst)
-				testNoRoute(t, s, nicID2, addr2, nic2Dst)
-
-				if upFn := test.upFn; upFn != nil {
-					// Bringing NIC1 up should make routes to odd addresses available
-					// again. Routes to even addresses should continue to be unavailable
-					// as NIC2 is still down.
-					if err := upFn(s, nicID1); err != nil {
-						t.Fatalf("test.upFn(_, %d): %s", nicID1, err)
-					}
-					testRoute(t, s, unspecifiedNIC, "", nic1Dst, addr1)
-					testRoute(t, s, unspecifiedNIC, addr1, nic1Dst, addr1)
-					testRoute(t, s, nicID1, addr1, nic1Dst, addr1)
-					testNoRoute(t, s, unspecifiedNIC, "", nic2Dst)
-					testNoRoute(t, s, unspecifiedNIC, addr2, nic2Dst)
-					testNoRoute(t, s, nicID2, addr2, nic2Dst)
-				}
-			})
-		}
-	})
-
-	// Tests that writing a packet using a Route through a down NIC fails.
-	t.Run("WritePacket", func(t *testing.T) {
-		for _, test := range tests {
-			t.Run(test.name, func(t *testing.T) {
-				s, ep1, ep2 := setup(t)
-
-				r1, err := s.FindRoute(nicID1, addr1, nic1Dst, fakeNetNumber, false /* multicastLoop */)
-				if err != nil {
-					t.Errorf("FindRoute(%d, %s, %s, %d, false): %s", nicID1, addr1, nic1Dst, fakeNetNumber, err)
-				}
-				defer r1.Release()
-
-				r2, err := s.FindRoute(nicID2, addr2, nic2Dst, fakeNetNumber, false /* multicastLoop */)
-				if err != nil {
-					t.Errorf("FindRoute(%d, %s, %s, %d, false): %s", nicID2, addr2, nic2Dst, fakeNetNumber, err)
-				}
-				defer r2.Release()
-
-				// If we failed to get routes r1 or r2, we cannot proceed with the test.
-				if t.Failed() {
-					t.FailNow()
-				}
-
-				buf := buffer.View([]byte{1})
-				testSend(t, r1, ep1, buf)
-				testSend(t, r2, ep2, buf)
-
-				// Writes with Routes that use NIC1 after being brought down should fail.
-				if err := test.downFn(s, nicID1); err != nil {
-					t.Fatalf("test.downFn(_, %d): %s", nicID1, err)
-				}
-				testFailingSend(t, r1, ep1, buf, &tcpip.ErrInvalidEndpointState{})
-				testSend(t, r2, ep2, buf)
-
-				// Writes with Routes that use NIC2 after being brought down should fail.
-				if err := test.downFn(s, nicID2); err != nil {
-					t.Fatalf("test.downFn(_, %d): %s", nicID2, err)
-				}
-				testFailingSend(t, r1, ep1, buf, &tcpip.ErrInvalidEndpointState{})
-				testFailingSend(t, r2, ep2, buf, &tcpip.ErrInvalidEndpointState{})
-
-				if upFn := test.upFn; upFn != nil {
-					// Writes with Routes that use NIC1 after being brought up should
-					// succeed.
-					//
-					// TODO(gvisor.dev/issue/1491): Should we instead completely
-					// invalidate all Routes that were bound to a NIC that was brought
-					// down at some point?
-					if err := upFn(s, nicID1); err != nil {
-						t.Fatalf("test.upFn(_, %d): %s", nicID1, err)
-					}
-					testSend(t, r1, ep1, buf)
-					testFailingSend(t, r2, ep2, buf, &tcpip.ErrInvalidEndpointState{})
-				}
-			})
-		}
-	})
-}
-
-func TestRoutes(t *testing.T) {
-	// Create a stack with the fake network protocol, two nics, and two
-	// addresses per nic, the first nic has odd address, the second one has
-	// even addresses.
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep1 := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep1); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x03"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	ep2 := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(2, ep2); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	if err := s.AddAddress(2, fakeNetNumber, "\x02"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	if err := s.AddAddress(2, fakeNetNumber, "\x04"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	// Set a route table that sends all packets with odd destination
-	// addresses through the first NIC, and all even destination address
-	// through the second one.
-	{
-		subnet0, err := tcpip.NewSubnet("\x00", "\x01")
-		if err != nil {
-			t.Fatal(err)
-		}
-		subnet1, err := tcpip.NewSubnet("\x01", "\x01")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{
-			{Destination: subnet1, Gateway: "\x00", NIC: 1},
-			{Destination: subnet0, Gateway: "\x00", NIC: 2},
-		})
-	}
-
-	// Test routes to odd address.
-	testRoute(t, s, 0, "", "\x05", "\x01")
-	testRoute(t, s, 0, "\x01", "\x05", "\x01")
-	testRoute(t, s, 1, "\x01", "\x05", "\x01")
-	testRoute(t, s, 0, "\x03", "\x05", "\x03")
-	testRoute(t, s, 1, "\x03", "\x05", "\x03")
-
-	// Test routes to even address.
-	testRoute(t, s, 0, "", "\x06", "\x02")
-	testRoute(t, s, 0, "\x02", "\x06", "\x02")
-	testRoute(t, s, 2, "\x02", "\x06", "\x02")
-	testRoute(t, s, 0, "\x04", "\x06", "\x04")
-	testRoute(t, s, 2, "\x04", "\x06", "\x04")
-
-	// Try to send to odd numbered address from even numbered ones, then
-	// vice-versa.
-	testNoRoute(t, s, 0, "\x02", "\x05")
-	testNoRoute(t, s, 2, "\x02", "\x05")
-	testNoRoute(t, s, 0, "\x04", "\x05")
-	testNoRoute(t, s, 2, "\x04", "\x05")
-
-	testNoRoute(t, s, 0, "\x01", "\x06")
-	testNoRoute(t, s, 1, "\x01", "\x06")
-	testNoRoute(t, s, 0, "\x03", "\x06")
-	testNoRoute(t, s, 1, "\x03", "\x06")
-}
-
-func TestAddressRemoval(t *testing.T) {
-	const localAddrByte byte = 0x01
-	localAddr := tcpip.Address([]byte{localAddrByte})
-	remoteAddr := tcpip.Address("\x02")
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, localAddr); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
-	buf := buffer.NewView(30)
-
-	// Send and receive packets, and verify they are received.
-	buf[dstAddrOffset] = localAddrByte
-	testRecv(t, fakeNet, localAddrByte, ep, buf)
-	testSendTo(t, s, remoteAddr, ep, nil)
-
-	// Remove the address, then check that send/receive doesn't work anymore.
-	if err := s.RemoveAddress(1, localAddr); err != nil {
-		t.Fatal("RemoveAddress failed:", err)
-	}
-	testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-	testFailingSendTo(t, s, remoteAddr, ep, nil, &tcpip.ErrNoRoute{})
-
-	// Check that removing the same address fails.
-	err := s.RemoveAddress(1, localAddr)
-	if _, ok := err.(*tcpip.ErrBadLocalAddress); !ok {
-		t.Fatalf("RemoveAddress returned unexpected error, got = %v, want = %s", err, &tcpip.ErrBadLocalAddress{})
-	}
-}
-
-func TestAddressRemovalWithRouteHeld(t *testing.T) {
-	const localAddrByte byte = 0x01
-	localAddr := tcpip.Address([]byte{localAddrByte})
-	remoteAddr := tcpip.Address("\x02")
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatalf("CreateNIC failed: %v", err)
-	}
-	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-	buf := buffer.NewView(30)
-
-	if err := s.AddAddress(1, fakeNetNumber, localAddr); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	r, err := s.FindRoute(0, "", remoteAddr, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatal("FindRoute failed:", err)
-	}
-
-	// Send and receive packets, and verify they are received.
-	buf[dstAddrOffset] = localAddrByte
-	testRecv(t, fakeNet, localAddrByte, ep, buf)
-	testSend(t, r, ep, nil)
-	testSendTo(t, s, remoteAddr, ep, nil)
-
-	// Remove the address, then check that send/receive doesn't work anymore.
-	if err := s.RemoveAddress(1, localAddr); err != nil {
-		t.Fatal("RemoveAddress failed:", err)
-	}
-	testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-	testFailingSend(t, r, ep, nil, &tcpip.ErrInvalidEndpointState{})
-	testFailingSendTo(t, s, remoteAddr, ep, nil, &tcpip.ErrNoRoute{})
-
-	// Check that removing the same address fails.
-	{
-		err := s.RemoveAddress(1, localAddr)
-		if _, ok := err.(*tcpip.ErrBadLocalAddress); !ok {
-			t.Fatalf("RemoveAddress returned unexpected error, got = %v, want = %s", err, &tcpip.ErrBadLocalAddress{})
-		}
-	}
-}
-
-func verifyAddress(t *testing.T, s *stack.Stack, nicID tcpip.NICID, addr tcpip.Address) {
-	t.Helper()
-	info, ok := s.NICInfo()[nicID]
-	if !ok {
-		t.Fatalf("NICInfo() failed to find nicID=%d", nicID)
-	}
-	if len(addr) == 0 {
-		// No address given, verify that there is no address assigned to the NIC.
-		for _, a := range info.ProtocolAddresses {
-			if a.Protocol == fakeNetNumber && a.AddressWithPrefix != (tcpip.AddressWithPrefix{}) {
-				t.Errorf("verify no-address: got = %s, want = %s", a.AddressWithPrefix, (tcpip.AddressWithPrefix{}))
-			}
-		}
-		return
-	}
-	// Address given, verify the address is assigned to the NIC and no other
-	// address is.
-	found := false
-	for _, a := range info.ProtocolAddresses {
-		if a.Protocol == fakeNetNumber {
-			if a.AddressWithPrefix.Address == addr {
-				found = true
-			} else {
-				t.Errorf("verify address: got = %s, want = %s", a.AddressWithPrefix.Address, addr)
-			}
-		}
-	}
-	if !found {
-		t.Errorf("verify address: couldn't find %s on the NIC", addr)
-	}
-}
-
-func TestEndpointExpiration(t *testing.T) {
-	const (
-		localAddrByte byte          = 0x01
-		remoteAddr    tcpip.Address = "\x03"
-		noAddr        tcpip.Address = ""
-		nicID         tcpip.NICID   = 1
-	)
-	localAddr := tcpip.Address([]byte{localAddrByte})
-
-	for _, promiscuous := range []bool{true, false} {
-		for _, spoofing := range []bool{true, false} {
-			t.Run(fmt.Sprintf("promiscuous=%t spoofing=%t", promiscuous, spoofing), func(t *testing.T) {
-				s := stack.New(stack.Options{
-					NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-				})
-
-				ep := channel.New(10, defaultMTU, "")
-				if err := s.CreateNIC(nicID, ep); err != nil {
-					t.Fatal("CreateNIC failed:", err)
-				}
-
-				{
-					subnet, err := tcpip.NewSubnet("\x00", "\x00")
-					if err != nil {
-						t.Fatal(err)
-					}
-					s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-				}
-
-				fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-				buf := buffer.NewView(30)
-				buf[dstAddrOffset] = localAddrByte
-
-				if promiscuous {
-					if err := s.SetPromiscuousMode(nicID, true); err != nil {
-						t.Fatal("SetPromiscuousMode failed:", err)
-					}
-				}
-
-				if spoofing {
-					if err := s.SetSpoofing(nicID, true); err != nil {
-						t.Fatal("SetSpoofing failed:", err)
-					}
-				}
-
-				// 1. No Address yet, send should only work for spoofing, receive for
-				// promiscuous mode.
-				//-----------------------
-				verifyAddress(t, s, nicID, noAddr)
-				if promiscuous {
-					testRecv(t, fakeNet, localAddrByte, ep, buf)
-				} else {
-					testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-				}
-				if spoofing {
-					// FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
-					// testSendTo(t, s, remoteAddr, ep, nil)
-				} else {
-					testFailingSendTo(t, s, remoteAddr, ep, nil, &tcpip.ErrNoRoute{})
-				}
-
-				// 2. Add Address, everything should work.
-				//-----------------------
-				if err := s.AddAddress(nicID, fakeNetNumber, localAddr); err != nil {
-					t.Fatal("AddAddress failed:", err)
-				}
-				verifyAddress(t, s, nicID, localAddr)
-				testRecv(t, fakeNet, localAddrByte, ep, buf)
-				testSendTo(t, s, remoteAddr, ep, nil)
-
-				// 3. Remove the address, send should only work for spoofing, receive
-				// for promiscuous mode.
-				//-----------------------
-				if err := s.RemoveAddress(nicID, localAddr); err != nil {
-					t.Fatal("RemoveAddress failed:", err)
-				}
-				verifyAddress(t, s, nicID, noAddr)
-				if promiscuous {
-					testRecv(t, fakeNet, localAddrByte, ep, buf)
-				} else {
-					testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-				}
-				if spoofing {
-					// FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
-					// testSendTo(t, s, remoteAddr, ep, nil)
-				} else {
-					testFailingSendTo(t, s, remoteAddr, ep, nil, &tcpip.ErrNoRoute{})
-				}
-
-				// 4. Add Address back, everything should work again.
-				//-----------------------
-				if err := s.AddAddress(nicID, fakeNetNumber, localAddr); err != nil {
-					t.Fatal("AddAddress failed:", err)
-				}
-				verifyAddress(t, s, nicID, localAddr)
-				testRecv(t, fakeNet, localAddrByte, ep, buf)
-				testSendTo(t, s, remoteAddr, ep, nil)
-
-				// 5. Take a reference to the endpoint by getting a route. Verify that
-				// we can still send/receive, including sending using the route.
-				//-----------------------
-				r, err := s.FindRoute(0, "", remoteAddr, fakeNetNumber, false /* multicastLoop */)
-				if err != nil {
-					t.Fatal("FindRoute failed:", err)
-				}
-				testRecv(t, fakeNet, localAddrByte, ep, buf)
-				testSendTo(t, s, remoteAddr, ep, nil)
-				testSend(t, r, ep, nil)
-
-				// 6. Remove the address. Send should only work for spoofing, receive
-				// for promiscuous mode.
-				//-----------------------
-				if err := s.RemoveAddress(nicID, localAddr); err != nil {
-					t.Fatal("RemoveAddress failed:", err)
-				}
-				verifyAddress(t, s, nicID, noAddr)
-				if promiscuous {
-					testRecv(t, fakeNet, localAddrByte, ep, buf)
-				} else {
-					testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-				}
-				if spoofing {
-					testSend(t, r, ep, nil)
-					testSendTo(t, s, remoteAddr, ep, nil)
-				} else {
-					testFailingSend(t, r, ep, nil, &tcpip.ErrInvalidEndpointState{})
-					testFailingSendTo(t, s, remoteAddr, ep, nil, &tcpip.ErrNoRoute{})
-				}
-
-				// 7. Add Address back, everything should work again.
-				//-----------------------
-				if err := s.AddAddress(nicID, fakeNetNumber, localAddr); err != nil {
-					t.Fatal("AddAddress failed:", err)
-				}
-				verifyAddress(t, s, nicID, localAddr)
-				testRecv(t, fakeNet, localAddrByte, ep, buf)
-				testSendTo(t, s, remoteAddr, ep, nil)
-				testSend(t, r, ep, nil)
-
-				// 8. Remove the route, sendTo/recv should still work.
-				//-----------------------
-				r.Release()
-				verifyAddress(t, s, nicID, localAddr)
-				testRecv(t, fakeNet, localAddrByte, ep, buf)
-				testSendTo(t, s, remoteAddr, ep, nil)
-
-				// 9. Remove the address. Send should only work for spoofing, receive
-				// for promiscuous mode.
-				//-----------------------
-				if err := s.RemoveAddress(nicID, localAddr); err != nil {
-					t.Fatal("RemoveAddress failed:", err)
-				}
-				verifyAddress(t, s, nicID, noAddr)
-				if promiscuous {
-					testRecv(t, fakeNet, localAddrByte, ep, buf)
-				} else {
-					testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-				}
-				if spoofing {
-					// FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
-					// testSendTo(t, s, remoteAddr, ep, nil)
-				} else {
-					testFailingSendTo(t, s, remoteAddr, ep, nil, &tcpip.ErrNoRoute{})
-				}
-			})
-		}
-	}
-}
-
-func TestPromiscuousMode(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
-	buf := buffer.NewView(30)
-
-	// Write a packet, and check that it doesn't get delivered as we don't
-	// have a matching endpoint.
-	const localAddrByte byte = 0x01
-	buf[dstAddrOffset] = localAddrByte
-	testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-
-	// Set promiscuous mode, then check that packet is delivered.
-	if err := s.SetPromiscuousMode(1, true); err != nil {
-		t.Fatal("SetPromiscuousMode failed:", err)
-	}
-	testRecv(t, fakeNet, localAddrByte, ep, buf)
-
-	// Check that we can't get a route as there is no local address.
-	_, err := s.FindRoute(0, "", "\x02", fakeNetNumber, false /* multicastLoop */)
-	if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-		t.Fatalf("FindRoute returned unexpected error: got = %v, want = %s", err, &tcpip.ErrNoRoute{})
-	}
-
-	// Set promiscuous mode to false, then check that packet can't be
-	// delivered anymore.
-	if err := s.SetPromiscuousMode(1, false); err != nil {
-		t.Fatal("SetPromiscuousMode failed:", err)
-	}
-	testFailingRecv(t, fakeNet, localAddrByte, ep, buf)
-}
-
-// TestExternalSendWithHandleLocal tests that the stack creates a non-local
-// route when spoofing or promiscuous mode are enabled.
-//
-// This test makes sure that packets are transmitted from the stack.
-func TestExternalSendWithHandleLocal(t *testing.T) {
-	const (
-		unspecifiedNICID = 0
-		nicID            = 1
-
-		localAddr = tcpip.Address("\x01")
-		dstAddr   = tcpip.Address("\x03")
-	)
-
-	subnet, err := tcpip.NewSubnet("\x00", "\x00")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	tests := []struct {
-		name           string
-		configureStack func(*testing.T, *stack.Stack)
-	}{
-		{
-			name:           "Default",
-			configureStack: func(*testing.T, *stack.Stack) {},
-		},
-		{
-			name: "Spoofing",
-			configureStack: func(t *testing.T, s *stack.Stack) {
-				if err := s.SetSpoofing(nicID, true); err != nil {
-					t.Fatalf("s.SetSpoofing(%d, true): %s", nicID, err)
-				}
-			},
-		},
-		{
-			name: "Promiscuous",
-			configureStack: func(t *testing.T, s *stack.Stack) {
-				if err := s.SetPromiscuousMode(nicID, true); err != nil {
-					t.Fatalf("s.SetPromiscuousMode(%d, true): %s", nicID, err)
-				}
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			for _, handleLocal := range []bool{true, false} {
-				t.Run(fmt.Sprintf("HandleLocal=%t", handleLocal), func(t *testing.T) {
-					s := stack.New(stack.Options{
-						NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-						HandleLocal:      handleLocal,
-					})
-
-					ep := channel.New(1, defaultMTU, "")
-					if err := s.CreateNIC(nicID, ep); err != nil {
-						t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
-					}
-					if err := s.AddAddress(nicID, fakeNetNumber, localAddr); err != nil {
-						t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, fakeNetNumber, localAddr, err)
-					}
-
-					s.SetRouteTable([]tcpip.Route{{Destination: subnet, NIC: nicID}})
-
-					test.configureStack(t, s)
-
-					r, err := s.FindRoute(unspecifiedNICID, localAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
-					if err != nil {
-						t.Fatalf("s.FindRoute(%d, %s, %s, %d, false): %s", unspecifiedNICID, localAddr, dstAddr, fakeNetNumber, err)
-					}
-					defer r.Release()
-
-					if r.LocalAddress() != localAddr {
-						t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), localAddr)
-					}
-					if r.RemoteAddress() != dstAddr {
-						t.Errorf("got r.RemoteAddress() = %s, want = %s", r.RemoteAddress(), dstAddr)
-					}
-
-					if n := ep.Drain(); n != 0 {
-						t.Fatalf("got ep.Drain() = %d, want = 0", n)
-					}
-					if err := r.WritePacket(nil /* gso */, stack.NetworkHeaderParams{
-						Protocol: fakeTransNumber,
-						TTL:      123,
-						TOS:      stack.DefaultTOS,
-					}, stack.NewPacketBuffer(stack.PacketBufferOptions{
-						ReserveHeaderBytes: int(r.MaxHeaderLength()),
-						Data:               buffer.NewView(10).ToVectorisedView(),
-					})); err != nil {
-						t.Fatalf("r.WritePacket(nil, _, _): %s", err)
-					}
-					if n := ep.Drain(); n != 1 {
-						t.Fatalf("got ep.Drain() = %d, want = 1", n)
-					}
-				})
-			}
-		})
-	}
-}
-
-func TestSpoofingWithAddress(t *testing.T) {
-	localAddr := tcpip.Address("\x01")
-	nonExistentLocalAddr := tcpip.Address("\x02")
-	dstAddr := tcpip.Address("\x03")
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, localAddr); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	// With address spoofing disabled, FindRoute does not permit an address
-	// that was not added to the NIC to be used as the source.
-	r, err := s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
-	if err == nil {
-		t.Errorf("FindRoute succeeded with route %+v when it should have failed", r)
-	}
-
-	// With address spoofing enabled, FindRoute permits any address to be used
-	// as the source.
-	if err := s.SetSpoofing(1, true); err != nil {
-		t.Fatal("SetSpoofing failed:", err)
-	}
-	r, err = s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatal("FindRoute failed:", err)
-	}
-	if r.LocalAddress() != nonExistentLocalAddr {
-		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nonExistentLocalAddr)
-	}
-	if r.RemoteAddress() != dstAddr {
-		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), dstAddr)
-	}
-	// Sending a packet works.
-	testSendTo(t, s, dstAddr, ep, nil)
-	testSend(t, r, ep, nil)
-
-	// FindRoute should also work with a local address that exists on the NIC.
-	r, err = s.FindRoute(0, localAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatal("FindRoute failed:", err)
-	}
-	if r.LocalAddress() != localAddr {
-		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nonExistentLocalAddr)
-	}
-	if r.RemoteAddress() != dstAddr {
-		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), dstAddr)
-	}
-	// Sending a packet using the route works.
-	testSend(t, r, ep, nil)
-}
-
-func TestSpoofingNoAddress(t *testing.T) {
-	nonExistentLocalAddr := tcpip.Address("\x01")
-	dstAddr := tcpip.Address("\x02")
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	// With address spoofing disabled, FindRoute does not permit an address
-	// that was not added to the NIC to be used as the source.
-	r, err := s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
-	if err == nil {
-		t.Errorf("FindRoute succeeded with route %+v when it should have failed", r)
-	}
-	// Sending a packet fails.
-	testFailingSendTo(t, s, dstAddr, ep, nil, &tcpip.ErrNoRoute{})
-
-	// With address spoofing enabled, FindRoute permits any address to be used
-	// as the source.
-	if err := s.SetSpoofing(1, true); err != nil {
-		t.Fatal("SetSpoofing failed:", err)
-	}
-	r, err = s.FindRoute(0, nonExistentLocalAddr, dstAddr, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatal("FindRoute failed:", err)
-	}
-	if r.LocalAddress() != nonExistentLocalAddr {
-		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nonExistentLocalAddr)
-	}
-	if r.RemoteAddress() != dstAddr {
-		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), dstAddr)
-	}
-	// Sending a packet works.
-	// FIXME(b/139841518):Spoofing doesn't work if there is no primary address.
-	// testSendTo(t, s, remoteAddr, ep, nil)
-}
-
-func TestOutgoingBroadcastWithEmptyRouteTable(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-	s.SetRouteTable([]tcpip.Route{})
-
-	// If there is no endpoint, it won't work.
-	{
-		_, err := s.FindRoute(1, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
-		if _, ok := err.(*tcpip.ErrNetworkUnreachable); !ok {
-			t.Fatalf("got FindRoute(1, %s, %s, %d) = %s, want = %s", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err, &tcpip.ErrNetworkUnreachable{})
-		}
-	}
-
-	protoAddr := tcpip.ProtocolAddress{Protocol: fakeNetNumber, AddressWithPrefix: tcpip.AddressWithPrefix{header.IPv4Any, 0}}
-	if err := s.AddProtocolAddress(1, protoAddr); err != nil {
-		t.Fatalf("AddProtocolAddress(1, %v) failed: %v", protoAddr, err)
-	}
-	r, err := s.FindRoute(1, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatalf("FindRoute(1, %v, %v, %d) failed: %v", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err)
-	}
-	if r.LocalAddress() != header.IPv4Any {
-		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), header.IPv4Any)
-	}
-
-	if r.RemoteAddress() != header.IPv4Broadcast {
-		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), header.IPv4Broadcast)
-	}
-
-	// If the NIC doesn't exist, it won't work.
-	{
-		_, err := s.FindRoute(2, header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
-		if _, ok := err.(*tcpip.ErrNetworkUnreachable); !ok {
-			t.Fatalf("got FindRoute(2, %v, %v, %d) = %v want = %v", header.IPv4Any, header.IPv4Broadcast, fakeNetNumber, err, &tcpip.ErrNetworkUnreachable{})
-		}
-	}
-}
-
-func TestOutgoingBroadcastWithRouteTable(t *testing.T) {
-	defaultAddr := tcpip.AddressWithPrefix{header.IPv4Any, 0}
-	// Local subnet on NIC1: 192.168.1.58/24, gateway 192.168.1.1.
-	nic1Addr := tcpip.AddressWithPrefix{"\xc0\xa8\x01\x3a", 24}
-	nic1Gateway := tcpip.Address("\xc0\xa8\x01\x01")
-	// Local subnet on NIC2: 10.10.10.5/24, gateway 10.10.10.1.
-	nic2Addr := tcpip.AddressWithPrefix{"\x0a\x0a\x0a\x05", 24}
-	nic2Gateway := tcpip.Address("\x0a\x0a\x0a\x01")
-
-	// Create a new stack with two NICs.
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatalf("CreateNIC failed: %s", err)
-	}
-	if err := s.CreateNIC(2, ep); err != nil {
-		t.Fatalf("CreateNIC failed: %s", err)
-	}
-	nic1ProtoAddr := tcpip.ProtocolAddress{fakeNetNumber, nic1Addr}
-	if err := s.AddProtocolAddress(1, nic1ProtoAddr); err != nil {
-		t.Fatalf("AddProtocolAddress(1, %v) failed: %v", nic1ProtoAddr, err)
-	}
-
-	nic2ProtoAddr := tcpip.ProtocolAddress{fakeNetNumber, nic2Addr}
-	if err := s.AddProtocolAddress(2, nic2ProtoAddr); err != nil {
-		t.Fatalf("AddAddress(2, %v) failed: %v", nic2ProtoAddr, err)
-	}
-
-	// Set the initial route table.
-	rt := []tcpip.Route{
-		{Destination: nic1Addr.Subnet(), NIC: 1},
-		{Destination: nic2Addr.Subnet(), NIC: 2},
-		{Destination: defaultAddr.Subnet(), Gateway: nic2Gateway, NIC: 2},
-		{Destination: defaultAddr.Subnet(), Gateway: nic1Gateway, NIC: 1},
-	}
-	s.SetRouteTable(rt)
-
-	// When an interface is given, the route for a broadcast goes through it.
-	r, err := s.FindRoute(1, nic1Addr.Address, header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatalf("FindRoute(1, %v, %v, %d) failed: %v", nic1Addr.Address, header.IPv4Broadcast, fakeNetNumber, err)
-	}
-	if r.LocalAddress() != nic1Addr.Address {
-		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nic1Addr.Address)
-	}
-
-	if r.RemoteAddress() != header.IPv4Broadcast {
-		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), header.IPv4Broadcast)
-	}
-
-	// When an interface is not given, it consults the route table.
-	// 1. Case: Using the default route.
-	r, err = s.FindRoute(0, "", header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatalf("FindRoute(0, \"\", %s, %d) failed: %s", header.IPv4Broadcast, fakeNetNumber, err)
-	}
-	if r.LocalAddress() != nic2Addr.Address {
-		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nic2Addr.Address)
-	}
-
-	if r.RemoteAddress() != header.IPv4Broadcast {
-		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), header.IPv4Broadcast)
-	}
-
-	// 2. Case: Having an explicit route for broadcast will select that one.
-	rt = append(
-		[]tcpip.Route{
-			{Destination: tcpip.AddressWithPrefix{header.IPv4Broadcast, 8 * header.IPv4AddressSize}.Subnet(), NIC: 1},
-		},
-		rt...,
-	)
-	s.SetRouteTable(rt)
-	r, err = s.FindRoute(0, "", header.IPv4Broadcast, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatalf("FindRoute(0, \"\", %s, %d) failed: %s", header.IPv4Broadcast, fakeNetNumber, err)
-	}
-	if r.LocalAddress() != nic1Addr.Address {
-		t.Errorf("got Route.LocalAddress() = %s, want = %s", r.LocalAddress(), nic1Addr.Address)
-	}
-
-	if r.RemoteAddress() != header.IPv4Broadcast {
-		t.Errorf("got Route.RemoteAddress() = %s, want = %s", r.RemoteAddress(), header.IPv4Broadcast)
-	}
-}
-
-func TestMulticastOrIPv6LinkLocalNeedsNoRoute(t *testing.T) {
-	for _, tc := range []struct {
-		name        string
-		routeNeeded bool
-		address     tcpip.Address
-	}{
-		// IPv4 multicast address range: 224.0.0.0 - 239.255.255.255
-		//                <=>  0xe0.0x00.0x00.0x00 - 0xef.0xff.0xff.0xff
-		{"IPv4 Multicast 1", false, "\xe0\x00\x00\x00"},
-		{"IPv4 Multicast 2", false, "\xef\xff\xff\xff"},
-		{"IPv4 Unicast 1", true, "\xdf\xff\xff\xff"},
-		{"IPv4 Unicast 2", true, "\xf0\x00\x00\x00"},
-		{"IPv4 Unicast 3", true, "\x00\x00\x00\x00"},
-
-		// IPv6 multicast address is 0xff[8] + flags[4] + scope[4] + groupId[112]
-		{"IPv6 Multicast 1", false, "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Multicast 2", false, "\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Multicast 3", false, "\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},
-
-		// IPv6 link-local address starts with fe80::/10.
-		{"IPv6 Unicast Link-Local 1", false, "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Unicast Link-Local 2", false, "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"},
-		{"IPv6 Unicast Link-Local 3", false, "\xfe\x80\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff"},
-		{"IPv6 Unicast Link-Local 4", false, "\xfe\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Unicast Link-Local 5", false, "\xfe\xbf\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},
-
-		// IPv6 addresses that are neither multicast nor link-local.
-		{"IPv6 Unicast Not Link-Local 1", true, "\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Unicast Not Link-Local 2", true, "\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"},
-		{"IPv6 Unicast Not Link-local 3", true, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Unicast Not Link-Local 4", true, "\xfe\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Unicast Not Link-Local 5", true, "\xfe\xdf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Unicast Not Link-Local 6", true, "\xfd\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-		{"IPv6 Unicast Not Link-Local 7", true, "\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
-	} {
-		t.Run(tc.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-			})
-
-			ep := channel.New(10, defaultMTU, "")
-			if err := s.CreateNIC(1, ep); err != nil {
-				t.Fatal("CreateNIC failed:", err)
-			}
-
-			s.SetRouteTable([]tcpip.Route{})
-
-			var anyAddr tcpip.Address
-			if len(tc.address) == header.IPv4AddressSize {
-				anyAddr = header.IPv4Any
-			} else {
-				anyAddr = header.IPv6Any
-			}
-
-			var want tcpip.Error = &tcpip.ErrNetworkUnreachable{}
-			if tc.routeNeeded {
-				want = &tcpip.ErrNoRoute{}
-			}
-
-			// If there is no endpoint, it won't work.
-			if _, err := s.FindRoute(1, anyAddr, tc.address, fakeNetNumber, false /* multicastLoop */); err != want {
-				t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %v", anyAddr, tc.address, fakeNetNumber, err, want)
-			}
-
-			if err := s.AddAddress(1, fakeNetNumber, anyAddr); err != nil {
-				t.Fatalf("AddAddress(%v, %v) failed: %v", fakeNetNumber, anyAddr, err)
-			}
-
-			if r, err := s.FindRoute(1, anyAddr, tc.address, fakeNetNumber, false /* multicastLoop */); tc.routeNeeded {
-				// Route table is empty but we need a route, this should cause an error.
-				if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-					t.Fatalf("got FindRoute(1, %v, %v, %v) = %v, want = %v", anyAddr, tc.address, fakeNetNumber, err, &tcpip.ErrNoRoute{})
-				}
-			} else {
-				if err != nil {
-					t.Fatalf("FindRoute(1, %v, %v, %v) failed: %v", anyAddr, tc.address, fakeNetNumber, err)
-				}
-				if r.LocalAddress() != anyAddr {
-					t.Errorf("Bad local address: got %v, want = %v", r.LocalAddress(), anyAddr)
-				}
-				if r.RemoteAddress() != tc.address {
-					t.Errorf("Bad remote address: got %v, want = %v", r.RemoteAddress(), tc.address)
-				}
-			}
-			// If the NIC doesn't exist, it won't work.
-			if _, err := s.FindRoute(2, anyAddr, tc.address, fakeNetNumber, false /* multicastLoop */); err != want {
-				t.Fatalf("got FindRoute(2, %v, %v, %v) = %v want = %v", anyAddr, tc.address, fakeNetNumber, err, want)
-			}
-		})
-	}
-}
-
-func TestNetworkOption(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{fakeNetFactory},
-		TransportProtocols: []stack.TransportProtocolFactory{},
-	})
-
-	opt := tcpip.DefaultTTLOption(5)
-	if err := s.SetNetworkProtocolOption(fakeNetNumber, &opt); err != nil {
-		t.Fatalf("s.SetNetworkProtocolOption(%d, &%T(%d)): %s", fakeNetNumber, opt, opt, err)
-	}
-
-	var optGot tcpip.DefaultTTLOption
-	if err := s.NetworkProtocolOption(fakeNetNumber, &optGot); err != nil {
-		t.Fatalf("s.NetworkProtocolOption(%d, &%T): %s", fakeNetNumber, optGot, err)
-	}
-
-	if opt != optGot {
-		t.Errorf("got optGot = %d, want = %d", optGot, opt)
-	}
-}
-
-func TestGetMainNICAddressAddPrimaryNonPrimary(t *testing.T) {
-	const nicID = 1
-
-	for _, addrLen := range []int{4, 16} {
-		t.Run(fmt.Sprintf("addrLen=%d", addrLen), func(t *testing.T) {
-			for canBe := 0; canBe < 3; canBe++ {
-				t.Run(fmt.Sprintf("canBe=%d", canBe), func(t *testing.T) {
-					for never := 0; never < 3; never++ {
-						t.Run(fmt.Sprintf("never=%d", never), func(t *testing.T) {
-							s := stack.New(stack.Options{
-								NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-							})
-							ep := channel.New(10, defaultMTU, "")
-							if err := s.CreateNIC(nicID, ep); err != nil {
-								t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-							}
-							// Insert <canBe> primary and <never> never-primary addresses.
-							// Each one will add a network endpoint to the NIC.
-							primaryAddrAdded := make(map[tcpip.AddressWithPrefix]struct{})
-							for i := 0; i < canBe+never; i++ {
-								var behavior stack.PrimaryEndpointBehavior
-								if i < canBe {
-									behavior = stack.CanBePrimaryEndpoint
-								} else {
-									behavior = stack.NeverPrimaryEndpoint
-								}
-								// Add an address and in case of a primary one include a
-								// prefixLen.
-								address := tcpip.Address(bytes.Repeat([]byte{byte(i)}, addrLen))
-								if behavior == stack.CanBePrimaryEndpoint {
-									protocolAddress := tcpip.ProtocolAddress{
-										Protocol: fakeNetNumber,
-										AddressWithPrefix: tcpip.AddressWithPrefix{
-											Address:   address,
-											PrefixLen: addrLen * 8,
-										},
-									}
-									if err := s.AddProtocolAddressWithOptions(nicID, protocolAddress, behavior); err != nil {
-										t.Fatalf("AddProtocolAddressWithOptions(%d, %#v, %d): %s", nicID, protocolAddress, behavior, err)
-									}
-									// Remember the address/prefix.
-									primaryAddrAdded[protocolAddress.AddressWithPrefix] = struct{}{}
-								} else {
-									if err := s.AddAddressWithOptions(nicID, fakeNetNumber, address, behavior); err != nil {
-										t.Fatalf("AddAddressWithOptions(%d, %d, %s, %d): %s:", nicID, fakeNetNumber, address, behavior, err)
-									}
-								}
-							}
-							// Check that GetMainNICAddress returns an address if at least
-							// one primary address was added. In that case make sure the
-							// address/prefixLen matches what we added.
-							gotAddr, err := s.GetMainNICAddress(nicID, fakeNetNumber)
-							if err != nil {
-								t.Fatalf("GetMainNICAddress(%d, %d): %s", nicID, fakeNetNumber, err)
-							}
-							if len(primaryAddrAdded) == 0 {
-								// No primary addresses present.
-								if wantAddr := (tcpip.AddressWithPrefix{}); gotAddr != wantAddr {
-									t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, gotAddr, wantAddr)
-								}
-							} else {
-								// At least one primary address was added, verify the returned
-								// address is in the list of primary addresses we added.
-								if _, ok := primaryAddrAdded[gotAddr]; !ok {
-									t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, gotAddr, primaryAddrAdded)
-								}
-							}
-						})
-					}
-				})
-			}
-		})
-	}
-}
-
-func TestGetMainNICAddressErrors(t *testing.T) {
-	const nicID = 1
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, arp.NewProtocol},
-	})
-	if err := s.CreateNIC(nicID, loopback.New()); err != nil {
-		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-	}
-
-	// Sanity check with a successful call.
-	if addr, err := s.GetMainNICAddress(nicID, ipv4.ProtocolNumber); err != nil {
-		t.Errorf("s.GetMainNICAddress(%d, %d): %s", nicID, ipv4.ProtocolNumber, err)
-	} else if want := (tcpip.AddressWithPrefix{}); addr != want {
-		t.Errorf("got s.GetMainNICAddress(%d, %d) = %s, want = %s", nicID, ipv4.ProtocolNumber, addr, want)
-	}
-
-	const unknownNICID = nicID + 1
-	switch addr, err := s.GetMainNICAddress(unknownNICID, ipv4.ProtocolNumber); err.(type) {
-	case *tcpip.ErrUnknownNICID:
-	default:
-		t.Errorf("got s.GetMainNICAddress(%d, %d) = (%s, %T), want = (_, tcpip.ErrUnknownNICID)", unknownNICID, ipv4.ProtocolNumber, addr, err)
-	}
-
-	// ARP is not an addressable network endpoint.
-	switch addr, err := s.GetMainNICAddress(nicID, arp.ProtocolNumber); err.(type) {
-	case *tcpip.ErrNotSupported:
-	default:
-		t.Errorf("got s.GetMainNICAddress(%d, %d) = (%s, %T), want = (_, tcpip.ErrNotSupported)", nicID, arp.ProtocolNumber, addr, err)
-	}
-
-	const unknownProtocolNumber = 1234
-	switch addr, err := s.GetMainNICAddress(nicID, unknownProtocolNumber); err.(type) {
-	case *tcpip.ErrUnknownProtocol:
-	default:
-		t.Errorf("got s.GetMainNICAddress(%d, %d) = (%s, %T), want = (_, tcpip.ErrUnknownProtocol)", nicID, unknownProtocolNumber, addr, err)
-	}
-}
-
-func TestGetMainNICAddressAddRemove(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	for _, tc := range []struct {
-		name      string
-		address   tcpip.Address
-		prefixLen int
-	}{
-		{"IPv4", "\x01\x01\x01\x01", 24},
-		{"IPv6", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 116},
-	} {
-		t.Run(tc.name, func(t *testing.T) {
-			protocolAddress := tcpip.ProtocolAddress{
-				Protocol: fakeNetNumber,
-				AddressWithPrefix: tcpip.AddressWithPrefix{
-					Address:   tc.address,
-					PrefixLen: tc.prefixLen,
-				},
-			}
-			if err := s.AddProtocolAddress(1, protocolAddress); err != nil {
-				t.Fatal("AddProtocolAddress failed:", err)
-			}
-
-			// Check that we get the right initial address and prefix length.
-			if err := checkGetMainNICAddress(s, 1, fakeNetNumber, protocolAddress.AddressWithPrefix); err != nil {
-				t.Fatal(err)
-			}
-
-			if err := s.RemoveAddress(1, protocolAddress.AddressWithPrefix.Address); err != nil {
-				t.Fatal("RemoveAddress failed:", err)
-			}
-
-			// Check that we get no address after removal.
-			if err := checkGetMainNICAddress(s, 1, fakeNetNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-		})
-	}
-}
-
-// Simple network address generator. Good for 255 addresses.
-type addressGenerator struct{ cnt byte }
-
-func (g *addressGenerator) next(addrLen int) tcpip.Address {
-	g.cnt++
-	return tcpip.Address(bytes.Repeat([]byte{g.cnt}, addrLen))
-}
-
-func verifyAddresses(t *testing.T, expectedAddresses, gotAddresses []tcpip.ProtocolAddress) {
-	t.Helper()
-
-	if len(gotAddresses) != len(expectedAddresses) {
-		t.Fatalf("got len(addresses) = %d, want = %d", len(gotAddresses), len(expectedAddresses))
-	}
-
-	sort.Slice(gotAddresses, func(i, j int) bool {
-		return gotAddresses[i].AddressWithPrefix.Address < gotAddresses[j].AddressWithPrefix.Address
-	})
-	sort.Slice(expectedAddresses, func(i, j int) bool {
-		return expectedAddresses[i].AddressWithPrefix.Address < expectedAddresses[j].AddressWithPrefix.Address
-	})
-
-	for i, gotAddr := range gotAddresses {
-		expectedAddr := expectedAddresses[i]
-		if gotAddr != expectedAddr {
-			t.Errorf("got address = %+v, wanted = %+v", gotAddr, expectedAddr)
-		}
-	}
-}
-
-func TestAddAddress(t *testing.T) {
-	const nicID = 1
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(nicID, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	var addrGen addressGenerator
-	expectedAddresses := make([]tcpip.ProtocolAddress, 0, 2)
-	for _, addrLen := range []int{4, 16} {
-		address := addrGen.next(addrLen)
-		if err := s.AddAddress(nicID, fakeNetNumber, address); err != nil {
-			t.Fatalf("AddAddress(address=%s) failed: %s", address, err)
-		}
-		expectedAddresses = append(expectedAddresses, tcpip.ProtocolAddress{
-			Protocol:          fakeNetNumber,
-			AddressWithPrefix: tcpip.AddressWithPrefix{address, fakeDefaultPrefixLen},
-		})
-	}
-
-	gotAddresses := s.AllAddresses()[nicID]
-	verifyAddresses(t, expectedAddresses, gotAddresses)
-}
-
-func TestAddProtocolAddress(t *testing.T) {
-	const nicID = 1
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(nicID, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	var addrGen addressGenerator
-	addrLenRange := []int{4, 16}
-	prefixLenRange := []int{8, 13, 20, 32}
-	expectedAddresses := make([]tcpip.ProtocolAddress, 0, len(addrLenRange)*len(prefixLenRange))
-	for _, addrLen := range addrLenRange {
-		for _, prefixLen := range prefixLenRange {
-			protocolAddress := tcpip.ProtocolAddress{
-				Protocol: fakeNetNumber,
-				AddressWithPrefix: tcpip.AddressWithPrefix{
-					Address:   addrGen.next(addrLen),
-					PrefixLen: prefixLen,
-				},
-			}
-			if err := s.AddProtocolAddress(nicID, protocolAddress); err != nil {
-				t.Errorf("AddProtocolAddress(%+v) failed: %s", protocolAddress, err)
-			}
-			expectedAddresses = append(expectedAddresses, protocolAddress)
-		}
-	}
-
-	gotAddresses := s.AllAddresses()[nicID]
-	verifyAddresses(t, expectedAddresses, gotAddresses)
-}
-
-func TestAddAddressWithOptions(t *testing.T) {
-	const nicID = 1
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(nicID, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	addrLenRange := []int{4, 16}
-	behaviorRange := []stack.PrimaryEndpointBehavior{stack.CanBePrimaryEndpoint, stack.FirstPrimaryEndpoint, stack.NeverPrimaryEndpoint}
-	expectedAddresses := make([]tcpip.ProtocolAddress, 0, len(addrLenRange)*len(behaviorRange))
-	var addrGen addressGenerator
-	for _, addrLen := range addrLenRange {
-		for _, behavior := range behaviorRange {
-			address := addrGen.next(addrLen)
-			if err := s.AddAddressWithOptions(nicID, fakeNetNumber, address, behavior); err != nil {
-				t.Fatalf("AddAddressWithOptions(address=%s, behavior=%d) failed: %s", address, behavior, err)
-			}
-			expectedAddresses = append(expectedAddresses, tcpip.ProtocolAddress{
-				Protocol:          fakeNetNumber,
-				AddressWithPrefix: tcpip.AddressWithPrefix{address, fakeDefaultPrefixLen},
-			})
-		}
-	}
-
-	gotAddresses := s.AllAddresses()[nicID]
-	verifyAddresses(t, expectedAddresses, gotAddresses)
-}
-
-func TestAddProtocolAddressWithOptions(t *testing.T) {
-	const nicID = 1
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	ep := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(nicID, ep); err != nil {
-		t.Fatal("CreateNIC failed:", err)
-	}
-
-	addrLenRange := []int{4, 16}
-	prefixLenRange := []int{8, 13, 20, 32}
-	behaviorRange := []stack.PrimaryEndpointBehavior{stack.CanBePrimaryEndpoint, stack.FirstPrimaryEndpoint, stack.NeverPrimaryEndpoint}
-	expectedAddresses := make([]tcpip.ProtocolAddress, 0, len(addrLenRange)*len(prefixLenRange)*len(behaviorRange))
-	var addrGen addressGenerator
-	for _, addrLen := range addrLenRange {
-		for _, prefixLen := range prefixLenRange {
-			for _, behavior := range behaviorRange {
-				protocolAddress := tcpip.ProtocolAddress{
-					Protocol: fakeNetNumber,
-					AddressWithPrefix: tcpip.AddressWithPrefix{
-						Address:   addrGen.next(addrLen),
-						PrefixLen: prefixLen,
-					},
-				}
-				if err := s.AddProtocolAddressWithOptions(nicID, protocolAddress, behavior); err != nil {
-					t.Fatalf("AddProtocolAddressWithOptions(%+v, %d) failed: %s", protocolAddress, behavior, err)
-				}
-				expectedAddresses = append(expectedAddresses, protocolAddress)
-			}
-		}
-	}
-
-	gotAddresses := s.AllAddresses()[nicID]
-	verifyAddresses(t, expectedAddresses, gotAddresses)
-}
-
-func TestCreateNICWithOptions(t *testing.T) {
-	type callArgsAndExpect struct {
-		nicID tcpip.NICID
-		opts  stack.NICOptions
-		err   tcpip.Error
-	}
-
-	tests := []struct {
-		desc  string
-		calls []callArgsAndExpect
-	}{
-		{
-			desc: "DuplicateNICID",
-			calls: []callArgsAndExpect{
-				{
-					nicID: tcpip.NICID(1),
-					opts:  stack.NICOptions{Name: "eth1"},
-					err:   nil,
-				},
-				{
-					nicID: tcpip.NICID(1),
-					opts:  stack.NICOptions{Name: "eth2"},
-					err:   &tcpip.ErrDuplicateNICID{},
-				},
-			},
-		},
-		{
-			desc: "DuplicateName",
-			calls: []callArgsAndExpect{
-				{
-					nicID: tcpip.NICID(1),
-					opts:  stack.NICOptions{Name: "lo"},
-					err:   nil,
-				},
-				{
-					nicID: tcpip.NICID(2),
-					opts:  stack.NICOptions{Name: "lo"},
-					err:   &tcpip.ErrDuplicateNICID{},
-				},
-			},
-		},
-		{
-			desc: "Unnamed",
-			calls: []callArgsAndExpect{
-				{
-					nicID: tcpip.NICID(1),
-					opts:  stack.NICOptions{},
-					err:   nil,
-				},
-				{
-					nicID: tcpip.NICID(2),
-					opts:  stack.NICOptions{},
-					err:   nil,
-				},
-			},
-		},
-		{
-			desc: "UnnamedDuplicateNICID",
-			calls: []callArgsAndExpect{
-				{
-					nicID: tcpip.NICID(1),
-					opts:  stack.NICOptions{},
-					err:   nil,
-				},
-				{
-					nicID: tcpip.NICID(1),
-					opts:  stack.NICOptions{},
-					err:   &tcpip.ErrDuplicateNICID{},
-				},
-			},
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.desc, func(t *testing.T) {
-			s := stack.New(stack.Options{})
-			ep := channel.New(0, 0, tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"))
-			for _, call := range test.calls {
-				if got, want := s.CreateNICWithOptions(call.nicID, ep, call.opts), call.err; got != want {
-					t.Fatalf("CreateNICWithOptions(%v, _, %+v) = %v, want %v", call.nicID, call.opts, got, want)
-				}
-			}
-		})
-	}
-}
-
-func TestNICStats(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-	ep1 := channel.New(10, defaultMTU, "")
-	if err := s.CreateNIC(1, ep1); err != nil {
-		t.Fatal("CreateNIC failed: ", err)
-	}
-	if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
-		t.Fatal("AddAddress failed:", err)
-	}
-	// Route all packets for address \x01 to NIC 1.
-	{
-		subnet, err := tcpip.NewSubnet("\x01", "\xff")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	// Send a packet to address 1.
-	buf := buffer.NewView(30)
-	ep1.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if got, want := s.NICInfo()[1].Stats.Rx.Packets.Value(), uint64(1); got != want {
-		t.Errorf("got Rx.Packets.Value() = %d, want = %d", got, want)
-	}
-
-	if got, want := s.NICInfo()[1].Stats.Rx.Bytes.Value(), uint64(len(buf)); got != want {
-		t.Errorf("got Rx.Bytes.Value() = %d, want = %d", got, want)
-	}
-
-	payload := buffer.NewView(10)
-	// Write a packet out via the address for NIC 1
-	if err := sendTo(s, "\x01", payload); err != nil {
-		t.Fatal("sendTo failed: ", err)
-	}
-	want := uint64(ep1.Drain())
-	if got := s.NICInfo()[1].Stats.Tx.Packets.Value(); got != want {
-		t.Errorf("got Tx.Packets.Value() = %d, ep1.Drain() = %d", got, want)
-	}
-
-	if got, want := s.NICInfo()[1].Stats.Tx.Bytes.Value(), uint64(len(payload)+fakeNetHeaderLen); got != want {
-		t.Errorf("got Tx.Bytes.Value() = %d, want = %d", got, want)
-	}
-}
-
-// TestNICContextPreservation tests that you can read out via stack.NICInfo the
-// Context data you pass via NICContext.Context in stack.CreateNICWithOptions.
-func TestNICContextPreservation(t *testing.T) {
-	var ctx *int
-	tests := []struct {
-		name string
-		opts stack.NICOptions
-		want stack.NICContext
-	}{
-		{
-			"context_set",
-			stack.NICOptions{Context: ctx},
-			ctx,
-		},
-		{
-			"context_not_set",
-			stack.NICOptions{},
-			nil,
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{})
-			id := tcpip.NICID(1)
-			ep := channel.New(0, 0, tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"))
-			if err := s.CreateNICWithOptions(id, ep, test.opts); err != nil {
-				t.Fatalf("got stack.CreateNICWithOptions(%d, %+v, %+v) = %s, want nil", id, ep, test.opts, err)
-			}
-			nicinfos := s.NICInfo()
-			nicinfo, ok := nicinfos[id]
-			if !ok {
-				t.Fatalf("got nicinfos[%d] = _, %t, want _, true; nicinfos = %+v", id, ok, nicinfos)
-			}
-			if got, want := nicinfo.Context == test.want, true; got != want {
-				t.Fatalf("got nicinfo.Context == ctx = %t, want %t; nicinfo.Context = %p, ctx = %p", got, want, nicinfo.Context, test.want)
-			}
-		})
-	}
-}
-
-// TestNICAutoGenLinkLocalAddr tests the auto-generation of IPv6 link-local
-// addresses.
-func TestNICAutoGenLinkLocalAddr(t *testing.T) {
-	const nicID = 1
-
-	var secretKey [header.OpaqueIIDSecretKeyMinBytes]byte
-	n, err := rand.Read(secretKey[:])
-	if err != nil {
-		t.Fatalf("rand.Read(_): %s", err)
-	}
-	if n != header.OpaqueIIDSecretKeyMinBytes {
-		t.Fatalf("expected rand.Read to read %d bytes, read %d bytes", header.OpaqueIIDSecretKeyMinBytes, n)
-	}
-
-	nicNameFunc := func(_ tcpip.NICID, name string) string {
-		return name
-	}
-
-	tests := []struct {
-		name         string
-		nicName      string
-		autoGen      bool
-		linkAddr     tcpip.LinkAddress
-		iidOpts      ipv6.OpaqueInterfaceIdentifierOptions
-		shouldGen    bool
-		expectedAddr tcpip.Address
-	}{
-		{
-			name:      "Disabled",
-			nicName:   "nic1",
-			autoGen:   false,
-			linkAddr:  linkAddr1,
-			shouldGen: false,
-		},
-		{
-			name:     "Disabled without OIID options",
-			nicName:  "nic1",
-			autoGen:  false,
-			linkAddr: linkAddr1,
-			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: nicNameFunc,
-				SecretKey:     secretKey[:],
-			},
-			shouldGen: false,
-		},
-
-		// Tests for EUI64 based addresses.
-		{
-			name:         "EUI64 Enabled",
-			autoGen:      true,
-			linkAddr:     linkAddr1,
-			shouldGen:    true,
-			expectedAddr: header.LinkLocalAddr(linkAddr1),
-		},
-		{
-			name:      "EUI64 Empty MAC",
-			autoGen:   true,
-			shouldGen: false,
-		},
-		{
-			name:      "EUI64 Invalid MAC",
-			autoGen:   true,
-			linkAddr:  "\x01\x02\x03",
-			shouldGen: false,
-		},
-		{
-			name:      "EUI64 Multicast MAC",
-			autoGen:   true,
-			linkAddr:  "\x01\x02\x03\x04\x05\x06",
-			shouldGen: false,
-		},
-		{
-			name:      "EUI64 Unspecified MAC",
-			autoGen:   true,
-			linkAddr:  "\x00\x00\x00\x00\x00\x00",
-			shouldGen: false,
-		},
-
-		// Tests for Opaque IID based addresses.
-		{
-			name:     "OIID Enabled",
-			nicName:  "nic1",
-			autoGen:  true,
-			linkAddr: linkAddr1,
-			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: nicNameFunc,
-				SecretKey:     secretKey[:],
-			},
-			shouldGen:    true,
-			expectedAddr: header.LinkLocalAddrWithOpaqueIID("nic1", 0, secretKey[:]),
-		},
-		// These are all cases where we would not have generated a
-		// link-local address if opaque IIDs were disabled.
-		{
-			name:    "OIID Empty MAC and empty nicName",
-			autoGen: true,
-			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: nicNameFunc,
-				SecretKey:     secretKey[:1],
-			},
-			shouldGen:    true,
-			expectedAddr: header.LinkLocalAddrWithOpaqueIID("", 0, secretKey[:1]),
-		},
-		{
-			name:     "OIID Invalid MAC",
-			nicName:  "test",
-			autoGen:  true,
-			linkAddr: "\x01\x02\x03",
-			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: nicNameFunc,
-				SecretKey:     secretKey[:2],
-			},
-			shouldGen:    true,
-			expectedAddr: header.LinkLocalAddrWithOpaqueIID("test", 0, secretKey[:2]),
-		},
-		{
-			name:     "OIID Multicast MAC",
-			nicName:  "test2",
-			autoGen:  true,
-			linkAddr: "\x01\x02\x03\x04\x05\x06",
-			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: nicNameFunc,
-				SecretKey:     secretKey[:3],
-			},
-			shouldGen:    true,
-			expectedAddr: header.LinkLocalAddrWithOpaqueIID("test2", 0, secretKey[:3]),
-		},
-		{
-			name:     "OIID Unspecified MAC and nil SecretKey",
-			nicName:  "test3",
-			autoGen:  true,
-			linkAddr: "\x00\x00\x00\x00\x00\x00",
-			iidOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: nicNameFunc,
-			},
-			shouldGen:    true,
-			expectedAddr: header.LinkLocalAddrWithOpaqueIID("test3", 0, nil),
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ndpDisp := ndpDispatcher{
-				autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1),
-			}
-			opts := stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					AutoGenLinkLocal: test.autoGen,
-					NDPDisp:          &ndpDisp,
-					OpaqueIIDOpts:    test.iidOpts,
-				})},
-			}
-
-			e := channel.New(0, 1280, test.linkAddr)
-			s := stack.New(opts)
-			nicOpts := stack.NICOptions{Name: test.nicName, Disabled: true}
-			if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
-				t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, err)
-			}
-
-			// A new disabled NIC should not have any address, even if auto generation
-			// was enabled.
-			allStackAddrs := s.AllAddresses()
-			allNICAddrs, ok := allStackAddrs[nicID]
-			if !ok {
-				t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
-			}
-			if l := len(allNICAddrs); l != 0 {
-				t.Fatalf("got len(allNICAddrs) = %d, want = 0", l)
-			}
-
-			// Enabling the NIC should attempt auto-generation of a link-local
-			// address.
-			if err := s.EnableNIC(nicID); err != nil {
-				t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-			}
-
-			var expectedMainAddr tcpip.AddressWithPrefix
-			if test.shouldGen {
-				expectedMainAddr = tcpip.AddressWithPrefix{
-					Address:   test.expectedAddr,
-					PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen,
-				}
-
-				// Should have auto-generated an address and resolved immediately (DAD
-				// is disabled).
-				select {
-				case e := <-ndpDisp.autoGenAddrC:
-					if diff := checkAutoGenAddrEvent(e, expectedMainAddr, newAddr); diff != "" {
-						t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
-					}
-				default:
-					t.Fatal("expected addr auto gen event")
-				}
-			} else {
-				// Should not have auto-generated an address.
-				select {
-				case <-ndpDisp.autoGenAddrC:
-					t.Fatal("unexpectedly auto-generated an address")
-				default:
-				}
-			}
-
-			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, expectedMainAddr); err != nil {
-				t.Fatal(err)
-			}
-
-			// Disabling the NIC should remove the auto-generated address.
-			if err := s.DisableNIC(nicID); err != nil {
-				t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-			}
-			if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-		})
-	}
-}
-
-// TestNoLinkLocalAutoGenForLoopbackNIC tests that IPv6 link-local addresses are
-// not auto-generated for loopback NICs.
-func TestNoLinkLocalAutoGenForLoopbackNIC(t *testing.T) {
-	const nicID = 1
-	const nicName = "nicName"
-
-	tests := []struct {
-		name          string
-		opaqueIIDOpts ipv6.OpaqueInterfaceIdentifierOptions
-	}{
-		{
-			name:          "IID From MAC",
-			opaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{},
-		},
-		{
-			name: "Opaque IID",
-			opaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
-				NICNameFromID: func(_ tcpip.NICID, nicName string) string {
-					return nicName
-				},
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			opts := stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					AutoGenLinkLocal: true,
-					OpaqueIIDOpts:    test.opaqueIIDOpts,
-				})},
-			}
-
-			e := loopback.New()
-			s := stack.New(opts)
-			nicOpts := stack.NICOptions{Name: nicName}
-			if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
-				t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, nicOpts, err)
-			}
-
-			if err := checkGetMainNICAddress(s, 1, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-				t.Fatal(err)
-			}
-		})
-	}
-}
-
-// TestNICAutoGenAddrDoesDAD tests that the successful auto-generation of IPv6
-// link-local addresses will only be assigned after the DAD process resolves.
-func TestNICAutoGenAddrDoesDAD(t *testing.T) {
-	const nicID = 1
-
-	ndpDisp := ndpDispatcher{
-		dadC: make(chan ndpDADEvent),
-	}
-	dadConfigs := stack.DefaultDADConfigurations()
-	opts := stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			AutoGenLinkLocal: true,
-			NDPDisp:          &ndpDisp,
-			DADConfigs:       dadConfigs,
-		})},
-	}
-
-	e := channel.New(int(dadConfigs.DupAddrDetectTransmits), 1280, linkAddr1)
-	s := stack.New(opts)
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	// Address should not be considered bound to the
-	// NIC yet (DAD ongoing).
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-		t.Fatal(err)
-	}
-
-	linkLocalAddr := header.LinkLocalAddr(linkAddr1)
-
-	// Wait for DAD to resolve.
-	select {
-	case <-time.After(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer + time.Second):
-		// We should get a resolution event after 1s (default time to
-		// resolve as per default NDP configurations). Waiting for that
-		// resolution time + an extra 1s without a resolution event
-		// means something is wrong.
-		t.Fatal("timed out waiting for DAD resolution")
-	case e := <-ndpDisp.dadC:
-		if diff := checkDADEvent(e, nicID, linkLocalAddr, &stack.DADSucceeded{}); diff != "" {
-			t.Errorf("dad event mismatch (-want +got):\n%s", diff)
-		}
-	}
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{Address: linkLocalAddr, PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen}); err != nil {
-		t.Fatal(err)
-	}
-}
-
-// TestNewPEB tests that a new PrimaryEndpointBehavior value (peb) is respected
-// when an address's kind gets "promoted" to permanent from permanentExpired.
-func TestNewPEBOnPromotionToPermanent(t *testing.T) {
-	const nicID = 1
-
-	pebs := []stack.PrimaryEndpointBehavior{
-		stack.NeverPrimaryEndpoint,
-		stack.CanBePrimaryEndpoint,
-		stack.FirstPrimaryEndpoint,
-	}
-
-	for _, pi := range pebs {
-		for _, ps := range pebs {
-			t.Run(fmt.Sprintf("%d-to-%d", pi, ps), func(t *testing.T) {
-				s := stack.New(stack.Options{
-					NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-				})
-				ep1 := channel.New(10, defaultMTU, "")
-				if err := s.CreateNIC(nicID, ep1); err != nil {
-					t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-				}
-
-				// Add a permanent address with initial
-				// PrimaryEndpointBehavior (peb), pi. If pi is
-				// NeverPrimaryEndpoint, the address should not
-				// be returned by a call to GetMainNICAddress;
-				// else, it should.
-				const address1 = tcpip.Address("\x01")
-				if err := s.AddAddressWithOptions(nicID, fakeNetNumber, address1, pi); err != nil {
-					t.Fatalf("AddAddressWithOptions(%d, %d, %s, %d): %s", nicID, fakeNetNumber, address1, pi, err)
-				}
-				addr, err := s.GetMainNICAddress(nicID, fakeNetNumber)
-				if err != nil {
-					t.Fatalf("GetMainNICAddress(%d, %d): %s", nicID, fakeNetNumber, err)
-				}
-				if pi == stack.NeverPrimaryEndpoint {
-					if want := (tcpip.AddressWithPrefix{}); addr != want {
-						t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, addr, want)
-
-					}
-				} else if addr.Address != address1 {
-					t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, addr.Address, address1)
-				}
-
-				{
-					subnet, err := tcpip.NewSubnet("\x00", "\x00")
-					if err != nil {
-						t.Fatalf("NewSubnet failed: %v", err)
-					}
-					s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-				}
-
-				// Take a route through the address so its ref
-				// count gets incremented and does not actually
-				// get deleted when RemoveAddress is called
-				// below. This is because we want to test that a
-				// new peb is respected when an address gets
-				// "promoted" to permanent from a
-				// permanentExpired kind.
-				const address2 = tcpip.Address("\x02")
-				r, err := s.FindRoute(nicID, address1, address2, fakeNetNumber, false)
-				if err != nil {
-					t.Fatalf("FindRoute(%d, %s, %s, %d, false): %s", nicID, address1, address2, fakeNetNumber, err)
-				}
-				defer r.Release()
-				if err := s.RemoveAddress(nicID, address1); err != nil {
-					t.Fatalf("RemoveAddress(%d, %s): %s", nicID, address1, err)
-				}
-
-				//
-				// At this point, the address should still be
-				// known by the NIC, but have its
-				// kind = permanentExpired.
-				//
-
-				// Add some other address with peb set to
-				// FirstPrimaryEndpoint.
-				const address3 = tcpip.Address("\x03")
-				if err := s.AddAddressWithOptions(nicID, fakeNetNumber, address3, stack.FirstPrimaryEndpoint); err != nil {
-					t.Fatalf("AddAddressWithOptions(%d, %d, %s, %d): %s", nicID, fakeNetNumber, address3, stack.FirstPrimaryEndpoint, err)
-
-				}
-
-				// Add back the address we removed earlier and
-				// make sure the new peb was respected.
-				// (The address should just be promoted now).
-				if err := s.AddAddressWithOptions(nicID, fakeNetNumber, address1, ps); err != nil {
-					t.Fatalf("AddAddressWithOptions(%d, %d, %s, %d): %s", nicID, fakeNetNumber, address1, pi, err)
-				}
-				var primaryAddrs []tcpip.Address
-				for _, pa := range s.NICInfo()[nicID].ProtocolAddresses {
-					primaryAddrs = append(primaryAddrs, pa.AddressWithPrefix.Address)
-				}
-				var expectedList []tcpip.Address
-				switch ps {
-				case stack.FirstPrimaryEndpoint:
-					expectedList = []tcpip.Address{
-						"\x01",
-						"\x03",
-					}
-				case stack.CanBePrimaryEndpoint:
-					expectedList = []tcpip.Address{
-						"\x03",
-						"\x01",
-					}
-				case stack.NeverPrimaryEndpoint:
-					expectedList = []tcpip.Address{
-						"\x03",
-					}
-				}
-				if !cmp.Equal(primaryAddrs, expectedList) {
-					t.Fatalf("got NIC's primary addresses = %v, want = %v", primaryAddrs, expectedList)
-				}
-
-				// Once we remove the other address, if the new
-				// peb, ps, was NeverPrimaryEndpoint, no address
-				// should be returned by a call to
-				// GetMainNICAddress; else, our original address
-				// should be returned.
-				if err := s.RemoveAddress(nicID, address3); err != nil {
-					t.Fatalf("RemoveAddress(%d, %s): %s", nicID, address3, err)
-				}
-				addr, err = s.GetMainNICAddress(nicID, fakeNetNumber)
-				if err != nil {
-					t.Fatalf("GetMainNICAddress(%d, %d): %s", nicID, fakeNetNumber, err)
-				}
-				if ps == stack.NeverPrimaryEndpoint {
-					if want := (tcpip.AddressWithPrefix{}); addr != want {
-						t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, addr, want)
-					}
-				} else {
-					if addr.Address != address1 {
-						t.Fatalf("got GetMainNICAddress(%d, %d) = %s, want = %s", nicID, fakeNetNumber, addr.Address, address1)
-					}
-				}
-			})
-		}
-	}
-}
-
-func TestIPv6SourceAddressSelectionScopeAndSameAddress(t *testing.T) {
-	const (
-		linkLocalAddr1         = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-		linkLocalAddr2         = tcpip.Address("\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-		linkLocalMulticastAddr = tcpip.Address("\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-		uniqueLocalAddr1       = tcpip.Address("\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-		uniqueLocalAddr2       = tcpip.Address("\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-		globalAddr1            = tcpip.Address("\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-		globalAddr2            = tcpip.Address("\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-		globalAddr3            = tcpip.Address("\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03")
-		ipv4MappedIPv6Addr1    = tcpip.Address("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x01")
-		ipv4MappedIPv6Addr2    = tcpip.Address("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x02")
-		toredoAddr1            = tcpip.Address("\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-		toredoAddr2            = tcpip.Address("\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-		ipv6ToIPv4Addr1        = tcpip.Address("\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
-		ipv6ToIPv4Addr2        = tcpip.Address("\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
-
-		nicID           = 1
-		lifetimeSeconds = 9999
-	)
-
-	prefix1, _, stableGlobalAddr1 := prefixSubnetAddr(0, linkAddr1)
-	prefix2, _, stableGlobalAddr2 := prefixSubnetAddr(1, linkAddr1)
-
-	var tempIIDHistory [header.IIDSize]byte
-	header.InitialTempIID(tempIIDHistory[:], nil, nicID)
-	tempGlobalAddr1 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], stableGlobalAddr1.Address).Address
-	tempGlobalAddr2 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], stableGlobalAddr2.Address).Address
-
-	// Rule 3 is not tested here, and is instead tested by NDP's AutoGenAddr test.
-	tests := []struct {
-		name                                   string
-		slaacPrefixForTempAddrBeforeNICAddrAdd tcpip.AddressWithPrefix
-		nicAddrs                               []tcpip.Address
-		slaacPrefixForTempAddrAfterNICAddrAdd  tcpip.AddressWithPrefix
-		remoteAddr                             tcpip.Address
-		expectedLocalAddr                      tcpip.Address
-	}{
-		// Test Rule 1 of RFC 6724 section 5 (prefer same address).
-		{
-			name:              "Same Global most preferred (last address)",
-			nicAddrs:          []tcpip.Address{linkLocalAddr1, globalAddr1},
-			remoteAddr:        globalAddr1,
-			expectedLocalAddr: globalAddr1,
-		},
-		{
-			name:              "Same Global most preferred (first address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, uniqueLocalAddr1},
-			remoteAddr:        globalAddr1,
-			expectedLocalAddr: globalAddr1,
-		},
-		{
-			name:              "Same Link Local most preferred (last address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, linkLocalAddr1},
-			remoteAddr:        linkLocalAddr1,
-			expectedLocalAddr: linkLocalAddr1,
-		},
-		{
-			name:              "Same Link Local most preferred (first address)",
-			nicAddrs:          []tcpip.Address{linkLocalAddr1, globalAddr1},
-			remoteAddr:        linkLocalAddr1,
-			expectedLocalAddr: linkLocalAddr1,
-		},
-		{
-			name:              "Same Unique Local most preferred (last address)",
-			nicAddrs:          []tcpip.Address{uniqueLocalAddr1, globalAddr1},
-			remoteAddr:        uniqueLocalAddr1,
-			expectedLocalAddr: uniqueLocalAddr1,
-		},
-		{
-			name:              "Same Unique Local most preferred (first address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, uniqueLocalAddr1},
-			remoteAddr:        uniqueLocalAddr1,
-			expectedLocalAddr: uniqueLocalAddr1,
-		},
-
-		// Test Rule 2 of RFC 6724 section 5 (prefer appropriate scope).
-		{
-			name:              "Global most preferred (last address)",
-			nicAddrs:          []tcpip.Address{linkLocalAddr1, globalAddr1},
-			remoteAddr:        globalAddr2,
-			expectedLocalAddr: globalAddr1,
-		},
-		{
-			name:              "Global most preferred (first address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, linkLocalAddr1},
-			remoteAddr:        globalAddr2,
-			expectedLocalAddr: globalAddr1,
-		},
-		{
-			name:              "Link Local most preferred (last address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, linkLocalAddr1},
-			remoteAddr:        linkLocalAddr2,
-			expectedLocalAddr: linkLocalAddr1,
-		},
-		{
-			name:              "Link Local most preferred (first address)",
-			nicAddrs:          []tcpip.Address{linkLocalAddr1, globalAddr1},
-			remoteAddr:        linkLocalAddr2,
-			expectedLocalAddr: linkLocalAddr1,
-		},
-		{
-			name:              "Link Local most preferred for link local multicast (last address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, linkLocalAddr1},
-			remoteAddr:        linkLocalMulticastAddr,
-			expectedLocalAddr: linkLocalAddr1,
-		},
-		{
-			name:              "Link Local most preferred for link local multicast (first address)",
-			nicAddrs:          []tcpip.Address{linkLocalAddr1, globalAddr1},
-			remoteAddr:        linkLocalMulticastAddr,
-			expectedLocalAddr: linkLocalAddr1,
-		},
-
-		// Test Rule 6 of 6724 section 5 (prefer matching label).
-		{
-			name:              "Unique Local most preferred (last address)",
-			nicAddrs:          []tcpip.Address{uniqueLocalAddr1, globalAddr1, ipv4MappedIPv6Addr1, toredoAddr1, ipv6ToIPv4Addr1},
-			remoteAddr:        uniqueLocalAddr2,
-			expectedLocalAddr: uniqueLocalAddr1,
-		},
-		{
-			name:              "Unique Local most preferred (first address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, ipv4MappedIPv6Addr1, toredoAddr1, ipv6ToIPv4Addr1, uniqueLocalAddr1},
-			remoteAddr:        uniqueLocalAddr2,
-			expectedLocalAddr: uniqueLocalAddr1,
-		},
-		{
-			name:              "Toredo most preferred (first address)",
-			nicAddrs:          []tcpip.Address{toredoAddr1, uniqueLocalAddr1, globalAddr1, ipv4MappedIPv6Addr1, ipv6ToIPv4Addr1},
-			remoteAddr:        toredoAddr2,
-			expectedLocalAddr: toredoAddr1,
-		},
-		{
-			name:              "Toredo most preferred (last address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, ipv4MappedIPv6Addr1, ipv6ToIPv4Addr1, uniqueLocalAddr1, toredoAddr1},
-			remoteAddr:        toredoAddr2,
-			expectedLocalAddr: toredoAddr1,
-		},
-		{
-			name:              "6To4 most preferred (first address)",
-			nicAddrs:          []tcpip.Address{ipv6ToIPv4Addr1, toredoAddr1, uniqueLocalAddr1, globalAddr1, ipv4MappedIPv6Addr1},
-			remoteAddr:        ipv6ToIPv4Addr2,
-			expectedLocalAddr: ipv6ToIPv4Addr1,
-		},
-		{
-			name:              "6To4 most preferred (last address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, ipv4MappedIPv6Addr1, uniqueLocalAddr1, toredoAddr1, ipv6ToIPv4Addr1},
-			remoteAddr:        ipv6ToIPv4Addr2,
-			expectedLocalAddr: ipv6ToIPv4Addr1,
-		},
-		{
-			name:              "IPv4 mapped IPv6 most preferred (first address)",
-			nicAddrs:          []tcpip.Address{ipv4MappedIPv6Addr1, ipv6ToIPv4Addr1, toredoAddr1, uniqueLocalAddr1, globalAddr1},
-			remoteAddr:        ipv4MappedIPv6Addr2,
-			expectedLocalAddr: ipv4MappedIPv6Addr1,
-		},
-		{
-			name:              "IPv4 mapped IPv6 most preferred (last address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, ipv6ToIPv4Addr1, uniqueLocalAddr1, toredoAddr1, ipv4MappedIPv6Addr1},
-			remoteAddr:        ipv4MappedIPv6Addr2,
-			expectedLocalAddr: ipv4MappedIPv6Addr1,
-		},
-
-		// Test Rule 7 of RFC 6724 section 5 (prefer temporary addresses).
-		{
-			name:                                   "Temp Global most preferred (last address)",
-			slaacPrefixForTempAddrBeforeNICAddrAdd: prefix1,
-			nicAddrs:                               []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, globalAddr1},
-			remoteAddr:                             globalAddr2,
-			expectedLocalAddr:                      tempGlobalAddr1,
-		},
-		{
-			name:                                  "Temp Global most preferred (first address)",
-			nicAddrs:                              []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, globalAddr1},
-			slaacPrefixForTempAddrAfterNICAddrAdd: prefix1,
-			remoteAddr:                            globalAddr2,
-			expectedLocalAddr:                     tempGlobalAddr1,
-		},
-
-		// Test Rule 8 of RFC 6724 section 5 (use longest matching prefix).
-		{
-			name:              "Longest prefix matched most preferred (first address)",
-			nicAddrs:          []tcpip.Address{globalAddr2, globalAddr1},
-			remoteAddr:        globalAddr3,
-			expectedLocalAddr: globalAddr2,
-		},
-		{
-			name:              "Longest prefix matched most preferred (last address)",
-			nicAddrs:          []tcpip.Address{globalAddr1, globalAddr2},
-			remoteAddr:        globalAddr3,
-			expectedLocalAddr: globalAddr2,
-		},
-
-		// Test returning the endpoint that is closest to the front when
-		// candidate addresses are "equal" from the perspective of RFC 6724
-		// section 5.
-		{
-			name:              "Unique Local for Global",
-			nicAddrs:          []tcpip.Address{linkLocalAddr1, uniqueLocalAddr1, uniqueLocalAddr2},
-			remoteAddr:        globalAddr2,
-			expectedLocalAddr: uniqueLocalAddr1,
-		},
-		{
-			name:              "Link Local for Global",
-			nicAddrs:          []tcpip.Address{linkLocalAddr1, linkLocalAddr2},
-			remoteAddr:        globalAddr2,
-			expectedLocalAddr: linkLocalAddr1,
-		},
-		{
-			name:              "Link Local for Unique Local",
-			nicAddrs:          []tcpip.Address{linkLocalAddr1, linkLocalAddr2},
-			remoteAddr:        uniqueLocalAddr2,
-			expectedLocalAddr: linkLocalAddr1,
-		},
-		{
-			name:                                   "Temp Global for Global",
-			slaacPrefixForTempAddrBeforeNICAddrAdd: prefix1,
-			slaacPrefixForTempAddrAfterNICAddrAdd:  prefix2,
-			remoteAddr:                             globalAddr1,
-			expectedLocalAddr:                      tempGlobalAddr2,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			e := channel.New(0, 1280, linkAddr1)
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-					NDPConfigs: ipv6.NDPConfigurations{
-						HandleRAs:                  true,
-						AutoGenGlobalAddresses:     true,
-						AutoGenTempGlobalAddresses: true,
-					},
-					NDPDisp: &ndpDispatcher{},
-				})},
-				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
-			})
-			if err := s.CreateNIC(nicID, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-			}
-
-			if test.slaacPrefixForTempAddrBeforeNICAddrAdd != (tcpip.AddressWithPrefix{}) {
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, test.slaacPrefixForTempAddrBeforeNICAddrAdd, true, true, lifetimeSeconds, lifetimeSeconds))
-			}
-
-			for _, a := range test.nicAddrs {
-				if err := s.AddAddress(nicID, ipv6.ProtocolNumber, a); err != nil {
-					t.Errorf("s.AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, a, err)
-				}
-			}
-
-			if test.slaacPrefixForTempAddrAfterNICAddrAdd != (tcpip.AddressWithPrefix{}) {
-				e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, test.slaacPrefixForTempAddrAfterNICAddrAdd, true, true, lifetimeSeconds, lifetimeSeconds))
-			}
-
-			if t.Failed() {
-				t.FailNow()
-			}
-
-			netEP, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber)
-			if err != nil {
-				t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
-			}
-
-			addressableEndpoint, ok := netEP.(stack.AddressableEndpoint)
-			if !ok {
-				t.Fatal("network endpoint is not addressable")
-			}
-
-			addressEP := addressableEndpoint.AcquireOutgoingPrimaryAddress(test.remoteAddr, false /* allowExpired */)
-			if addressEP == nil {
-				t.Fatal("expected a non-nil address endpoint")
-			}
-			defer addressEP.DecRef()
-
-			if got := addressEP.AddressWithPrefix().Address; got != test.expectedLocalAddr {
-				t.Errorf("got local address = %s, want = %s", got, test.expectedLocalAddr)
-			}
-		})
-	}
-}
-
-func TestAddRemoveIPv4BroadcastAddressOnNICEnableDisable(t *testing.T) {
-	const nicID = 1
-	broadcastAddr := tcpip.ProtocolAddress{
-		Protocol: header.IPv4ProtocolNumber,
-		AddressWithPrefix: tcpip.AddressWithPrefix{
-			Address:   header.IPv4Broadcast,
-			PrefixLen: 32,
-		},
-	}
-
-	e := loopback.New()
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
-	})
-	nicOpts := stack.NICOptions{Disabled: true}
-	if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
-		t.Fatalf("CreateNIC(%d, _, %+v) = %s", nicID, nicOpts, err)
-	}
-
-	{
-		allStackAddrs := s.AllAddresses()
-		if allNICAddrs, ok := allStackAddrs[nicID]; !ok {
-			t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
-		} else if containsAddr(allNICAddrs, broadcastAddr) {
-			t.Fatalf("got allNICAddrs = %+v, don't want = %+v", allNICAddrs, broadcastAddr)
-		}
-	}
-
-	// Enabling the NIC should add the IPv4 broadcast address.
-	if err := s.EnableNIC(nicID); err != nil {
-		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-	}
-
-	{
-		allStackAddrs := s.AllAddresses()
-		if allNICAddrs, ok := allStackAddrs[nicID]; !ok {
-			t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
-		} else if !containsAddr(allNICAddrs, broadcastAddr) {
-			t.Fatalf("got allNICAddrs = %+v, want = %+v", allNICAddrs, broadcastAddr)
-		}
-	}
-
-	// Disabling the NIC should remove the IPv4 broadcast address.
-	if err := s.DisableNIC(nicID); err != nil {
-		t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-	}
-
-	{
-		allStackAddrs := s.AllAddresses()
-		if allNICAddrs, ok := allStackAddrs[nicID]; !ok {
-			t.Fatalf("entry for %d missing from allStackAddrs = %+v", nicID, allStackAddrs)
-		} else if containsAddr(allNICAddrs, broadcastAddr) {
-			t.Fatalf("got allNICAddrs = %+v, don't want = %+v", allNICAddrs, broadcastAddr)
-		}
-	}
-}
-
-// TestLeaveIPv6SolicitedNodeAddrBeforeAddrRemoval tests that removing an IPv6
-// address after leaving its solicited node multicast address does not result in
-// an error.
-func TestLeaveIPv6SolicitedNodeAddrBeforeAddrRemoval(t *testing.T) {
-	const nicID = 1
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocol},
-	})
-	e := channel.New(10, 1280, linkAddr1)
-	if err := s.CreateNIC(1, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-	}
-
-	if err := s.AddAddress(nicID, ipv6.ProtocolNumber, addr1); err != nil {
-		t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, addr1, err)
-	}
-
-	// The NIC should have joined addr1's solicited node multicast address.
-	snmc := header.SolicitedNodeAddr(addr1)
-	in, err := s.IsInGroup(nicID, snmc)
-	if err != nil {
-		t.Fatalf("IsInGroup(%d, %s): %s", nicID, snmc, err)
-	}
-	if !in {
-		t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, snmc)
-	}
-
-	if err := s.LeaveGroup(ipv6.ProtocolNumber, nicID, snmc); err != nil {
-		t.Fatalf("LeaveGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, snmc, err)
-	}
-	in, err = s.IsInGroup(nicID, snmc)
-	if err != nil {
-		t.Fatalf("IsInGroup(%d, %s): %s", nicID, snmc, err)
-	}
-	if in {
-		t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, snmc)
-	}
-
-	if err := s.RemoveAddress(nicID, addr1); err != nil {
-		t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr1, err)
-	}
-}
-
-func TestJoinLeaveMulticastOnNICEnableDisable(t *testing.T) {
-	const nicID = 1
-
-	tests := []struct {
-		name  string
-		proto tcpip.NetworkProtocolNumber
-		addr  tcpip.Address
-	}{
-		{
-			name:  "IPv6 All-Nodes",
-			proto: header.IPv6ProtocolNumber,
-			addr:  header.IPv6AllNodesMulticastAddress,
-		},
-		{
-			name:  "IPv4 All-Systems",
-			proto: header.IPv4ProtocolNumber,
-			addr:  header.IPv4AllSystems,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			e := loopback.New()
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
-			})
-			nicOpts := stack.NICOptions{Disabled: true}
-			if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
-				t.Fatalf("CreateNIC(%d, _, %+v) = %s", nicID, nicOpts, err)
-			}
-
-			// Should not be in the multicast group yet because the NIC has not been
-			// enabled yet.
-			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
-				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
-			} else if isInGroup {
-				t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, test.addr)
-			}
-
-			// The all-nodes multicast group should be joined when the NIC is enabled.
-			if err := s.EnableNIC(nicID); err != nil {
-				t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-			}
-
-			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
-				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
-			} else if !isInGroup {
-				t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, test.addr)
-			}
-
-			// The multicast group should be left when the NIC is disabled.
-			if err := s.DisableNIC(nicID); err != nil {
-				t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-			}
-
-			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
-				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
-			} else if isInGroup {
-				t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, test.addr)
-			}
-
-			// The all-nodes multicast group should be joined when the NIC is enabled.
-			if err := s.EnableNIC(nicID); err != nil {
-				t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-			}
-
-			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
-				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
-			} else if !isInGroup {
-				t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, test.addr)
-			}
-
-			// Leaving the group before disabling the NIC should not cause an error.
-			if err := s.LeaveGroup(test.proto, nicID, test.addr); err != nil {
-				t.Fatalf("s.LeaveGroup(%d, %d, %s): %s", test.proto, nicID, test.addr, err)
-			}
-
-			if err := s.DisableNIC(nicID); err != nil {
-				t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-			}
-
-			if isInGroup, err := s.IsInGroup(nicID, test.addr); err != nil {
-				t.Fatalf("IsInGroup(%d, %s): %s", nicID, test.addr, err)
-			} else if isInGroup {
-				t.Fatalf("got IsInGroup(%d, %s) = true, want = false", nicID, test.addr)
-			}
-		})
-	}
-}
-
-// TestDoDADWhenNICEnabled tests that IPv6 endpoints that were added while a NIC
-// was disabled have DAD performed on them when the NIC is enabled.
-func TestDoDADWhenNICEnabled(t *testing.T) {
-	const dadTransmits = 1
-	const retransmitTimer = time.Second
-	const nicID = 1
-
-	ndpDisp := ndpDispatcher{
-		dadC: make(chan ndpDADEvent),
-	}
-	opts := stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
-			DADConfigs: stack.DADConfigurations{
-				DupAddrDetectTransmits: dadTransmits,
-				RetransmitTimer:        retransmitTimer,
-			},
-			NDPDisp: &ndpDisp,
-		})},
-	}
-
-	e := channel.New(dadTransmits, 1280, linkAddr1)
-	s := stack.New(opts)
-	nicOpts := stack.NICOptions{Disabled: true}
-	if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
-		t.Fatalf("CreateNIC(%d, _, %+v) = %s", nicID, nicOpts, err)
-	}
-
-	addr := tcpip.ProtocolAddress{
-		Protocol: header.IPv6ProtocolNumber,
-		AddressWithPrefix: tcpip.AddressWithPrefix{
-			Address:   llAddr1,
-			PrefixLen: 128,
-		},
-	}
-	if err := s.AddProtocolAddress(nicID, addr); err != nil {
-		t.Fatalf("AddProtocolAddress(%d, %+v): %s", nicID, addr, err)
-	}
-
-	// Address should be in the list of all addresses.
-	if addrs := s.AllAddresses()[nicID]; !containsV6Addr(addrs, addr.AddressWithPrefix) {
-		t.Fatalf("got s.AllAddresses()[%d] = %+v, want = %+v", nicID, addrs, addr)
-	}
-
-	// Address should be tentative so it should not be a main address.
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-		t.Fatal(err)
-	}
-
-	// Enabling the NIC should start DAD for the address.
-	if err := s.EnableNIC(nicID); err != nil {
-		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-	}
-	if addrs := s.AllAddresses()[nicID]; !containsV6Addr(addrs, addr.AddressWithPrefix) {
-		t.Fatalf("got s.AllAddresses()[%d] = %+v, want = %+v", nicID, addrs, addr)
-	}
-
-	// Address should not be considered bound to the NIC yet (DAD ongoing).
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
-		t.Fatal(err)
-	}
-
-	// Wait for DAD to resolve.
-	select {
-	case <-time.After(dadTransmits*retransmitTimer + defaultAsyncPositiveEventTimeout):
-		t.Fatal("timed out waiting for DAD resolution")
-	case e := <-ndpDisp.dadC:
-		if diff := checkDADEvent(e, nicID, addr.AddressWithPrefix.Address, &stack.DADSucceeded{}); diff != "" {
-			t.Errorf("dad event mismatch (-want +got):\n%s", diff)
-		}
-	}
-	if addrs := s.AllAddresses()[nicID]; !containsV6Addr(addrs, addr.AddressWithPrefix) {
-		t.Fatalf("got s.AllAddresses()[%d] = %+v, want = %+v", nicID, addrs, addr)
-	}
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr.AddressWithPrefix); err != nil {
-		t.Fatal(err)
-	}
-
-	// Enabling the NIC again should be a no-op.
-	if err := s.EnableNIC(nicID); err != nil {
-		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-	}
-	if addrs := s.AllAddresses()[nicID]; !containsV6Addr(addrs, addr.AddressWithPrefix) {
-		t.Fatalf("got s.AllAddresses()[%d] = %+v, want = %+v", nicID, addrs, addr)
-	}
-	if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr.AddressWithPrefix); err != nil {
-		t.Fatal(err)
-	}
-}
-
-func TestStackReceiveBufferSizeOption(t *testing.T) {
-	const sMin = stack.MinBufferSize
-	testCases := []struct {
-		name string
-		rs   stack.ReceiveBufferSizeOption
-		err  tcpip.Error
-	}{
-		// Invalid configurations.
-		{"min_below_zero", stack.ReceiveBufferSizeOption{Min: -1, Default: sMin, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
-		{"min_zero", stack.ReceiveBufferSizeOption{Min: 0, Default: sMin, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
-		{"default_below_min", stack.ReceiveBufferSizeOption{Min: sMin, Default: sMin - 1, Max: sMin - 1}, &tcpip.ErrInvalidOptionValue{}},
-		{"default_above_max", stack.ReceiveBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
-		{"max_below_min", stack.ReceiveBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin - 1}, &tcpip.ErrInvalidOptionValue{}},
-
-		// Valid Configurations
-		{"in_ascending_order", stack.ReceiveBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin + 2}, nil},
-		{"all_equal", stack.ReceiveBufferSizeOption{Min: sMin, Default: sMin, Max: sMin}, nil},
-		{"min_default_equal", stack.ReceiveBufferSizeOption{Min: sMin, Default: sMin, Max: sMin + 1}, nil},
-		{"default_max_equal", stack.ReceiveBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin + 1}, nil},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			s := stack.New(stack.Options{})
-			defer s.Close()
-			if err := s.SetOption(tc.rs); err != tc.err {
-				t.Fatalf("s.SetOption(%#v) = %v, want: %v", tc.rs, err, tc.err)
-			}
-			var rs stack.ReceiveBufferSizeOption
-			if tc.err == nil {
-				if err := s.Option(&rs); err != nil {
-					t.Fatalf("s.Option(%#v) = %v, want: nil", rs, err)
-				}
-				if got, want := rs, tc.rs; got != want {
-					t.Fatalf("s.Option(..) returned unexpected value got: %#v, want: %#v", got, want)
-				}
-			}
-		})
-	}
-}
-
-func TestStackSendBufferSizeOption(t *testing.T) {
-	const sMin = stack.MinBufferSize
-	testCases := []struct {
-		name string
-		ss   tcpip.SendBufferSizeOption
-		err  tcpip.Error
-	}{
-		// Invalid configurations.
-		{"min_below_zero", tcpip.SendBufferSizeOption{Min: -1, Default: sMin, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
-		{"min_zero", tcpip.SendBufferSizeOption{Min: 0, Default: sMin, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
-		{"default_below_min", tcpip.SendBufferSizeOption{Min: 0, Default: sMin - 1, Max: sMin - 1}, &tcpip.ErrInvalidOptionValue{}},
-		{"default_above_max", tcpip.SendBufferSizeOption{Min: 0, Default: sMin + 1, Max: sMin}, &tcpip.ErrInvalidOptionValue{}},
-		{"max_below_min", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin - 1}, &tcpip.ErrInvalidOptionValue{}},
-
-		// Valid Configurations
-		{"in_ascending_order", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin + 2}, nil},
-		{"all_equal", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin, Max: sMin}, nil},
-		{"min_default_equal", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin, Max: sMin + 1}, nil},
-		{"default_max_equal", tcpip.SendBufferSizeOption{Min: sMin, Default: sMin + 1, Max: sMin + 1}, nil},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			s := stack.New(stack.Options{})
-			defer s.Close()
-			err := s.SetOption(tc.ss)
-			if diff := cmp.Diff(tc.err, err); diff != "" {
-				t.Fatalf("unexpected error from s.SetOption(%+v), (-want, +got):\n%s", tc.ss, diff)
-			}
-			if tc.err == nil {
-				var ss tcpip.SendBufferSizeOption
-				if err := s.Option(&ss); err != nil {
-					t.Fatalf("s.Option(%+v) = %v, want: nil", ss, err)
-				}
-				if got, want := ss, tc.ss; got != want {
-					t.Fatalf("s.Option(..) returned unexpected value got: %#v, want: %#v", got, want)
-				}
-			}
-		})
-	}
-}
-
-func TestOutgoingSubnetBroadcast(t *testing.T) {
-	const (
-		unspecifiedNICID = 0
-		nicID1           = 1
-	)
-
-	defaultAddr := tcpip.AddressWithPrefix{
-		Address:   header.IPv4Any,
-		PrefixLen: 0,
-	}
-	defaultSubnet := defaultAddr.Subnet()
-	ipv4Addr := tcpip.AddressWithPrefix{
-		Address:   "\xc0\xa8\x01\x3a",
-		PrefixLen: 24,
-	}
-	ipv4Subnet := ipv4Addr.Subnet()
-	ipv4SubnetBcast := ipv4Subnet.Broadcast()
-	ipv4Gateway := tcpip.Address("\xc0\xa8\x01\x01")
-	ipv4AddrPrefix31 := tcpip.AddressWithPrefix{
-		Address:   "\xc0\xa8\x01\x3a",
-		PrefixLen: 31,
-	}
-	ipv4Subnet31 := ipv4AddrPrefix31.Subnet()
-	ipv4Subnet31Bcast := ipv4Subnet31.Broadcast()
-	ipv4AddrPrefix32 := tcpip.AddressWithPrefix{
-		Address:   "\xc0\xa8\x01\x3a",
-		PrefixLen: 32,
-	}
-	ipv4Subnet32 := ipv4AddrPrefix32.Subnet()
-	ipv4Subnet32Bcast := ipv4Subnet32.Broadcast()
-	ipv6Addr := tcpip.AddressWithPrefix{
-		Address:   "\x20\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
-		PrefixLen: 64,
-	}
-	ipv6Subnet := ipv6Addr.Subnet()
-	ipv6SubnetBcast := ipv6Subnet.Broadcast()
-	remNetAddr := tcpip.AddressWithPrefix{
-		Address:   "\x64\x0a\x7b\x18",
-		PrefixLen: 24,
-	}
-	remNetSubnet := remNetAddr.Subnet()
-	remNetSubnetBcast := remNetSubnet.Broadcast()
-
-	tests := []struct {
-		name                      string
-		nicAddr                   tcpip.ProtocolAddress
-		routes                    []tcpip.Route
-		remoteAddr                tcpip.Address
-		expectedLocalAddress      tcpip.Address
-		expectedRemoteAddress     tcpip.Address
-		expectedRemoteLinkAddress tcpip.LinkAddress
-		expectedNextHop           tcpip.Address
-		expectedNetProto          tcpip.NetworkProtocolNumber
-		expectedLoop              stack.PacketLooping
-	}{
-		// Broadcast to a locally attached subnet populates the broadcast MAC.
-		{
-			name: "IPv4 Broadcast to local subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4Addr,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: ipv4Subnet,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:                ipv4SubnetBcast,
-			expectedLocalAddress:      ipv4Addr.Address,
-			expectedRemoteAddress:     ipv4SubnetBcast,
-			expectedRemoteLinkAddress: header.EthernetBroadcastAddress,
-			expectedNetProto:          header.IPv4ProtocolNumber,
-			expectedLoop:              stack.PacketOut | stack.PacketLoop,
-		},
-		// Broadcast to a locally attached /31 subnet does not populate the
-		// broadcast MAC.
-		{
-			name: "IPv4 Broadcast to local /31 subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4AddrPrefix31,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: ipv4Subnet31,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:            ipv4Subnet31Bcast,
-			expectedLocalAddress:  ipv4AddrPrefix31.Address,
-			expectedRemoteAddress: ipv4Subnet31Bcast,
-			expectedNetProto:      header.IPv4ProtocolNumber,
-			expectedLoop:          stack.PacketOut,
-		},
-		// Broadcast to a locally attached /32 subnet does not populate the
-		// broadcast MAC.
-		{
-			name: "IPv4 Broadcast to local /32 subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4AddrPrefix32,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: ipv4Subnet32,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:            ipv4Subnet32Bcast,
-			expectedLocalAddress:  ipv4AddrPrefix32.Address,
-			expectedRemoteAddress: ipv4Subnet32Bcast,
-			expectedNetProto:      header.IPv4ProtocolNumber,
-			expectedLoop:          stack.PacketOut,
-		},
-		// IPv6 has no notion of a broadcast.
-		{
-			name: "IPv6 'Broadcast' to local subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv6ProtocolNumber,
-				AddressWithPrefix: ipv6Addr,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: ipv6Subnet,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:            ipv6SubnetBcast,
-			expectedLocalAddress:  ipv6Addr.Address,
-			expectedRemoteAddress: ipv6SubnetBcast,
-			expectedNetProto:      header.IPv6ProtocolNumber,
-			expectedLoop:          stack.PacketOut,
-		},
-		// Broadcast to a remote subnet in the route table is send to the next-hop
-		// gateway.
-		{
-			name: "IPv4 Broadcast to remote subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4Addr,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: remNetSubnet,
-					Gateway:     ipv4Gateway,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:            remNetSubnetBcast,
-			expectedLocalAddress:  ipv4Addr.Address,
-			expectedRemoteAddress: remNetSubnetBcast,
-			expectedNextHop:       ipv4Gateway,
-			expectedNetProto:      header.IPv4ProtocolNumber,
-			expectedLoop:          stack.PacketOut,
-		},
-		// Broadcast to an unknown subnet follows the default route. Note that this
-		// is essentially just routing an unknown destination IP, because w/o any
-		// subnet prefix information a subnet broadcast address is just a normal IP.
-		{
-			name: "IPv4 Broadcast to unknown subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4Addr,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: defaultSubnet,
-					Gateway:     ipv4Gateway,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:            remNetSubnetBcast,
-			expectedLocalAddress:  ipv4Addr.Address,
-			expectedRemoteAddress: remNetSubnetBcast,
-			expectedNextHop:       ipv4Gateway,
-			expectedNetProto:      header.IPv4ProtocolNumber,
-			expectedLoop:          stack.PacketOut,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
-			})
-			ep := channel.New(0, defaultMTU, "")
-			ep.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-			if err := s.CreateNIC(nicID1, ep); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
-			}
-			if err := s.AddProtocolAddress(nicID1, test.nicAddr); err != nil {
-				t.Fatalf("AddProtocolAddress(%d, %+v): %s", nicID1, test.nicAddr, err)
-			}
-
-			s.SetRouteTable(test.routes)
-
-			var netProto tcpip.NetworkProtocolNumber
-			switch l := len(test.remoteAddr); l {
-			case header.IPv4AddressSize:
-				netProto = header.IPv4ProtocolNumber
-			case header.IPv6AddressSize:
-				netProto = header.IPv6ProtocolNumber
-			default:
-				t.Fatalf("got unexpected address length = %d bytes", l)
-			}
-
-			r, err := s.FindRoute(unspecifiedNICID, "" /* localAddr */, test.remoteAddr, netProto, false /* multicastLoop */)
-			if err != nil {
-				t.Fatalf("FindRoute(%d, '', %s, %d): %s", unspecifiedNICID, test.remoteAddr, netProto, err)
-			}
-			if r.LocalAddress() != test.expectedLocalAddress {
-				t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), test.expectedLocalAddress)
-			}
-			if r.RemoteAddress() != test.expectedRemoteAddress {
-				t.Errorf("got r.RemoteAddress = %s, want = %s", r.RemoteAddress(), test.expectedRemoteAddress)
-			}
-			if got := r.RemoteLinkAddress(); got != test.expectedRemoteLinkAddress {
-				t.Errorf("got r.RemoteLinkAddress() = %s, want = %s", got, test.expectedRemoteLinkAddress)
-			}
-			if r.NextHop() != test.expectedNextHop {
-				t.Errorf("got r.NextHop() = %s, want = %s", r.NextHop(), test.expectedNextHop)
-			}
-			if r.NetProto() != test.expectedNetProto {
-				t.Errorf("got r.NetProto() = %d, want = %d", r.NetProto(), test.expectedNetProto)
-			}
-			if r.Loop() != test.expectedLoop {
-				t.Errorf("got r.Loop() = %x, want = %x", r.Loop(), test.expectedLoop)
-			}
-		})
-	}
-}
-
-func TestResolveWith(t *testing.T) {
-	const (
-		unspecifiedNICID = 0
-		nicID            = 1
-	)
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, arp.NewProtocol},
-	})
-	ep := channel.New(0, defaultMTU, "")
-	ep.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-	if err := s.CreateNIC(nicID, ep); err != nil {
-		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-	}
-	addr := tcpip.ProtocolAddress{
-		Protocol: header.IPv4ProtocolNumber,
-		AddressWithPrefix: tcpip.AddressWithPrefix{
-			Address:   tcpip.Address([]byte{192, 168, 1, 58}),
-			PrefixLen: 24,
-		},
-	}
-	if err := s.AddProtocolAddress(nicID, addr); err != nil {
-		t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID, addr, err)
-	}
-
-	s.SetRouteTable([]tcpip.Route{{Destination: header.IPv4EmptySubnet, NIC: nicID}})
-
-	remoteAddr := tcpip.Address([]byte{192, 168, 1, 59})
-	r, err := s.FindRoute(unspecifiedNICID, "" /* localAddr */, remoteAddr, header.IPv4ProtocolNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatalf("FindRoute(%d, '', %s, %d): %s", unspecifiedNICID, remoteAddr, header.IPv4ProtocolNumber, err)
-	}
-	defer r.Release()
-
-	// Should initially require resolution.
-	if !r.IsResolutionRequired() {
-		t.Fatal("got r.IsResolutionRequired() = false, want = true")
-	}
-
-	// Manually resolving the route should no longer require resolution.
-	r.ResolveWith("\x01")
-	if r.IsResolutionRequired() {
-		t.Fatal("got r.IsResolutionRequired() = true, want = false")
-	}
-}
-
-// TestRouteReleaseAfterAddrRemoval tests that releasing a Route after its
-// associated address is removed should not cause a panic.
-func TestRouteReleaseAfterAddrRemoval(t *testing.T) {
-	const (
-		nicID      = 1
-		localAddr  = tcpip.Address("\x01")
-		remoteAddr = tcpip.Address("\x02")
-	)
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	ep := channel.New(0, defaultMTU, "")
-	if err := s.CreateNIC(nicID, ep); err != nil {
-		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-	}
-	if err := s.AddAddress(nicID, fakeNetNumber, localAddr); err != nil {
-		t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, fakeNetNumber, localAddr, err)
-	}
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	r, err := s.FindRoute(nicID, localAddr, remoteAddr, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		t.Fatalf("s.FindRoute(%d, %s, %s, %d, false): %s", nicID, localAddr, remoteAddr, fakeNetNumber, err)
-	}
-	// Should not panic.
-	defer r.Release()
-
-	// Check that removing the same address fails.
-	if err := s.RemoveAddress(nicID, localAddr); err != nil {
-		t.Fatalf("s.RemoveAddress(%d, %s): %s", nicID, localAddr, err)
-	}
-}
-
-func TestGetNetworkEndpoint(t *testing.T) {
-	const nicID = 1
-
-	tests := []struct {
-		name         string
-		protoFactory stack.NetworkProtocolFactory
-		protoNum     tcpip.NetworkProtocolNumber
-	}{
-		{
-			name:         "IPv4",
-			protoFactory: ipv4.NewProtocol,
-			protoNum:     ipv4.ProtocolNumber,
-		},
-		{
-			name:         "IPv6",
-			protoFactory: ipv6.NewProtocol,
-			protoNum:     ipv6.ProtocolNumber,
-		},
-	}
-
-	factories := make([]stack.NetworkProtocolFactory, 0, len(tests))
-	for _, test := range tests {
-		factories = append(factories, test.protoFactory)
-	}
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: factories,
-	})
-
-	if err := s.CreateNIC(nicID, channel.New(0, defaultMTU, "")); err != nil {
-		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ep, err := s.GetNetworkEndpoint(nicID, test.protoNum)
-			if err != nil {
-				t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, test.protoNum, err)
-			}
-
-			if got := ep.NetworkProtocolNumber(); got != test.protoNum {
-				t.Fatalf("got ep.NetworkProtocolNumber() = %d, want = %d", got, test.protoNum)
-			}
-		})
-	}
-}
-
-func TestGetMainNICAddressWhenNICDisabled(t *testing.T) {
-	const nicID = 1
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{fakeNetFactory},
-	})
-
-	if err := s.CreateNIC(nicID, channel.New(0, defaultMTU, "")); err != nil {
-		t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
-	}
-
-	protocolAddress := tcpip.ProtocolAddress{
-		Protocol: fakeNetNumber,
-		AddressWithPrefix: tcpip.AddressWithPrefix{
-			Address:   "\x01",
-			PrefixLen: 8,
-		},
-	}
-	if err := s.AddProtocolAddress(nicID, protocolAddress); err != nil {
-		t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID, protocolAddress, err)
-	}
-
-	// Check that we get the right initial address and prefix length.
-	if err := checkGetMainNICAddress(s, nicID, fakeNetNumber, protocolAddress.AddressWithPrefix); err != nil {
-		t.Fatal(err)
-	}
-
-	// Should still get the address when the NIC is diabled.
-	if err := s.DisableNIC(nicID); err != nil {
-		t.Fatalf("DisableNIC(%d): %s", nicID, err)
-	}
-	if err := checkGetMainNICAddress(s, nicID, fakeNetNumber, protocolAddress.AddressWithPrefix); err != nil {
-		t.Fatal(err)
-	}
-}
-
-// TestAddRoute tests Stack.AddRoute
-func TestAddRoute(t *testing.T) {
-	const nicID = 1
-
-	s := stack.New(stack.Options{})
-
-	subnet1, err := tcpip.NewSubnet("\x00", "\x00")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	subnet2, err := tcpip.NewSubnet("\x01", "\x01")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	expected := []tcpip.Route{
-		{Destination: subnet1, Gateway: "\x00", NIC: 1},
-		{Destination: subnet2, Gateway: "\x00", NIC: 1},
-	}
-
-	// Initialize the route table with one route.
-	s.SetRouteTable([]tcpip.Route{expected[0]})
-
-	// Add another route.
-	s.AddRoute(expected[1])
-
-	rt := s.GetRouteTable()
-	if got, want := len(rt), len(expected); got != want {
-		t.Fatalf("Unexpected route table length got = %d, want = %d", got, want)
-	}
-	for i, route := range rt {
-		if got, want := route, expected[i]; got != want {
-			t.Fatalf("Unexpected route got = %#v, want = %#v", got, want)
-		}
-	}
-}
-
-// TestRemoveRoutes tests Stack.RemoveRoutes
-func TestRemoveRoutes(t *testing.T) {
-	const nicID = 1
-
-	s := stack.New(stack.Options{})
-
-	addressToRemove := tcpip.Address("\x01")
-	subnet1, err := tcpip.NewSubnet(addressToRemove, "\x01")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	subnet2, err := tcpip.NewSubnet(addressToRemove, "\x01")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	subnet3, err := tcpip.NewSubnet("\x02", "\x02")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Initialize the route table with three routes.
-	s.SetRouteTable([]tcpip.Route{
-		{Destination: subnet1, Gateway: "\x00", NIC: 1},
-		{Destination: subnet2, Gateway: "\x00", NIC: 1},
-		{Destination: subnet3, Gateway: "\x00", NIC: 1},
-	})
-
-	// Remove routes with the specific address.
-	s.RemoveRoutes(func(r tcpip.Route) bool {
-		return r.Destination.ID() == addressToRemove
-	})
-
-	expected := []tcpip.Route{{Destination: subnet3, Gateway: "\x00", NIC: 1}}
-	rt := s.GetRouteTable()
-	if got, want := len(rt), len(expected); got != want {
-		t.Fatalf("Unexpected route table length got = %d, want = %d", got, want)
-	}
-	for i, route := range rt {
-		if got, want := route, expected[i]; got != want {
-			t.Fatalf("Unexpected route got = %#v, want = %#v", got, want)
-		}
-	}
-}
-
-func TestFindRouteWithForwarding(t *testing.T) {
-	const (
-		nicID1 = 1
-		nicID2 = 2
-
-		nic1Addr   = tcpip.Address("\x01")
-		nic2Addr   = tcpip.Address("\x02")
-		remoteAddr = tcpip.Address("\x03")
-	)
-
-	type netCfg struct {
-		proto      tcpip.NetworkProtocolNumber
-		factory    stack.NetworkProtocolFactory
-		nic1Addr   tcpip.Address
-		nic2Addr   tcpip.Address
-		remoteAddr tcpip.Address
-	}
-
-	fakeNetCfg := netCfg{
-		proto:      fakeNetNumber,
-		factory:    fakeNetFactory,
-		nic1Addr:   nic1Addr,
-		nic2Addr:   nic2Addr,
-		remoteAddr: remoteAddr,
-	}
-
-	globalIPv6Addr1 := tcpip.Address(net.ParseIP("a::1").To16())
-	globalIPv6Addr2 := tcpip.Address(net.ParseIP("a::2").To16())
-
-	ipv6LinkLocalNIC1WithGlobalRemote := netCfg{
-		proto:      ipv6.ProtocolNumber,
-		factory:    ipv6.NewProtocol,
-		nic1Addr:   llAddr1,
-		nic2Addr:   globalIPv6Addr2,
-		remoteAddr: globalIPv6Addr1,
-	}
-	ipv6GlobalNIC1WithLinkLocalRemote := netCfg{
-		proto:      ipv6.ProtocolNumber,
-		factory:    ipv6.NewProtocol,
-		nic1Addr:   globalIPv6Addr1,
-		nic2Addr:   llAddr1,
-		remoteAddr: llAddr2,
-	}
-	ipv6GlobalNIC1WithLinkLocalMulticastRemote := netCfg{
-		proto:      ipv6.ProtocolNumber,
-		factory:    ipv6.NewProtocol,
-		nic1Addr:   globalIPv6Addr1,
-		nic2Addr:   globalIPv6Addr2,
-		remoteAddr: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
-	}
-
-	tests := []struct {
-		name string
-
-		netCfg            netCfg
-		forwardingEnabled bool
-
-		addrNIC   tcpip.NICID
-		localAddr tcpip.Address
-
-		findRouteErr          tcpip.Error
-		dependentOnForwarding bool
-	}{
-		{
-			name:                  "forwarding disabled and localAddr not on specified NIC but route from different NIC",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     false,
-			addrNIC:               nicID1,
-			localAddr:             fakeNetCfg.nic2Addr,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and localAddr not on specified NIC but route from different NIC",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     true,
-			addrNIC:               nicID1,
-			localAddr:             fakeNetCfg.nic2Addr,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and localAddr on specified NIC but route from different NIC",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     false,
-			addrNIC:               nicID1,
-			localAddr:             fakeNetCfg.nic1Addr,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and localAddr on specified NIC but route from different NIC",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     true,
-			addrNIC:               nicID1,
-			localAddr:             fakeNetCfg.nic1Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: true,
-		},
-		{
-			name:                  "forwarding disabled and localAddr on specified NIC and route from same NIC",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     false,
-			addrNIC:               nicID2,
-			localAddr:             fakeNetCfg.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and localAddr on specified NIC and route from same NIC",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     true,
-			addrNIC:               nicID2,
-			localAddr:             fakeNetCfg.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and localAddr not on specified NIC but route from same NIC",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     false,
-			addrNIC:               nicID2,
-			localAddr:             fakeNetCfg.nic1Addr,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and localAddr not on specified NIC but route from same NIC",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     true,
-			addrNIC:               nicID2,
-			localAddr:             fakeNetCfg.nic1Addr,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and localAddr on same NIC as route",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     false,
-			localAddr:             fakeNetCfg.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and localAddr on same NIC as route",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     false,
-			localAddr:             fakeNetCfg.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and localAddr on different NIC as route",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     false,
-			localAddr:             fakeNetCfg.nic1Addr,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and localAddr on different NIC as route",
-			netCfg:                fakeNetCfg,
-			forwardingEnabled:     true,
-			localAddr:             fakeNetCfg.nic1Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: true,
-		},
-		{
-			name:                  "forwarding disabled and specified NIC only has link-local addr with route on different NIC",
-			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
-			forwardingEnabled:     false,
-			addrNIC:               nicID1,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and specified NIC only has link-local addr with route on different NIC",
-			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
-			forwardingEnabled:     true,
-			addrNIC:               nicID1,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and link-local local addr with route on different NIC",
-			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
-			forwardingEnabled:     false,
-			localAddr:             ipv6LinkLocalNIC1WithGlobalRemote.nic1Addr,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and link-local local addr with route on same NIC",
-			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
-			forwardingEnabled:     true,
-			localAddr:             ipv6LinkLocalNIC1WithGlobalRemote.nic1Addr,
-			findRouteErr:          &tcpip.ErrNoRoute{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and global local addr with route on same NIC",
-			netCfg:                ipv6LinkLocalNIC1WithGlobalRemote,
-			forwardingEnabled:     true,
-			localAddr:             ipv6LinkLocalNIC1WithGlobalRemote.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and link-local local addr with route on same NIC",
-			netCfg:                ipv6GlobalNIC1WithLinkLocalRemote,
-			forwardingEnabled:     false,
-			localAddr:             ipv6GlobalNIC1WithLinkLocalRemote.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and link-local local addr with route on same NIC",
-			netCfg:                ipv6GlobalNIC1WithLinkLocalRemote,
-			forwardingEnabled:     true,
-			localAddr:             ipv6GlobalNIC1WithLinkLocalRemote.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and global local addr with link-local remote on different NIC",
-			netCfg:                ipv6GlobalNIC1WithLinkLocalRemote,
-			forwardingEnabled:     false,
-			localAddr:             ipv6GlobalNIC1WithLinkLocalRemote.nic1Addr,
-			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and global local addr with link-local remote on different NIC",
-			netCfg:                ipv6GlobalNIC1WithLinkLocalRemote,
-			forwardingEnabled:     true,
-			localAddr:             ipv6GlobalNIC1WithLinkLocalRemote.nic1Addr,
-			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and global local addr with link-local multicast remote on different NIC",
-			netCfg:                ipv6GlobalNIC1WithLinkLocalMulticastRemote,
-			forwardingEnabled:     false,
-			localAddr:             ipv6GlobalNIC1WithLinkLocalMulticastRemote.nic1Addr,
-			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and global local addr with link-local multicast remote on different NIC",
-			netCfg:                ipv6GlobalNIC1WithLinkLocalMulticastRemote,
-			forwardingEnabled:     true,
-			localAddr:             ipv6GlobalNIC1WithLinkLocalMulticastRemote.nic1Addr,
-			findRouteErr:          &tcpip.ErrNetworkUnreachable{},
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding disabled and global local addr with link-local multicast remote on same NIC",
-			netCfg:                ipv6GlobalNIC1WithLinkLocalMulticastRemote,
-			forwardingEnabled:     false,
-			localAddr:             ipv6GlobalNIC1WithLinkLocalMulticastRemote.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-		{
-			name:                  "forwarding enabled and global local addr with link-local multicast remote on same NIC",
-			netCfg:                ipv6GlobalNIC1WithLinkLocalMulticastRemote,
-			forwardingEnabled:     true,
-			localAddr:             ipv6GlobalNIC1WithLinkLocalMulticastRemote.nic2Addr,
-			findRouteErr:          nil,
-			dependentOnForwarding: false,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols: []stack.NetworkProtocolFactory{test.netCfg.factory},
-			})
-
-			ep1 := channel.New(1, defaultMTU, "")
-			if err := s.CreateNIC(nicID1, ep1); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s:", nicID1, err)
-			}
-
-			ep2 := channel.New(1, defaultMTU, "")
-			if err := s.CreateNIC(nicID2, ep2); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s:", nicID2, err)
-			}
-
-			if err := s.AddAddress(nicID1, test.netCfg.proto, test.netCfg.nic1Addr); err != nil {
-				t.Fatalf("AddAddress(%d, %d, %s): %s", nicID1, test.netCfg.proto, test.netCfg.nic1Addr, err)
-			}
-
-			if err := s.AddAddress(nicID2, test.netCfg.proto, test.netCfg.nic2Addr); err != nil {
-				t.Fatalf("AddAddress(%d, %d, %s): %s", nicID2, test.netCfg.proto, test.netCfg.nic2Addr, err)
-			}
-
-			if err := s.SetForwarding(test.netCfg.proto, test.forwardingEnabled); err != nil {
-				t.Fatalf("SetForwarding(%d, %t): %s", test.netCfg.proto, test.forwardingEnabled, err)
-			}
-
-			s.SetRouteTable([]tcpip.Route{{Destination: test.netCfg.remoteAddr.WithPrefix().Subnet(), NIC: nicID2}})
-
-			r, err := s.FindRoute(test.addrNIC, test.localAddr, test.netCfg.remoteAddr, test.netCfg.proto, false /* multicastLoop */)
-			if r != nil {
-				defer r.Release()
-			}
-			if diff := cmp.Diff(test.findRouteErr, err); diff != "" {
-				t.Fatalf("unexpected error from FindRoute(%d, %s, %s, %d, false), (-want, +got):\n%s", test.addrNIC, test.localAddr, test.netCfg.remoteAddr, test.netCfg.proto, diff)
-			}
-
-			if test.findRouteErr != nil {
-				return
-			}
-
-			if r.LocalAddress() != test.localAddr {
-				t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), test.localAddr)
-			}
-			if r.RemoteAddress() != test.netCfg.remoteAddr {
-				t.Errorf("got r.RemoteAddress() = %s, want = %s", r.RemoteAddress(), test.netCfg.remoteAddr)
-			}
-
-			if t.Failed() {
-				t.FailNow()
-			}
-
-			// Sending a packet should always go through NIC2 since we only install a
-			// route to test.netCfg.remoteAddr through NIC2.
-			data := buffer.View([]byte{1, 2, 3, 4})
-			if err := send(r, data); err != nil {
-				t.Fatalf("send(_, _): %s", err)
-			}
-			if n := ep1.Drain(); n != 0 {
-				t.Errorf("got %d unexpected packets from ep1", n)
-			}
-			pkt, ok := ep2.Read()
-			if !ok {
-				t.Fatal("packet not sent through ep2")
-			}
-			if pkt.Route.LocalAddress != test.localAddr {
-				t.Errorf("got pkt.Route.LocalAddress = %s, want = %s", pkt.Route.LocalAddress, test.localAddr)
-			}
-			if pkt.Route.RemoteAddress != test.netCfg.remoteAddr {
-				t.Errorf("got pkt.Route.RemoteAddress = %s, want = %s", pkt.Route.RemoteAddress, test.netCfg.remoteAddr)
-			}
-
-			if !test.forwardingEnabled || !test.dependentOnForwarding {
-				return
-			}
-
-			// Disabling forwarding when the route is dependent on forwarding being
-			// enabled should make the route invalid.
-			if err := s.SetForwarding(test.netCfg.proto, false); err != nil {
-				t.Fatalf("SetForwarding(%d, false): %s", test.netCfg.proto, err)
-			}
-			{
-				err := send(r, data)
-				if _, ok := err.(*tcpip.ErrInvalidEndpointState); !ok {
-					t.Fatalf("got send(_, _) = %s, want = %s", err, &tcpip.ErrInvalidEndpointState{})
-				}
-			}
-			if n := ep1.Drain(); n != 0 {
-				t.Errorf("got %d unexpected packets from ep1", n)
-			}
-			if n := ep2.Drain(); n != 0 {
-				t.Errorf("got %d unexpected packets from ep2", n)
-			}
-		})
-	}
-}
-
-func TestWritePacketToRemote(t *testing.T) {
-	const nicID = 1
-	const MTU = 1280
-	e := channel.New(1, MTU, linkAddr1)
-	s := stack.New(stack.Options{})
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-	if err := s.EnableNIC(nicID); err != nil {
-		t.Fatalf("CreateNIC(%d) = %s", nicID, err)
-	}
-	tests := []struct {
-		name     string
-		protocol tcpip.NetworkProtocolNumber
-		payload  []byte
-	}{
-		{
-			name:     "SuccessIPv4",
-			protocol: header.IPv4ProtocolNumber,
-			payload:  []byte{1, 2, 3, 4},
-		},
-		{
-			name:     "SuccessIPv6",
-			protocol: header.IPv6ProtocolNumber,
-			payload:  []byte{5, 6, 7, 8},
-		},
-	}
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			if err := s.WritePacketToRemote(nicID, linkAddr2, test.protocol, buffer.View(test.payload).ToVectorisedView()); err != nil {
-				t.Fatalf("s.WritePacketToRemote(_, _, _, _) = %s", err)
-			}
-
-			pkt, ok := e.Read()
-			if got, want := ok, true; got != want {
-				t.Fatalf("e.Read() = %t, want %t", got, want)
-			}
-			if got, want := pkt.Proto, test.protocol; got != want {
-				t.Fatalf("pkt.Proto = %d, want %d", got, want)
-			}
-			if pkt.Route.RemoteLinkAddress != linkAddr2 {
-				t.Fatalf("pkt.Route.RemoteAddress = %s, want %s", pkt.Route.RemoteLinkAddress, linkAddr2)
-			}
-			if diff := cmp.Diff(pkt.Pkt.Data().AsRange().ToOwnedView(), buffer.View(test.payload)); diff != "" {
-				t.Errorf("pkt.Pkt.Data mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-
-	t.Run("InvalidNICID", func(t *testing.T) {
-		err := s.WritePacketToRemote(234, linkAddr2, header.IPv4ProtocolNumber, buffer.View([]byte{1}).ToVectorisedView())
-		if _, ok := err.(*tcpip.ErrUnknownDevice); !ok {
-			t.Fatalf("s.WritePacketToRemote(_, _, _, _) = %s, want = %s", err, &tcpip.ErrUnknownDevice{})
-		}
-		pkt, ok := e.Read()
-		if got, want := ok, false; got != want {
-			t.Fatalf("e.Read() = %t, %v; want %t", got, pkt, want)
-		}
-	})
-}
-
-func TestClearNeighborCacheOnNICDisable(t *testing.T) {
-	const (
-		nicID = 1
-
-		ipv4Addr = tcpip.Address("\x01\x02\x03\x04")
-		ipv6Addr = tcpip.Address("\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04")
-		linkAddr = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
-	)
-
-	clock := faketime.NewManualClock()
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
-		Clock:            clock,
-	})
-	e := channel.New(0, 0, "")
-	e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	addrs := []struct {
-		proto tcpip.NetworkProtocolNumber
-		addr  tcpip.Address
-	}{
-		{
-			proto: ipv4.ProtocolNumber,
-			addr:  ipv4Addr,
-		},
-		{
-			proto: ipv6.ProtocolNumber,
-			addr:  ipv6Addr,
-		},
-	}
-	for _, addr := range addrs {
-		if err := s.AddStaticNeighbor(nicID, addr.proto, addr.addr, linkAddr); err != nil {
-			t.Fatalf("s.AddStaticNeighbor(%d, %d, %s, %s): %s", nicID, addr.proto, addr.addr, linkAddr, err)
-		}
-
-		if neighbors, err := s.Neighbors(nicID, addr.proto); err != nil {
-			t.Fatalf("s.Neighbors(%d, %d): %s", nicID, addr.proto, err)
-		} else if diff := cmp.Diff(
-			[]stack.NeighborEntry{{Addr: addr.addr, LinkAddr: linkAddr, State: stack.Static, UpdatedAtNanos: clock.NowNanoseconds()}},
-			neighbors,
-		); diff != "" {
-			t.Fatalf("proto=%d neighbors mismatch (-want +got):\n%s", addr.proto, diff)
-		}
-	}
-
-	// Disabling the NIC should clear the neighbor table.
-	if err := s.DisableNIC(nicID); err != nil {
-		t.Fatalf("s.DisableNIC(%d): %s", nicID, err)
-	}
-	for _, addr := range addrs {
-		if neighbors, err := s.Neighbors(nicID, addr.proto); err != nil {
-			t.Fatalf("s.Neighbors(%d, %d): %s", nicID, addr.proto, err)
-		} else if len(neighbors) != 0 {
-			t.Fatalf("got proto=%d len(neighbors) = %d, want = 0; neighbors = %#v", addr.proto, len(neighbors), neighbors)
-		}
-	}
-
-	// Enabling the NIC should have an empty neighbor table.
-	if err := s.EnableNIC(nicID); err != nil {
-		t.Fatalf("s.EnableNIC(%d): %s", nicID, err)
-	}
-	for _, addr := range addrs {
-		if neighbors, err := s.Neighbors(nicID, addr.proto); err != nil {
-			t.Fatalf("s.Neighbors(%d, %d): %s", nicID, addr.proto, err)
-		} else if len(neighbors) != 0 {
-			t.Fatalf("got proto=%d len(neighbors) = %d, want = 0; neighbors = %#v", addr.proto, len(neighbors), neighbors)
-		}
-	}
-}
-
-func TestGetLinkAddressErrors(t *testing.T) {
-	const (
-		nicID        = 1
-		unknownNICID = nicID + 1
-	)
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol},
-	})
-	if err := s.CreateNIC(nicID, channel.New(0, 0, "")); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	{
-		err := s.GetLinkAddress(unknownNICID, "", "", ipv4.ProtocolNumber, nil)
-		if _, ok := err.(*tcpip.ErrUnknownNICID); !ok {
-			t.Errorf("got s.GetLinkAddress(%d, '', '', %d, nil) = %s, want = %s", unknownNICID, ipv4.ProtocolNumber, err, &tcpip.ErrUnknownNICID{})
-		}
-	}
-	{
-		err := s.GetLinkAddress(nicID, "", "", ipv4.ProtocolNumber, nil)
-		if _, ok := err.(*tcpip.ErrNotSupported); !ok {
-			t.Errorf("got s.GetLinkAddress(%d, '', '', %d, nil) = %s, want = %s", unknownNICID, ipv4.ProtocolNumber, err, &tcpip.ErrNotSupported{})
-		}
-	}
-}
-
-func TestStaticGetLinkAddress(t *testing.T) {
-	const (
-		nicID = 1
-	)
-
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
-	})
-	e := channel.New(0, 0, "")
-	e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
-	if err := s.CreateNIC(nicID, e); err != nil {
-		t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
-	}
-
-	tests := []struct {
-		name             string
-		proto            tcpip.NetworkProtocolNumber
-		addr             tcpip.Address
-		expectedLinkAddr tcpip.LinkAddress
-	}{
-		{
-			name:             "IPv4",
-			proto:            ipv4.ProtocolNumber,
-			addr:             header.IPv4Broadcast,
-			expectedLinkAddr: header.EthernetBroadcastAddress,
-		},
-		{
-			name:             "IPv6",
-			proto:            ipv6.ProtocolNumber,
-			addr:             header.IPv6AllNodesMulticastAddress,
-			expectedLinkAddr: header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllNodesMulticastAddress),
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			ch := make(chan stack.LinkResolutionResult, 1)
-			if err := s.GetLinkAddress(nicID, test.addr, "", test.proto, func(r stack.LinkResolutionResult) {
-				ch <- r
-			}); err != nil {
-				t.Fatalf("s.GetLinkAddress(%d, %s, '', %d, _): %s", nicID, test.addr, test.proto, err)
-			}
-
-			if diff := cmp.Diff(stack.LinkResolutionResult{LinkAddress: test.expectedLinkAddr, Err: nil}, <-ch); diff != "" {
-				t.Fatalf("link resolution result mismatch (-want +got):\n%s", diff)
-			}
-		})
-	}
-}
diff --git a/pkg/tcpip/stack/stack_unsafe_state_autogen.go b/pkg/tcpip/stack/stack_unsafe_state_autogen.go
new file mode 100644
index 0000000..758ab34
--- /dev/null
+++ b/pkg/tcpip/stack/stack_unsafe_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package stack
diff --git a/pkg/tcpip/stack/transport_demuxer_test.go b/pkg/tcpip/stack/transport_demuxer_test.go
deleted file mode 100644
index c1c6cbc..0000000
--- a/pkg/tcpip/stack/transport_demuxer_test.go
+++ /dev/null
@@ -1,385 +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 stack_test
-
-import (
-	"io/ioutil"
-	"math"
-	"math/rand"
-	"testing"
-
-	"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/network/ipv4"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
-	"gvisor.dev/gvisor/pkg/tcpip/ports"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
-	"gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
-	testSrcAddrV6 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
-	testDstAddrV6 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
-
-	testSrcAddrV4 = "\x0a\x00\x00\x01"
-	testDstAddrV4 = "\x0a\x00\x00\x02"
-
-	testDstPort = 1234
-	testSrcPort = 4096
-)
-
-type testContext struct {
-	linkEps map[tcpip.NICID]*channel.Endpoint
-	s       *stack.Stack
-	wq      waiter.Queue
-}
-
-// newDualTestContextMultiNIC creates the testing context and also linkEpIDs NICs.
-func newDualTestContextMultiNIC(t *testing.T, mtu uint32, linkEpIDs []tcpip.NICID) *testContext {
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
-	})
-	linkEps := make(map[tcpip.NICID]*channel.Endpoint)
-	for _, linkEpID := range linkEpIDs {
-		channelEp := channel.New(256, mtu, "")
-		if err := s.CreateNIC(linkEpID, channelEp); err != nil {
-			t.Fatalf("CreateNIC failed: %s", err)
-		}
-		linkEps[linkEpID] = channelEp
-
-		if err := s.AddAddress(linkEpID, ipv4.ProtocolNumber, testDstAddrV4); err != nil {
-			t.Fatalf("AddAddress IPv4 failed: %s", err)
-		}
-
-		if err := s.AddAddress(linkEpID, ipv6.ProtocolNumber, testDstAddrV6); err != nil {
-			t.Fatalf("AddAddress IPv6 failed: %s", err)
-		}
-	}
-
-	s.SetRouteTable([]tcpip.Route{
-		{Destination: header.IPv4EmptySubnet, NIC: 1},
-		{Destination: header.IPv6EmptySubnet, NIC: 1},
-	})
-
-	return &testContext{
-		s:       s,
-		linkEps: linkEps,
-	}
-}
-
-type headers struct {
-	srcPort, dstPort uint16
-}
-
-func newPayload() []byte {
-	b := make([]byte, 30+rand.Intn(100))
-	for i := range b {
-		b[i] = byte(rand.Intn(256))
-	}
-	return b
-}
-
-func (c *testContext) sendV4Packet(payload []byte, h *headers, linkEpID tcpip.NICID) {
-	buf := buffer.NewView(header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
-	payloadStart := len(buf) - len(payload)
-	copy(buf[payloadStart:], payload)
-
-	// Initialize the IP header.
-	ip := header.IPv4(buf)
-	ip.Encode(&header.IPv4Fields{
-		TOS:         0x80,
-		TotalLength: uint16(len(buf)),
-		TTL:         65,
-		Protocol:    uint8(udp.ProtocolNumber),
-		SrcAddr:     testSrcAddrV4,
-		DstAddr:     testDstAddrV4,
-	})
-	ip.SetChecksum(^ip.CalculateChecksum())
-
-	// Initialize the UDP header.
-	u := header.UDP(buf[header.IPv4MinimumSize:])
-	u.Encode(&header.UDPFields{
-		SrcPort: h.srcPort,
-		DstPort: h.dstPort,
-		Length:  uint16(header.UDPMinimumSize + len(payload)),
-	})
-
-	// Calculate the UDP pseudo-header checksum.
-	xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, testSrcAddrV4, testDstAddrV4, uint16(len(u)))
-
-	// Calculate the UDP checksum and set it.
-	xsum = header.Checksum(payload, xsum)
-	u.SetChecksum(^u.CalculateChecksum(xsum))
-
-	// Inject packet.
-	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	})
-	c.linkEps[linkEpID].InjectInbound(ipv4.ProtocolNumber, pkt)
-}
-
-func (c *testContext) sendV6Packet(payload []byte, h *headers, linkEpID tcpip.NICID) {
-	// Allocate a buffer for data and headers.
-	buf := buffer.NewView(header.UDPMinimumSize + header.IPv6MinimumSize + len(payload))
-	copy(buf[len(buf)-len(payload):], payload)
-
-	// Initialize the IP header.
-	ip := header.IPv6(buf)
-	ip.Encode(&header.IPv6Fields{
-		PayloadLength:     uint16(header.UDPMinimumSize + len(payload)),
-		TransportProtocol: udp.ProtocolNumber,
-		HopLimit:          65,
-		SrcAddr:           testSrcAddrV6,
-		DstAddr:           testDstAddrV6,
-	})
-
-	// Initialize the UDP header.
-	u := header.UDP(buf[header.IPv6MinimumSize:])
-	u.Encode(&header.UDPFields{
-		SrcPort: h.srcPort,
-		DstPort: h.dstPort,
-		Length:  uint16(header.UDPMinimumSize + len(payload)),
-	})
-
-	// Calculate the UDP pseudo-header checksum.
-	xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, testSrcAddrV6, testDstAddrV6, uint16(len(u)))
-
-	// Calculate the UDP checksum and set it.
-	xsum = header.Checksum(payload, xsum)
-	u.SetChecksum(^u.CalculateChecksum(xsum))
-
-	// Inject packet.
-	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	})
-	c.linkEps[linkEpID].InjectInbound(ipv6.ProtocolNumber, pkt)
-}
-
-func TestTransportDemuxerRegister(t *testing.T) {
-	for _, test := range []struct {
-		name  string
-		proto tcpip.NetworkProtocolNumber
-		want  tcpip.Error
-	}{
-		{"failure", ipv6.ProtocolNumber, &tcpip.ErrUnknownProtocol{}},
-		{"success", ipv4.ProtocolNumber, nil},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol},
-				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
-			})
-			var wq waiter.Queue
-			ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
-			if err != nil {
-				t.Fatal(err)
-			}
-			tEP, ok := ep.(stack.TransportEndpoint)
-			if !ok {
-				t.Fatalf("%T does not implement stack.TransportEndpoint", ep)
-			}
-			if got, want := s.RegisterTransportEndpoint([]tcpip.NetworkProtocolNumber{test.proto}, udp.ProtocolNumber, stack.TransportEndpointID{}, tEP, ports.Flags{}, 0), test.want; got != want {
-				t.Fatalf("s.RegisterTransportEndpoint(...) = %s, want %s", got, want)
-			}
-		})
-	}
-}
-
-// TestBindToDeviceDistribution injects varied packets on input devices and checks that
-// the distribution of packets received matches expectations.
-func TestBindToDeviceDistribution(t *testing.T) {
-	type endpointSockopts struct {
-		reuse        bool
-		bindToDevice tcpip.NICID
-	}
-	for _, test := range []struct {
-		name string
-		// endpoints will received the inject packets.
-		endpoints []endpointSockopts
-		// wantDistributions is the want ratio of packets received on each
-		// endpoint for each NIC on which packets are injected.
-		wantDistributions map[tcpip.NICID][]float64
-	}{
-		{
-			"BindPortReuse",
-			// 5 endpoints that all have reuse set.
-			[]endpointSockopts{
-				{reuse: true, bindToDevice: 0},
-				{reuse: true, bindToDevice: 0},
-				{reuse: true, bindToDevice: 0},
-				{reuse: true, bindToDevice: 0},
-				{reuse: true, bindToDevice: 0},
-			},
-			map[tcpip.NICID][]float64{
-				// Injected packets on dev0 get distributed evenly.
-				1: {0.2, 0.2, 0.2, 0.2, 0.2},
-			},
-		},
-		{
-			"BindToDevice",
-			// 3 endpoints with various bindings.
-			[]endpointSockopts{
-				{reuse: false, bindToDevice: 1},
-				{reuse: false, bindToDevice: 2},
-				{reuse: false, bindToDevice: 3},
-			},
-			map[tcpip.NICID][]float64{
-				// Injected packets on dev0 go only to the endpoint bound to dev0.
-				1: {1, 0, 0},
-				// Injected packets on dev1 go only to the endpoint bound to dev1.
-				2: {0, 1, 0},
-				// Injected packets on dev2 go only to the endpoint bound to dev2.
-				3: {0, 0, 1},
-			},
-		},
-		{
-			"ReuseAndBindToDevice",
-			// 6 endpoints with various bindings.
-			[]endpointSockopts{
-				{reuse: true, bindToDevice: 1},
-				{reuse: true, bindToDevice: 1},
-				{reuse: true, bindToDevice: 2},
-				{reuse: true, bindToDevice: 2},
-				{reuse: true, bindToDevice: 2},
-				{reuse: true, bindToDevice: 0},
-			},
-			map[tcpip.NICID][]float64{
-				// Injected packets on dev0 get distributed among endpoints bound to
-				// dev0.
-				1: {0.5, 0.5, 0, 0, 0, 0},
-				// Injected packets on dev1 get distributed among endpoints bound to
-				// dev1 or unbound.
-				2: {0, 0, 1. / 3, 1. / 3, 1. / 3, 0},
-				// Injected packets on dev999 go only to the unbound.
-				1000: {0, 0, 0, 0, 0, 1},
-			},
-		},
-	} {
-		for protoName, netProtoNum := range map[string]tcpip.NetworkProtocolNumber{
-			"IPv4": ipv4.ProtocolNumber,
-			"IPv6": ipv6.ProtocolNumber,
-		} {
-			for device, wantDistribution := range test.wantDistributions {
-				t.Run(test.name+protoName+string(device), func(t *testing.T) {
-					var devices []tcpip.NICID
-					for d := range test.wantDistributions {
-						devices = append(devices, d)
-					}
-					c := newDualTestContextMultiNIC(t, defaultMTU, devices)
-
-					eps := make(map[tcpip.Endpoint]int)
-
-					pollChannel := make(chan tcpip.Endpoint)
-					for i, endpoint := range test.endpoints {
-						// Try to receive the data.
-						wq := waiter.Queue{}
-						we, ch := waiter.NewChannelEntry(nil)
-						wq.EventRegister(&we, waiter.EventIn)
-						defer wq.EventUnregister(&we)
-						defer close(ch)
-
-						var err tcpip.Error
-						ep, err := c.s.NewEndpoint(udp.ProtocolNumber, netProtoNum, &wq)
-						if err != nil {
-							t.Fatalf("NewEndpoint failed: %s", err)
-						}
-						eps[ep] = i
-
-						go func(ep tcpip.Endpoint) {
-							for range ch {
-								pollChannel <- ep
-							}
-						}(ep)
-
-						defer ep.Close()
-						ep.SocketOptions().SetReusePort(endpoint.reuse)
-						if err := ep.SocketOptions().SetBindToDevice(int32(endpoint.bindToDevice)); err != nil {
-							t.Fatalf("SetSockOpt(&%T(%d)) on endpoint %d failed: %s", endpoint.bindToDevice, endpoint.bindToDevice, i, err)
-						}
-
-						var dstAddr tcpip.Address
-						switch netProtoNum {
-						case ipv4.ProtocolNumber:
-							dstAddr = testDstAddrV4
-						case ipv6.ProtocolNumber:
-							dstAddr = testDstAddrV6
-						default:
-							t.Fatalf("unexpected protocol number: %d", netProtoNum)
-						}
-						if err := ep.Bind(tcpip.FullAddress{Addr: dstAddr, Port: testDstPort}); err != nil {
-							t.Fatalf("ep.Bind(...) on endpoint %d failed: %s", i, err)
-						}
-					}
-
-					npackets := 100000
-					nports := 10000
-					if got, want := len(test.endpoints), len(wantDistribution); got != want {
-						t.Fatalf("got len(test.endpoints) = %d, want %d", got, want)
-					}
-					ports := make(map[uint16]tcpip.Endpoint)
-					stats := make(map[tcpip.Endpoint]int)
-					for i := 0; i < npackets; i++ {
-						// Send a packet.
-						port := uint16(i % nports)
-						payload := newPayload()
-						hdrs := &headers{
-							srcPort: testSrcPort + port,
-							dstPort: testDstPort,
-						}
-						switch netProtoNum {
-						case ipv4.ProtocolNumber:
-							c.sendV4Packet(payload, hdrs, device)
-						case ipv6.ProtocolNumber:
-							c.sendV6Packet(payload, hdrs, device)
-						default:
-							t.Fatalf("unexpected protocol number: %d", netProtoNum)
-						}
-
-						ep := <-pollChannel
-						if _, err := ep.Read(ioutil.Discard, tcpip.ReadOptions{}); err != nil {
-							t.Fatalf("Read on endpoint %d failed: %s", eps[ep], err)
-						}
-						stats[ep]++
-						if i < nports {
-							ports[uint16(i)] = ep
-						} else {
-							// Check that all packets from one client are handled by the same
-							// socket.
-							if want, got := ports[port], ep; want != got {
-								t.Fatalf("Packet sent on port %d expected on endpoint %d but received on endpoint %d", port, eps[want], eps[got])
-							}
-						}
-					}
-
-					// Check that a packet distribution is as expected.
-					for ep, i := range eps {
-						wantRatio := wantDistribution[i]
-						wantRecv := wantRatio * float64(npackets)
-						actualRecv := stats[ep]
-						actualRatio := float64(stats[ep]) / float64(npackets)
-						// The deviation is less than 10%.
-						if math.Abs(actualRatio-wantRatio) > 0.05 {
-							t.Errorf("want about %.0f%% (%.0f of %d) packets to arrive on endpoint %d, got %.0f%% (%d of %d)", wantRatio*100, wantRecv, npackets, i, actualRatio*100, actualRecv, npackets)
-						}
-					}
-				})
-			}
-		}
-	}
-}
diff --git a/pkg/tcpip/stack/transport_test.go b/pkg/tcpip/stack/transport_test.go
deleted file mode 100644
index 054cced..0000000
--- a/pkg/tcpip/stack/transport_test.go
+++ /dev/null
@@ -1,555 +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 stack_test
-
-import (
-	"bytes"
-	"io"
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/buffer"
-	"gvisor.dev/gvisor/pkg/tcpip/link/channel"
-	"gvisor.dev/gvisor/pkg/tcpip/ports"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-	"gvisor.dev/gvisor/pkg/waiter"
-)
-
-const (
-	fakeTransNumber    tcpip.TransportProtocolNumber = 1
-	fakeTransHeaderLen int                           = 3
-)
-
-// fakeTransportEndpoint is a transport-layer protocol endpoint. It counts
-// received packets; the counts of all endpoints are aggregated in the protocol
-// descriptor.
-//
-// Headers of this protocol are fakeTransHeaderLen bytes, but we currently don't
-// use it.
-type fakeTransportEndpoint struct {
-	stack.TransportEndpointInfo
-	tcpip.DefaultSocketOptionsHandler
-
-	proto    *fakeTransportProtocol
-	peerAddr tcpip.Address
-	route    *stack.Route
-	uniqueID uint64
-
-	// acceptQueue is non-nil iff bound.
-	acceptQueue []*fakeTransportEndpoint
-
-	// ops is used to set and get socket options.
-	ops tcpip.SocketOptions
-}
-
-func (f *fakeTransportEndpoint) Info() tcpip.EndpointInfo {
-	return &f.TransportEndpointInfo
-}
-
-func (*fakeTransportEndpoint) Stats() tcpip.EndpointStats {
-	return nil
-}
-
-func (*fakeTransportEndpoint) SetOwner(owner tcpip.PacketOwner) {}
-
-func (f *fakeTransportEndpoint) SocketOptions() *tcpip.SocketOptions {
-	return &f.ops
-}
-
-func newFakeTransportEndpoint(proto *fakeTransportProtocol, netProto tcpip.NetworkProtocolNumber, s *stack.Stack) tcpip.Endpoint {
-	ep := &fakeTransportEndpoint{TransportEndpointInfo: stack.TransportEndpointInfo{NetProto: netProto}, proto: proto, uniqueID: s.UniqueID()}
-	ep.ops.InitHandler(ep, s, tcpip.GetStackSendBufferLimits)
-	return ep
-}
-
-func (f *fakeTransportEndpoint) Abort() {
-	f.Close()
-}
-
-func (f *fakeTransportEndpoint) Close() {
-	// TODO(gvisor.dev/issue/5153): Consider retaining the route.
-	f.route.Release()
-}
-
-func (*fakeTransportEndpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
-	return mask
-}
-
-func (*fakeTransportEndpoint) Read(io.Writer, tcpip.ReadOptions) (tcpip.ReadResult, tcpip.Error) {
-	return tcpip.ReadResult{}, nil
-}
-
-func (f *fakeTransportEndpoint) Write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, tcpip.Error) {
-	if len(f.route.RemoteAddress()) == 0 {
-		return 0, &tcpip.ErrNoRoute{}
-	}
-
-	v := make([]byte, p.Len())
-	if _, err := io.ReadFull(p, v); err != nil {
-		return 0, &tcpip.ErrBadBuffer{}
-	}
-
-	pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
-		ReserveHeaderBytes: int(f.route.MaxHeaderLength()) + fakeTransHeaderLen,
-		Data:               buffer.View(v).ToVectorisedView(),
-	})
-	_ = pkt.TransportHeader().Push(fakeTransHeaderLen)
-	if err := f.route.WritePacket(nil /* gso */, stack.NetworkHeaderParams{Protocol: fakeTransNumber, TTL: 123, TOS: stack.DefaultTOS}, pkt); err != nil {
-		return 0, err
-	}
-
-	return int64(len(v)), nil
-}
-
-// SetSockOpt sets a socket option. Currently not supported.
-func (*fakeTransportEndpoint) SetSockOpt(tcpip.SettableSocketOption) tcpip.Error {
-	return &tcpip.ErrInvalidEndpointState{}
-}
-
-// SetSockOptInt sets a socket option. Currently not supported.
-func (*fakeTransportEndpoint) SetSockOptInt(tcpip.SockOptInt, int) tcpip.Error {
-	return &tcpip.ErrInvalidEndpointState{}
-}
-
-// GetSockOptInt implements tcpip.Endpoint.GetSockOptInt.
-func (*fakeTransportEndpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
-	return -1, &tcpip.ErrUnknownProtocolOption{}
-}
-
-// GetSockOpt implements tcpip.Endpoint.GetSockOpt.
-func (*fakeTransportEndpoint) GetSockOpt(tcpip.GettableSocketOption) tcpip.Error {
-	return &tcpip.ErrInvalidEndpointState{}
-}
-
-// Disconnect implements tcpip.Endpoint.Disconnect.
-func (*fakeTransportEndpoint) Disconnect() tcpip.Error {
-	return &tcpip.ErrNotSupported{}
-}
-
-func (f *fakeTransportEndpoint) Connect(addr tcpip.FullAddress) tcpip.Error {
-	f.peerAddr = addr.Addr
-
-	// Find the route.
-	r, err := f.proto.stack.FindRoute(addr.NIC, "", addr.Addr, fakeNetNumber, false /* multicastLoop */)
-	if err != nil {
-		return &tcpip.ErrNoRoute{}
-	}
-
-	// Try to register so that we can start receiving packets.
-	f.ID.RemoteAddress = addr.Addr
-	err = f.proto.stack.RegisterTransportEndpoint([]tcpip.NetworkProtocolNumber{fakeNetNumber}, fakeTransNumber, f.ID, f, ports.Flags{}, 0 /* bindToDevice */)
-	if err != nil {
-		r.Release()
-		return err
-	}
-
-	f.route = r
-
-	return nil
-}
-
-func (f *fakeTransportEndpoint) UniqueID() uint64 {
-	return f.uniqueID
-}
-
-func (*fakeTransportEndpoint) ConnectEndpoint(e tcpip.Endpoint) tcpip.Error {
-	return nil
-}
-
-func (*fakeTransportEndpoint) Shutdown(tcpip.ShutdownFlags) tcpip.Error {
-	return nil
-}
-
-func (*fakeTransportEndpoint) Reset() {
-}
-
-func (*fakeTransportEndpoint) Listen(int) tcpip.Error {
-	return nil
-}
-
-func (f *fakeTransportEndpoint) Accept(*tcpip.FullAddress) (tcpip.Endpoint, *waiter.Queue, tcpip.Error) {
-	if len(f.acceptQueue) == 0 {
-		return nil, nil, nil
-	}
-	a := f.acceptQueue[0]
-	f.acceptQueue = f.acceptQueue[1:]
-	return a, nil, nil
-}
-
-func (f *fakeTransportEndpoint) Bind(a tcpip.FullAddress) tcpip.Error {
-	if err := f.proto.stack.RegisterTransportEndpoint(
-		[]tcpip.NetworkProtocolNumber{fakeNetNumber},
-		fakeTransNumber,
-		stack.TransportEndpointID{LocalAddress: a.Addr},
-		f,
-		ports.Flags{},
-		0, /* bindtoDevice */
-	); err != nil {
-		return err
-	}
-	f.acceptQueue = []*fakeTransportEndpoint{}
-	return nil
-}
-
-func (*fakeTransportEndpoint) GetLocalAddress() (tcpip.FullAddress, tcpip.Error) {
-	return tcpip.FullAddress{}, nil
-}
-
-func (*fakeTransportEndpoint) GetRemoteAddress() (tcpip.FullAddress, tcpip.Error) {
-	return tcpip.FullAddress{}, nil
-}
-
-func (f *fakeTransportEndpoint) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) {
-	// Increment the number of received packets.
-	f.proto.packetCount++
-	if f.acceptQueue == nil {
-		return
-	}
-
-	netHdr := pkt.NetworkHeader().View()
-	route, err := f.proto.stack.FindRoute(pkt.NICID, tcpip.Address(netHdr[dstAddrOffset]), tcpip.Address(netHdr[srcAddrOffset]), pkt.NetworkProtocolNumber, false /* multicastLoop */)
-	if err != nil {
-		return
-	}
-
-	ep := &fakeTransportEndpoint{
-		TransportEndpointInfo: stack.TransportEndpointInfo{
-			ID:       f.ID,
-			NetProto: f.NetProto,
-		},
-		proto:    f.proto,
-		peerAddr: route.RemoteAddress(),
-		route:    route,
-	}
-	ep.ops.InitHandler(ep, f.proto.stack, tcpip.GetStackSendBufferLimits)
-	f.acceptQueue = append(f.acceptQueue, ep)
-}
-
-func (f *fakeTransportEndpoint) HandleError(stack.TransportError, *stack.PacketBuffer) {
-	// Increment the number of received control packets.
-	f.proto.controlCount++
-}
-
-func (*fakeTransportEndpoint) State() uint32 {
-	return 0
-}
-
-func (*fakeTransportEndpoint) ModerateRecvBuf(copied int) {}
-
-func (*fakeTransportEndpoint) Resume(*stack.Stack) {}
-
-func (*fakeTransportEndpoint) Wait() {}
-
-func (*fakeTransportEndpoint) LastError() tcpip.Error {
-	return nil
-}
-
-type fakeTransportGoodOption bool
-
-type fakeTransportBadOption bool
-
-type fakeTransportInvalidValueOption int
-
-type fakeTransportProtocolOptions struct {
-	good bool
-}
-
-// fakeTransportProtocol is a transport-layer protocol descriptor. It
-// aggregates the number of packets received via endpoints of this protocol.
-type fakeTransportProtocol struct {
-	stack *stack.Stack
-
-	packetCount  int
-	controlCount int
-	opts         fakeTransportProtocolOptions
-}
-
-func (*fakeTransportProtocol) Number() tcpip.TransportProtocolNumber {
-	return fakeTransNumber
-}
-
-func (f *fakeTransportProtocol) NewEndpoint(netProto tcpip.NetworkProtocolNumber, _ *waiter.Queue) (tcpip.Endpoint, tcpip.Error) {
-	return newFakeTransportEndpoint(f, netProto, f.stack), nil
-}
-
-func (*fakeTransportProtocol) NewRawEndpoint(tcpip.NetworkProtocolNumber, *waiter.Queue) (tcpip.Endpoint, tcpip.Error) {
-	return nil, &tcpip.ErrUnknownProtocol{}
-}
-
-func (*fakeTransportProtocol) MinimumPacketSize() int {
-	return fakeTransHeaderLen
-}
-
-func (*fakeTransportProtocol) ParsePorts(buffer.View) (src, dst uint16, err tcpip.Error) {
-	return 0, 0, nil
-}
-
-func (*fakeTransportProtocol) HandleUnknownDestinationPacket(stack.TransportEndpointID, *stack.PacketBuffer) stack.UnknownDestinationPacketDisposition {
-	return stack.UnknownDestinationPacketHandled
-}
-
-func (f *fakeTransportProtocol) SetOption(option tcpip.SettableTransportProtocolOption) tcpip.Error {
-	switch v := option.(type) {
-	case *tcpip.TCPModerateReceiveBufferOption:
-		f.opts.good = bool(*v)
-		return nil
-	default:
-		return &tcpip.ErrUnknownProtocolOption{}
-	}
-}
-
-func (f *fakeTransportProtocol) Option(option tcpip.GettableTransportProtocolOption) tcpip.Error {
-	switch v := option.(type) {
-	case *tcpip.TCPModerateReceiveBufferOption:
-		*v = tcpip.TCPModerateReceiveBufferOption(f.opts.good)
-		return nil
-	default:
-		return &tcpip.ErrUnknownProtocolOption{}
-	}
-}
-
-// Abort implements TransportProtocol.Abort.
-func (*fakeTransportProtocol) Abort() {}
-
-// Close implements tcpip.Endpoint.Close.
-func (*fakeTransportProtocol) Close() {}
-
-// Wait implements TransportProtocol.Wait.
-func (*fakeTransportProtocol) Wait() {}
-
-// Parse implements TransportProtocol.Parse.
-func (*fakeTransportProtocol) Parse(pkt *stack.PacketBuffer) bool {
-	_, ok := pkt.TransportHeader().Consume(fakeTransHeaderLen)
-	return ok
-}
-
-func fakeTransFactory(s *stack.Stack) stack.TransportProtocol {
-	return &fakeTransportProtocol{stack: s}
-}
-
-func TestTransportReceive(t *testing.T) {
-	linkEP := channel.New(10, defaultMTU, "")
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{fakeNetFactory},
-		TransportProtocols: []stack.TransportProtocolFactory{fakeTransFactory},
-	})
-	if err := s.CreateNIC(1, linkEP); err != nil {
-		t.Fatalf("CreateNIC failed: %v", err)
-	}
-
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
-		t.Fatalf("AddAddress failed: %v", err)
-	}
-
-	// Create endpoint and connect to remote address.
-	wq := waiter.Queue{}
-	ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %v", err)
-	}
-
-	if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
-		t.Fatalf("Connect failed: %v", err)
-	}
-
-	fakeTrans := s.TransportProtocolInstance(fakeTransNumber).(*fakeTransportProtocol)
-
-	// Create buffer that will hold the packet.
-	buf := buffer.NewView(30)
-
-	// Make sure packet with wrong protocol is not delivered.
-	buf[0] = 1
-	buf[2] = 0
-	linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeTrans.packetCount != 0 {
-		t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 0)
-	}
-
-	// Make sure packet from the wrong source is not delivered.
-	buf[0] = 1
-	buf[1] = 3
-	buf[2] = byte(fakeTransNumber)
-	linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeTrans.packetCount != 0 {
-		t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 0)
-	}
-
-	// Make sure packet is delivered.
-	buf[0] = 1
-	buf[1] = 2
-	buf[2] = byte(fakeTransNumber)
-	linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeTrans.packetCount != 1 {
-		t.Errorf("packetCount = %d, want %d", fakeTrans.packetCount, 1)
-	}
-}
-
-func TestTransportControlReceive(t *testing.T) {
-	linkEP := channel.New(10, defaultMTU, "")
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{fakeNetFactory},
-		TransportProtocols: []stack.TransportProtocolFactory{fakeTransFactory},
-	})
-	if err := s.CreateNIC(1, linkEP); err != nil {
-		t.Fatalf("CreateNIC failed: %v", err)
-	}
-
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
-		t.Fatalf("AddAddress failed: %v", err)
-	}
-
-	// Create endpoint and connect to remote address.
-	wq := waiter.Queue{}
-	ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %v", err)
-	}
-
-	if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
-		t.Fatalf("Connect failed: %v", err)
-	}
-
-	fakeTrans := s.TransportProtocolInstance(fakeTransNumber).(*fakeTransportProtocol)
-
-	// Create buffer that will hold the control packet.
-	buf := buffer.NewView(2*fakeNetHeaderLen + 30)
-
-	// Outer packet contains the control protocol number.
-	buf[0] = 1
-	buf[1] = 0xfe
-	buf[2] = uint8(fakeControlProtocol)
-
-	// Make sure packet with wrong protocol is not delivered.
-	buf[fakeNetHeaderLen+0] = 0
-	buf[fakeNetHeaderLen+1] = 1
-	buf[fakeNetHeaderLen+2] = 0
-	linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeTrans.controlCount != 0 {
-		t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 0)
-	}
-
-	// Make sure packet from the wrong source is not delivered.
-	buf[fakeNetHeaderLen+0] = 3
-	buf[fakeNetHeaderLen+1] = 1
-	buf[fakeNetHeaderLen+2] = byte(fakeTransNumber)
-	linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeTrans.controlCount != 0 {
-		t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 0)
-	}
-
-	// Make sure packet is delivered.
-	buf[fakeNetHeaderLen+0] = 2
-	buf[fakeNetHeaderLen+1] = 1
-	buf[fakeNetHeaderLen+2] = byte(fakeTransNumber)
-	linkEP.InjectInbound(fakeNetNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-	if fakeTrans.controlCount != 1 {
-		t.Errorf("controlCount = %d, want %d", fakeTrans.controlCount, 1)
-	}
-}
-
-func TestTransportSend(t *testing.T) {
-	linkEP := channel.New(10, defaultMTU, "")
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{fakeNetFactory},
-		TransportProtocols: []stack.TransportProtocolFactory{fakeTransFactory},
-	})
-	if err := s.CreateNIC(1, linkEP); err != nil {
-		t.Fatalf("CreateNIC failed: %v", err)
-	}
-
-	if err := s.AddAddress(1, fakeNetNumber, "\x01"); err != nil {
-		t.Fatalf("AddAddress failed: %v", err)
-	}
-
-	{
-		subnet, err := tcpip.NewSubnet("\x00", "\x00")
-		if err != nil {
-			t.Fatal(err)
-		}
-		s.SetRouteTable([]tcpip.Route{{Destination: subnet, Gateway: "\x00", NIC: 1}})
-	}
-
-	// Create endpoint and bind it.
-	wq := waiter.Queue{}
-	ep, err := s.NewEndpoint(fakeTransNumber, fakeNetNumber, &wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %v", err)
-	}
-
-	if err := ep.Connect(tcpip.FullAddress{0, "\x02", 0}); err != nil {
-		t.Fatalf("Connect failed: %v", err)
-	}
-
-	// Create buffer that will hold the payload.
-	b := make([]byte, 30)
-	var r bytes.Reader
-	r.Reset(b)
-	if _, err := ep.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("write failed: %v", err)
-	}
-
-	fakeNet := s.NetworkProtocolInstance(fakeNetNumber).(*fakeNetworkProtocol)
-
-	if fakeNet.sendPacketCount[2] != 1 {
-		t.Errorf("sendPacketCount = %d, want %d", fakeNet.sendPacketCount[2], 1)
-	}
-}
-
-func TestTransportOptions(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{fakeNetFactory},
-		TransportProtocols: []stack.TransportProtocolFactory{fakeTransFactory},
-	})
-
-	v := tcpip.TCPModerateReceiveBufferOption(true)
-	if err := s.SetTransportProtocolOption(fakeTransNumber, &v); err != nil {
-		t.Errorf("s.SetTransportProtocolOption(fakeTrans, &%T(%t)): %s", v, v, err)
-	}
-	v = false
-	if err := s.TransportProtocolOption(fakeTransNumber, &v); err != nil {
-		t.Fatalf("s.TransportProtocolOption(fakeTransNumber, &%T): %s", v, err)
-	}
-	if !v {
-		t.Fatalf("got tcpip.TCPModerateReceiveBufferOption = false, want = true")
-	}
-}
diff --git a/pkg/tcpip/stack/tuple_list.go b/pkg/tcpip/stack/tuple_list.go
new file mode 100644
index 0000000..31d0fee
--- /dev/null
+++ b/pkg/tcpip/stack/tuple_list.go
@@ -0,0 +1,221 @@
+package stack
+
+// 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 tupleElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (tupleElementMapper) linkerFor(elem *tuple) *tuple { 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 tupleList struct {
+	head *tuple
+	tail *tuple
+}
+
+// Reset resets list l to the empty state.
+func (l *tupleList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *tupleList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *tupleList) Front() *tuple {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *tupleList) Back() *tuple {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *tupleList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (tupleElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *tupleList) PushFront(e *tuple) {
+	linker := tupleElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		tupleElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *tupleList) PushBack(e *tuple) {
+	linker := tupleElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		tupleElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *tupleList) PushBackList(m *tupleList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		tupleElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		tupleElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *tupleList) InsertAfter(b, e *tuple) {
+	bLinker := tupleElementMapper{}.linkerFor(b)
+	eLinker := tupleElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		tupleElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *tupleList) InsertBefore(a, e *tuple) {
+	aLinker := tupleElementMapper{}.linkerFor(a)
+	eLinker := tupleElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		tupleElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *tupleList) Remove(e *tuple) {
+	linker := tupleElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		tupleElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		tupleElementMapper{}.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 tupleEntry struct {
+	next *tuple
+	prev *tuple
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *tupleEntry) Next() *tuple {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *tupleEntry) Prev() *tuple {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *tupleEntry) SetNext(elem *tuple) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *tupleEntry) SetPrev(elem *tuple) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/tcpip_state_autogen.go b/pkg/tcpip/tcpip_state_autogen.go
new file mode 100644
index 0000000..2557f2b
--- /dev/null
+++ b/pkg/tcpip/tcpip_state_autogen.go
@@ -0,0 +1,1269 @@
+// automatically generated by stateify.
+
+package tcpip
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (e *ErrAborted) StateTypeName() string {
+	return "pkg/tcpip.ErrAborted"
+}
+
+func (e *ErrAborted) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrAborted) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrAborted) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrAborted) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrAborted) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrAddressFamilyNotSupported) StateTypeName() string {
+	return "pkg/tcpip.ErrAddressFamilyNotSupported"
+}
+
+func (e *ErrAddressFamilyNotSupported) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrAddressFamilyNotSupported) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrAddressFamilyNotSupported) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrAddressFamilyNotSupported) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrAddressFamilyNotSupported) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrAlreadyBound) StateTypeName() string {
+	return "pkg/tcpip.ErrAlreadyBound"
+}
+
+func (e *ErrAlreadyBound) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrAlreadyBound) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrAlreadyBound) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrAlreadyBound) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrAlreadyBound) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrAlreadyConnected) StateTypeName() string {
+	return "pkg/tcpip.ErrAlreadyConnected"
+}
+
+func (e *ErrAlreadyConnected) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrAlreadyConnected) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrAlreadyConnected) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrAlreadyConnected) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrAlreadyConnected) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrAlreadyConnecting) StateTypeName() string {
+	return "pkg/tcpip.ErrAlreadyConnecting"
+}
+
+func (e *ErrAlreadyConnecting) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrAlreadyConnecting) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrAlreadyConnecting) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrAlreadyConnecting) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrAlreadyConnecting) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrBadAddress) StateTypeName() string {
+	return "pkg/tcpip.ErrBadAddress"
+}
+
+func (e *ErrBadAddress) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrBadAddress) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrBadAddress) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrBadAddress) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrBadAddress) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrBadBuffer) StateTypeName() string {
+	return "pkg/tcpip.ErrBadBuffer"
+}
+
+func (e *ErrBadBuffer) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrBadBuffer) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrBadBuffer) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrBadBuffer) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrBadBuffer) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrBadLocalAddress) StateTypeName() string {
+	return "pkg/tcpip.ErrBadLocalAddress"
+}
+
+func (e *ErrBadLocalAddress) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrBadLocalAddress) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrBadLocalAddress) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrBadLocalAddress) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrBadLocalAddress) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrBroadcastDisabled) StateTypeName() string {
+	return "pkg/tcpip.ErrBroadcastDisabled"
+}
+
+func (e *ErrBroadcastDisabled) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrBroadcastDisabled) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrBroadcastDisabled) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrBroadcastDisabled) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrBroadcastDisabled) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrClosedForReceive) StateTypeName() string {
+	return "pkg/tcpip.ErrClosedForReceive"
+}
+
+func (e *ErrClosedForReceive) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrClosedForReceive) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrClosedForReceive) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrClosedForReceive) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrClosedForReceive) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrClosedForSend) StateTypeName() string {
+	return "pkg/tcpip.ErrClosedForSend"
+}
+
+func (e *ErrClosedForSend) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrClosedForSend) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrClosedForSend) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrClosedForSend) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrClosedForSend) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrConnectStarted) StateTypeName() string {
+	return "pkg/tcpip.ErrConnectStarted"
+}
+
+func (e *ErrConnectStarted) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrConnectStarted) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrConnectStarted) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrConnectStarted) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrConnectStarted) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrConnectionAborted) StateTypeName() string {
+	return "pkg/tcpip.ErrConnectionAborted"
+}
+
+func (e *ErrConnectionAborted) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrConnectionAborted) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrConnectionAborted) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrConnectionAborted) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrConnectionAborted) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrConnectionRefused) StateTypeName() string {
+	return "pkg/tcpip.ErrConnectionRefused"
+}
+
+func (e *ErrConnectionRefused) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrConnectionRefused) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrConnectionRefused) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrConnectionRefused) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrConnectionRefused) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrConnectionReset) StateTypeName() string {
+	return "pkg/tcpip.ErrConnectionReset"
+}
+
+func (e *ErrConnectionReset) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrConnectionReset) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrConnectionReset) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrConnectionReset) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrConnectionReset) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrDestinationRequired) StateTypeName() string {
+	return "pkg/tcpip.ErrDestinationRequired"
+}
+
+func (e *ErrDestinationRequired) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrDestinationRequired) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrDestinationRequired) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrDestinationRequired) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrDestinationRequired) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrDuplicateAddress) StateTypeName() string {
+	return "pkg/tcpip.ErrDuplicateAddress"
+}
+
+func (e *ErrDuplicateAddress) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrDuplicateAddress) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrDuplicateAddress) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrDuplicateAddress) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrDuplicateAddress) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrDuplicateNICID) StateTypeName() string {
+	return "pkg/tcpip.ErrDuplicateNICID"
+}
+
+func (e *ErrDuplicateNICID) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrDuplicateNICID) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrDuplicateNICID) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrDuplicateNICID) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrDuplicateNICID) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrInvalidEndpointState) StateTypeName() string {
+	return "pkg/tcpip.ErrInvalidEndpointState"
+}
+
+func (e *ErrInvalidEndpointState) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrInvalidEndpointState) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrInvalidEndpointState) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrInvalidEndpointState) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrInvalidEndpointState) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrInvalidOptionValue) StateTypeName() string {
+	return "pkg/tcpip.ErrInvalidOptionValue"
+}
+
+func (e *ErrInvalidOptionValue) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrInvalidOptionValue) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrInvalidOptionValue) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrInvalidOptionValue) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrInvalidOptionValue) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrInvalidPortRange) StateTypeName() string {
+	return "pkg/tcpip.ErrInvalidPortRange"
+}
+
+func (e *ErrInvalidPortRange) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrInvalidPortRange) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrInvalidPortRange) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrInvalidPortRange) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrInvalidPortRange) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrMalformedHeader) StateTypeName() string {
+	return "pkg/tcpip.ErrMalformedHeader"
+}
+
+func (e *ErrMalformedHeader) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrMalformedHeader) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrMalformedHeader) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrMalformedHeader) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrMalformedHeader) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrMessageTooLong) StateTypeName() string {
+	return "pkg/tcpip.ErrMessageTooLong"
+}
+
+func (e *ErrMessageTooLong) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrMessageTooLong) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrMessageTooLong) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrMessageTooLong) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrMessageTooLong) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrNetworkUnreachable) StateTypeName() string {
+	return "pkg/tcpip.ErrNetworkUnreachable"
+}
+
+func (e *ErrNetworkUnreachable) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrNetworkUnreachable) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrNetworkUnreachable) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrNetworkUnreachable) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrNetworkUnreachable) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrNoBufferSpace) StateTypeName() string {
+	return "pkg/tcpip.ErrNoBufferSpace"
+}
+
+func (e *ErrNoBufferSpace) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrNoBufferSpace) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrNoBufferSpace) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrNoBufferSpace) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrNoBufferSpace) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrNoPortAvailable) StateTypeName() string {
+	return "pkg/tcpip.ErrNoPortAvailable"
+}
+
+func (e *ErrNoPortAvailable) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrNoPortAvailable) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrNoPortAvailable) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrNoPortAvailable) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrNoPortAvailable) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrNoRoute) StateTypeName() string {
+	return "pkg/tcpip.ErrNoRoute"
+}
+
+func (e *ErrNoRoute) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrNoRoute) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrNoRoute) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrNoRoute) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrNoRoute) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrNoSuchFile) StateTypeName() string {
+	return "pkg/tcpip.ErrNoSuchFile"
+}
+
+func (e *ErrNoSuchFile) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrNoSuchFile) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrNoSuchFile) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrNoSuchFile) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrNoSuchFile) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrNotConnected) StateTypeName() string {
+	return "pkg/tcpip.ErrNotConnected"
+}
+
+func (e *ErrNotConnected) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrNotConnected) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrNotConnected) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrNotConnected) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrNotConnected) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrNotPermitted) StateTypeName() string {
+	return "pkg/tcpip.ErrNotPermitted"
+}
+
+func (e *ErrNotPermitted) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrNotPermitted) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrNotPermitted) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrNotPermitted) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrNotPermitted) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrNotSupported) StateTypeName() string {
+	return "pkg/tcpip.ErrNotSupported"
+}
+
+func (e *ErrNotSupported) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrNotSupported) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrNotSupported) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrNotSupported) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrNotSupported) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrPortInUse) StateTypeName() string {
+	return "pkg/tcpip.ErrPortInUse"
+}
+
+func (e *ErrPortInUse) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrPortInUse) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrPortInUse) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrPortInUse) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrPortInUse) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrQueueSizeNotSupported) StateTypeName() string {
+	return "pkg/tcpip.ErrQueueSizeNotSupported"
+}
+
+func (e *ErrQueueSizeNotSupported) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrQueueSizeNotSupported) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrQueueSizeNotSupported) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrQueueSizeNotSupported) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrQueueSizeNotSupported) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrTimeout) StateTypeName() string {
+	return "pkg/tcpip.ErrTimeout"
+}
+
+func (e *ErrTimeout) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrTimeout) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrTimeout) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrTimeout) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrTimeout) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrUnknownDevice) StateTypeName() string {
+	return "pkg/tcpip.ErrUnknownDevice"
+}
+
+func (e *ErrUnknownDevice) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrUnknownDevice) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrUnknownDevice) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrUnknownDevice) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrUnknownDevice) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrUnknownNICID) StateTypeName() string {
+	return "pkg/tcpip.ErrUnknownNICID"
+}
+
+func (e *ErrUnknownNICID) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrUnknownNICID) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrUnknownNICID) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrUnknownNICID) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrUnknownNICID) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrUnknownProtocol) StateTypeName() string {
+	return "pkg/tcpip.ErrUnknownProtocol"
+}
+
+func (e *ErrUnknownProtocol) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrUnknownProtocol) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrUnknownProtocol) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrUnknownProtocol) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrUnknownProtocol) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrUnknownProtocolOption) StateTypeName() string {
+	return "pkg/tcpip.ErrUnknownProtocolOption"
+}
+
+func (e *ErrUnknownProtocolOption) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrUnknownProtocolOption) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrUnknownProtocolOption) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrUnknownProtocolOption) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrUnknownProtocolOption) StateLoad(stateSourceObject state.Source) {
+}
+
+func (e *ErrWouldBlock) StateTypeName() string {
+	return "pkg/tcpip.ErrWouldBlock"
+}
+
+func (e *ErrWouldBlock) StateFields() []string {
+	return []string{}
+}
+
+func (e *ErrWouldBlock) beforeSave() {}
+
+// +checklocksignore
+func (e *ErrWouldBlock) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+}
+
+func (e *ErrWouldBlock) afterLoad() {}
+
+// +checklocksignore
+func (e *ErrWouldBlock) StateLoad(stateSourceObject state.Source) {
+}
+
+func (l *sockErrorList) StateTypeName() string {
+	return "pkg/tcpip.sockErrorList"
+}
+
+func (l *sockErrorList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *sockErrorList) beforeSave() {}
+
+// +checklocksignore
+func (l *sockErrorList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *sockErrorList) afterLoad() {}
+
+// +checklocksignore
+func (l *sockErrorList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *sockErrorEntry) StateTypeName() string {
+	return "pkg/tcpip.sockErrorEntry"
+}
+
+func (e *sockErrorEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *sockErrorEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *sockErrorEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *sockErrorEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *sockErrorEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func (so *SocketOptions) StateTypeName() string {
+	return "pkg/tcpip.SocketOptions"
+}
+
+func (so *SocketOptions) StateFields() []string {
+	return []string{
+		"handler",
+		"broadcastEnabled",
+		"passCredEnabled",
+		"noChecksumEnabled",
+		"reuseAddressEnabled",
+		"reusePortEnabled",
+		"keepAliveEnabled",
+		"multicastLoopEnabled",
+		"receiveTOSEnabled",
+		"receiveTClassEnabled",
+		"receivePacketInfoEnabled",
+		"hdrIncludedEnabled",
+		"v6OnlyEnabled",
+		"quickAckEnabled",
+		"delayOptionEnabled",
+		"corkOptionEnabled",
+		"receiveOriginalDstAddress",
+		"recvErrEnabled",
+		"errQueue",
+		"bindToDevice",
+		"sendBufferSize",
+		"linger",
+	}
+}
+
+func (so *SocketOptions) beforeSave() {}
+
+// +checklocksignore
+func (so *SocketOptions) StateSave(stateSinkObject state.Sink) {
+	so.beforeSave()
+	stateSinkObject.Save(0, &so.handler)
+	stateSinkObject.Save(1, &so.broadcastEnabled)
+	stateSinkObject.Save(2, &so.passCredEnabled)
+	stateSinkObject.Save(3, &so.noChecksumEnabled)
+	stateSinkObject.Save(4, &so.reuseAddressEnabled)
+	stateSinkObject.Save(5, &so.reusePortEnabled)
+	stateSinkObject.Save(6, &so.keepAliveEnabled)
+	stateSinkObject.Save(7, &so.multicastLoopEnabled)
+	stateSinkObject.Save(8, &so.receiveTOSEnabled)
+	stateSinkObject.Save(9, &so.receiveTClassEnabled)
+	stateSinkObject.Save(10, &so.receivePacketInfoEnabled)
+	stateSinkObject.Save(11, &so.hdrIncludedEnabled)
+	stateSinkObject.Save(12, &so.v6OnlyEnabled)
+	stateSinkObject.Save(13, &so.quickAckEnabled)
+	stateSinkObject.Save(14, &so.delayOptionEnabled)
+	stateSinkObject.Save(15, &so.corkOptionEnabled)
+	stateSinkObject.Save(16, &so.receiveOriginalDstAddress)
+	stateSinkObject.Save(17, &so.recvErrEnabled)
+	stateSinkObject.Save(18, &so.errQueue)
+	stateSinkObject.Save(19, &so.bindToDevice)
+	stateSinkObject.Save(20, &so.sendBufferSize)
+	stateSinkObject.Save(21, &so.linger)
+}
+
+func (so *SocketOptions) afterLoad() {}
+
+// +checklocksignore
+func (so *SocketOptions) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &so.handler)
+	stateSourceObject.Load(1, &so.broadcastEnabled)
+	stateSourceObject.Load(2, &so.passCredEnabled)
+	stateSourceObject.Load(3, &so.noChecksumEnabled)
+	stateSourceObject.Load(4, &so.reuseAddressEnabled)
+	stateSourceObject.Load(5, &so.reusePortEnabled)
+	stateSourceObject.Load(6, &so.keepAliveEnabled)
+	stateSourceObject.Load(7, &so.multicastLoopEnabled)
+	stateSourceObject.Load(8, &so.receiveTOSEnabled)
+	stateSourceObject.Load(9, &so.receiveTClassEnabled)
+	stateSourceObject.Load(10, &so.receivePacketInfoEnabled)
+	stateSourceObject.Load(11, &so.hdrIncludedEnabled)
+	stateSourceObject.Load(12, &so.v6OnlyEnabled)
+	stateSourceObject.Load(13, &so.quickAckEnabled)
+	stateSourceObject.Load(14, &so.delayOptionEnabled)
+	stateSourceObject.Load(15, &so.corkOptionEnabled)
+	stateSourceObject.Load(16, &so.receiveOriginalDstAddress)
+	stateSourceObject.Load(17, &so.recvErrEnabled)
+	stateSourceObject.Load(18, &so.errQueue)
+	stateSourceObject.Load(19, &so.bindToDevice)
+	stateSourceObject.Load(20, &so.sendBufferSize)
+	stateSourceObject.Load(21, &so.linger)
+}
+
+func (l *LocalSockError) StateTypeName() string {
+	return "pkg/tcpip.LocalSockError"
+}
+
+func (l *LocalSockError) StateFields() []string {
+	return []string{
+		"info",
+	}
+}
+
+func (l *LocalSockError) beforeSave() {}
+
+// +checklocksignore
+func (l *LocalSockError) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.info)
+}
+
+func (l *LocalSockError) afterLoad() {}
+
+// +checklocksignore
+func (l *LocalSockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.info)
+}
+
+func (s *SockError) StateTypeName() string {
+	return "pkg/tcpip.SockError"
+}
+
+func (s *SockError) StateFields() []string {
+	return []string{
+		"sockErrorEntry",
+		"Err",
+		"Cause",
+		"Payload",
+		"Dst",
+		"Offender",
+		"NetProto",
+	}
+}
+
+func (s *SockError) beforeSave() {}
+
+// +checklocksignore
+func (s *SockError) StateSave(stateSinkObject state.Sink) {
+	s.beforeSave()
+	stateSinkObject.Save(0, &s.sockErrorEntry)
+	stateSinkObject.Save(1, &s.Err)
+	stateSinkObject.Save(2, &s.Cause)
+	stateSinkObject.Save(3, &s.Payload)
+	stateSinkObject.Save(4, &s.Dst)
+	stateSinkObject.Save(5, &s.Offender)
+	stateSinkObject.Save(6, &s.NetProto)
+}
+
+func (s *SockError) afterLoad() {}
+
+// +checklocksignore
+func (s *SockError) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &s.sockErrorEntry)
+	stateSourceObject.Load(1, &s.Err)
+	stateSourceObject.Load(2, &s.Cause)
+	stateSourceObject.Load(3, &s.Payload)
+	stateSourceObject.Load(4, &s.Dst)
+	stateSourceObject.Load(5, &s.Offender)
+	stateSourceObject.Load(6, &s.NetProto)
+}
+
+func (f *FullAddress) StateTypeName() string {
+	return "pkg/tcpip.FullAddress"
+}
+
+func (f *FullAddress) StateFields() []string {
+	return []string{
+		"NIC",
+		"Addr",
+		"Port",
+	}
+}
+
+func (f *FullAddress) beforeSave() {}
+
+// +checklocksignore
+func (f *FullAddress) StateSave(stateSinkObject state.Sink) {
+	f.beforeSave()
+	stateSinkObject.Save(0, &f.NIC)
+	stateSinkObject.Save(1, &f.Addr)
+	stateSinkObject.Save(2, &f.Port)
+}
+
+func (f *FullAddress) afterLoad() {}
+
+// +checklocksignore
+func (f *FullAddress) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &f.NIC)
+	stateSourceObject.Load(1, &f.Addr)
+	stateSourceObject.Load(2, &f.Port)
+}
+
+func (c *ControlMessages) StateTypeName() string {
+	return "pkg/tcpip.ControlMessages"
+}
+
+func (c *ControlMessages) StateFields() []string {
+	return []string{
+		"HasTimestamp",
+		"Timestamp",
+		"HasInq",
+		"Inq",
+		"HasTOS",
+		"TOS",
+		"HasTClass",
+		"TClass",
+		"HasIPPacketInfo",
+		"PacketInfo",
+		"HasOriginalDstAddress",
+		"OriginalDstAddress",
+		"SockErr",
+	}
+}
+
+func (c *ControlMessages) beforeSave() {}
+
+// +checklocksignore
+func (c *ControlMessages) StateSave(stateSinkObject state.Sink) {
+	c.beforeSave()
+	stateSinkObject.Save(0, &c.HasTimestamp)
+	stateSinkObject.Save(1, &c.Timestamp)
+	stateSinkObject.Save(2, &c.HasInq)
+	stateSinkObject.Save(3, &c.Inq)
+	stateSinkObject.Save(4, &c.HasTOS)
+	stateSinkObject.Save(5, &c.TOS)
+	stateSinkObject.Save(6, &c.HasTClass)
+	stateSinkObject.Save(7, &c.TClass)
+	stateSinkObject.Save(8, &c.HasIPPacketInfo)
+	stateSinkObject.Save(9, &c.PacketInfo)
+	stateSinkObject.Save(10, &c.HasOriginalDstAddress)
+	stateSinkObject.Save(11, &c.OriginalDstAddress)
+	stateSinkObject.Save(12, &c.SockErr)
+}
+
+func (c *ControlMessages) afterLoad() {}
+
+// +checklocksignore
+func (c *ControlMessages) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &c.HasTimestamp)
+	stateSourceObject.Load(1, &c.Timestamp)
+	stateSourceObject.Load(2, &c.HasInq)
+	stateSourceObject.Load(3, &c.Inq)
+	stateSourceObject.Load(4, &c.HasTOS)
+	stateSourceObject.Load(5, &c.TOS)
+	stateSourceObject.Load(6, &c.HasTClass)
+	stateSourceObject.Load(7, &c.TClass)
+	stateSourceObject.Load(8, &c.HasIPPacketInfo)
+	stateSourceObject.Load(9, &c.PacketInfo)
+	stateSourceObject.Load(10, &c.HasOriginalDstAddress)
+	stateSourceObject.Load(11, &c.OriginalDstAddress)
+	stateSourceObject.Load(12, &c.SockErr)
+}
+
+func (l *LinkPacketInfo) StateTypeName() string {
+	return "pkg/tcpip.LinkPacketInfo"
+}
+
+func (l *LinkPacketInfo) StateFields() []string {
+	return []string{
+		"Protocol",
+		"PktType",
+	}
+}
+
+func (l *LinkPacketInfo) beforeSave() {}
+
+// +checklocksignore
+func (l *LinkPacketInfo) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.Protocol)
+	stateSinkObject.Save(1, &l.PktType)
+}
+
+func (l *LinkPacketInfo) afterLoad() {}
+
+// +checklocksignore
+func (l *LinkPacketInfo) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.Protocol)
+	stateSourceObject.Load(1, &l.PktType)
+}
+
+func (l *LingerOption) StateTypeName() string {
+	return "pkg/tcpip.LingerOption"
+}
+
+func (l *LingerOption) StateFields() []string {
+	return []string{
+		"Enabled",
+		"Timeout",
+	}
+}
+
+func (l *LingerOption) beforeSave() {}
+
+// +checklocksignore
+func (l *LingerOption) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.Enabled)
+	stateSinkObject.Save(1, &l.Timeout)
+}
+
+func (l *LingerOption) afterLoad() {}
+
+// +checklocksignore
+func (l *LingerOption) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.Enabled)
+	stateSourceObject.Load(1, &l.Timeout)
+}
+
+func (i *IPPacketInfo) StateTypeName() string {
+	return "pkg/tcpip.IPPacketInfo"
+}
+
+func (i *IPPacketInfo) StateFields() []string {
+	return []string{
+		"NIC",
+		"LocalAddr",
+		"DestinationAddr",
+	}
+}
+
+func (i *IPPacketInfo) beforeSave() {}
+
+// +checklocksignore
+func (i *IPPacketInfo) StateSave(stateSinkObject state.Sink) {
+	i.beforeSave()
+	stateSinkObject.Save(0, &i.NIC)
+	stateSinkObject.Save(1, &i.LocalAddr)
+	stateSinkObject.Save(2, &i.DestinationAddr)
+}
+
+func (i *IPPacketInfo) afterLoad() {}
+
+// +checklocksignore
+func (i *IPPacketInfo) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &i.NIC)
+	stateSourceObject.Load(1, &i.LocalAddr)
+	stateSourceObject.Load(2, &i.DestinationAddr)
+}
+
+func init() {
+	state.Register((*ErrAborted)(nil))
+	state.Register((*ErrAddressFamilyNotSupported)(nil))
+	state.Register((*ErrAlreadyBound)(nil))
+	state.Register((*ErrAlreadyConnected)(nil))
+	state.Register((*ErrAlreadyConnecting)(nil))
+	state.Register((*ErrBadAddress)(nil))
+	state.Register((*ErrBadBuffer)(nil))
+	state.Register((*ErrBadLocalAddress)(nil))
+	state.Register((*ErrBroadcastDisabled)(nil))
+	state.Register((*ErrClosedForReceive)(nil))
+	state.Register((*ErrClosedForSend)(nil))
+	state.Register((*ErrConnectStarted)(nil))
+	state.Register((*ErrConnectionAborted)(nil))
+	state.Register((*ErrConnectionRefused)(nil))
+	state.Register((*ErrConnectionReset)(nil))
+	state.Register((*ErrDestinationRequired)(nil))
+	state.Register((*ErrDuplicateAddress)(nil))
+	state.Register((*ErrDuplicateNICID)(nil))
+	state.Register((*ErrInvalidEndpointState)(nil))
+	state.Register((*ErrInvalidOptionValue)(nil))
+	state.Register((*ErrInvalidPortRange)(nil))
+	state.Register((*ErrMalformedHeader)(nil))
+	state.Register((*ErrMessageTooLong)(nil))
+	state.Register((*ErrNetworkUnreachable)(nil))
+	state.Register((*ErrNoBufferSpace)(nil))
+	state.Register((*ErrNoPortAvailable)(nil))
+	state.Register((*ErrNoRoute)(nil))
+	state.Register((*ErrNoSuchFile)(nil))
+	state.Register((*ErrNotConnected)(nil))
+	state.Register((*ErrNotPermitted)(nil))
+	state.Register((*ErrNotSupported)(nil))
+	state.Register((*ErrPortInUse)(nil))
+	state.Register((*ErrQueueSizeNotSupported)(nil))
+	state.Register((*ErrTimeout)(nil))
+	state.Register((*ErrUnknownDevice)(nil))
+	state.Register((*ErrUnknownNICID)(nil))
+	state.Register((*ErrUnknownProtocol)(nil))
+	state.Register((*ErrUnknownProtocolOption)(nil))
+	state.Register((*ErrWouldBlock)(nil))
+	state.Register((*sockErrorList)(nil))
+	state.Register((*sockErrorEntry)(nil))
+	state.Register((*SocketOptions)(nil))
+	state.Register((*LocalSockError)(nil))
+	state.Register((*SockError)(nil))
+	state.Register((*FullAddress)(nil))
+	state.Register((*ControlMessages)(nil))
+	state.Register((*LinkPacketInfo)(nil))
+	state.Register((*LingerOption)(nil))
+	state.Register((*IPPacketInfo)(nil))
+}
diff --git a/pkg/tcpip/tcpip_test.go b/pkg/tcpip/tcpip_test.go
deleted file mode 100644
index 269081f..0000000
--- a/pkg/tcpip/tcpip_test.go
+++ /dev/null
@@ -1,346 +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 tcpip
-
-import (
-	"bytes"
-	"fmt"
-	"io"
-	"net"
-	"strings"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-)
-
-func TestLimitedWriter_Write(t *testing.T) {
-	var b bytes.Buffer
-	l := LimitedWriter{
-		W: &b,
-		N: 5,
-	}
-	if n, err := l.Write([]byte{0, 1, 2}); err != nil {
-		t.Errorf("got l.Write(3/5) = (_, %s), want nil", err)
-	} else if n != 3 {
-		t.Errorf("got l.Write(3/5) = (%d, _), want 3", n)
-	}
-	if n, err := l.Write([]byte{3, 4, 5}); err != io.ErrShortWrite {
-		t.Errorf("got l.Write(3/2) = (_, %s), want io.ErrShortWrite", err)
-	} else if n != 2 {
-		t.Errorf("got l.Write(3/2) = (%d, _), want 2", n)
-	}
-	if l.N != 0 {
-		t.Errorf("got l.N = %d, want 0", l.N)
-	}
-	l.N = 1
-	if n, err := l.Write([]byte{5}); err != nil {
-		t.Errorf("got l.Write(1/1) = (_, %s), want nil", err)
-	} else if n != 1 {
-		t.Errorf("got l.Write(1/1) = (%d, _), want 1", n)
-	}
-	if diff := cmp.Diff(b.Bytes(), []byte{0, 1, 2, 3, 4, 5}); diff != "" {
-		t.Errorf("%T wrote incorrect data: (-want +got):\n%s", l, diff)
-	}
-}
-
-func TestSubnetContains(t *testing.T) {
-	tests := []struct {
-		s    Address
-		m    AddressMask
-		a    Address
-		want bool
-	}{
-		{"\xa0", "\xf0", "\x90", false},
-		{"\xa0", "\xf0", "\xa0", true},
-		{"\xa0", "\xf0", "\xa5", true},
-		{"\xa0", "\xf0", "\xaf", true},
-		{"\xa0", "\xf0", "\xb0", false},
-		{"\xa0", "\xf0", "", false},
-		{"\xa0", "\xf0", "\xa0\x00", false},
-		{"\xc2\x80", "\xff\xf0", "\xc2\x80", true},
-		{"\xc2\x80", "\xff\xf0", "\xc2\x00", false},
-		{"\xc2\x00", "\xff\xf0", "\xc2\x00", true},
-		{"\xc2\x00", "\xff\xf0", "\xc2\x80", false},
-	}
-	for _, tt := range tests {
-		s, err := NewSubnet(tt.s, tt.m)
-		if err != nil {
-			t.Errorf("NewSubnet(%v, %v) = %v", tt.s, tt.m, err)
-			continue
-		}
-		if got := s.Contains(tt.a); got != tt.want {
-			t.Errorf("Subnet(%v).Contains(%v) = %v, want %v", s, tt.a, got, tt.want)
-		}
-	}
-}
-
-func TestSubnetBits(t *testing.T) {
-	tests := []struct {
-		a     AddressMask
-		want1 int
-		want0 int
-	}{
-		{"\x00", 0, 8},
-		{"\x00\x00", 0, 16},
-		{"\x36", 0, 8},
-		{"\x5c", 0, 8},
-		{"\x5c\x5c", 0, 16},
-		{"\x5c\x36", 0, 16},
-		{"\x36\x5c", 0, 16},
-		{"\x36\x36", 0, 16},
-		{"\xff", 8, 0},
-		{"\xff\xff", 16, 0},
-	}
-	for _, tt := range tests {
-		s := &Subnet{mask: tt.a}
-		got1, got0 := s.Bits()
-		if got1 != tt.want1 || got0 != tt.want0 {
-			t.Errorf("Subnet{mask: %x}.Bits() = %d, %d, want %d, %d", tt.a, got1, got0, tt.want1, tt.want0)
-		}
-	}
-}
-
-func TestSubnetPrefix(t *testing.T) {
-	tests := []struct {
-		a    AddressMask
-		want int
-	}{
-		{"\x00", 0},
-		{"\x00\x00", 0},
-		{"\x36", 0},
-		{"\x86", 1},
-		{"\xc5", 2},
-		{"\xff\x00", 8},
-		{"\xff\x36", 8},
-		{"\xff\x8c", 9},
-		{"\xff\xc8", 10},
-		{"\xff", 8},
-		{"\xff\xff", 16},
-	}
-	for _, tt := range tests {
-		s := &Subnet{mask: tt.a}
-		got := s.Prefix()
-		if got != tt.want {
-			t.Errorf("Subnet{mask: %x}.Bits() = %d want %d", tt.a, got, tt.want)
-		}
-	}
-}
-
-func TestSubnetCreation(t *testing.T) {
-	tests := []struct {
-		a    Address
-		m    AddressMask
-		want error
-	}{
-		{"\xa0", "\xf0", nil},
-		{"\xa0\xa0", "\xf0", errSubnetLengthMismatch},
-		{"\xaa", "\xf0", errSubnetAddressMasked},
-		{"", "", nil},
-	}
-	for _, tt := range tests {
-		if _, err := NewSubnet(tt.a, tt.m); err != tt.want {
-			t.Errorf("NewSubnet(%v, %v) = %v, want %v", tt.a, tt.m, err, tt.want)
-		}
-	}
-}
-
-func TestAddressString(t *testing.T) {
-	for _, want := range []string{
-		// Taken from stdlib.
-		"2001:db8::123:12:1",
-		"2001:db8::1",
-		"2001:db8:0:1:0:1:0:1",
-		"2001:db8:1:0:1:0:1:0",
-		"2001::1:0:0:1",
-		"2001:db8:0:0:1::",
-		"2001:db8::1:0:0:1",
-		"2001:db8::a:b:c:d",
-
-		// Leading zeros.
-		"::1",
-		// Trailing zeros.
-		"8::",
-		// No zeros.
-		"1:1:1:1:1:1:1:1",
-		// Longer sequence is after other zeros, but not at the end.
-		"1:0:0:1::1",
-		// Longer sequence is at the beginning, shorter sequence is at
-		// the end.
-		"::1:1:1:0:0",
-		// Longer sequence is not at the beginning, shorter sequence is
-		// at the end.
-		"1::1:1:0:0",
-		// Longer sequence is at the beginning, shorter sequence is not
-		// at the end.
-		"::1:1:0:0:1",
-		// Neither sequence is at an end, longer is after shorter.
-		"1:0:0:1::1",
-		// Shorter sequence is at the beginning, longer sequence is not
-		// at the end.
-		"0:0:1:1::1",
-		// Shorter sequence is at the beginning, longer sequence is at
-		// the end.
-		"0:0:1:1:1::",
-		// Short sequences at both ends, longer one in the middle.
-		"0:1:1::1:1:0",
-		// Short sequences at both ends, longer one in the middle.
-		"0:1::1:0:0",
-		// Short sequences at both ends, longer one in the middle.
-		"0:0:1::1:0",
-		// Longer sequence surrounded by shorter sequences, but none at
-		// the end.
-		"1:0:1::1:0:1",
-	} {
-		addr := Address(net.ParseIP(want))
-		if got := addr.String(); got != want {
-			t.Errorf("Address(%x).String() = '%s', want = '%s'", addr, got, want)
-		}
-	}
-}
-
-func TestStatsString(t *testing.T) {
-	got := fmt.Sprintf("%+v", Stats{}.FillIn())
-
-	matchers := []string{
-		// Print root-level stats correctly.
-		"UnknownProtocolRcvdPackets:0",
-		// Print protocol-specific stats correctly.
-		"TCP:{ActiveConnectionOpenings:0",
-	}
-
-	for _, m := range matchers {
-		if !strings.Contains(got, m) {
-			t.Errorf("string.Contains(got, %q) = false", m)
-		}
-	}
-	if t.Failed() {
-		t.Logf(`got = fmt.Sprintf("%%+v", Stats{}.FillIn()) = %q`, got)
-	}
-}
-
-func TestAddressWithPrefixSubnet(t *testing.T) {
-	tests := []struct {
-		addr       Address
-		prefixLen  int
-		subnetAddr Address
-		subnetMask AddressMask
-	}{
-		{"\xaa\x55\x33\x42", -1, "\x00\x00\x00\x00", "\x00\x00\x00\x00"},
-		{"\xaa\x55\x33\x42", 0, "\x00\x00\x00\x00", "\x00\x00\x00\x00"},
-		{"\xaa\x55\x33\x42", 1, "\x80\x00\x00\x00", "\x80\x00\x00\x00"},
-		{"\xaa\x55\x33\x42", 7, "\xaa\x00\x00\x00", "\xfe\x00\x00\x00"},
-		{"\xaa\x55\x33\x42", 8, "\xaa\x00\x00\x00", "\xff\x00\x00\x00"},
-		{"\xaa\x55\x33\x42", 24, "\xaa\x55\x33\x00", "\xff\xff\xff\x00"},
-		{"\xaa\x55\x33\x42", 31, "\xaa\x55\x33\x42", "\xff\xff\xff\xfe"},
-		{"\xaa\x55\x33\x42", 32, "\xaa\x55\x33\x42", "\xff\xff\xff\xff"},
-		{"\xaa\x55\x33\x42", 33, "\xaa\x55\x33\x42", "\xff\xff\xff\xff"},
-	}
-	for _, tt := range tests {
-		ap := AddressWithPrefix{Address: tt.addr, PrefixLen: tt.prefixLen}
-		gotSubnet := ap.Subnet()
-		wantSubnet, err := NewSubnet(tt.subnetAddr, tt.subnetMask)
-		if err != nil {
-			t.Errorf("NewSubnet(%q, %q) failed: %s", tt.subnetAddr, tt.subnetMask, err)
-			continue
-		}
-		if gotSubnet != wantSubnet {
-			t.Errorf("got subnet = %q, want = %q", gotSubnet, wantSubnet)
-		}
-	}
-}
-
-func TestAddressUnspecified(t *testing.T) {
-	tests := []struct {
-		addr        Address
-		unspecified bool
-	}{
-		{
-			addr:        "",
-			unspecified: true,
-		},
-		{
-			addr:        "\x00",
-			unspecified: true,
-		},
-		{
-			addr:        "\x01",
-			unspecified: false,
-		},
-		{
-			addr:        "\x00\x00",
-			unspecified: true,
-		},
-		{
-			addr:        "\x01\x00",
-			unspecified: false,
-		},
-		{
-			addr:        "\x00\x01",
-			unspecified: false,
-		},
-		{
-			addr:        "\x01\x01",
-			unspecified: false,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(fmt.Sprintf("addr=%s", test.addr), func(t *testing.T) {
-			if got := test.addr.Unspecified(); got != test.unspecified {
-				t.Fatalf("got addr.Unspecified() = %t, want = %t", got, test.unspecified)
-			}
-		})
-	}
-}
-
-func TestAddressMatchingPrefix(t *testing.T) {
-	tests := []struct {
-		addrA  Address
-		addrB  Address
-		prefix uint8
-	}{
-		{
-			addrA:  "\x01\x01",
-			addrB:  "\x01\x01",
-			prefix: 16,
-		},
-		{
-			addrA:  "\x01\x01",
-			addrB:  "\x01\x00",
-			prefix: 15,
-		},
-		{
-			addrA:  "\x01\x01",
-			addrB:  "\x81\x00",
-			prefix: 0,
-		},
-		{
-			addrA:  "\x01\x01",
-			addrB:  "\x01\x80",
-			prefix: 8,
-		},
-		{
-			addrA:  "\x01\x01",
-			addrB:  "\x02\x80",
-			prefix: 6,
-		},
-	}
-
-	for _, test := range tests {
-		if got := test.addrA.MatchingPrefix(test.addrB); got != test.prefix {
-			t.Errorf("got (%s).MatchingPrefix(%s) = %d, want = %d", test.addrA, test.addrB, got, test.prefix)
-		}
-	}
-}
diff --git a/pkg/tcpip/tcpip_unsafe_state_autogen.go b/pkg/tcpip/tcpip_unsafe_state_autogen.go
new file mode 100644
index 0000000..387b5aa
--- /dev/null
+++ b/pkg/tcpip/tcpip_unsafe_state_autogen.go
@@ -0,0 +1,35 @@
+// automatically generated by stateify.
+
+// +build go1.9
+// +build !go1.18
+
+package tcpip
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (s *StdClock) StateTypeName() string {
+	return "pkg/tcpip.StdClock"
+}
+
+func (s *StdClock) StateFields() []string {
+	return []string{}
+}
+
+func (s *StdClock) beforeSave() {}
+
+// +checklocksignore
+func (s *StdClock) StateSave(stateSinkObject state.Sink) {
+	s.beforeSave()
+}
+
+func (s *StdClock) afterLoad() {}
+
+// +checklocksignore
+func (s *StdClock) StateLoad(stateSourceObject state.Source) {
+}
+
+func init() {
+	state.Register((*StdClock)(nil))
+}
diff --git a/pkg/tcpip/time.s b/pkg/tcpip/time.s
deleted file mode 100644
index fb37360..0000000
--- a/pkg/tcpip/time.s
+++ /dev/null
@@ -1,15 +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.
-
-// Empty assembly file so empty func definitions work.
diff --git a/pkg/tcpip/timer_test.go b/pkg/tcpip/timer_test.go
deleted file mode 100644
index a82384c..0000000
--- a/pkg/tcpip/timer_test.go
+++ /dev/null
@@ -1,268 +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 tcpip_test
-
-import (
-	"sync"
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-)
-
-const (
-	shortDuration  = 1 * time.Nanosecond
-	middleDuration = 100 * time.Millisecond
-	longDuration   = 1 * time.Second
-)
-
-func TestJobReschedule(t *testing.T) {
-	var clock tcpip.StdClock
-	var wg sync.WaitGroup
-	var lock sync.Mutex
-
-	for i := 0; i < 2; i++ {
-		wg.Add(1)
-
-		go func() {
-			lock.Lock()
-			// Assigning a new timer value updates the timer's locker and function.
-			// This test makes sure there is no data race when reassigning a timer
-			// that has an active timer (even if it has been stopped as a stopped
-			// timer may be blocked on a lock before it can check if it has been
-			// stopped while another goroutine holds the same lock).
-			job := tcpip.NewJob(&clock, &lock, func() {
-				wg.Done()
-			})
-			job.Schedule(shortDuration)
-			lock.Unlock()
-		}()
-	}
-	wg.Wait()
-}
-
-func TestJobExecution(t *testing.T) {
-	t.Parallel()
-
-	var clock tcpip.StdClock
-	var lock sync.Mutex
-	ch := make(chan struct{})
-
-	job := tcpip.NewJob(&clock, &lock, func() {
-		ch <- struct{}{}
-	})
-	job.Schedule(shortDuration)
-
-	// Wait for timer to fire.
-	select {
-	case <-ch:
-	case <-time.After(middleDuration):
-		t.Fatal("timed out waiting for timer to fire")
-	}
-
-	// The timer should have fired only once.
-	select {
-	case <-ch:
-		t.Fatal("no other timers should have fired")
-	case <-time.After(middleDuration):
-	}
-}
-
-func TestCancellableTimerResetFromLongDuration(t *testing.T) {
-	t.Parallel()
-
-	var clock tcpip.StdClock
-	var lock sync.Mutex
-	ch := make(chan struct{})
-
-	job := tcpip.NewJob(&clock, &lock, func() { ch <- struct{}{} })
-	job.Schedule(middleDuration)
-
-	lock.Lock()
-	job.Cancel()
-	lock.Unlock()
-
-	job.Schedule(shortDuration)
-
-	// Wait for timer to fire.
-	select {
-	case <-ch:
-	case <-time.After(middleDuration):
-		t.Fatal("timed out waiting for timer to fire")
-	}
-
-	// The timer should have fired only once.
-	select {
-	case <-ch:
-		t.Fatal("no other timers should have fired")
-	case <-time.After(middleDuration):
-	}
-}
-
-func TestJobRescheduleFromShortDuration(t *testing.T) {
-	t.Parallel()
-
-	var clock tcpip.StdClock
-	var lock sync.Mutex
-	ch := make(chan struct{})
-
-	lock.Lock()
-	job := tcpip.NewJob(&clock, &lock, func() { ch <- struct{}{} })
-	job.Schedule(shortDuration)
-	job.Cancel()
-	lock.Unlock()
-
-	// Wait for timer to fire if it wasn't correctly stopped.
-	select {
-	case <-ch:
-		t.Fatal("timer fired after being stopped")
-	case <-time.After(middleDuration):
-	}
-
-	job.Schedule(shortDuration)
-
-	// Wait for timer to fire.
-	select {
-	case <-ch:
-	case <-time.After(middleDuration):
-		t.Fatal("timed out waiting for timer to fire")
-	}
-
-	// The timer should have fired only once.
-	select {
-	case <-ch:
-		t.Fatal("no other timers should have fired")
-	case <-time.After(middleDuration):
-	}
-}
-
-func TestJobImmediatelyCancel(t *testing.T) {
-	t.Parallel()
-
-	var clock tcpip.StdClock
-	var lock sync.Mutex
-	ch := make(chan struct{})
-
-	for i := 0; i < 1000; i++ {
-		lock.Lock()
-		job := tcpip.NewJob(&clock, &lock, func() { ch <- struct{}{} })
-		job.Schedule(shortDuration)
-		job.Cancel()
-		lock.Unlock()
-	}
-
-	// Wait for timer to fire if it wasn't correctly stopped.
-	select {
-	case <-ch:
-		t.Fatal("timer fired after being stopped")
-	case <-time.After(middleDuration):
-	}
-}
-
-func TestJobCancelledRescheduleWithoutLock(t *testing.T) {
-	t.Parallel()
-
-	var clock tcpip.StdClock
-	var lock sync.Mutex
-	ch := make(chan struct{})
-
-	lock.Lock()
-	job := tcpip.NewJob(&clock, &lock, func() { ch <- struct{}{} })
-	job.Schedule(shortDuration)
-	job.Cancel()
-	lock.Unlock()
-
-	for i := 0; i < 10; i++ {
-		job.Schedule(middleDuration)
-
-		lock.Lock()
-		// Sleep until the timer fires and gets blocked trying to take the lock.
-		time.Sleep(middleDuration * 2)
-		job.Cancel()
-		lock.Unlock()
-	}
-
-	// Wait for double the duration so timers that weren't correctly stopped can
-	// fire.
-	select {
-	case <-ch:
-		t.Fatal("timer fired after being stopped")
-	case <-time.After(middleDuration * 2):
-	}
-}
-
-func TestManyCancellableTimerResetAfterBlockedOnLock(t *testing.T) {
-	t.Parallel()
-
-	var clock tcpip.StdClock
-	var lock sync.Mutex
-	ch := make(chan struct{})
-
-	lock.Lock()
-	job := tcpip.NewJob(&clock, &lock, func() { ch <- struct{}{} })
-	job.Schedule(shortDuration)
-	for i := 0; i < 10; i++ {
-		// Sleep until the timer fires and gets blocked trying to take the lock.
-		time.Sleep(middleDuration)
-		job.Cancel()
-		job.Schedule(shortDuration)
-	}
-	lock.Unlock()
-
-	// Wait for double the duration for the last timer to fire.
-	select {
-	case <-ch:
-	case <-time.After(middleDuration):
-		t.Fatal("timed out waiting for timer to fire")
-	}
-
-	// The timer should have fired only once.
-	select {
-	case <-ch:
-		t.Fatal("no other timers should have fired")
-	case <-time.After(middleDuration):
-	}
-}
-
-func TestManyJobReschedulesUnderLock(t *testing.T) {
-	t.Parallel()
-
-	var clock tcpip.StdClock
-	var lock sync.Mutex
-	ch := make(chan struct{})
-
-	lock.Lock()
-	job := tcpip.NewJob(&clock, &lock, func() { ch <- struct{}{} })
-	job.Schedule(shortDuration)
-	for i := 0; i < 10; i++ {
-		job.Cancel()
-		job.Schedule(shortDuration)
-	}
-	lock.Unlock()
-
-	// Wait for double the duration for the last timer to fire.
-	select {
-	case <-ch:
-	case <-time.After(middleDuration):
-		t.Fatal("timed out waiting for timer to fire")
-	}
-
-	// The timer should have fired only once.
-	select {
-	case <-ch:
-		t.Fatal("no other timers should have fired")
-	case <-time.After(middleDuration):
-	}
-}
diff --git a/pkg/tcpip/transport/icmp/icmp_packet_list.go b/pkg/tcpip/transport/icmp/icmp_packet_list.go
new file mode 100644
index 0000000..0aacdad
--- /dev/null
+++ b/pkg/tcpip/transport/icmp/icmp_packet_list.go
@@ -0,0 +1,221 @@
+package icmp
+
+// 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 icmpPacketElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (icmpPacketElementMapper) linkerFor(elem *icmpPacket) *icmpPacket { 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 icmpPacketList struct {
+	head *icmpPacket
+	tail *icmpPacket
+}
+
+// Reset resets list l to the empty state.
+func (l *icmpPacketList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *icmpPacketList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *icmpPacketList) Front() *icmpPacket {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *icmpPacketList) Back() *icmpPacket {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *icmpPacketList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (icmpPacketElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *icmpPacketList) PushFront(e *icmpPacket) {
+	linker := icmpPacketElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		icmpPacketElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *icmpPacketList) PushBack(e *icmpPacket) {
+	linker := icmpPacketElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		icmpPacketElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *icmpPacketList) PushBackList(m *icmpPacketList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		icmpPacketElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		icmpPacketElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *icmpPacketList) InsertAfter(b, e *icmpPacket) {
+	bLinker := icmpPacketElementMapper{}.linkerFor(b)
+	eLinker := icmpPacketElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		icmpPacketElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *icmpPacketList) InsertBefore(a, e *icmpPacket) {
+	aLinker := icmpPacketElementMapper{}.linkerFor(a)
+	eLinker := icmpPacketElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		icmpPacketElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *icmpPacketList) Remove(e *icmpPacket) {
+	linker := icmpPacketElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		icmpPacketElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		icmpPacketElementMapper{}.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 icmpPacketEntry struct {
+	next *icmpPacket
+	prev *icmpPacket
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *icmpPacketEntry) Next() *icmpPacket {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *icmpPacketEntry) Prev() *icmpPacket {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *icmpPacketEntry) SetNext(elem *icmpPacket) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *icmpPacketEntry) SetPrev(elem *icmpPacket) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/transport/icmp/icmp_state_autogen.go b/pkg/tcpip/transport/icmp/icmp_state_autogen.go
new file mode 100644
index 0000000..4175f0f
--- /dev/null
+++ b/pkg/tcpip/transport/icmp/icmp_state_autogen.go
@@ -0,0 +1,168 @@
+// automatically generated by stateify.
+
+package icmp
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+	"gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (p *icmpPacket) StateTypeName() string {
+	return "pkg/tcpip/transport/icmp.icmpPacket"
+}
+
+func (p *icmpPacket) StateFields() []string {
+	return []string{
+		"icmpPacketEntry",
+		"senderAddress",
+		"data",
+		"timestamp",
+	}
+}
+
+func (p *icmpPacket) beforeSave() {}
+
+// +checklocksignore
+func (p *icmpPacket) StateSave(stateSinkObject state.Sink) {
+	p.beforeSave()
+	var dataValue buffer.VectorisedView = p.saveData()
+	stateSinkObject.SaveValue(2, dataValue)
+	stateSinkObject.Save(0, &p.icmpPacketEntry)
+	stateSinkObject.Save(1, &p.senderAddress)
+	stateSinkObject.Save(3, &p.timestamp)
+}
+
+func (p *icmpPacket) afterLoad() {}
+
+// +checklocksignore
+func (p *icmpPacket) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &p.icmpPacketEntry)
+	stateSourceObject.Load(1, &p.senderAddress)
+	stateSourceObject.Load(3, &p.timestamp)
+	stateSourceObject.LoadValue(2, new(buffer.VectorisedView), func(y interface{}) { p.loadData(y.(buffer.VectorisedView)) })
+}
+
+func (e *endpoint) StateTypeName() string {
+	return "pkg/tcpip/transport/icmp.endpoint"
+}
+
+func (e *endpoint) StateFields() []string {
+	return []string{
+		"TransportEndpointInfo",
+		"DefaultSocketOptionsHandler",
+		"waiterQueue",
+		"uniqueID",
+		"rcvReady",
+		"rcvList",
+		"rcvBufSizeMax",
+		"rcvBufSize",
+		"rcvClosed",
+		"shutdownFlags",
+		"state",
+		"ttl",
+		"owner",
+		"ops",
+	}
+}
+
+// +checklocksignore
+func (e *endpoint) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	var rcvBufSizeMaxValue int = e.saveRcvBufSizeMax()
+	stateSinkObject.SaveValue(6, rcvBufSizeMaxValue)
+	stateSinkObject.Save(0, &e.TransportEndpointInfo)
+	stateSinkObject.Save(1, &e.DefaultSocketOptionsHandler)
+	stateSinkObject.Save(2, &e.waiterQueue)
+	stateSinkObject.Save(3, &e.uniqueID)
+	stateSinkObject.Save(4, &e.rcvReady)
+	stateSinkObject.Save(5, &e.rcvList)
+	stateSinkObject.Save(7, &e.rcvBufSize)
+	stateSinkObject.Save(8, &e.rcvClosed)
+	stateSinkObject.Save(9, &e.shutdownFlags)
+	stateSinkObject.Save(10, &e.state)
+	stateSinkObject.Save(11, &e.ttl)
+	stateSinkObject.Save(12, &e.owner)
+	stateSinkObject.Save(13, &e.ops)
+}
+
+// +checklocksignore
+func (e *endpoint) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.TransportEndpointInfo)
+	stateSourceObject.Load(1, &e.DefaultSocketOptionsHandler)
+	stateSourceObject.Load(2, &e.waiterQueue)
+	stateSourceObject.Load(3, &e.uniqueID)
+	stateSourceObject.Load(4, &e.rcvReady)
+	stateSourceObject.Load(5, &e.rcvList)
+	stateSourceObject.Load(7, &e.rcvBufSize)
+	stateSourceObject.Load(8, &e.rcvClosed)
+	stateSourceObject.Load(9, &e.shutdownFlags)
+	stateSourceObject.Load(10, &e.state)
+	stateSourceObject.Load(11, &e.ttl)
+	stateSourceObject.Load(12, &e.owner)
+	stateSourceObject.Load(13, &e.ops)
+	stateSourceObject.LoadValue(6, new(int), func(y interface{}) { e.loadRcvBufSizeMax(y.(int)) })
+	stateSourceObject.AfterLoad(e.afterLoad)
+}
+
+func (l *icmpPacketList) StateTypeName() string {
+	return "pkg/tcpip/transport/icmp.icmpPacketList"
+}
+
+func (l *icmpPacketList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *icmpPacketList) beforeSave() {}
+
+// +checklocksignore
+func (l *icmpPacketList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *icmpPacketList) afterLoad() {}
+
+// +checklocksignore
+func (l *icmpPacketList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *icmpPacketEntry) StateTypeName() string {
+	return "pkg/tcpip/transport/icmp.icmpPacketEntry"
+}
+
+func (e *icmpPacketEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *icmpPacketEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *icmpPacketEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *icmpPacketEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *icmpPacketEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+	state.Register((*icmpPacket)(nil))
+	state.Register((*endpoint)(nil))
+	state.Register((*icmpPacketList)(nil))
+	state.Register((*icmpPacketEntry)(nil))
+}
diff --git a/pkg/tcpip/transport/packet/packet_list.go b/pkg/tcpip/transport/packet/packet_list.go
new file mode 100644
index 0000000..2c983aa
--- /dev/null
+++ b/pkg/tcpip/transport/packet/packet_list.go
@@ -0,0 +1,221 @@
+package packet
+
+// 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 packetElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (packetElementMapper) linkerFor(elem *packet) *packet { 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 packetList struct {
+	head *packet
+	tail *packet
+}
+
+// Reset resets list l to the empty state.
+func (l *packetList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *packetList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *packetList) Front() *packet {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *packetList) Back() *packet {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *packetList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (packetElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *packetList) PushFront(e *packet) {
+	linker := packetElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		packetElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *packetList) PushBack(e *packet) {
+	linker := packetElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		packetElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *packetList) PushBackList(m *packetList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		packetElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		packetElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *packetList) InsertAfter(b, e *packet) {
+	bLinker := packetElementMapper{}.linkerFor(b)
+	eLinker := packetElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		packetElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *packetList) InsertBefore(a, e *packet) {
+	aLinker := packetElementMapper{}.linkerFor(a)
+	eLinker := packetElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		packetElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *packetList) Remove(e *packet) {
+	linker := packetElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		packetElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		packetElementMapper{}.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 packetEntry struct {
+	next *packet
+	prev *packet
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *packetEntry) Next() *packet {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *packetEntry) Prev() *packet {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *packetEntry) SetNext(elem *packet) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *packetEntry) SetPrev(elem *packet) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/transport/packet/packet_state_autogen.go b/pkg/tcpip/transport/packet/packet_state_autogen.go
new file mode 100644
index 0000000..f8b82e5
--- /dev/null
+++ b/pkg/tcpip/transport/packet/packet_state_autogen.go
@@ -0,0 +1,171 @@
+// automatically generated by stateify.
+
+package packet
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+	"gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (p *packet) StateTypeName() string {
+	return "pkg/tcpip/transport/packet.packet"
+}
+
+func (p *packet) StateFields() []string {
+	return []string{
+		"packetEntry",
+		"data",
+		"timestampNS",
+		"senderAddr",
+		"packetInfo",
+	}
+}
+
+func (p *packet) beforeSave() {}
+
+// +checklocksignore
+func (p *packet) StateSave(stateSinkObject state.Sink) {
+	p.beforeSave()
+	var dataValue buffer.VectorisedView = p.saveData()
+	stateSinkObject.SaveValue(1, dataValue)
+	stateSinkObject.Save(0, &p.packetEntry)
+	stateSinkObject.Save(2, &p.timestampNS)
+	stateSinkObject.Save(3, &p.senderAddr)
+	stateSinkObject.Save(4, &p.packetInfo)
+}
+
+func (p *packet) afterLoad() {}
+
+// +checklocksignore
+func (p *packet) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &p.packetEntry)
+	stateSourceObject.Load(2, &p.timestampNS)
+	stateSourceObject.Load(3, &p.senderAddr)
+	stateSourceObject.Load(4, &p.packetInfo)
+	stateSourceObject.LoadValue(1, new(buffer.VectorisedView), func(y interface{}) { p.loadData(y.(buffer.VectorisedView)) })
+}
+
+func (ep *endpoint) StateTypeName() string {
+	return "pkg/tcpip/transport/packet.endpoint"
+}
+
+func (ep *endpoint) StateFields() []string {
+	return []string{
+		"TransportEndpointInfo",
+		"DefaultSocketOptionsHandler",
+		"netProto",
+		"waiterQueue",
+		"cooked",
+		"rcvList",
+		"rcvBufSizeMax",
+		"rcvBufSize",
+		"rcvClosed",
+		"closed",
+		"bound",
+		"boundNIC",
+		"lastError",
+		"ops",
+	}
+}
+
+// +checklocksignore
+func (ep *endpoint) StateSave(stateSinkObject state.Sink) {
+	ep.beforeSave()
+	var rcvBufSizeMaxValue int = ep.saveRcvBufSizeMax()
+	stateSinkObject.SaveValue(6, rcvBufSizeMaxValue)
+	stateSinkObject.Save(0, &ep.TransportEndpointInfo)
+	stateSinkObject.Save(1, &ep.DefaultSocketOptionsHandler)
+	stateSinkObject.Save(2, &ep.netProto)
+	stateSinkObject.Save(3, &ep.waiterQueue)
+	stateSinkObject.Save(4, &ep.cooked)
+	stateSinkObject.Save(5, &ep.rcvList)
+	stateSinkObject.Save(7, &ep.rcvBufSize)
+	stateSinkObject.Save(8, &ep.rcvClosed)
+	stateSinkObject.Save(9, &ep.closed)
+	stateSinkObject.Save(10, &ep.bound)
+	stateSinkObject.Save(11, &ep.boundNIC)
+	stateSinkObject.Save(12, &ep.lastError)
+	stateSinkObject.Save(13, &ep.ops)
+}
+
+// +checklocksignore
+func (ep *endpoint) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &ep.TransportEndpointInfo)
+	stateSourceObject.Load(1, &ep.DefaultSocketOptionsHandler)
+	stateSourceObject.Load(2, &ep.netProto)
+	stateSourceObject.Load(3, &ep.waiterQueue)
+	stateSourceObject.Load(4, &ep.cooked)
+	stateSourceObject.Load(5, &ep.rcvList)
+	stateSourceObject.Load(7, &ep.rcvBufSize)
+	stateSourceObject.Load(8, &ep.rcvClosed)
+	stateSourceObject.Load(9, &ep.closed)
+	stateSourceObject.Load(10, &ep.bound)
+	stateSourceObject.Load(11, &ep.boundNIC)
+	stateSourceObject.Load(12, &ep.lastError)
+	stateSourceObject.Load(13, &ep.ops)
+	stateSourceObject.LoadValue(6, new(int), func(y interface{}) { ep.loadRcvBufSizeMax(y.(int)) })
+	stateSourceObject.AfterLoad(ep.afterLoad)
+}
+
+func (l *packetList) StateTypeName() string {
+	return "pkg/tcpip/transport/packet.packetList"
+}
+
+func (l *packetList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *packetList) beforeSave() {}
+
+// +checklocksignore
+func (l *packetList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *packetList) afterLoad() {}
+
+// +checklocksignore
+func (l *packetList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *packetEntry) StateTypeName() string {
+	return "pkg/tcpip/transport/packet.packetEntry"
+}
+
+func (e *packetEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *packetEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *packetEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *packetEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *packetEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+	state.Register((*packet)(nil))
+	state.Register((*endpoint)(nil))
+	state.Register((*packetList)(nil))
+	state.Register((*packetEntry)(nil))
+}
diff --git a/pkg/tcpip/transport/raw/raw_packet_list.go b/pkg/tcpip/transport/raw/raw_packet_list.go
new file mode 100644
index 0000000..48804ff
--- /dev/null
+++ b/pkg/tcpip/transport/raw/raw_packet_list.go
@@ -0,0 +1,221 @@
+package raw
+
+// 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 rawPacketElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (rawPacketElementMapper) linkerFor(elem *rawPacket) *rawPacket { 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 rawPacketList struct {
+	head *rawPacket
+	tail *rawPacket
+}
+
+// Reset resets list l to the empty state.
+func (l *rawPacketList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *rawPacketList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *rawPacketList) Front() *rawPacket {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *rawPacketList) Back() *rawPacket {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *rawPacketList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (rawPacketElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *rawPacketList) PushFront(e *rawPacket) {
+	linker := rawPacketElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		rawPacketElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *rawPacketList) PushBack(e *rawPacket) {
+	linker := rawPacketElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		rawPacketElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *rawPacketList) PushBackList(m *rawPacketList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		rawPacketElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		rawPacketElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *rawPacketList) InsertAfter(b, e *rawPacket) {
+	bLinker := rawPacketElementMapper{}.linkerFor(b)
+	eLinker := rawPacketElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		rawPacketElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *rawPacketList) InsertBefore(a, e *rawPacket) {
+	aLinker := rawPacketElementMapper{}.linkerFor(a)
+	eLinker := rawPacketElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		rawPacketElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *rawPacketList) Remove(e *rawPacket) {
+	linker := rawPacketElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		rawPacketElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		rawPacketElementMapper{}.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 rawPacketEntry struct {
+	next *rawPacket
+	prev *rawPacket
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *rawPacketEntry) Next() *rawPacket {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *rawPacketEntry) Prev() *rawPacket {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *rawPacketEntry) SetNext(elem *rawPacket) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *rawPacketEntry) SetPrev(elem *rawPacket) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/transport/raw/raw_state_autogen.go b/pkg/tcpip/transport/raw/raw_state_autogen.go
new file mode 100644
index 0000000..3bcfc7c
--- /dev/null
+++ b/pkg/tcpip/transport/raw/raw_state_autogen.go
@@ -0,0 +1,165 @@
+// automatically generated by stateify.
+
+package raw
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+	"gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (p *rawPacket) StateTypeName() string {
+	return "pkg/tcpip/transport/raw.rawPacket"
+}
+
+func (p *rawPacket) StateFields() []string {
+	return []string{
+		"rawPacketEntry",
+		"data",
+		"timestampNS",
+		"senderAddr",
+	}
+}
+
+func (p *rawPacket) beforeSave() {}
+
+// +checklocksignore
+func (p *rawPacket) StateSave(stateSinkObject state.Sink) {
+	p.beforeSave()
+	var dataValue buffer.VectorisedView = p.saveData()
+	stateSinkObject.SaveValue(1, dataValue)
+	stateSinkObject.Save(0, &p.rawPacketEntry)
+	stateSinkObject.Save(2, &p.timestampNS)
+	stateSinkObject.Save(3, &p.senderAddr)
+}
+
+func (p *rawPacket) afterLoad() {}
+
+// +checklocksignore
+func (p *rawPacket) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &p.rawPacketEntry)
+	stateSourceObject.Load(2, &p.timestampNS)
+	stateSourceObject.Load(3, &p.senderAddr)
+	stateSourceObject.LoadValue(1, new(buffer.VectorisedView), func(y interface{}) { p.loadData(y.(buffer.VectorisedView)) })
+}
+
+func (e *endpoint) StateTypeName() string {
+	return "pkg/tcpip/transport/raw.endpoint"
+}
+
+func (e *endpoint) StateFields() []string {
+	return []string{
+		"TransportEndpointInfo",
+		"DefaultSocketOptionsHandler",
+		"waiterQueue",
+		"associated",
+		"rcvList",
+		"rcvBufSize",
+		"rcvBufSizeMax",
+		"rcvClosed",
+		"closed",
+		"connected",
+		"bound",
+		"owner",
+		"ops",
+	}
+}
+
+// +checklocksignore
+func (e *endpoint) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	var rcvBufSizeMaxValue int = e.saveRcvBufSizeMax()
+	stateSinkObject.SaveValue(6, rcvBufSizeMaxValue)
+	stateSinkObject.Save(0, &e.TransportEndpointInfo)
+	stateSinkObject.Save(1, &e.DefaultSocketOptionsHandler)
+	stateSinkObject.Save(2, &e.waiterQueue)
+	stateSinkObject.Save(3, &e.associated)
+	stateSinkObject.Save(4, &e.rcvList)
+	stateSinkObject.Save(5, &e.rcvBufSize)
+	stateSinkObject.Save(7, &e.rcvClosed)
+	stateSinkObject.Save(8, &e.closed)
+	stateSinkObject.Save(9, &e.connected)
+	stateSinkObject.Save(10, &e.bound)
+	stateSinkObject.Save(11, &e.owner)
+	stateSinkObject.Save(12, &e.ops)
+}
+
+// +checklocksignore
+func (e *endpoint) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.TransportEndpointInfo)
+	stateSourceObject.Load(1, &e.DefaultSocketOptionsHandler)
+	stateSourceObject.Load(2, &e.waiterQueue)
+	stateSourceObject.Load(3, &e.associated)
+	stateSourceObject.Load(4, &e.rcvList)
+	stateSourceObject.Load(5, &e.rcvBufSize)
+	stateSourceObject.Load(7, &e.rcvClosed)
+	stateSourceObject.Load(8, &e.closed)
+	stateSourceObject.Load(9, &e.connected)
+	stateSourceObject.Load(10, &e.bound)
+	stateSourceObject.Load(11, &e.owner)
+	stateSourceObject.Load(12, &e.ops)
+	stateSourceObject.LoadValue(6, new(int), func(y interface{}) { e.loadRcvBufSizeMax(y.(int)) })
+	stateSourceObject.AfterLoad(e.afterLoad)
+}
+
+func (l *rawPacketList) StateTypeName() string {
+	return "pkg/tcpip/transport/raw.rawPacketList"
+}
+
+func (l *rawPacketList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *rawPacketList) beforeSave() {}
+
+// +checklocksignore
+func (l *rawPacketList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *rawPacketList) afterLoad() {}
+
+// +checklocksignore
+func (l *rawPacketList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *rawPacketEntry) StateTypeName() string {
+	return "pkg/tcpip/transport/raw.rawPacketEntry"
+}
+
+func (e *rawPacketEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *rawPacketEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *rawPacketEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *rawPacketEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *rawPacketEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+	state.Register((*rawPacket)(nil))
+	state.Register((*endpoint)(nil))
+	state.Register((*rawPacketList)(nil))
+	state.Register((*rawPacketEntry)(nil))
+}
diff --git a/pkg/tcpip/transport/tcp/dual_stack_test.go b/pkg/tcpip/transport/tcp/dual_stack_test.go
deleted file mode 100644
index 2d90246..0000000
--- a/pkg/tcpip/transport/tcp/dual_stack_test.go
+++ /dev/null
@@ -1,651 +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 tcp_test
-
-import (
-	"strings"
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/checker"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
-	"gvisor.dev/gvisor/pkg/tcpip/seqnum"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
-	"gvisor.dev/gvisor/pkg/waiter"
-)
-
-func TestV4MappedConnectOnV6Only(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(true)
-
-	// Start connection attempt, it must fail.
-	err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV4MappedAddr, Port: context.TestPort})
-	if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-		t.Fatalf("Unexpected return value from Connect: %v", err)
-	}
-}
-
-func testV4Connect(t *testing.T, c *context.Context, checkers ...checker.NetworkChecker) {
-	// Start connection attempt.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventOut)
-	defer c.WQ.EventUnregister(&we)
-
-	err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV4MappedAddr, Port: context.TestPort})
-	if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-		t.Fatalf("Unexpected return value from Connect: %v", err)
-	}
-
-	// Receive SYN packet.
-	b := c.GetPacket()
-	synCheckers := append(checkers, checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagSyn),
-	))
-	checker.IPv4(t, b, synCheckers...)
-
-	tcp := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcp.SequenceNumber())
-
-	iss := seqnum.Value(789)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: tcp.DestinationPort(),
-		DstPort: tcp.SourcePort(),
-		Flags:   header.TCPFlagSyn | header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Receive ACK packet.
-	ackCheckers := append(checkers, checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(c.IRS)+1),
-		checker.TCPAckNum(uint32(iss)+1),
-	))
-	checker.IPv4(t, c.GetPacket(), ackCheckers...)
-
-	// Wait for connection to be established.
-	select {
-	case <-ch:
-		if err := c.EP.LastError(); err != nil {
-			t.Fatalf("Unexpected error when connecting: %v", err)
-		}
-	case <-time.After(1 * time.Second):
-		t.Fatalf("Timed out waiting for connection")
-	}
-}
-
-func TestV4MappedConnect(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Test the connection request.
-	testV4Connect(t, c)
-}
-
-func TestV4ConnectWhenBoundToWildcard(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test the connection request.
-	testV4Connect(t, c)
-}
-
-func TestV4ConnectWhenBoundToV4MappedWildcard(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind to v4 mapped wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Addr: context.V4MappedWildcardAddr}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test the connection request.
-	testV4Connect(t, c)
-}
-
-func TestV4ConnectWhenBoundToV4Mapped(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind to v4 mapped address.
-	if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackV4MappedAddr}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test the connection request.
-	testV4Connect(t, c)
-}
-
-func testV6Connect(t *testing.T, c *context.Context, checkers ...checker.NetworkChecker) {
-	// Start connection attempt to IPv6 address.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventOut)
-	defer c.WQ.EventUnregister(&we)
-
-	err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestV6Addr, Port: context.TestPort})
-	if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-		t.Fatalf("Unexpected return value from Connect: %v", err)
-	}
-
-	// Receive SYN packet.
-	b := c.GetV6Packet()
-	synCheckers := append(checkers, checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagSyn),
-	))
-	checker.IPv6(t, b, synCheckers...)
-
-	tcp := header.TCP(header.IPv6(b).Payload())
-	c.IRS = seqnum.Value(tcp.SequenceNumber())
-
-	iss := seqnum.Value(789)
-	c.SendV6Packet(nil, &context.Headers{
-		SrcPort: tcp.DestinationPort(),
-		DstPort: tcp.SourcePort(),
-		Flags:   header.TCPFlagSyn | header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Receive ACK packet.
-	ackCheckers := append(checkers, checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(c.IRS)+1),
-		checker.TCPAckNum(uint32(iss)+1),
-	))
-	checker.IPv6(t, c.GetV6Packet(), ackCheckers...)
-
-	// Wait for connection to be established.
-	select {
-	case <-ch:
-		if err := c.EP.LastError(); err != nil {
-			t.Fatalf("Unexpected error when connecting: %v", err)
-		}
-	case <-time.After(1 * time.Second):
-		t.Fatalf("Timed out waiting for connection")
-	}
-}
-
-func TestV6Connect(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Test the connection request.
-	testV6Connect(t, c)
-}
-
-func TestV6ConnectV6Only(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(true)
-
-	// Test the connection request.
-	testV6Connect(t, c)
-}
-
-func TestV6ConnectWhenBoundToWildcard(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test the connection request.
-	testV6Connect(t, c)
-}
-
-func TestStackV6OnlyConnectWhenBoundToWildcard(t *testing.T) {
-	c := context.NewWithOpts(t, context.Options{
-		EnableV6: true,
-		MTU:      defaultMTU,
-	})
-	defer c.Cleanup()
-
-	// Create a v6 endpoint but don't set the v6-only TCP option.
-	c.CreateV6Endpoint(false)
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test the connection request.
-	testV6Connect(t, c)
-}
-
-func TestV6ConnectWhenBoundToLocalAddress(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind to local address.
-	if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackV6Addr}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test the connection request.
-	testV6Connect(t, c)
-}
-
-func TestV4RefuseOnV6Only(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(true)
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Start listening.
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %v", err)
-	}
-
-	// Send a SYN request.
-	irs := seqnum.Value(789)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-
-	// Receive the RST reply.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.SrcPort(context.StackPort),
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
-			checker.TCPAckNum(uint32(irs)+1),
-		),
-	)
-}
-
-func TestV6RefuseOnBoundToV4Mapped(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind and listen.
-	if err := c.EP.Bind(tcpip.FullAddress{Addr: context.V4MappedWildcardAddr, Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %v", err)
-	}
-
-	// Send a SYN request.
-	irs := seqnum.Value(789)
-	c.SendV6Packet(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-
-	// Receive the RST reply.
-	checker.IPv6(t, c.GetV6Packet(),
-		checker.TCP(
-			checker.SrcPort(context.StackPort),
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
-			checker.TCPAckNum(uint32(irs)+1),
-		),
-	)
-}
-
-func testV4Accept(t *testing.T, c *context.Context) {
-	c.SetGSOEnabled(true)
-	defer c.SetGSOEnabled(false)
-
-	// Start listening.
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %v", err)
-	}
-
-	// Send a SYN request.
-	irs := seqnum.Value(789)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcp := header.TCP(header.IPv4(b).Payload())
-	iss := seqnum.Value(tcp.SequenceNumber())
-	checker.IPv4(t, b,
-		checker.TCP(
-			checker.SrcPort(context.StackPort),
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagSyn),
-			checker.TCPAckNum(uint32(irs)+1),
-		),
-	)
-
-	// Send ACK.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-		RcvWnd:  30000,
-	})
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	nep, _, err := c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			nep, _, err = c.EP.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %v", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Check the peer address.
-	addr, err := nep.GetRemoteAddress()
-	if err != nil {
-		t.Fatalf("GetRemoteAddress failed failed: %v", err)
-	}
-
-	if addr.Addr != context.TestAddr {
-		t.Fatalf("Unexpected remote address: got %v, want %v", addr.Addr, context.TestAddr)
-	}
-
-	var r strings.Reader
-	data := "Don't panic"
-	r.Reset(data)
-	nep.Write(&r, tcpip.WriteOptions{})
-	b = c.GetPacket()
-	tcp = header.TCP(header.IPv4(b).Payload())
-	if string(tcp.Payload()) != data {
-		t.Fatalf("Unexpected data: got %v, want %v", string(tcp.Payload()), data)
-	}
-}
-
-func TestV4AcceptOnV6(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test acceptance.
-	testV4Accept(t, c)
-}
-
-func TestV4AcceptOnBoundToV4MappedWildcard(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind to v4 mapped wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Addr: context.V4MappedWildcardAddr, Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test acceptance.
-	testV4Accept(t, c)
-}
-
-func TestV4AcceptOnBoundToV4Mapped(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind and listen.
-	if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackV4MappedAddr, Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test acceptance.
-	testV4Accept(t, c)
-}
-
-func TestV6AcceptOnV6(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	// Bind and listen.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %v", err)
-	}
-
-	// Send a SYN request.
-	irs := seqnum.Value(789)
-	c.SendV6Packet(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetV6Packet()
-	tcp := header.TCP(header.IPv6(b).Payload())
-	iss := seqnum.Value(tcp.SequenceNumber())
-	checker.IPv6(t, b,
-		checker.TCP(
-			checker.SrcPort(context.StackPort),
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagSyn),
-			checker.TCPAckNum(uint32(irs)+1),
-		),
-	)
-
-	// Send ACK.
-	c.SendV6Packet(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-		RcvWnd:  30000,
-	})
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-	var addr tcpip.FullAddress
-	_, _, err := c.EP.Accept(&addr)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			_, _, err = c.EP.Accept(&addr)
-			if err != nil {
-				t.Fatalf("Accept failed: %v", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	if addr.Addr != context.TestV6Addr {
-		t.Errorf("Unexpected remote address: got %s, want %s", addr.Addr, context.TestV6Addr)
-	}
-}
-
-func TestV4AcceptOnV4(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %v", err)
-	}
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test acceptance.
-	testV4Accept(t, c)
-}
-
-func testV4ListenClose(t *testing.T, c *context.Context) {
-	// Set the SynRcvd threshold to zero to force a syn cookie based accept
-	// to happen.
-	var opt tcpip.TCPSynRcvdCountThresholdOption
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("setting TCPSynRcvdCountThresholdOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-
-	const n = uint16(32)
-
-	// Start listening.
-	if err := c.EP.Listen(int(tcp.SynRcvdCountThreshold + 1)); err != nil {
-		t.Fatalf("Listen failed: %v", err)
-	}
-
-	irs := seqnum.Value(789)
-	for i := uint16(0); i < n; i++ {
-		// Send a SYN request.
-		c.SendPacket(nil, &context.Headers{
-			SrcPort: context.TestPort + i,
-			DstPort: context.StackPort,
-			Flags:   header.TCPFlagSyn,
-			SeqNum:  irs,
-			RcvWnd:  30000,
-		})
-	}
-
-	// Each of these ACK's will cause a syn-cookie based connection to be
-	// accepted and delivered to the listening endpoint.
-	for i := uint16(0); i < n; i++ {
-		b := c.GetPacket()
-		tcp := header.TCP(header.IPv4(b).Payload())
-		iss := seqnum.Value(tcp.SequenceNumber())
-		// Send ACK.
-		c.SendPacket(nil, &context.Headers{
-			SrcPort: tcp.DestinationPort(),
-			DstPort: context.StackPort,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  irs + 1,
-			AckNum:  iss + 1,
-			RcvWnd:  30000,
-		})
-	}
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-	nep, _, err := c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			nep, _, err = c.EP.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %v", err)
-			}
-
-		case <-time.After(10 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-	nep.Close()
-	c.EP.Close()
-}
-
-func TestV4ListenCloseOnV4(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %v", err)
-	}
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %v", err)
-	}
-
-	// Test acceptance.
-	testV4ListenClose(t, c)
-}
diff --git a/pkg/tcpip/transport/tcp/rcv_test.go b/pkg/tcpip/transport/tcp/rcv_test.go
deleted file mode 100644
index 8a026ec..0000000
--- a/pkg/tcpip/transport/tcp/rcv_test.go
+++ /dev/null
@@ -1,74 +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 rcv_test
-
-import (
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/seqnum"
-)
-
-func TestAcceptable(t *testing.T) {
-	for _, tt := range []struct {
-		segSeq         seqnum.Value
-		segLen         seqnum.Size
-		rcvNxt, rcvAcc seqnum.Value
-		want           bool
-	}{
-		// The segment is smaller than the window.
-		{105, 2, 100, 104, false},
-		{105, 2, 101, 105, true},
-		{105, 2, 102, 106, true},
-		{105, 2, 103, 107, true},
-		{105, 2, 104, 108, true},
-		{105, 2, 105, 109, true},
-		{105, 2, 106, 110, true},
-		{105, 2, 107, 111, false},
-
-		// The segment is larger than the window.
-		{105, 4, 103, 105, true},
-		{105, 4, 104, 106, true},
-		{105, 4, 105, 107, true},
-		{105, 4, 106, 108, true},
-		{105, 4, 107, 109, true},
-		{105, 4, 108, 110, true},
-		{105, 4, 109, 111, false},
-		{105, 4, 110, 112, false},
-
-		// The segment has no width.
-		{105, 0, 100, 102, false},
-		{105, 0, 101, 103, false},
-		{105, 0, 102, 104, false},
-		{105, 0, 103, 105, true},
-		{105, 0, 104, 106, true},
-		{105, 0, 105, 107, true},
-		{105, 0, 106, 108, false},
-		{105, 0, 107, 109, false},
-
-		// The receive window has no width.
-		{105, 2, 103, 103, false},
-		{105, 2, 104, 104, false},
-		{105, 2, 105, 105, false},
-		{105, 2, 106, 106, false},
-		{105, 2, 107, 107, false},
-		{105, 2, 108, 108, false},
-		{105, 2, 109, 109, false},
-	} {
-		if got := header.Acceptable(tt.segSeq, tt.segLen, tt.rcvNxt, tt.rcvAcc); got != tt.want {
-			t.Errorf("header.Acceptable(%d, %d, %d, %d) = %t, want %t", tt.segSeq, tt.segLen, tt.rcvNxt, tt.rcvAcc, got, tt.want)
-		}
-	}
-}
diff --git a/pkg/tcpip/transport/tcp/sack_scoreboard_test.go b/pkg/tcpip/transport/tcp/sack_scoreboard_test.go
deleted file mode 100644
index b4e5ba0..0000000
--- a/pkg/tcpip/transport/tcp/sack_scoreboard_test.go
+++ /dev/null
@@ -1,249 +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 tcp_test
-
-import (
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/seqnum"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
-)
-
-const smss = 1500
-
-func initScoreboard(blocks []header.SACKBlock, iss seqnum.Value) *tcp.SACKScoreboard {
-	s := tcp.NewSACKScoreboard(smss, iss)
-	for _, blk := range blocks {
-		s.Insert(blk)
-	}
-	return s
-}
-
-func TestSACKScoreboardIsSACKED(t *testing.T) {
-	type blockTest struct {
-		block  header.SACKBlock
-		sacked bool
-	}
-	testCases := []struct {
-		comment          string
-		scoreboardBlocks []header.SACKBlock
-		blockTests       []blockTest
-		iss              seqnum.Value
-	}{
-		{
-			"Test holes and unsacked SACK blocks in SACKed ranges and insertion of overlapping SACK blocks",
-			[]header.SACKBlock{{10, 20}, {10, 30}, {30, 40}, {41, 50}, {5, 10}, {1, 50}, {111, 120}, {101, 110}, {52, 120}},
-			[]blockTest{
-				{header.SACKBlock{15, 21}, true},
-				{header.SACKBlock{200, 201}, false},
-				{header.SACKBlock{50, 51}, false},
-				{header.SACKBlock{53, 120}, true},
-			},
-			0,
-		},
-		{
-			"Test disjoint SACKBlocks",
-			[]header.SACKBlock{{2288624809, 2288810057}, {2288811477, 2288838565}},
-			[]blockTest{
-				{header.SACKBlock{2288624809, 2288810057}, true},
-				{header.SACKBlock{2288811477, 2288838565}, true},
-				{header.SACKBlock{2288810057, 2288811477}, false},
-			},
-			2288624809,
-		},
-		{
-			"Test sequence number wrap around",
-			[]header.SACKBlock{{4294254144, 225652}, {5340409, 5350509}},
-			[]blockTest{
-				{header.SACKBlock{4294254144, 4294254145}, true},
-				{header.SACKBlock{4294254143, 4294254144}, false},
-				{header.SACKBlock{4294254144, 1}, true},
-				{header.SACKBlock{225652, 5350509}, false},
-				{header.SACKBlock{5340409, 5350509}, true},
-				{header.SACKBlock{5350509, 5350609}, false},
-			},
-			4294254144,
-		},
-		{
-			"Test disjoint SACKBlocks out of order",
-			[]header.SACKBlock{{827450276, 827454536}, {827426028, 827428868}},
-			[]blockTest{
-				{header.SACKBlock{827426028, 827428867}, true},
-				{header.SACKBlock{827450168, 827450275}, false},
-			},
-			827426000,
-		},
-	}
-	for _, tc := range testCases {
-		sb := initScoreboard(tc.scoreboardBlocks, tc.iss)
-		for _, blkTest := range tc.blockTests {
-			if want, got := blkTest.sacked, sb.IsSACKED(blkTest.block); got != want {
-				t.Errorf("%s: s.IsSACKED(%v) = %v, want %v", tc.comment, blkTest.block, got, want)
-			}
-		}
-	}
-}
-
-func TestSACKScoreboardIsRangeLost(t *testing.T) {
-	s := tcp.NewSACKScoreboard(10, 0)
-	s.Insert(header.SACKBlock{1, 25})
-	s.Insert(header.SACKBlock{25, 50})
-	s.Insert(header.SACKBlock{51, 100})
-	s.Insert(header.SACKBlock{111, 120})
-	s.Insert(header.SACKBlock{101, 110})
-	s.Insert(header.SACKBlock{121, 141})
-	s.Insert(header.SACKBlock{145, 146})
-	s.Insert(header.SACKBlock{147, 148})
-	s.Insert(header.SACKBlock{149, 150})
-	s.Insert(header.SACKBlock{153, 154})
-	s.Insert(header.SACKBlock{155, 156})
-	testCases := []struct {
-		block header.SACKBlock
-		lost  bool
-	}{
-		// Block not covered by SACK block and has more than
-		// nDupAckThreshold discontiguous SACK blocks after it as well
-		// as (nDupAckThreshold -1) * 10 (smss) bytes that have been
-		// SACKED above the sequence number covered by this block.
-		{block: header.SACKBlock{0, 1}, lost: true},
-
-		// These blocks have all been SACKed and should not be
-		// considered lost.
-		{block: header.SACKBlock{1, 2}, lost: false},
-		{block: header.SACKBlock{25, 26}, lost: false},
-		{block: header.SACKBlock{1, 45}, lost: false},
-
-		// Same as the first case above.
-		{block: header.SACKBlock{50, 51}, lost: true},
-
-		// This block has been SACKed and should not be considered lost.
-		{block: header.SACKBlock{119, 120}, lost: false},
-
-		// This one should return true because there are >
-		// (nDupAckThreshold - 1) * 10 (smss) bytes that have been
-		// sacked above this sequence number.
-		{block: header.SACKBlock{120, 121}, lost: true},
-
-		// This block has been SACKed and should not be considered lost.
-		{block: header.SACKBlock{125, 126}, lost: false},
-
-		// This block has not been SACKed and there are nDupAckThreshold
-		// number of SACKed blocks after it.
-		{block: header.SACKBlock{141, 145}, lost: true},
-
-		// This block has not been SACKed and there are less than
-		// nDupAckThreshold SACKed sequences after it.
-		{block: header.SACKBlock{151, 152}, lost: false},
-	}
-	for _, tc := range testCases {
-		if want, got := tc.lost, s.IsRangeLost(tc.block); got != want {
-			t.Errorf("s.IsRangeLost(%v) = %v, want %v", tc.block, got, want)
-		}
-	}
-}
-
-func TestSACKScoreboardIsLost(t *testing.T) {
-	s := tcp.NewSACKScoreboard(10, 0)
-	s.Insert(header.SACKBlock{1, 25})
-	s.Insert(header.SACKBlock{25, 50})
-	s.Insert(header.SACKBlock{51, 100})
-	s.Insert(header.SACKBlock{111, 120})
-	s.Insert(header.SACKBlock{101, 110})
-	s.Insert(header.SACKBlock{121, 141})
-	s.Insert(header.SACKBlock{121, 141})
-	s.Insert(header.SACKBlock{145, 146})
-	s.Insert(header.SACKBlock{147, 148})
-	s.Insert(header.SACKBlock{149, 150})
-	s.Insert(header.SACKBlock{153, 154})
-	s.Insert(header.SACKBlock{155, 156})
-	testCases := []struct {
-		seq  seqnum.Value
-		lost bool
-	}{
-		// Sequence number not covered by SACK block and has more than
-		// nDupAckThreshold discontiguous SACK blocks after it as well
-		// as (nDupAckThreshold -1) * 10 (smss) bytes that have been
-		// SACKED above the sequence number.
-		{seq: 0, lost: true},
-
-		// These sequence numbers have all been SACKed and should not be
-		// considered lost.
-		{seq: 1, lost: false},
-		{seq: 25, lost: false},
-		{seq: 45, lost: false},
-
-		// Same as first case above.
-		{seq: 50, lost: true},
-
-		// This block has been SACKed and should not be considered lost.
-		{seq: 119, lost: false},
-
-		// This one should return true because there are >
-		// (nDupAckThreshold - 1) * 10 (smss) bytes that have been
-		// sacked above this sequence number.
-		{seq: 120, lost: true},
-
-		// This sequence number has been SACKed and should not be
-		// considered lost.
-		{seq: 125, lost: false},
-
-		// This sequence number has not been SACKed and there are
-		// nDupAckThreshold number of SACKed blocks after it.
-		{seq: 141, lost: true},
-
-		// This sequence number has not been SACKed and there are less
-		// than nDupAckThreshold SACKed sequences after it.
-		{seq: 151, lost: false},
-	}
-	for _, tc := range testCases {
-		if want, got := tc.lost, s.IsLost(tc.seq); got != want {
-			t.Errorf("s.IsLost(%v) = %v, want %v", tc.seq, got, want)
-		}
-	}
-}
-
-func TestSACKScoreboardDelete(t *testing.T) {
-	blocks := []header.SACKBlock{{4294254144, 225652}, {5340409, 5350509}}
-	s := initScoreboard(blocks, 4294254143)
-	s.Delete(5340408)
-	if s.Empty() {
-		t.Fatalf("s.Empty() = true, want false")
-	}
-	if got, want := s.Sacked(), blocks[1].Start.Size(blocks[1].End); got != want {
-		t.Fatalf("incorrect sacked bytes in scoreboard got: %v, want: %v", got, want)
-	}
-	s.Delete(5340410)
-	if s.Empty() {
-		t.Fatal("s.Empty() = true, want false")
-	}
-	newSB := header.SACKBlock{5340410, 5350509}
-	if !s.IsSACKED(newSB) {
-		t.Fatalf("s.IsSACKED(%v) = false, want true, scoreboard: %v", newSB, s)
-	}
-	s.Delete(5350509)
-	lastOctet := header.SACKBlock{5350508, 5350509}
-	if s.IsSACKED(lastOctet) {
-		t.Fatalf("s.IsSACKED(%v) = false, want true", lastOctet)
-	}
-
-	s.Delete(5350510)
-	if !s.Empty() {
-		t.Fatal("s.Empty() = false, want true")
-	}
-	if got, want := s.Sacked(), seqnum.Size(0); got != want {
-		t.Fatalf("incorrect sacked bytes in scoreboard got: %v, want: %v", got, want)
-	}
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_endpoint_list.go b/pkg/tcpip/transport/tcp/tcp_endpoint_list.go
new file mode 100644
index 0000000..a7dc5df
--- /dev/null
+++ b/pkg/tcpip/transport/tcp/tcp_endpoint_list.go
@@ -0,0 +1,221 @@
+package tcp
+
+// 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 endpointElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (endpointElementMapper) linkerFor(elem *endpoint) *endpoint { 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 endpointList struct {
+	head *endpoint
+	tail *endpoint
+}
+
+// Reset resets list l to the empty state.
+func (l *endpointList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *endpointList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *endpointList) Front() *endpoint {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *endpointList) Back() *endpoint {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *endpointList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (endpointElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *endpointList) PushFront(e *endpoint) {
+	linker := endpointElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		endpointElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *endpointList) PushBack(e *endpoint) {
+	linker := endpointElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		endpointElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *endpointList) PushBackList(m *endpointList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		endpointElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		endpointElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *endpointList) InsertAfter(b, e *endpoint) {
+	bLinker := endpointElementMapper{}.linkerFor(b)
+	eLinker := endpointElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		endpointElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *endpointList) InsertBefore(a, e *endpoint) {
+	aLinker := endpointElementMapper{}.linkerFor(a)
+	eLinker := endpointElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		endpointElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *endpointList) Remove(e *endpoint) {
+	linker := endpointElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		endpointElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		endpointElementMapper{}.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 endpointEntry struct {
+	next *endpoint
+	prev *endpoint
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *endpointEntry) Next() *endpoint {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *endpointEntry) Prev() *endpoint {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *endpointEntry) SetNext(elem *endpoint) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *endpointEntry) SetPrev(elem *endpoint) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/transport/tcp/tcp_noracedetector_test.go b/pkg/tcpip/transport/tcp/tcp_noracedetector_test.go
deleted file mode 100644
index ced3a9c..0000000
--- a/pkg/tcpip/transport/tcp/tcp_noracedetector_test.go
+++ /dev/null
@@ -1,558 +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.
-//
-// These tests are flaky when run under the go race detector due to some
-// iterations taking long enough that the retransmit timer can kick in causing
-// the congestion window measurements to fail due to extra packets etc.
-//
-// +build !race
-
-package tcp_test
-
-import (
-	"bytes"
-	"fmt"
-	"math"
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
-	"gvisor.dev/gvisor/pkg/test/testutil"
-)
-
-func TestFastRecovery(t *testing.T) {
-	maxPayload := 32
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
-	defer c.Cleanup()
-
-	c.CreateConnected(789, 30000, -1 /* epRcvBuf */)
-
-	const iterations = 3
-	data := make([]byte, 2*maxPayload*(tcp.InitialCwnd<<(iterations+1)))
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write all the data in one shot. Packets will only be written at the
-	// MTU size though.
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Do slow start for a few iterations.
-	expected := tcp.InitialCwnd
-	bytesRead := 0
-	for i := 0; i < iterations; i++ {
-		expected = tcp.InitialCwnd << uint(i)
-		if i > 0 {
-			// Acknowledge all the data received so far if not on
-			// first iteration.
-			c.SendAck(790, bytesRead)
-		}
-
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-			bytesRead += maxPayload
-		}
-
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
-	}
-
-	// Send 3 duplicate acks. This should force an immediate retransmit of
-	// the pending packet and put the sender into fast recovery.
-	rtxOffset := bytesRead - maxPayload*expected
-	for i := 0; i < 3; i++ {
-		c.SendAck(790, rtxOffset)
-	}
-
-	// Receive the retransmitted packet.
-	c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
-	// Wait before checking metrics.
-	metricPollFn := func() error {
-		if got, want := c.Stack().Stats().TCP.FastRetransmit.Value(), uint64(1); got != want {
-			return fmt.Errorf("got stats.TCP.FastRetransmit.Value = %d, want = %d", got, want)
-		}
-		if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(1); got != want {
-			return fmt.Errorf("got stats.TCP.Retransmit.Value = %d, want = %d", got, want)
-		}
-
-		if got, want := c.Stack().Stats().TCP.FastRecovery.Value(), uint64(1); got != want {
-			return fmt.Errorf("got stats.TCP.FastRecovery.Value = %d, want = %d", got, want)
-		}
-		return nil
-	}
-
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-
-	// Now send 7 mode duplicate acks. Each of these should cause a window
-	// inflation by 1 and cause the sender to send an extra packet.
-	for i := 0; i < 7; i++ {
-		c.SendAck(790, rtxOffset)
-	}
-
-	recover := bytesRead
-
-	// Ensure no new packets arrive.
-	c.CheckNoPacketTimeout("More packets received than expected during recovery after dupacks for this cwnd.",
-		50*time.Millisecond)
-
-	// Acknowledge half of the pending data.
-	rtxOffset = bytesRead - expected*maxPayload/2
-	c.SendAck(790, rtxOffset)
-
-	// Receive the retransmit due to partial ack.
-	c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
-	// Wait before checking metrics.
-	metricPollFn = func() error {
-		if got, want := c.Stack().Stats().TCP.FastRetransmit.Value(), uint64(2); got != want {
-			return fmt.Errorf("got stats.TCP.FastRetransmit.Value = %d, want = %d", got, want)
-		}
-		if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(2); got != want {
-			return fmt.Errorf("got stats.TCP.Retransmit.Value = %d, want = %d", got, want)
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-
-	// Receive the 10 extra packets that should have been released due to
-	// the congestion window inflation in recovery.
-	for i := 0; i < 10; i++ {
-		c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-		bytesRead += maxPayload
-	}
-
-	// A partial ACK during recovery should reduce congestion window by the
-	// number acked. Since we had "expected" packets outstanding before sending
-	// partial ack and we acked expected/2 , the cwnd and outstanding should
-	// be expected/2 + 10 (7 dupAcks + 3 for the original 3 dupacks that triggered
-	// fast recovery). Which means the sender should not send any more packets
-	// till we ack this one.
-	c.CheckNoPacketTimeout("More packets received than expected during recovery after partial ack for this cwnd.",
-		50*time.Millisecond)
-
-	// Acknowledge all pending data to recover point.
-	c.SendAck(790, recover)
-
-	// At this point, the cwnd should reset to expected/2 and there are 10
-	// packets outstanding.
-	//
-	// NOTE: Technically netstack is incorrect in that we adjust the cwnd on
-	// the same segment that takes us out of recovery. But because of that
-	// the actual cwnd at exit of recovery will be expected/2 + 1 as we
-	// acked a cwnd worth of packets which will increase the cwnd further by
-	// 1 in congestion avoidance.
-	//
-	// Now in the first iteration since there are 10 packets outstanding.
-	// We would expect to get expected/2 +1 - 10 packets. But subsequent
-	// iterations will send us expected/2 + 1 + 1 (per iteration).
-	expected = expected/2 + 1 - 10
-	for i := 0; i < iterations; i++ {
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-			bytesRead += maxPayload
-		}
-
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout(fmt.Sprintf("More packets received(after deflation) than expected %d for this cwnd.", expected), 50*time.Millisecond)
-
-		// Acknowledge all the data received so far.
-		c.SendAck(790, bytesRead)
-
-		// In cogestion avoidance, the packets trains increase by 1 in
-		// each iteration.
-		if i == 0 {
-			// After the first iteration we expect to get the full
-			// congestion window worth of packets in every
-			// iteration.
-			expected += 10
-		}
-		expected++
-	}
-}
-
-func TestExponentialIncreaseDuringSlowStart(t *testing.T) {
-	maxPayload := 32
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
-	defer c.Cleanup()
-
-	c.CreateConnected(789, 30000, -1 /* epRcvBuf */)
-
-	const iterations = 3
-	data := make([]byte, maxPayload*(tcp.InitialCwnd<<(iterations+1)))
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write all the data in one shot. Packets will only be written at the
-	// MTU size though.
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	expected := tcp.InitialCwnd
-	bytesRead := 0
-	for i := 0; i < iterations; i++ {
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-			bytesRead += maxPayload
-		}
-
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
-
-		// Acknowledge all the data received so far.
-		c.SendAck(790, bytesRead)
-
-		// Double the number of expected packets for the next iteration.
-		expected *= 2
-	}
-}
-
-func TestCongestionAvoidance(t *testing.T) {
-	maxPayload := 32
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
-	defer c.Cleanup()
-
-	c.CreateConnected(789, 30000, -1 /* epRcvBuf */)
-
-	const iterations = 3
-	data := make([]byte, 2*maxPayload*(tcp.InitialCwnd<<(iterations+1)))
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write all the data in one shot. Packets will only be written at the
-	// MTU size though.
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Do slow start for a few iterations.
-	expected := tcp.InitialCwnd
-	bytesRead := 0
-	for i := 0; i < iterations; i++ {
-		expected = tcp.InitialCwnd << uint(i)
-		if i > 0 {
-			// Acknowledge all the data received so far if not on
-			// first iteration.
-			c.SendAck(790, bytesRead)
-		}
-
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-			bytesRead += maxPayload
-		}
-
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout("More packets received than expected for this cwnd (slow start phase).", 50*time.Millisecond)
-	}
-
-	// Don't acknowledge the first packet of the last packet train. Let's
-	// wait for them to time out, which will trigger a restart of slow
-	// start, and initialization of ssthresh to cwnd/2.
-	rtxOffset := bytesRead - maxPayload*expected
-	c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
-	// Acknowledge all the data received so far.
-	c.SendAck(790, bytesRead)
-
-	// This part is tricky: when the timeout happened, we had "expected"
-	// packets pending, cwnd reset to 1, and ssthresh set to expected/2.
-	// By acknowledging "expected" packets, the slow-start part will
-	// increase cwnd to expected/2 (which "consumes" expected/2-1 of the
-	// acknowledgements), then the congestion avoidance part will consume
-	// an extra expected/2 acks to take cwnd to expected/2 + 1. One ack
-	// remains in the "ack count" (which will cause cwnd to be incremented
-	// once it reaches cwnd acks).
-	//
-	// So we're straight into congestion avoidance with cwnd set to
-	// expected/2 + 1.
-	//
-	// Check that packets trains of cwnd packets are sent, and that cwnd is
-	// incremented by 1 after we acknowledge each packet.
-	expected = expected/2 + 1
-	for i := 0; i < iterations; i++ {
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-			bytesRead += maxPayload
-		}
-
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout("More packets received than expected for this cwnd (congestion avoidance phase).", 50*time.Millisecond)
-
-		// Acknowledge all the data received so far.
-		c.SendAck(790, bytesRead)
-
-		// In cogestion avoidance, the packets trains increase by 1 in
-		// each iteration.
-		expected++
-	}
-}
-
-// cubicCwnd returns an estimate of a cubic window given the
-// originalCwnd, wMax, last congestion event time and sRTT.
-func cubicCwnd(origCwnd int, wMax int, congEventTime time.Time, sRTT time.Duration) int {
-	cwnd := float64(origCwnd)
-	// We wait 50ms between each iteration so sRTT as computed by cubic
-	// should be close to 50ms.
-	elapsed := (time.Since(congEventTime) + sRTT).Seconds()
-	k := math.Cbrt(float64(wMax) * 0.3 / 0.7)
-	wtRTT := 0.4*math.Pow(elapsed-k, 3) + float64(wMax)
-	cwnd += (wtRTT - cwnd) / cwnd
-	return int(cwnd)
-}
-
-func TestCubicCongestionAvoidance(t *testing.T) {
-	maxPayload := 32
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
-	defer c.Cleanup()
-
-	enableCUBIC(t, c)
-
-	c.CreateConnected(789, 30000, -1 /* epRcvBuf */)
-
-	const iterations = 3
-	data := make([]byte, 2*maxPayload*(tcp.InitialCwnd<<(iterations+1)))
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write all the data in one shot. Packets will only be written at the
-	// MTU size though.
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Do slow start for a few iterations.
-	expected := tcp.InitialCwnd
-	bytesRead := 0
-	for i := 0; i < iterations; i++ {
-		expected = tcp.InitialCwnd << uint(i)
-		if i > 0 {
-			// Acknowledge all the data received so far if not on
-			// first iteration.
-			c.SendAck(790, bytesRead)
-		}
-
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-			bytesRead += maxPayload
-		}
-
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout("More packets received than expected for this cwnd (during slow-start phase).", 50*time.Millisecond)
-	}
-
-	// Don't acknowledge the first packet of the last packet train. Let's
-	// wait for them to time out, which will trigger a restart of slow
-	// start, and initialization of ssthresh to cwnd * 0.7.
-	rtxOffset := bytesRead - maxPayload*expected
-	c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
-	// Acknowledge all pending data.
-	c.SendAck(790, bytesRead)
-
-	// Store away the time we sent the ACK and assuming a 200ms RTO
-	// we estimate that the sender will have an RTO 200ms from now
-	// and go back into slow start.
-	packetDropTime := time.Now().Add(200 * time.Millisecond)
-
-	// This part is tricky: when the timeout happened, we had "expected"
-	// packets pending, cwnd reset to 1, and ssthresh set to expected * 0.7.
-	// By acknowledging "expected" packets, the slow-start part will
-	// increase cwnd to expected/2 essentially putting the connection
-	// straight into congestion avoidance.
-	wMax := expected
-	// Lower expected as per cubic spec after a congestion event.
-	expected = int(float64(expected) * 0.7)
-	cwnd := expected
-	for i := 0; i < iterations; i++ {
-		// Cubic grows window independent of ACKs. Cubic Window growth
-		// is a function of time elapsed since last congestion event.
-		// As a result the congestion window does not grow
-		// deterministically in response to ACKs.
-		//
-		// We need to roughly estimate what the cwnd of the sender is
-		// based on when we sent the dupacks.
-		cwnd := cubicCwnd(cwnd, wMax, packetDropTime, 50*time.Millisecond)
-
-		packetsExpected := cwnd
-		for j := 0; j < packetsExpected; j++ {
-			c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-			bytesRead += maxPayload
-		}
-		t.Logf("expected packets received, next trying to receive any extra packets that may come")
-
-		// If our estimate was correct there should be no more pending packets.
-		// We attempt to read a packet a few times with a short sleep in between
-		// to ensure that we don't see the sender send any unexpected packets.
-		unexpectedPackets := 0
-		for {
-			gotPacket := c.ReceiveNonBlockingAndCheckPacket(data, bytesRead, maxPayload)
-			if !gotPacket {
-				break
-			}
-			bytesRead += maxPayload
-			unexpectedPackets++
-			time.Sleep(1 * time.Millisecond)
-		}
-		if unexpectedPackets != 0 {
-			t.Fatalf("received %d unexpected packets for iteration %d", unexpectedPackets, i)
-		}
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout("More packets received than expected for this cwnd(congestion avoidance)", 5*time.Millisecond)
-
-		// Acknowledge all the data received so far.
-		c.SendAck(790, bytesRead)
-	}
-}
-
-func TestRetransmit(t *testing.T) {
-	maxPayload := 32
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
-	defer c.Cleanup()
-
-	c.CreateConnected(789, 30000, -1 /* epRcvBuf */)
-
-	const iterations = 3
-	data := make([]byte, maxPayload*(tcp.InitialCwnd<<(iterations+1)))
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write all the data in two shots. Packets will only be written at the
-	// MTU size though.
-	var r bytes.Reader
-	r.Reset(data[:len(data)/2])
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-	r.Reset(data[len(data)/2:])
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Do slow start for a few iterations.
-	expected := tcp.InitialCwnd
-	bytesRead := 0
-	for i := 0; i < iterations; i++ {
-		expected = tcp.InitialCwnd << uint(i)
-		if i > 0 {
-			// Acknowledge all the data received so far if not on
-			// first iteration.
-			c.SendAck(790, bytesRead)
-		}
-
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacket(data, bytesRead, maxPayload)
-			bytesRead += maxPayload
-		}
-
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
-	}
-
-	// Wait for a timeout and retransmit.
-	rtxOffset := bytesRead - maxPayload*expected
-	c.ReceiveAndCheckPacket(data, rtxOffset, maxPayload)
-
-	metricPollFn := func() error {
-		if got, want := c.Stack().Stats().TCP.Timeouts.Value(), uint64(1); got != want {
-			return fmt.Errorf("got stats.TCP.Timeouts.Value = %d, want = %d", got, want)
-		}
-
-		if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(1); got != want {
-			return fmt.Errorf("got stats.TCP.Retransmits.Value = %d, want = %d", got, want)
-		}
-
-		if got, want := c.EP.Stats().(*tcp.Stats).SendErrors.Timeouts.Value(), uint64(1); got != want {
-			return fmt.Errorf("got EP SendErrors.Timeouts.Value = %d, want = %d", got, want)
-		}
-
-		if got, want := c.EP.Stats().(*tcp.Stats).SendErrors.Retransmits.Value(), uint64(1); got != want {
-			return fmt.Errorf("got EP stats SendErrors.Retransmits.Value = %d, want = %d", got, want)
-		}
-
-		if got, want := c.Stack().Stats().TCP.SlowStartRetransmits.Value(), uint64(1); got != want {
-			return fmt.Errorf("got stats.TCP.SlowStartRetransmits.Value = %d, want = %d", got, want)
-		}
-
-		return nil
-	}
-
-	// Poll when checking metrics.
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-
-	// Acknowledge half of the pending data.
-	rtxOffset = bytesRead - expected*maxPayload/2
-	c.SendAck(790, rtxOffset)
-
-	// Receive the remaining data, making sure that acknowledged data is not
-	// retransmitted.
-	for offset := rtxOffset; offset < len(data); offset += maxPayload {
-		c.ReceiveAndCheckPacket(data, offset, maxPayload)
-		c.SendAck(790, offset+maxPayload)
-	}
-
-	c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_rack_test.go b/pkg/tcpip/transport/tcp/tcp_rack_test.go
deleted file mode 100644
index 5cdd5b5..0000000
--- a/pkg/tcpip/transport/tcp/tcp_rack_test.go
+++ /dev/null
@@ -1,1057 +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 tcp_test
-
-import (
-	"bytes"
-	"fmt"
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/seqnum"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
-	"gvisor.dev/gvisor/pkg/test/testutil"
-)
-
-const (
-	maxPayload       = 10
-	tsOptionSize     = 12
-	maxTCPOptionSize = 40
-	mtu              = header.TCPMinimumSize + header.IPv4MinimumSize + maxTCPOptionSize + maxPayload
-)
-
-func setStackRACKPermitted(t *testing.T, c *context.Context) {
-	t.Helper()
-	opt := tcpip.TCPRecovery(tcpip.TCPRACKLossDetection)
-	if err := c.Stack().SetTransportProtocolOption(header.TCPProtocolNumber, &opt); err != nil {
-		t.Fatalf("c.s.SetTransportProtocolOption(%d, &%v(%v)): %s", header.TCPProtocolNumber, opt, opt, err)
-	}
-}
-
-// TestRACKUpdate tests the RACK related fields are updated when an ACK is
-// received on a SACK enabled connection.
-func TestRACKUpdate(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	var xmitTime time.Time
-	probeDone := make(chan struct{})
-	c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
-		// Validate that the endpoint Sender.RACKState is what we expect.
-		if state.Sender.RACKState.XmitTime.Before(xmitTime) {
-			t.Fatalf("RACK transmit time failed to update when an ACK is received")
-		}
-
-		gotSeq := state.Sender.RACKState.EndSequence
-		wantSeq := state.Sender.SndNxt
-		if !gotSeq.LessThanEq(wantSeq) || gotSeq.LessThan(wantSeq) {
-			t.Fatalf("RACK sequence number failed to update, got: %v, but want: %v", gotSeq, wantSeq)
-		}
-
-		if state.Sender.RACKState.RTT == 0 {
-			t.Fatalf("RACK RTT failed to update when an ACK is received, got RACKState.RTT == 0 want != 0")
-		}
-		close(probeDone)
-	})
-	setStackSACKPermitted(t, c, true)
-	setStackRACKPermitted(t, c)
-	createConnectedWithSACKAndTS(c)
-
-	data := make([]byte, maxPayload)
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write the data.
-	xmitTime = time.Now()
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	bytesRead := 0
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-	bytesRead += maxPayload
-	c.SendAck(seqnum.Value(context.TestInitialSequenceNumber).Add(1), bytesRead)
-
-	// Wait for the probe function to finish processing the ACK before the
-	// test completes.
-	<-probeDone
-}
-
-// TestRACKDetectReorder tests that RACK detects packet reordering.
-func TestRACKDetectReorder(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	t.Skipf("Skipping this test as reorder detection does not consider DSACK.")
-
-	var n int
-	const ackNumToVerify = 2
-	probeDone := make(chan struct{})
-	c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
-		gotSeq := state.Sender.RACKState.FACK
-		wantSeq := state.Sender.SndNxt
-		// FACK should be updated to the highest ending sequence number of the
-		// segment acknowledged most recently.
-		if !gotSeq.LessThanEq(wantSeq) || gotSeq.LessThan(wantSeq) {
-			t.Fatalf("RACK FACK failed to update, got: %v, but want: %v", gotSeq, wantSeq)
-		}
-
-		n++
-		if n < ackNumToVerify {
-			if state.Sender.RACKState.Reord {
-				t.Fatalf("RACK reorder detected when there is no reordering")
-			}
-			return
-		}
-
-		if state.Sender.RACKState.Reord == false {
-			t.Fatalf("RACK reorder detection failed")
-		}
-		close(probeDone)
-	})
-	setStackSACKPermitted(t, c, true)
-	setStackRACKPermitted(t, c)
-	createConnectedWithSACKAndTS(c)
-	data := make([]byte, ackNumToVerify*maxPayload)
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write the data.
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	bytesRead := 0
-	for i := 0; i < ackNumToVerify; i++ {
-		c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-		bytesRead += maxPayload
-	}
-
-	start := c.IRS.Add(maxPayload + 1)
-	end := start.Add(maxPayload)
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendAckWithSACK(seq, 0, []header.SACKBlock{{start, end}})
-	c.SendAck(seq, bytesRead)
-
-	// Wait for the probe function to finish processing the ACK before the
-	// test completes.
-	<-probeDone
-}
-
-func sendAndReceiveWithSACK(t *testing.T, c *context.Context, numPackets int, enableRACK bool) []byte {
-	setStackSACKPermitted(t, c, true)
-	if enableRACK {
-		setStackRACKPermitted(t, c)
-	}
-	createConnectedWithSACKAndTS(c)
-
-	data := make([]byte, numPackets*maxPayload)
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write the data.
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	bytesRead := 0
-	for i := 0; i < numPackets; i++ {
-		c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-		bytesRead += maxPayload
-	}
-
-	return data
-}
-
-const (
-	validDSACKDetected   = 1
-	failedToDetectDSACK  = 2
-	invalidDSACKDetected = 3
-)
-
-func addDSACKSeenCheckerProbe(t *testing.T, c *context.Context, numACK int, probeDone chan int) {
-	var n int
-	c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
-		// Validate that RACK detects DSACK.
-		n++
-		if n < numACK {
-			if state.Sender.RACKState.DSACKSeen {
-				probeDone <- invalidDSACKDetected
-			}
-			return
-		}
-
-		if !state.Sender.RACKState.DSACKSeen {
-			probeDone <- failedToDetectDSACK
-			return
-		}
-		probeDone <- validDSACKDetected
-	})
-}
-
-// TestRACKTLPRecovery tests that RACK sends a tail loss probe (TLP) in the
-// case of a tail loss. This simulates a situation where the TLP is able to
-// insinuate the SACK holes and sender is able to retransmit the rest.
-func TestRACKTLPRecovery(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	// Send 8 packets.
-	numPackets := 8
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Packets [6-8] are lost. Send cumulative ACK for [1-5].
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 5 * maxPayload
-	c.SendAck(seq, bytesRead)
-
-	// PTO should fire and send #8 packet as a TLP.
-	c.ReceiveAndCheckPacketWithOptions(data, 7*maxPayload, maxPayload, tsOptionSize)
-	var info tcpip.TCPInfoOption
-	if err := c.EP.GetSockOpt(&info); err != nil {
-		t.Fatalf("GetSockOpt failed: %v", err)
-	}
-
-	// Send the SACK after RTT because RACK RFC states that if the ACK for a
-	// retransmission arrives before the smoothed RTT then the sender should not
-	// update RACK state as it could be a spurious inference.
-	time.Sleep(info.RTT)
-
-	// Okay, let the sender know we got #8 using a SACK block.
-	eighthPStart := c.IRS.Add(1 + seqnum.Size(7*maxPayload))
-	eighthPEnd := eighthPStart.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{eighthPStart, eighthPEnd}})
-
-	// The sender should be entering RACK based loss-recovery and sending #6 and
-	// #7 one after another.
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-	bytesRead += maxPayload
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-	bytesRead += 2 * maxPayload
-	c.SendAck(seq, bytesRead)
-
-	metricPollFn := func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			// One fast retransmit after the SACK.
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 1},
-			// Recovery should be SACK recovery.
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 1},
-			// Packets 6, 7 and 8 were retransmitted.
-			{tcpStats.Retransmits, "stats.TCP.Retransmits", 3},
-			// TLP recovery should have been detected.
-			{tcpStats.TLPRecovery, "stats.TCP.TLPRecovery", 1},
-			// No RTOs should have occurred.
-			{tcpStats.Timeouts, "stats.TCP.Timeouts", 0},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-}
-
-// TestRACKTLPFallbackRTO tests that RACK sends a tail loss probe (TLP) in the
-// case of a tail loss. This simulates a situation where either the TLP or its
-// ACK is lost. The sender should retransmit when RTO fires.
-func TestRACKTLPFallbackRTO(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	// Send 8 packets.
-	numPackets := 8
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Packets [6-8] are lost. Send cumulative ACK for [1-5].
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 5 * maxPayload
-	c.SendAck(seq, bytesRead)
-
-	// PTO should fire and send #8 packet as a TLP.
-	c.ReceiveAndCheckPacketWithOptions(data, 7*maxPayload, maxPayload, tsOptionSize)
-
-	// Either the TLP or the ACK the receiver sent with SACK blocks was lost.
-
-	// Confirm that RTO fires and retransmits packet #6.
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-
-	metricPollFn := func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			// No fast retransmits happened.
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 0},
-			// No SACK recovery happened.
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 0},
-			// TLP was unsuccessful.
-			{tcpStats.TLPRecovery, "stats.TCP.TLPRecovery", 0},
-			// RTO should have fired.
-			{tcpStats.Timeouts, "stats.TCP.Timeouts", 1},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-}
-
-// TestNoTLPRecoveryOnDSACK tests the scenario where the sender speculates a
-// tail loss and sends a TLP. Everything is received and acked. The probe
-// segment is DSACKed. No fast recovery should be triggered in this case.
-func TestNoTLPRecoveryOnDSACK(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	// Send 8 packets.
-	numPackets := 8
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Packets [1-5] are received first. [6-8] took a detour and will take a
-	// while to arrive. Ack [1-5].
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 5 * maxPayload
-	c.SendAck(seq, bytesRead)
-
-	// The tail loss probe (#8 packet) is received.
-	c.ReceiveAndCheckPacketWithOptions(data, 7*maxPayload, maxPayload, tsOptionSize)
-
-	// Now that all 8 packets are received + duplicate 8th packet, send ack.
-	bytesRead += 3 * maxPayload
-	eighthPStart := c.IRS.Add(1 + seqnum.Size(7*maxPayload))
-	eighthPEnd := eighthPStart.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{eighthPStart, eighthPEnd}})
-
-	// Wait for RTO and make sure that nothing else is received.
-	var info tcpip.TCPInfoOption
-	if err := c.EP.GetSockOpt(&info); err != nil {
-		t.Fatalf("GetSockOpt failed: %v", err)
-	}
-	if p := c.GetPacketWithTimeout(info.RTO); p != nil {
-		t.Errorf("received an unexpected packet: %v", p)
-	}
-
-	metricPollFn := func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			// Make sure no recovery was entered.
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 0},
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 0},
-			{tcpStats.TLPRecovery, "stats.TCP.TLPRecovery", 0},
-			// RTO should not have fired.
-			{tcpStats.Timeouts, "stats.TCP.Timeouts", 0},
-			// Only #8 was retransmitted.
-			{tcpStats.Retransmits, "stats.TCP.Retransmits", 1},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-}
-
-// TestNoTLPOnSACK tests the scenario where there is not exactly a tail loss
-// due to the presence of multiple SACK holes. In such a scenario, TLP should
-// not be sent.
-func TestNoTLPOnSACK(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	// Send 8 packets.
-	numPackets := 8
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Packets [1-5] and #7 were received. #6 and #8 were dropped.
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 5 * maxPayload
-	seventhStart := c.IRS.Add(1 + seqnum.Size(6*maxPayload))
-	seventhEnd := seventhStart.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{seventhStart, seventhEnd}})
-
-	// The sender should retransmit #6. If the sender sends a TLP, then #8 will
-	// received and fail this test.
-	c.ReceiveAndCheckPacketWithOptions(data, 5*maxPayload, maxPayload, tsOptionSize)
-
-	metricPollFn := func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			// #6 was retransmitted due to SACK recovery.
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 1},
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 1},
-			{tcpStats.TLPRecovery, "stats.TCP.TLPRecovery", 0},
-			// RTO should not have fired.
-			{tcpStats.Timeouts, "stats.TCP.Timeouts", 0},
-			// Only #6 was retransmitted.
-			{tcpStats.Retransmits, "stats.TCP.Retransmits", 1},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-}
-
-// TestRACKOnePacketTailLoss tests the trivial case of a tail loss of only one
-// packet. The probe should itself repairs the loss instead of having to go
-// into any recovery.
-func TestRACKOnePacketTailLoss(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	// Send 3 packets.
-	numPackets := 3
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Packets [1-2] are received. #3 is lost.
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 2 * maxPayload
-	c.SendAck(seq, bytesRead)
-
-	// PTO should fire and send #3 packet as a TLP.
-	c.ReceiveAndCheckPacketWithOptions(data, 2*maxPayload, maxPayload, tsOptionSize)
-	bytesRead += maxPayload
-	c.SendAck(seq, bytesRead)
-
-	metricPollFn := func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			// #3 was retransmitted as TLP.
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 0},
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 0},
-			{tcpStats.TLPRecovery, "stats.TCP.TLPRecovery", 0},
-			// RTO should not have fired.
-			{tcpStats.Timeouts, "stats.TCP.Timeouts", 0},
-			// Only #3 was retransmitted.
-			{tcpStats.Retransmits, "stats.TCP.Retransmits", 1},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-}
-
-// TestRACKDetectDSACK tests that RACK detects DSACK with duplicate segments.
-// See: https://tools.ietf.org/html/rfc2883#section-4.1.1.
-func TestRACKDetectDSACK(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan int)
-	const ackNumToVerify = 2
-	addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
-
-	numPackets := 8
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Cumulative ACK for [1-5] packets and SACK #8 packet (to prevent TLP).
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 5 * maxPayload
-	eighthPStart := c.IRS.Add(1 + seqnum.Size(7*maxPayload))
-	eighthPEnd := eighthPStart.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{eighthPStart, eighthPEnd}})
-
-	// Expect retransmission of #6 packet after RTO expires.
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-
-	// Send DSACK block for #6 packet indicating both
-	// initial and retransmitted packet are received and
-	// packets [1-8] are received.
-	start := c.IRS.Add(1 + seqnum.Size(bytesRead))
-	end := start.Add(maxPayload)
-	bytesRead += 3 * maxPayload
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Wait for the probe function to finish processing the
-	// ACK before the test completes.
-	err := <-probeDone
-	switch err {
-	case failedToDetectDSACK:
-		t.Fatalf("RACK DSACK detection failed")
-	case invalidDSACKDetected:
-		t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
-	}
-}
-
-// TestRACKDetectDSACKWithOutOfOrder tests that RACK detects DSACK with out of
-// order segments.
-// See: https://tools.ietf.org/html/rfc2883#section-4.1.2.
-func TestRACKDetectDSACKWithOutOfOrder(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan int)
-	const ackNumToVerify = 2
-	addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
-
-	numPackets := 10
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Cumulative ACK for [1-5] packets and SACK for #7 packet (to prevent TLP).
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 5 * maxPayload
-	seventhPStart := c.IRS.Add(1 + seqnum.Size(6*maxPayload))
-	seventhPEnd := seventhPStart.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{seventhPStart, seventhPEnd}})
-
-	// Expect retransmission of #6 packet.
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-
-	// Send DSACK block for #6 packet indicating both
-	// initial and retransmitted packet are received and
-	// packets [1-7] are received.
-	start := c.IRS.Add(1 + seqnum.Size(bytesRead))
-	end := start.Add(maxPayload)
-	bytesRead += 2 * maxPayload
-	// Send DSACK block for #6 along with SACK for out of
-	// order #9 packet.
-	start1 := c.IRS.Add(1 + seqnum.Size(bytesRead) + maxPayload)
-	end1 := start1.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}, {start1, end1}})
-
-	// Wait for the probe function to finish processing the
-	// ACK before the test completes.
-	err := <-probeDone
-	switch err {
-	case failedToDetectDSACK:
-		t.Fatalf("RACK DSACK detection failed")
-	case invalidDSACKDetected:
-		t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
-	}
-}
-
-// TestRACKDetectDSACKWithOutOfOrderDup tests that DSACK is detected on a
-// duplicate of out of order packet.
-// See: https://tools.ietf.org/html/rfc2883#section-4.1.3
-func TestRACKDetectDSACKWithOutOfOrderDup(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan int)
-	const ackNumToVerify = 4
-	addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
-
-	numPackets := 10
-	sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// ACK [1-5] packets.
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 5 * maxPayload
-	c.SendAck(seq, bytesRead)
-
-	// Send SACK indicating #6 packet is missing and received #7 packet.
-	offset := seqnum.Size(bytesRead + maxPayload)
-	start := c.IRS.Add(1 + offset)
-	end := start.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Send SACK with #6 packet is missing and received [7-8] packets.
-	end = start.Add(2 * maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Consider #8 packet is duplicated on the network and send DSACK.
-	dsackStart := c.IRS.Add(1 + offset + maxPayload)
-	dsackEnd := dsackStart.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{dsackStart, dsackEnd}, {start, end}})
-
-	// Wait for the probe function to finish processing the ACK before the
-	// test completes.
-	err := <-probeDone
-	switch err {
-	case failedToDetectDSACK:
-		t.Fatalf("RACK DSACK detection failed")
-	case invalidDSACKDetected:
-		t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
-	}
-}
-
-// TestRACKDetectDSACKSingleDup tests DSACK for a single duplicate subsegment.
-// See: https://tools.ietf.org/html/rfc2883#section-4.2.1.
-func TestRACKDetectDSACKSingleDup(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan int)
-	const ackNumToVerify = 4
-	addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
-
-	numPackets := 4
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Send ACK for #1 packet.
-	bytesRead := maxPayload
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendAck(seq, bytesRead)
-
-	// Missing [2-3] packets and received #4 packet.
-	seq = seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	start := c.IRS.Add(1 + seqnum.Size(3*maxPayload))
-	end := start.Add(seqnum.Size(maxPayload))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Expect retransmission of #2 packet.
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-
-	// ACK for retransmitted #2 packet.
-	bytesRead += maxPayload
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Simulate receving delayed subsegment of #2 packet and delayed #3 packet by
-	// sending DSACK block for the subsegment.
-	dsackStart := c.IRS.Add(1 + seqnum.Size(bytesRead))
-	dsackEnd := dsackStart.Add(seqnum.Size(maxPayload / 2))
-	c.SendAckWithSACK(seq, numPackets*maxPayload, []header.SACKBlock{{dsackStart, dsackEnd}})
-
-	// Wait for the probe function to finish processing the ACK before the
-	// test completes.
-	err := <-probeDone
-	switch err {
-	case failedToDetectDSACK:
-		t.Fatalf("RACK DSACK detection failed")
-	case invalidDSACKDetected:
-		t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
-	}
-}
-
-// TestRACKDetectDSACKDupWithCumulativeACK tests DSACK for two non-contiguous
-// duplicate subsegments covered by the cumulative acknowledgement.
-// See: https://tools.ietf.org/html/rfc2883#section-4.2.2.
-func TestRACKDetectDSACKDupWithCumulativeACK(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan int)
-	const ackNumToVerify = 5
-	addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
-
-	numPackets := 6
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Send ACK for #1 packet.
-	bytesRead := maxPayload
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendAck(seq, bytesRead)
-
-	// Missing [2-5] packets and received #6 packet.
-	seq = seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	start := c.IRS.Add(1 + seqnum.Size(5*maxPayload))
-	end := start.Add(seqnum.Size(maxPayload))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Expect retransmission of #2 packet.
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-
-	// Received delayed #2 packet.
-	bytesRead += maxPayload
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Received delayed #4 packet.
-	start1 := c.IRS.Add(1 + seqnum.Size(3*maxPayload))
-	end1 := start1.Add(seqnum.Size(maxPayload))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start1, end1}, {start, end}})
-
-	// Simulate receiving retransmitted subsegment for #2 packet and delayed #3
-	// packet by sending DSACK block for #2 packet.
-	dsackStart := c.IRS.Add(1 + seqnum.Size(maxPayload))
-	dsackEnd := dsackStart.Add(seqnum.Size(maxPayload / 2))
-	c.SendAckWithSACK(seq, 4*maxPayload, []header.SACKBlock{{dsackStart, dsackEnd}, {start, end}})
-
-	// Wait for the probe function to finish processing the ACK before the
-	// test completes.
-	err := <-probeDone
-	switch err {
-	case failedToDetectDSACK:
-		t.Fatalf("RACK DSACK detection failed")
-	case invalidDSACKDetected:
-		t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
-	}
-}
-
-// TestRACKDetectDSACKDup tests two non-contiguous duplicate subsegments not
-// covered by the cumulative acknowledgement.
-// See: https://tools.ietf.org/html/rfc2883#section-4.2.3.
-func TestRACKDetectDSACKDup(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan int)
-	const ackNumToVerify = 5
-	addDSACKSeenCheckerProbe(t, c, ackNumToVerify, probeDone)
-
-	numPackets := 7
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Send ACK for #1 packet.
-	bytesRead := maxPayload
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendAck(seq, bytesRead)
-
-	// Missing [2-6] packets and SACK #7 packet.
-	seq = seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	start := c.IRS.Add(1 + seqnum.Size(6*maxPayload))
-	end := start.Add(seqnum.Size(maxPayload))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Received delayed #3 packet.
-	start1 := c.IRS.Add(1 + seqnum.Size(2*maxPayload))
-	end1 := start1.Add(seqnum.Size(maxPayload))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start1, end1}, {start, end}})
-
-	// Expect retransmission of #2 packet.
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-
-	// Consider #2 packet has been dropped and SACK #4 packet.
-	start2 := c.IRS.Add(1 + seqnum.Size(3*maxPayload))
-	end2 := start2.Add(seqnum.Size(maxPayload))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start2, end2}, {start1, end1}, {start, end}})
-
-	// Simulate receiving retransmitted subsegment for #3 packet and delayed #5
-	// packet by sending DSACK block for the subsegment.
-	dsackStart := c.IRS.Add(1 + seqnum.Size(2*maxPayload))
-	dsackEnd := dsackStart.Add(seqnum.Size(maxPayload / 2))
-	end1 = end1.Add(seqnum.Size(2 * maxPayload))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{dsackStart, dsackEnd}, {start1, end1}})
-
-	// Wait for the probe function to finish processing the ACK before the
-	// test completes.
-	err := <-probeDone
-	switch err {
-	case failedToDetectDSACK:
-		t.Fatalf("RACK DSACK detection failed")
-	case invalidDSACKDetected:
-		t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
-	}
-}
-
-// TestRACKWithInvalidDSACKBlock tests that DSACK is not detected when DSACK
-// is not the first SACK block.
-func TestRACKWithInvalidDSACKBlock(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan struct{})
-	const ackNumToVerify = 2
-	var n int
-	c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
-		// Validate that RACK does not detect DSACK when DSACK block is
-		// not the first SACK block.
-		n++
-		t.Helper()
-		if state.Sender.RACKState.DSACKSeen {
-			t.Fatalf("RACK DSACK detected when there is no duplicate SACK")
-		}
-
-		if n == ackNumToVerify {
-			close(probeDone)
-		}
-	})
-
-	numPackets := 10
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Cumulative ACK for [1-5] packets and SACK for #7 packet (to prevent TLP).
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	bytesRead := 5 * maxPayload
-	seventhPStart := c.IRS.Add(1 + seqnum.Size(6*maxPayload))
-	seventhPEnd := seventhPStart.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{seventhPStart, seventhPEnd}})
-
-	// Expect retransmission of #6 packet.
-	c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-
-	// Send DSACK block for #6 packet indicating both
-	// initial and retransmitted packet are received and
-	// packets [1-7] are received.
-	start := c.IRS.Add(1 + seqnum.Size(bytesRead))
-	end := start.Add(maxPayload)
-	bytesRead += 2 * maxPayload
-
-	// Send DSACK block as second block. The first block is a SACK for #9 packet.
-	start1 := c.IRS.Add(1 + seqnum.Size(bytesRead) + maxPayload)
-	end1 := start1.Add(maxPayload)
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start1, end1}, {start, end}})
-
-	// Wait for the probe function to finish processing the
-	// ACK before the test completes.
-	<-probeDone
-}
-
-func addReorderWindowCheckerProbe(c *context.Context, numACK int, probeDone chan error) {
-	var n int
-	c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
-		// Validate that RACK detects DSACK.
-		n++
-		if n < numACK {
-			return
-		}
-
-		if state.Sender.RACKState.ReoWnd == 0 || state.Sender.RACKState.ReoWnd > state.Sender.SRTT {
-			probeDone <- fmt.Errorf("got RACKState.ReoWnd: %v, expected it to be greater than 0 and less than %v", state.Sender.RACKState.ReoWnd, state.Sender.SRTT)
-			return
-		}
-
-		if state.Sender.RACKState.ReoWndIncr != 1 {
-			probeDone <- fmt.Errorf("got RACKState.ReoWndIncr: %v, want: 1", state.Sender.RACKState.ReoWndIncr)
-			return
-		}
-
-		if state.Sender.RACKState.ReoWndPersist > 0 {
-			probeDone <- fmt.Errorf("got RACKState.ReoWndPersist: %v, want: greater than 0", state.Sender.RACKState.ReoWndPersist)
-			return
-		}
-		probeDone <- nil
-	})
-}
-
-func TestRACKCheckReorderWindow(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan error)
-	const ackNumToVerify = 3
-	addReorderWindowCheckerProbe(c, ackNumToVerify, probeDone)
-
-	const numPackets = 7
-	sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Send ACK for #1 packet.
-	bytesRead := maxPayload
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendAck(seq, bytesRead)
-
-	// Missing [2-6] packets and SACK #7 packet.
-	start := c.IRS.Add(1 + seqnum.Size(6*maxPayload))
-	end := start.Add(seqnum.Size(maxPayload))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	// Received delayed packets [2-6] which indicates there is reordering
-	// in the connection.
-	bytesRead += 6 * maxPayload
-	c.SendAck(seq, bytesRead)
-
-	// Wait for the probe function to finish processing the ACK before the
-	// test completes.
-	if err := <-probeDone; err != nil {
-		t.Fatalf("unexpected values for RACK variables: %v", err)
-	}
-}
-
-func TestRACKWithDuplicateACK(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	const numPackets = 4
-	data := sendAndReceiveWithSACK(t, c, numPackets, true /* enableRACK */)
-
-	// Send three duplicate ACKs to trigger fast recovery. The first
-	// segment is considered as lost and will be retransmitted after
-	// receiving the duplicate ACKs.
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	start := c.IRS.Add(1 + seqnum.Size(maxPayload))
-	end := start.Add(seqnum.Size(maxPayload))
-	for i := 0; i < 3; i++ {
-		c.SendAckWithSACK(seq, 0, []header.SACKBlock{{start, end}})
-		end = end.Add(seqnum.Size(maxPayload))
-	}
-
-	// Receive the retransmitted packet.
-	c.ReceiveAndCheckPacketWithOptions(data, 0, maxPayload, tsOptionSize)
-
-	metricPollFn := func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 1},
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 1},
-			{tcpStats.FastRecovery, "stats.TCP.FastRecovery", 0},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-}
-
-// TestRACKUpdateSackedOut tests the sacked out field is updated when a SACK
-// is received.
-func TestRACKUpdateSackedOut(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	probeDone := make(chan struct{})
-	ackNum := 0
-	c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
-		// Validate that the endpoint Sender.SackedOut is what we expect.
-		if state.Sender.SackedOut != 2 && ackNum == 0 {
-			t.Fatalf("SackedOut got updated to wrong value got: %v want: 2", state.Sender.SackedOut)
-		}
-
-		if state.Sender.SackedOut != 0 && ackNum == 1 {
-			t.Fatalf("SackedOut got updated to wrong value got: %v want: 0", state.Sender.SackedOut)
-		}
-		if ackNum > 0 {
-			close(probeDone)
-		}
-		ackNum++
-	})
-
-	sendAndReceiveWithSACK(t, c, 8, true /* enableRACK */)
-
-	// ACK for [3-5] packets.
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	start := c.IRS.Add(seqnum.Size(1 + 3*maxPayload))
-	bytesRead := 2 * maxPayload
-	end := start.Add(seqnum.Size(bytesRead))
-	c.SendAckWithSACK(seq, bytesRead, []header.SACKBlock{{start, end}})
-
-	bytesRead += 3 * maxPayload
-	c.SendAck(seq, bytesRead)
-
-	// Wait for the probe function to finish processing the ACK before the
-	// test completes.
-	<-probeDone
-}
-
-// TestRACKWithWindowFull tests that RACK honors the receive window size.
-func TestRACKWithWindowFull(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	setStackSACKPermitted(t, c, true)
-	setStackRACKPermitted(t, c)
-	createConnectedWithSACKAndTS(c)
-
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	const numPkts = 10
-	data := make([]byte, numPkts*maxPayload)
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write the data.
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	bytesRead := 0
-	for i := 0; i < numPkts; i++ {
-		c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-		bytesRead += maxPayload
-		if i == 0 {
-			// Send ACK for the first packet to establish RTT.
-			c.SendAck(seq, maxPayload)
-		}
-	}
-
-	// SACK for #10 packet.
-	start := c.IRS.Add(seqnum.Size(1 + (numPkts-1)*maxPayload))
-	end := start.Add(seqnum.Size(maxPayload))
-	c.SendAckWithSACK(seq, 2*maxPayload, []header.SACKBlock{{start, end}})
-
-	var info tcpip.TCPInfoOption
-	if err := c.EP.GetSockOpt(&info); err != nil {
-		t.Fatalf("GetSockOpt failed: %v", err)
-	}
-	// Wait for RTT to trigger recovery.
-	time.Sleep(info.RTT)
-
-	// Expect retransmission of #2 packet.
-	c.ReceiveAndCheckPacketWithOptions(data, 2*maxPayload, maxPayload, tsOptionSize)
-
-	// Send ACK for #2 packet.
-	c.SendAck(seq, 3*maxPayload)
-
-	// Expect retransmission of #3 packet.
-	c.ReceiveAndCheckPacketWithOptions(data, 3*maxPayload, maxPayload, tsOptionSize)
-
-	// Send ACK with zero window size.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  seq,
-		AckNum:  c.IRS.Add(1 + 4*maxPayload),
-		RcvWnd:  0,
-	})
-
-	// No packet should be received as the receive window size is zero.
-	c.CheckNoPacket("unexpected packet received after userTimeout has expired")
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_sack_test.go b/pkg/tcpip/transport/tcp/tcp_sack_test.go
deleted file mode 100644
index 81f800c..0000000
--- a/pkg/tcpip/transport/tcp/tcp_sack_test.go
+++ /dev/null
@@ -1,705 +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 tcp_test
-
-import (
-	"bytes"
-	"fmt"
-	"log"
-	"reflect"
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/seqnum"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
-	"gvisor.dev/gvisor/pkg/test/testutil"
-)
-
-// createConnectedWithSACKPermittedOption creates and connects c.ep with the
-// SACKPermitted option enabled if the stack in the context has the SACK support
-// enabled.
-func createConnectedWithSACKPermittedOption(c *context.Context) *context.RawEndpoint {
-	return c.CreateConnectedWithOptions(header.TCPSynOptions{SACKPermitted: c.SACKEnabled()})
-}
-
-// createConnectedWithSACKAndTS creates and connects c.ep with the SACK & TS
-// option enabled if the stack in the context has SACK and TS enabled.
-func createConnectedWithSACKAndTS(c *context.Context) *context.RawEndpoint {
-	return c.CreateConnectedWithOptions(header.TCPSynOptions{SACKPermitted: c.SACKEnabled(), TS: true})
-}
-
-func setStackSACKPermitted(t *testing.T, c *context.Context, enable bool) {
-	t.Helper()
-	opt := tcpip.TCPSACKEnabled(enable)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("c.s.SetTransportProtocolOption(%d, &%T(%t)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-}
-
-// TestSackPermittedConnect establishes a connection with the SACK option
-// enabled.
-func TestSackPermittedConnect(t *testing.T) {
-	for _, sackEnabled := range []bool{false, true} {
-		t.Run(fmt.Sprintf("stack.sackEnabled: %v", sackEnabled), func(t *testing.T) {
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			setStackSACKPermitted(t, c, sackEnabled)
-			rep := createConnectedWithSACKPermittedOption(c)
-			data := []byte{1, 2, 3}
-
-			rep.SendPacket(data, nil)
-			savedSeqNum := rep.NextSeqNum
-			rep.VerifyACKNoSACK()
-
-			// Make an out of order packet and send it.
-			rep.NextSeqNum += 3
-			sackBlocks := []header.SACKBlock{
-				{rep.NextSeqNum, rep.NextSeqNum.Add(seqnum.Size(len(data)))},
-			}
-			rep.SendPacket(data, nil)
-
-			// Restore the saved sequence number so that the
-			// VerifyXXX calls use the right sequence number for
-			// checking ACK numbers.
-			rep.NextSeqNum = savedSeqNum
-			if sackEnabled {
-				rep.VerifyACKHasSACK(sackBlocks)
-			} else {
-				rep.VerifyACKNoSACK()
-			}
-
-			// Send the missing segment.
-			rep.SendPacket(data, nil)
-			// The ACK should contain the cumulative ACK for all 9
-			// bytes sent and no SACK blocks.
-			rep.NextSeqNum += 3
-			// Check that no SACK block is returned in the ACK.
-			rep.VerifyACKNoSACK()
-		})
-	}
-}
-
-// TestSackDisabledConnect establishes a connection with the SACK option
-// disabled and verifies that no SACKs are sent for out of order segments.
-func TestSackDisabledConnect(t *testing.T) {
-	for _, sackEnabled := range []bool{false, true} {
-		t.Run(fmt.Sprintf("sackEnabled: %v", sackEnabled), func(t *testing.T) {
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			setStackSACKPermitted(t, c, sackEnabled)
-
-			rep := c.CreateConnectedWithOptions(header.TCPSynOptions{})
-
-			data := []byte{1, 2, 3}
-
-			rep.SendPacket(data, nil)
-			savedSeqNum := rep.NextSeqNum
-			rep.VerifyACKNoSACK()
-
-			// Make an out of order packet and send it.
-			rep.NextSeqNum += 3
-			rep.SendPacket(data, nil)
-
-			// The ACK should contain the older sequence number and
-			// no SACK blocks.
-			rep.NextSeqNum = savedSeqNum
-			rep.VerifyACKNoSACK()
-
-			// Send the missing segment.
-			rep.SendPacket(data, nil)
-			// The ACK should contain the cumulative ACK for all 9
-			// bytes sent and no SACK blocks.
-			rep.NextSeqNum += 3
-			// Check that no SACK block is returned in the ACK.
-			rep.VerifyACKNoSACK()
-		})
-	}
-}
-
-// TestSackPermittedAccept accepts and establishes a connection with the
-// SACKPermitted option enabled if the connection request specifies the
-// SACKPermitted option. In case of SYN cookies SACK should be disabled as we
-// don't encode the SACK information in the cookie.
-func TestSackPermittedAccept(t *testing.T) {
-	type testCase struct {
-		cookieEnabled bool
-		sackPermitted bool
-		wndScale      int
-		wndSize       uint16
-	}
-
-	testCases := []testCase{
-		// When cookie is used window scaling is disabled.
-		{true, false, -1, 0xffff}, // When cookie is used window scaling is disabled.
-		{false, true, 5, 0x8000},  // 0x8000 * 2^5 = 1<<20 = 1MB window (the default).
-	}
-
-	for _, tc := range testCases {
-		t.Run(fmt.Sprintf("test: %#v", tc), func(t *testing.T) {
-			for _, sackEnabled := range []bool{false, true} {
-				t.Run(fmt.Sprintf("test stack.sackEnabled: %v", sackEnabled), func(t *testing.T) {
-					c := context.New(t, defaultMTU)
-					defer c.Cleanup()
-
-					if tc.cookieEnabled {
-						// Set the SynRcvd threshold to
-						// zero to force a syn cookie
-						// based accept to happen.
-						var opt tcpip.TCPSynRcvdCountThresholdOption
-						if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-							t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-						}
-					}
-					setStackSACKPermitted(t, c, sackEnabled)
-
-					rep := c.AcceptWithOptions(tc.wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS, SACKPermitted: tc.sackPermitted})
-					//  Now verify no SACK blocks are
-					//  received when sack is disabled.
-					data := []byte{1, 2, 3}
-					rep.SendPacket(data, nil)
-					rep.VerifyACKNoSACK()
-
-					savedSeqNum := rep.NextSeqNum
-
-					// Make an out of order packet and send
-					// it.
-					rep.NextSeqNum += 3
-					sackBlocks := []header.SACKBlock{
-						{rep.NextSeqNum, rep.NextSeqNum.Add(seqnum.Size(len(data)))},
-					}
-					rep.SendPacket(data, nil)
-
-					// The ACK should contain the older
-					// sequence number.
-					rep.NextSeqNum = savedSeqNum
-					if sackEnabled && tc.sackPermitted {
-						rep.VerifyACKHasSACK(sackBlocks)
-					} else {
-						rep.VerifyACKNoSACK()
-					}
-
-					// Send the missing segment.
-					rep.SendPacket(data, nil)
-					// The ACK should contain the cumulative
-					// ACK for all 9 bytes sent and no SACK
-					// blocks.
-					rep.NextSeqNum += 3
-					// Check that no SACK block is returned
-					// in the ACK.
-					rep.VerifyACKNoSACK()
-				})
-			}
-		})
-	}
-}
-
-// TestSackDisabledAccept accepts and establishes a connection with
-// the SACKPermitted option disabled and verifies that no SACKs are
-// sent for out of order packets.
-func TestSackDisabledAccept(t *testing.T) {
-	type testCase struct {
-		cookieEnabled bool
-		wndScale      int
-		wndSize       uint16
-	}
-
-	testCases := []testCase{
-		// When cookie is used window scaling is disabled.
-		{true, -1, 0xffff}, // When cookie is used window scaling is disabled.
-		{false, 5, 0x8000}, // 0x8000 * 2^5 = 1<<20 = 1MB window (the default).
-	}
-
-	for _, tc := range testCases {
-		t.Run(fmt.Sprintf("test: %#v", tc), func(t *testing.T) {
-			for _, sackEnabled := range []bool{false, true} {
-				t.Run(fmt.Sprintf("test: sackEnabled: %v", sackEnabled), func(t *testing.T) {
-					c := context.New(t, defaultMTU)
-					defer c.Cleanup()
-
-					if tc.cookieEnabled {
-						// Set the SynRcvd threshold to
-						// zero to force a syn cookie
-						// based accept to happen.
-						var opt tcpip.TCPSynRcvdCountThresholdOption
-						if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-							t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-						}
-					}
-
-					setStackSACKPermitted(t, c, sackEnabled)
-
-					rep := c.AcceptWithOptions(tc.wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
-					//  Now verify no SACK blocks are
-					//  received when sack is disabled.
-					data := []byte{1, 2, 3}
-					rep.SendPacket(data, nil)
-					rep.VerifyACKNoSACK()
-					savedSeqNum := rep.NextSeqNum
-
-					// Make an out of order packet and send
-					// it.
-					rep.NextSeqNum += 3
-					rep.SendPacket(data, nil)
-
-					// The ACK should contain the older
-					// sequence number and no SACK blocks.
-					rep.NextSeqNum = savedSeqNum
-					rep.VerifyACKNoSACK()
-
-					// Send the missing segment.
-					rep.SendPacket(data, nil)
-					// The ACK should contain the cumulative
-					// ACK for all 9 bytes sent and no SACK
-					// blocks.
-					rep.NextSeqNum += 3
-					// Check that no SACK block is returned
-					// in the ACK.
-					rep.VerifyACKNoSACK()
-				})
-			}
-		})
-	}
-}
-
-func TestUpdateSACKBlocks(t *testing.T) {
-	testCases := []struct {
-		segStart   seqnum.Value
-		segEnd     seqnum.Value
-		rcvNxt     seqnum.Value
-		sackBlocks []header.SACKBlock
-		updated    []header.SACKBlock
-	}{
-		// Trivial cases where current SACK block list is empty and we
-		// have an out of order delivery.
-		{10, 11, 2, []header.SACKBlock{}, []header.SACKBlock{{10, 11}}},
-		{10, 12, 2, []header.SACKBlock{}, []header.SACKBlock{{10, 12}}},
-		{10, 20, 2, []header.SACKBlock{}, []header.SACKBlock{{10, 20}}},
-
-		// Cases where current SACK block list is not empty and we have
-		// an out of order delivery. Tests that the updated SACK block
-		// list has the first block as the one that contains the new
-		// SACK block representing the segment that was just delivered.
-		{10, 11, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{10, 11}, {12, 20}}},
-		{24, 30, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{24, 30}, {12, 20}}},
-		{24, 30, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{24, 30}, {12, 20}, {32, 40}}},
-
-		// Ensure that we only retain header.MaxSACKBlocks and drop the
-		// oldest one if adding a new block exceeds
-		// header.MaxSACKBlocks.
-		{24, 30, 9,
-			[]header.SACKBlock{{12, 20}, {32, 40}, {42, 50}, {52, 60}, {62, 70}, {72, 80}},
-			[]header.SACKBlock{{24, 30}, {12, 20}, {32, 40}, {42, 50}, {52, 60}, {62, 70}}},
-
-		// Cases where segment extends an existing SACK block.
-		{10, 12, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{10, 20}}},
-		{10, 22, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{10, 22}}},
-		{10, 22, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{10, 22}}},
-		{15, 22, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{12, 22}}},
-		{15, 25, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{12, 25}}},
-		{11, 25, 9, []header.SACKBlock{{12, 20}}, []header.SACKBlock{{11, 25}}},
-		{10, 12, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{10, 20}, {32, 40}}},
-		{10, 22, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{10, 22}, {32, 40}}},
-		{10, 22, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{10, 22}, {32, 40}}},
-		{15, 22, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{12, 22}, {32, 40}}},
-		{15, 25, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{12, 25}, {32, 40}}},
-		{11, 25, 9, []header.SACKBlock{{12, 20}, {32, 40}}, []header.SACKBlock{{11, 25}, {32, 40}}},
-
-		// Cases where segment contains rcvNxt.
-		{10, 20, 15, []header.SACKBlock{{20, 30}, {40, 50}}, []header.SACKBlock{{40, 50}}},
-	}
-
-	for _, tc := range testCases {
-		var sack tcp.SACKInfo
-		copy(sack.Blocks[:], tc.sackBlocks)
-		sack.NumBlocks = len(tc.sackBlocks)
-		tcp.UpdateSACKBlocks(&sack, tc.segStart, tc.segEnd, tc.rcvNxt)
-		if got, want := sack.Blocks[:sack.NumBlocks], tc.updated; !reflect.DeepEqual(got, want) {
-			t.Errorf("UpdateSACKBlocks(%v, %v, %v, %v), got: %v, want: %v", tc.sackBlocks, tc.segStart, tc.segEnd, tc.rcvNxt, got, want)
-		}
-
-	}
-}
-
-func TestTrimSackBlockList(t *testing.T) {
-	testCases := []struct {
-		rcvNxt     seqnum.Value
-		sackBlocks []header.SACKBlock
-		trimmed    []header.SACKBlock
-	}{
-		// Simple cases where we trim whole entries.
-		{2, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}},
-		{21, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{22, 30}, {32, 40}}},
-		{31, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{32, 40}}},
-		{40, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{}},
-		// Cases where we need to update a block.
-		{12, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{12, 20}, {22, 30}, {32, 40}}},
-		{23, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{23, 30}, {32, 40}}},
-		{33, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{{33, 40}}},
-		{41, []header.SACKBlock{{10, 20}, {22, 30}, {32, 40}}, []header.SACKBlock{}},
-	}
-	for _, tc := range testCases {
-		var sack tcp.SACKInfo
-		copy(sack.Blocks[:], tc.sackBlocks)
-		sack.NumBlocks = len(tc.sackBlocks)
-		tcp.TrimSACKBlockList(&sack, tc.rcvNxt)
-		if got, want := sack.Blocks[:sack.NumBlocks], tc.trimmed; !reflect.DeepEqual(got, want) {
-			t.Errorf("TrimSackBlockList(%v, %v), got: %v, want: %v", tc.sackBlocks, tc.rcvNxt, got, want)
-		}
-	}
-}
-
-func TestSACKRecovery(t *testing.T) {
-	const maxPayload = 10
-	// See: tcp.makeOptions for why tsOptionSize is set to 12 here.
-	const tsOptionSize = 12
-	// Enabling SACK means the payload size is reduced to account
-	// for the extra space required for the TCP options.
-	//
-	// We increase the MTU by 40 bytes to account for SACK and Timestamp
-	// options.
-	const maxTCPOptionSize = 40
-
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxTCPOptionSize+maxPayload))
-	defer c.Cleanup()
-
-	c.Stack().AddTCPProbe(func(s stack.TCPEndpointState) {
-		// We use log.Printf instead of t.Logf here because this probe
-		// can fire even when the test function has finished. This is
-		// because closing the endpoint in cleanup() does not mean the
-		// actual worker loop terminates immediately as it still has to
-		// do a full TCP shutdown. But this test can finish running
-		// before the shutdown is done. Using t.Logf in such a case
-		// causes the test to panic due to logging after test finished.
-		log.Printf("state: %+v\n", s)
-	})
-	setStackSACKPermitted(t, c, true)
-	createConnectedWithSACKAndTS(c)
-
-	const iterations = 3
-	data := make([]byte, 2*maxPayload*(tcp.InitialCwnd<<(iterations+1)))
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	// Write all the data in one shot. Packets will only be written at the
-	// MTU size though.
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Do slow start for a few iterations.
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	expected := tcp.InitialCwnd
-	bytesRead := 0
-	for i := 0; i < iterations; i++ {
-		expected = tcp.InitialCwnd << uint(i)
-		if i > 0 {
-			// Acknowledge all the data received so far if not on
-			// first iteration.
-			c.SendAck(seq, bytesRead)
-		}
-
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-			bytesRead += maxPayload
-		}
-
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout("More packets received than expected for this cwnd.", 50*time.Millisecond)
-	}
-
-	// Send 3 duplicate acks. This should force an immediate retransmit of
-	// the pending packet and put the sender into fast recovery.
-	rtxOffset := bytesRead - maxPayload*expected
-	start := c.IRS.Add(seqnum.Size(rtxOffset) + 30 + 1)
-	end := start.Add(10)
-	for i := 0; i < 3; i++ {
-		c.SendAckWithSACK(seq, rtxOffset, []header.SACKBlock{{start, end}})
-		end = end.Add(10)
-	}
-
-	// Receive the retransmitted packet.
-	c.ReceiveAndCheckPacketWithOptions(data, rtxOffset, maxPayload, tsOptionSize)
-
-	metricPollFn := func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 1},
-			{tcpStats.Retransmits, "stats.TCP.Retransmits", 1},
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 1},
-			{tcpStats.FastRecovery, "stats.TCP.FastRecovery", 0},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-
-	// Now send 7 mode duplicate ACKs. In SACK TCP dupAcks do not cause
-	// window inflation and sending of packets is completely handled by the
-	// SACK Recovery algorithm. We should see no packets being released, as
-	// the cwnd at this point after entering recovery should be half of the
-	// outstanding number of packets in flight.
-	for i := 0; i < 7; i++ {
-		c.SendAckWithSACK(seq, rtxOffset, []header.SACKBlock{{start, end}})
-		end = end.Add(10)
-	}
-
-	recover := bytesRead
-
-	// Ensure no new packets arrive.
-	c.CheckNoPacketTimeout("More packets received than expected during recovery after dupacks for this cwnd.",
-		50*time.Millisecond)
-
-	// Acknowledge half of the pending data. This along with the 10 sacked
-	// segments above should reduce the outstanding below the current
-	// congestion window allowing the sender to transmit data.
-	rtxOffset = bytesRead - expected*maxPayload/2
-
-	// Now send a partial ACK w/ a SACK block that indicates that the next 3
-	// segments are lost and we have received 6 segments after the lost
-	// segments. This should cause the sender to immediately transmit all 3
-	// segments in response to this ACK unlike in FastRecovery where only 1
-	// segment is retransmitted per ACK.
-	start = c.IRS.Add(seqnum.Size(rtxOffset) + 30 + 1)
-	end = start.Add(60)
-	c.SendAckWithSACK(seq, rtxOffset, []header.SACKBlock{{start, end}})
-
-	// At this point, we acked expected/2 packets and we SACKED 6 packets and
-	// 3 segments were considered lost due to the SACK block we sent.
-	//
-	// So total packets outstanding can be calculated as follows after 7
-	// iterations of slow start -> 10/20/40/80/160/320/640. So expected
-	// should be 640 at start, then we went to recover at which point the
-	// cwnd should be set to 320 + 3 (for the 3 dupAcks which have left the
-	// network).
-	// Outstanding at this point after acking half the window
-	// (320 packets) will be:
-	//    outstanding = 640-320-6(due to SACK block)-3 = 311
-	//
-	// The last 3 is due to the fact that the first 3 packets after
-	// rtxOffset will be considered lost due to the SACK blocks sent.
-	// Receive the retransmit due to partial ack.
-
-	c.ReceiveAndCheckPacketWithOptions(data, rtxOffset, maxPayload, tsOptionSize)
-	// Receive the 2 extra packets that should have been retransmitted as
-	// those should be considered lost and immediately retransmitted based
-	// on the SACK information in the previous ACK sent above.
-	for i := 0; i < 2; i++ {
-		c.ReceiveAndCheckPacketWithOptions(data, rtxOffset+maxPayload*(i+1), maxPayload, tsOptionSize)
-	}
-
-	// Now we should get 9 more new unsent packets as the cwnd is 323 and
-	// outstanding is 311.
-	for i := 0; i < 9; i++ {
-		c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-		bytesRead += maxPayload
-	}
-
-	metricPollFn = func() error {
-		// In SACK recovery only the first segment is fast retransmitted when
-		// entering recovery.
-		if got, want := c.Stack().Stats().TCP.FastRetransmit.Value(), uint64(1); got != want {
-			return fmt.Errorf("got stats.TCP.FastRetransmit.Value = %d, want = %d", got, want)
-		}
-
-		if got, want := c.EP.Stats().(*tcp.Stats).SendErrors.FastRetransmit.Value(), uint64(1); got != want {
-			return fmt.Errorf("got EP stats SendErrors.FastRetransmit = %d, want = %d", got, want)
-		}
-
-		if got, want := c.Stack().Stats().TCP.Retransmits.Value(), uint64(4); got != want {
-			return fmt.Errorf("got stats.TCP.Retransmits.Value = %d, want = %d", got, want)
-		}
-
-		if got, want := c.EP.Stats().(*tcp.Stats).SendErrors.Retransmits.Value(), uint64(4); got != want {
-			return fmt.Errorf("got EP stats Stats.SendErrors.Retransmits = %d, want = %d", got, want)
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-
-	c.CheckNoPacketTimeout("More packets received than expected during recovery after partial ack for this cwnd.", 50*time.Millisecond)
-
-	// Acknowledge all pending data to recover point.
-	c.SendAck(seq, recover)
-
-	// At this point, the cwnd should reset to expected/2 and there are 9
-	// packets outstanding.
-	//
-	// Now in the first iteration since there are 9 packets outstanding.
-	// We would expect to get expected/2  - 9 packets. But subsequent
-	// iterations will send us expected/2  + 1 (per iteration).
-	expected = expected/2 - 9
-	for i := 0; i < iterations; i++ {
-		// Read all packets expected on this iteration. Don't
-		// acknowledge any of them just yet, so that we can measure the
-		// congestion window.
-		for j := 0; j < expected; j++ {
-			c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-			bytesRead += maxPayload
-		}
-		// Check we don't receive any more packets on this iteration.
-		// The timeout can't be too high or we'll trigger a timeout.
-		c.CheckNoPacketTimeout(fmt.Sprintf("More packets received(after deflation) than expected %d for this cwnd and iteration: %d.", expected, i), 50*time.Millisecond)
-
-		// Acknowledge all the data received so far.
-		c.SendAck(seq, bytesRead)
-
-		// In cogestion avoidance, the packets trains increase by 1 in
-		// each iteration.
-		if i == 0 {
-			// After the first iteration we expect to get the full
-			// congestion window worth of packets in every
-			// iteration.
-			expected += 9
-		}
-		expected++
-	}
-}
-
-// TestRecoveryEntry tests the following two properties of entering recovery:
-// - Fast SACK recovery is entered when SND.UNA is considered lost by the SACK
-//   scoreboard but dupack count is still below threshold.
-// - Only enter recovery when at least one more byte of data beyond the highest
-//   byte that was outstanding when fast retransmit was last entered is acked.
-func TestRecoveryEntry(t *testing.T) {
-	c := context.New(t, uint32(mtu))
-	defer c.Cleanup()
-
-	numPackets := 5
-	data := sendAndReceiveWithSACK(t, c, numPackets, false /* enableRACK */)
-
-	// Ack #1 packet.
-	seq := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendAck(seq, maxPayload)
-
-	// Now SACK #3, #4 and #5 packets. This will simulate a situation where
-	// SND.UNA should be considered lost and the sender should enter fast recovery
-	// (even though dupack count is still below threshold).
-	p3Start := c.IRS.Add(1 + seqnum.Size(2*maxPayload))
-	p3End := p3Start.Add(maxPayload)
-	p4Start := p3End
-	p4End := p4Start.Add(maxPayload)
-	p5Start := p4End
-	p5End := p5Start.Add(maxPayload)
-	c.SendAckWithSACK(seq, maxPayload, []header.SACKBlock{{p3Start, p3End}, {p4Start, p4End}, {p5Start, p5End}})
-
-	// Expect #2 to be retransmitted.
-	c.ReceiveAndCheckPacketWithOptions(data, maxPayload, maxPayload, tsOptionSize)
-
-	metricPollFn := func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			// SACK recovery must have happened.
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 1},
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 1},
-			// #2 was retransmitted.
-			{tcpStats.Retransmits, "stats.TCP.Retransmits", 1},
-			// No RTOs should have fired yet.
-			{tcpStats.Timeouts, "stats.TCP.Timeouts", 0},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-
-	// Send 4 more packets.
-	var r bytes.Reader
-	data = append(data, data...)
-	r.Reset(data[5*maxPayload : 9*maxPayload])
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	var sackBlocks []header.SACKBlock
-	bytesRead := numPackets * maxPayload
-	for i := 0; i < 4; i++ {
-		c.ReceiveAndCheckPacketWithOptions(data, bytesRead, maxPayload, tsOptionSize)
-		if i > 0 {
-			pStart := c.IRS.Add(1 + seqnum.Size(bytesRead))
-			sackBlocks = append(sackBlocks, header.SACKBlock{pStart, pStart.Add(maxPayload)})
-			c.SendAckWithSACK(seq, 5*maxPayload, sackBlocks)
-		}
-		bytesRead += maxPayload
-	}
-
-	// #6 should be retransmitted after RTO. The sender should NOT enter fast
-	// recovery because the highest byte that was outstanding when fast recovery
-	// was last entered is #5 packet's end. And the sender requires at least one
-	// more byte beyond that (#6 packet start) to be acked to enter recovery.
-	c.ReceiveAndCheckPacketWithOptions(data, 5*maxPayload, maxPayload, tsOptionSize)
-	c.SendAck(seq, 9*maxPayload)
-
-	metricPollFn = func() error {
-		tcpStats := c.Stack().Stats().TCP
-		stats := []struct {
-			stat *tcpip.StatCounter
-			name string
-			want uint64
-		}{
-			// Only 1 SACK recovery must have happened.
-			{tcpStats.FastRetransmit, "stats.TCP.FastRetransmit", 1},
-			{tcpStats.SACKRecovery, "stats.TCP.SACKRecovery", 1},
-			// #2 and #6 were retransmitted.
-			{tcpStats.Retransmits, "stats.TCP.Retransmits", 2},
-			// RTO should have fired once.
-			{tcpStats.Timeouts, "stats.TCP.Timeouts", 1},
-		}
-		for _, s := range stats {
-			if got, want := s.stat.Value(), s.want; got != want {
-				return fmt.Errorf("got %s.Value() = %d, want = %d", s.name, got, want)
-			}
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_segment_list.go b/pkg/tcpip/transport/tcp/tcp_segment_list.go
new file mode 100644
index 0000000..a14cff2
--- /dev/null
+++ b/pkg/tcpip/transport/tcp/tcp_segment_list.go
@@ -0,0 +1,221 @@
+package tcp
+
+// 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 segmentElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (segmentElementMapper) linkerFor(elem *segment) *segment { 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 segmentList struct {
+	head *segment
+	tail *segment
+}
+
+// Reset resets list l to the empty state.
+func (l *segmentList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *segmentList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *segmentList) Front() *segment {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *segmentList) Back() *segment {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *segmentList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (segmentElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *segmentList) PushFront(e *segment) {
+	linker := segmentElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		segmentElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *segmentList) PushBack(e *segment) {
+	linker := segmentElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		segmentElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *segmentList) PushBackList(m *segmentList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		segmentElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		segmentElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *segmentList) InsertAfter(b, e *segment) {
+	bLinker := segmentElementMapper{}.linkerFor(b)
+	eLinker := segmentElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		segmentElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *segmentList) InsertBefore(a, e *segment) {
+	aLinker := segmentElementMapper{}.linkerFor(a)
+	eLinker := segmentElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		segmentElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *segmentList) Remove(e *segment) {
+	linker := segmentElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		segmentElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		segmentElementMapper{}.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 segmentEntry struct {
+	next *segment
+	prev *segment
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *segmentEntry) Next() *segment {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *segmentEntry) Prev() *segment {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *segmentEntry) SetNext(elem *segment) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *segmentEntry) SetPrev(elem *segment) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/transport/tcp/tcp_state_autogen.go b/pkg/tcpip/transport/tcp/tcp_state_autogen.go
new file mode 100644
index 0000000..a92bec6
--- /dev/null
+++ b/pkg/tcpip/transport/tcp/tcp_state_autogen.go
@@ -0,0 +1,1114 @@
+// automatically generated by stateify.
+
+package tcp
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+	"gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (c *cubicState) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.cubicState"
+}
+
+func (c *cubicState) StateFields() []string {
+	return []string{
+		"wLastMax",
+		"wMax",
+		"t",
+		"numCongestionEvents",
+		"c",
+		"k",
+		"beta",
+		"wC",
+		"wEst",
+		"s",
+	}
+}
+
+func (c *cubicState) beforeSave() {}
+
+// +checklocksignore
+func (c *cubicState) StateSave(stateSinkObject state.Sink) {
+	c.beforeSave()
+	var tValue unixTime = c.saveT()
+	stateSinkObject.SaveValue(2, tValue)
+	stateSinkObject.Save(0, &c.wLastMax)
+	stateSinkObject.Save(1, &c.wMax)
+	stateSinkObject.Save(3, &c.numCongestionEvents)
+	stateSinkObject.Save(4, &c.c)
+	stateSinkObject.Save(5, &c.k)
+	stateSinkObject.Save(6, &c.beta)
+	stateSinkObject.Save(7, &c.wC)
+	stateSinkObject.Save(8, &c.wEst)
+	stateSinkObject.Save(9, &c.s)
+}
+
+func (c *cubicState) afterLoad() {}
+
+// +checklocksignore
+func (c *cubicState) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &c.wLastMax)
+	stateSourceObject.Load(1, &c.wMax)
+	stateSourceObject.Load(3, &c.numCongestionEvents)
+	stateSourceObject.Load(4, &c.c)
+	stateSourceObject.Load(5, &c.k)
+	stateSourceObject.Load(6, &c.beta)
+	stateSourceObject.Load(7, &c.wC)
+	stateSourceObject.Load(8, &c.wEst)
+	stateSourceObject.Load(9, &c.s)
+	stateSourceObject.LoadValue(2, new(unixTime), func(y interface{}) { c.loadT(y.(unixTime)) })
+}
+
+func (s *SACKInfo) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.SACKInfo"
+}
+
+func (s *SACKInfo) StateFields() []string {
+	return []string{
+		"Blocks",
+		"NumBlocks",
+	}
+}
+
+func (s *SACKInfo) beforeSave() {}
+
+// +checklocksignore
+func (s *SACKInfo) StateSave(stateSinkObject state.Sink) {
+	s.beforeSave()
+	stateSinkObject.Save(0, &s.Blocks)
+	stateSinkObject.Save(1, &s.NumBlocks)
+}
+
+func (s *SACKInfo) afterLoad() {}
+
+// +checklocksignore
+func (s *SACKInfo) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &s.Blocks)
+	stateSourceObject.Load(1, &s.NumBlocks)
+}
+
+func (r *rcvBufAutoTuneParams) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.rcvBufAutoTuneParams"
+}
+
+func (r *rcvBufAutoTuneParams) StateFields() []string {
+	return []string{
+		"measureTime",
+		"copied",
+		"prevCopied",
+		"rtt",
+		"rttMeasureSeqNumber",
+		"rttMeasureTime",
+		"disabled",
+	}
+}
+
+func (r *rcvBufAutoTuneParams) beforeSave() {}
+
+// +checklocksignore
+func (r *rcvBufAutoTuneParams) StateSave(stateSinkObject state.Sink) {
+	r.beforeSave()
+	var measureTimeValue unixTime = r.saveMeasureTime()
+	stateSinkObject.SaveValue(0, measureTimeValue)
+	var rttMeasureTimeValue unixTime = r.saveRttMeasureTime()
+	stateSinkObject.SaveValue(5, rttMeasureTimeValue)
+	stateSinkObject.Save(1, &r.copied)
+	stateSinkObject.Save(2, &r.prevCopied)
+	stateSinkObject.Save(3, &r.rtt)
+	stateSinkObject.Save(4, &r.rttMeasureSeqNumber)
+	stateSinkObject.Save(6, &r.disabled)
+}
+
+func (r *rcvBufAutoTuneParams) afterLoad() {}
+
+// +checklocksignore
+func (r *rcvBufAutoTuneParams) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(1, &r.copied)
+	stateSourceObject.Load(2, &r.prevCopied)
+	stateSourceObject.Load(3, &r.rtt)
+	stateSourceObject.Load(4, &r.rttMeasureSeqNumber)
+	stateSourceObject.Load(6, &r.disabled)
+	stateSourceObject.LoadValue(0, new(unixTime), func(y interface{}) { r.loadMeasureTime(y.(unixTime)) })
+	stateSourceObject.LoadValue(5, new(unixTime), func(y interface{}) { r.loadRttMeasureTime(y.(unixTime)) })
+}
+
+func (e *EndpointInfo) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.EndpointInfo"
+}
+
+func (e *EndpointInfo) StateFields() []string {
+	return []string{
+		"TransportEndpointInfo",
+	}
+}
+
+func (e *EndpointInfo) beforeSave() {}
+
+// +checklocksignore
+func (e *EndpointInfo) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.TransportEndpointInfo)
+}
+
+func (e *EndpointInfo) afterLoad() {}
+
+// +checklocksignore
+func (e *EndpointInfo) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.TransportEndpointInfo)
+}
+
+func (e *endpoint) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.endpoint"
+}
+
+func (e *endpoint) StateFields() []string {
+	return []string{
+		"EndpointInfo",
+		"DefaultSocketOptionsHandler",
+		"waiterQueue",
+		"uniqueID",
+		"hardError",
+		"lastError",
+		"rcvList",
+		"rcvClosed",
+		"rcvBufSize",
+		"rcvBufUsed",
+		"rcvAutoParams",
+		"rcvMemUsed",
+		"ownedByUser",
+		"state",
+		"boundNICID",
+		"ttl",
+		"isConnectNotified",
+		"portFlags",
+		"boundBindToDevice",
+		"boundPortFlags",
+		"boundDest",
+		"effectiveNetProtos",
+		"workerRunning",
+		"workerCleanup",
+		"sendTSOk",
+		"recentTS",
+		"recentTSTime",
+		"tsOffset",
+		"shutdownFlags",
+		"tcpRecovery",
+		"sackPermitted",
+		"sack",
+		"delay",
+		"scoreboard",
+		"segmentQueue",
+		"synRcvdCount",
+		"userMSS",
+		"maxSynRetries",
+		"windowClamp",
+		"sndBufUsed",
+		"sndClosed",
+		"sndBufInQueue",
+		"sndQueue",
+		"cc",
+		"packetTooBigCount",
+		"sndMTU",
+		"keepalive",
+		"userTimeout",
+		"deferAccept",
+		"acceptedChan",
+		"rcv",
+		"snd",
+		"connectingAddress",
+		"amss",
+		"sendTOS",
+		"gso",
+		"tcpLingerTimeout",
+		"closed",
+		"txHash",
+		"owner",
+		"ops",
+		"lastOutOfWindowAckTime",
+	}
+}
+
+// +checklocksignore
+func (e *endpoint) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	var stateValue EndpointState = e.saveState()
+	stateSinkObject.SaveValue(13, stateValue)
+	var recentTSTimeValue unixTime = e.saveRecentTSTime()
+	stateSinkObject.SaveValue(26, recentTSTimeValue)
+	var acceptedChanValue []*endpoint = e.saveAcceptedChan()
+	stateSinkObject.SaveValue(49, acceptedChanValue)
+	var lastOutOfWindowAckTimeValue unixTime = e.saveLastOutOfWindowAckTime()
+	stateSinkObject.SaveValue(61, lastOutOfWindowAckTimeValue)
+	stateSinkObject.Save(0, &e.EndpointInfo)
+	stateSinkObject.Save(1, &e.DefaultSocketOptionsHandler)
+	stateSinkObject.Save(2, &e.waiterQueue)
+	stateSinkObject.Save(3, &e.uniqueID)
+	stateSinkObject.Save(4, &e.hardError)
+	stateSinkObject.Save(5, &e.lastError)
+	stateSinkObject.Save(6, &e.rcvList)
+	stateSinkObject.Save(7, &e.rcvClosed)
+	stateSinkObject.Save(8, &e.rcvBufSize)
+	stateSinkObject.Save(9, &e.rcvBufUsed)
+	stateSinkObject.Save(10, &e.rcvAutoParams)
+	stateSinkObject.Save(11, &e.rcvMemUsed)
+	stateSinkObject.Save(12, &e.ownedByUser)
+	stateSinkObject.Save(14, &e.boundNICID)
+	stateSinkObject.Save(15, &e.ttl)
+	stateSinkObject.Save(16, &e.isConnectNotified)
+	stateSinkObject.Save(17, &e.portFlags)
+	stateSinkObject.Save(18, &e.boundBindToDevice)
+	stateSinkObject.Save(19, &e.boundPortFlags)
+	stateSinkObject.Save(20, &e.boundDest)
+	stateSinkObject.Save(21, &e.effectiveNetProtos)
+	stateSinkObject.Save(22, &e.workerRunning)
+	stateSinkObject.Save(23, &e.workerCleanup)
+	stateSinkObject.Save(24, &e.sendTSOk)
+	stateSinkObject.Save(25, &e.recentTS)
+	stateSinkObject.Save(27, &e.tsOffset)
+	stateSinkObject.Save(28, &e.shutdownFlags)
+	stateSinkObject.Save(29, &e.tcpRecovery)
+	stateSinkObject.Save(30, &e.sackPermitted)
+	stateSinkObject.Save(31, &e.sack)
+	stateSinkObject.Save(32, &e.delay)
+	stateSinkObject.Save(33, &e.scoreboard)
+	stateSinkObject.Save(34, &e.segmentQueue)
+	stateSinkObject.Save(35, &e.synRcvdCount)
+	stateSinkObject.Save(36, &e.userMSS)
+	stateSinkObject.Save(37, &e.maxSynRetries)
+	stateSinkObject.Save(38, &e.windowClamp)
+	stateSinkObject.Save(39, &e.sndBufUsed)
+	stateSinkObject.Save(40, &e.sndClosed)
+	stateSinkObject.Save(41, &e.sndBufInQueue)
+	stateSinkObject.Save(42, &e.sndQueue)
+	stateSinkObject.Save(43, &e.cc)
+	stateSinkObject.Save(44, &e.packetTooBigCount)
+	stateSinkObject.Save(45, &e.sndMTU)
+	stateSinkObject.Save(46, &e.keepalive)
+	stateSinkObject.Save(47, &e.userTimeout)
+	stateSinkObject.Save(48, &e.deferAccept)
+	stateSinkObject.Save(50, &e.rcv)
+	stateSinkObject.Save(51, &e.snd)
+	stateSinkObject.Save(52, &e.connectingAddress)
+	stateSinkObject.Save(53, &e.amss)
+	stateSinkObject.Save(54, &e.sendTOS)
+	stateSinkObject.Save(55, &e.gso)
+	stateSinkObject.Save(56, &e.tcpLingerTimeout)
+	stateSinkObject.Save(57, &e.closed)
+	stateSinkObject.Save(58, &e.txHash)
+	stateSinkObject.Save(59, &e.owner)
+	stateSinkObject.Save(60, &e.ops)
+}
+
+// +checklocksignore
+func (e *endpoint) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.EndpointInfo)
+	stateSourceObject.Load(1, &e.DefaultSocketOptionsHandler)
+	stateSourceObject.LoadWait(2, &e.waiterQueue)
+	stateSourceObject.Load(3, &e.uniqueID)
+	stateSourceObject.Load(4, &e.hardError)
+	stateSourceObject.Load(5, &e.lastError)
+	stateSourceObject.LoadWait(6, &e.rcvList)
+	stateSourceObject.Load(7, &e.rcvClosed)
+	stateSourceObject.Load(8, &e.rcvBufSize)
+	stateSourceObject.Load(9, &e.rcvBufUsed)
+	stateSourceObject.Load(10, &e.rcvAutoParams)
+	stateSourceObject.Load(11, &e.rcvMemUsed)
+	stateSourceObject.Load(12, &e.ownedByUser)
+	stateSourceObject.Load(14, &e.boundNICID)
+	stateSourceObject.Load(15, &e.ttl)
+	stateSourceObject.Load(16, &e.isConnectNotified)
+	stateSourceObject.Load(17, &e.portFlags)
+	stateSourceObject.Load(18, &e.boundBindToDevice)
+	stateSourceObject.Load(19, &e.boundPortFlags)
+	stateSourceObject.Load(20, &e.boundDest)
+	stateSourceObject.Load(21, &e.effectiveNetProtos)
+	stateSourceObject.Load(22, &e.workerRunning)
+	stateSourceObject.Load(23, &e.workerCleanup)
+	stateSourceObject.Load(24, &e.sendTSOk)
+	stateSourceObject.Load(25, &e.recentTS)
+	stateSourceObject.Load(27, &e.tsOffset)
+	stateSourceObject.Load(28, &e.shutdownFlags)
+	stateSourceObject.Load(29, &e.tcpRecovery)
+	stateSourceObject.Load(30, &e.sackPermitted)
+	stateSourceObject.Load(31, &e.sack)
+	stateSourceObject.Load(32, &e.delay)
+	stateSourceObject.Load(33, &e.scoreboard)
+	stateSourceObject.LoadWait(34, &e.segmentQueue)
+	stateSourceObject.Load(35, &e.synRcvdCount)
+	stateSourceObject.Load(36, &e.userMSS)
+	stateSourceObject.Load(37, &e.maxSynRetries)
+	stateSourceObject.Load(38, &e.windowClamp)
+	stateSourceObject.Load(39, &e.sndBufUsed)
+	stateSourceObject.Load(40, &e.sndClosed)
+	stateSourceObject.Load(41, &e.sndBufInQueue)
+	stateSourceObject.LoadWait(42, &e.sndQueue)
+	stateSourceObject.Load(43, &e.cc)
+	stateSourceObject.Load(44, &e.packetTooBigCount)
+	stateSourceObject.Load(45, &e.sndMTU)
+	stateSourceObject.Load(46, &e.keepalive)
+	stateSourceObject.Load(47, &e.userTimeout)
+	stateSourceObject.Load(48, &e.deferAccept)
+	stateSourceObject.LoadWait(50, &e.rcv)
+	stateSourceObject.LoadWait(51, &e.snd)
+	stateSourceObject.Load(52, &e.connectingAddress)
+	stateSourceObject.Load(53, &e.amss)
+	stateSourceObject.Load(54, &e.sendTOS)
+	stateSourceObject.Load(55, &e.gso)
+	stateSourceObject.Load(56, &e.tcpLingerTimeout)
+	stateSourceObject.Load(57, &e.closed)
+	stateSourceObject.Load(58, &e.txHash)
+	stateSourceObject.Load(59, &e.owner)
+	stateSourceObject.Load(60, &e.ops)
+	stateSourceObject.LoadValue(13, new(EndpointState), func(y interface{}) { e.loadState(y.(EndpointState)) })
+	stateSourceObject.LoadValue(26, new(unixTime), func(y interface{}) { e.loadRecentTSTime(y.(unixTime)) })
+	stateSourceObject.LoadValue(49, new([]*endpoint), func(y interface{}) { e.loadAcceptedChan(y.([]*endpoint)) })
+	stateSourceObject.LoadValue(61, new(unixTime), func(y interface{}) { e.loadLastOutOfWindowAckTime(y.(unixTime)) })
+	stateSourceObject.AfterLoad(e.afterLoad)
+}
+
+func (k *keepalive) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.keepalive"
+}
+
+func (k *keepalive) StateFields() []string {
+	return []string{
+		"idle",
+		"interval",
+		"count",
+		"unacked",
+	}
+}
+
+func (k *keepalive) beforeSave() {}
+
+// +checklocksignore
+func (k *keepalive) StateSave(stateSinkObject state.Sink) {
+	k.beforeSave()
+	stateSinkObject.Save(0, &k.idle)
+	stateSinkObject.Save(1, &k.interval)
+	stateSinkObject.Save(2, &k.count)
+	stateSinkObject.Save(3, &k.unacked)
+}
+
+func (k *keepalive) afterLoad() {}
+
+// +checklocksignore
+func (k *keepalive) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &k.idle)
+	stateSourceObject.Load(1, &k.interval)
+	stateSourceObject.Load(2, &k.count)
+	stateSourceObject.Load(3, &k.unacked)
+}
+
+func (rc *rackControl) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.rackControl"
+}
+
+func (rc *rackControl) StateFields() []string {
+	return []string{
+		"dsackSeen",
+		"endSequence",
+		"exitedRecovery",
+		"fack",
+		"minRTT",
+		"reorderSeen",
+		"reoWnd",
+		"reoWndIncr",
+		"reoWndPersist",
+		"rtt",
+		"rttSeq",
+		"xmitTime",
+		"tlpRxtOut",
+		"tlpHighRxt",
+		"snd",
+	}
+}
+
+func (rc *rackControl) beforeSave() {}
+
+// +checklocksignore
+func (rc *rackControl) StateSave(stateSinkObject state.Sink) {
+	rc.beforeSave()
+	var xmitTimeValue unixTime = rc.saveXmitTime()
+	stateSinkObject.SaveValue(11, xmitTimeValue)
+	stateSinkObject.Save(0, &rc.dsackSeen)
+	stateSinkObject.Save(1, &rc.endSequence)
+	stateSinkObject.Save(2, &rc.exitedRecovery)
+	stateSinkObject.Save(3, &rc.fack)
+	stateSinkObject.Save(4, &rc.minRTT)
+	stateSinkObject.Save(5, &rc.reorderSeen)
+	stateSinkObject.Save(6, &rc.reoWnd)
+	stateSinkObject.Save(7, &rc.reoWndIncr)
+	stateSinkObject.Save(8, &rc.reoWndPersist)
+	stateSinkObject.Save(9, &rc.rtt)
+	stateSinkObject.Save(10, &rc.rttSeq)
+	stateSinkObject.Save(12, &rc.tlpRxtOut)
+	stateSinkObject.Save(13, &rc.tlpHighRxt)
+	stateSinkObject.Save(14, &rc.snd)
+}
+
+func (rc *rackControl) afterLoad() {}
+
+// +checklocksignore
+func (rc *rackControl) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &rc.dsackSeen)
+	stateSourceObject.Load(1, &rc.endSequence)
+	stateSourceObject.Load(2, &rc.exitedRecovery)
+	stateSourceObject.Load(3, &rc.fack)
+	stateSourceObject.Load(4, &rc.minRTT)
+	stateSourceObject.Load(5, &rc.reorderSeen)
+	stateSourceObject.Load(6, &rc.reoWnd)
+	stateSourceObject.Load(7, &rc.reoWndIncr)
+	stateSourceObject.Load(8, &rc.reoWndPersist)
+	stateSourceObject.Load(9, &rc.rtt)
+	stateSourceObject.Load(10, &rc.rttSeq)
+	stateSourceObject.Load(12, &rc.tlpRxtOut)
+	stateSourceObject.Load(13, &rc.tlpHighRxt)
+	stateSourceObject.Load(14, &rc.snd)
+	stateSourceObject.LoadValue(11, new(unixTime), func(y interface{}) { rc.loadXmitTime(y.(unixTime)) })
+}
+
+func (r *receiver) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.receiver"
+}
+
+func (r *receiver) StateFields() []string {
+	return []string{
+		"ep",
+		"rcvNxt",
+		"rcvAcc",
+		"rcvWnd",
+		"rcvWUP",
+		"rcvWndScale",
+		"prevBufUsed",
+		"closed",
+		"pendingRcvdSegments",
+		"pendingBufUsed",
+		"lastRcvdAckTime",
+	}
+}
+
+func (r *receiver) beforeSave() {}
+
+// +checklocksignore
+func (r *receiver) StateSave(stateSinkObject state.Sink) {
+	r.beforeSave()
+	var lastRcvdAckTimeValue unixTime = r.saveLastRcvdAckTime()
+	stateSinkObject.SaveValue(10, lastRcvdAckTimeValue)
+	stateSinkObject.Save(0, &r.ep)
+	stateSinkObject.Save(1, &r.rcvNxt)
+	stateSinkObject.Save(2, &r.rcvAcc)
+	stateSinkObject.Save(3, &r.rcvWnd)
+	stateSinkObject.Save(4, &r.rcvWUP)
+	stateSinkObject.Save(5, &r.rcvWndScale)
+	stateSinkObject.Save(6, &r.prevBufUsed)
+	stateSinkObject.Save(7, &r.closed)
+	stateSinkObject.Save(8, &r.pendingRcvdSegments)
+	stateSinkObject.Save(9, &r.pendingBufUsed)
+}
+
+func (r *receiver) afterLoad() {}
+
+// +checklocksignore
+func (r *receiver) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &r.ep)
+	stateSourceObject.Load(1, &r.rcvNxt)
+	stateSourceObject.Load(2, &r.rcvAcc)
+	stateSourceObject.Load(3, &r.rcvWnd)
+	stateSourceObject.Load(4, &r.rcvWUP)
+	stateSourceObject.Load(5, &r.rcvWndScale)
+	stateSourceObject.Load(6, &r.prevBufUsed)
+	stateSourceObject.Load(7, &r.closed)
+	stateSourceObject.Load(8, &r.pendingRcvdSegments)
+	stateSourceObject.Load(9, &r.pendingBufUsed)
+	stateSourceObject.LoadValue(10, new(unixTime), func(y interface{}) { r.loadLastRcvdAckTime(y.(unixTime)) })
+}
+
+func (r *renoState) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.renoState"
+}
+
+func (r *renoState) StateFields() []string {
+	return []string{
+		"s",
+	}
+}
+
+func (r *renoState) beforeSave() {}
+
+// +checklocksignore
+func (r *renoState) StateSave(stateSinkObject state.Sink) {
+	r.beforeSave()
+	stateSinkObject.Save(0, &r.s)
+}
+
+func (r *renoState) afterLoad() {}
+
+// +checklocksignore
+func (r *renoState) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &r.s)
+}
+
+func (rr *renoRecovery) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.renoRecovery"
+}
+
+func (rr *renoRecovery) StateFields() []string {
+	return []string{
+		"s",
+	}
+}
+
+func (rr *renoRecovery) beforeSave() {}
+
+// +checklocksignore
+func (rr *renoRecovery) StateSave(stateSinkObject state.Sink) {
+	rr.beforeSave()
+	stateSinkObject.Save(0, &rr.s)
+}
+
+func (rr *renoRecovery) afterLoad() {}
+
+// +checklocksignore
+func (rr *renoRecovery) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &rr.s)
+}
+
+func (sr *sackRecovery) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.sackRecovery"
+}
+
+func (sr *sackRecovery) StateFields() []string {
+	return []string{
+		"s",
+	}
+}
+
+func (sr *sackRecovery) beforeSave() {}
+
+// +checklocksignore
+func (sr *sackRecovery) StateSave(stateSinkObject state.Sink) {
+	sr.beforeSave()
+	stateSinkObject.Save(0, &sr.s)
+}
+
+func (sr *sackRecovery) afterLoad() {}
+
+// +checklocksignore
+func (sr *sackRecovery) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &sr.s)
+}
+
+func (s *SACKScoreboard) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.SACKScoreboard"
+}
+
+func (s *SACKScoreboard) StateFields() []string {
+	return []string{
+		"smss",
+		"maxSACKED",
+	}
+}
+
+func (s *SACKScoreboard) beforeSave() {}
+
+// +checklocksignore
+func (s *SACKScoreboard) StateSave(stateSinkObject state.Sink) {
+	s.beforeSave()
+	stateSinkObject.Save(0, &s.smss)
+	stateSinkObject.Save(1, &s.maxSACKED)
+}
+
+func (s *SACKScoreboard) afterLoad() {}
+
+// +checklocksignore
+func (s *SACKScoreboard) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &s.smss)
+	stateSourceObject.Load(1, &s.maxSACKED)
+}
+
+func (s *segment) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.segment"
+}
+
+func (s *segment) StateFields() []string {
+	return []string{
+		"segmentEntry",
+		"refCnt",
+		"ep",
+		"qFlags",
+		"srcAddr",
+		"dstAddr",
+		"netProto",
+		"nicID",
+		"data",
+		"hdr",
+		"sequenceNumber",
+		"ackNumber",
+		"flags",
+		"window",
+		"csum",
+		"csumValid",
+		"parsedOptions",
+		"options",
+		"hasNewSACKInfo",
+		"rcvdTime",
+		"xmitTime",
+		"xmitCount",
+		"acked",
+		"dataMemSize",
+		"lost",
+	}
+}
+
+func (s *segment) beforeSave() {}
+
+// +checklocksignore
+func (s *segment) StateSave(stateSinkObject state.Sink) {
+	s.beforeSave()
+	var dataValue buffer.VectorisedView = s.saveData()
+	stateSinkObject.SaveValue(8, dataValue)
+	var optionsValue []byte = s.saveOptions()
+	stateSinkObject.SaveValue(17, optionsValue)
+	var rcvdTimeValue unixTime = s.saveRcvdTime()
+	stateSinkObject.SaveValue(19, rcvdTimeValue)
+	var xmitTimeValue unixTime = s.saveXmitTime()
+	stateSinkObject.SaveValue(20, xmitTimeValue)
+	stateSinkObject.Save(0, &s.segmentEntry)
+	stateSinkObject.Save(1, &s.refCnt)
+	stateSinkObject.Save(2, &s.ep)
+	stateSinkObject.Save(3, &s.qFlags)
+	stateSinkObject.Save(4, &s.srcAddr)
+	stateSinkObject.Save(5, &s.dstAddr)
+	stateSinkObject.Save(6, &s.netProto)
+	stateSinkObject.Save(7, &s.nicID)
+	stateSinkObject.Save(9, &s.hdr)
+	stateSinkObject.Save(10, &s.sequenceNumber)
+	stateSinkObject.Save(11, &s.ackNumber)
+	stateSinkObject.Save(12, &s.flags)
+	stateSinkObject.Save(13, &s.window)
+	stateSinkObject.Save(14, &s.csum)
+	stateSinkObject.Save(15, &s.csumValid)
+	stateSinkObject.Save(16, &s.parsedOptions)
+	stateSinkObject.Save(18, &s.hasNewSACKInfo)
+	stateSinkObject.Save(21, &s.xmitCount)
+	stateSinkObject.Save(22, &s.acked)
+	stateSinkObject.Save(23, &s.dataMemSize)
+	stateSinkObject.Save(24, &s.lost)
+}
+
+func (s *segment) afterLoad() {}
+
+// +checklocksignore
+func (s *segment) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &s.segmentEntry)
+	stateSourceObject.Load(1, &s.refCnt)
+	stateSourceObject.Load(2, &s.ep)
+	stateSourceObject.Load(3, &s.qFlags)
+	stateSourceObject.Load(4, &s.srcAddr)
+	stateSourceObject.Load(5, &s.dstAddr)
+	stateSourceObject.Load(6, &s.netProto)
+	stateSourceObject.Load(7, &s.nicID)
+	stateSourceObject.Load(9, &s.hdr)
+	stateSourceObject.Load(10, &s.sequenceNumber)
+	stateSourceObject.Load(11, &s.ackNumber)
+	stateSourceObject.Load(12, &s.flags)
+	stateSourceObject.Load(13, &s.window)
+	stateSourceObject.Load(14, &s.csum)
+	stateSourceObject.Load(15, &s.csumValid)
+	stateSourceObject.Load(16, &s.parsedOptions)
+	stateSourceObject.Load(18, &s.hasNewSACKInfo)
+	stateSourceObject.Load(21, &s.xmitCount)
+	stateSourceObject.Load(22, &s.acked)
+	stateSourceObject.Load(23, &s.dataMemSize)
+	stateSourceObject.Load(24, &s.lost)
+	stateSourceObject.LoadValue(8, new(buffer.VectorisedView), func(y interface{}) { s.loadData(y.(buffer.VectorisedView)) })
+	stateSourceObject.LoadValue(17, new([]byte), func(y interface{}) { s.loadOptions(y.([]byte)) })
+	stateSourceObject.LoadValue(19, new(unixTime), func(y interface{}) { s.loadRcvdTime(y.(unixTime)) })
+	stateSourceObject.LoadValue(20, new(unixTime), func(y interface{}) { s.loadXmitTime(y.(unixTime)) })
+}
+
+func (q *segmentQueue) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.segmentQueue"
+}
+
+func (q *segmentQueue) StateFields() []string {
+	return []string{
+		"list",
+		"ep",
+		"frozen",
+	}
+}
+
+func (q *segmentQueue) beforeSave() {}
+
+// +checklocksignore
+func (q *segmentQueue) StateSave(stateSinkObject state.Sink) {
+	q.beforeSave()
+	stateSinkObject.Save(0, &q.list)
+	stateSinkObject.Save(1, &q.ep)
+	stateSinkObject.Save(2, &q.frozen)
+}
+
+func (q *segmentQueue) afterLoad() {}
+
+// +checklocksignore
+func (q *segmentQueue) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.LoadWait(0, &q.list)
+	stateSourceObject.Load(1, &q.ep)
+	stateSourceObject.Load(2, &q.frozen)
+}
+
+func (s *sender) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.sender"
+}
+
+func (s *sender) StateFields() []string {
+	return []string{
+		"ep",
+		"lastSendTime",
+		"dupAckCount",
+		"fr",
+		"lr",
+		"sndCwnd",
+		"sndSsthresh",
+		"sndCAAckCount",
+		"outstanding",
+		"sackedOut",
+		"sndWnd",
+		"sndUna",
+		"sndNxt",
+		"rttMeasureSeqNum",
+		"rttMeasureTime",
+		"firstRetransmittedSegXmitTime",
+		"closed",
+		"writeNext",
+		"writeList",
+		"rtt",
+		"rto",
+		"minRTO",
+		"maxRTO",
+		"maxRetries",
+		"maxPayloadSize",
+		"gso",
+		"sndWndScale",
+		"maxSentAck",
+		"state",
+		"cc",
+		"rc",
+	}
+}
+
+func (s *sender) beforeSave() {}
+
+// +checklocksignore
+func (s *sender) StateSave(stateSinkObject state.Sink) {
+	s.beforeSave()
+	var lastSendTimeValue unixTime = s.saveLastSendTime()
+	stateSinkObject.SaveValue(1, lastSendTimeValue)
+	var rttMeasureTimeValue unixTime = s.saveRttMeasureTime()
+	stateSinkObject.SaveValue(14, rttMeasureTimeValue)
+	var firstRetransmittedSegXmitTimeValue unixTime = s.saveFirstRetransmittedSegXmitTime()
+	stateSinkObject.SaveValue(15, firstRetransmittedSegXmitTimeValue)
+	stateSinkObject.Save(0, &s.ep)
+	stateSinkObject.Save(2, &s.dupAckCount)
+	stateSinkObject.Save(3, &s.fr)
+	stateSinkObject.Save(4, &s.lr)
+	stateSinkObject.Save(5, &s.sndCwnd)
+	stateSinkObject.Save(6, &s.sndSsthresh)
+	stateSinkObject.Save(7, &s.sndCAAckCount)
+	stateSinkObject.Save(8, &s.outstanding)
+	stateSinkObject.Save(9, &s.sackedOut)
+	stateSinkObject.Save(10, &s.sndWnd)
+	stateSinkObject.Save(11, &s.sndUna)
+	stateSinkObject.Save(12, &s.sndNxt)
+	stateSinkObject.Save(13, &s.rttMeasureSeqNum)
+	stateSinkObject.Save(16, &s.closed)
+	stateSinkObject.Save(17, &s.writeNext)
+	stateSinkObject.Save(18, &s.writeList)
+	stateSinkObject.Save(19, &s.rtt)
+	stateSinkObject.Save(20, &s.rto)
+	stateSinkObject.Save(21, &s.minRTO)
+	stateSinkObject.Save(22, &s.maxRTO)
+	stateSinkObject.Save(23, &s.maxRetries)
+	stateSinkObject.Save(24, &s.maxPayloadSize)
+	stateSinkObject.Save(25, &s.gso)
+	stateSinkObject.Save(26, &s.sndWndScale)
+	stateSinkObject.Save(27, &s.maxSentAck)
+	stateSinkObject.Save(28, &s.state)
+	stateSinkObject.Save(29, &s.cc)
+	stateSinkObject.Save(30, &s.rc)
+}
+
+// +checklocksignore
+func (s *sender) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &s.ep)
+	stateSourceObject.Load(2, &s.dupAckCount)
+	stateSourceObject.Load(3, &s.fr)
+	stateSourceObject.Load(4, &s.lr)
+	stateSourceObject.Load(5, &s.sndCwnd)
+	stateSourceObject.Load(6, &s.sndSsthresh)
+	stateSourceObject.Load(7, &s.sndCAAckCount)
+	stateSourceObject.Load(8, &s.outstanding)
+	stateSourceObject.Load(9, &s.sackedOut)
+	stateSourceObject.Load(10, &s.sndWnd)
+	stateSourceObject.Load(11, &s.sndUna)
+	stateSourceObject.Load(12, &s.sndNxt)
+	stateSourceObject.Load(13, &s.rttMeasureSeqNum)
+	stateSourceObject.Load(16, &s.closed)
+	stateSourceObject.Load(17, &s.writeNext)
+	stateSourceObject.Load(18, &s.writeList)
+	stateSourceObject.Load(19, &s.rtt)
+	stateSourceObject.Load(20, &s.rto)
+	stateSourceObject.Load(21, &s.minRTO)
+	stateSourceObject.Load(22, &s.maxRTO)
+	stateSourceObject.Load(23, &s.maxRetries)
+	stateSourceObject.Load(24, &s.maxPayloadSize)
+	stateSourceObject.Load(25, &s.gso)
+	stateSourceObject.Load(26, &s.sndWndScale)
+	stateSourceObject.Load(27, &s.maxSentAck)
+	stateSourceObject.Load(28, &s.state)
+	stateSourceObject.Load(29, &s.cc)
+	stateSourceObject.Load(30, &s.rc)
+	stateSourceObject.LoadValue(1, new(unixTime), func(y interface{}) { s.loadLastSendTime(y.(unixTime)) })
+	stateSourceObject.LoadValue(14, new(unixTime), func(y interface{}) { s.loadRttMeasureTime(y.(unixTime)) })
+	stateSourceObject.LoadValue(15, new(unixTime), func(y interface{}) { s.loadFirstRetransmittedSegXmitTime(y.(unixTime)) })
+	stateSourceObject.AfterLoad(s.afterLoad)
+}
+
+func (r *rtt) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.rtt"
+}
+
+func (r *rtt) StateFields() []string {
+	return []string{
+		"srtt",
+		"rttvar",
+		"srttInited",
+	}
+}
+
+func (r *rtt) beforeSave() {}
+
+// +checklocksignore
+func (r *rtt) StateSave(stateSinkObject state.Sink) {
+	r.beforeSave()
+	stateSinkObject.Save(0, &r.srtt)
+	stateSinkObject.Save(1, &r.rttvar)
+	stateSinkObject.Save(2, &r.srttInited)
+}
+
+func (r *rtt) afterLoad() {}
+
+// +checklocksignore
+func (r *rtt) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &r.srtt)
+	stateSourceObject.Load(1, &r.rttvar)
+	stateSourceObject.Load(2, &r.srttInited)
+}
+
+func (f *fastRecovery) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.fastRecovery"
+}
+
+func (f *fastRecovery) StateFields() []string {
+	return []string{
+		"active",
+		"first",
+		"last",
+		"maxCwnd",
+		"highRxt",
+		"rescueRxt",
+	}
+}
+
+func (f *fastRecovery) beforeSave() {}
+
+// +checklocksignore
+func (f *fastRecovery) StateSave(stateSinkObject state.Sink) {
+	f.beforeSave()
+	stateSinkObject.Save(0, &f.active)
+	stateSinkObject.Save(1, &f.first)
+	stateSinkObject.Save(2, &f.last)
+	stateSinkObject.Save(3, &f.maxCwnd)
+	stateSinkObject.Save(4, &f.highRxt)
+	stateSinkObject.Save(5, &f.rescueRxt)
+}
+
+func (f *fastRecovery) afterLoad() {}
+
+// +checklocksignore
+func (f *fastRecovery) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &f.active)
+	stateSourceObject.Load(1, &f.first)
+	stateSourceObject.Load(2, &f.last)
+	stateSourceObject.Load(3, &f.maxCwnd)
+	stateSourceObject.Load(4, &f.highRxt)
+	stateSourceObject.Load(5, &f.rescueRxt)
+}
+
+func (u *unixTime) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.unixTime"
+}
+
+func (u *unixTime) StateFields() []string {
+	return []string{
+		"second",
+		"nano",
+	}
+}
+
+func (u *unixTime) beforeSave() {}
+
+// +checklocksignore
+func (u *unixTime) StateSave(stateSinkObject state.Sink) {
+	u.beforeSave()
+	stateSinkObject.Save(0, &u.second)
+	stateSinkObject.Save(1, &u.nano)
+}
+
+func (u *unixTime) afterLoad() {}
+
+// +checklocksignore
+func (u *unixTime) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &u.second)
+	stateSourceObject.Load(1, &u.nano)
+}
+
+func (l *endpointList) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.endpointList"
+}
+
+func (l *endpointList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *endpointList) beforeSave() {}
+
+// +checklocksignore
+func (l *endpointList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *endpointList) afterLoad() {}
+
+// +checklocksignore
+func (l *endpointList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *endpointEntry) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.endpointEntry"
+}
+
+func (e *endpointEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *endpointEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *endpointEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *endpointEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *endpointEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func (l *segmentList) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.segmentList"
+}
+
+func (l *segmentList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *segmentList) beforeSave() {}
+
+// +checklocksignore
+func (l *segmentList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *segmentList) afterLoad() {}
+
+// +checklocksignore
+func (l *segmentList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *segmentEntry) StateTypeName() string {
+	return "pkg/tcpip/transport/tcp.segmentEntry"
+}
+
+func (e *segmentEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *segmentEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *segmentEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *segmentEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *segmentEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+	state.Register((*cubicState)(nil))
+	state.Register((*SACKInfo)(nil))
+	state.Register((*rcvBufAutoTuneParams)(nil))
+	state.Register((*EndpointInfo)(nil))
+	state.Register((*endpoint)(nil))
+	state.Register((*keepalive)(nil))
+	state.Register((*rackControl)(nil))
+	state.Register((*receiver)(nil))
+	state.Register((*renoState)(nil))
+	state.Register((*renoRecovery)(nil))
+	state.Register((*sackRecovery)(nil))
+	state.Register((*SACKScoreboard)(nil))
+	state.Register((*segment)(nil))
+	state.Register((*segmentQueue)(nil))
+	state.Register((*sender)(nil))
+	state.Register((*rtt)(nil))
+	state.Register((*fastRecovery)(nil))
+	state.Register((*unixTime)(nil))
+	state.Register((*endpointList)(nil))
+	state.Register((*endpointEntry)(nil))
+	state.Register((*segmentList)(nil))
+	state.Register((*segmentEntry)(nil))
+}
diff --git a/pkg/tcpip/transport/tcp/tcp_test.go b/pkg/tcpip/transport/tcp/tcp_test.go
deleted file mode 100644
index 6c86ae1..0000000
--- a/pkg/tcpip/transport/tcp/tcp_test.go
+++ /dev/null
@@ -1,7814 +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 tcp_test
-
-import (
-	"bytes"
-	"fmt"
-	"io/ioutil"
-	"math"
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/rand"
-	"gvisor.dev/gvisor/pkg/sync"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/checker"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
-	"gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
-	"gvisor.dev/gvisor/pkg/tcpip/seqnum"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
-	"gvisor.dev/gvisor/pkg/test/testutil"
-	"gvisor.dev/gvisor/pkg/waiter"
-)
-
-// endpointTester provides helper functions to test a tcpip.Endpoint.
-type endpointTester struct {
-	ep tcpip.Endpoint
-}
-
-// CheckReadError issues a read to the endpoint and checking for an error.
-func (e *endpointTester) CheckReadError(t *testing.T, want tcpip.Error) {
-	t.Helper()
-	res, got := e.ep.Read(ioutil.Discard, tcpip.ReadOptions{})
-	if got != want {
-		t.Fatalf("ep.Read = %s, want %s", got, want)
-	}
-	if diff := cmp.Diff(tcpip.ReadResult{}, res); diff != "" {
-		t.Errorf("ep.Read: unexpected non-zero result (-want +got):\n%s", diff)
-	}
-}
-
-// CheckRead issues a read to the endpoint and checking for a success, returning
-// the data read.
-func (e *endpointTester) CheckRead(t *testing.T) []byte {
-	t.Helper()
-	var buf bytes.Buffer
-	res, err := e.ep.Read(&buf, tcpip.ReadOptions{})
-	if err != nil {
-		t.Fatalf("ep.Read = _, %s; want _, nil", err)
-	}
-	if diff := cmp.Diff(tcpip.ReadResult{
-		Count: buf.Len(),
-		Total: buf.Len(),
-	}, res, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
-		t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
-	}
-	return buf.Bytes()
-}
-
-// CheckReadFull reads from the endpoint for exactly count bytes.
-func (e *endpointTester) CheckReadFull(t *testing.T, count int, notifyRead <-chan struct{}, timeout time.Duration) []byte {
-	t.Helper()
-	var buf bytes.Buffer
-	w := tcpip.LimitedWriter{
-		W: &buf,
-		N: int64(count),
-	}
-	for w.N != 0 {
-		_, err := e.ep.Read(&w, tcpip.ReadOptions{})
-		if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-			// Wait for receive to be notified.
-			select {
-			case <-notifyRead:
-			case <-time.After(timeout):
-				t.Fatalf("Timed out waiting for data to arrive")
-			}
-			continue
-		} else if err != nil {
-			t.Fatalf("ep.Read = _, %s; want _, nil", err)
-		}
-	}
-	return buf.Bytes()
-}
-
-const (
-	// defaultMTU is the MTU, in bytes, used throughout the tests, except
-	// where another value is explicitly used. It is chosen to match the MTU
-	// of loopback interfaces on linux systems.
-	defaultMTU = 65535
-
-	// defaultIPv4MSS is the MSS sent by the network stack in SYN/SYN-ACK for an
-	// IPv4 endpoint when the MTU is set to defaultMTU in the test.
-	defaultIPv4MSS = defaultMTU - header.IPv4MinimumSize - header.TCPMinimumSize
-)
-
-func TestGiveUpConnect(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	var wq waiter.Queue
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	// Register for notification, then start connection attempt.
-	waitEntry, notifyCh := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&waitEntry, waiter.EventHUp)
-	defer wq.EventUnregister(&waitEntry)
-
-	{
-		err := ep.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort})
-		if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-			t.Fatalf("got ep.Connect(...) = %v, want = %s", err, &tcpip.ErrConnectStarted{})
-		}
-	}
-
-	// Close the connection, wait for completion.
-	ep.Close()
-
-	// Wait for ep to become writable.
-	<-notifyCh
-
-	// Call Connect again to retreive the handshake failure status
-	// and stats updates.
-	{
-		err := ep.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort})
-		if _, ok := err.(*tcpip.ErrAborted); !ok {
-			t.Fatalf("got ep.Connect(...) = %v, want = %s", err, &tcpip.ErrAborted{})
-		}
-	}
-
-	if got := c.Stack().Stats().TCP.FailedConnectionAttempts.Value(); got != 1 {
-		t.Errorf("got stats.TCP.FailedConnectionAttempts.Value() = %d, want = 1", got)
-	}
-
-	if got := c.Stack().Stats().TCP.CurrentEstablished.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentEstablished.Value() = %d, want = 0", got)
-	}
-}
-
-func TestConnectIncrementActiveConnection(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	stats := c.Stack().Stats()
-	want := stats.TCP.ActiveConnectionOpenings.Value() + 1
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	if got := stats.TCP.ActiveConnectionOpenings.Value(); got != want {
-		t.Errorf("got stats.TCP.ActtiveConnectionOpenings.Value() = %d, want = %d", got, want)
-	}
-}
-
-func TestConnectDoesNotIncrementFailedConnectionAttempts(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	stats := c.Stack().Stats()
-	want := stats.TCP.FailedConnectionAttempts.Value()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	if got := stats.TCP.FailedConnectionAttempts.Value(); got != want {
-		t.Errorf("got stats.TCP.FailedConnectionAttempts.Value() = %d, want = %d", got, want)
-	}
-	if got := c.EP.Stats().(*tcp.Stats).FailedConnectionAttempts.Value(); got != want {
-		t.Errorf("got EP stats.FailedConnectionAttempts = %d, want = %d", got, want)
-	}
-}
-
-func TestActiveFailedConnectionAttemptIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	stats := c.Stack().Stats()
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	c.EP = ep
-	want := stats.TCP.FailedConnectionAttempts.Value() + 1
-
-	{
-		err := c.EP.Connect(tcpip.FullAddress{NIC: 2, Addr: context.TestAddr, Port: context.TestPort})
-		if _, ok := err.(*tcpip.ErrNoRoute); !ok {
-			t.Errorf("got c.EP.Connect(...) = %v, want = %s", err, &tcpip.ErrNoRoute{})
-		}
-	}
-
-	if got := stats.TCP.FailedConnectionAttempts.Value(); got != want {
-		t.Errorf("got stats.TCP.FailedConnectionAttempts.Value() = %d, want = %d", got, want)
-	}
-	if got := c.EP.Stats().(*tcp.Stats).FailedConnectionAttempts.Value(); got != want {
-		t.Errorf("got EP stats FailedConnectionAttempts = %d, want = %d", got, want)
-	}
-}
-
-func TestCloseWithoutConnect(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	c.EP.Close()
-
-	if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 0", got)
-	}
-}
-
-func TestTCPSegmentsSentIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	stats := c.Stack().Stats()
-	// SYN and ACK
-	want := stats.TCP.SegmentsSent.Value() + 2
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	if got := stats.TCP.SegmentsSent.Value(); got != want {
-		t.Errorf("got stats.TCP.SegmentsSent.Value() = %d, want = %d", got, want)
-	}
-	if got := c.EP.Stats().(*tcp.Stats).SegmentsSent.Value(); got != want {
-		t.Errorf("got EP stats SegmentsSent.Value() = %d, want = %d", got, want)
-	}
-}
-
-func TestTCPResetsSentIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-	stats := c.Stack().Stats()
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	want := stats.TCP.SegmentsSent.Value() + 1
-
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send a SYN request.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	ackHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		// If the AckNum is not the increment of the last sequence number, a RST
-		// segment is sent back in response.
-		AckNum: c.IRS + 2,
-	}
-
-	// Send ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	c.GetPacket()
-
-	metricPollFn := func() error {
-		if got := stats.TCP.ResetsSent.Value(); got != want {
-			return fmt.Errorf("got stats.TCP.ResetsSent.Value() = %d, want = %d", got, want)
-		}
-		return nil
-	}
-	if err := testutil.Poll(metricPollFn, 1*time.Second); err != nil {
-		t.Error(err)
-	}
-}
-
-// TestTCPResetsSentNoICMP confirms that we don't get an ICMP
-// DstUnreachable packet when we try send a packet which is not part
-// of an active session.
-func TestTCPResetsSentNoICMP(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-	stats := c.Stack().Stats()
-
-	// Send a SYN request for a closed port. This should elicit an RST
-	// but NOT an ICMPv4 DstUnreachable packet.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-	})
-
-	// Receive whatever comes back.
-	b := c.GetPacket()
-	ipHdr := header.IPv4(b)
-	if got, want := ipHdr.Protocol(), uint8(header.TCPProtocolNumber); got != want {
-		t.Errorf("unexpected protocol, got = %d, want = %d", got, want)
-	}
-
-	// Read outgoing ICMP stats and check no ICMP DstUnreachable was recorded.
-	sent := stats.ICMP.V4.PacketsSent
-	if got, want := sent.DstUnreachable.Value(), uint64(0); got != want {
-		t.Errorf("got ICMP DstUnreachable.Value() = %d, want = %d", got, want)
-	}
-}
-
-// TestTCPResetSentForACKWhenNotUsingSynCookies checks that the stack generates
-// a RST if an ACK is received on the listening socket for which there is no
-// active handshake in progress and we are not using SYN cookies.
-func TestTCPResetSentForACKWhenNotUsingSynCookies(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set TCPLingerTimeout to 5 seconds so that sockets are marked closed
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send a SYN request.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	ackHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 1,
-	}
-
-	// Send ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Lower stackwide TIME_WAIT timeout so that the reservations
-	// are released instantly on Close.
-	tcpTW := tcpip.TCPTimeWaitTimeoutOption(1 * time.Millisecond)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &tcpTW); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, tcpTW, tcpTW, err)
-	}
-
-	c.EP.Close()
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+1)),
-		checker.TCPAckNum(uint32(iss)+1),
-		checker.TCPFlags(header.TCPFlagFin|header.TCPFlagAck)))
-	finHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 2,
-	}
-
-	c.SendPacket(nil, finHeaders)
-
-	// Get the ACK to the FIN we just sent.
-	c.GetPacket()
-
-	// Since an active close was done we need to wait for a little more than
-	// tcpLingerTimeout for the port reservations to be released and the
-	// socket to move to a CLOSED state.
-	time.Sleep(20 * time.Millisecond)
-
-	// Now resend the same ACK, this ACK should generate a RST as there
-	// should be no endpoint in SYN-RCVD state and we are not using
-	// syn-cookies yet. The reason we send the same ACK is we need a valid
-	// cookie(IRS) generated by the netstack without which the ACK will be
-	// rejected.
-	c.SendPacket(nil, ackHeaders)
-
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+1)),
-		checker.TCPAckNum(0),
-		checker.TCPFlags(header.TCPFlagRst)))
-}
-
-func TestTCPResetsReceivedIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	stats := c.Stack().Stats()
-	want := stats.TCP.ResetsReceived.Value() + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	rcvWnd := seqnum.Size(30000)
-	c.CreateConnected(iss, rcvWnd, -1 /* epRcvBuf */)
-
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		SeqNum:  iss.Add(1),
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  rcvWnd,
-		Flags:   header.TCPFlagRst,
-	})
-
-	if got := stats.TCP.ResetsReceived.Value(); got != want {
-		t.Errorf("got stats.TCP.ResetsReceived.Value() = %d, want = %d", got, want)
-	}
-}
-
-func TestTCPResetsDoNotGenerateResets(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	stats := c.Stack().Stats()
-	want := stats.TCP.ResetsReceived.Value() + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	rcvWnd := seqnum.Size(30000)
-	c.CreateConnected(iss, rcvWnd, -1 /* epRcvBuf */)
-
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		SeqNum:  iss.Add(1),
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  rcvWnd,
-		Flags:   header.TCPFlagRst,
-	})
-
-	if got := stats.TCP.ResetsReceived.Value(); got != want {
-		t.Errorf("got stats.TCP.ResetsReceived.Value() = %d, want = %d", got, want)
-	}
-	c.CheckNoPacketTimeout("got an unexpected packet", 100*time.Millisecond)
-}
-
-func TestActiveHandshake(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-}
-
-func TestNonBlockingClose(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	ep := c.EP
-	c.EP = nil
-
-	// Close the endpoint and measure how long it takes.
-	t0 := time.Now()
-	ep.Close()
-	if diff := time.Now().Sub(t0); diff > 3*time.Second {
-		t.Fatalf("Took too long to close: %s", diff)
-	}
-}
-
-func TestConnectResetAfterClose(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set TCPLinger to 3 seconds so that sockets are marked closed
-	// after 3 second in FIN_WAIT2 state.
-	tcpLingerTimeout := 3 * time.Second
-	opt := tcpip.TCPLingerTimeoutOption(tcpLingerTimeout)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	ep := c.EP
-	c.EP = nil
-
-	// Close the endpoint, make sure we get a FIN segment, then acknowledge
-	// to complete closure of sender, but don't send our own FIN.
-	ep.Close()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	// Wait for the ep to give up waiting for a FIN.
-	time.Sleep(tcpLingerTimeout + 1*time.Second)
-
-	// Now send an ACK and it should trigger a RST as the endpoint should
-	// not exist anymore.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	for {
-		b := c.GetPacket()
-		tcpHdr := header.TCP(header.IPv4(b).Payload())
-		if tcpHdr.Flags() == header.TCPFlagAck|header.TCPFlagFin {
-			// This is a retransmit of the FIN, ignore it.
-			continue
-		}
-
-		checker.IPv4(t, b,
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				// RST is always generated with sndNxt which if the FIN
-				// has been sent will be 1 higher than the sequence number
-				// of the FIN itself.
-				checker.TCPSeqNum(uint32(c.IRS)+2),
-				checker.TCPAckNum(0),
-				checker.TCPFlags(header.TCPFlagRst),
-			),
-		)
-		break
-	}
-}
-
-// TestCurrentConnectedIncrement tests increment of the current
-// established and connected counters.
-func TestCurrentConnectedIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set TCPTimeWaitTimeout to 1 seconds so that sockets are marked closed
-	// after 1 second in TIME_WAIT state.
-	tcpTimeWaitTimeout := 1 * time.Second
-	opt := tcpip.TCPTimeWaitTimeoutOption(tcpTimeWaitTimeout)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	ep := c.EP
-	c.EP = nil
-
-	if got := c.Stack().Stats().TCP.CurrentEstablished.Value(); got != 1 {
-		t.Errorf("got stats.TCP.CurrentEstablished.Value() = %d, want = 1", got)
-	}
-	gotConnected := c.Stack().Stats().TCP.CurrentConnected.Value()
-	if gotConnected != 1 {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 1", gotConnected)
-	}
-
-	ep.Close()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	if got := c.Stack().Stats().TCP.CurrentEstablished.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentEstablished.Value() = %d, want = 0", got)
-	}
-	if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != gotConnected {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = %d", got, gotConnected)
-	}
-
-	// Ack and send FIN as well.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	// Check that the stack acks the FIN.
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+2),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-
-	// Wait for a little more than the TIME-WAIT duration for the socket to
-	// transition to CLOSED state.
-	time.Sleep(1200 * time.Millisecond)
-
-	if got := c.Stack().Stats().TCP.CurrentEstablished.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentEstablished.Value() = %d, want = 0", got)
-	}
-	if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 0", got)
-	}
-}
-
-// TestClosingWithEnqueuedSegments tests handling of still enqueued segments
-// when the endpoint transitions to StateClose. The in-flight segments would be
-// re-enqueued to a any listening endpoint.
-func TestClosingWithEnqueuedSegments(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	ep := c.EP
-	c.EP = nil
-
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateEstablished; got != want {
-		t.Errorf("unexpected endpoint state: want %d, got %d", want, got)
-	}
-
-	// Send a FIN for ESTABLISHED --> CLOSED-WAIT
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagFin | header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Get the ACK for the FIN we sent.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-
-	// Give the stack a few ms to transition the endpoint out of ESTABLISHED
-	// state.
-	time.Sleep(10 * time.Millisecond)
-
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateCloseWait; got != want {
-		t.Errorf("unexpected endpoint state: want %d, got %d", want, got)
-	}
-
-	// Close the application endpoint for CLOSE_WAIT --> LAST_ACK
-	ep.Close()
-
-	// Get the FIN
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateLastAck; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	// Pause the endpoint`s protocolMainLoop.
-	ep.(interface{ StopWork() }).StopWork()
-
-	// Enqueue last ACK followed by an ACK matching the endpoint
-	//
-	// Send Last ACK for LAST_ACK --> CLOSED
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss.Add(1),
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	// Send a packet with ACK set, this would generate RST when
-	// not using SYN cookies as in this test.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss.Add(2),
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	// Unpause endpoint`s protocolMainLoop.
-	ep.(interface{ ResumeWork() }).ResumeWork()
-
-	// Wait for the protocolMainLoop to resume and update state.
-	time.Sleep(10 * time.Millisecond)
-
-	// Expect the endpoint to be closed.
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateClose; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	if got := c.Stack().Stats().TCP.EstablishedClosed.Value(); got != 1 {
-		t.Errorf("got c.Stack().Stats().TCP.EstablishedClosed = %d, want = 1", got)
-	}
-
-	if got := c.Stack().Stats().TCP.CurrentEstablished.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentEstablished.Value() = %d, want = 0", got)
-	}
-
-	// Check if the endpoint was moved to CLOSED and netstack a reset in
-	// response to the ACK packet that we sent after last-ACK.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+2),
-			checker.TCPAckNum(0),
-			checker.TCPFlags(header.TCPFlagRst),
-		),
-	)
-}
-
-func TestSimpleReceive(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	ept := endpointTester{c.EP}
-
-	data := []byte{1, 2, 3}
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(data, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Wait for receive to be notified.
-	select {
-	case <-ch:
-	case <-time.After(1 * time.Second):
-		t.Fatalf("Timed out waiting for data to arrive")
-	}
-
-	// Receive data.
-	v := ept.CheckRead(t)
-	if !bytes.Equal(data, v) {
-		t.Fatalf("got data = %v, want = %v", v, data)
-	}
-
-	// Check that ACK is received.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(len(data))),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-// TestUserSuppliedMSSOnConnect tests that the user supplied MSS is used when
-// creating a new active TCP socket. It should be present in the sent TCP
-// SYN segment.
-func TestUserSuppliedMSSOnConnect(t *testing.T) {
-	const mtu = 5000
-
-	ips := []struct {
-		name        string
-		createEP    func(*context.Context)
-		connectAddr tcpip.Address
-		checker     func(*testing.T, *context.Context, uint16, int)
-		maxMSS      uint16
-	}{
-		{
-			name: "IPv4",
-			createEP: func(c *context.Context) {
-				c.Create(-1)
-			},
-			connectAddr: context.TestAddr,
-			checker: func(t *testing.T, c *context.Context, mss uint16, ws int) {
-				checker.IPv4(t, c.GetPacket(), checker.TCP(
-					checker.DstPort(context.TestPort),
-					checker.TCPFlags(header.TCPFlagSyn),
-					checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: ws})))
-			},
-			maxMSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize,
-		},
-		{
-			name: "IPv6",
-			createEP: func(c *context.Context) {
-				c.CreateV6Endpoint(true)
-			},
-			connectAddr: context.TestV6Addr,
-			checker: func(t *testing.T, c *context.Context, mss uint16, ws int) {
-				checker.IPv6(t, c.GetV6Packet(), checker.TCP(
-					checker.DstPort(context.TestPort),
-					checker.TCPFlags(header.TCPFlagSyn),
-					checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: ws})))
-			},
-			maxMSS: mtu - header.IPv6MinimumSize - header.TCPMinimumSize,
-		},
-	}
-
-	for _, ip := range ips {
-		t.Run(ip.name, func(t *testing.T) {
-			tests := []struct {
-				name   string
-				setMSS uint16
-				expMSS uint16
-			}{
-				{
-					name:   "EqualToMaxMSS",
-					setMSS: ip.maxMSS,
-					expMSS: ip.maxMSS,
-				},
-				{
-					name:   "LessThanMaxMSS",
-					setMSS: ip.maxMSS - 1,
-					expMSS: ip.maxMSS - 1,
-				},
-				{
-					name:   "GreaterThanMaxMSS",
-					setMSS: ip.maxMSS + 1,
-					expMSS: ip.maxMSS,
-				},
-			}
-
-			for _, test := range tests {
-				t.Run(test.name, func(t *testing.T) {
-					c := context.New(t, mtu)
-					defer c.Cleanup()
-
-					ip.createEP(c)
-
-					// Set the MSS socket option.
-					if err := c.EP.SetSockOptInt(tcpip.MaxSegOption, int(test.setMSS)); err != nil {
-						t.Fatalf("SetSockOptInt(MaxSegOption, %d): %s", test.setMSS, err)
-					}
-
-					// Get expected window size.
-					rcvBufSize, err := c.EP.GetSockOptInt(tcpip.ReceiveBufferSizeOption)
-					if err != nil {
-						t.Fatalf("GetSockOptInt(ReceiveBufferSizeOption): %s", err)
-					}
-					ws := tcp.FindWndScale(seqnum.Size(rcvBufSize))
-
-					connectAddr := tcpip.FullAddress{Addr: ip.connectAddr, Port: context.TestPort}
-					{
-						err := c.EP.Connect(connectAddr)
-						if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-							t.Fatalf("Connect(%+v): %s", connectAddr, err)
-						}
-					}
-
-					// Receive SYN packet with our user supplied MSS.
-					ip.checker(t, c, test.expMSS, ws)
-				})
-			}
-		})
-	}
-}
-
-// TestUserSuppliedMSSOnListenAccept tests that the user supplied MSS is used
-// when completing the handshake for a new TCP connection from a TCP
-// listening socket. It should be present in the sent TCP SYN-ACK segment.
-func TestUserSuppliedMSSOnListenAccept(t *testing.T) {
-	const (
-		nonSynCookieAccepts = 2
-		totalAccepts        = 4
-		mtu                 = 5000
-	)
-
-	ips := []struct {
-		name     string
-		createEP func(*context.Context)
-		sendPkt  func(*context.Context, *context.Headers)
-		checker  func(*testing.T, *context.Context, uint16, uint16)
-		maxMSS   uint16
-	}{
-		{
-			name: "IPv4",
-			createEP: func(c *context.Context) {
-				c.Create(-1)
-			},
-			sendPkt: func(c *context.Context, h *context.Headers) {
-				c.SendPacket(nil, h)
-			},
-			checker: func(t *testing.T, c *context.Context, srcPort, mss uint16) {
-				checker.IPv4(t, c.GetPacket(), checker.TCP(
-					checker.DstPort(srcPort),
-					checker.TCPFlags(header.TCPFlagSyn|header.TCPFlagAck),
-					checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: -1})))
-			},
-			maxMSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize,
-		},
-		{
-			name: "IPv6",
-			createEP: func(c *context.Context) {
-				c.CreateV6Endpoint(false)
-			},
-			sendPkt: func(c *context.Context, h *context.Headers) {
-				c.SendV6Packet(nil, h)
-			},
-			checker: func(t *testing.T, c *context.Context, srcPort, mss uint16) {
-				checker.IPv6(t, c.GetV6Packet(), checker.TCP(
-					checker.DstPort(srcPort),
-					checker.TCPFlags(header.TCPFlagSyn|header.TCPFlagAck),
-					checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: -1})))
-			},
-			maxMSS: mtu - header.IPv6MinimumSize - header.TCPMinimumSize,
-		},
-	}
-
-	for _, ip := range ips {
-		t.Run(ip.name, func(t *testing.T) {
-			tests := []struct {
-				name   string
-				setMSS uint16
-				expMSS uint16
-			}{
-				{
-					name:   "EqualToMaxMSS",
-					setMSS: ip.maxMSS,
-					expMSS: ip.maxMSS,
-				},
-				{
-					name:   "LessThanMaxMSS",
-					setMSS: ip.maxMSS - 1,
-					expMSS: ip.maxMSS - 1,
-				},
-				{
-					name:   "GreaterThanMaxMSS",
-					setMSS: ip.maxMSS + 1,
-					expMSS: ip.maxMSS,
-				},
-			}
-
-			for _, test := range tests {
-				t.Run(test.name, func(t *testing.T) {
-					c := context.New(t, mtu)
-					defer c.Cleanup()
-
-					ip.createEP(c)
-
-					// Set the SynRcvd threshold to force a syn cookie based accept to happen.
-					opt := tcpip.TCPSynRcvdCountThresholdOption(nonSynCookieAccepts)
-					if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-						t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-					}
-
-					if err := c.EP.SetSockOptInt(tcpip.MaxSegOption, int(test.setMSS)); err != nil {
-						t.Fatalf("SetSockOptInt(MaxSegOption, %d): %s", test.setMSS, err)
-					}
-
-					bindAddr := tcpip.FullAddress{Port: context.StackPort}
-					if err := c.EP.Bind(bindAddr); err != nil {
-						t.Fatalf("Bind(%+v): %s:", bindAddr, err)
-					}
-
-					if err := c.EP.Listen(totalAccepts); err != nil {
-						t.Fatalf("Listen(%d): %s:", totalAccepts, err)
-					}
-
-					// The first nonSynCookieAccepts packets sent will trigger a gorooutine
-					// based accept. The rest will trigger a cookie based accept.
-					for i := 0; i < totalAccepts; i++ {
-						// Send a SYN requests.
-						iss := seqnum.Value(i)
-						srcPort := context.TestPort + uint16(i)
-						ip.sendPkt(c, &context.Headers{
-							SrcPort: srcPort,
-							DstPort: context.StackPort,
-							Flags:   header.TCPFlagSyn,
-							SeqNum:  iss,
-						})
-
-						// Receive the SYN-ACK reply.
-						ip.checker(t, c, srcPort, test.expMSS)
-					}
-				})
-			}
-		})
-	}
-}
-func TestSendRstOnListenerRxSynAckV4(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.Create(-1)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn | header.TCPFlagAck,
-		SeqNum:  100,
-		AckNum:  200,
-	})
-
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagRst),
-		checker.TCPSeqNum(200)))
-}
-
-func TestSendRstOnListenerRxSynAckV6(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(true)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	c.SendV6Packet(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn | header.TCPFlagAck,
-		SeqNum:  100,
-		AckNum:  200,
-	})
-
-	checker.IPv6(t, c.GetV6Packet(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagRst),
-		checker.TCPSeqNum(200)))
-}
-
-// TestTCPAckBeforeAcceptV4 tests that once the 3-way handshake is complete,
-// peers can send data and expect a response within a reasonable ammount of time
-// without calling Accept on the listening endpoint first.
-//
-// This test uses IPv4.
-func TestTCPAckBeforeAcceptV4(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.Create(-1)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	irs, iss := executeHandshake(t, c, context.TestPort, false /* synCookiesInUse */)
-
-	// Send data before accepting the connection.
-	c.SendPacket([]byte{1, 2, 3, 4}, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-	})
-
-	// Receive ACK for the data we sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(iss+1)),
-		checker.TCPAckNum(uint32(irs+5))))
-}
-
-// TestTCPAckBeforeAcceptV6 tests that once the 3-way handshake is complete,
-// peers can send data and expect a response within a reasonable ammount of time
-// without calling Accept on the listening endpoint first.
-//
-// This test uses IPv6.
-func TestTCPAckBeforeAcceptV6(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(true)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	irs, iss := executeV6Handshake(t, c, context.TestPort, false /* synCookiesInUse */)
-
-	// Send data before accepting the connection.
-	c.SendV6Packet([]byte{1, 2, 3, 4}, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-	})
-
-	// Receive ACK for the data we sent.
-	checker.IPv6(t, c.GetV6Packet(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(iss+1)),
-		checker.TCPAckNum(uint32(irs+5))))
-}
-
-func TestSendRstOnListenerRxAckV4(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.Create(-1 /* epRcvBuf */)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(10 /* backlog */); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagFin | header.TCPFlagAck,
-		SeqNum:  100,
-		AckNum:  200,
-	})
-
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagRst),
-		checker.TCPSeqNum(200)))
-}
-
-func TestSendRstOnListenerRxAckV6(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(true /* v6Only */)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(10 /* backlog */); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	c.SendV6Packet(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagFin | header.TCPFlagAck,
-		SeqNum:  100,
-		AckNum:  200,
-	})
-
-	checker.IPv6(t, c.GetV6Packet(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagRst),
-		checker.TCPSeqNum(200)))
-}
-
-// TestListenShutdown tests for the listening endpoint replying with RST
-// on read shutdown.
-func TestListenShutdown(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.Create(-1 /* epRcvBuf */)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(1 /* backlog */); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	if err := c.EP.Shutdown(tcpip.ShutdownRead); err != nil {
-		t.Fatal("Shutdown failed:", err)
-	}
-
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  100,
-		AckNum:  200,
-	})
-
-	// Expect the listening endpoint to reset the connection.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
-		))
-}
-
-// TestListenCloseWhileConnect tests for the listening endpoint to
-// drain the accept-queue when closed. This should reset all of the
-// pending connections that are waiting to be accepted.
-func TestListenCloseWhileConnect(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.Create(-1 /* epRcvBuf */)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(1 /* backlog */); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	waitEntry, notifyCh := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&waitEntry, waiter.EventIn)
-	defer c.WQ.EventUnregister(&waitEntry)
-
-	executeHandshake(t, c, context.TestPort, false /* synCookiesInUse */)
-	// Wait for the new endpoint created because of handshake to be delivered
-	// to the listening endpoint's accept queue.
-	<-notifyCh
-
-	// Close the listening endpoint.
-	c.EP.Close()
-
-	// Expect the listening endpoint to reset the connection.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
-		))
-}
-
-func TestTOSV4(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	c.EP = ep
-
-	const tos = 0xC0
-	if err := c.EP.SetSockOptInt(tcpip.IPv4TOSOption, tos); err != nil {
-		t.Errorf("SetSockOptInt(IPv4TOSOption, %d) failed: %s", tos, err)
-	}
-
-	v, err := c.EP.GetSockOptInt(tcpip.IPv4TOSOption)
-	if err != nil {
-		t.Errorf("GetSockoptInt(IPv4TOSOption) failed: %s", err)
-	}
-
-	if v != tos {
-		t.Errorf("got GetSockOptInt(IPv4TOSOption) = %d, want = %d", v, tos)
-	}
-
-	testV4Connect(t, c, checker.TOS(tos, 0))
-
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received.
-	b := c.GetPacket()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)), // Acknum is initial sequence number + 1
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-		checker.TOS(tos, 0),
-	)
-
-	if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(data, p) {
-		t.Errorf("got data = %x, want = %x", p, data)
-	}
-}
-
-func TestTrafficClassV6(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateV6Endpoint(false)
-
-	const tos = 0xC0
-	if err := c.EP.SetSockOptInt(tcpip.IPv6TrafficClassOption, tos); err != nil {
-		t.Errorf("SetSockOpInt(IPv6TrafficClassOption, %d) failed: %s", tos, err)
-	}
-
-	v, err := c.EP.GetSockOptInt(tcpip.IPv6TrafficClassOption)
-	if err != nil {
-		t.Fatalf("GetSockoptInt(IPv6TrafficClassOption) failed: %s", err)
-	}
-
-	if v != tos {
-		t.Errorf("got GetSockOptInt(IPv6TrafficClassOption) = %d, want = %d", v, tos)
-	}
-
-	// Test the connection request.
-	testV6Connect(t, c, checker.TOS(tos, 0))
-
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received.
-	b := c.GetV6Packet()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv6(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-		checker.TOS(tos, 0),
-	)
-
-	if p := b[header.IPv6MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(data, p) {
-		t.Errorf("got data = %x, want = %x", p, data)
-	}
-}
-
-func TestConnectBindToDevice(t *testing.T) {
-	for _, test := range []struct {
-		name   string
-		device tcpip.NICID
-		want   tcp.EndpointState
-	}{
-		{"RightDevice", 1, tcp.StateEstablished},
-		{"WrongDevice", 2, tcp.StateSynSent},
-		{"AnyDevice", 0, tcp.StateEstablished},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			c.Create(-1)
-			if err := c.EP.SocketOptions().SetBindToDevice(int32(test.device)); err != nil {
-				t.Fatalf("c.EP.SetSockOpt(&%T(%d)): %s", test.device, test.device, err)
-			}
-			// Start connection attempt.
-			waitEntry, _ := waiter.NewChannelEntry(nil)
-			c.WQ.EventRegister(&waitEntry, waiter.EventOut)
-			defer c.WQ.EventUnregister(&waitEntry)
-
-			err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort})
-			if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-				t.Fatalf("unexpected return value from Connect: %s", err)
-			}
-
-			// Receive SYN packet.
-			b := c.GetPacket()
-			checker.IPv4(t, b,
-				checker.TCP(
-					checker.DstPort(context.TestPort),
-					checker.TCPFlags(header.TCPFlagSyn),
-				),
-			)
-			if got, want := tcp.EndpointState(c.EP.State()), tcp.StateSynSent; got != want {
-				t.Fatalf("unexpected endpoint state: want %s, got %s", want, got)
-			}
-			tcpHdr := header.TCP(header.IPv4(b).Payload())
-			c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-			iss := seqnum.Value(context.TestInitialSequenceNumber)
-			rcvWnd := seqnum.Size(30000)
-			c.SendPacket(nil, &context.Headers{
-				SrcPort: tcpHdr.DestinationPort(),
-				DstPort: tcpHdr.SourcePort(),
-				Flags:   header.TCPFlagSyn | header.TCPFlagAck,
-				SeqNum:  iss,
-				AckNum:  c.IRS.Add(1),
-				RcvWnd:  rcvWnd,
-				TCPOpts: nil,
-			})
-
-			c.GetPacket()
-			if got, want := tcp.EndpointState(c.EP.State()), test.want; got != want {
-				t.Fatalf("unexpected endpoint state: want %s, got %s", want, got)
-			}
-		})
-	}
-}
-
-func TestSynSent(t *testing.T) {
-	for _, test := range []struct {
-		name  string
-		reset bool
-	}{
-		{"RstOnSynSent", true},
-		{"CloseOnSynSent", false},
-	} {
-		t.Run(test.name, func(t *testing.T) {
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			// Create an endpoint, don't handshake because we want to interfere with the
-			// handshake process.
-			c.Create(-1)
-
-			// Start connection attempt.
-			waitEntry, ch := waiter.NewChannelEntry(nil)
-			c.WQ.EventRegister(&waitEntry, waiter.EventHUp)
-			defer c.WQ.EventUnregister(&waitEntry)
-
-			addr := tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort}
-			err := c.EP.Connect(addr)
-			if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-				t.Fatalf("got Connect(%+v) = %v, want %s", addr, err, &tcpip.ErrConnectStarted{})
-			}
-
-			// Receive SYN packet.
-			b := c.GetPacket()
-			checker.IPv4(t, b,
-				checker.TCP(
-					checker.DstPort(context.TestPort),
-					checker.TCPFlags(header.TCPFlagSyn),
-				),
-			)
-
-			if got, want := tcp.EndpointState(c.EP.State()), tcp.StateSynSent; got != want {
-				t.Fatalf("got State() = %s, want %s", got, want)
-			}
-			tcpHdr := header.TCP(header.IPv4(b).Payload())
-			c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-			if test.reset {
-				// Send a packet with a proper ACK and a RST flag to cause the socket
-				// to error and close out.
-				iss := seqnum.Value(context.TestInitialSequenceNumber)
-				rcvWnd := seqnum.Size(30000)
-				c.SendPacket(nil, &context.Headers{
-					SrcPort: tcpHdr.DestinationPort(),
-					DstPort: tcpHdr.SourcePort(),
-					Flags:   header.TCPFlagRst | header.TCPFlagAck,
-					SeqNum:  iss,
-					AckNum:  c.IRS.Add(1),
-					RcvWnd:  rcvWnd,
-					TCPOpts: nil,
-				})
-			} else {
-				c.EP.Close()
-			}
-
-			// Wait for receive to be notified.
-			select {
-			case <-ch:
-			case <-time.After(3 * time.Second):
-				t.Fatal("timed out waiting for packet to arrive")
-			}
-
-			ept := endpointTester{c.EP}
-			if test.reset {
-				ept.CheckReadError(t, &tcpip.ErrConnectionRefused{})
-			} else {
-				ept.CheckReadError(t, &tcpip.ErrAborted{})
-			}
-
-			if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != 0 {
-				t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 0", got)
-			}
-
-			// Due to the RST the endpoint should be in an error state.
-			if got, want := tcp.EndpointState(c.EP.State()), tcp.StateError; got != want {
-				t.Fatalf("got State() = %s, want %s", got, want)
-			}
-		})
-	}
-}
-
-func TestOutOfOrderReceive(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	// Send second half of data first, with seqnum 3 ahead of expected.
-	data := []byte{1, 2, 3, 4, 5, 6}
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(data[3:], &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss.Add(3),
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Check that we get an ACK specifying which seqnum is expected.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-
-	// Wait 200ms and check that no data has been received.
-	time.Sleep(200 * time.Millisecond)
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	// Send the first 3 bytes now.
-	c.SendPacket(data[:3], &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Receive data.
-	read := ept.CheckReadFull(t, 6, ch, 5*time.Second)
-
-	// Check that we received the data in proper order.
-	if !bytes.Equal(data, read) {
-		t.Fatalf("got data = %v, want = %v", read, data)
-	}
-
-	// Check that the whole data is acknowledged.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(len(data))),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestOutOfOrderFlood(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	rcvBufSz := math.MaxUint16
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, rcvBufSz)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	// Send 100 packets before the actual one that is expected.
-	data := []byte{1, 2, 3, 4, 5, 6}
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	for i := 0; i < 100; i++ {
-		c.SendPacket(data[3:], &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss.Add(6),
-			AckNum:  c.IRS.Add(1),
-			RcvWnd:  30000,
-		})
-
-		checker.IPv4(t, c.GetPacket(),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(c.IRS)+1),
-				checker.TCPAckNum(uint32(iss)),
-				checker.TCPFlags(header.TCPFlagAck),
-			),
-		)
-	}
-
-	// Send packet with seqnum as initial + 3. It must be discarded because the
-	// out-of-order buffer was filled by the previous packets.
-	c.SendPacket(data[3:], &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss.Add(3),
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-
-	// Now send the expected packet with initial sequence number.
-	c.SendPacket(data[:3], &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Check that only packet with initial sequence number is acknowledged.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+3),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestRstOnCloseWithUnreadData(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	data := []byte{1, 2, 3}
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(data, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Wait for receive to be notified.
-	select {
-	case <-ch:
-	case <-time.After(3 * time.Second):
-		t.Fatalf("Timed out waiting for data to arrive")
-	}
-
-	// Check that ACK is received, this happens regardless of the read.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(len(data))),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-
-	// Now that we know we have unread data, let's just close the connection
-	// and verify that netstack sends an RST rather than a FIN.
-	c.EP.Close()
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
-			// We shouldn't consume a sequence number on RST.
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-		))
-	// The RST puts the endpoint into an error state.
-	if got, want := tcp.EndpointState(c.EP.State()), tcp.StateError; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	// This final ACK should be ignored because an ACK on a reset doesn't mean
-	// anything.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss.Add(seqnum.Size(len(data))),
-		AckNum:  c.IRS.Add(seqnum.Size(2)),
-		RcvWnd:  30000,
-	})
-}
-
-func TestRstOnCloseWithUnreadDataFinConvertRst(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	data := []byte{1, 2, 3}
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(data, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Wait for receive to be notified.
-	select {
-	case <-ch:
-	case <-time.After(3 * time.Second):
-		t.Fatalf("Timed out waiting for data to arrive")
-	}
-
-	// Check that ACK is received, this happens regardless of the read.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(len(data))),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-
-	// Cause a FIN to be generated.
-	c.EP.Shutdown(tcpip.ShutdownWrite)
-
-	// Make sure we get the FIN but DON't ACK IT.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-		))
-
-	if got, want := tcp.EndpointState(c.EP.State()), tcp.StateFinWait1; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	// Cause a RST to be generated by closing the read end now since we have
-	// unread data.
-	c.EP.Shutdown(tcpip.ShutdownRead)
-
-	// Make sure we get the RST
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
-			// RST is always generated with sndNxt which if the FIN
-			// has been sent will be 1 higher than the sequence
-			// number of the FIN itself.
-			checker.TCPSeqNum(uint32(c.IRS)+2),
-		))
-	// The RST puts the endpoint into an error state.
-	if got, want := tcp.EndpointState(c.EP.State()), tcp.StateError; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	// The ACK to the FIN should now be rejected since the connection has been
-	// closed by a RST.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss.Add(seqnum.Size(len(data))),
-		AckNum:  c.IRS.Add(seqnum.Size(2)),
-		RcvWnd:  30000,
-	})
-}
-
-func TestShutdownRead(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	if err := c.EP.Shutdown(tcpip.ShutdownRead); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	ept.CheckReadError(t, &tcpip.ErrClosedForReceive{})
-	var want uint64 = 1
-	if got := c.EP.Stats().(*tcp.Stats).ReadErrors.ReadClosed.Value(); got != want {
-		t.Fatalf("got EP stats Stats.ReadErrors.ReadClosed got %d want %d", got, want)
-	}
-}
-
-func TestFullWindowReceive(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	const rcvBufSz = 10
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, rcvBufSz)
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	// Fill up the window w/ tcp.SegOverheadFactor*rcvBufSz as netstack multiplies
-	// the provided buffer value by tcp.SegOverheadFactor to calculate the actual
-	// receive buffer size.
-	data := make([]byte, tcp.SegOverheadFactor*rcvBufSz)
-	for i := range data {
-		data[i] = byte(i % 255)
-	}
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(data, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Wait for receive to be notified.
-	select {
-	case <-ch:
-	case <-time.After(5 * time.Second):
-		t.Fatalf("Timed out waiting for data to arrive")
-	}
-
-	// Check that data is acknowledged, and window goes to zero.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(len(data))),
-			checker.TCPFlags(header.TCPFlagAck),
-			checker.TCPWindow(0),
-		),
-	)
-
-	// Receive data and check it.
-	v := ept.CheckRead(t)
-	if !bytes.Equal(data, v) {
-		t.Fatalf("got data = %v, want = %v", v, data)
-	}
-
-	var want uint64 = 1
-	if got := c.EP.Stats().(*tcp.Stats).ReceiveErrors.ZeroRcvWindowState.Value(); got != want {
-		t.Fatalf("got EP stats ReceiveErrors.ZeroRcvWindowState got %d want %d", got, want)
-	}
-
-	// Check that we get an ACK for the newly non-zero window.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(len(data))),
-			checker.TCPFlags(header.TCPFlagAck),
-			checker.TCPWindow(10),
-		),
-	)
-}
-
-// Test the stack receive window advertisement on receiving segments smaller than
-// segment overhead. It tests for the right edge of the window to not grow when
-// the endpoint is not being read from.
-func TestSmallSegReceiveWindowAdvertisement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	opt := tcpip.TCPReceiveBufferSizeRangeOption{
-		Min:     1,
-		Default: tcp.DefaultReceiveBufferSize,
-		Max:     tcp.DefaultReceiveBufferSize << tcp.FindWndScale(seqnum.Size(tcp.DefaultReceiveBufferSize)),
-	}
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%#v): %s", tcp.ProtocolNumber, opt, err)
-	}
-
-	c.AcceptWithOptions(tcp.FindWndScale(seqnum.Size(opt.Default)), header.TCPSynOptions{MSS: defaultIPv4MSS})
-
-	// Bump up the receive buffer size such that, when the receive window grows,
-	// the scaled window exceeds maxUint16.
-	if err := c.EP.SetSockOptInt(tcpip.ReceiveBufferSizeOption, opt.Max); err != nil {
-		t.Fatalf("SetSockOptInt(ReceiveBufferSizeOption, %d) failed: %s", opt.Max, err)
-	}
-
-	// Keep the payload size < segment overhead and such that it is a multiple
-	// of the window scaled value. This enables the test to perform equality
-	// checks on the incoming receive window.
-	payloadSize := 1 << c.RcvdWindowScale
-	if payloadSize >= tcp.SegSize {
-		t.Fatalf("payload size of %d is not less than the segment overhead of %d", payloadSize, tcp.SegSize)
-	}
-	payload := generateRandomPayload(t, payloadSize)
-	payloadLen := seqnum.Size(len(payload))
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-
-	// Send payload to the endpoint and return the advertised receive window
-	// from the endpoint.
-	getIncomingRcvWnd := func() uint32 {
-		c.SendPacket(payload, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			SeqNum:  iss,
-			AckNum:  c.IRS.Add(1),
-			Flags:   header.TCPFlagAck,
-			RcvWnd:  30000,
-		})
-		iss = iss.Add(payloadLen)
-
-		pkt := c.GetPacket()
-		return uint32(header.TCP(header.IPv4(pkt).Payload()).WindowSize()) << c.RcvdWindowScale
-	}
-
-	// Read the advertised receive window with the ACK for payload.
-	rcvWnd := getIncomingRcvWnd()
-
-	// Check if the subsequent ACK to our send has not grown the right edge of
-	// the window.
-	if got, want := getIncomingRcvWnd(), rcvWnd-uint32(len(payload)); got != want {
-		t.Fatalf("got incomingRcvwnd %d want %d", got, want)
-	}
-
-	// Read the data so that the subsequent ACK from the endpoint
-	// grows the right edge of the window.
-	var buf bytes.Buffer
-	if _, err := c.EP.Read(&buf, tcpip.ReadOptions{}); err != nil {
-		t.Fatalf("c.EP.Read: %s", err)
-	}
-
-	// Check if we have received max uint16 as our advertised
-	// scaled window now after a read above.
-	maxRcv := uint32(math.MaxUint16 << c.RcvdWindowScale)
-	if got, want := getIncomingRcvWnd(), maxRcv; got != want {
-		t.Fatalf("got incomingRcvwnd %d want %d", got, want)
-	}
-
-	// Check if the subsequent ACK to our send has not grown the right edge of
-	// the window.
-	if got, want := getIncomingRcvWnd(), maxRcv-uint32(len(payload)); got != want {
-		t.Fatalf("got incomingRcvwnd %d want %d", got, want)
-	}
-}
-
-func TestNoWindowShrinking(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Start off with a certain receive buffer then cut it in half and verify that
-	// the right edge of the window does not shrink.
-	// NOTE: Netstack doubles the value specified here.
-	rcvBufSize := 65536
-	// Enable window scaling with a scale of zero from our end.
-	c.CreateConnectedWithRawOptions(context.TestInitialSequenceNumber, 30000, rcvBufSize, []byte{
-		header.TCPOptionWS, 3, 0, header.TCPOptionNOP,
-	})
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	// Send a 1 byte payload so that we can record the current receive window.
-	// Send a payload of half the size of rcvBufSize.
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	payload := []byte{1}
-	c.SendPacket(payload, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Wait for receive to be notified.
-	select {
-	case <-ch:
-	case <-time.After(5 * time.Second):
-		t.Fatalf("Timed out waiting for data to arrive")
-	}
-
-	// Read the 1 byte payload we just sent.
-	if got, want := payload, ept.CheckRead(t); !bytes.Equal(got, want) {
-		t.Fatalf("got data: %v, want: %v", got, want)
-	}
-
-	// Verify that the ACK does not shrink the window.
-	pkt := c.GetPacket()
-	iss = iss.Add(1)
-	checker.IPv4(t, pkt,
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-	// Stash the initial window.
-	initialWnd := header.TCP(header.IPv4(pkt).Payload()).WindowSize() << c.RcvdWindowScale
-	initialLastAcceptableSeq := iss.Add(seqnum.Size(initialWnd))
-	// Now shrink the receive buffer to half its original size.
-	if err := c.EP.SetSockOptInt(tcpip.ReceiveBufferSizeOption, rcvBufSize/2); err != nil {
-		t.Fatalf("SetSockOptInt(ReceiveBufferSizeOption, 5) failed: %s", err)
-	}
-
-	data := generateRandomPayload(t, rcvBufSize)
-	// Send a payload of half the size of rcvBufSize.
-	c.SendPacket(data[:rcvBufSize/2], &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-	iss = iss.Add(seqnum.Size(rcvBufSize / 2))
-
-	// Verify that the ACK does not shrink the window.
-	pkt = c.GetPacket()
-	checker.IPv4(t, pkt,
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-	newWnd := header.TCP(header.IPv4(pkt).Payload()).WindowSize() << c.RcvdWindowScale
-	newLastAcceptableSeq := iss.Add(seqnum.Size(newWnd))
-	if newLastAcceptableSeq.LessThan(initialLastAcceptableSeq) {
-		t.Fatalf("receive window shrunk unexpectedly got: %d, want >= %d", newLastAcceptableSeq, initialLastAcceptableSeq)
-	}
-
-	// Send another payload of half the size of rcvBufSize. This should fill up the
-	// socket receive buffer and we should see a zero window.
-	c.SendPacket(data[rcvBufSize/2:], &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-	iss = iss.Add(seqnum.Size(rcvBufSize / 2))
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck),
-			checker.TCPWindow(0),
-		),
-	)
-
-	// Receive data and check it.
-	read := ept.CheckReadFull(t, len(data), ch, 5*time.Second)
-	if !bytes.Equal(data, read) {
-		t.Fatalf("got data = %v, want = %v", read, data)
-	}
-
-	// Check that we get an ACK for the newly non-zero window, which is the new
-	// receive buffer size we set after the connection was established.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck),
-			checker.TCPWindow(uint16(rcvBufSize/2)>>c.RcvdWindowScale),
-		),
-	)
-}
-
-func TestSimpleSend(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received.
-	b := c.GetPacket()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(data, p) {
-		t.Fatalf("got data = %v, want = %v", p, data)
-	}
-
-	// Acknowledge the data.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1 + seqnum.Size(len(data))),
-		RcvWnd:  30000,
-	})
-}
-
-func TestZeroWindowSend(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 0 /* rcvWnd */, -1 /* epRcvBuf */)
-
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check if we got a zero-window probe.
-	b := c.GetPacket()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, b,
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	// Open up the window. Data should be received now.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Check that data is received.
-	b = c.GetPacket()
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(data, p) {
-		t.Fatalf("got data = %v, want = %v", p, data)
-	}
-
-	// Acknowledge the data.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1 + seqnum.Size(len(data))),
-		RcvWnd:  30000,
-	})
-}
-
-func TestScaledWindowConnect(t *testing.T) {
-	// This test ensures that window scaling is used when the peer
-	// does advertise it and connection is established with Connect().
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set the window size greater than the maximum non-scaled window.
-	c.CreateConnectedWithRawOptions(context.TestInitialSequenceNumber, 30000, 65535*3, []byte{
-		header.TCPOptionWS, 3, 0, header.TCPOptionNOP,
-	})
-
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received, and that advertised window is 0x5fff,
-	// that is, that it is scaled.
-	b := c.GetPacket()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPWindow(0x5fff),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-}
-
-func TestNonScaledWindowConnect(t *testing.T) {
-	// This test ensures that window scaling is not used when the peer
-	// doesn't advertise it and connection is established with Connect().
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set the window size greater than the maximum non-scaled window.
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, 65535*3)
-
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received, and that advertised window is 0xffff,
-	// that is, that it's not scaled.
-	b := c.GetPacket()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPWindow(0xffff),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-}
-
-func TestScaledWindowAccept(t *testing.T) {
-	// This test ensures that window scaling is used when the peer
-	// does advertise it and connection is established with Accept().
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create EP and start listening.
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	defer ep.Close()
-
-	// Set the window size greater than the maximum non-scaled window.
-	if err := ep.SetSockOptInt(tcpip.ReceiveBufferSizeOption, 65535*3); err != nil {
-		t.Fatalf("SetSockOptInt(ReceiveBufferSizeOption, 65535*3) failed failed: %s", err)
-	}
-
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Do 3-way handshake.
-	// wndScale expected is 3 as 65535 * 3 * 2 < 65535 * 2^3 but > 65535 *2 *2
-	c.PassiveConnectWithOptions(100, 3 /* wndScale */, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received, and that advertised window is 0x5fff,
-	// that is, that it is scaled.
-	b := c.GetPacket()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPWindow(0x5fff),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-}
-
-func TestNonScaledWindowAccept(t *testing.T) {
-	// This test ensures that window scaling is not used when the peer
-	// doesn't advertise it and connection is established with Accept().
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create EP and start listening.
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	defer ep.Close()
-
-	// Set the window size greater than the maximum non-scaled window.
-	if err := ep.SetSockOptInt(tcpip.ReceiveBufferSizeOption, 65535*3); err != nil {
-		t.Fatalf("SetSockOptInt(ReceiveBufferSizeOption, 65535*3) failed failed: %s", err)
-	}
-
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Do 3-way handshake w/ window scaling disabled. The SYN-ACK to the SYN
-	// should not carry the window scaling option.
-	c.PassiveConnect(100, -1, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received, and that advertised window is 0xffff,
-	// that is, that it's not scaled.
-	b := c.GetPacket()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPWindow(0xffff),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-}
-
-func TestZeroScaledWindowReceive(t *testing.T) {
-	// This test ensures that the endpoint sends a non-zero window size
-	// advertisement when the scaled window transitions from 0 to non-zero,
-	// but the actual window (not scaled) hasn't gotten to zero.
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set the buffer size such that a window scale of 5 will be used.
-	const bufSz = 65535 * 10
-	const ws = uint32(5)
-	c.CreateConnectedWithRawOptions(context.TestInitialSequenceNumber, 30000, bufSz, []byte{
-		header.TCPOptionWS, 3, 0, header.TCPOptionNOP,
-	})
-
-	// Write chunks of 50000 bytes.
-	remain := 0
-	sent := 0
-	data := make([]byte, 50000)
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	// Keep writing till the window drops below len(data).
-	for {
-		c.SendPacket(data, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss.Add(seqnum.Size(sent)),
-			AckNum:  c.IRS.Add(1),
-			RcvWnd:  30000,
-		})
-		sent += len(data)
-		pkt := c.GetPacket()
-		checker.IPv4(t, pkt,
-			checker.PayloadLen(header.TCPMinimumSize),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(c.IRS)+1),
-				checker.TCPAckNum(uint32(iss)+uint32(sent)),
-				checker.TCPFlags(header.TCPFlagAck),
-			),
-		)
-		// Don't reduce window to zero here.
-		if wnd := int(header.TCP(header.IPv4(pkt).Payload()).WindowSize()); wnd<<ws < len(data) {
-			remain = wnd << ws
-			break
-		}
-	}
-
-	// Make the window non-zero, but the scaled window zero.
-	for remain >= 16 {
-		data = data[:remain-15]
-		c.SendPacket(data, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss.Add(seqnum.Size(sent)),
-			AckNum:  c.IRS.Add(1),
-			RcvWnd:  30000,
-		})
-		sent += len(data)
-		pkt := c.GetPacket()
-		checker.IPv4(t, pkt,
-			checker.PayloadLen(header.TCPMinimumSize),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(c.IRS)+1),
-				checker.TCPAckNum(uint32(iss)+uint32(sent)),
-				checker.TCPFlags(header.TCPFlagAck),
-			),
-		)
-		// Since the receive buffer is split between window advertisement and
-		// application data buffer the window does not always reflect the space
-		// available and actual space available can be a bit more than what is
-		// advertised in the window.
-		wnd := int(header.TCP(header.IPv4(pkt).Payload()).WindowSize())
-		if wnd == 0 {
-			break
-		}
-		remain = wnd << ws
-	}
-
-	// Read at least 2MSS of data. An ack should be sent in response to that.
-	// Since buffer space is now split in half between window and application
-	// data we need to read more than 1 MSS(65536) of data for a non-zero window
-	// update to be sent. For 1MSS worth of window to be available we need to
-	// read at least 128KB. Since our segments above were 50KB each it means
-	// we need to read at 3 packets.
-	w := tcpip.LimitedWriter{
-		W: ioutil.Discard,
-		N: defaultMTU * 2,
-	}
-	for w.N != 0 {
-		res, err := c.EP.Read(&w, tcpip.ReadOptions{})
-		t.Logf("err=%v res=%#v", err, res)
-		if err != nil {
-			t.Fatalf("Read failed: %s", err)
-		}
-	}
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(sent)),
-			checker.TCPWindowGreaterThanEq(uint16(defaultMTU>>ws)),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestSegmentMerging(t *testing.T) {
-	tests := []struct {
-		name   string
-		stop   func(tcpip.Endpoint)
-		resume func(tcpip.Endpoint)
-	}{
-		{
-			"stop work",
-			func(ep tcpip.Endpoint) {
-				ep.(interface{ StopWork() }).StopWork()
-			},
-			func(ep tcpip.Endpoint) {
-				ep.(interface{ ResumeWork() }).ResumeWork()
-			},
-		},
-		{
-			"cork",
-			func(ep tcpip.Endpoint) {
-				ep.SocketOptions().SetCorkOption(true)
-			},
-			func(ep tcpip.Endpoint) {
-				ep.SocketOptions().SetCorkOption(false)
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-			// Send tcp.InitialCwnd number of segments to fill up
-			// InitialWindow but don't ACK. That should prevent
-			// anymore packets from going out.
-			var r bytes.Reader
-			for i := 0; i < tcp.InitialCwnd; i++ {
-				r.Reset([]byte{0})
-				if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-					t.Fatalf("Write #%d failed: %s", i+1, err)
-				}
-			}
-
-			// Now send the segments that should get merged as the congestion
-			// window is full and we won't be able to send any more packets.
-			var allData []byte
-			for i, data := range [][]byte{{1, 2, 3, 4}, {5, 6, 7}, {8, 9}, {10}, {11}} {
-				allData = append(allData, data...)
-				r.Reset(data)
-				if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-					t.Fatalf("Write #%d failed: %s", i+1, err)
-				}
-			}
-
-			// Check that we get tcp.InitialCwnd packets.
-			iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-			for i := 0; i < tcp.InitialCwnd; i++ {
-				b := c.GetPacket()
-				checker.IPv4(t, b,
-					checker.PayloadLen(header.TCPMinimumSize+1),
-					checker.TCP(
-						checker.DstPort(context.TestPort),
-						checker.TCPSeqNum(uint32(c.IRS)+uint32(i)+1),
-						checker.TCPAckNum(uint32(iss)),
-						checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-					),
-				)
-			}
-
-			// Acknowledge the data.
-			c.SendPacket(nil, &context.Headers{
-				SrcPort: context.TestPort,
-				DstPort: c.Port,
-				Flags:   header.TCPFlagAck,
-				SeqNum:  iss,
-				AckNum:  c.IRS.Add(1 + 10), // 10 for the 10 bytes of payload.
-				RcvWnd:  30000,
-			})
-
-			// Check that data is received.
-			b := c.GetPacket()
-			checker.IPv4(t, b,
-				checker.PayloadLen(len(allData)+header.TCPMinimumSize),
-				checker.TCP(
-					checker.DstPort(context.TestPort),
-					checker.TCPSeqNum(uint32(c.IRS)+11),
-					checker.TCPAckNum(uint32(iss)),
-					checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-				),
-			)
-
-			if got := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(got, allData) {
-				t.Fatalf("got data = %v, want = %v", got, allData)
-			}
-
-			// Acknowledge the data.
-			c.SendPacket(nil, &context.Headers{
-				SrcPort: context.TestPort,
-				DstPort: c.Port,
-				Flags:   header.TCPFlagAck,
-				SeqNum:  iss,
-				AckNum:  c.IRS.Add(11 + seqnum.Size(len(allData))),
-				RcvWnd:  30000,
-			})
-		})
-	}
-}
-
-func TestDelay(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	c.EP.SocketOptions().SetDelayOption(true)
-
-	var allData []byte
-	for i, data := range [][]byte{{0}, {1, 2, 3, 4}, {5, 6, 7}, {8, 9}, {10}, {11}} {
-		allData = append(allData, data...)
-		var r bytes.Reader
-		r.Reset(data)
-		if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-			t.Fatalf("Write #%d failed: %s", i+1, err)
-		}
-	}
-
-	seq := c.IRS.Add(1)
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	for _, want := range [][]byte{allData[:1], allData[1:]} {
-		// Check that data is received.
-		b := c.GetPacket()
-		checker.IPv4(t, b,
-			checker.PayloadLen(len(want)+header.TCPMinimumSize),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(seq)),
-				checker.TCPAckNum(uint32(iss)),
-				checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-			),
-		)
-
-		if got := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(got, want) {
-			t.Fatalf("got data = %v, want = %v", got, want)
-		}
-
-		seq = seq.Add(seqnum.Size(len(want)))
-		// Acknowledge the data.
-		c.SendPacket(nil, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss,
-			AckNum:  seq,
-			RcvWnd:  30000,
-		})
-	}
-}
-
-func TestUndelay(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	c.EP.SocketOptions().SetDelayOption(true)
-
-	allData := [][]byte{{0}, {1, 2, 3}}
-	for i, data := range allData {
-		var r bytes.Reader
-		r.Reset(data)
-		if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-			t.Fatalf("Write #%d failed: %s", i+1, err)
-		}
-	}
-
-	seq := c.IRS.Add(1)
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	// Check that data is received.
-	first := c.GetPacket()
-	checker.IPv4(t, first,
-		checker.PayloadLen(len(allData[0])+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(seq)),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	if got, want := first[header.IPv4MinimumSize+header.TCPMinimumSize:], allData[0]; !bytes.Equal(got, want) {
-		t.Fatalf("got first packet's data = %v, want = %v", got, want)
-	}
-
-	seq = seq.Add(seqnum.Size(len(allData[0])))
-
-	// Check that we don't get the second packet yet.
-	c.CheckNoPacketTimeout("delayed second packet transmitted", 100*time.Millisecond)
-
-	c.EP.SocketOptions().SetDelayOption(false)
-
-	// Check that data is received.
-	second := c.GetPacket()
-	checker.IPv4(t, second,
-		checker.PayloadLen(len(allData[1])+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(seq)),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	if got, want := second[header.IPv4MinimumSize+header.TCPMinimumSize:], allData[1]; !bytes.Equal(got, want) {
-		t.Fatalf("got second packet's data = %v, want = %v", got, want)
-	}
-
-	seq = seq.Add(seqnum.Size(len(allData[1])))
-
-	// Acknowledge the data.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  seq,
-		RcvWnd:  30000,
-	})
-}
-
-func TestMSSNotDelayed(t *testing.T) {
-	tests := []struct {
-		name string
-		fn   func(tcpip.Endpoint)
-	}{
-		{"no-op", func(tcpip.Endpoint) {}},
-		{"delay", func(ep tcpip.Endpoint) { ep.SocketOptions().SetDelayOption(true) }},
-		{"cork", func(ep tcpip.Endpoint) { ep.SocketOptions().SetCorkOption(true) }},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			const maxPayload = 100
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			c.CreateConnectedWithRawOptions(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */, []byte{
-				header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256),
-			})
-
-			test.fn(c.EP)
-
-			allData := [][]byte{{0}, make([]byte, maxPayload), make([]byte, maxPayload)}
-			for i, data := range allData {
-				var r bytes.Reader
-				r.Reset(data)
-				if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-					t.Fatalf("Write #%d failed: %s", i+1, err)
-				}
-			}
-
-			seq := c.IRS.Add(1)
-			iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-			for i, data := range allData {
-				// Check that data is received.
-				packet := c.GetPacket()
-				checker.IPv4(t, packet,
-					checker.PayloadLen(len(data)+header.TCPMinimumSize),
-					checker.TCP(
-						checker.DstPort(context.TestPort),
-						checker.TCPSeqNum(uint32(seq)),
-						checker.TCPAckNum(uint32(iss)),
-						checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-					),
-				)
-
-				if got, want := packet[header.IPv4MinimumSize+header.TCPMinimumSize:], data; !bytes.Equal(got, want) {
-					t.Fatalf("got packet #%d's data = %v, want = %v", i+1, got, want)
-				}
-
-				seq = seq.Add(seqnum.Size(len(data)))
-			}
-
-			// Acknowledge the data.
-			c.SendPacket(nil, &context.Headers{
-				SrcPort: context.TestPort,
-				DstPort: c.Port,
-				Flags:   header.TCPFlagAck,
-				SeqNum:  iss,
-				AckNum:  seq,
-				RcvWnd:  30000,
-			})
-		})
-	}
-}
-
-func testBrokenUpWrite(t *testing.T, c *context.Context, maxPayload int) {
-	payloadMultiplier := 10
-	dataLen := payloadMultiplier * maxPayload
-	data := make([]byte, dataLen)
-	for i := range data {
-		data[i] = byte(i)
-	}
-
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received in chunks.
-	bytesReceived := 0
-	numPackets := 0
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	for bytesReceived != dataLen {
-		b := c.GetPacket()
-		numPackets++
-		tcpHdr := header.TCP(header.IPv4(b).Payload())
-		payloadLen := len(tcpHdr.Payload())
-		checker.IPv4(t, b,
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(c.IRS)+1+uint32(bytesReceived)),
-				checker.TCPAckNum(uint32(iss)),
-				checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-			),
-		)
-
-		pdata := data[bytesReceived : bytesReceived+payloadLen]
-		if p := tcpHdr.Payload(); !bytes.Equal(pdata, p) {
-			t.Fatalf("got data = %v, want = %v", p, pdata)
-		}
-		bytesReceived += payloadLen
-		var options []byte
-		if c.TimeStampEnabled {
-			// If timestamp option is enabled, echo back the timestamp and increment
-			// the TSEcr value included in the packet and send that back as the TSVal.
-			parsedOpts := tcpHdr.ParsedOptions()
-			tsOpt := [12]byte{header.TCPOptionNOP, header.TCPOptionNOP}
-			header.EncodeTSOption(parsedOpts.TSEcr+1, parsedOpts.TSVal, tsOpt[2:])
-			options = tsOpt[:]
-		}
-		// Acknowledge the data.
-		c.SendPacket(nil, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss,
-			AckNum:  c.IRS.Add(1 + seqnum.Size(bytesReceived)),
-			RcvWnd:  30000,
-			TCPOpts: options,
-		})
-	}
-	if numPackets == 1 {
-		t.Fatalf("expected write to be broken up into multiple packets, but got 1 packet")
-	}
-}
-
-func TestSendGreaterThanMTU(t *testing.T) {
-	const maxPayload = 100
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestSetTTL(t *testing.T) {
-	for _, wantTTL := range []uint8{1, 2, 50, 64, 128, 254, 255} {
-		t.Run(fmt.Sprintf("TTL:%d", wantTTL), func(t *testing.T) {
-			c := context.New(t, 65535)
-			defer c.Cleanup()
-
-			var err tcpip.Error
-			c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-			if err != nil {
-				t.Fatalf("NewEndpoint failed: %s", err)
-			}
-
-			if err := c.EP.SetSockOptInt(tcpip.TTLOption, int(wantTTL)); err != nil {
-				t.Fatalf("SetSockOptInt(TTLOption, %d) failed: %s", wantTTL, err)
-			}
-
-			{
-				err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort})
-				if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-					t.Fatalf("unexpected return value from Connect: %s", err)
-				}
-			}
-
-			// Receive SYN packet.
-			b := c.GetPacket()
-
-			checker.IPv4(t, b, checker.TTL(wantTTL))
-		})
-	}
-}
-
-func TestActiveSendMSSLessThanMTU(t *testing.T) {
-	const maxPayload = 100
-	c := context.New(t, 65535)
-	defer c.Cleanup()
-
-	c.CreateConnectedWithRawOptions(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */, []byte{
-		header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256),
-	})
-	testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestPassiveSendMSSLessThanMTU(t *testing.T) {
-	const maxPayload = 100
-	const mtu = 1200
-	c := context.New(t, mtu)
-	defer c.Cleanup()
-
-	// Create EP and start listening.
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	defer ep.Close()
-
-	// Set the buffer size to a deterministic size so that we can check the
-	// window scaling option.
-	const rcvBufferSize = 0x20000
-	if err := ep.SetSockOptInt(tcpip.ReceiveBufferSizeOption, rcvBufferSize); err != nil {
-		t.Fatalf("SetSockOptInt(ReceiveBufferSizeOption, %d) failed failed: %s", rcvBufferSize, err)
-	}
-
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Do 3-way handshake.
-	c.PassiveConnect(maxPayload, -1, header.TCPSynOptions{MSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize})
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Check that data gets properly segmented.
-	testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestSynCookiePassiveSendMSSLessThanMTU(t *testing.T) {
-	const maxPayload = 536
-	const mtu = 2000
-	c := context.New(t, mtu)
-	defer c.Cleanup()
-
-	// Set the SynRcvd threshold to zero to force a syn cookie based accept
-	// to happen.
-	opt := tcpip.TCPSynRcvdCountThresholdOption(0)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-
-	// Create EP and start listening.
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	defer ep.Close()
-
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Do 3-way handshake.
-	c.PassiveConnect(maxPayload, -1, header.TCPSynOptions{MSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize})
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Check that data gets properly segmented.
-	testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestForwarderSendMSSLessThanMTU(t *testing.T) {
-	const maxPayload = 100
-	const mtu = 1200
-	c := context.New(t, mtu)
-	defer c.Cleanup()
-
-	s := c.Stack()
-	ch := make(chan tcpip.Error, 1)
-	f := tcp.NewForwarder(s, 65536, 10, func(r *tcp.ForwarderRequest) {
-		var err tcpip.Error
-		c.EP, err = r.CreateEndpoint(&c.WQ)
-		ch <- err
-	})
-	s.SetTransportProtocolHandler(tcp.ProtocolNumber, f.HandlePacket)
-
-	// Do 3-way handshake.
-	c.PassiveConnect(maxPayload, -1, header.TCPSynOptions{MSS: mtu - header.IPv4MinimumSize - header.TCPMinimumSize})
-
-	// Wait for connection to be available.
-	select {
-	case err := <-ch:
-		if err != nil {
-			t.Fatalf("Error creating endpoint: %s", err)
-		}
-	case <-time.After(2 * time.Second):
-		t.Fatalf("Timed out waiting for connection")
-	}
-
-	// Check that data gets properly segmented.
-	testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestSynOptionsOnActiveConnect(t *testing.T) {
-	const mtu = 1400
-	c := context.New(t, mtu)
-	defer c.Cleanup()
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	// Set the buffer size to a deterministic size so that we can check the
-	// window scaling option.
-	const rcvBufferSize = 0x20000
-	const wndScale = 3
-	if err := c.EP.SetSockOptInt(tcpip.ReceiveBufferSizeOption, rcvBufferSize); err != nil {
-		t.Fatalf("SetSockOptInt(ReceiveBufferSizeOption, %d) failed failed: %s", rcvBufferSize, err)
-	}
-
-	// Start connection attempt.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventOut)
-	defer c.WQ.EventUnregister(&we)
-
-	{
-		err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort})
-		if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-			t.Fatalf("got c.EP.Connect(...) = %v, want = %s", err, &tcpip.ErrConnectStarted{})
-		}
-	}
-
-	// Receive SYN packet.
-	b := c.GetPacket()
-	mss := uint16(mtu - header.IPv4MinimumSize - header.TCPMinimumSize)
-	checker.IPv4(t, b,
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagSyn),
-			checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: wndScale}),
-		),
-	)
-
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	// Wait for retransmit.
-	time.Sleep(1 * time.Second)
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagSyn),
-			checker.SrcPort(tcpHdr.SourcePort()),
-			checker.TCPSeqNum(tcpHdr.SequenceNumber()),
-			checker.TCPSynOptions(header.TCPSynOptions{MSS: mss, WS: wndScale}),
-		),
-	)
-
-	// Send SYN-ACK.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: tcpHdr.DestinationPort(),
-		DstPort: tcpHdr.SourcePort(),
-		Flags:   header.TCPFlagSyn | header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Receive ACK packet.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagAck),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+1),
-		),
-	)
-
-	// Wait for connection to be established.
-	select {
-	case <-ch:
-		if err := c.EP.LastError(); err != nil {
-			t.Fatalf("Connect failed: %s", err)
-		}
-	case <-time.After(1 * time.Second):
-		t.Fatalf("Timed out waiting for connection")
-	}
-}
-
-func TestCloseListener(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create listener.
-	var wq waiter.Queue
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	if err := ep.Bind(tcpip.FullAddress{}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Close the listener and measure how long it takes.
-	t0 := time.Now()
-	ep.Close()
-	if diff := time.Now().Sub(t0); diff > 3*time.Second {
-		t.Fatalf("Took too long to close: %s", diff)
-	}
-}
-
-func TestReceiveOnResetConnection(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	// Send RST segment.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagRst,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	// Try to read.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-loop:
-	for {
-		switch _, err := c.EP.Read(ioutil.Discard, tcpip.ReadOptions{}); err.(type) {
-		case *tcpip.ErrWouldBlock:
-			select {
-			case <-ch:
-				// Expect the state to be StateError and subsequent Reads to fail with HardError.
-				_, err := c.EP.Read(ioutil.Discard, tcpip.ReadOptions{})
-				if _, ok := err.(*tcpip.ErrConnectionReset); !ok {
-					t.Fatalf("got c.EP.Read() = %v, want = %s", err, &tcpip.ErrConnectionReset{})
-				}
-				break loop
-			case <-time.After(1 * time.Second):
-				t.Fatalf("Timed out waiting for reset to arrive")
-			}
-		case *tcpip.ErrConnectionReset:
-			break loop
-		default:
-			t.Fatalf("got c.EP.Read(nil) = %v, want = %s", err, &tcpip.ErrConnectionReset{})
-		}
-	}
-
-	if tcp.EndpointState(c.EP.State()) != tcp.StateError {
-		t.Fatalf("got EP state is not StateError")
-	}
-	if got := c.Stack().Stats().TCP.EstablishedResets.Value(); got != 1 {
-		t.Errorf("got stats.TCP.EstablishedResets.Value() = %d, want = 1", got)
-	}
-	if got := c.Stack().Stats().TCP.CurrentEstablished.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentEstablished.Value() = %d, want = 0", got)
-	}
-	if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 0", got)
-	}
-}
-
-func TestSendOnResetConnection(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	// Send RST segment.
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagRst,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	// Wait for the RST to be received.
-	time.Sleep(1 * time.Second)
-
-	// Try to write.
-	var r bytes.Reader
-	r.Reset(make([]byte, 10))
-	_, err := c.EP.Write(&r, tcpip.WriteOptions{})
-	if _, ok := err.(*tcpip.ErrConnectionReset); !ok {
-		t.Fatalf("got c.EP.Write(...) = %v, want = %s", err, &tcpip.ErrConnectionReset{})
-	}
-}
-
-// TestMaxRetransmitsTimeout tests if the connection is timed out after
-// a segment has been retransmitted MaxRetries times.
-func TestMaxRetransmitsTimeout(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	const numRetries = 2
-	opt := tcpip.TCPMaxRetriesOption(numRetries)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000 /* rcvWnd */, -1 /* epRcvBuf */)
-
-	waitEntry, notifyCh := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&waitEntry, waiter.EventHUp)
-	defer c.WQ.EventUnregister(&waitEntry)
-
-	var r bytes.Reader
-	r.Reset(make([]byte, 1))
-	_, err := c.EP.Write(&r, tcpip.WriteOptions{})
-	if err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Expect first transmit and MaxRetries retransmits.
-	for i := 0; i < numRetries+1; i++ {
-		checker.IPv4(t, c.GetPacket(),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPFlags(header.TCPFlagAck|header.TCPFlagPsh),
-			),
-		)
-	}
-	// Wait for the connection to timeout after MaxRetries retransmits.
-	initRTO := 1 * time.Second
-	select {
-	case <-notifyCh:
-	case <-time.After((2 << numRetries) * initRTO):
-		t.Fatalf("connection still alive after maximum retransmits.\n")
-	}
-
-	// Send an ACK and expect a RST as the connection would have been closed.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-	})
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlags(header.TCPFlagRst),
-		),
-	)
-
-	if got := c.Stack().Stats().TCP.EstablishedTimedout.Value(); got != 1 {
-		t.Errorf("got c.Stack().Stats().TCP.EstablishedTimedout.Value() = %d, want = 1", got)
-	}
-	if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 0", got)
-	}
-}
-
-// TestMaxRTO tests if the retransmit interval caps to MaxRTO.
-func TestMaxRTO(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	rto := 1 * time.Second
-	opt := tcpip.TCPMaxRTOOption(rto)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000 /* rcvWnd */, -1 /* epRcvBuf */)
-
-	var r bytes.Reader
-	r.Reset(make([]byte, 1))
-	_, err := c.EP.Write(&r, tcpip.WriteOptions{})
-	if err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-	const numRetransmits = 2
-	for i := 0; i < numRetransmits; i++ {
-		start := time.Now()
-		checker.IPv4(t, c.GetPacket(),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-			),
-		)
-		if time.Since(start).Round(time.Second).Seconds() != rto.Seconds() {
-			t.Errorf("Retransmit interval not capped to MaxRTO.\n")
-		}
-	}
-}
-
-// TestRetransmitIPv4IDUniqueness tests that the IPv4 Identification field is
-// unique on retransmits.
-func TestRetransmitIPv4IDUniqueness(t *testing.T) {
-	for _, tc := range []struct {
-		name string
-		size int
-	}{
-		{"1Byte", 1},
-		{"512Bytes", 512},
-	} {
-		t.Run(tc.name, func(t *testing.T) {
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			c.CreateConnected(context.TestInitialSequenceNumber, 30000 /* rcvWnd */, -1 /* epRcvBuf */)
-
-			// Disabling PMTU discovery causes all packets sent from this socket to
-			// have DF=0. This needs to be done because the IPv4 ID uniqueness
-			// applies only to non-atomic IPv4 datagrams as defined in RFC 6864
-			// Section 4, and datagrams with DF=0 are non-atomic.
-			if err := c.EP.SetSockOptInt(tcpip.MTUDiscoverOption, tcpip.PMTUDiscoveryDont); err != nil {
-				t.Fatalf("disabling PMTU discovery via sockopt to force DF=0 failed: %s", err)
-			}
-
-			var r bytes.Reader
-			r.Reset(make([]byte, tc.size))
-			if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-				t.Fatalf("Write failed: %s", err)
-			}
-			pkt := c.GetPacket()
-			checker.IPv4(t, pkt,
-				checker.FragmentFlags(0),
-				checker.TCP(
-					checker.DstPort(context.TestPort),
-					checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-				),
-			)
-			idSet := map[uint16]struct{}{header.IPv4(pkt).ID(): {}}
-			// Expect two retransmitted packets, and that all packets received have
-			// unique IPv4 ID values.
-			for i := 0; i <= 2; i++ {
-				pkt := c.GetPacket()
-				checker.IPv4(t, pkt,
-					checker.FragmentFlags(0),
-					checker.TCP(
-						checker.DstPort(context.TestPort),
-						checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-					),
-				)
-				id := header.IPv4(pkt).ID()
-				if _, exists := idSet[id]; exists {
-					t.Fatalf("duplicate IPv4 ID=%d found in retransmitted packet", id)
-				}
-				idSet[id] = struct{}{}
-			}
-		})
-	}
-}
-
-func TestFinImmediately(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	// Shutdown immediately, check that we get a FIN.
-	if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-
-	// Ack and send FIN as well.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	// Check that the stack acks the FIN.
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+2),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestFinRetransmit(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	// Shutdown immediately, check that we get a FIN.
-	if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-
-	// Don't acknowledge yet. We should get a retransmit of the FIN.
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-
-	// Ack and send FIN as well.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	// Check that the stack acks the FIN.
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+2),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestFinWithNoPendingData(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	// Write something out, and have it acknowledged.
-	view := make([]byte, 10)
-	var r bytes.Reader
-	r.Reset(view)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	next := uint32(c.IRS) + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-	next += uint32(len(view))
-
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	// Shutdown, check that we get a FIN.
-	if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-	next++
-
-	// Ack and send FIN as well.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	// Check that the stack acks the FIN.
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestFinWithPendingDataCwndFull(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	// Write enough segments to fill the congestion window before ACK'ing
-	// any of them.
-	view := make([]byte, 10)
-	var r bytes.Reader
-	for i := tcp.InitialCwnd; i > 0; i-- {
-		r.Reset(view)
-		if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-			t.Fatalf("Write failed: %s", err)
-		}
-	}
-
-	next := uint32(c.IRS) + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	for i := tcp.InitialCwnd; i > 0; i-- {
-		checker.IPv4(t, c.GetPacket(),
-			checker.PayloadLen(len(view)+header.TCPMinimumSize),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(next),
-				checker.TCPAckNum(uint32(iss)),
-				checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-			),
-		)
-		next += uint32(len(view))
-	}
-
-	// Shutdown the connection, check that the FIN segment isn't sent
-	// because the congestion window doesn't allow it. Wait until a
-	// retransmit is received.
-	if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	// Send the ACK that will allow the FIN to be sent as well.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-	next++
-
-	// Send a FIN that acknowledges everything. Get an ACK back.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestFinWithPendingData(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	// Write something out, and acknowledge it to get cwnd to 2.
-	view := make([]byte, 10)
-	var r bytes.Reader
-	r.Reset(view)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	next := uint32(c.IRS) + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-	next += uint32(len(view))
-
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	// Write new data, but don't acknowledge it.
-	r.Reset(view)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-	next += uint32(len(view))
-
-	// Shutdown the connection, check that we do get a FIN.
-	if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-	next++
-
-	// Send a FIN that acknowledges everything. Get an ACK back.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestFinWithPartialAck(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	// Write something out, and acknowledge it to get cwnd to 2. Also send
-	// FIN from the test side.
-	view := make([]byte, 10)
-	var r bytes.Reader
-	r.Reset(view)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	next := uint32(c.IRS) + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-	next += uint32(len(view))
-
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	// Check that we get an ACK for the fin.
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	// Write new data, but don't acknowledge it.
-	r.Reset(view)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-	next += uint32(len(view))
-
-	// Shutdown the connection, check that we do get a FIN.
-	if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)+1),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-	next++
-
-	// Send an ACK for the data, but not for the FIN yet.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss.Add(1),
-		AckNum:  seqnum.Value(next - 1),
-		RcvWnd:  30000,
-	})
-
-	// Check that we don't get a retransmit of the FIN.
-	c.CheckNoPacketTimeout("FIN retransmitted when data was ack'd", 100*time.Millisecond)
-
-	// Ack the FIN.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss.Add(1),
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-}
-
-func TestUpdateListenBacklog(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create listener.
-	var wq waiter.Queue
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	if err := ep.Bind(tcpip.FullAddress{}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Update the backlog with another Listen() on the same endpoint.
-	if err := ep.Listen(20); err != nil {
-		t.Fatalf("Listen failed to update backlog: %s", err)
-	}
-
-	ep.Close()
-}
-
-func scaledSendWindow(t *testing.T, scale uint8) {
-	// This test ensures that the endpoint is using the right scaling by
-	// sending a buffer that is larger than the window size, and ensuring
-	// that the endpoint doesn't send more than allowed.
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	maxPayload := defaultMTU - header.IPv4MinimumSize - header.TCPMinimumSize
-	c.CreateConnectedWithRawOptions(context.TestInitialSequenceNumber, 0, -1 /* epRcvBuf */, []byte{
-		header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256),
-		header.TCPOptionWS, 3, scale, header.TCPOptionNOP,
-	})
-
-	// Open up the window with a scaled value.
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  1,
-	})
-
-	// Send some data. Check that it's capped by the window size.
-	view := make([]byte, 65535)
-	var r bytes.Reader
-	r.Reset(view)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that only data that fits in the scaled window is sent.
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen((1<<scale)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	// Reset the connection to free resources.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagRst,
-		SeqNum:  iss,
-	})
-}
-
-func TestScaledSendWindow(t *testing.T) {
-	for scale := uint8(0); scale <= 14; scale++ {
-		scaledSendWindow(t, scale)
-	}
-}
-
-func TestReceivedValidSegmentCountIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	stats := c.Stack().Stats()
-	want := stats.TCP.ValidSegmentsReceived.Value() + 1
-
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	if got := stats.TCP.ValidSegmentsReceived.Value(); got != want {
-		t.Errorf("got stats.TCP.ValidSegmentsReceived.Value() = %d, want = %d", got, want)
-	}
-	if got := c.EP.Stats().(*tcp.Stats).SegmentsReceived.Value(); got != want {
-		t.Errorf("got EP stats Stats.SegmentsReceived = %d, want = %d", got, want)
-	}
-	// Ensure there were no errors during handshake. If these stats have
-	// incremented, then the connection should not have been established.
-	if got := c.EP.Stats().(*tcp.Stats).SendErrors.NoRoute.Value(); got != 0 {
-		t.Errorf("got EP stats Stats.SendErrors.NoRoute = %d, want = %d", got, 0)
-	}
-}
-
-func TestReceivedInvalidSegmentCountIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	stats := c.Stack().Stats()
-	want := stats.TCP.InvalidSegmentsReceived.Value() + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	vv := c.BuildSegment(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  seqnum.Value(iss),
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-	tcpbuf := vv.ToView()[header.IPv4MinimumSize:]
-	tcpbuf[header.TCPDataOffset] = ((header.TCPMinimumSize - 1) / 4) << 4
-
-	c.SendSegment(vv)
-
-	if got := stats.TCP.InvalidSegmentsReceived.Value(); got != want {
-		t.Errorf("got stats.TCP.InvalidSegmentsReceived.Value() = %d, want = %d", got, want)
-	}
-	if got := c.EP.Stats().(*tcp.Stats).ReceiveErrors.MalformedPacketsReceived.Value(); got != want {
-		t.Errorf("got EP Stats.ReceiveErrors.MalformedPacketsReceived stats = %d, want = %d", got, want)
-	}
-}
-
-func TestReceivedIncorrectChecksumIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-	stats := c.Stack().Stats()
-	want := stats.TCP.ChecksumErrors.Value() + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	vv := c.BuildSegment([]byte{0x1, 0x2, 0x3}, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-	tcpbuf := vv.ToView()[header.IPv4MinimumSize:]
-	// Overwrite a byte in the payload which should cause checksum
-	// verification to fail.
-	tcpbuf[(tcpbuf[header.TCPDataOffset]>>4)*4] = 0x4
-
-	c.SendSegment(vv)
-
-	if got := stats.TCP.ChecksumErrors.Value(); got != want {
-		t.Errorf("got stats.TCP.ChecksumErrors.Value() = %d, want = %d", got, want)
-	}
-	if got := c.EP.Stats().(*tcp.Stats).ReceiveErrors.ChecksumErrors.Value(); got != want {
-		t.Errorf("got EP stats Stats.ReceiveErrors.ChecksumErrors = %d, want = %d", got, want)
-	}
-}
-
-func TestReceivedSegmentQueuing(t *testing.T) {
-	// This test sends 200 segments containing a few bytes each to an
-	// endpoint and checks that they're all received and acknowledged by
-	// the endpoint, that is, that none of the segments are dropped by
-	// internal queues.
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	// Send 200 segments.
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	data := []byte{1, 2, 3}
-	for i := 0; i < 200; i++ {
-		c.SendPacket(data, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss.Add(seqnum.Size(i * len(data))),
-			AckNum:  c.IRS.Add(1),
-			RcvWnd:  30000,
-		})
-	}
-
-	// Receive ACKs for all segments.
-	last := iss.Add(seqnum.Size(200 * len(data)))
-	for {
-		b := c.GetPacket()
-		checker.IPv4(t, b,
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(c.IRS)+1),
-				checker.TCPFlags(header.TCPFlagAck),
-			),
-		)
-		tcpHdr := header.TCP(header.IPv4(b).Payload())
-		ack := seqnum.Value(tcpHdr.AckNumber())
-		if ack == last {
-			break
-		}
-
-		if last.LessThan(ack) {
-			t.Fatalf("Acknowledge (%v) beyond the expected (%v)", ack, last)
-		}
-	}
-}
-
-func TestReadAfterClosedState(t *testing.T) {
-	// This test ensures that calling Read() or Peek() after the endpoint
-	// has transitioned to closedState still works if there is pending
-	// data. To transition to stateClosed without calling Close(), we must
-	// shutdown the send path and the peer must send its own FIN.
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set TCPTimeWaitTimeout to 1 seconds so that sockets are marked closed
-	// after 1 second in TIME_WAIT state.
-	tcpTimeWaitTimeout := 1 * time.Second
-	opt := tcpip.TCPTimeWaitTimeoutOption(tcpTimeWaitTimeout)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	// Shutdown immediately for write, check that we get a FIN.
-	if err := c.EP.Shutdown(tcpip.ShutdownWrite); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
-		),
-	)
-
-	if got, want := tcp.EndpointState(c.EP.State()), tcp.StateFinWait1; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	// Send some data and acknowledge the FIN.
-	data := []byte{1, 2, 3}
-	c.SendPacket(data, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(2),
-		RcvWnd:  30000,
-	})
-
-	// Check that ACK is received.
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+2),
-			checker.TCPAckNum(uint32(iss)+uint32(len(data))+1),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-
-	// Give the stack the chance to transition to closed state from
-	// TIME_WAIT.
-	time.Sleep(tcpTimeWaitTimeout * 2)
-
-	if got, want := tcp.EndpointState(c.EP.State()), tcp.StateClose; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	// Wait for receive to be notified.
-	select {
-	case <-ch:
-	case <-time.After(1 * time.Second):
-		t.Fatalf("Timed out waiting for data to arrive")
-	}
-
-	// Check that peek works.
-	var peekBuf bytes.Buffer
-	res, err := c.EP.Read(&peekBuf, tcpip.ReadOptions{Peek: true})
-	if err != nil {
-		t.Fatalf("Peek failed: %s", err)
-	}
-
-	if got, want := res.Count, len(data); got != want {
-		t.Fatalf("res.Count = %d, want %d", got, want)
-	}
-	if !bytes.Equal(data, peekBuf.Bytes()) {
-		t.Fatalf("got data = %v, want = %v", peekBuf.Bytes(), data)
-	}
-
-	// Receive data.
-	v := ept.CheckRead(t)
-	if !bytes.Equal(data, v) {
-		t.Fatalf("got data = %v, want = %v", v, data)
-	}
-
-	// Now that we drained the queue, check that functions fail with the
-	// right error code.
-	ept.CheckReadError(t, &tcpip.ErrClosedForReceive{})
-	var buf bytes.Buffer
-	{
-		_, err := c.EP.Read(&buf, tcpip.ReadOptions{Peek: true})
-		if _, ok := err.(*tcpip.ErrClosedForReceive); !ok {
-			t.Fatalf("c.EP.Read(_, {Peek: true}) = %v, %s; want _, %s", res, err, &tcpip.ErrClosedForReceive{})
-		}
-	}
-}
-
-func TestReusePort(t *testing.T) {
-	// This test ensures that ports are immediately available for reuse
-	// after Close on the endpoints using them returns.
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// First case, just an endpoint that was bound.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	c.EP.SocketOptions().SetReuseAddress(true)
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	c.EP.Close()
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	c.EP.SocketOptions().SetReuseAddress(true)
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-	c.EP.Close()
-
-	// Second case, an endpoint that was bound and is connecting..
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	c.EP.SocketOptions().SetReuseAddress(true)
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-	{
-		err := c.EP.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort})
-		if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-			t.Fatalf("got c.EP.Connect(...) = %v, want = %s", err, &tcpip.ErrConnectStarted{})
-		}
-	}
-	c.EP.Close()
-
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	c.EP.SocketOptions().SetReuseAddress(true)
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-	c.EP.Close()
-
-	// Third case, an endpoint that was bound and is listening.
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	c.EP.SocketOptions().SetReuseAddress(true)
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-	c.EP.Close()
-
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	c.EP.SocketOptions().SetReuseAddress(true)
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-}
-
-func checkRecvBufferSize(t *testing.T, ep tcpip.Endpoint, v int) {
-	t.Helper()
-
-	s, err := ep.GetSockOptInt(tcpip.ReceiveBufferSizeOption)
-	if err != nil {
-		t.Fatalf("GetSockOpt failed: %s", err)
-	}
-
-	if int(s) != v {
-		t.Fatalf("got receive buffer size = %d, want = %d", s, v)
-	}
-}
-
-func checkSendBufferSize(t *testing.T, ep tcpip.Endpoint, v int) {
-	t.Helper()
-
-	if s := ep.SocketOptions().GetSendBufferSize(); int(s) != v {
-		t.Fatalf("got send buffer size = %d, want = %d", s, v)
-	}
-}
-
-func TestDefaultBufferSizes(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
-	})
-
-	// Check the default values.
-	ep, err := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	defer func() {
-		if ep != nil {
-			ep.Close()
-		}
-	}()
-
-	checkSendBufferSize(t, ep, tcp.DefaultSendBufferSize)
-	checkRecvBufferSize(t, ep, tcp.DefaultReceiveBufferSize)
-
-	// Change the default send buffer size.
-	{
-		opt := tcpip.TCPSendBufferSizeRangeOption{
-			Min:     1,
-			Default: tcp.DefaultSendBufferSize * 2,
-			Max:     tcp.DefaultSendBufferSize * 20,
-		}
-		if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%#v): %s", tcp.ProtocolNumber, opt, err)
-		}
-	}
-
-	ep.Close()
-	ep, err = s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-
-	checkSendBufferSize(t, ep, tcp.DefaultSendBufferSize*2)
-	checkRecvBufferSize(t, ep, tcp.DefaultReceiveBufferSize)
-
-	// Change the default receive buffer size.
-	{
-		opt := tcpip.TCPReceiveBufferSizeRangeOption{
-			Min:     1,
-			Default: tcp.DefaultReceiveBufferSize * 3,
-			Max:     tcp.DefaultReceiveBufferSize * 30,
-		}
-		if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%#v): %s", tcp.ProtocolNumber, opt, err)
-		}
-	}
-
-	ep.Close()
-	ep, err = s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-
-	checkSendBufferSize(t, ep, tcp.DefaultSendBufferSize*2)
-	checkRecvBufferSize(t, ep, tcp.DefaultReceiveBufferSize*3)
-}
-
-func TestMinMaxBufferSizes(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
-	})
-
-	// Check the default values.
-	ep, err := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	defer ep.Close()
-
-	// Change the min/max values for send/receive
-	{
-		opt := tcpip.TCPReceiveBufferSizeRangeOption{Min: 200, Default: tcp.DefaultReceiveBufferSize * 2, Max: tcp.DefaultReceiveBufferSize * 20}
-		if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%#v): %s", tcp.ProtocolNumber, opt, err)
-		}
-	}
-
-	{
-		opt := tcpip.TCPSendBufferSizeRangeOption{Min: 300, Default: tcp.DefaultSendBufferSize * 3, Max: tcp.DefaultSendBufferSize * 30}
-		if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%#v): %s", tcp.ProtocolNumber, opt, err)
-		}
-	}
-
-	// Set values below the min/2.
-	if err := ep.SetSockOptInt(tcpip.ReceiveBufferSizeOption, 99); err != nil {
-		t.Fatalf("SetSockOptInt(ReceiveBufferSizeOption, 199) failed: %s", err)
-	}
-
-	checkRecvBufferSize(t, ep, 200)
-
-	ep.SocketOptions().SetSendBufferSize(149, true)
-
-	checkSendBufferSize(t, ep, 300)
-
-	// Set values above the max.
-	if err := ep.SetSockOptInt(tcpip.ReceiveBufferSizeOption, 1+tcp.DefaultReceiveBufferSize*20); err != nil {
-		t.Fatalf("SetSockOptInt(ReceiveBufferSizeOption) failed: %s", err)
-	}
-
-	// Values above max are capped at max and then doubled.
-	checkRecvBufferSize(t, ep, tcp.DefaultReceiveBufferSize*20*2)
-
-	ep.SocketOptions().SetSendBufferSize(1+tcp.DefaultSendBufferSize*30, true)
-
-	// Values above max are capped at max and then doubled.
-	checkSendBufferSize(t, ep, tcp.DefaultSendBufferSize*30*2)
-}
-
-func TestBindToDeviceOption(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol}})
-
-	ep, err := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	defer ep.Close()
-
-	if err := s.CreateNIC(321, loopback.New()); err != nil {
-		t.Errorf("CreateNIC failed: %s", err)
-	}
-
-	// nicIDPtr is used instead of taking the address of NICID literals, which is
-	// a compiler error.
-	nicIDPtr := func(s tcpip.NICID) *tcpip.NICID {
-		return &s
-	}
-
-	testActions := []struct {
-		name                 string
-		setBindToDevice      *tcpip.NICID
-		setBindToDeviceError tcpip.Error
-		getBindToDevice      int32
-	}{
-		{"GetDefaultValue", nil, nil, 0},
-		{"BindToNonExistent", nicIDPtr(999), &tcpip.ErrUnknownDevice{}, 0},
-		{"BindToExistent", nicIDPtr(321), nil, 321},
-		{"UnbindToDevice", nicIDPtr(0), nil, 0},
-	}
-	for _, testAction := range testActions {
-		t.Run(testAction.name, func(t *testing.T) {
-			if testAction.setBindToDevice != nil {
-				bindToDevice := int32(*testAction.setBindToDevice)
-				if gotErr, wantErr := ep.SocketOptions().SetBindToDevice(bindToDevice), testAction.setBindToDeviceError; gotErr != wantErr {
-					t.Errorf("got SetSockOpt(&%T(%d)) = %s, want = %s", bindToDevice, bindToDevice, gotErr, wantErr)
-				}
-			}
-			bindToDevice := ep.SocketOptions().GetBindToDevice()
-			if bindToDevice != testAction.getBindToDevice {
-				t.Errorf("got bindToDevice = %d, want %d", bindToDevice, testAction.getBindToDevice)
-			}
-		})
-	}
-}
-
-func makeStack() (*stack.Stack, tcpip.Error) {
-	s := stack.New(stack.Options{
-		NetworkProtocols: []stack.NetworkProtocolFactory{
-			ipv4.NewProtocol,
-			ipv6.NewProtocol,
-		},
-		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol},
-	})
-
-	id := loopback.New()
-	if testing.Verbose() {
-		id = sniffer.New(id)
-	}
-
-	if err := s.CreateNIC(1, id); err != nil {
-		return nil, err
-	}
-
-	for _, ct := range []struct {
-		number  tcpip.NetworkProtocolNumber
-		address tcpip.Address
-	}{
-		{ipv4.ProtocolNumber, context.StackAddr},
-		{ipv6.ProtocolNumber, context.StackV6Addr},
-	} {
-		if err := s.AddAddress(1, ct.number, ct.address); err != nil {
-			return nil, err
-		}
-	}
-
-	s.SetRouteTable([]tcpip.Route{
-		{
-			Destination: header.IPv4EmptySubnet,
-			NIC:         1,
-		},
-		{
-			Destination: header.IPv6EmptySubnet,
-			NIC:         1,
-		},
-	})
-
-	return s, nil
-}
-
-func TestSelfConnect(t *testing.T) {
-	// This test ensures that intentional self-connects work. In particular,
-	// it checks that if an endpoint binds to say 127.0.0.1:1000 then
-	// connects to 127.0.0.1:1000, then it will be connected to itself, and
-	// is able to send and receive data through the same endpoint.
-	s, err := makeStack()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	var wq waiter.Queue
-	ep, err := s.NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	defer ep.Close()
-
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Register for notification, then start connection attempt.
-	waitEntry, notifyCh := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&waitEntry, waiter.EventOut)
-	defer wq.EventUnregister(&waitEntry)
-
-	{
-		err := ep.Connect(tcpip.FullAddress{Addr: context.StackAddr, Port: context.StackPort})
-		if _, ok := err.(*tcpip.ErrConnectStarted); !ok {
-			t.Fatalf("got ep.Connect(...) = %v, want = %s", err, &tcpip.ErrConnectStarted{})
-		}
-	}
-
-	<-notifyCh
-	if err := ep.LastError(); err != nil {
-		t.Fatalf("Connect failed: %s", err)
-	}
-
-	// Write something.
-	data := []byte{1, 2, 3}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := ep.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Read back what was written.
-	wq.EventUnregister(&waitEntry)
-	wq.EventRegister(&waitEntry, waiter.EventIn)
-	ept := endpointTester{ep}
-	rd := ept.CheckReadFull(t, len(data), notifyCh, 5*time.Second)
-
-	if !bytes.Equal(data, rd) {
-		t.Fatalf("got data = %v, want = %v", rd, data)
-	}
-}
-
-func TestConnectAvoidsBoundPorts(t *testing.T) {
-	addressTypes := func(t *testing.T, network string) []string {
-		switch network {
-		case "ipv4":
-			return []string{"v4"}
-		case "ipv6":
-			return []string{"v6"}
-		case "dual":
-			return []string{"v6", "mapped"}
-		default:
-			t.Fatalf("unknown network: '%s'", network)
-		}
-
-		panic("unreachable")
-	}
-
-	address := func(t *testing.T, addressType string, isAny bool) tcpip.Address {
-		switch addressType {
-		case "v4":
-			if isAny {
-				return ""
-			}
-			return context.StackAddr
-		case "v6":
-			if isAny {
-				return ""
-			}
-			return context.StackV6Addr
-		case "mapped":
-			if isAny {
-				return context.V4MappedWildcardAddr
-			}
-			return context.StackV4MappedAddr
-		default:
-			t.Fatalf("unknown address type: '%s'", addressType)
-		}
-
-		panic("unreachable")
-	}
-	// This test ensures that Endpoint.Connect doesn't select already-bound ports.
-	networks := []string{"ipv4", "ipv6", "dual"}
-	for _, exhaustedNetwork := range networks {
-		t.Run(fmt.Sprintf("exhaustedNetwork=%s", exhaustedNetwork), func(t *testing.T) {
-			for _, exhaustedAddressType := range addressTypes(t, exhaustedNetwork) {
-				t.Run(fmt.Sprintf("exhaustedAddressType=%s", exhaustedAddressType), func(t *testing.T) {
-					for _, isAny := range []bool{false, true} {
-						t.Run(fmt.Sprintf("isAny=%t", isAny), func(t *testing.T) {
-							for _, candidateNetwork := range networks {
-								t.Run(fmt.Sprintf("candidateNetwork=%s", candidateNetwork), func(t *testing.T) {
-									for _, candidateAddressType := range addressTypes(t, candidateNetwork) {
-										t.Run(fmt.Sprintf("candidateAddressType=%s", candidateAddressType), func(t *testing.T) {
-											s, err := makeStack()
-											if err != nil {
-												t.Fatal(err)
-											}
-
-											var wq waiter.Queue
-											var eps []tcpip.Endpoint
-											defer func() {
-												for _, ep := range eps {
-													ep.Close()
-												}
-											}()
-											makeEP := func(network string) tcpip.Endpoint {
-												var networkProtocolNumber tcpip.NetworkProtocolNumber
-												switch network {
-												case "ipv4":
-													networkProtocolNumber = ipv4.ProtocolNumber
-												case "ipv6", "dual":
-													networkProtocolNumber = ipv6.ProtocolNumber
-												default:
-													t.Fatalf("unknown network: '%s'", network)
-												}
-												ep, err := s.NewEndpoint(tcp.ProtocolNumber, networkProtocolNumber, &wq)
-												if err != nil {
-													t.Fatalf("NewEndpoint failed: %s", err)
-												}
-												eps = append(eps, ep)
-												switch network {
-												case "ipv4":
-												case "ipv6":
-													ep.SocketOptions().SetV6Only(true)
-												case "dual":
-													ep.SocketOptions().SetV6Only(false)
-												default:
-													t.Fatalf("unknown network: '%s'", network)
-												}
-												return ep
-											}
-
-											var v4reserved, v6reserved bool
-											switch exhaustedAddressType {
-											case "v4", "mapped":
-												v4reserved = true
-											case "v6":
-												v6reserved = true
-												// Dual stack sockets bound to v6 any reserve on v4 as
-												// well.
-												if isAny {
-													switch exhaustedNetwork {
-													case "ipv6":
-													case "dual":
-														v4reserved = true
-													default:
-														t.Fatalf("unknown address type: '%s'", exhaustedNetwork)
-													}
-												}
-											default:
-												t.Fatalf("unknown address type: '%s'", exhaustedAddressType)
-											}
-											var collides bool
-											switch candidateAddressType {
-											case "v4", "mapped":
-												collides = v4reserved
-											case "v6":
-												collides = v6reserved
-											default:
-												t.Fatalf("unknown address type: '%s'", candidateAddressType)
-											}
-
-											start, end := s.PortRange()
-											for i := start; i <= end; i++ {
-												if makeEP(exhaustedNetwork).Bind(tcpip.FullAddress{Addr: address(t, exhaustedAddressType, isAny), Port: uint16(i)}); err != nil {
-													t.Fatalf("Bind(%d) failed: %s", i, err)
-												}
-											}
-											var want tcpip.Error = &tcpip.ErrConnectStarted{}
-											if collides {
-												want = &tcpip.ErrNoPortAvailable{}
-											}
-											if err := makeEP(candidateNetwork).Connect(tcpip.FullAddress{Addr: address(t, candidateAddressType, false), Port: 31337}); err != want {
-												t.Fatalf("got ep.Connect(..) = %s, want = %s", err, want)
-											}
-										})
-									}
-								})
-							}
-						})
-					}
-				})
-			}
-		})
-	}
-}
-
-func TestPathMTUDiscovery(t *testing.T) {
-	// This test verifies the stack retransmits packets after it receives an
-	// ICMP packet indicating that the path MTU has been exceeded.
-	c := context.New(t, 1500)
-	defer c.Cleanup()
-
-	// Create new connection with MSS of 1460.
-	const maxPayload = 1500 - header.TCPMinimumSize - header.IPv4MinimumSize
-	c.CreateConnectedWithRawOptions(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */, []byte{
-		header.TCPOptionMSS, 4, byte(maxPayload / 256), byte(maxPayload % 256),
-	})
-
-	// Send 3200 bytes of data.
-	const writeSize = 3200
-	data := make([]byte, writeSize)
-	for i := range data {
-		data[i] = byte(i)
-	}
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	receivePackets := func(c *context.Context, sizes []int, which int, seqNum uint32) []byte {
-		var ret []byte
-		iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-		for i, size := range sizes {
-			p := c.GetPacket()
-			if i == which {
-				ret = p
-			}
-			checker.IPv4(t, p,
-				checker.PayloadLen(size+header.TCPMinimumSize),
-				checker.TCP(
-					checker.DstPort(context.TestPort),
-					checker.TCPSeqNum(seqNum),
-					checker.TCPAckNum(uint32(iss)),
-					checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-				),
-			)
-			seqNum += uint32(size)
-		}
-		return ret
-	}
-
-	// Receive three packets.
-	sizes := []int{maxPayload, maxPayload, writeSize - 2*maxPayload}
-	first := receivePackets(c, sizes, 0, uint32(c.IRS)+1)
-
-	// Send "packet too big" messages back to netstack.
-	const newMTU = 1200
-	const newMaxPayload = newMTU - header.IPv4MinimumSize - header.TCPMinimumSize
-	mtu := []byte{0, 0, newMTU / 256, newMTU % 256}
-	c.SendICMPPacket(header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, mtu, first, newMTU)
-
-	// See retransmitted packets. None exceeding the new max.
-	sizes = []int{newMaxPayload, maxPayload - newMaxPayload, newMaxPayload, maxPayload - newMaxPayload, writeSize - 2*maxPayload}
-	receivePackets(c, sizes, -1, uint32(c.IRS)+1)
-}
-
-func TestTCPEndpointProbe(t *testing.T) {
-	c := context.New(t, 1500)
-	defer c.Cleanup()
-
-	invoked := make(chan struct{})
-	c.Stack().AddTCPProbe(func(state stack.TCPEndpointState) {
-		// Validate that the endpoint ID is what we expect.
-		//
-		// We don't do an extensive validation of every field but a
-		// basic sanity test.
-		if got, want := state.ID.LocalAddress, tcpip.Address(context.StackAddr); got != want {
-			t.Fatalf("got LocalAddress: %q, want: %q", got, want)
-		}
-		if got, want := state.ID.LocalPort, c.Port; got != want {
-			t.Fatalf("got LocalPort: %d, want: %d", got, want)
-		}
-		if got, want := state.ID.RemoteAddress, tcpip.Address(context.TestAddr); got != want {
-			t.Fatalf("got RemoteAddress: %q, want: %q", got, want)
-		}
-		if got, want := state.ID.RemotePort, uint16(context.TestPort); got != want {
-			t.Fatalf("got RemotePort: %d, want: %d", got, want)
-		}
-
-		invoked <- struct{}{}
-	})
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	data := []byte{1, 2, 3}
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket(data, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  c.IRS.Add(1),
-		RcvWnd:  30000,
-	})
-
-	select {
-	case <-invoked:
-	case <-time.After(100 * time.Millisecond):
-		t.Fatalf("TCP Probe function was not called")
-	}
-}
-
-func TestStackSetCongestionControl(t *testing.T) {
-	testCases := []struct {
-		cc  tcpip.CongestionControlOption
-		err tcpip.Error
-	}{
-		{"reno", nil},
-		{"cubic", nil},
-		{"blahblah", &tcpip.ErrNoSuchFile{}},
-	}
-
-	for _, tc := range testCases {
-		t.Run(fmt.Sprintf("SetTransportProtocolOption(.., %v)", tc.cc), func(t *testing.T) {
-			c := context.New(t, 1500)
-			defer c.Cleanup()
-
-			s := c.Stack()
-
-			var oldCC tcpip.CongestionControlOption
-			if err := s.TransportProtocolOption(tcp.ProtocolNumber, &oldCC); err != nil {
-				t.Fatalf("s.TransportProtocolOption(%v, %v) = %s", tcp.ProtocolNumber, &oldCC, err)
-			}
-
-			if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &tc.cc); err != tc.err {
-				t.Fatalf("s.SetTransportProtocolOption(%d, &%T(%s)) = %s, want = %s", tcp.ProtocolNumber, tc.cc, tc.cc, err, tc.err)
-			}
-
-			var cc tcpip.CongestionControlOption
-			if err := s.TransportProtocolOption(tcp.ProtocolNumber, &cc); err != nil {
-				t.Fatalf("s.TransportProtocolOption(%v, %v) = %v", tcp.ProtocolNumber, &cc, err)
-			}
-
-			got, want := cc, oldCC
-			// If SetTransportProtocolOption is expected to succeed
-			// then the returned value for congestion control should
-			// match the one specified in the
-			// SetTransportProtocolOption call above, else it should
-			// be what it was before the call to
-			// SetTransportProtocolOption.
-			if tc.err == nil {
-				want = tc.cc
-			}
-			if got != want {
-				t.Fatalf("got congestion control: %v, want: %v", got, want)
-			}
-		})
-	}
-}
-
-func TestStackAvailableCongestionControl(t *testing.T) {
-	c := context.New(t, 1500)
-	defer c.Cleanup()
-
-	s := c.Stack()
-
-	// Query permitted congestion control algorithms.
-	var aCC tcpip.TCPAvailableCongestionControlOption
-	if err := s.TransportProtocolOption(tcp.ProtocolNumber, &aCC); err != nil {
-		t.Fatalf("s.TransportProtocolOption(%v, %v) = %v", tcp.ProtocolNumber, &aCC, err)
-	}
-	if got, want := aCC, tcpip.TCPAvailableCongestionControlOption("reno cubic"); got != want {
-		t.Fatalf("got tcpip.TCPAvailableCongestionControlOption: %v, want: %v", got, want)
-	}
-}
-
-func TestStackSetAvailableCongestionControl(t *testing.T) {
-	c := context.New(t, 1500)
-	defer c.Cleanup()
-
-	s := c.Stack()
-
-	// Setting AvailableCongestionControlOption should fail.
-	aCC := tcpip.TCPAvailableCongestionControlOption("xyz")
-	if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &aCC); err == nil {
-		t.Fatalf("s.SetTransportProtocolOption(%d, &%T(%s)) = nil, want non-nil", tcp.ProtocolNumber, aCC, aCC)
-	}
-
-	// Verify that we still get the expected list of congestion control options.
-	var cc tcpip.TCPAvailableCongestionControlOption
-	if err := s.TransportProtocolOption(tcp.ProtocolNumber, &cc); err != nil {
-		t.Fatalf("s.TransportProtocolOptio(%d, &%T(%s)): %s", tcp.ProtocolNumber, cc, cc, err)
-	}
-	if got, want := cc, tcpip.TCPAvailableCongestionControlOption("reno cubic"); got != want {
-		t.Fatalf("got tcpip.TCPAvailableCongestionControlOption = %s, want = %s", got, want)
-	}
-}
-
-func TestEndpointSetCongestionControl(t *testing.T) {
-	testCases := []struct {
-		cc  tcpip.CongestionControlOption
-		err tcpip.Error
-	}{
-		{"reno", nil},
-		{"cubic", nil},
-		{"blahblah", &tcpip.ErrNoSuchFile{}},
-	}
-
-	for _, connected := range []bool{false, true} {
-		for _, tc := range testCases {
-			t.Run(fmt.Sprintf("SetSockOpt(.., %v) w/ connected = %v", tc.cc, connected), func(t *testing.T) {
-				c := context.New(t, 1500)
-				defer c.Cleanup()
-
-				// Create TCP endpoint.
-				var err tcpip.Error
-				c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-				if err != nil {
-					t.Fatalf("NewEndpoint failed: %s", err)
-				}
-
-				var oldCC tcpip.CongestionControlOption
-				if err := c.EP.GetSockOpt(&oldCC); err != nil {
-					t.Fatalf("c.EP.GetSockOpt(&%T) = %s", oldCC, err)
-				}
-
-				if connected {
-					c.Connect(context.TestInitialSequenceNumber, 32768 /* rcvWnd */, nil)
-				}
-
-				if err := c.EP.SetSockOpt(&tc.cc); err != tc.err {
-					t.Fatalf("got c.EP.SetSockOpt(&%#v) = %s, want %s", tc.cc, err, tc.err)
-				}
-
-				var cc tcpip.CongestionControlOption
-				if err := c.EP.GetSockOpt(&cc); err != nil {
-					t.Fatalf("c.EP.GetSockOpt(&%T): %s", cc, err)
-				}
-
-				got, want := cc, oldCC
-				// If SetSockOpt is expected to succeed then the
-				// returned value for congestion control should match
-				// the one specified in the SetSockOpt above, else it
-				// should be what it was before the call to SetSockOpt.
-				if tc.err == nil {
-					want = tc.cc
-				}
-				if got != want {
-					t.Fatalf("got congestion control = %+v, want = %+v", got, want)
-				}
-			})
-		}
-	}
-}
-
-func enableCUBIC(t *testing.T, c *context.Context) {
-	t.Helper()
-	opt := tcpip.CongestionControlOption("cubic")
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%s)) %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-}
-
-func TestKeepalive(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	const keepAliveIdle = 100 * time.Millisecond
-	const keepAliveInterval = 3 * time.Second
-	keepAliveIdleOpt := tcpip.KeepaliveIdleOption(keepAliveIdle)
-	if err := c.EP.SetSockOpt(&keepAliveIdleOpt); err != nil {
-		t.Fatalf("c.EP.SetSockOpt(&%T(%s)): %s", keepAliveIdleOpt, keepAliveIdle, err)
-	}
-	keepAliveIntervalOpt := tcpip.KeepaliveIntervalOption(keepAliveInterval)
-	if err := c.EP.SetSockOpt(&keepAliveIntervalOpt); err != nil {
-		t.Fatalf("c.EP.SetSockOpt(&%T(%s)): %s", keepAliveIntervalOpt, keepAliveInterval, err)
-	}
-	c.EP.SetSockOptInt(tcpip.KeepaliveCountOption, 5)
-	if err := c.EP.SetSockOptInt(tcpip.KeepaliveCountOption, 5); err != nil {
-		t.Fatalf("c.EP.SetSockOptInt(tcpip.KeepaliveCountOption, 5): %s", err)
-	}
-	c.EP.SocketOptions().SetKeepAlive(true)
-
-	// 5 unacked keepalives are sent. ACK each one, and check that the
-	// connection stays alive after 5.
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	for i := 0; i < 10; i++ {
-		b := c.GetPacket()
-		checker.IPv4(t, b,
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(c.IRS)),
-				checker.TCPAckNum(uint32(iss)),
-				checker.TCPFlags(header.TCPFlagAck),
-			),
-		)
-
-		// Acknowledge the keepalive.
-		c.SendPacket(nil, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss,
-			AckNum:  c.IRS,
-			RcvWnd:  30000,
-		})
-	}
-
-	// Check that the connection is still alive.
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	// Send some data and wait before ACKing it. Keepalives should be disabled
-	// during this period.
-	view := make([]byte, 3)
-	var r bytes.Reader
-	r.Reset(view)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	next := uint32(c.IRS) + 1
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	// Wait for the packet to be retransmitted. Verify that no keepalives
-	// were sent.
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck|header.TCPFlagPsh),
-		),
-	)
-	c.CheckNoPacket("Keepalive packet received while unACKed data is pending")
-
-	next += uint32(len(view))
-
-	// Send ACK. Keepalives should start sending again.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	// Now receive 5 keepalives, but don't ACK them. The connection
-	// should be reset after 5.
-	for i := 0; i < 5; i++ {
-		b := c.GetPacket()
-		checker.IPv4(t, b,
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(next-1)),
-				checker.TCPAckNum(uint32(iss)),
-				checker.TCPFlags(header.TCPFlagAck),
-			),
-		)
-	}
-
-	// Sleep for a litte over the KeepAlive interval to make sure
-	// the timer has time to fire after the last ACK and close the
-	// close the socket.
-	time.Sleep(keepAliveInterval + keepAliveInterval/2)
-
-	// The connection should be terminated after 5 unacked keepalives.
-	// Send an ACK to trigger a RST from the stack as the endpoint should
-	// be dead.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(next)),
-			checker.TCPAckNum(uint32(0)),
-			checker.TCPFlags(header.TCPFlagRst),
-		),
-	)
-
-	if got := c.Stack().Stats().TCP.EstablishedTimedout.Value(); got != 1 {
-		t.Errorf("got c.Stack().Stats().TCP.EstablishedTimedout.Value() = %d, want = 1", got)
-	}
-
-	ept.CheckReadError(t, &tcpip.ErrTimeout{})
-
-	if got := c.Stack().Stats().TCP.CurrentEstablished.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentEstablished.Value() = %d, want = 0", got)
-	}
-	if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 0", got)
-	}
-}
-
-func executeHandshake(t *testing.T, c *context.Context, srcPort uint16, synCookieInUse bool) (irs, iss seqnum.Value) {
-	t.Helper()
-	// Send a SYN request.
-	irs = seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: srcPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcp := header.TCP(header.IPv4(b).Payload())
-	iss = seqnum.Value(tcp.SequenceNumber())
-	tcpCheckers := []checker.TransportChecker{
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(srcPort),
-		checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn),
-		checker.TCPAckNum(uint32(irs) + 1),
-	}
-
-	if synCookieInUse {
-		// When cookies are in use window scaling is disabled.
-		tcpCheckers = append(tcpCheckers, checker.TCPSynOptions(header.TCPSynOptions{
-			WS:  -1,
-			MSS: c.MSSWithoutOptions(),
-		}))
-	}
-
-	checker.IPv4(t, b, checker.TCP(tcpCheckers...))
-
-	// Send ACK.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: srcPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-		RcvWnd:  30000,
-	})
-	return irs, iss
-}
-
-func executeV6Handshake(t *testing.T, c *context.Context, srcPort uint16, synCookieInUse bool) (irs, iss seqnum.Value) {
-	t.Helper()
-	// Send a SYN request.
-	irs = seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendV6Packet(nil, &context.Headers{
-		SrcPort: srcPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetV6Packet()
-	tcp := header.TCP(header.IPv6(b).Payload())
-	iss = seqnum.Value(tcp.SequenceNumber())
-	tcpCheckers := []checker.TransportChecker{
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(srcPort),
-		checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn),
-		checker.TCPAckNum(uint32(irs) + 1),
-	}
-
-	if synCookieInUse {
-		// When cookies are in use window scaling is disabled.
-		tcpCheckers = append(tcpCheckers, checker.TCPSynOptions(header.TCPSynOptions{
-			WS:  -1,
-			MSS: c.MSSWithoutOptionsV6(),
-		}))
-	}
-
-	checker.IPv6(t, b, checker.TCP(tcpCheckers...))
-
-	// Send ACK.
-	c.SendV6Packet(nil, &context.Headers{
-		SrcPort: srcPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-		RcvWnd:  30000,
-	})
-	return irs, iss
-}
-
-// TestListenBacklogFull tests that netstack does not complete handshakes if the
-// listen backlog for the endpoint is full.
-func TestListenBacklogFull(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Test acceptance.
-	// Start listening.
-	listenBacklog := 10
-	if err := c.EP.Listen(listenBacklog); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	lastPortOffset := uint16(0)
-	for ; int(lastPortOffset) < listenBacklog; lastPortOffset++ {
-		executeHandshake(t, c, context.TestPort+lastPortOffset, false /*synCookieInUse */)
-	}
-
-	time.Sleep(50 * time.Millisecond)
-
-	// Now execute send one more SYN. The stack should not respond as the backlog
-	// is full at this point.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort + uint16(lastPortOffset),
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  seqnum.Value(context.TestInitialSequenceNumber),
-		RcvWnd:  30000,
-	})
-	c.CheckNoPacketTimeout("unexpected packet received", 50*time.Millisecond)
-
-	// Try to accept the connections in the backlog.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	for i := 0; i < listenBacklog; i++ {
-		_, _, err = c.EP.Accept(nil)
-		if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-			// Wait for connection to be established.
-			select {
-			case <-ch:
-				_, _, err = c.EP.Accept(nil)
-				if err != nil {
-					t.Fatalf("Accept failed: %s", err)
-				}
-
-			case <-time.After(1 * time.Second):
-				t.Fatalf("Timed out waiting for accept")
-			}
-		}
-	}
-
-	// Now verify that there are no more connections that can be accepted.
-	_, _, err = c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-		select {
-		case <-ch:
-			t.Fatalf("unexpected endpoint delivered on Accept: %+v", c.EP)
-		case <-time.After(1 * time.Second):
-		}
-	}
-
-	// Now a new handshake must succeed.
-	executeHandshake(t, c, context.TestPort+lastPortOffset, false /*synCookieInUse */)
-
-	newEP, _, err := c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			newEP, _, err = c.EP.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Now verify that the TCP socket is usable and in a connected state.
-	data := "Don't panic"
-	var r strings.Reader
-	r.Reset(data)
-	newEP.Write(&r, tcpip.WriteOptions{})
-	b := c.GetPacket()
-	tcp := header.TCP(header.IPv4(b).Payload())
-	if string(tcp.Payload()) != data {
-		t.Fatalf("unexpected data: got %s, want %s", string(tcp.Payload()), data)
-	}
-}
-
-// TestListenNoAcceptMulticastBroadcastV4 makes sure that TCP segments with a
-// non unicast IPv4 address are not accepted.
-func TestListenNoAcceptNonUnicastV4(t *testing.T) {
-	multicastAddr := tcpip.Address("\xe0\x00\x01\x02")
-	otherMulticastAddr := tcpip.Address("\xe0\x00\x01\x03")
-	subnet := context.StackAddrWithPrefix.Subnet()
-	subnetBroadcastAddr := subnet.Broadcast()
-
-	tests := []struct {
-		name    string
-		srcAddr tcpip.Address
-		dstAddr tcpip.Address
-	}{
-		{
-			name:    "SourceUnspecified",
-			srcAddr: header.IPv4Any,
-			dstAddr: context.StackAddr,
-		},
-		{
-			name:    "SourceBroadcast",
-			srcAddr: header.IPv4Broadcast,
-			dstAddr: context.StackAddr,
-		},
-		{
-			name:    "SourceOurMulticast",
-			srcAddr: multicastAddr,
-			dstAddr: context.StackAddr,
-		},
-		{
-			name:    "SourceOtherMulticast",
-			srcAddr: otherMulticastAddr,
-			dstAddr: context.StackAddr,
-		},
-		{
-			name:    "DestUnspecified",
-			srcAddr: context.TestAddr,
-			dstAddr: header.IPv4Any,
-		},
-		{
-			name:    "DestBroadcast",
-			srcAddr: context.TestAddr,
-			dstAddr: header.IPv4Broadcast,
-		},
-		{
-			name:    "DestOurMulticast",
-			srcAddr: context.TestAddr,
-			dstAddr: multicastAddr,
-		},
-		{
-			name:    "DestOtherMulticast",
-			srcAddr: context.TestAddr,
-			dstAddr: otherMulticastAddr,
-		},
-		{
-			name:    "SrcSubnetBroadcast",
-			srcAddr: subnetBroadcastAddr,
-			dstAddr: context.StackAddr,
-		},
-		{
-			name:    "DestSubnetBroadcast",
-			srcAddr: context.TestAddr,
-			dstAddr: subnetBroadcastAddr,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			c.Create(-1)
-
-			if err := c.Stack().JoinGroup(header.IPv4ProtocolNumber, 1, multicastAddr); err != nil {
-				t.Fatalf("JoinGroup failed: %s", err)
-			}
-
-			if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-				t.Fatalf("Bind failed: %s", err)
-			}
-
-			if err := c.EP.Listen(1); err != nil {
-				t.Fatalf("Listen failed: %s", err)
-			}
-
-			irs := seqnum.Value(context.TestInitialSequenceNumber)
-			c.SendPacketWithAddrs(nil, &context.Headers{
-				SrcPort: context.TestPort,
-				DstPort: context.StackPort,
-				Flags:   header.TCPFlagSyn,
-				SeqNum:  irs,
-				RcvWnd:  30000,
-			}, test.srcAddr, test.dstAddr)
-			c.CheckNoPacket("Should not have received a response")
-
-			// Handle normal packet.
-			c.SendPacketWithAddrs(nil, &context.Headers{
-				SrcPort: context.TestPort,
-				DstPort: context.StackPort,
-				Flags:   header.TCPFlagSyn,
-				SeqNum:  irs,
-				RcvWnd:  30000,
-			}, context.TestAddr, context.StackAddr)
-			checker.IPv4(t, c.GetPacket(),
-				checker.TCP(
-					checker.SrcPort(context.StackPort),
-					checker.DstPort(context.TestPort),
-					checker.TCPFlags(header.TCPFlagAck|header.TCPFlagSyn),
-					checker.TCPAckNum(uint32(irs)+1)))
-		})
-	}
-}
-
-// TestListenNoAcceptMulticastBroadcastV6 makes sure that TCP segments with a
-// non unicast IPv6 address are not accepted.
-func TestListenNoAcceptNonUnicastV6(t *testing.T) {
-	multicastAddr := tcpip.Address("\xff\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01")
-	otherMulticastAddr := tcpip.Address("\xff\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02")
-
-	tests := []struct {
-		name    string
-		srcAddr tcpip.Address
-		dstAddr tcpip.Address
-	}{
-		{
-			"SourceUnspecified",
-			header.IPv6Any,
-			context.StackV6Addr,
-		},
-		{
-			"SourceAllNodes",
-			header.IPv6AllNodesMulticastAddress,
-			context.StackV6Addr,
-		},
-		{
-			"SourceOurMulticast",
-			multicastAddr,
-			context.StackV6Addr,
-		},
-		{
-			"SourceOtherMulticast",
-			otherMulticastAddr,
-			context.StackV6Addr,
-		},
-		{
-			"DestUnspecified",
-			context.TestV6Addr,
-			header.IPv6Any,
-		},
-		{
-			"DestAllNodes",
-			context.TestV6Addr,
-			header.IPv6AllNodesMulticastAddress,
-		},
-		{
-			"DestOurMulticast",
-			context.TestV6Addr,
-			multicastAddr,
-		},
-		{
-			"DestOtherMulticast",
-			context.TestV6Addr,
-			otherMulticastAddr,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			c := context.New(t, defaultMTU)
-			defer c.Cleanup()
-
-			c.CreateV6Endpoint(true)
-
-			if err := c.Stack().JoinGroup(header.IPv6ProtocolNumber, 1, multicastAddr); err != nil {
-				t.Fatalf("JoinGroup failed: %s", err)
-			}
-
-			if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-				t.Fatalf("Bind failed: %s", err)
-			}
-
-			if err := c.EP.Listen(1); err != nil {
-				t.Fatalf("Listen failed: %s", err)
-			}
-
-			irs := seqnum.Value(context.TestInitialSequenceNumber)
-			c.SendV6PacketWithAddrs(nil, &context.Headers{
-				SrcPort: context.TestPort,
-				DstPort: context.StackPort,
-				Flags:   header.TCPFlagSyn,
-				SeqNum:  irs,
-				RcvWnd:  30000,
-			}, test.srcAddr, test.dstAddr)
-			c.CheckNoPacket("Should not have received a response")
-
-			// Handle normal packet.
-			c.SendV6PacketWithAddrs(nil, &context.Headers{
-				SrcPort: context.TestPort,
-				DstPort: context.StackPort,
-				Flags:   header.TCPFlagSyn,
-				SeqNum:  irs,
-				RcvWnd:  30000,
-			}, context.TestV6Addr, context.StackV6Addr)
-			checker.IPv6(t, c.GetV6Packet(),
-				checker.TCP(
-					checker.SrcPort(context.StackPort),
-					checker.DstPort(context.TestPort),
-					checker.TCPFlags(header.TCPFlagAck|header.TCPFlagSyn),
-					checker.TCPAckNum(uint32(irs)+1)))
-		})
-	}
-}
-
-func TestListenSynRcvdQueueFull(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Test acceptance.
-	// Start listening.
-	listenBacklog := 1
-	if err := c.EP.Listen(listenBacklog); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send two SYN's the first one should get a SYN-ACK, the
-	// second one should not get any response and is dropped as
-	// the synRcvd count will be equal to backlog.
-	irs := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcp := header.TCP(header.IPv4(b).Payload())
-	iss := seqnum.Value(tcp.SequenceNumber())
-	tcpCheckers := []checker.TransportChecker{
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn),
-		checker.TCPAckNum(uint32(irs) + 1),
-	}
-	checker.IPv4(t, b, checker.TCP(tcpCheckers...))
-
-	// Now execute send one more SYN. The stack should not respond as the backlog
-	// is full at this point.
-	//
-	// NOTE: we did not complete the handshake for the previous one so the
-	// accept backlog should be empty and there should be one connection in
-	// synRcvd state.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort + 1,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  seqnum.Value(889),
-		RcvWnd:  30000,
-	})
-	c.CheckNoPacketTimeout("unexpected packet received", 50*time.Millisecond)
-
-	// Now complete the previous connection and verify that there is a connection
-	// to accept.
-	// Send ACK.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-		RcvWnd:  30000,
-	})
-
-	// Try to accept the connections in the backlog.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	newEP, _, err := c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			newEP, _, err = c.EP.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Now verify that the TCP socket is usable and in a connected state.
-	data := "Don't panic"
-	var r strings.Reader
-	r.Reset(data)
-	newEP.Write(&r, tcpip.WriteOptions{})
-	pkt := c.GetPacket()
-	tcp = header.TCP(header.IPv4(pkt).Payload())
-	if string(tcp.Payload()) != data {
-		t.Fatalf("unexpected data: got %s, want %s", string(tcp.Payload()), data)
-	}
-}
-
-func TestListenBacklogFullSynCookieInUse(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	opt := tcpip.TCPSynRcvdCountThresholdOption(1)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-	}
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Start listening.
-	listenBacklog := 1
-	if err := c.EP.Listen(listenBacklog); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	executeHandshake(t, c, context.TestPort, false)
-
-	// Wait for this to be delivered to the accept queue.
-	time.Sleep(50 * time.Millisecond)
-
-	// Send a SYN request.
-	irs := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		// pick a different src port for new SYN.
-		SrcPort: context.TestPort + 1,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-	// The Syn should be dropped as the endpoint's backlog is full.
-	c.CheckNoPacketTimeout("unexpected packet received", 50*time.Millisecond)
-
-	// Verify that there is only one acceptable connection at this point.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	_, _, err = c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			_, _, err = c.EP.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Now verify that there are no more connections that can be accepted.
-	_, _, err = c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-		select {
-		case <-ch:
-			t.Fatalf("unexpected endpoint delivered on Accept: %+v", c.EP)
-		case <-time.After(1 * time.Second):
-		}
-	}
-}
-
-func TestSYNRetransmit(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Start listening.
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send the same SYN packet multiple times. We should still get a valid SYN-ACK
-	// reply.
-	irs := seqnum.Value(context.TestInitialSequenceNumber)
-	for i := 0; i < 5; i++ {
-		c.SendPacket(nil, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: context.StackPort,
-			Flags:   header.TCPFlagSyn,
-			SeqNum:  irs,
-			RcvWnd:  30000,
-		})
-	}
-
-	// Receive the SYN-ACK reply.
-	tcpCheckers := []checker.TransportChecker{
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn),
-		checker.TCPAckNum(uint32(irs) + 1),
-	}
-	checker.IPv4(t, c.GetPacket(), checker.TCP(tcpCheckers...))
-}
-
-func TestSynRcvdBadSeqNumber(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Create TCP endpoint.
-	var err tcpip.Error
-	c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	// Bind to wildcard.
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Start listening.
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send a SYN to get a SYN-ACK. This should put the ep into SYN-RCVD state
-	irs := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  irs,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	iss := seqnum.Value(tcpHdr.SequenceNumber())
-	tcpCheckers := []checker.TransportChecker{
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck | header.TCPFlagSyn),
-		checker.TCPAckNum(uint32(irs) + 1),
-	}
-	checker.IPv4(t, b, checker.TCP(tcpCheckers...))
-
-	// Now send a packet with an out-of-window sequence number
-	largeSeqnum := irs + seqnum.Value(tcpHdr.WindowSize()) + 1
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  largeSeqnum,
-		AckNum:  iss + 1,
-		RcvWnd:  30000,
-	})
-
-	// Should receive an ACK with the expected SEQ number
-	b = c.GetPacket()
-	tcpCheckers = []checker.TransportChecker{
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck),
-		checker.TCPAckNum(uint32(irs) + 1),
-		checker.TCPSeqNum(uint32(iss + 1)),
-	}
-	checker.IPv4(t, b, checker.TCP(tcpCheckers...))
-
-	// Now that the socket replied appropriately with the ACK,
-	// complete the connection to test that the large SEQ num
-	// did not change the state from SYN-RCVD.
-
-	// Send ACK to move to ESTABLISHED state.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-		RcvWnd:  30000,
-	})
-
-	newEP, _, err := c.EP.Accept(nil)
-	switch err.(type) {
-	case nil, *tcpip.ErrWouldBlock:
-	default:
-		t.Fatalf("Accept failed: %s", err)
-	}
-
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Try to accept the connections in the backlog.
-		we, ch := waiter.NewChannelEntry(nil)
-		c.WQ.EventRegister(&we, waiter.EventIn)
-		defer c.WQ.EventUnregister(&we)
-
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			newEP, _, err = c.EP.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Now verify that the TCP socket is usable and in a connected state.
-	data := "Don't panic"
-	var r strings.Reader
-	r.Reset(data)
-	if _, err := newEP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	pkt := c.GetPacket()
-	tcpHdr = header.TCP(header.IPv4(pkt).Payload())
-	if string(tcpHdr.Payload()) != data {
-		t.Fatalf("unexpected data: got %s, want %s", string(tcpHdr.Payload()), data)
-	}
-}
-
-func TestPassiveConnectionAttemptIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	c.EP = ep
-	if err := ep.Bind(tcpip.FullAddress{Addr: context.StackAddr, Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateBound; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-	if err := c.EP.Listen(1); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-	if got, want := tcp.EndpointState(c.EP.State()), tcp.StateListen; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	stats := c.Stack().Stats()
-	want := stats.TCP.PassiveConnectionOpenings.Value() + 1
-
-	srcPort := uint16(context.TestPort)
-	executeHandshake(t, c, srcPort+1, false)
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	// Verify that there is only one acceptable connection at this point.
-	_, _, err = c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			_, _, err = c.EP.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	if got := stats.TCP.PassiveConnectionOpenings.Value(); got != want {
-		t.Errorf("got stats.TCP.PassiveConnectionOpenings.Value() = %d, want = %d", got, want)
-	}
-}
-
-func TestPassiveFailedConnectionAttemptIncrement(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	stats := c.Stack().Stats()
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	c.EP = ep
-	if err := c.EP.Bind(tcpip.FullAddress{Addr: context.StackAddr, Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-	if err := c.EP.Listen(1); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	srcPort := uint16(context.TestPort)
-	// Now attempt a handshakes it will fill up the accept backlog.
-	executeHandshake(t, c, srcPort, false)
-
-	// Give time for the final ACK to be processed as otherwise the next handshake could
-	// get accepted before the previous one based on goroutine scheduling.
-	time.Sleep(50 * time.Millisecond)
-
-	want := stats.TCP.ListenOverflowSynDrop.Value() + 1
-
-	// Now we will send one more SYN and this one should get dropped
-	// Send a SYN request.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: srcPort + 2,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  seqnum.Value(context.TestInitialSequenceNumber),
-		RcvWnd:  30000,
-	})
-
-	time.Sleep(50 * time.Millisecond)
-	if got := stats.TCP.ListenOverflowSynDrop.Value(); got != want {
-		t.Errorf("got stats.TCP.ListenOverflowSynDrop.Value() = %d, want = %d", got, want)
-	}
-	if got := c.EP.Stats().(*tcp.Stats).ReceiveErrors.ListenOverflowSynDrop.Value(); got != want {
-		t.Errorf("got EP stats Stats.ReceiveErrors.ListenOverflowSynDrop = %d, want = %d", got, want)
-	}
-
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	// Now check that there is one acceptable connections.
-	_, _, err = c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			_, _, err = c.EP.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-}
-
-func TestEndpointBindListenAcceptState(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateBound; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	ept := endpointTester{ep}
-	ept.CheckReadError(t, &tcpip.ErrNotConnected{})
-	if got := ep.Stats().(*tcp.Stats).ReadErrors.NotConnected.Value(); got != 1 {
-		t.Errorf("got EP stats Stats.ReadErrors.NotConnected got %d want %d", got, 1)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateListen; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	c.PassiveConnectWithOptions(100, 5, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	aep, _, err := ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			aep, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-	if got, want := tcp.EndpointState(aep.State()), tcp.StateEstablished; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-	{
-		err := aep.Connect(tcpip.FullAddress{Addr: context.TestAddr, Port: context.TestPort})
-		if _, ok := err.(*tcpip.ErrAlreadyConnected); !ok {
-			t.Errorf("unexpected error attempting to call connect on an established endpoint, got: %v, want: %s", err, &tcpip.ErrAlreadyConnected{})
-		}
-	}
-	// Listening endpoint remains in listen state.
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateListen; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-	ep.Close()
-	// Give worker goroutines time to receive the close notification.
-	time.Sleep(1 * time.Second)
-	if got, want := tcp.EndpointState(ep.State()), tcp.StateClose; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-	// Accepted endpoint remains open when the listen endpoint is closed.
-	if got, want := tcp.EndpointState(aep.State()), tcp.StateEstablished; got != want {
-		t.Errorf("unexpected endpoint state: want %s, got %s", want, got)
-	}
-
-}
-
-// This test verifies that the auto tuning does not grow the receive buffer if
-// the application is not reading the data actively.
-func TestReceiveBufferAutoTuningApplicationLimited(t *testing.T) {
-	const mtu = 1500
-	const mss = mtu - header.IPv4MinimumSize - header.TCPMinimumSize
-
-	c := context.New(t, mtu)
-	defer c.Cleanup()
-
-	stk := c.Stack()
-	// Set lower limits for auto-tuning tests. This is required because the
-	// test stops the worker which can cause packets to be dropped because
-	// the segment queue holding unprocessed packets is limited to 500.
-	const receiveBufferSize = 80 << 10 // 80KB.
-	const maxReceiveBufferSize = receiveBufferSize * 10
-	{
-		opt := tcpip.TCPReceiveBufferSizeRangeOption{Min: 1, Default: receiveBufferSize, Max: maxReceiveBufferSize}
-		if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%#v): %s", tcp.ProtocolNumber, opt, err)
-		}
-	}
-
-	// Enable auto-tuning.
-	{
-		opt := tcpip.TCPModerateReceiveBufferOption(true)
-		if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%T(%t)): %s", tcp.ProtocolNumber, opt, opt, err)
-		}
-	}
-	// Change the expected window scale to match the value needed for the
-	// maximum buffer size defined above.
-	c.WindowScale = uint8(tcp.FindWndScale(maxReceiveBufferSize))
-
-	rawEP := c.CreateConnectedWithOptions(header.TCPSynOptions{TS: true, WS: 4})
-
-	// NOTE: The timestamp values in the sent packets are meaningless to the
-	// peer so we just increment the timestamp value by 1 every batch as we
-	// are not really using them for anything. Send a single byte to verify
-	// the advertised window.
-	tsVal := rawEP.TSVal + 1
-
-	// Introduce a 25ms latency by delaying the first byte.
-	latency := 25 * time.Millisecond
-	time.Sleep(latency)
-	// Send an initial payload with atleast segment overhead size. The receive
-	// window would not grow for smaller segments.
-	rawEP.SendPacketWithTS(make([]byte, tcp.SegSize), tsVal)
-
-	pkt := rawEP.VerifyAndReturnACKWithTS(tsVal)
-	rcvWnd := header.TCP(header.IPv4(pkt).Payload()).WindowSize()
-
-	time.Sleep(25 * time.Millisecond)
-
-	// Allocate a large enough payload for the test.
-	payloadSize := receiveBufferSize * 2
-	b := make([]byte, int(payloadSize))
-
-	worker := (c.EP).(interface {
-		StopWork()
-		ResumeWork()
-	})
-	tsVal++
-
-	// Stop the worker goroutine.
-	worker.StopWork()
-	start := 0
-	end := payloadSize / 2
-	packetsSent := 0
-	for ; start < end; start += mss {
-		packetEnd := start + mss
-		if start+mss > end {
-			packetEnd = end
-		}
-		rawEP.SendPacketWithTS(b[start:packetEnd], tsVal)
-		packetsSent++
-	}
-
-	// Resume the worker so that it only sees the packets once all of them
-	// are waiting to be read.
-	worker.ResumeWork()
-
-	// Since we sent almost the full receive buffer worth of data (some may have
-	// been dropped due to segment overheads), we should get a zero window back.
-	pkt = c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(pkt).Payload())
-	gotRcvWnd := tcpHdr.WindowSize()
-	wantAckNum := tcpHdr.AckNumber()
-	if got, want := int(gotRcvWnd), 0; got != want {
-		t.Fatalf("got rcvWnd: %d, want: %d", got, want)
-	}
-
-	time.Sleep(25 * time.Millisecond)
-	// Verify that sending more data when receiveBuffer is exhausted.
-	rawEP.SendPacketWithTS(b[start:start+mss], tsVal)
-
-	// Now read all the data from the endpoint and verify that advertised
-	// window increases to the full available buffer size.
-	for {
-		_, err := c.EP.Read(ioutil.Discard, tcpip.ReadOptions{})
-		if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-			break
-		}
-	}
-
-	// Verify that we receive a non-zero window update ACK. When running
-	// under thread santizer this test can end up sending more than 1
-	// ack, 1 for the non-zero window
-	p := c.GetPacket()
-	checker.IPv4(t, p, checker.TCP(
-		checker.TCPAckNum(uint32(wantAckNum)),
-		func(t *testing.T, h header.Transport) {
-			tcp, ok := h.(header.TCP)
-			if !ok {
-				return
-			}
-			// We use 10% here as the error margin upwards as the initial window we
-			// got was afer 1 segment was already in the receive buffer queue.
-			tolerance := 1.1
-			if w := tcp.WindowSize(); w == 0 || w > uint16(float64(rcvWnd)*tolerance) {
-				t.Errorf("expected a non-zero window: got %d, want <= %d", w, uint16(float64(rcvWnd)*tolerance))
-			}
-		},
-	))
-}
-
-// This test verifies that the advertised window is auto-tuned up as the
-// application is reading the data that is being received.
-func TestReceiveBufferAutoTuning(t *testing.T) {
-	const mtu = 1500
-	const mss = mtu - header.IPv4MinimumSize - header.TCPMinimumSize
-
-	c := context.New(t, mtu)
-	defer c.Cleanup()
-
-	// Enable Auto-tuning.
-	stk := c.Stack()
-	// Disable out of window rate limiting for this test by setting it to 0 as we
-	// use out of window ACKs to measure the advertised window.
-	var tcpInvalidRateLimit stack.TCPInvalidRateLimitOption
-	if err := stk.SetOption(tcpInvalidRateLimit); err != nil {
-		t.Fatalf("e.stack.SetOption(%#v) = %s", tcpInvalidRateLimit, err)
-	}
-
-	const receiveBufferSize = 80 << 10 // 80KB.
-	const maxReceiveBufferSize = receiveBufferSize * 10
-	{
-		opt := tcpip.TCPReceiveBufferSizeRangeOption{Min: 1, Default: receiveBufferSize, Max: maxReceiveBufferSize}
-		if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%#v): %s", tcp.ProtocolNumber, opt, err)
-		}
-	}
-
-	// Enable auto-tuning.
-	{
-		opt := tcpip.TCPModerateReceiveBufferOption(true)
-		if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%T(%t)): %s", tcp.ProtocolNumber, opt, opt, err)
-		}
-	}
-	// Change the expected window scale to match the value needed for the
-	// maximum buffer size used by stack.
-	c.WindowScale = uint8(tcp.FindWndScale(maxReceiveBufferSize))
-
-	rawEP := c.CreateConnectedWithOptions(header.TCPSynOptions{TS: true, WS: 4})
-	tsVal := uint32(rawEP.TSVal)
-	rawEP.NextSeqNum--
-	rawEP.SendPacketWithTS(nil, tsVal)
-	rawEP.NextSeqNum++
-	pkt := rawEP.VerifyAndReturnACKWithTS(tsVal)
-	curRcvWnd := int(header.TCP(header.IPv4(pkt).Payload()).WindowSize()) << c.WindowScale
-	scaleRcvWnd := func(rcvWnd int) uint16 {
-		return uint16(rcvWnd >> uint16(c.WindowScale))
-	}
-	// Allocate a large array to send to the endpoint.
-	b := make([]byte, receiveBufferSize*48)
-
-	// In every iteration we will send double the number of bytes sent in
-	// the previous iteration and read the same from the app. The received
-	// window should grow by at least 2x of bytes read by the app in every
-	// RTT.
-	offset := 0
-	payloadSize := receiveBufferSize / 8
-	worker := (c.EP).(interface {
-		StopWork()
-		ResumeWork()
-	})
-	latency := 1 * time.Millisecond
-	for i := 0; i < 5; i++ {
-		tsVal++
-
-		// Stop the worker goroutine.
-		worker.StopWork()
-		start := offset
-		end := offset + payloadSize
-		totalSent := 0
-		packetsSent := 0
-		for ; start < end; start += mss {
-			rawEP.SendPacketWithTS(b[start:start+mss], tsVal)
-			totalSent += mss
-			packetsSent++
-		}
-
-		// Resume it so that it only sees the packets once all of them
-		// are waiting to be read.
-		worker.ResumeWork()
-
-		// Give 1ms for the worker to process the packets.
-		time.Sleep(1 * time.Millisecond)
-
-		lastACK := c.GetPacket()
-		// Discard any intermediate ACKs and only check the last ACK we get in a
-		// short time period of few ms.
-		for {
-			time.Sleep(1 * time.Millisecond)
-			pkt := c.GetPacketNonBlocking()
-			if pkt == nil {
-				break
-			}
-			lastACK = pkt
-		}
-		if got, want := int(header.TCP(header.IPv4(lastACK).Payload()).WindowSize()), int(scaleRcvWnd(curRcvWnd)); got > want {
-			t.Fatalf("advertised window got: %d, want <= %d", got, want)
-		}
-
-		// Now read all the data from the endpoint and invoke the
-		// moderation API to allow for receive buffer auto-tuning
-		// to happen before we measure the new window.
-		totalCopied := 0
-		for {
-			res, err := c.EP.Read(ioutil.Discard, tcpip.ReadOptions{})
-			if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-				break
-			}
-			totalCopied += res.Count
-		}
-
-		// Invoke the moderation API. This is required for auto-tuning
-		// to happen. This method is normally expected to be invoked
-		// from a higher layer than tcpip.Endpoint. So we simulate
-		// copying to userspace by invoking it explicitly here.
-		c.EP.ModerateRecvBuf(totalCopied)
-
-		// Now send a keep-alive packet to trigger an ACK so that we can
-		// measure the new window.
-		rawEP.NextSeqNum--
-		rawEP.SendPacketWithTS(nil, tsVal)
-		rawEP.NextSeqNum++
-
-		if i == 0 {
-			// In the first iteration the receiver based RTT is not
-			// yet known as a result the moderation code should not
-			// increase the advertised window.
-			rawEP.VerifyACKRcvWnd(scaleRcvWnd(curRcvWnd))
-		} else {
-			// Read loop above could generate an ACK if the window had dropped to
-			// zero and then read had opened it up.
-			lastACK := c.GetPacket()
-			// Discard any intermediate ACKs and only check the last ACK we get in a
-			// short time period of few ms.
-			for {
-				time.Sleep(1 * time.Millisecond)
-				pkt := c.GetPacketNonBlocking()
-				if pkt == nil {
-					break
-				}
-				lastACK = pkt
-			}
-			curRcvWnd = int(header.TCP(header.IPv4(lastACK).Payload()).WindowSize()) << c.WindowScale
-			// If thew new current window is close maxReceiveBufferSize then terminate
-			// the loop. This can happen before all iterations are done due to timing
-			// differences when running the test.
-			if int(float64(curRcvWnd)*1.1) > maxReceiveBufferSize/2 {
-				break
-			}
-			// Increase the latency after first two iterations to
-			// establish a low RTT value in the receiver since it
-			// only tracks the lowest value. This ensures that when
-			// ModerateRcvBuf is called the elapsed time is always >
-			// rtt. Without this the test is flaky due to delays due
-			// to scheduling/wakeup etc.
-			latency += 50 * time.Millisecond
-		}
-		time.Sleep(latency)
-		offset += payloadSize
-		payloadSize *= 2
-	}
-	// Check that at the end of our iterations the receive window grew close to the maximum
-	// permissible size of maxReceiveBufferSize/2
-	if got, want := int(float64(curRcvWnd)*1.1), maxReceiveBufferSize/2; got < want {
-		t.Fatalf("unexpected rcvWnd got: %d, want > %d", got, want)
-	}
-
-}
-
-func TestDelayEnabled(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-	checkDelayOption(t, c, false, false) // Delay is disabled by default.
-
-	for _, v := range []struct {
-		delayEnabled    tcpip.TCPDelayEnabled
-		wantDelayOption bool
-	}{
-		{delayEnabled: false, wantDelayOption: false},
-		{delayEnabled: true, wantDelayOption: true},
-	} {
-		c := context.New(t, defaultMTU)
-		defer c.Cleanup()
-		if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &v.delayEnabled); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%T(%t)): %s", tcp.ProtocolNumber, v.delayEnabled, v.delayEnabled, err)
-		}
-		checkDelayOption(t, c, v.delayEnabled, v.wantDelayOption)
-	}
-}
-
-func checkDelayOption(t *testing.T, c *context.Context, wantDelayEnabled tcpip.TCPDelayEnabled, wantDelayOption bool) {
-	t.Helper()
-
-	var gotDelayEnabled tcpip.TCPDelayEnabled
-	if err := c.Stack().TransportProtocolOption(tcp.ProtocolNumber, &gotDelayEnabled); err != nil {
-		t.Fatalf("TransportProtocolOption(tcp, &gotDelayEnabled) failed: %s", err)
-	}
-	if gotDelayEnabled != wantDelayEnabled {
-		t.Errorf("TransportProtocolOption(tcp, &gotDelayEnabled) got %t, want %t", gotDelayEnabled, wantDelayEnabled)
-	}
-
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, new(waiter.Queue))
-	if err != nil {
-		t.Fatalf("NewEndPoint(tcp, ipv4, new(waiter.Queue)) failed: %s", err)
-	}
-	gotDelayOption := ep.SocketOptions().GetDelayOption()
-	if gotDelayOption != wantDelayOption {
-		t.Errorf("ep.GetSockOptBool(tcpip.DelayOption) got: %t, want: %t", gotDelayOption, wantDelayOption)
-	}
-}
-
-func TestTCPLingerTimeout(t *testing.T) {
-	c := context.New(t, 1500 /* mtu */)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	testCases := []struct {
-		name             string
-		tcpLingerTimeout time.Duration
-		want             time.Duration
-	}{
-		{"NegativeLingerTimeout", -123123, -1},
-		// Zero is treated same as the stack's default TCP_LINGER2 timeout.
-		{"ZeroLingerTimeout", 0, tcp.DefaultTCPLingerTimeout},
-		{"InRangeLingerTimeout", 10 * time.Second, 10 * time.Second},
-		// Values > stack's TCPLingerTimeout are capped to the stack's
-		// value. Defaults to tcp.DefaultTCPLingerTimeout(60 seconds)
-		{"AboveMaxLingerTimeout", tcp.MaxTCPLingerTimeout + 5*time.Second, tcp.MaxTCPLingerTimeout},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			v := tcpip.TCPLingerTimeoutOption(tc.tcpLingerTimeout)
-			if err := c.EP.SetSockOpt(&v); err != nil {
-				t.Fatalf("SetSockOpt(&%T(%s)) = %s", v, tc.tcpLingerTimeout, err)
-			}
-
-			v = 0
-			if err := c.EP.GetSockOpt(&v); err != nil {
-				t.Fatalf("GetSockOpt(&%T) = %s", v, err)
-			}
-			if got, want := time.Duration(v), tc.want; got != want {
-				t.Fatalf("got linger timeout = %s, want = %s", got, want)
-			}
-		})
-	}
-}
-
-func TestTCPTimeWaitRSTIgnored(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send a SYN request.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	ackHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 1,
-	}
-
-	// Send ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	c.EP.Close()
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+1)),
-		checker.TCPAckNum(uint32(iss)+1),
-		checker.TCPFlags(header.TCPFlagFin|header.TCPFlagAck)))
-
-	finHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 2,
-	}
-
-	c.SendPacket(nil, finHeaders)
-
-	// Get the ACK to the FIN we just sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+2)),
-		checker.TCPAckNum(uint32(iss)+2),
-		checker.TCPFlags(header.TCPFlagAck)))
-
-	// Now send a RST and this should be ignored and not
-	// generate an ACK.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagRst,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 2,
-	})
-
-	c.CheckNoPacketTimeout("unexpected packet received in TIME_WAIT state", 1*time.Second)
-
-	// Out of order ACK should generate an immediate ACK in
-	// TIME_WAIT.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 3,
-	})
-
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+2)),
-		checker.TCPAckNum(uint32(iss)+2),
-		checker.TCPFlags(header.TCPFlagAck)))
-}
-
-func TestTCPTimeWaitOutOfOrder(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send a SYN request.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	ackHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 1,
-	}
-
-	// Send ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	c.EP.Close()
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+1)),
-		checker.TCPAckNum(uint32(iss)+1),
-		checker.TCPFlags(header.TCPFlagFin|header.TCPFlagAck)))
-
-	finHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 2,
-	}
-
-	c.SendPacket(nil, finHeaders)
-
-	// Get the ACK to the FIN we just sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+2)),
-		checker.TCPAckNum(uint32(iss)+2),
-		checker.TCPFlags(header.TCPFlagAck)))
-
-	// Out of order ACK should generate an immediate ACK in
-	// TIME_WAIT.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 3,
-	})
-
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+2)),
-		checker.TCPAckNum(uint32(iss)+2),
-		checker.TCPFlags(header.TCPFlagAck)))
-}
-
-func TestTCPTimeWaitNewSyn(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send a SYN request.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	ackHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 1,
-	}
-
-	// Send ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	c.EP.Close()
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+1)),
-		checker.TCPAckNum(uint32(iss)+1),
-		checker.TCPFlags(header.TCPFlagFin|header.TCPFlagAck)))
-
-	finHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 2,
-	}
-
-	c.SendPacket(nil, finHeaders)
-
-	// Get the ACK to the FIN we just sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+2)),
-		checker.TCPAckNum(uint32(iss)+2),
-		checker.TCPFlags(header.TCPFlagAck)))
-
-	// Send a SYN request w/ sequence number lower than
-	// the highest sequence number sent. We just reuse
-	// the same number.
-	iss = seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	c.CheckNoPacketTimeout("unexpected packet received in response to SYN", 1*time.Second)
-
-	// drain any older notifications from the notification channel before attempting
-	// 2nd connection.
-	select {
-	case <-ch:
-	default:
-	}
-
-	// Send a SYN request w/ sequence number higher than
-	// the highest sequence number sent.
-	iss = iss.Add(3)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b = c.GetPacket()
-	tcpHdr = header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	ackHeaders = &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 1,
-	}
-
-	// Send ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	// Try to accept the connection.
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-}
-
-func TestTCPTimeWaitDuplicateFINExtendsTimeWait(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set TCPTimeWaitTimeout to 5 seconds so that sockets are marked closed
-	// after 5 seconds in TIME_WAIT state.
-	tcpTimeWaitTimeout := 5 * time.Second
-	opt := tcpip.TCPTimeWaitTimeoutOption(tcpTimeWaitTimeout)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%s)): %s", tcp.ProtocolNumber, opt, tcpTimeWaitTimeout, err)
-	}
-
-	want := c.Stack().Stats().TCP.EstablishedClosed.Value() + 1
-
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send a SYN request.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	ackHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 1,
-	}
-
-	// Send ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	c.EP.Close()
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+1)),
-		checker.TCPAckNum(uint32(iss)+1),
-		checker.TCPFlags(header.TCPFlagFin|header.TCPFlagAck)))
-
-	finHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 2,
-	}
-
-	c.SendPacket(nil, finHeaders)
-
-	// Get the ACK to the FIN we just sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+2)),
-		checker.TCPAckNum(uint32(iss)+2),
-		checker.TCPFlags(header.TCPFlagAck)))
-
-	time.Sleep(2 * time.Second)
-
-	// Now send a duplicate FIN. This should cause the TIME_WAIT to extend
-	// by another 5 seconds and also send us a duplicate ACK as it should
-	// indicate that the final ACK was potentially lost.
-	c.SendPacket(nil, finHeaders)
-
-	// Get the ACK to the FIN we just sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+2)),
-		checker.TCPAckNum(uint32(iss)+2),
-		checker.TCPFlags(header.TCPFlagAck)))
-
-	// Sleep for 4 seconds so at this point we are 1 second past the
-	// original tcpLingerTimeout of 5 seconds.
-	time.Sleep(4 * time.Second)
-
-	// Send an ACK and it should not generate any packet as the socket
-	// should still be in TIME_WAIT for another another 5 seconds due
-	// to the duplicate FIN we sent earlier.
-	*ackHeaders = *finHeaders
-	ackHeaders.SeqNum = ackHeaders.SeqNum + 1
-	ackHeaders.Flags = header.TCPFlagAck
-	c.SendPacket(nil, ackHeaders)
-
-	c.CheckNoPacketTimeout("unexpected packet received from endpoint in TIME_WAIT", 1*time.Second)
-	// Now sleep for another 2 seconds so that we are past the
-	// extended TIME_WAIT of 7 seconds (2 + 5).
-	time.Sleep(2 * time.Second)
-
-	// Resend the same ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	// Receive the RST that should be generated as there is no valid
-	// endpoint.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(ackHeaders.AckNum)),
-		checker.TCPAckNum(0),
-		checker.TCPFlags(header.TCPFlagRst)))
-
-	if got := c.Stack().Stats().TCP.EstablishedClosed.Value(); got != want {
-		t.Errorf("got c.Stack().Stats().TCP.EstablishedClosed = %d, want = %d", got, want)
-	}
-	if got := c.Stack().Stats().TCP.CurrentEstablished.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentEstablished.Value() = %d, want = 0", got)
-	}
-}
-
-func TestTCPCloseWithData(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	// Set TCPTimeWaitTimeout to 5 seconds so that sockets are marked closed
-	// after 5 seconds in TIME_WAIT state.
-	tcpTimeWaitTimeout := 5 * time.Second
-	opt := tcpip.TCPTimeWaitTimeoutOption(tcpTimeWaitTimeout)
-	if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-		t.Fatalf("SetTransportProtocolOption(%d, &%T(%s)): %s", tcp.ProtocolNumber, opt, tcpTimeWaitTimeout, err)
-	}
-
-	wq := &waiter.Queue{}
-	ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
-	if err != nil {
-		t.Fatalf("NewEndpoint failed: %s", err)
-	}
-	if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := ep.Listen(10); err != nil {
-		t.Fatalf("Listen failed: %s", err)
-	}
-
-	// Send a SYN request.
-	iss := seqnum.Value(context.TestInitialSequenceNumber)
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagSyn,
-		SeqNum:  iss,
-		RcvWnd:  30000,
-	})
-
-	// Receive the SYN-ACK reply.
-	b := c.GetPacket()
-	tcpHdr := header.TCP(header.IPv4(b).Payload())
-	c.IRS = seqnum.Value(tcpHdr.SequenceNumber())
-
-	ackHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 1,
-		RcvWnd:  30000,
-	}
-
-	// Send ACK.
-	c.SendPacket(nil, ackHeaders)
-
-	// Try to accept the connection.
-	we, ch := waiter.NewChannelEntry(nil)
-	wq.EventRegister(&we, waiter.EventIn)
-	defer wq.EventUnregister(&we)
-
-	c.EP, _, err = ep.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for connection to be established.
-		select {
-		case <-ch:
-			c.EP, _, err = ep.Accept(nil)
-			if err != nil {
-				t.Fatalf("Accept failed: %s", err)
-			}
-
-		case <-time.After(1 * time.Second):
-			t.Fatalf("Timed out waiting for accept")
-		}
-	}
-
-	// Now trigger a passive close by sending a FIN.
-	finHeaders := &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck | header.TCPFlagFin,
-		SeqNum:  iss + 1,
-		AckNum:  c.IRS + 2,
-		RcvWnd:  30000,
-	}
-
-	c.SendPacket(nil, finHeaders)
-
-	// Get the ACK to the FIN we just sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+1)),
-		checker.TCPAckNum(uint32(iss)+2),
-		checker.TCPFlags(header.TCPFlagAck)))
-
-	// Now write a few bytes and then close the endpoint.
-	data := []byte{1, 2, 3}
-
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	// Check that data is received.
-	b = c.GetPacket()
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+2), // Acknum is initial sequence number + 1
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	if p := b[header.IPv4MinimumSize+header.TCPMinimumSize:]; !bytes.Equal(data, p) {
-		t.Errorf("got data = %x, want = %x", p, data)
-	}
-
-	c.EP.Close()
-	// Check the FIN.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(c.IRS+1)+uint32(len(data))),
-		checker.TCPAckNum(uint32(iss+2)),
-		checker.TCPFlags(header.TCPFlagFin|header.TCPFlagAck)))
-
-	// First send a partial ACK.
-	ackHeaders = &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 2,
-		AckNum:  c.IRS + 1 + seqnum.Value(len(data)-1),
-		RcvWnd:  30000,
-	}
-	c.SendPacket(nil, ackHeaders)
-
-	// Now send a full ACK.
-	ackHeaders = &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 2,
-		AckNum:  c.IRS + 1 + seqnum.Value(len(data)),
-		RcvWnd:  30000,
-	}
-	c.SendPacket(nil, ackHeaders)
-
-	// Now ACK the FIN.
-	ackHeaders.AckNum++
-	c.SendPacket(nil, ackHeaders)
-
-	// Now send an ACK and we should get a RST back as the endpoint should
-	// be in CLOSED state.
-	ackHeaders = &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss + 2,
-		AckNum:  c.IRS + 1 + seqnum.Value(len(data)),
-		RcvWnd:  30000,
-	}
-	c.SendPacket(nil, ackHeaders)
-
-	// Check the RST.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPSeqNum(uint32(ackHeaders.AckNum)),
-		checker.TCPAckNum(0),
-		checker.TCPFlags(header.TCPFlagRst)))
-}
-
-func TestTCPUserTimeout(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	waitEntry, notifyCh := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&waitEntry, waiter.EventHUp)
-	defer c.WQ.EventUnregister(&waitEntry)
-
-	origEstablishedTimedout := c.Stack().Stats().TCP.EstablishedTimedout.Value()
-
-	// Ensure that on the next retransmit timer fire, the user timeout has
-	// expired.
-	initRTO := 1 * time.Second
-	userTimeout := initRTO / 2
-	v := tcpip.TCPUserTimeoutOption(userTimeout)
-	if err := c.EP.SetSockOpt(&v); err != nil {
-		t.Fatalf("c.EP.SetSockOpt(&%T(%s): %s", v, userTimeout, err)
-	}
-
-	// Send some data and wait before ACKing it.
-	view := make([]byte, 3)
-	var r bytes.Reader
-	r.Reset(view)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Write failed: %s", err)
-	}
-
-	next := uint32(c.IRS) + 1
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(len(view)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(next),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-		),
-	)
-
-	// Wait for the retransmit timer to be fired and the user timeout to cause
-	// close of the connection.
-	select {
-	case <-notifyCh:
-	case <-time.After(2 * initRTO):
-		t.Fatalf("connection still alive after %s, should have been closed after :%s", 2*initRTO, userTimeout)
-	}
-
-	// No packet should be received as the connection should be silently
-	// closed due to timeout.
-	c.CheckNoPacket("unexpected packet received after userTimeout has expired")
-
-	next += uint32(len(view))
-
-	// The connection should be terminated after userTimeout has expired.
-	// Send an ACK to trigger a RST from the stack as the endpoint should
-	// be dead.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(next),
-		RcvWnd:  30000,
-	})
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(next)),
-			checker.TCPAckNum(uint32(0)),
-			checker.TCPFlags(header.TCPFlagRst),
-		),
-	)
-
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrTimeout{})
-
-	if got, want := c.Stack().Stats().TCP.EstablishedTimedout.Value(), origEstablishedTimedout+1; got != want {
-		t.Errorf("got c.Stack().Stats().TCP.EstablishedTimedout = %d, want = %d", got, want)
-	}
-	if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 0", got)
-	}
-}
-
-func TestKeepaliveWithUserTimeout(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRcvBuf */)
-
-	origEstablishedTimedout := c.Stack().Stats().TCP.EstablishedTimedout.Value()
-
-	const keepAliveIdle = 100 * time.Millisecond
-	const keepAliveInterval = 3 * time.Second
-	keepAliveIdleOption := tcpip.KeepaliveIdleOption(keepAliveIdle)
-	if err := c.EP.SetSockOpt(&keepAliveIdleOption); err != nil {
-		t.Fatalf("c.EP.SetSockOpt(&%T(%s)): %s", keepAliveIdleOption, keepAliveIdle, err)
-	}
-	keepAliveIntervalOption := tcpip.KeepaliveIntervalOption(keepAliveInterval)
-	if err := c.EP.SetSockOpt(&keepAliveIntervalOption); err != nil {
-		t.Fatalf("c.EP.SetSockOpt(&%T(%s)): %s", keepAliveIntervalOption, keepAliveInterval, err)
-	}
-	if err := c.EP.SetSockOptInt(tcpip.KeepaliveCountOption, 10); err != nil {
-		t.Fatalf("c.EP.SetSockOptInt(tcpip.KeepaliveCountOption, 10): %s", err)
-	}
-	c.EP.SocketOptions().SetKeepAlive(true)
-
-	// Set userTimeout to be the duration to be 1 keepalive
-	// probes. Which means that after the first probe is sent
-	// the second one should cause the connection to be
-	// closed due to userTimeout being hit.
-	userTimeout := tcpip.TCPUserTimeoutOption(keepAliveInterval)
-	if err := c.EP.SetSockOpt(&userTimeout); err != nil {
-		t.Fatalf("c.EP.SetSockOpt(&%T(%s)): %s", userTimeout, keepAliveInterval, err)
-	}
-
-	// Check that the connection is still alive.
-	ept := endpointTester{c.EP}
-	ept.CheckReadError(t, &tcpip.ErrWouldBlock{})
-
-	// Now receive 1 keepalives, but don't ACK it.
-	b := c.GetPacket()
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	checker.IPv4(t, b,
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)),
-			checker.TCPAckNum(uint32(iss)),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-
-	// Sleep for a litte over the KeepAlive interval to make sure
-	// the timer has time to fire after the last ACK and close the
-	// close the socket.
-	time.Sleep(keepAliveInterval + keepAliveInterval/2)
-
-	// The connection should be closed with a timeout.
-	// Send an ACK to trigger a RST from the stack as the endpoint should
-	// be dead.
-	c.SendPacket(nil, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  seqnum.Value(c.IRS + 1),
-		RcvWnd:  30000,
-	})
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS+1)),
-			checker.TCPAckNum(uint32(0)),
-			checker.TCPFlags(header.TCPFlagRst),
-		),
-	)
-
-	ept.CheckReadError(t, &tcpip.ErrTimeout{})
-	if got, want := c.Stack().Stats().TCP.EstablishedTimedout.Value(), origEstablishedTimedout+1; got != want {
-		t.Errorf("got c.Stack().Stats().TCP.EstablishedTimedout = %d, want = %d", got, want)
-	}
-	if got := c.Stack().Stats().TCP.CurrentConnected.Value(); got != 0 {
-		t.Errorf("got stats.TCP.CurrentConnected.Value() = %d, want = 0", got)
-	}
-}
-
-func TestIncreaseWindowOnRead(t *testing.T) {
-	// This test ensures that the endpoint sends an ack,
-	// after read() when the window grows by more than 1 MSS.
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	const rcvBuf = 65535 * 10
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, rcvBuf)
-
-	// Write chunks of ~30000 bytes. It's important that two
-	// payloads make it equal or longer than MSS.
-	remain := rcvBuf * 2
-	sent := 0
-	data := make([]byte, defaultMTU/2)
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	for remain > len(data) {
-		c.SendPacket(data, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss.Add(seqnum.Size(sent)),
-			AckNum:  c.IRS.Add(1),
-			RcvWnd:  30000,
-		})
-		sent += len(data)
-		remain -= len(data)
-		pkt := c.GetPacket()
-		checker.IPv4(t, pkt,
-			checker.PayloadLen(header.TCPMinimumSize),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(c.IRS)+1),
-				checker.TCPAckNum(uint32(iss)+uint32(sent)),
-				checker.TCPFlags(header.TCPFlagAck),
-			),
-		)
-		// Break once the window drops below defaultMTU/2
-		if wnd := header.TCP(header.IPv4(pkt).Payload()).WindowSize(); wnd < defaultMTU/2 {
-			break
-		}
-	}
-
-	// We now have < 1 MSS in the buffer space. Read at least > 2 MSS
-	// worth of data as receive buffer space
-	w := tcpip.LimitedWriter{
-		W: ioutil.Discard,
-		// defaultMTU is a good enough estimate for the MSS used for this
-		// connection.
-		N: defaultMTU * 2,
-	}
-	for w.N != 0 {
-		_, err := c.EP.Read(&w, tcpip.ReadOptions{})
-		if err != nil {
-			t.Fatalf("Read failed: %s", err)
-		}
-	}
-
-	// After reading > MSS worth of data, we surely crossed MSS. See the ack:
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(sent)),
-			checker.TCPWindow(uint16(0xffff)),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestIncreaseWindowOnBufferResize(t *testing.T) {
-	// This test ensures that the endpoint sends an ack,
-	// after available recv buffer grows to more than 1 MSS.
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	const rcvBuf = 65535 * 10
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, rcvBuf)
-
-	// Write chunks of ~30000 bytes. It's important that two
-	// payloads make it equal or longer than MSS.
-	remain := rcvBuf
-	sent := 0
-	data := make([]byte, defaultMTU/2)
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	for remain > len(data) {
-		c.SendPacket(data, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			Flags:   header.TCPFlagAck,
-			SeqNum:  iss.Add(seqnum.Size(sent)),
-			AckNum:  c.IRS.Add(1),
-			RcvWnd:  30000,
-		})
-		sent += len(data)
-		remain -= len(data)
-		checker.IPv4(t, c.GetPacket(),
-			checker.PayloadLen(header.TCPMinimumSize),
-			checker.TCP(
-				checker.DstPort(context.TestPort),
-				checker.TCPSeqNum(uint32(c.IRS)+1),
-				checker.TCPAckNum(uint32(iss)+uint32(sent)),
-				checker.TCPWindowLessThanEq(0xffff),
-				checker.TCPFlags(header.TCPFlagAck),
-			),
-		)
-	}
-
-	// Increasing the buffer from should generate an ACK,
-	// since window grew from small value to larger equal MSS
-	c.EP.SetSockOptInt(tcpip.ReceiveBufferSizeOption, rcvBuf*2)
-
-	checker.IPv4(t, c.GetPacket(),
-		checker.PayloadLen(header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(uint32(iss)+uint32(sent)),
-			checker.TCPWindow(uint16(0xffff)),
-			checker.TCPFlags(header.TCPFlagAck),
-		),
-	)
-}
-
-func TestTCPDeferAccept(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.Create(-1)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	const tcpDeferAccept = 1 * time.Second
-	tcpDeferAcceptOption := tcpip.TCPDeferAcceptOption(tcpDeferAccept)
-	if err := c.EP.SetSockOpt(&tcpDeferAcceptOption); err != nil {
-		t.Fatalf("c.EP.SetSockOpt(&%T(%s)): %s", tcpDeferAcceptOption, tcpDeferAccept, err)
-	}
-
-	irs, iss := executeHandshake(t, c, context.TestPort, false /* synCookiesInUse */)
-
-	_, _, err := c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-		t.Fatalf("got c.EP.Accept(nil) = %v, want: %s", err, &tcpip.ErrWouldBlock{})
-	}
-
-	// Send data. This should result in an acceptable endpoint.
-	c.SendPacket([]byte{1, 2, 3, 4}, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-	})
-
-	// Receive ACK for the data we sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(iss+1)),
-		checker.TCPAckNum(uint32(irs+5))))
-
-	// Give a bit of time for the socket to be delivered to the accept queue.
-	time.Sleep(50 * time.Millisecond)
-	aep, _, err := c.EP.Accept(nil)
-	if err != nil {
-		t.Fatalf("got c.EP.Accept(nil) = %s, want: nil", err)
-	}
-
-	aep.Close()
-	// Closing aep without reading the data should trigger a RST.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(iss+1)),
-		checker.TCPAckNum(uint32(irs+5))))
-}
-
-func TestTCPDeferAcceptTimeout(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.Create(-1)
-
-	if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
-		t.Fatal("Bind failed:", err)
-	}
-
-	if err := c.EP.Listen(10); err != nil {
-		t.Fatal("Listen failed:", err)
-	}
-
-	const tcpDeferAccept = 1 * time.Second
-	tcpDeferAcceptOpt := tcpip.TCPDeferAcceptOption(tcpDeferAccept)
-	if err := c.EP.SetSockOpt(&tcpDeferAcceptOpt); err != nil {
-		t.Fatalf("c.EP.SetSockOpt(&%T(%s)) failed: %s", tcpDeferAcceptOpt, tcpDeferAccept, err)
-	}
-
-	irs, iss := executeHandshake(t, c, context.TestPort, false /* synCookiesInUse */)
-
-	_, _, err := c.EP.Accept(nil)
-	if _, ok := err.(*tcpip.ErrWouldBlock); !ok {
-		t.Fatalf("got c.EP.Accept(nil) = %v, want: %s", err, &tcpip.ErrWouldBlock{})
-	}
-
-	// Sleep for a little of the tcpDeferAccept timeout.
-	time.Sleep(tcpDeferAccept + 100*time.Millisecond)
-
-	// On timeout expiry we should get a SYN-ACK retransmission.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck|header.TCPFlagSyn),
-		checker.TCPAckNum(uint32(irs)+1)))
-
-	// Send data. This should result in an acceptable endpoint.
-	c.SendPacket([]byte{1, 2, 3, 4}, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: context.StackPort,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  irs + 1,
-		AckNum:  iss + 1,
-	})
-
-	// Receive ACK for the data we sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(iss+1)),
-		checker.TCPAckNum(uint32(irs+5))))
-
-	// Give sometime for the endpoint to be delivered to the accept queue.
-	time.Sleep(50 * time.Millisecond)
-	aep, _, err := c.EP.Accept(nil)
-	if err != nil {
-		t.Fatalf("got c.EP.Accept(nil) = %s, want: nil", err)
-	}
-
-	aep.Close()
-	// Closing aep without reading the data should trigger a RST.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.SrcPort(context.StackPort),
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(iss+1)),
-		checker.TCPAckNum(uint32(irs+5))))
-}
-
-func TestResetDuringClose(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnected(context.TestInitialSequenceNumber, 30000, -1 /* epRecvBuf */)
-	// Send some data to make sure there is some unread
-	// data to trigger a reset on c.Close.
-	irs := c.IRS
-	iss := seqnum.Value(context.TestInitialSequenceNumber).Add(1)
-	c.SendPacket([]byte{1, 2, 3, 4}, &context.Headers{
-		SrcPort: context.TestPort,
-		DstPort: c.Port,
-		Flags:   header.TCPFlagAck,
-		SeqNum:  iss,
-		AckNum:  irs.Add(1),
-		RcvWnd:  30000,
-	})
-
-	// Receive ACK for the data we sent.
-	checker.IPv4(t, c.GetPacket(), checker.TCP(
-		checker.DstPort(context.TestPort),
-		checker.TCPFlags(header.TCPFlagAck),
-		checker.TCPSeqNum(uint32(irs.Add(1))),
-		checker.TCPAckNum(uint32(iss)+4)))
-
-	// Close in a separate goroutine so that we can trigger
-	// a race with the RST we send below. This should not
-	// panic due to the route being released depeding on
-	// whether Close() sends an active RST or the RST sent
-	// below is processed by the worker first.
-	var wg sync.WaitGroup
-
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-		c.SendPacket(nil, &context.Headers{
-			SrcPort: context.TestPort,
-			DstPort: c.Port,
-			SeqNum:  iss.Add(4),
-			AckNum:  c.IRS.Add(5),
-			RcvWnd:  30000,
-			Flags:   header.TCPFlagRst,
-		})
-	}()
-
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-		c.EP.Close()
-	}()
-
-	wg.Wait()
-}
-
-func TestStackTimeWaitReuse(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	s := c.Stack()
-	var twReuse tcpip.TCPTimeWaitReuseOption
-	if err := s.TransportProtocolOption(tcp.ProtocolNumber, &twReuse); err != nil {
-		t.Fatalf("s.TransportProtocolOption(%v, %v) = %v", tcp.ProtocolNumber, &twReuse, err)
-	}
-	if got, want := twReuse, tcpip.TCPTimeWaitReuseLoopbackOnly; got != want {
-		t.Fatalf("got tcpip.TCPTimeWaitReuseOption: %v, want: %v", got, want)
-	}
-}
-
-func TestSetStackTimeWaitReuse(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	s := c.Stack()
-	testCases := []struct {
-		v   int
-		err tcpip.Error
-	}{
-		{int(tcpip.TCPTimeWaitReuseDisabled), nil},
-		{int(tcpip.TCPTimeWaitReuseGlobal), nil},
-		{int(tcpip.TCPTimeWaitReuseLoopbackOnly), nil},
-		{int(tcpip.TCPTimeWaitReuseLoopbackOnly) + 1, &tcpip.ErrInvalidOptionValue{}},
-		{int(tcpip.TCPTimeWaitReuseDisabled) - 1, &tcpip.ErrInvalidOptionValue{}},
-	}
-
-	for _, tc := range testCases {
-		opt := tcpip.TCPTimeWaitReuseOption(tc.v)
-		err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt)
-		if got, want := err, tc.err; got != want {
-			t.Fatalf("s.SetTransportProtocolOption(%d, &%T(%d)) = %s, want = %s", tcp.ProtocolNumber, tc.v, tc.v, err, tc.err)
-		}
-		if tc.err != nil {
-			continue
-		}
-
-		var twReuse tcpip.TCPTimeWaitReuseOption
-		if err := s.TransportProtocolOption(tcp.ProtocolNumber, &twReuse); err != nil {
-			t.Fatalf("s.TransportProtocolOption(%v, %v) = %v, want nil", tcp.ProtocolNumber, &twReuse, err)
-		}
-
-		if got, want := twReuse, tcpip.TCPTimeWaitReuseOption(tc.v); got != want {
-			t.Fatalf("got tcpip.TCPTimeWaitReuseOption: %v, want: %v", got, want)
-		}
-	}
-}
-
-// generateRandomPayload generates a random byte slice of the specified length
-// causing a fatal test failure if it is unable to do so.
-func generateRandomPayload(t *testing.T, n int) []byte {
-	t.Helper()
-	buf := make([]byte, n)
-	if _, err := rand.Read(buf); err != nil {
-		t.Fatalf("rand.Read(buf) failed: %s", err)
-	}
-	return buf
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_timestamp_test.go b/pkg/tcpip/transport/tcp/tcp_timestamp_test.go
deleted file mode 100644
index cb4f829..0000000
--- a/pkg/tcpip/transport/tcp/tcp_timestamp_test.go
+++ /dev/null
@@ -1,311 +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 tcp_test
-
-import (
-	"bytes"
-	"math/rand"
-	"testing"
-	"time"
-
-	"github.com/google/go-cmp/cmp"
-	"gvisor.dev/gvisor/pkg/tcpip"
-	"gvisor.dev/gvisor/pkg/tcpip/checker"
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp/testing/context"
-	"gvisor.dev/gvisor/pkg/waiter"
-)
-
-// createConnectedWithTimestampOption creates and connects c.ep with the
-// timestamp option enabled.
-func createConnectedWithTimestampOption(c *context.Context) *context.RawEndpoint {
-	return c.CreateConnectedWithOptions(header.TCPSynOptions{TS: true, TSVal: 1})
-}
-
-// TestTimeStampEnabledConnect tests that netstack sends the timestamp option on
-// an active connect and sets the TS Echo Reply fields correctly when the
-// SYN-ACK also indicates support for the TS option and provides a TSVal.
-func TestTimeStampEnabledConnect(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	rep := createConnectedWithTimestampOption(c)
-
-	// Register for read and validate that we have data to read.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	// The following tests ensure that TS option once enabled behaves
-	// correctly as described in
-	// https://tools.ietf.org/html/rfc7323#section-4.3.
-	//
-	// We are not testing delayed ACKs here, but we do test out of order
-	// packet delivery and filling the sequence number hole created due to
-	// the out of order packet.
-	//
-	// The test also verifies that the sequence numbers and timestamps are
-	// as expected.
-	data := []byte{1, 2, 3}
-
-	// First we increment tsVal by a small amount.
-	tsVal := rep.TSVal + 100
-	rep.SendPacketWithTS(data, tsVal)
-	rep.VerifyACKWithTS(tsVal)
-
-	// Next we send an out of order packet.
-	rep.NextSeqNum += 3
-	tsVal += 200
-	rep.SendPacketWithTS(data, tsVal)
-
-	// The ACK should contain the original sequenceNumber and an older TS.
-	rep.NextSeqNum -= 6
-	rep.VerifyACKWithTS(tsVal - 200)
-
-	// Next we fill the hole and the returned ACK should contain the
-	// cumulative sequence number acking all data sent till now and have the
-	// latest timestamp sent below in its TSEcr field.
-	tsVal -= 100
-	rep.SendPacketWithTS(data, tsVal)
-	rep.NextSeqNum += 3
-	rep.VerifyACKWithTS(tsVal)
-
-	// Increment tsVal by a large value that doesn't result in a wrap around.
-	tsVal += 0x7fffffff
-	rep.SendPacketWithTS(data, tsVal)
-	rep.VerifyACKWithTS(tsVal)
-
-	// Increment tsVal again by a large value which should cause the
-	// timestamp value to wrap around. The returned ACK should contain the
-	// wrapped around timestamp in its tsEcr field and not the tsVal from
-	// the previous packet sent above.
-	tsVal += 0x7fffffff
-	rep.SendPacketWithTS(data, tsVal)
-	rep.VerifyACKWithTS(tsVal)
-
-	select {
-	case <-ch:
-	case <-time.After(1 * time.Second):
-		t.Fatalf("Timed out waiting for data to arrive")
-	}
-
-	// There should be 5 views to read and each of them should
-	// contain the same data.
-	for i := 0; i < 5; i++ {
-		buf := make([]byte, len(data))
-		w := tcpip.SliceWriter(buf)
-		result, err := c.EP.Read(&w, tcpip.ReadOptions{})
-		if err != nil {
-			t.Fatalf("Unexpected error from Read: %v", err)
-		}
-		if diff := cmp.Diff(tcpip.ReadResult{
-			Count: len(buf),
-			Total: len(buf),
-		}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
-			t.Errorf("Read: unexpected result (-want +got):\n%s", diff)
-		}
-		if got, want := buf, data; bytes.Compare(got, want) != 0 {
-			t.Fatalf("Data is different: got: %v, want: %v", got, want)
-		}
-	}
-}
-
-// TestTimeStampDisabledConnect tests that netstack sends timestamp option on an
-// active connect but if the SYN-ACK doesn't specify the TS option then
-// timestamp option is not enabled and future packets do not contain a
-// timestamp.
-func TestTimeStampDisabledConnect(t *testing.T) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	c.CreateConnectedWithOptions(header.TCPSynOptions{})
-}
-
-func timeStampEnabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndSize uint16) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	if cookieEnabled {
-		var opt tcpip.TCPSynRcvdCountThresholdOption
-		if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-		}
-	}
-
-	t.Logf("Test w/ CookieEnabled = %v", cookieEnabled)
-	tsVal := rand.Uint32()
-	c.AcceptWithOptions(wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS, TS: true, TSVal: tsVal})
-
-	// Now send some data and validate that timestamp is echoed correctly in the ACK.
-	data := []byte{1, 2, 3}
-
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Unexpected error from Write: %s", err)
-	}
-
-	// Check that data is received and that the timestamp option TSEcr field
-	// matches the expected value.
-	b := c.GetPacket()
-	checker.IPv4(t, b,
-		// Add 12 bytes for the timestamp option + 2 NOPs to align at 4
-		// byte boundary.
-		checker.PayloadLen(len(data)+header.TCPMinimumSize+12),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(790),
-			checker.TCPWindow(wndSize),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-			checker.TCPTimestampChecker(true, 0, tsVal+1),
-		),
-	)
-}
-
-// TestTimeStampEnabledAccept tests that if the SYN on a passive connect
-// specifies the Timestamp option then the Timestamp option is sent on a SYN-ACK
-// and echoes the tsVal field of the original SYN in the tcEcr field of the
-// SYN-ACK. We cover the cases where SYN cookies are enabled/disabled and verify
-// that Timestamp option is enabled in both cases if requested in the original
-// SYN.
-func TestTimeStampEnabledAccept(t *testing.T) {
-	testCases := []struct {
-		cookieEnabled bool
-		wndScale      int
-		wndSize       uint16
-	}{
-		{true, -1, 0xffff}, // When cookie is used window scaling is disabled.
-		// DefaultReceiveBufferSize is 1MB >> 5. Advertised window will be 1/2 of that.
-		{false, 5, 0x4000},
-	}
-	for _, tc := range testCases {
-		timeStampEnabledAccept(t, tc.cookieEnabled, tc.wndScale, tc.wndSize)
-	}
-}
-
-func timeStampDisabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndSize uint16) {
-	c := context.New(t, defaultMTU)
-	defer c.Cleanup()
-
-	if cookieEnabled {
-		var opt tcpip.TCPSynRcvdCountThresholdOption
-		if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
-			t.Fatalf("SetTransportProtocolOption(%d, &%T(%d)): %s", tcp.ProtocolNumber, opt, opt, err)
-		}
-	}
-
-	t.Logf("Test w/ CookieEnabled = %v", cookieEnabled)
-	c.AcceptWithOptions(wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS})
-
-	// Now send some data with the accepted connection endpoint and validate
-	// that no timestamp option is sent in the TCP segment.
-	data := []byte{1, 2, 3}
-
-	var r bytes.Reader
-	r.Reset(data)
-	if _, err := c.EP.Write(&r, tcpip.WriteOptions{}); err != nil {
-		t.Fatalf("Unexpected error from Write: %s", err)
-	}
-
-	// Check that data is received and that the timestamp option is disabled
-	// when SYN cookies are enabled/disabled.
-	b := c.GetPacket()
-	checker.IPv4(t, b,
-		checker.PayloadLen(len(data)+header.TCPMinimumSize),
-		checker.TCP(
-			checker.DstPort(context.TestPort),
-			checker.TCPSeqNum(uint32(c.IRS)+1),
-			checker.TCPAckNum(790),
-			checker.TCPWindow(wndSize),
-			checker.TCPFlagsMatch(header.TCPFlagAck, ^header.TCPFlagPsh),
-			checker.TCPTimestampChecker(false, 0, 0),
-		),
-	)
-}
-
-// TestTimeStampDisabledAccept tests that Timestamp option is not used when the
-// peer doesn't advertise it and connection is established with Accept().
-func TestTimeStampDisabledAccept(t *testing.T) {
-	testCases := []struct {
-		cookieEnabled bool
-		wndScale      int
-		wndSize       uint16
-	}{
-		{true, -1, 0xffff}, // When cookie is used window scaling is disabled.
-		// DefaultReceiveBufferSize is 1MB >> 5. Advertised window will be half of
-		// that.
-		{false, 5, 0x4000},
-	}
-	for _, tc := range testCases {
-		timeStampDisabledAccept(t, tc.cookieEnabled, tc.wndScale, tc.wndSize)
-	}
-}
-
-func TestSendGreaterThanMTUWithOptions(t *testing.T) {
-	const maxPayload = 100
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
-	defer c.Cleanup()
-
-	createConnectedWithTimestampOption(c)
-	testBrokenUpWrite(t, c, maxPayload)
-}
-
-func TestSegmentNotDroppedWhenTimestampMissing(t *testing.T) {
-	const maxPayload = 100
-	c := context.New(t, uint32(header.TCPMinimumSize+header.IPv4MinimumSize+maxPayload))
-	defer c.Cleanup()
-
-	rep := createConnectedWithTimestampOption(c)
-
-	// Register for read.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.WQ.EventRegister(&we, waiter.EventIn)
-	defer c.WQ.EventUnregister(&we)
-
-	droppedPacketsStat := c.Stack().Stats().DroppedPackets
-	droppedPackets := droppedPacketsStat.Value()
-	data := []byte{1, 2, 3}
-	// Send a packet with no TCP options/timestamp.
-	rep.SendPacket(data, nil)
-
-	select {
-	case <-ch:
-	case <-time.After(1 * time.Second):
-		t.Fatalf("Timed out waiting for data to arrive")
-	}
-
-	// Assert that DroppedPackets was not incremented.
-	if got, want := droppedPacketsStat.Value(), droppedPackets; got != want {
-		t.Fatalf("incorrect number of dropped packets, got: %v, want: %v", got, want)
-	}
-
-	// Issue a read and we should data.
-	var buf bytes.Buffer
-	result, err := c.EP.Read(&buf, tcpip.ReadOptions{})
-	if err != nil {
-		t.Fatalf("Unexpected error from Read: %v", err)
-	}
-	if diff := cmp.Diff(tcpip.ReadResult{
-		Count: buf.Len(),
-		Total: buf.Len(),
-	}, result, checker.IgnoreCmpPath("ControlMessages")); diff != "" {
-		t.Errorf("Read: unexpected result (-want +got):\n%s", diff)
-	}
-	if got, want := buf.Bytes(), data; bytes.Compare(got, want) != 0 {
-		t.Fatalf("Data is different: got: %v, want: %v", got, want)
-	}
-}
diff --git a/pkg/tcpip/transport/tcp/tcp_unsafe_state_autogen.go b/pkg/tcpip/transport/tcp/tcp_unsafe_state_autogen.go
new file mode 100644
index 0000000..4cb82fc
--- /dev/null
+++ b/pkg/tcpip/transport/tcp/tcp_unsafe_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package tcp
diff --git a/pkg/tcpip/transport/tcp/timer_test.go b/pkg/tcpip/transport/tcp/timer_test.go
deleted file mode 100644
index dbd6dff..0000000
--- a/pkg/tcpip/transport/tcp/timer_test.go
+++ /dev/null
@@ -1,47 +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 tcp
-
-import (
-	"testing"
-	"time"
-
-	"gvisor.dev/gvisor/pkg/sleep"
-)
-
-func TestCleanup(t *testing.T) {
-	const (
-		timerDurationSeconds     = 2
-		isAssertedTimeoutSeconds = timerDurationSeconds + 1
-	)
-
-	tmr := timer{}
-	w := sleep.Waker{}
-	tmr.init(&w)
-	tmr.enable(timerDurationSeconds * time.Second)
-	tmr.cleanup()
-
-	if want := (timer{}); tmr != want {
-		t.Errorf("got tmr = %+v, want = %+v", tmr, want)
-	}
-
-	// The waker should not be asserted.
-	for i := 0; i < isAssertedTimeoutSeconds; i++ {
-		time.Sleep(time.Second)
-		if w.IsAsserted() {
-			t.Fatalf("waker asserted unexpectedly")
-		}
-	}
-}
diff --git a/pkg/tcpip/transport/tcpconntrack/tcp_conntrack_test.go b/pkg/tcpip/transport/tcpconntrack/tcp_conntrack_test.go
deleted file mode 100644
index 6c5ddc3..0000000
--- a/pkg/tcpip/transport/tcpconntrack/tcp_conntrack_test.go
+++ /dev/null
@@ -1,511 +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 tcpconntrack_test
-
-import (
-	"testing"
-
-	"gvisor.dev/gvisor/pkg/tcpip/header"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/tcpconntrack"
-)
-
-// connected creates a connection tracker TCB and sets it to a connected state
-// by performing a 3-way handshake.
-func connected(t *testing.T, iss, irs uint32, isw, irw uint16) *tcpconntrack.TCB {
-	// Send SYN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     iss,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: irw,
-	})
-
-	tcb := tcpconntrack.TCB{}
-	tcb.Init(tcp)
-
-	// Receive SYN-ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     irs,
-		AckNum:     iss + 1,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn | header.TCPFlagAck,
-		WindowSize: isw,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Send ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     iss + 1,
-		AckNum:     irs + 1,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck,
-		WindowSize: irw,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	return &tcb
-}
-
-func TestConnectionRefused(t *testing.T) {
-	// Send SYN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1234,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 30000,
-	})
-
-	tcb := tcpconntrack.TCB{}
-	tcb.Init(tcp)
-
-	// Receive RST.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     789,
-		AckNum:     1235,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagRst | header.TCPFlagAck,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultReset {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultReset)
-	}
-}
-
-func TestConnectionRefusedInSynRcvd(t *testing.T) {
-	// Send SYN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1234,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 30000,
-	})
-
-	tcb := tcpconntrack.TCB{}
-	tcb.Init(tcp)
-
-	// Receive SYN.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     789,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Receive RST with no ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     790,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagRst,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultReset {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultReset)
-	}
-}
-
-func TestConnectionResetInSynRcvd(t *testing.T) {
-	// Send SYN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1234,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 30000,
-	})
-
-	tcb := tcpconntrack.TCB{}
-	tcb.Init(tcp)
-
-	// Receive SYN.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     789,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Send RST with no ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1235,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagRst,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultReset {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultReset)
-	}
-}
-
-func TestRetransmitOnSynSent(t *testing.T) {
-	// Send initial SYN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1234,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 30000,
-	})
-
-	tcb := tcpconntrack.TCB{}
-	tcb.Init(tcp)
-
-	// Retransmit the same SYN.
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultConnecting {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultConnecting)
-	}
-}
-
-func TestRetransmitOnSynRcvd(t *testing.T) {
-	// Send initial SYN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1234,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 30000,
-	})
-
-	tcb := tcpconntrack.TCB{}
-	tcb.Init(tcp)
-
-	// Receive SYN. This will cause the state to go to SYN-RCVD.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     789,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Retransmit the original SYN.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1234,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 30000,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Transmit a SYN-ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1234,
-		AckNum:     790,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn | header.TCPFlagAck,
-		WindowSize: 30000,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-}
-
-func TestClosedBySelf(t *testing.T) {
-	tcb := connected(t, 1234, 789, 30000, 50000)
-
-	// Send FIN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1235,
-		AckNum:     790,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck | header.TCPFlagFin,
-		WindowSize: 30000,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Receive FIN/ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     790,
-		AckNum:     1236,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck | header.TCPFlagFin,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Send ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1236,
-		AckNum:     791,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck,
-		WindowSize: 30000,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultClosedBySelf {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultClosedBySelf)
-	}
-}
-
-func TestClosedByPeer(t *testing.T) {
-	tcb := connected(t, 1234, 789, 30000, 50000)
-
-	// Receive FIN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     790,
-		AckNum:     1235,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck | header.TCPFlagFin,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Send FIN/ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1235,
-		AckNum:     791,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck | header.TCPFlagFin,
-		WindowSize: 30000,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Receive ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     791,
-		AckNum:     1236,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultClosedByPeer {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultClosedByPeer)
-	}
-}
-
-func TestSendAndReceiveDataClosedBySelf(t *testing.T) {
-	sseq := uint32(1234)
-	rseq := uint32(789)
-	tcb := connected(t, sseq, rseq, 30000, 50000)
-	sseq++
-	rseq++
-
-	// Send some data.
-	tcp := make(header.TCP, header.TCPMinimumSize+1024)
-
-	for i := uint32(0); i < 10; i++ {
-		// Send some data.
-		tcp.Encode(&header.TCPFields{
-			SeqNum:     sseq,
-			AckNum:     rseq,
-			DataOffset: header.TCPMinimumSize,
-			Flags:      header.TCPFlagAck,
-			WindowSize: 30000,
-		})
-		sseq += uint32(len(tcp)) - header.TCPMinimumSize
-
-		if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
-			t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-		}
-
-		// Receive ack for data.
-		tcp.Encode(&header.TCPFields{
-			SeqNum:     rseq,
-			AckNum:     sseq,
-			DataOffset: header.TCPMinimumSize,
-			Flags:      header.TCPFlagAck,
-			WindowSize: 50000,
-		})
-
-		if r := tcb.UpdateStateInbound(tcp[:header.TCPMinimumSize]); r != tcpconntrack.ResultAlive {
-			t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-		}
-	}
-
-	for i := uint32(0); i < 10; i++ {
-		// Receive some data.
-		tcp.Encode(&header.TCPFields{
-			SeqNum:     rseq,
-			AckNum:     sseq,
-			DataOffset: header.TCPMinimumSize,
-			Flags:      header.TCPFlagAck,
-			WindowSize: 50000,
-		})
-		rseq += uint32(len(tcp)) - header.TCPMinimumSize
-
-		if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-			t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-		}
-
-		// Send ack for data.
-		tcp.Encode(&header.TCPFields{
-			SeqNum:     sseq,
-			AckNum:     rseq,
-			DataOffset: header.TCPMinimumSize,
-			Flags:      header.TCPFlagAck,
-			WindowSize: 30000,
-		})
-
-		if r := tcb.UpdateStateOutbound(tcp[:header.TCPMinimumSize]); r != tcpconntrack.ResultAlive {
-			t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-		}
-	}
-
-	// Send FIN.
-	tcp = tcp[:header.TCPMinimumSize]
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     sseq,
-		AckNum:     rseq,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck | header.TCPFlagFin,
-		WindowSize: 30000,
-	})
-	sseq++
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Receive FIN/ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     rseq,
-		AckNum:     sseq,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck | header.TCPFlagFin,
-		WindowSize: 50000,
-	})
-	rseq++
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Send ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     sseq,
-		AckNum:     rseq,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck,
-		WindowSize: 30000,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultClosedBySelf {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultClosedBySelf)
-	}
-}
-
-func TestIgnoreBadResetOnSynSent(t *testing.T) {
-	// Send SYN.
-	tcp := make(header.TCP, header.TCPMinimumSize)
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1234,
-		AckNum:     0,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn,
-		WindowSize: 30000,
-	})
-
-	tcb := tcpconntrack.TCB{}
-	tcb.Init(tcp)
-
-	// Receive a RST with a bad ACK, it should not cause the connection to
-	// be reset.
-	acks := []uint32{1234, 1236, 1000, 5000}
-	flags := []header.TCPFlags{header.TCPFlagRst, header.TCPFlagRst | header.TCPFlagAck}
-	for _, a := range acks {
-		for _, f := range flags {
-			tcp.Encode(&header.TCPFields{
-				SeqNum:     789,
-				AckNum:     a,
-				DataOffset: header.TCPMinimumSize,
-				Flags:      f,
-				WindowSize: 50000,
-			})
-
-			if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultConnecting {
-				t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-			}
-		}
-	}
-
-	// Complete the handshake.
-	// Receive SYN-ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     789,
-		AckNum:     1235,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagSyn | header.TCPFlagAck,
-		WindowSize: 50000,
-	})
-
-	if r := tcb.UpdateStateInbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-
-	// Send ACK.
-	tcp.Encode(&header.TCPFields{
-		SeqNum:     1235,
-		AckNum:     790,
-		DataOffset: header.TCPMinimumSize,
-		Flags:      header.TCPFlagAck,
-		WindowSize: 30000,
-	})
-
-	if r := tcb.UpdateStateOutbound(tcp); r != tcpconntrack.ResultAlive {
-		t.Fatalf("Bad result: got %v, want %v", r, tcpconntrack.ResultAlive)
-	}
-}
diff --git a/pkg/tcpip/transport/tcpconntrack/tcpconntrack_state_autogen.go b/pkg/tcpip/transport/tcpconntrack/tcpconntrack_state_autogen.go
new file mode 100644
index 0000000..ff53204
--- /dev/null
+++ b/pkg/tcpip/transport/tcpconntrack/tcpconntrack_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package tcpconntrack
diff --git a/pkg/tcpip/transport/udp/udp_packet_list.go b/pkg/tcpip/transport/udp/udp_packet_list.go
new file mode 100644
index 0000000..c396f77
--- /dev/null
+++ b/pkg/tcpip/transport/udp/udp_packet_list.go
@@ -0,0 +1,221 @@
+package udp
+
+// 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 udpPacketElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (udpPacketElementMapper) linkerFor(elem *udpPacket) *udpPacket { 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 udpPacketList struct {
+	head *udpPacket
+	tail *udpPacket
+}
+
+// Reset resets list l to the empty state.
+func (l *udpPacketList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *udpPacketList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *udpPacketList) Front() *udpPacket {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *udpPacketList) Back() *udpPacket {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *udpPacketList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (udpPacketElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *udpPacketList) PushFront(e *udpPacket) {
+	linker := udpPacketElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		udpPacketElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *udpPacketList) PushBack(e *udpPacket) {
+	linker := udpPacketElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		udpPacketElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *udpPacketList) PushBackList(m *udpPacketList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		udpPacketElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		udpPacketElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *udpPacketList) InsertAfter(b, e *udpPacket) {
+	bLinker := udpPacketElementMapper{}.linkerFor(b)
+	eLinker := udpPacketElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		udpPacketElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *udpPacketList) InsertBefore(a, e *udpPacket) {
+	aLinker := udpPacketElementMapper{}.linkerFor(a)
+	eLinker := udpPacketElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		udpPacketElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *udpPacketList) Remove(e *udpPacket) {
+	linker := udpPacketElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		udpPacketElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		udpPacketElementMapper{}.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 udpPacketEntry struct {
+	next *udpPacket
+	prev *udpPacket
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *udpPacketEntry) Next() *udpPacket {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *udpPacketEntry) Prev() *udpPacket {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *udpPacketEntry) SetNext(elem *udpPacket) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *udpPacketEntry) SetPrev(elem *udpPacket) {
+	e.prev = elem
+}
diff --git a/pkg/tcpip/transport/udp/udp_state_autogen.go b/pkg/tcpip/transport/udp/udp_state_autogen.go
new file mode 100644
index 0000000..5d7859a
--- /dev/null
+++ b/pkg/tcpip/transport/udp/udp_state_autogen.go
@@ -0,0 +1,239 @@
+// automatically generated by stateify.
+
+package udp
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+	"gvisor.dev/gvisor/pkg/tcpip/buffer"
+)
+
+func (u *udpPacket) StateTypeName() string {
+	return "pkg/tcpip/transport/udp.udpPacket"
+}
+
+func (u *udpPacket) StateFields() []string {
+	return []string{
+		"udpPacketEntry",
+		"senderAddress",
+		"destinationAddress",
+		"packetInfo",
+		"data",
+		"timestamp",
+		"tos",
+	}
+}
+
+func (u *udpPacket) beforeSave() {}
+
+// +checklocksignore
+func (u *udpPacket) StateSave(stateSinkObject state.Sink) {
+	u.beforeSave()
+	var dataValue buffer.VectorisedView = u.saveData()
+	stateSinkObject.SaveValue(4, dataValue)
+	stateSinkObject.Save(0, &u.udpPacketEntry)
+	stateSinkObject.Save(1, &u.senderAddress)
+	stateSinkObject.Save(2, &u.destinationAddress)
+	stateSinkObject.Save(3, &u.packetInfo)
+	stateSinkObject.Save(5, &u.timestamp)
+	stateSinkObject.Save(6, &u.tos)
+}
+
+func (u *udpPacket) afterLoad() {}
+
+// +checklocksignore
+func (u *udpPacket) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &u.udpPacketEntry)
+	stateSourceObject.Load(1, &u.senderAddress)
+	stateSourceObject.Load(2, &u.destinationAddress)
+	stateSourceObject.Load(3, &u.packetInfo)
+	stateSourceObject.Load(5, &u.timestamp)
+	stateSourceObject.Load(6, &u.tos)
+	stateSourceObject.LoadValue(4, new(buffer.VectorisedView), func(y interface{}) { u.loadData(y.(buffer.VectorisedView)) })
+}
+
+func (e *endpoint) StateTypeName() string {
+	return "pkg/tcpip/transport/udp.endpoint"
+}
+
+func (e *endpoint) StateFields() []string {
+	return []string{
+		"TransportEndpointInfo",
+		"DefaultSocketOptionsHandler",
+		"waiterQueue",
+		"uniqueID",
+		"rcvReady",
+		"rcvList",
+		"rcvBufSizeMax",
+		"rcvBufSize",
+		"rcvClosed",
+		"state",
+		"dstPort",
+		"ttl",
+		"multicastTTL",
+		"multicastAddr",
+		"multicastNICID",
+		"portFlags",
+		"lastError",
+		"boundBindToDevice",
+		"boundPortFlags",
+		"sendTOS",
+		"shutdownFlags",
+		"multicastMemberships",
+		"effectiveNetProtos",
+		"owner",
+		"ops",
+	}
+}
+
+// +checklocksignore
+func (e *endpoint) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	var rcvBufSizeMaxValue int = e.saveRcvBufSizeMax()
+	stateSinkObject.SaveValue(6, rcvBufSizeMaxValue)
+	stateSinkObject.Save(0, &e.TransportEndpointInfo)
+	stateSinkObject.Save(1, &e.DefaultSocketOptionsHandler)
+	stateSinkObject.Save(2, &e.waiterQueue)
+	stateSinkObject.Save(3, &e.uniqueID)
+	stateSinkObject.Save(4, &e.rcvReady)
+	stateSinkObject.Save(5, &e.rcvList)
+	stateSinkObject.Save(7, &e.rcvBufSize)
+	stateSinkObject.Save(8, &e.rcvClosed)
+	stateSinkObject.Save(9, &e.state)
+	stateSinkObject.Save(10, &e.dstPort)
+	stateSinkObject.Save(11, &e.ttl)
+	stateSinkObject.Save(12, &e.multicastTTL)
+	stateSinkObject.Save(13, &e.multicastAddr)
+	stateSinkObject.Save(14, &e.multicastNICID)
+	stateSinkObject.Save(15, &e.portFlags)
+	stateSinkObject.Save(16, &e.lastError)
+	stateSinkObject.Save(17, &e.boundBindToDevice)
+	stateSinkObject.Save(18, &e.boundPortFlags)
+	stateSinkObject.Save(19, &e.sendTOS)
+	stateSinkObject.Save(20, &e.shutdownFlags)
+	stateSinkObject.Save(21, &e.multicastMemberships)
+	stateSinkObject.Save(22, &e.effectiveNetProtos)
+	stateSinkObject.Save(23, &e.owner)
+	stateSinkObject.Save(24, &e.ops)
+}
+
+// +checklocksignore
+func (e *endpoint) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.TransportEndpointInfo)
+	stateSourceObject.Load(1, &e.DefaultSocketOptionsHandler)
+	stateSourceObject.Load(2, &e.waiterQueue)
+	stateSourceObject.Load(3, &e.uniqueID)
+	stateSourceObject.Load(4, &e.rcvReady)
+	stateSourceObject.Load(5, &e.rcvList)
+	stateSourceObject.Load(7, &e.rcvBufSize)
+	stateSourceObject.Load(8, &e.rcvClosed)
+	stateSourceObject.Load(9, &e.state)
+	stateSourceObject.Load(10, &e.dstPort)
+	stateSourceObject.Load(11, &e.ttl)
+	stateSourceObject.Load(12, &e.multicastTTL)
+	stateSourceObject.Load(13, &e.multicastAddr)
+	stateSourceObject.Load(14, &e.multicastNICID)
+	stateSourceObject.Load(15, &e.portFlags)
+	stateSourceObject.Load(16, &e.lastError)
+	stateSourceObject.Load(17, &e.boundBindToDevice)
+	stateSourceObject.Load(18, &e.boundPortFlags)
+	stateSourceObject.Load(19, &e.sendTOS)
+	stateSourceObject.Load(20, &e.shutdownFlags)
+	stateSourceObject.Load(21, &e.multicastMemberships)
+	stateSourceObject.Load(22, &e.effectiveNetProtos)
+	stateSourceObject.Load(23, &e.owner)
+	stateSourceObject.Load(24, &e.ops)
+	stateSourceObject.LoadValue(6, new(int), func(y interface{}) { e.loadRcvBufSizeMax(y.(int)) })
+	stateSourceObject.AfterLoad(e.afterLoad)
+}
+
+func (m *multicastMembership) StateTypeName() string {
+	return "pkg/tcpip/transport/udp.multicastMembership"
+}
+
+func (m *multicastMembership) StateFields() []string {
+	return []string{
+		"nicID",
+		"multicastAddr",
+	}
+}
+
+func (m *multicastMembership) beforeSave() {}
+
+// +checklocksignore
+func (m *multicastMembership) StateSave(stateSinkObject state.Sink) {
+	m.beforeSave()
+	stateSinkObject.Save(0, &m.nicID)
+	stateSinkObject.Save(1, &m.multicastAddr)
+}
+
+func (m *multicastMembership) afterLoad() {}
+
+// +checklocksignore
+func (m *multicastMembership) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &m.nicID)
+	stateSourceObject.Load(1, &m.multicastAddr)
+}
+
+func (l *udpPacketList) StateTypeName() string {
+	return "pkg/tcpip/transport/udp.udpPacketList"
+}
+
+func (l *udpPacketList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *udpPacketList) beforeSave() {}
+
+// +checklocksignore
+func (l *udpPacketList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *udpPacketList) afterLoad() {}
+
+// +checklocksignore
+func (l *udpPacketList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *udpPacketEntry) StateTypeName() string {
+	return "pkg/tcpip/transport/udp.udpPacketEntry"
+}
+
+func (e *udpPacketEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *udpPacketEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *udpPacketEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *udpPacketEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *udpPacketEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+	state.Register((*udpPacket)(nil))
+	state.Register((*endpoint)(nil))
+	state.Register((*multicastMembership)(nil))
+	state.Register((*udpPacketList)(nil))
+	state.Register((*udpPacketEntry)(nil))
+}
diff --git a/pkg/tcpip/transport/udp/udp_test.go b/pkg/tcpip/transport/udp/udp_test.go
deleted file mode 100644
index c8126b5..0000000
--- a/pkg/tcpip/transport/udp/udp_test.go
+++ /dev/null
@@ -1,2565 +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 udp_test
-
-import (
-	"bytes"
-	"context"
-	"fmt"
-	"io/ioutil"
-	"math/rand"
-	"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/link/loopback"
-	"gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
-	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
-	"gvisor.dev/gvisor/pkg/tcpip/stack"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
-	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
-	"gvisor.dev/gvisor/pkg/waiter"
-)
-
-// Addresses and ports used for testing. It is recommended that tests stick to
-// using these addresses as it allows using the testFlow helper.
-// Naming rules: 'stack*'' denotes local addresses and ports, while 'test*'
-// represents the remote endpoint.
-const (
-	v4MappedAddrPrefix    = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff"
-	stackV6Addr           = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
-	testV6Addr            = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
-	stackV4MappedAddr     = v4MappedAddrPrefix + stackAddr
-	testV4MappedAddr      = v4MappedAddrPrefix + testAddr
-	multicastV4MappedAddr = v4MappedAddrPrefix + multicastAddr
-	broadcastV4MappedAddr = v4MappedAddrPrefix + broadcastAddr
-	v4MappedWildcardAddr  = v4MappedAddrPrefix + "\x00\x00\x00\x00"
-
-	stackAddr       = "\x0a\x00\x00\x01"
-	stackPort       = 1234
-	testAddr        = "\x0a\x00\x00\x02"
-	testPort        = 4096
-	invalidPort     = 8192
-	multicastAddr   = "\xe8\x2b\xd3\xea"
-	multicastV6Addr = "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
-	broadcastAddr   = header.IPv4Broadcast
-	testTOS         = 0x80
-
-	// defaultMTU is the MTU, in bytes, used throughout the tests, except
-	// where another value is explicitly used. It is chosen to match the MTU
-	// of loopback interfaces on linux systems.
-	defaultMTU = 65536
-)
-
-// header4Tuple stores the 4-tuple {src-IP, src-port, dst-IP, dst-port} used in
-// a packet header. These values are used to populate a header or verify one.
-// Note that because they are used in packet headers, the addresses are never in
-// a V4-mapped format.
-type header4Tuple struct {
-	srcAddr tcpip.FullAddress
-	dstAddr tcpip.FullAddress
-}
-
-// testFlow implements a helper type used for sending and receiving test
-// packets. A given test flow value defines 1) the socket endpoint used for the
-// test and 2) the type of packet send or received on the endpoint. E.g., a
-// multicastV6Only flow is a V6 multicast packet passing through a V6-only
-// endpoint. The type provides helper methods to characterize the flow (e.g.,
-// isV4) as well as return a proper header4Tuple for it.
-type testFlow int
-
-const (
-	unicastV4         testFlow = iota // V4 unicast on a V4 socket
-	unicastV4in6                      // V4-mapped unicast on a V6-dual socket
-	unicastV6                         // V6 unicast on a V6 socket
-	unicastV6Only                     // V6 unicast on a V6-only socket
-	multicastV4                       // V4 multicast on a V4 socket
-	multicastV4in6                    // V4-mapped multicast on a V6-dual socket
-	multicastV6                       // V6 multicast on a V6 socket
-	multicastV6Only                   // V6 multicast on a V6-only socket
-	broadcast                         // V4 broadcast on a V4 socket
-	broadcastIn6                      // V4-mapped broadcast on a V6-dual socket
-	reverseMulticast4                 // V4 multicast src. Must fail.
-	reverseMulticast6                 // V6 multicast src. Must fail.
-)
-
-func (flow testFlow) String() string {
-	switch flow {
-	case unicastV4:
-		return "unicastV4"
-	case unicastV6:
-		return "unicastV6"
-	case unicastV6Only:
-		return "unicastV6Only"
-	case unicastV4in6:
-		return "unicastV4in6"
-	case multicastV4:
-		return "multicastV4"
-	case multicastV6:
-		return "multicastV6"
-	case multicastV6Only:
-		return "multicastV6Only"
-	case multicastV4in6:
-		return "multicastV4in6"
-	case broadcast:
-		return "broadcast"
-	case broadcastIn6:
-		return "broadcastIn6"
-	case reverseMulticast4:
-		return "reverseMulticast4"
-	case reverseMulticast6:
-		return "reverseMulticast6"
-	default:
-		return "unknown"
-	}
-}
-
-// packetDirection explains if a flow is incoming (read) or outgoing (write).
-type packetDirection int
-
-const (
-	incoming packetDirection = iota
-	outgoing
-)
-
-// header4Tuple returns the header4Tuple for the given flow and direction. Note
-// that the tuple contains no mapped addresses as those only exist at the socket
-// level but not at the packet header level.
-func (flow testFlow) header4Tuple(d packetDirection) header4Tuple {
-	var h header4Tuple
-	if flow.isV4() {
-		if d == outgoing {
-			h = header4Tuple{
-				srcAddr: tcpip.FullAddress{Addr: stackAddr, Port: stackPort},
-				dstAddr: tcpip.FullAddress{Addr: testAddr, Port: testPort},
-			}
-		} else {
-			h = header4Tuple{
-				srcAddr: tcpip.FullAddress{Addr: testAddr, Port: testPort},
-				dstAddr: tcpip.FullAddress{Addr: stackAddr, Port: stackPort},
-			}
-		}
-		if flow.isMulticast() {
-			h.dstAddr.Addr = multicastAddr
-		} else if flow.isBroadcast() {
-			h.dstAddr.Addr = broadcastAddr
-		}
-	} else { // IPv6
-		if d == outgoing {
-			h = header4Tuple{
-				srcAddr: tcpip.FullAddress{Addr: stackV6Addr, Port: stackPort},
-				dstAddr: tcpip.FullAddress{Addr: testV6Addr, Port: testPort},
-			}
-		} else {
-			h = header4Tuple{
-				srcAddr: tcpip.FullAddress{Addr: testV6Addr, Port: testPort},
-				dstAddr: tcpip.FullAddress{Addr: stackV6Addr, Port: stackPort},
-			}
-		}
-		if flow.isMulticast() {
-			h.dstAddr.Addr = multicastV6Addr
-		}
-	}
-	if flow.isReverseMulticast() {
-		h.srcAddr.Addr = flow.getMcastAddr()
-	}
-	return h
-}
-
-func (flow testFlow) getMcastAddr() tcpip.Address {
-	if flow.isV4() {
-		return multicastAddr
-	}
-	return multicastV6Addr
-}
-
-// mapAddrIfApplicable converts the given V4 address into its V4-mapped version
-// if it is applicable to the flow.
-func (flow testFlow) mapAddrIfApplicable(v4Addr tcpip.Address) tcpip.Address {
-	if flow.isMapped() {
-		return v4MappedAddrPrefix + v4Addr
-	}
-	return v4Addr
-}
-
-// netProto returns the protocol number used for the network packet.
-func (flow testFlow) netProto() tcpip.NetworkProtocolNumber {
-	if flow.isV4() {
-		return ipv4.ProtocolNumber
-	}
-	return ipv6.ProtocolNumber
-}
-
-// sockProto returns the protocol number used when creating the socket
-// endpoint for this flow.
-func (flow testFlow) sockProto() tcpip.NetworkProtocolNumber {
-	switch flow {
-	case unicastV4in6, unicastV6, unicastV6Only, multicastV4in6, multicastV6, multicastV6Only, broadcastIn6, reverseMulticast6:
-		return ipv6.ProtocolNumber
-	case unicastV4, multicastV4, broadcast, reverseMulticast4:
-		return ipv4.ProtocolNumber
-	default:
-		panic(fmt.Sprintf("invalid testFlow given: %d", flow))
-	}
-}
-
-func (flow testFlow) checkerFn() func(*testing.T, []byte, ...checker.NetworkChecker) {
-	if flow.isV4() {
-		return checker.IPv4
-	}
-	return checker.IPv6
-}
-
-func (flow testFlow) isV6() bool { return !flow.isV4() }
-func (flow testFlow) isV4() bool {
-	return flow.sockProto() == ipv4.ProtocolNumber || flow.isMapped()
-}
-
-func (flow testFlow) isV6Only() bool {
-	switch flow {
-	case unicastV6Only, multicastV6Only:
-		return true
-	case unicastV4, unicastV4in6, unicastV6, multicastV4, multicastV4in6, multicastV6, broadcast, broadcastIn6, reverseMulticast4, reverseMulticast6:
-		return false
-	default:
-		panic(fmt.Sprintf("invalid testFlow given: %d", flow))
-	}
-}
-
-func (flow testFlow) isMulticast() bool {
-	switch flow {
-	case multicastV4, multicastV4in6, multicastV6, multicastV6Only:
-		return true
-	case unicastV4, unicastV4in6, unicastV6, unicastV6Only, broadcast, broadcastIn6, reverseMulticast4, reverseMulticast6:
-		return false
-	default:
-		panic(fmt.Sprintf("invalid testFlow given: %d", flow))
-	}
-}
-
-func (flow testFlow) isBroadcast() bool {
-	switch flow {
-	case broadcast, broadcastIn6:
-		return true
-	case unicastV4, unicastV4in6, unicastV6, unicastV6Only, multicastV4, multicastV4in6, multicastV6, multicastV6Only, reverseMulticast4, reverseMulticast6:
-		return false
-	default:
-		panic(fmt.Sprintf("invalid testFlow given: %d", flow))
-	}
-}
-
-func (flow testFlow) isMapped() bool {
-	switch flow {
-	case unicastV4in6, multicastV4in6, broadcastIn6:
-		return true
-	case unicastV4, unicastV6, unicastV6Only, multicastV4, multicastV6, multicastV6Only, broadcast, reverseMulticast4, reverseMulticast6:
-		return false
-	default:
-		panic(fmt.Sprintf("invalid testFlow given: %d", flow))
-	}
-}
-
-func (flow testFlow) isReverseMulticast() bool {
-	switch flow {
-	case reverseMulticast4, reverseMulticast6:
-		return true
-	default:
-		return false
-	}
-}
-
-type testContext struct {
-	t      *testing.T
-	linkEP *channel.Endpoint
-	s      *stack.Stack
-
-	ep tcpip.Endpoint
-	wq waiter.Queue
-}
-
-func newDualTestContext(t *testing.T, mtu uint32) *testContext {
-	t.Helper()
-	return newDualTestContextWithOptions(t, mtu, stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol6, icmp.NewProtocol4},
-		HandleLocal:        true,
-	})
-}
-
-func newDualTestContextWithOptions(t *testing.T, mtu uint32, options stack.Options) *testContext {
-	t.Helper()
-
-	s := stack.New(options)
-	ep := channel.New(256, mtu, "")
-	wep := stack.LinkEndpoint(ep)
-
-	if testing.Verbose() {
-		wep = sniffer.New(ep)
-	}
-	if err := s.CreateNIC(1, wep); err != nil {
-		t.Fatalf("CreateNIC failed: %s", err)
-	}
-
-	if err := s.AddAddress(1, ipv4.ProtocolNumber, stackAddr); err != nil {
-		t.Fatalf("AddAddress failed: %s", err)
-	}
-
-	if err := s.AddAddress(1, ipv6.ProtocolNumber, stackV6Addr); err != nil {
-		t.Fatalf("AddAddress failed: %s", err)
-	}
-
-	s.SetRouteTable([]tcpip.Route{
-		{
-			Destination: header.IPv4EmptySubnet,
-			NIC:         1,
-		},
-		{
-			Destination: header.IPv6EmptySubnet,
-			NIC:         1,
-		},
-	})
-
-	return &testContext{
-		t:      t,
-		s:      s,
-		linkEP: ep,
-	}
-}
-
-func (c *testContext) cleanup() {
-	if c.ep != nil {
-		c.ep.Close()
-	}
-}
-
-func (c *testContext) createEndpoint(proto tcpip.NetworkProtocolNumber) {
-	c.t.Helper()
-
-	var err tcpip.Error
-	c.ep, err = c.s.NewEndpoint(udp.ProtocolNumber, proto, &c.wq)
-	if err != nil {
-		c.t.Fatal("NewEndpoint failed: ", err)
-	}
-}
-
-func (c *testContext) createEndpointForFlow(flow testFlow) {
-	c.t.Helper()
-
-	c.createEndpoint(flow.sockProto())
-	if flow.isV6Only() {
-		c.ep.SocketOptions().SetV6Only(true)
-	} else if flow.isBroadcast() {
-		c.ep.SocketOptions().SetBroadcast(true)
-	}
-}
-
-// getPacketAndVerify reads a packet from the link endpoint and verifies the
-// header against expected values from the given test flow. In addition, it
-// calls any extra checker functions provided.
-func (c *testContext) getPacketAndVerify(flow testFlow, checkers ...checker.NetworkChecker) []byte {
-	c.t.Helper()
-
-	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
-	defer cancel()
-	p, ok := c.linkEP.ReadContext(ctx)
-	if !ok {
-		c.t.Fatalf("Packet wasn't written out")
-		return nil
-	}
-
-	if p.Proto != flow.netProto() {
-		c.t.Fatalf("Bad network protocol: got %v, wanted %v", p.Proto, flow.netProto())
-	}
-
-	if got, want := p.Pkt.TransportProtocolNumber, header.UDPProtocolNumber; got != want {
-		c.t.Errorf("got p.Pkt.TransportProtocolNumber = %d, want = %d", got, want)
-	}
-
-	vv := buffer.NewVectorisedView(p.Pkt.Size(), p.Pkt.Views())
-	b := vv.ToView()
-
-	h := flow.header4Tuple(outgoing)
-	checkers = append(
-		checkers,
-		checker.SrcAddr(h.srcAddr.Addr),
-		checker.DstAddr(h.dstAddr.Addr),
-		checker.UDP(checker.DstPort(h.dstAddr.Port)),
-	)
-	flow.checkerFn()(c.t, b, checkers...)
-	return b
-}
-
-// injectPacket creates a packet of the given flow and with the given payload,
-// and injects it into the link endpoint. If badChecksum is true, the packet has
-// a bad checksum in the UDP header.
-func (c *testContext) injectPacket(flow testFlow, payload []byte, badChecksum bool) {
-	c.t.Helper()
-
-	h := flow.header4Tuple(incoming)
-	if flow.isV4() {
-		buf := c.buildV4Packet(payload, &h)
-		if badChecksum {
-			// Invalidate the UDP header checksum field, taking care to avoid
-			// overflow to zero, which would disable checksum validation.
-			for u := header.UDP(buf[header.IPv4MinimumSize:]); ; {
-				u.SetChecksum(u.Checksum() + 1)
-				if u.Checksum() != 0 {
-					break
-				}
-			}
-		}
-		c.linkEP.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-			Data: buf.ToVectorisedView(),
-		}))
-	} else {
-		buf := c.buildV6Packet(payload, &h)
-		if badChecksum {
-			// Invalidate the UDP header checksum field (Unlike IPv4, zero is
-			// a valid checksum value for IPv6 so no need to avoid it).
-			u := header.UDP(buf[header.IPv6MinimumSize:])
-			u.SetChecksum(u.Checksum() + 1)
-		}
-		c.linkEP.InjectInbound(ipv6.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-			Data: buf.ToVectorisedView(),
-		}))
-	}
-}
-
-// buildV6Packet creates a V6 test packet with the given payload and header
-// values in a buffer.
-func (c *testContext) buildV6Packet(payload []byte, h *header4Tuple) buffer.View {
-	// Allocate a buffer for data and headers.
-	buf := buffer.NewView(header.UDPMinimumSize + header.IPv6MinimumSize + len(payload))
-	payloadStart := len(buf) - len(payload)
-	copy(buf[payloadStart:], payload)
-
-	// Initialize the IP header.
-	ip := header.IPv6(buf)
-	ip.Encode(&header.IPv6Fields{
-		TrafficClass:      testTOS,
-		PayloadLength:     uint16(header.UDPMinimumSize + len(payload)),
-		TransportProtocol: udp.ProtocolNumber,
-		HopLimit:          65,
-		SrcAddr:           h.srcAddr.Addr,
-		DstAddr:           h.dstAddr.Addr,
-	})
-
-	// Initialize the UDP header.
-	u := header.UDP(buf[header.IPv6MinimumSize:])
-	u.Encode(&header.UDPFields{
-		SrcPort: h.srcAddr.Port,
-		DstPort: h.dstAddr.Port,
-		Length:  uint16(header.UDPMinimumSize + len(payload)),
-	})
-
-	// Calculate the UDP pseudo-header checksum.
-	xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.srcAddr.Addr, h.dstAddr.Addr, uint16(len(u)))
-
-	// Calculate the UDP checksum and set it.
-	xsum = header.Checksum(payload, xsum)
-	u.SetChecksum(^u.CalculateChecksum(xsum))
-
-	return buf
-}
-
-// buildV4Packet creates a V4 test packet with the given payload and header
-// values in a buffer.
-func (c *testContext) buildV4Packet(payload []byte, h *header4Tuple) buffer.View {
-	// Allocate a buffer for data and headers.
-	buf := buffer.NewView(header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
-	payloadStart := len(buf) - len(payload)
-	copy(buf[payloadStart:], payload)
-
-	// Initialize the IP header.
-	ip := header.IPv4(buf)
-	ip.Encode(&header.IPv4Fields{
-		TOS:         testTOS,
-		TotalLength: uint16(len(buf)),
-		TTL:         65,
-		Protocol:    uint8(udp.ProtocolNumber),
-		SrcAddr:     h.srcAddr.Addr,
-		DstAddr:     h.dstAddr.Addr,
-	})
-	ip.SetChecksum(^ip.CalculateChecksum())
-
-	// Initialize the UDP header.
-	u := header.UDP(buf[header.IPv4MinimumSize:])
-	u.Encode(&header.UDPFields{
-		SrcPort: h.srcAddr.Port,
-		DstPort: h.dstAddr.Port,
-		Length:  uint16(header.UDPMinimumSize + len(payload)),
-	})
-
-	// Calculate the UDP pseudo-header checksum.
-	xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.srcAddr.Addr, h.dstAddr.Addr, uint16(len(u)))
-
-	// Calculate the UDP checksum and set it.
-	xsum = header.Checksum(payload, xsum)
-	u.SetChecksum(^u.CalculateChecksum(xsum))
-
-	return buf
-}
-
-func newPayload() []byte {
-	return newMinPayload(30)
-}
-
-func newMinPayload(minSize int) []byte {
-	b := make([]byte, minSize+rand.Intn(100))
-	for i := range b {
-		b[i] = byte(rand.Intn(256))
-	}
-	return b
-}
-
-func TestBindToDeviceOption(t *testing.T) {
-	s := stack.New(stack.Options{
-		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol},
-		TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol}})
-
-	ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &waiter.Queue{})
-	if err != nil {
-		t.Fatalf("NewEndpoint failed; %s", err)
-	}
-	defer ep.Close()
-
-	opts := stack.NICOptions{Name: "my_device"}
-	if err := s.CreateNICWithOptions(321, loopback.New(), opts); err != nil {
-		t.Errorf("CreateNICWithOptions(_, _, %+v) failed: %s", opts, err)
-	}
-
-	// nicIDPtr is used instead of taking the address of NICID literals, which is
-	// a compiler error.
-	nicIDPtr := func(s tcpip.NICID) *tcpip.NICID {
-		return &s
-	}
-
-	testActions := []struct {
-		name                 string
-		setBindToDevice      *tcpip.NICID
-		setBindToDeviceError tcpip.Error
-		getBindToDevice      int32
-	}{
-		{"GetDefaultValue", nil, nil, 0},
-		{"BindToNonExistent", nicIDPtr(999), &tcpip.ErrUnknownDevice{}, 0},
-		{"BindToExistent", nicIDPtr(321), nil, 321},
-		{"UnbindToDevice", nicIDPtr(0), nil, 0},
-	}
-	for _, testAction := range testActions {
-		t.Run(testAction.name, func(t *testing.T) {
-			if testAction.setBindToDevice != nil {
-				bindToDevice := int32(*testAction.setBindToDevice)
-				if gotErr, wantErr := ep.SocketOptions().SetBindToDevice(bindToDevice), testAction.setBindToDeviceError; gotErr != wantErr {
-					t.Errorf("got SetSockOpt(&%T(%d)) = %s, want = %s", bindToDevice, bindToDevice, gotErr, wantErr)
-				}
-			}
-			bindToDevice := ep.SocketOptions().GetBindToDevice()
-			if bindToDevice != testAction.getBindToDevice {
-				t.Errorf("got bindToDevice = %d, want = %d", bindToDevice, testAction.getBindToDevice)
-			}
-		})
-	}
-}
-
-// testReadInternal sends a packet of the given test flow into the stack by
-// injecting it into the link endpoint. It then attempts to read it from the
-// UDP endpoint and depending on if this was expected to succeed verifies its
-// correctness including any additional checker functions provided.
-func testReadInternal(c *testContext, flow testFlow, packetShouldBeDropped, expectReadError bool, checkers ...checker.ControlMessagesChecker) {
-	c.t.Helper()
-
-	payload := newPayload()
-	c.injectPacket(flow, payload, false)
-
-	// Try to receive the data.
-	we, ch := waiter.NewChannelEntry(nil)
-	c.wq.EventRegister(&we, waiter.EventIn)
-	defer c.wq.EventUnregister(&we)
-
-	// Take a snapshot of the stats to validate them at the end of the test.
-	epstats := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone()
-
-	var buf bytes.Buffer
-	res, err := c.ep.Read(&buf, tcpip.ReadOptions{NeedRemoteAddr: true})
-	if _, ok := err.(*tcpip.ErrWouldBlock); ok {
-		// Wait for data to become available.
-		select {
-		case <-ch:
-			res, err = c.ep.Read(&buf, tcpip.ReadOptions{NeedRemoteAddr: true})
-
-		case <-time.After(300 * time.Millisecond):
-			if packetShouldBeDropped {
-				return // expected to time out
-			}
-			c.t.Fatal("timed out waiting for data")
-		}
-	}
-
-	if expectReadError && err != nil {
-		c.checkEndpointReadStats(1, epstats, err)
-		return
-	}
-
-	if err != nil {
-		c.t.Fatal("Read failed:", err)
-	}
-
-	if packetShouldBeDropped {
-		c.t.Fatalf("Read unexpectedly received data from %s", res.RemoteAddr.Addr)
-	}
-
-	// Check the read result.
-	h := flow.header4Tuple(incoming)
-	if diff := cmp.Diff(tcpip.ReadResult{
-		Count:      buf.Len(),
-		Total:      buf.Len(),
-		RemoteAddr: tcpip.FullAddress{Addr: h.srcAddr.Addr},
-	}, res, checker.IgnoreCmpPath(
-		"ControlMessages", // ControlMessages will be checked later.
-		"RemoteAddr.NIC",
-		"RemoteAddr.Port",
-	)); diff != "" {
-		c.t.Fatalf("Read: unexpected result (-want +got):\n%s", diff)
-	}
-
-	// Check the payload.
-	v := buf.Bytes()
-	if !bytes.Equal(payload, v) {
-		c.t.Fatalf("got payload = %x, want = %x", v, payload)
-	}
-
-	// Run any checkers against the ControlMessages.
-	for _, f := range checkers {
-		f(c.t, res.ControlMessages)
-	}
-
-	c.checkEndpointReadStats(1, epstats, err)
-}
-
-// testRead sends a packet of the given test flow into the stack by injecting it
-// into the link endpoint. It then reads it from the UDP endpoint and verifies
-// its correctness including any additional checker functions provided.
-func testRead(c *testContext, flow testFlow, checkers ...checker.ControlMessagesChecker) {
-	c.t.Helper()
-	testReadInternal(c, flow, false /* packetShouldBeDropped */, false /* expectReadError */, checkers...)
-}
-
-// testFailingRead sends a packet of the given test flow into the stack by
-// injecting it into the link endpoint. It then tries to read it from the UDP
-// endpoint and expects this to fail.
-func testFailingRead(c *testContext, flow testFlow, expectReadError bool) {
-	c.t.Helper()
-	testReadInternal(c, flow, true /* packetShouldBeDropped */, expectReadError)
-}
-
-func TestBindEphemeralPort(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	if err := c.ep.Bind(tcpip.FullAddress{}); err != nil {
-		t.Fatalf("ep.Bind(...) failed: %s", err)
-	}
-}
-
-func TestBindReservedPort(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil {
-		c.t.Fatalf("Connect failed: %s", err)
-	}
-
-	addr, err := c.ep.GetLocalAddress()
-	if err != nil {
-		t.Fatalf("GetLocalAddress failed: %s", err)
-	}
-
-	// We can't bind the address reserved by the connected endpoint above.
-	{
-		ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv6.ProtocolNumber, &c.wq)
-		if err != nil {
-			t.Fatalf("NewEndpoint failed: %s", err)
-		}
-		defer ep.Close()
-		{
-			err := ep.Bind(addr)
-			if _, ok := err.(*tcpip.ErrPortInUse); !ok {
-				t.Fatalf("got ep.Bind(...) = %s, want = %s", err, &tcpip.ErrPortInUse{})
-			}
-		}
-	}
-
-	func() {
-		ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &c.wq)
-		if err != nil {
-			t.Fatalf("NewEndpoint failed: %s", err)
-		}
-		defer ep.Close()
-		// We can't bind ipv4-any on the port reserved by the connected endpoint
-		// above, since the endpoint is dual-stack.
-		{
-			err := ep.Bind(tcpip.FullAddress{Port: addr.Port})
-			if _, ok := err.(*tcpip.ErrPortInUse); !ok {
-				t.Fatalf("got ep.Bind(...) = %s, want = %s", err, &tcpip.ErrPortInUse{})
-			}
-		}
-		// We can bind an ipv4 address on this port, though.
-		if err := ep.Bind(tcpip.FullAddress{Addr: stackAddr, Port: addr.Port}); err != nil {
-			t.Fatalf("ep.Bind(...) failed: %s", err)
-		}
-	}()
-
-	// Once the connected endpoint releases its port reservation, we are able to
-	// bind ipv4-any once again.
-	c.ep.Close()
-	func() {
-		ep, err := c.s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &c.wq)
-		if err != nil {
-			t.Fatalf("NewEndpoint failed: %s", err)
-		}
-		defer ep.Close()
-		if err := ep.Bind(tcpip.FullAddress{Port: addr.Port}); err != nil {
-			t.Fatalf("ep.Bind(...) failed: %s", err)
-		}
-	}()
-}
-
-func TestV4ReadOnV6(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpointForFlow(unicastV4in6)
-
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Test acceptance.
-	testRead(c, unicastV4in6)
-}
-
-func TestV4ReadOnBoundToV4MappedWildcard(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpointForFlow(unicastV4in6)
-
-	// Bind to v4 mapped wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Addr: v4MappedWildcardAddr, Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Test acceptance.
-	testRead(c, unicastV4in6)
-}
-
-func TestV4ReadOnBoundToV4Mapped(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpointForFlow(unicastV4in6)
-
-	// Bind to local address.
-	if err := c.ep.Bind(tcpip.FullAddress{Addr: stackV4MappedAddr, Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Test acceptance.
-	testRead(c, unicastV4in6)
-}
-
-func TestV6ReadOnV6(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpointForFlow(unicastV6)
-
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Test acceptance.
-	testRead(c, unicastV6)
-}
-
-// TestV4ReadSelfSource checks that packets coming from a local IP address are
-// correctly dropped when handleLocal is true and not otherwise.
-func TestV4ReadSelfSource(t *testing.T) {
-	for _, tt := range []struct {
-		name              string
-		handleLocal       bool
-		wantErr           tcpip.Error
-		wantInvalidSource uint64
-	}{
-		{"HandleLocal", false, nil, 0},
-		{"NoHandleLocal", true, &tcpip.ErrWouldBlock{}, 1},
-	} {
-		t.Run(tt.name, func(t *testing.T) {
-			c := newDualTestContextWithOptions(t, defaultMTU, stack.Options{
-				NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
-				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
-				HandleLocal:        tt.handleLocal,
-			})
-			defer c.cleanup()
-
-			c.createEndpointForFlow(unicastV4)
-
-			if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-				t.Fatalf("Bind failed: %s", err)
-			}
-
-			payload := newPayload()
-			h := unicastV4.header4Tuple(incoming)
-			h.srcAddr = h.dstAddr
-
-			buf := c.buildV4Packet(payload, &h)
-			c.linkEP.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-				Data: buf.ToVectorisedView(),
-			}))
-
-			if got := c.s.Stats().IP.InvalidSourceAddressesReceived.Value(); got != tt.wantInvalidSource {
-				t.Errorf("c.s.Stats().IP.InvalidSourceAddressesReceived got %d, want %d", got, tt.wantInvalidSource)
-			}
-
-			if _, err := c.ep.Read(ioutil.Discard, tcpip.ReadOptions{}); err != tt.wantErr {
-				t.Errorf("got c.ep.Read = %s, want = %s", err, tt.wantErr)
-			}
-		})
-	}
-}
-
-func TestV4ReadOnV4(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpointForFlow(unicastV4)
-
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Test acceptance.
-	testRead(c, unicastV4)
-}
-
-// TestReadOnBoundToMulticast checks that an endpoint can bind to a multicast
-// address and receive data sent to that address.
-func TestReadOnBoundToMulticast(t *testing.T) {
-	// FIXME(b/128189410): multicastV4in6 currently doesn't work as
-	// AddMembershipOption doesn't handle V4in6 addresses.
-	for _, flow := range []testFlow{multicastV4, multicastV6, multicastV6Only} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to multicast address.
-			mcastAddr := flow.mapAddrIfApplicable(flow.getMcastAddr())
-			if err := c.ep.Bind(tcpip.FullAddress{Addr: mcastAddr, Port: stackPort}); err != nil {
-				c.t.Fatal("Bind failed:", err)
-			}
-
-			// Join multicast group.
-			ifoptSet := tcpip.AddMembershipOption{NIC: 1, MulticastAddr: mcastAddr}
-			if err := c.ep.SetSockOpt(&ifoptSet); err != nil {
-				c.t.Fatalf("SetSockOpt(&%#v): %s", ifoptSet, err)
-			}
-
-			// Check that we receive multicast packets but not unicast or broadcast
-			// ones.
-			testRead(c, flow)
-			testFailingRead(c, broadcast, false /* expectReadError */)
-			testFailingRead(c, unicastV4, false /* expectReadError */)
-		})
-	}
-}
-
-// TestV4ReadOnBoundToBroadcast checks that an endpoint can bind to a broadcast
-// address and can receive only broadcast data.
-func TestV4ReadOnBoundToBroadcast(t *testing.T) {
-	for _, flow := range []testFlow{broadcast, broadcastIn6} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to broadcast address.
-			bcastAddr := flow.mapAddrIfApplicable(broadcastAddr)
-			if err := c.ep.Bind(tcpip.FullAddress{Addr: bcastAddr, Port: stackPort}); err != nil {
-				c.t.Fatalf("Bind failed: %s", err)
-			}
-
-			// Check that we receive broadcast packets but not unicast ones.
-			testRead(c, flow)
-			testFailingRead(c, unicastV4, false /* expectReadError */)
-		})
-	}
-}
-
-// TestReadFromMulticast checks that an endpoint will NOT receive a packet
-// that was sent with multicast SOURCE address.
-func TestReadFromMulticast(t *testing.T) {
-	for _, flow := range []testFlow{reverseMulticast4, reverseMulticast6} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-				t.Fatalf("Bind failed: %s", err)
-			}
-			testFailingRead(c, flow, false /* expectReadError */)
-		})
-	}
-}
-
-// TestV4ReadBroadcastOnBoundToWildcard checks that an endpoint can bind to ANY
-// and receive broadcast and unicast data.
-func TestV4ReadBroadcastOnBoundToWildcard(t *testing.T) {
-	for _, flow := range []testFlow{broadcast, broadcastIn6} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to wildcard.
-			if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-				c.t.Fatalf("Bind failed: %s (", err)
-			}
-
-			// Check that we receive both broadcast and unicast packets.
-			testRead(c, flow)
-			testRead(c, unicastV4)
-		})
-	}
-}
-
-// testFailingWrite sends a packet of the given test flow into the UDP endpoint
-// and verifies it fails with the provided error code.
-func testFailingWrite(c *testContext, flow testFlow, wantErr tcpip.Error) {
-	c.t.Helper()
-	// Take a snapshot of the stats to validate them at the end of the test.
-	epstats := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone()
-	h := flow.header4Tuple(outgoing)
-	writeDstAddr := flow.mapAddrIfApplicable(h.dstAddr.Addr)
-
-	var r bytes.Reader
-	r.Reset(newPayload())
-	_, gotErr := c.ep.Write(&r, tcpip.WriteOptions{
-		To: &tcpip.FullAddress{Addr: writeDstAddr, Port: h.dstAddr.Port},
-	})
-	c.checkEndpointWriteStats(1, epstats, gotErr)
-	if gotErr != wantErr {
-		c.t.Fatalf("Write returned unexpected error: got %v, want %v", gotErr, wantErr)
-	}
-}
-
-// testWrite sends a packet of the given test flow from the UDP endpoint to the
-// flow's destination address:port. It then receives it from the link endpoint
-// and verifies its correctness including any additional checker functions
-// provided.
-func testWrite(c *testContext, flow testFlow, checkers ...checker.NetworkChecker) uint16 {
-	c.t.Helper()
-	return testWriteAndVerifyInternal(c, flow, true, checkers...)
-}
-
-// testWriteWithoutDestination sends a packet of the given test flow from the
-// UDP endpoint without giving a destination address:port. It then receives it
-// from the link endpoint and verifies its correctness including any additional
-// checker functions provided.
-func testWriteWithoutDestination(c *testContext, flow testFlow, checkers ...checker.NetworkChecker) uint16 {
-	c.t.Helper()
-	return testWriteAndVerifyInternal(c, flow, false, checkers...)
-}
-
-func testWriteNoVerify(c *testContext, flow testFlow, setDest bool) buffer.View {
-	c.t.Helper()
-	// Take a snapshot of the stats to validate them at the end of the test.
-	epstats := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone()
-
-	writeOpts := tcpip.WriteOptions{}
-	if setDest {
-		h := flow.header4Tuple(outgoing)
-		writeDstAddr := flow.mapAddrIfApplicable(h.dstAddr.Addr)
-		writeOpts = tcpip.WriteOptions{
-			To: &tcpip.FullAddress{Addr: writeDstAddr, Port: h.dstAddr.Port},
-		}
-	}
-	var r bytes.Reader
-	payload := newPayload()
-	r.Reset(payload)
-	n, err := c.ep.Write(&r, writeOpts)
-	if err != nil {
-		c.t.Fatalf("Write failed: %s", err)
-	}
-	if n != int64(len(payload)) {
-		c.t.Fatalf("Bad number of bytes written: got %v, want %v", n, len(payload))
-	}
-	c.checkEndpointWriteStats(1, epstats, err)
-	return payload
-}
-
-func testWriteAndVerifyInternal(c *testContext, flow testFlow, setDest bool, checkers ...checker.NetworkChecker) uint16 {
-	c.t.Helper()
-	payload := testWriteNoVerify(c, flow, setDest)
-	// Received the packet and check the payload.
-	b := c.getPacketAndVerify(flow, checkers...)
-	var udp header.UDP
-	if flow.isV4() {
-		udp = header.UDP(header.IPv4(b).Payload())
-	} else {
-		udp = header.UDP(header.IPv6(b).Payload())
-	}
-	if !bytes.Equal(payload, udp.Payload()) {
-		c.t.Fatalf("Bad payload: got %x, want %x", udp.Payload(), payload)
-	}
-
-	return udp.SourcePort()
-}
-
-func testDualWrite(c *testContext) uint16 {
-	c.t.Helper()
-
-	v4Port := testWrite(c, unicastV4in6)
-	v6Port := testWrite(c, unicastV6)
-	if v4Port != v6Port {
-		c.t.Fatalf("expected v4 and v6 ports to be equal: got v4Port = %d, v6Port = %d", v4Port, v6Port)
-	}
-
-	return v4Port
-}
-
-func TestDualWriteUnbound(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	testDualWrite(c)
-}
-
-func TestDualWriteBoundToWildcard(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	p := testDualWrite(c)
-	if p != stackPort {
-		c.t.Fatalf("Bad port: got %v, want %v", p, stackPort)
-	}
-}
-
-func TestDualWriteConnectedToV6(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	// Connect to v6 address.
-	if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	testWrite(c, unicastV6)
-
-	// Write to V4 mapped address.
-	testFailingWrite(c, unicastV4in6, &tcpip.ErrNetworkUnreachable{})
-	const want = 1
-	if got := c.ep.Stats().(*tcpip.TransportEndpointStats).SendErrors.NoRoute.Value(); got != want {
-		c.t.Fatalf("Endpoint stat not updated. got %d want %d", got, want)
-	}
-}
-
-func TestDualWriteConnectedToV4Mapped(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	// Connect to v4 mapped address.
-	if err := c.ep.Connect(tcpip.FullAddress{Addr: testV4MappedAddr, Port: testPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	testWrite(c, unicastV4in6)
-
-	// Write to v6 address.
-	testFailingWrite(c, unicastV6, &tcpip.ErrInvalidEndpointState{})
-}
-
-func TestV4WriteOnV6Only(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpointForFlow(unicastV6Only)
-
-	// Write to V4 mapped address.
-	testFailingWrite(c, unicastV4in6, &tcpip.ErrNoRoute{})
-}
-
-func TestV6WriteOnBoundToV4Mapped(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	// Bind to v4 mapped address.
-	if err := c.ep.Bind(tcpip.FullAddress{Addr: stackV4MappedAddr, Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	// Write to v6 address.
-	testFailingWrite(c, unicastV6, &tcpip.ErrInvalidEndpointState{})
-}
-
-func TestV6WriteOnConnected(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	// Connect to v6 address.
-	if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil {
-		c.t.Fatalf("Connect failed: %s", err)
-	}
-
-	testWriteWithoutDestination(c, unicastV6)
-}
-
-func TestV4WriteOnConnected(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	// Connect to v4 mapped address.
-	if err := c.ep.Connect(tcpip.FullAddress{Addr: testV4MappedAddr, Port: testPort}); err != nil {
-		c.t.Fatalf("Connect failed: %s", err)
-	}
-
-	testWriteWithoutDestination(c, unicastV4)
-}
-
-func TestWriteOnConnectedInvalidPort(t *testing.T) {
-	protocols := map[string]tcpip.NetworkProtocolNumber{
-		"ipv4": ipv4.ProtocolNumber,
-		"ipv6": ipv6.ProtocolNumber,
-	}
-	for name, pn := range protocols {
-		t.Run(name, func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpoint(pn)
-			if err := c.ep.Connect(tcpip.FullAddress{Addr: stackAddr, Port: invalidPort}); err != nil {
-				c.t.Fatalf("Connect failed: %s", err)
-			}
-			writeOpts := tcpip.WriteOptions{
-				To: &tcpip.FullAddress{Addr: stackAddr, Port: invalidPort},
-			}
-			var r bytes.Reader
-			payload := newPayload()
-			r.Reset(payload)
-			n, err := c.ep.Write(&r, writeOpts)
-			if err != nil {
-				c.t.Fatalf("c.ep.Write(...) = %+s, want nil", err)
-			}
-			if got, want := n, int64(len(payload)); got != want {
-				c.t.Fatalf("c.ep.Write(...) wrote %d bytes, want %d bytes", got, want)
-			}
-
-			{
-				err := c.ep.LastError()
-				if _, ok := err.(*tcpip.ErrConnectionRefused); !ok {
-					c.t.Fatalf("expected c.ep.LastError() == ErrConnectionRefused, got: %+v", err)
-				}
-			}
-		})
-	}
-}
-
-// TestWriteOnBoundToV4Multicast checks that we can send packets out of a socket
-// that is bound to a V4 multicast address.
-func TestWriteOnBoundToV4Multicast(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4, multicastV4, broadcast} {
-		t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to V4 mcast address.
-			if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastAddr, Port: stackPort}); err != nil {
-				c.t.Fatal("Bind failed:", err)
-			}
-
-			testWrite(c, flow)
-		})
-	}
-}
-
-// TestWriteOnBoundToV4MappedMulticast checks that we can send packets out of a
-// socket that is bound to a V4-mapped multicast address.
-func TestWriteOnBoundToV4MappedMulticast(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4in6, multicastV4in6, broadcastIn6} {
-		t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to V4Mapped mcast address.
-			if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV4MappedAddr, Port: stackPort}); err != nil {
-				c.t.Fatalf("Bind failed: %s", err)
-			}
-
-			testWrite(c, flow)
-		})
-	}
-}
-
-// TestWriteOnBoundToV6Multicast checks that we can send packets out of a
-// socket that is bound to a V6 multicast address.
-func TestWriteOnBoundToV6Multicast(t *testing.T) {
-	for _, flow := range []testFlow{unicastV6, multicastV6} {
-		t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to V6 mcast address.
-			if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV6Addr, Port: stackPort}); err != nil {
-				c.t.Fatalf("Bind failed: %s", err)
-			}
-
-			testWrite(c, flow)
-		})
-	}
-}
-
-// TestWriteOnBoundToV6Multicast checks that we can send packets out of a
-// V6-only socket that is bound to a V6 multicast address.
-func TestWriteOnBoundToV6OnlyMulticast(t *testing.T) {
-	for _, flow := range []testFlow{unicastV6Only, multicastV6Only} {
-		t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to V6 mcast address.
-			if err := c.ep.Bind(tcpip.FullAddress{Addr: multicastV6Addr, Port: stackPort}); err != nil {
-				c.t.Fatalf("Bind failed: %s", err)
-			}
-
-			testWrite(c, flow)
-		})
-	}
-}
-
-// TestWriteOnBoundToBroadcast checks that we can send packets out of a
-// socket that is bound to the broadcast address.
-func TestWriteOnBoundToBroadcast(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4, multicastV4, broadcast} {
-		t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to V4 broadcast address.
-			if err := c.ep.Bind(tcpip.FullAddress{Addr: broadcastAddr, Port: stackPort}); err != nil {
-				c.t.Fatal("Bind failed:", err)
-			}
-
-			testWrite(c, flow)
-		})
-	}
-}
-
-// TestWriteOnBoundToV4MappedBroadcast checks that we can send packets out of a
-// socket that is bound to the V4-mapped broadcast address.
-func TestWriteOnBoundToV4MappedBroadcast(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4in6, multicastV4in6, broadcastIn6} {
-		t.Run(fmt.Sprintf("%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Bind to V4Mapped mcast address.
-			if err := c.ep.Bind(tcpip.FullAddress{Addr: broadcastV4MappedAddr, Port: stackPort}); err != nil {
-				c.t.Fatalf("Bind failed: %s", err)
-			}
-
-			testWrite(c, flow)
-		})
-	}
-}
-
-func TestReadIncrementsPacketsReceived(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	// Create IPv4 UDP endpoint
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	testRead(c, unicastV4)
-
-	var want uint64 = 1
-	if got := c.s.Stats().UDP.PacketsReceived.Value(); got != want {
-		c.t.Fatalf("Read did not increment PacketsReceived: got %v, want %v", got, want)
-	}
-}
-
-func TestReadIPPacketInfo(t *testing.T) {
-	tests := []struct {
-		name              string
-		proto             tcpip.NetworkProtocolNumber
-		flow              testFlow
-		expectedLocalAddr tcpip.Address
-		expectedDestAddr  tcpip.Address
-	}{
-		{
-			name:              "IPv4 unicast",
-			proto:             header.IPv4ProtocolNumber,
-			flow:              unicastV4,
-			expectedLocalAddr: stackAddr,
-			expectedDestAddr:  stackAddr,
-		},
-		{
-			name:  "IPv4 multicast",
-			proto: header.IPv4ProtocolNumber,
-			flow:  multicastV4,
-			// This should actually be a unicast address assigned to the interface.
-			//
-			// TODO(gvisor.dev/issue/3556): This check is validating incorrect
-			// behaviour. We still include the test so that once the bug is
-			// resolved, this test will start to fail and the individual tasked
-			// with fixing this bug knows to also fix this test :).
-			expectedLocalAddr: multicastAddr,
-			expectedDestAddr:  multicastAddr,
-		},
-		{
-			name:  "IPv4 broadcast",
-			proto: header.IPv4ProtocolNumber,
-			flow:  broadcast,
-			// This should actually be a unicast address assigned to the interface.
-			//
-			// TODO(gvisor.dev/issue/3556): This check is validating incorrect
-			// behaviour. We still include the test so that once the bug is
-			// resolved, this test will start to fail and the individual tasked
-			// with fixing this bug knows to also fix this test :).
-			expectedLocalAddr: broadcastAddr,
-			expectedDestAddr:  broadcastAddr,
-		},
-		{
-			name:              "IPv6 unicast",
-			proto:             header.IPv6ProtocolNumber,
-			flow:              unicastV6,
-			expectedLocalAddr: stackV6Addr,
-			expectedDestAddr:  stackV6Addr,
-		},
-		{
-			name:  "IPv6 multicast",
-			proto: header.IPv6ProtocolNumber,
-			flow:  multicastV6,
-			// This should actually be a unicast address assigned to the interface.
-			//
-			// TODO(gvisor.dev/issue/3556): This check is validating incorrect
-			// behaviour. We still include the test so that once the bug is
-			// resolved, this test will start to fail and the individual tasked
-			// with fixing this bug knows to also fix this test :).
-			expectedLocalAddr: multicastV6Addr,
-			expectedDestAddr:  multicastV6Addr,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpoint(test.proto)
-
-			bindAddr := tcpip.FullAddress{Port: stackPort}
-			if err := c.ep.Bind(bindAddr); err != nil {
-				t.Fatalf("Bind(%+v): %s", bindAddr, err)
-			}
-
-			if test.flow.isMulticast() {
-				ifoptSet := tcpip.AddMembershipOption{NIC: 1, MulticastAddr: test.flow.getMcastAddr()}
-				if err := c.ep.SetSockOpt(&ifoptSet); err != nil {
-					c.t.Fatalf("SetSockOpt(&%#v): %s:", ifoptSet, err)
-				}
-			}
-
-			c.ep.SocketOptions().SetReceivePacketInfo(true)
-
-			testRead(c, test.flow, checker.ReceiveIPPacketInfo(tcpip.IPPacketInfo{
-				NIC:             1,
-				LocalAddr:       test.expectedLocalAddr,
-				DestinationAddr: test.expectedDestAddr,
-			}))
-
-			if got := c.s.Stats().UDP.PacketsReceived.Value(); got != 1 {
-				t.Fatalf("Read did not increment PacketsReceived: got = %d, want = 1", got)
-			}
-		})
-	}
-}
-
-func TestReadRecvOriginalDstAddr(t *testing.T) {
-	tests := []struct {
-		name                    string
-		proto                   tcpip.NetworkProtocolNumber
-		flow                    testFlow
-		expectedOriginalDstAddr tcpip.FullAddress
-	}{
-		{
-			name:                    "IPv4 unicast",
-			proto:                   header.IPv4ProtocolNumber,
-			flow:                    unicastV4,
-			expectedOriginalDstAddr: tcpip.FullAddress{1, stackAddr, stackPort},
-		},
-		{
-			name:  "IPv4 multicast",
-			proto: header.IPv4ProtocolNumber,
-			flow:  multicastV4,
-			// This should actually be a unicast address assigned to the interface.
-			//
-			// TODO(gvisor.dev/issue/3556): This check is validating incorrect
-			// behaviour. We still include the test so that once the bug is
-			// resolved, this test will start to fail and the individual tasked
-			// with fixing this bug knows to also fix this test :).
-			expectedOriginalDstAddr: tcpip.FullAddress{1, multicastAddr, stackPort},
-		},
-		{
-			name:  "IPv4 broadcast",
-			proto: header.IPv4ProtocolNumber,
-			flow:  broadcast,
-			// This should actually be a unicast address assigned to the interface.
-			//
-			// TODO(gvisor.dev/issue/3556): This check is validating incorrect
-			// behaviour. We still include the test so that once the bug is
-			// resolved, this test will start to fail and the individual tasked
-			// with fixing this bug knows to also fix this test :).
-			expectedOriginalDstAddr: tcpip.FullAddress{1, broadcastAddr, stackPort},
-		},
-		{
-			name:                    "IPv6 unicast",
-			proto:                   header.IPv6ProtocolNumber,
-			flow:                    unicastV6,
-			expectedOriginalDstAddr: tcpip.FullAddress{1, stackV6Addr, stackPort},
-		},
-		{
-			name:  "IPv6 multicast",
-			proto: header.IPv6ProtocolNumber,
-			flow:  multicastV6,
-			// This should actually be a unicast address assigned to the interface.
-			//
-			// TODO(gvisor.dev/issue/3556): This check is validating incorrect
-			// behaviour. We still include the test so that once the bug is
-			// resolved, this test will start to fail and the individual tasked
-			// with fixing this bug knows to also fix this test :).
-			expectedOriginalDstAddr: tcpip.FullAddress{1, multicastV6Addr, stackPort},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpoint(test.proto)
-
-			bindAddr := tcpip.FullAddress{Port: stackPort}
-			if err := c.ep.Bind(bindAddr); err != nil {
-				t.Fatalf("Bind(%#v): %s", bindAddr, err)
-			}
-
-			if test.flow.isMulticast() {
-				ifoptSet := tcpip.AddMembershipOption{NIC: 1, MulticastAddr: test.flow.getMcastAddr()}
-				if err := c.ep.SetSockOpt(&ifoptSet); err != nil {
-					c.t.Fatalf("SetSockOpt(&%#v): %s:", ifoptSet, err)
-				}
-			}
-
-			c.ep.SocketOptions().SetReceiveOriginalDstAddress(true)
-
-			testRead(c, test.flow, checker.ReceiveOriginalDstAddr(test.expectedOriginalDstAddr))
-
-			if got := c.s.Stats().UDP.PacketsReceived.Value(); got != 1 {
-				t.Fatalf("Read did not increment PacketsReceived: got = %d, want = 1", got)
-			}
-		})
-	}
-}
-
-func TestWriteIncrementsPacketsSent(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	testDualWrite(c)
-
-	var want uint64 = 2
-	if got := c.s.Stats().UDP.PacketsSent.Value(); got != want {
-		c.t.Fatalf("Write did not increment PacketsSent: got %v, want %v", got, want)
-	}
-}
-
-func TestNoChecksum(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4, unicastV6} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			// Disable the checksum generation.
-			c.ep.SocketOptions().SetNoChecksum(true)
-			// This option is effective on IPv4 only.
-			testWrite(c, flow, checker.UDP(checker.NoChecksum(flow.isV4())))
-
-			// Enable the checksum generation.
-			c.ep.SocketOptions().SetNoChecksum(false)
-			testWrite(c, flow, checker.UDP(checker.NoChecksum(false)))
-		})
-	}
-}
-
-var _ stack.NetworkInterface = (*testInterface)(nil)
-
-type testInterface struct {
-	stack.NetworkInterface
-}
-
-func (*testInterface) ID() tcpip.NICID {
-	return 0
-}
-
-func (*testInterface) Enabled() bool {
-	return true
-}
-
-func TestTTL(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4, unicastV4in6, unicastV6, unicastV6Only, multicastV4, multicastV4in6, multicastV6, broadcast, broadcastIn6} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			const multicastTTL = 42
-			if err := c.ep.SetSockOptInt(tcpip.MulticastTTLOption, multicastTTL); err != nil {
-				c.t.Fatalf("SetSockOptInt failed: %s", err)
-			}
-
-			var wantTTL uint8
-			if flow.isMulticast() {
-				wantTTL = multicastTTL
-			} else {
-				var p stack.NetworkProtocolFactory
-				var n tcpip.NetworkProtocolNumber
-				if flow.isV4() {
-					p = ipv4.NewProtocol
-					n = ipv4.ProtocolNumber
-				} else {
-					p = ipv6.NewProtocol
-					n = ipv6.ProtocolNumber
-				}
-				s := stack.New(stack.Options{
-					NetworkProtocols: []stack.NetworkProtocolFactory{p},
-				})
-				ep := s.NetworkProtocolInstance(n).NewEndpoint(&testInterface{}, nil)
-				wantTTL = ep.DefaultTTL()
-				ep.Close()
-			}
-
-			testWrite(c, flow, checker.TTL(wantTTL))
-		})
-	}
-}
-
-func TestSetTTL(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4, unicastV4in6, unicastV6, unicastV6Only, broadcast, broadcastIn6} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			for _, wantTTL := range []uint8{1, 2, 50, 64, 128, 254, 255} {
-				t.Run(fmt.Sprintf("TTL:%d", wantTTL), func(t *testing.T) {
-					c := newDualTestContext(t, defaultMTU)
-					defer c.cleanup()
-
-					c.createEndpointForFlow(flow)
-
-					if err := c.ep.SetSockOptInt(tcpip.TTLOption, int(wantTTL)); err != nil {
-						c.t.Fatalf("SetSockOptInt(TTLOption, %d) failed: %s", wantTTL, err)
-					}
-
-					testWrite(c, flow, checker.TTL(wantTTL))
-				})
-			}
-		})
-	}
-}
-
-func TestSetTOS(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4, multicastV4, broadcast} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			const tos = testTOS
-			v, err := c.ep.GetSockOptInt(tcpip.IPv4TOSOption)
-			if err != nil {
-				c.t.Errorf("GetSockOptInt(IPv4TOSOption) failed: %s", err)
-			}
-			// Test for expected default value.
-			if v != 0 {
-				c.t.Errorf("got GetSockOptInt(IPv4TOSOption) = 0x%x, want = 0x%x", v, 0)
-			}
-
-			if err := c.ep.SetSockOptInt(tcpip.IPv4TOSOption, tos); err != nil {
-				c.t.Errorf("SetSockOptInt(IPv4TOSOption, 0x%x) failed: %s", tos, err)
-			}
-
-			v, err = c.ep.GetSockOptInt(tcpip.IPv4TOSOption)
-			if err != nil {
-				c.t.Errorf("GetSockOptInt(IPv4TOSOption) failed: %s", err)
-			}
-
-			if v != tos {
-				c.t.Errorf("got GetSockOptInt(IPv4TOSOption) = 0x%x, want = 0x%x", v, tos)
-			}
-
-			testWrite(c, flow, checker.TOS(tos, 0))
-		})
-	}
-}
-
-func TestSetTClass(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4in6, unicastV6, unicastV6Only, multicastV4in6, multicastV6, broadcastIn6} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			c := newDualTestContext(t, defaultMTU)
-			defer c.cleanup()
-
-			c.createEndpointForFlow(flow)
-
-			const tClass = testTOS
-			v, err := c.ep.GetSockOptInt(tcpip.IPv6TrafficClassOption)
-			if err != nil {
-				c.t.Errorf("GetSockOptInt(IPv6TrafficClassOption) failed: %s", err)
-			}
-			// Test for expected default value.
-			if v != 0 {
-				c.t.Errorf("got GetSockOptInt(IPv6TrafficClassOption) = 0x%x, want = 0x%x", v, 0)
-			}
-
-			if err := c.ep.SetSockOptInt(tcpip.IPv6TrafficClassOption, tClass); err != nil {
-				c.t.Errorf("SetSockOptInt(IPv6TrafficClassOption, 0x%x) failed: %s", tClass, err)
-			}
-
-			v, err = c.ep.GetSockOptInt(tcpip.IPv6TrafficClassOption)
-			if err != nil {
-				c.t.Errorf("GetSockOptInt(IPv6TrafficClassOption) failed: %s", err)
-			}
-
-			if v != tClass {
-				c.t.Errorf("got GetSockOptInt(IPv6TrafficClassOption) = 0x%x, want = 0x%x", v, tClass)
-			}
-
-			// The header getter for TClass is called TOS, so use that checker.
-			testWrite(c, flow, checker.TOS(tClass, 0))
-		})
-	}
-}
-
-func TestReceiveTosTClass(t *testing.T) {
-	const RcvTOSOpt = "ReceiveTosOption"
-	const RcvTClassOpt = "ReceiveTClassOption"
-
-	testCases := []struct {
-		name  string
-		tests []testFlow
-	}{
-		{RcvTOSOpt, []testFlow{unicastV4, broadcast}},
-		{RcvTClassOpt, []testFlow{unicastV4in6, unicastV6, unicastV6Only, broadcastIn6}},
-	}
-	for _, testCase := range testCases {
-		for _, flow := range testCase.tests {
-			t.Run(fmt.Sprintf("%s:flow:%s", testCase.name, flow), func(t *testing.T) {
-				c := newDualTestContext(t, defaultMTU)
-				defer c.cleanup()
-
-				c.createEndpointForFlow(flow)
-				name := testCase.name
-
-				var optionGetter func() bool
-				var optionSetter func(bool)
-				switch name {
-				case RcvTOSOpt:
-					optionGetter = c.ep.SocketOptions().GetReceiveTOS
-					optionSetter = c.ep.SocketOptions().SetReceiveTOS
-				case RcvTClassOpt:
-					optionGetter = c.ep.SocketOptions().GetReceiveTClass
-					optionSetter = c.ep.SocketOptions().SetReceiveTClass
-				default:
-					t.Fatalf("unkown test variant: %s", name)
-				}
-
-				// Verify that setting and reading the option works.
-				v := optionGetter()
-				// Test for expected default value.
-				if v != false {
-					c.t.Errorf("got GetSockOptBool(%s) = %t, want = %t", name, v, false)
-				}
-
-				want := true
-				optionSetter(want)
-
-				got := optionGetter()
-				if got != want {
-					c.t.Errorf("got GetSockOptBool(%s) = %t, want = %t", name, got, want)
-				}
-
-				// Verify that the correct received TOS or TClass is handed through as
-				// ancillary data to the ControlMessages struct.
-				if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-					c.t.Fatalf("Bind failed: %s", err)
-				}
-				switch name {
-				case RcvTClassOpt:
-					testRead(c, flow, checker.ReceiveTClass(testTOS))
-				case RcvTOSOpt:
-					testRead(c, flow, checker.ReceiveTOS(testTOS))
-				default:
-					t.Fatalf("unknown test variant: %s", name)
-				}
-			})
-		}
-	}
-}
-
-func TestMulticastInterfaceOption(t *testing.T) {
-	for _, flow := range []testFlow{multicastV4, multicastV4in6, multicastV6, multicastV6Only} {
-		t.Run(fmt.Sprintf("flow:%s", flow), func(t *testing.T) {
-			for _, bindTyp := range []string{"bound", "unbound"} {
-				t.Run(bindTyp, func(t *testing.T) {
-					for _, optTyp := range []string{"use local-addr", "use NICID", "use local-addr and NIC"} {
-						t.Run(optTyp, func(t *testing.T) {
-							h := flow.header4Tuple(outgoing)
-							mcastAddr := h.dstAddr.Addr
-							localIfAddr := h.srcAddr.Addr
-
-							var ifoptSet tcpip.MulticastInterfaceOption
-							switch optTyp {
-							case "use local-addr":
-								ifoptSet.InterfaceAddr = localIfAddr
-							case "use NICID":
-								ifoptSet.NIC = 1
-							case "use local-addr and NIC":
-								ifoptSet.InterfaceAddr = localIfAddr
-								ifoptSet.NIC = 1
-							default:
-								t.Fatal("unknown test variant")
-							}
-
-							c := newDualTestContext(t, defaultMTU)
-							defer c.cleanup()
-
-							c.createEndpoint(flow.sockProto())
-
-							if bindTyp == "bound" {
-								// Bind the socket by connecting to the multicast address.
-								// This may have an influence on how the multicast interface
-								// is set.
-								addr := tcpip.FullAddress{
-									Addr: flow.mapAddrIfApplicable(mcastAddr),
-									Port: stackPort,
-								}
-								if err := c.ep.Connect(addr); err != nil {
-									c.t.Fatalf("Connect failed: %s", err)
-								}
-							}
-
-							if err := c.ep.SetSockOpt(&ifoptSet); err != nil {
-								c.t.Fatalf("SetSockOpt(&%#v): %s", ifoptSet, err)
-							}
-
-							// Verify multicast interface addr and NIC were set correctly.
-							// Note that NIC must be 1 since this is our outgoing interface.
-							var ifoptGot tcpip.MulticastInterfaceOption
-							if err := c.ep.GetSockOpt(&ifoptGot); err != nil {
-								c.t.Fatalf("GetSockOpt(&%T): %s", ifoptGot, err)
-							} else if ifoptWant := (tcpip.MulticastInterfaceOption{NIC: 1, InterfaceAddr: ifoptSet.InterfaceAddr}); ifoptGot != ifoptWant {
-								c.t.Errorf("got multicast interface option = %#v, want = %#v", ifoptGot, ifoptWant)
-							}
-						})
-					}
-				})
-			}
-		})
-	}
-}
-
-// TestV4UnknownDestination verifies that we generate an ICMPv4 Destination
-// Unreachable message when a udp datagram is received on ports for which there
-// is no bound udp socket.
-func TestV4UnknownDestination(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	testCases := []struct {
-		flow         testFlow
-		icmpRequired bool
-		// largePayload if true, will result in a payload large enough
-		// so that the final generated IPv4 packet is larger than
-		// header.IPv4MinimumProcessableDatagramSize.
-		largePayload bool
-		// badChecksum if true, will set an invalid checksum in the
-		// header.
-		badChecksum bool
-	}{
-		{unicastV4, true, false, false},
-		{unicastV4, true, true, false},
-		{unicastV4, false, false, true},
-		{unicastV4, false, true, true},
-		{multicastV4, false, false, false},
-		{multicastV4, false, true, false},
-		{broadcast, false, false, false},
-		{broadcast, false, true, false},
-	}
-	checksumErrors := uint64(0)
-	for _, tc := range testCases {
-		t.Run(fmt.Sprintf("flow:%s icmpRequired:%t largePayload:%t badChecksum:%t", tc.flow, tc.icmpRequired, tc.largePayload, tc.badChecksum), func(t *testing.T) {
-			payload := newPayload()
-			if tc.largePayload {
-				payload = newMinPayload(576)
-			}
-			c.injectPacket(tc.flow, payload, tc.badChecksum)
-			if tc.badChecksum {
-				checksumErrors++
-				if got, want := c.s.Stats().UDP.ChecksumErrors.Value(), checksumErrors; got != want {
-					t.Fatalf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want)
-				}
-			}
-			if !tc.icmpRequired {
-				ctx, cancel := context.WithTimeout(context.Background(), time.Second)
-				defer cancel()
-				if p, ok := c.linkEP.ReadContext(ctx); ok {
-					t.Fatalf("unexpected packet received: %+v", p)
-				}
-				return
-			}
-
-			// ICMP required.
-			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
-			defer cancel()
-			p, ok := c.linkEP.ReadContext(ctx)
-			if !ok {
-				t.Fatalf("packet wasn't written out")
-				return
-			}
-
-			vv := buffer.NewVectorisedView(p.Pkt.Size(), p.Pkt.Views())
-			pkt := vv.ToView()
-			if got, want := len(pkt), header.IPv4MinimumProcessableDatagramSize; got > want {
-				t.Fatalf("got an ICMP packet of size: %d, want: sz <= %d", got, want)
-			}
-
-			hdr := header.IPv4(pkt)
-			checker.IPv4(t, hdr, checker.ICMPv4(
-				checker.ICMPv4Type(header.ICMPv4DstUnreachable),
-				checker.ICMPv4Code(header.ICMPv4PortUnreachable)))
-
-			// We need to compare the included data part of the UDP packet that is in
-			// the ICMP packet with the matching original data.
-			icmpPkt := header.ICMPv4(hdr.Payload())
-			payloadIPHeader := header.IPv4(icmpPkt.Payload())
-			incomingHeaderLength := header.IPv4MinimumSize + header.UDPMinimumSize
-			wantLen := len(payload)
-			if tc.largePayload {
-				// To work out the data size we need to simulate what the sender would
-				// have done. The wanted size is the total available minus the sum of
-				// the headers in the UDP AND ICMP packets, given that we know the test
-				// had only a minimal IP header but the ICMP sender will have allowed
-				// for a maximally sized packet header.
-				wantLen = header.IPv4MinimumProcessableDatagramSize - header.IPv4MaximumHeaderSize - header.ICMPv4MinimumSize - incomingHeaderLength
-			}
-
-			// In the case of large payloads the IP packet may be truncated. Update
-			// the length field before retrieving the udp datagram payload.
-			// Add back the two headers within the payload.
-			payloadIPHeader.SetTotalLength(uint16(wantLen + incomingHeaderLength))
-
-			origDgram := header.UDP(payloadIPHeader.Payload())
-			if got, want := len(origDgram.Payload()), wantLen; got != want {
-				t.Fatalf("unexpected payload length got: %d, want: %d", got, want)
-			}
-			if got, want := origDgram.Payload(), payload[:wantLen]; !bytes.Equal(got, want) {
-				t.Fatalf("unexpected payload got: %d, want: %d", got, want)
-			}
-		})
-	}
-}
-
-// TestV6UnknownDestination verifies that we generate an ICMPv6 Destination
-// Unreachable message when a udp datagram is received on ports for which there
-// is no bound udp socket.
-func TestV6UnknownDestination(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	testCases := []struct {
-		flow         testFlow
-		icmpRequired bool
-		// largePayload if true will result in a payload large enough to
-		// create an IPv6 packet > header.IPv6MinimumMTU bytes.
-		largePayload bool
-		// badChecksum if true, will set an invalid checksum in the
-		// header.
-		badChecksum bool
-	}{
-		{unicastV6, true, false, false},
-		{unicastV6, true, true, false},
-		{unicastV6, false, false, true},
-		{unicastV6, false, true, true},
-		{multicastV6, false, false, false},
-		{multicastV6, false, true, false},
-	}
-	checksumErrors := uint64(0)
-	for _, tc := range testCases {
-		t.Run(fmt.Sprintf("flow:%s icmpRequired:%t largePayload:%t badChecksum:%t", tc.flow, tc.icmpRequired, tc.largePayload, tc.badChecksum), func(t *testing.T) {
-			payload := newPayload()
-			if tc.largePayload {
-				payload = newMinPayload(1280)
-			}
-			c.injectPacket(tc.flow, payload, tc.badChecksum)
-			if tc.badChecksum {
-				checksumErrors++
-				if got, want := c.s.Stats().UDP.ChecksumErrors.Value(), checksumErrors; got != want {
-					t.Fatalf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want)
-				}
-			}
-			if !tc.icmpRequired {
-				ctx, cancel := context.WithTimeout(context.Background(), time.Second)
-				defer cancel()
-				if p, ok := c.linkEP.ReadContext(ctx); ok {
-					t.Fatalf("unexpected packet received: %+v", p)
-				}
-				return
-			}
-
-			// ICMP required.
-			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
-			defer cancel()
-			p, ok := c.linkEP.ReadContext(ctx)
-			if !ok {
-				t.Fatalf("packet wasn't written out")
-				return
-			}
-
-			vv := buffer.NewVectorisedView(p.Pkt.Size(), p.Pkt.Views())
-			pkt := vv.ToView()
-			if got, want := len(pkt), header.IPv6MinimumMTU; got > want {
-				t.Fatalf("got an ICMP packet of size: %d, want: sz <= %d", got, want)
-			}
-
-			hdr := header.IPv6(pkt)
-			checker.IPv6(t, hdr, checker.ICMPv6(
-				checker.ICMPv6Type(header.ICMPv6DstUnreachable),
-				checker.ICMPv6Code(header.ICMPv6PortUnreachable)))
-
-			icmpPkt := header.ICMPv6(hdr.Payload())
-			payloadIPHeader := header.IPv6(icmpPkt.Payload())
-			wantLen := len(payload)
-			if tc.largePayload {
-				wantLen = header.IPv6MinimumMTU - header.IPv6MinimumSize*2 - header.ICMPv6MinimumSize - header.UDPMinimumSize
-			}
-			// In case of large payloads the IP packet may be truncated. Update
-			// the length field before retrieving the udp datagram payload.
-			payloadIPHeader.SetPayloadLength(uint16(wantLen + header.UDPMinimumSize))
-
-			origDgram := header.UDP(payloadIPHeader.Payload())
-			if got, want := len(origDgram.Payload()), wantLen; got != want {
-				t.Fatalf("unexpected payload length got: %d, want: %d", got, want)
-			}
-			if got, want := origDgram.Payload(), payload[:wantLen]; !bytes.Equal(got, want) {
-				t.Fatalf("unexpected payload got: %v, want: %v", got, want)
-			}
-		})
-	}
-}
-
-// TestIncrementMalformedPacketsReceived verifies if the malformed received
-// global and endpoint stats are incremented.
-func TestIncrementMalformedPacketsReceived(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	payload := newPayload()
-	h := unicastV6.header4Tuple(incoming)
-	buf := c.buildV6Packet(payload, &h)
-
-	// Invalidate the UDP header length field.
-	u := header.UDP(buf[header.IPv6MinimumSize:])
-	u.SetLength(u.Length() + 1)
-
-	c.linkEP.InjectInbound(ipv6.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	const want = 1
-	if got := c.s.Stats().UDP.MalformedPacketsReceived.Value(); got != want {
-		t.Errorf("got stats.UDP.MalformedPacketsReceived.Value() = %d, want = %d", got, want)
-	}
-	if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.MalformedPacketsReceived.Value(); got != want {
-		t.Errorf("got EP Stats.ReceiveErrors.MalformedPacketsReceived stats = %d, want = %d", got, want)
-	}
-}
-
-// TestShortHeader verifies that when a packet with a too-short UDP header is
-// received, the malformed received global stat gets incremented.
-func TestShortHeader(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	h := unicastV6.header4Tuple(incoming)
-
-	// Allocate a buffer for an IPv6 and too-short UDP header.
-	const udpSize = header.UDPMinimumSize - 1
-	buf := buffer.NewView(header.IPv6MinimumSize + udpSize)
-	// Initialize the IP header.
-	ip := header.IPv6(buf)
-	ip.Encode(&header.IPv6Fields{
-		TrafficClass:      testTOS,
-		PayloadLength:     uint16(udpSize),
-		TransportProtocol: udp.ProtocolNumber,
-		HopLimit:          65,
-		SrcAddr:           h.srcAddr.Addr,
-		DstAddr:           h.dstAddr.Addr,
-	})
-
-	// Initialize the UDP header.
-	udpHdr := header.UDP(buffer.NewView(header.UDPMinimumSize))
-	udpHdr.Encode(&header.UDPFields{
-		SrcPort: h.srcAddr.Port,
-		DstPort: h.dstAddr.Port,
-		Length:  header.UDPMinimumSize,
-	})
-	// Calculate the UDP pseudo-header checksum.
-	xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, h.srcAddr.Addr, h.dstAddr.Addr, uint16(len(udpHdr)))
-	udpHdr.SetChecksum(^udpHdr.CalculateChecksum(xsum))
-	// Copy all but the last byte of the UDP header into the packet.
-	copy(buf[header.IPv6MinimumSize:], udpHdr)
-
-	// Inject packet.
-	c.linkEP.InjectInbound(ipv6.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	if got, want := c.s.Stats().MalformedRcvdPackets.Value(), uint64(1); got != want {
-		t.Errorf("got c.s.Stats().MalformedRcvdPackets.Value() = %d, want = %d", got, want)
-	}
-}
-
-// TestBadChecksumErrors verifies if a checksum error is detected,
-// global and endpoint stats are incremented.
-func TestBadChecksumErrors(t *testing.T) {
-	for _, flow := range []testFlow{unicastV4, unicastV6} {
-		c := newDualTestContext(t, defaultMTU)
-		defer c.cleanup()
-
-		c.createEndpoint(flow.sockProto())
-		// Bind to wildcard.
-		if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-			c.t.Fatalf("Bind failed: %s", err)
-		}
-
-		payload := newPayload()
-		c.injectPacket(flow, payload, true /* badChecksum */)
-
-		const want = 1
-		if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want {
-			t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want)
-		}
-		if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want {
-			t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want)
-		}
-	}
-}
-
-// TestPayloadModifiedV4 verifies if a checksum error is detected,
-// global and endpoint stats are incremented.
-func TestPayloadModifiedV4(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv4.ProtocolNumber)
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	payload := newPayload()
-	h := unicastV4.header4Tuple(incoming)
-	buf := c.buildV4Packet(payload, &h)
-	// Modify the payload so that the checksum value in the UDP header will be
-	// incorrect.
-	buf[len(buf)-1]++
-	c.linkEP.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	const want = 1
-	if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want {
-		t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want)
-	}
-	if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want {
-		t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want)
-	}
-}
-
-// TestPayloadModifiedV6 verifies if a checksum error is detected,
-// global and endpoint stats are incremented.
-func TestPayloadModifiedV6(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	payload := newPayload()
-	h := unicastV6.header4Tuple(incoming)
-	buf := c.buildV6Packet(payload, &h)
-	// Modify the payload so that the checksum value in the UDP header will be
-	// incorrect.
-	buf[len(buf)-1]++
-	c.linkEP.InjectInbound(ipv6.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	const want = 1
-	if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want {
-		t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want)
-	}
-	if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want {
-		t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want)
-	}
-}
-
-// TestChecksumZeroV4 verifies if the checksum value is zero, global and
-// endpoint states are *not* incremented (UDP checksum is optional on IPv4).
-func TestChecksumZeroV4(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv4.ProtocolNumber)
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	payload := newPayload()
-	h := unicastV4.header4Tuple(incoming)
-	buf := c.buildV4Packet(payload, &h)
-	// Set the checksum field in the UDP header to zero.
-	u := header.UDP(buf[header.IPv4MinimumSize:])
-	u.SetChecksum(0)
-	c.linkEP.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	const want = 0
-	if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want {
-		t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want)
-	}
-	if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want {
-		t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want)
-	}
-}
-
-// TestChecksumZeroV6 verifies if the checksum value is zero, global and
-// endpoint states are incremented (UDP checksum is *not* optional on IPv6).
-func TestChecksumZeroV6(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	payload := newPayload()
-	h := unicastV6.header4Tuple(incoming)
-	buf := c.buildV6Packet(payload, &h)
-	// Set the checksum field in the UDP header to zero.
-	u := header.UDP(buf[header.IPv6MinimumSize:])
-	u.SetChecksum(0)
-	c.linkEP.InjectInbound(ipv6.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
-		Data: buf.ToVectorisedView(),
-	}))
-
-	const want = 1
-	if got := c.s.Stats().UDP.ChecksumErrors.Value(); got != want {
-		t.Errorf("got stats.UDP.ChecksumErrors.Value() = %d, want = %d", got, want)
-	}
-	if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ChecksumErrors.Value(); got != want {
-		t.Errorf("got EP Stats.ReceiveErrors.ChecksumErrors stats = %d, want = %d", got, want)
-	}
-}
-
-// TestShutdownRead verifies endpoint read shutdown and error
-// stats increment on packet receive.
-func TestShutdownRead(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	// Bind to wildcard.
-	if err := c.ep.Bind(tcpip.FullAddress{Port: stackPort}); err != nil {
-		c.t.Fatalf("Bind failed: %s", err)
-	}
-
-	if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil {
-		c.t.Fatalf("Connect failed: %s", err)
-	}
-
-	if err := c.ep.Shutdown(tcpip.ShutdownRead); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	testFailingRead(c, unicastV6, true /* expectReadError */)
-
-	var want uint64 = 1
-	if got := c.s.Stats().UDP.ReceiveBufferErrors.Value(); got != want {
-		t.Errorf("got stats.UDP.ReceiveBufferErrors.Value() = %v, want = %v", got, want)
-	}
-	if got := c.ep.Stats().(*tcpip.TransportEndpointStats).ReceiveErrors.ClosedReceiver.Value(); got != want {
-		t.Errorf("got EP Stats.ReceiveErrors.ClosedReceiver stats = %v, want = %v", got, want)
-	}
-}
-
-// TestShutdownWrite verifies endpoint write shutdown and error
-// stats increment on packet write.
-func TestShutdownWrite(t *testing.T) {
-	c := newDualTestContext(t, defaultMTU)
-	defer c.cleanup()
-
-	c.createEndpoint(ipv6.ProtocolNumber)
-
-	if err := c.ep.Connect(tcpip.FullAddress{Addr: testV6Addr, Port: testPort}); err != nil {
-		c.t.Fatalf("Connect failed: %s", err)
-	}
-
-	if err := c.ep.Shutdown(tcpip.ShutdownWrite); err != nil {
-		t.Fatalf("Shutdown failed: %s", err)
-	}
-
-	testFailingWrite(c, unicastV6, &tcpip.ErrClosedForSend{})
-}
-
-func (c *testContext) checkEndpointWriteStats(incr uint64, want tcpip.TransportEndpointStats, err tcpip.Error) {
-	got := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone()
-	switch err.(type) {
-	case nil:
-		want.PacketsSent.IncrementBy(incr)
-	case *tcpip.ErrMessageTooLong, *tcpip.ErrInvalidOptionValue:
-		want.WriteErrors.InvalidArgs.IncrementBy(incr)
-	case *tcpip.ErrClosedForSend:
-		want.WriteErrors.WriteClosed.IncrementBy(incr)
-	case *tcpip.ErrInvalidEndpointState:
-		want.WriteErrors.InvalidEndpointState.IncrementBy(incr)
-	case *tcpip.ErrNoRoute, *tcpip.ErrBroadcastDisabled, *tcpip.ErrNetworkUnreachable:
-		want.SendErrors.NoRoute.IncrementBy(incr)
-	default:
-		want.SendErrors.SendToNetworkFailed.IncrementBy(incr)
-	}
-	if got != want {
-		c.t.Errorf("Endpoint stats not matching for error %s got %+v want %+v", err, got, want)
-	}
-}
-
-func (c *testContext) checkEndpointReadStats(incr uint64, want tcpip.TransportEndpointStats, err tcpip.Error) {
-	got := c.ep.Stats().(*tcpip.TransportEndpointStats).Clone()
-	switch err.(type) {
-	case nil, *tcpip.ErrWouldBlock:
-	case *tcpip.ErrClosedForReceive:
-		want.ReadErrors.ReadClosed.IncrementBy(incr)
-	default:
-		c.t.Errorf("Endpoint error missing stats update err %v", err)
-	}
-	if got != want {
-		c.t.Errorf("Endpoint stats not matching for error %s got %+v want %+v", err, got, want)
-	}
-}
-
-func TestOutgoingSubnetBroadcast(t *testing.T) {
-	const nicID1 = 1
-
-	ipv4Addr := tcpip.AddressWithPrefix{
-		Address:   "\xc0\xa8\x01\x3a",
-		PrefixLen: 24,
-	}
-	ipv4Subnet := ipv4Addr.Subnet()
-	ipv4SubnetBcast := ipv4Subnet.Broadcast()
-	ipv4Gateway := tcpip.Address("\xc0\xa8\x01\x01")
-	ipv4AddrPrefix31 := tcpip.AddressWithPrefix{
-		Address:   "\xc0\xa8\x01\x3a",
-		PrefixLen: 31,
-	}
-	ipv4Subnet31 := ipv4AddrPrefix31.Subnet()
-	ipv4Subnet31Bcast := ipv4Subnet31.Broadcast()
-	ipv4AddrPrefix32 := tcpip.AddressWithPrefix{
-		Address:   "\xc0\xa8\x01\x3a",
-		PrefixLen: 32,
-	}
-	ipv4Subnet32 := ipv4AddrPrefix32.Subnet()
-	ipv4Subnet32Bcast := ipv4Subnet32.Broadcast()
-	ipv6Addr := tcpip.AddressWithPrefix{
-		Address:   "\x20\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
-		PrefixLen: 64,
-	}
-	ipv6Subnet := ipv6Addr.Subnet()
-	ipv6SubnetBcast := ipv6Subnet.Broadcast()
-	remNetAddr := tcpip.AddressWithPrefix{
-		Address:   "\x64\x0a\x7b\x18",
-		PrefixLen: 24,
-	}
-	remNetSubnet := remNetAddr.Subnet()
-	remNetSubnetBcast := remNetSubnet.Broadcast()
-
-	tests := []struct {
-		name                 string
-		nicAddr              tcpip.ProtocolAddress
-		routes               []tcpip.Route
-		remoteAddr           tcpip.Address
-		requiresBroadcastOpt bool
-	}{
-		{
-			name: "IPv4 Broadcast to local subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4Addr,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: ipv4Subnet,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:           ipv4SubnetBcast,
-			requiresBroadcastOpt: true,
-		},
-		{
-			name: "IPv4 Broadcast to local /31 subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4AddrPrefix31,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: ipv4Subnet31,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:           ipv4Subnet31Bcast,
-			requiresBroadcastOpt: false,
-		},
-		{
-			name: "IPv4 Broadcast to local /32 subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4AddrPrefix32,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: ipv4Subnet32,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:           ipv4Subnet32Bcast,
-			requiresBroadcastOpt: false,
-		},
-		// IPv6 has no notion of a broadcast.
-		{
-			name: "IPv6 'Broadcast' to local subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv6ProtocolNumber,
-				AddressWithPrefix: ipv6Addr,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: ipv6Subnet,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr:           ipv6SubnetBcast,
-			requiresBroadcastOpt: false,
-		},
-		{
-			name: "IPv4 Broadcast to remote subnet",
-			nicAddr: tcpip.ProtocolAddress{
-				Protocol:          header.IPv4ProtocolNumber,
-				AddressWithPrefix: ipv4Addr,
-			},
-			routes: []tcpip.Route{
-				{
-					Destination: remNetSubnet,
-					Gateway:     ipv4Gateway,
-					NIC:         nicID1,
-				},
-			},
-			remoteAddr: remNetSubnetBcast,
-			// TODO(gvisor.dev/issue/3938): Once we support marking a route as
-			// broadcast, this test should require the broadcast option to be set.
-			requiresBroadcastOpt: false,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			s := stack.New(stack.Options{
-				NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
-				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
-			})
-			e := channel.New(0, defaultMTU, "")
-			if err := s.CreateNIC(nicID1, e); err != nil {
-				t.Fatalf("CreateNIC(%d, _): %s", nicID1, err)
-			}
-			if err := s.AddProtocolAddress(nicID1, test.nicAddr); err != nil {
-				t.Fatalf("AddProtocolAddress(%d, %+v): %s", nicID1, test.nicAddr, err)
-			}
-
-			s.SetRouteTable(test.routes)
-
-			var netProto tcpip.NetworkProtocolNumber
-			switch l := len(test.remoteAddr); l {
-			case header.IPv4AddressSize:
-				netProto = header.IPv4ProtocolNumber
-			case header.IPv6AddressSize:
-				netProto = header.IPv6ProtocolNumber
-			default:
-				t.Fatalf("got unexpected address length = %d bytes", l)
-			}
-
-			wq := waiter.Queue{}
-			ep, err := s.NewEndpoint(udp.ProtocolNumber, netProto, &wq)
-			if err != nil {
-				t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, netProto, err)
-			}
-			defer ep.Close()
-
-			var r bytes.Reader
-			data := []byte{1, 2, 3, 4}
-			to := tcpip.FullAddress{
-				Addr: test.remoteAddr,
-				Port: 80,
-			}
-			opts := tcpip.WriteOptions{To: &to}
-			expectedErrWithoutBcastOpt := func(err tcpip.Error) tcpip.Error {
-				if _, ok := err.(*tcpip.ErrBroadcastDisabled); ok {
-					return nil
-				}
-				return &tcpip.ErrBroadcastDisabled{}
-			}
-			if !test.requiresBroadcastOpt {
-				expectedErrWithoutBcastOpt = nil
-			}
-
-			r.Reset(data)
-			{
-				n, err := ep.Write(&r, opts)
-				if expectedErrWithoutBcastOpt != nil {
-					if want := expectedErrWithoutBcastOpt(err); want != nil {
-						t.Fatalf("got ep.Write(_, %#v) = (%d, %s), want = (_, %s)", opts, n, err, want)
-					}
-				} else if err != nil {
-					t.Fatalf("got ep.Write(_, %#v) = (%d, %s), want = (_, nil)", opts, n, err)
-				}
-			}
-
-			ep.SocketOptions().SetBroadcast(true)
-
-			r.Reset(data)
-			if n, err := ep.Write(&r, opts); err != nil {
-				t.Fatalf("got ep.Write(_, %#v) = (%d, %s), want = (_, nil)", opts, n, err)
-			}
-
-			ep.SocketOptions().SetBroadcast(false)
-
-			r.Reset(data)
-			{
-				n, err := ep.Write(&r, opts)
-				if expectedErrWithoutBcastOpt != nil {
-					if want := expectedErrWithoutBcastOpt(err); want != nil {
-						t.Fatalf("got ep.Write(_, %#v) = (%d, %s), want = (_, %s)", opts, n, err, want)
-					}
-				} else if err != nil {
-					t.Fatalf("got ep.Write(_, %#v) = (%d, %s), want = (_, nil)", opts, n, err)
-				}
-			}
-		})
-	}
-}
diff --git a/pkg/waiter/waiter_list.go b/pkg/waiter/waiter_list.go
new file mode 100644
index 0000000..f83aeb4
--- /dev/null
+++ b/pkg/waiter/waiter_list.go
@@ -0,0 +1,221 @@
+package waiter
+
+// 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 waiterElementMapper struct{}
+
+// linkerFor maps an Element to a Linker.
+//
+// This default implementation should be inlined.
+//
+//go:nosplit
+func (waiterElementMapper) linkerFor(elem *Entry) *Entry { 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 waiterList struct {
+	head *Entry
+	tail *Entry
+}
+
+// Reset resets list l to the empty state.
+func (l *waiterList) Reset() {
+	l.head = nil
+	l.tail = nil
+}
+
+// Empty returns true iff the list is empty.
+//
+//go:nosplit
+func (l *waiterList) Empty() bool {
+	return l.head == nil
+}
+
+// Front returns the first element of list l or nil.
+//
+//go:nosplit
+func (l *waiterList) Front() *Entry {
+	return l.head
+}
+
+// Back returns the last element of list l or nil.
+//
+//go:nosplit
+func (l *waiterList) Back() *Entry {
+	return l.tail
+}
+
+// Len returns the number of elements in the list.
+//
+// NOTE: This is an O(n) operation.
+//
+//go:nosplit
+func (l *waiterList) Len() (count int) {
+	for e := l.Front(); e != nil; e = (waiterElementMapper{}.linkerFor(e)).Next() {
+		count++
+	}
+	return count
+}
+
+// PushFront inserts the element e at the front of list l.
+//
+//go:nosplit
+func (l *waiterList) PushFront(e *Entry) {
+	linker := waiterElementMapper{}.linkerFor(e)
+	linker.SetNext(l.head)
+	linker.SetPrev(nil)
+	if l.head != nil {
+		waiterElementMapper{}.linkerFor(l.head).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+
+	l.head = e
+}
+
+// PushBack inserts the element e at the back of list l.
+//
+//go:nosplit
+func (l *waiterList) PushBack(e *Entry) {
+	linker := waiterElementMapper{}.linkerFor(e)
+	linker.SetNext(nil)
+	linker.SetPrev(l.tail)
+	if l.tail != nil {
+		waiterElementMapper{}.linkerFor(l.tail).SetNext(e)
+	} else {
+		l.head = e
+	}
+
+	l.tail = e
+}
+
+// PushBackList inserts list m at the end of list l, emptying m.
+//
+//go:nosplit
+func (l *waiterList) PushBackList(m *waiterList) {
+	if l.head == nil {
+		l.head = m.head
+		l.tail = m.tail
+	} else if m.head != nil {
+		waiterElementMapper{}.linkerFor(l.tail).SetNext(m.head)
+		waiterElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
+
+		l.tail = m.tail
+	}
+	m.head = nil
+	m.tail = nil
+}
+
+// InsertAfter inserts e after b.
+//
+//go:nosplit
+func (l *waiterList) InsertAfter(b, e *Entry) {
+	bLinker := waiterElementMapper{}.linkerFor(b)
+	eLinker := waiterElementMapper{}.linkerFor(e)
+
+	a := bLinker.Next()
+
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	bLinker.SetNext(e)
+
+	if a != nil {
+		waiterElementMapper{}.linkerFor(a).SetPrev(e)
+	} else {
+		l.tail = e
+	}
+}
+
+// InsertBefore inserts e before a.
+//
+//go:nosplit
+func (l *waiterList) InsertBefore(a, e *Entry) {
+	aLinker := waiterElementMapper{}.linkerFor(a)
+	eLinker := waiterElementMapper{}.linkerFor(e)
+
+	b := aLinker.Prev()
+	eLinker.SetNext(a)
+	eLinker.SetPrev(b)
+	aLinker.SetPrev(e)
+
+	if b != nil {
+		waiterElementMapper{}.linkerFor(b).SetNext(e)
+	} else {
+		l.head = e
+	}
+}
+
+// Remove removes e from l.
+//
+//go:nosplit
+func (l *waiterList) Remove(e *Entry) {
+	linker := waiterElementMapper{}.linkerFor(e)
+	prev := linker.Prev()
+	next := linker.Next()
+
+	if prev != nil {
+		waiterElementMapper{}.linkerFor(prev).SetNext(next)
+	} else if l.head == e {
+		l.head = next
+	}
+
+	if next != nil {
+		waiterElementMapper{}.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 waiterEntry struct {
+	next *Entry
+	prev *Entry
+}
+
+// Next returns the entry that follows e in the list.
+//
+//go:nosplit
+func (e *waiterEntry) Next() *Entry {
+	return e.next
+}
+
+// Prev returns the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *waiterEntry) Prev() *Entry {
+	return e.prev
+}
+
+// SetNext assigns 'entry' as the entry that follows e in the list.
+//
+//go:nosplit
+func (e *waiterEntry) SetNext(elem *Entry) {
+	e.next = elem
+}
+
+// SetPrev assigns 'entry' as the entry that precedes e in the list.
+//
+//go:nosplit
+func (e *waiterEntry) SetPrev(elem *Entry) {
+	e.prev = elem
+}
diff --git a/pkg/waiter/waiter_state_autogen.go b/pkg/waiter/waiter_state_autogen.go
new file mode 100644
index 0000000..498b7e7
--- /dev/null
+++ b/pkg/waiter/waiter_state_autogen.go
@@ -0,0 +1,126 @@
+// automatically generated by stateify.
+
+package waiter
+
+import (
+	"gvisor.dev/gvisor/pkg/state"
+)
+
+func (e *Entry) StateTypeName() string {
+	return "pkg/waiter.Entry"
+}
+
+func (e *Entry) StateFields() []string {
+	return []string{
+		"Callback",
+		"mask",
+		"waiterEntry",
+	}
+}
+
+func (e *Entry) beforeSave() {}
+
+// +checklocksignore
+func (e *Entry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.Callback)
+	stateSinkObject.Save(1, &e.mask)
+	stateSinkObject.Save(2, &e.waiterEntry)
+}
+
+func (e *Entry) afterLoad() {}
+
+// +checklocksignore
+func (e *Entry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.Callback)
+	stateSourceObject.Load(1, &e.mask)
+	stateSourceObject.Load(2, &e.waiterEntry)
+}
+
+func (q *Queue) StateTypeName() string {
+	return "pkg/waiter.Queue"
+}
+
+func (q *Queue) StateFields() []string {
+	return []string{
+		"list",
+	}
+}
+
+func (q *Queue) beforeSave() {}
+
+// +checklocksignore
+func (q *Queue) StateSave(stateSinkObject state.Sink) {
+	q.beforeSave()
+	stateSinkObject.Save(0, &q.list)
+}
+
+func (q *Queue) afterLoad() {}
+
+// +checklocksignore
+func (q *Queue) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &q.list)
+}
+
+func (l *waiterList) StateTypeName() string {
+	return "pkg/waiter.waiterList"
+}
+
+func (l *waiterList) StateFields() []string {
+	return []string{
+		"head",
+		"tail",
+	}
+}
+
+func (l *waiterList) beforeSave() {}
+
+// +checklocksignore
+func (l *waiterList) StateSave(stateSinkObject state.Sink) {
+	l.beforeSave()
+	stateSinkObject.Save(0, &l.head)
+	stateSinkObject.Save(1, &l.tail)
+}
+
+func (l *waiterList) afterLoad() {}
+
+// +checklocksignore
+func (l *waiterList) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &l.head)
+	stateSourceObject.Load(1, &l.tail)
+}
+
+func (e *waiterEntry) StateTypeName() string {
+	return "pkg/waiter.waiterEntry"
+}
+
+func (e *waiterEntry) StateFields() []string {
+	return []string{
+		"next",
+		"prev",
+	}
+}
+
+func (e *waiterEntry) beforeSave() {}
+
+// +checklocksignore
+func (e *waiterEntry) StateSave(stateSinkObject state.Sink) {
+	e.beforeSave()
+	stateSinkObject.Save(0, &e.next)
+	stateSinkObject.Save(1, &e.prev)
+}
+
+func (e *waiterEntry) afterLoad() {}
+
+// +checklocksignore
+func (e *waiterEntry) StateLoad(stateSourceObject state.Source) {
+	stateSourceObject.Load(0, &e.next)
+	stateSourceObject.Load(1, &e.prev)
+}
+
+func init() {
+	state.Register((*Entry)(nil))
+	state.Register((*Queue)(nil))
+	state.Register((*waiterList)(nil))
+	state.Register((*waiterEntry)(nil))
+}
diff --git a/pkg/waiter/waiter_test.go b/pkg/waiter/waiter_test.go
deleted file mode 100644
index 6928f28..0000000
--- a/pkg/waiter/waiter_test.go
+++ /dev/null
@@ -1,198 +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 waiter
-
-import (
-	"sync/atomic"
-	"testing"
-)
-
-type callbackStub struct {
-	f func(e *Entry, m EventMask)
-}
-
-// Callback implements EntryCallback.Callback.
-func (c *callbackStub) Callback(e *Entry, m EventMask) {
-	c.f(e, m)
-}
-
-func TestEmptyQueue(t *testing.T) {
-	var q Queue
-
-	// Notify the zero-value of a queue.
-	q.Notify(EventIn)
-
-	// Register then unregister a waiter, then notify the queue.
-	cnt := 0
-	e := Entry{Callback: &callbackStub{func(*Entry, EventMask) { cnt++ }}}
-	q.EventRegister(&e, EventIn)
-	q.EventUnregister(&e)
-	q.Notify(EventIn)
-	if cnt != 0 {
-		t.Errorf("Callback was called when it shouldn't have been")
-	}
-}
-
-func TestMask(t *testing.T) {
-	// Register a waiter.
-	var q Queue
-	var cnt int
-	e := Entry{Callback: &callbackStub{func(*Entry, EventMask) { cnt++ }}}
-	q.EventRegister(&e, EventIn|EventErr)
-
-	// Notify with an overlapping mask.
-	cnt = 0
-	q.Notify(EventIn | EventOut)
-	if cnt != 1 {
-		t.Errorf("Callback wasn't called when it should have been")
-	}
-
-	// Notify with a subset mask.
-	cnt = 0
-	q.Notify(EventIn)
-	if cnt != 1 {
-		t.Errorf("Callback wasn't called when it should have been")
-	}
-
-	// Notify with a superset mask.
-	cnt = 0
-	q.Notify(EventIn | EventErr | EventOut)
-	if cnt != 1 {
-		t.Errorf("Callback wasn't called when it should have been")
-	}
-
-	// Notify with the exact same mask.
-	cnt = 0
-	q.Notify(EventIn | EventErr)
-	if cnt != 1 {
-		t.Errorf("Callback wasn't called when it should have been")
-	}
-
-	// Notify with a disjoint mask.
-	cnt = 0
-	q.Notify(EventOut | EventHUp)
-	if cnt != 0 {
-		t.Errorf("Callback was called when it shouldn't have been")
-	}
-}
-
-func TestConcurrentRegistration(t *testing.T) {
-	var q Queue
-	var cnt int
-	const concurrency = 1000
-
-	ch1 := make(chan struct{})
-	ch2 := make(chan struct{})
-	ch3 := make(chan struct{})
-
-	// Create goroutines that will all register/unregister concurrently.
-	for i := 0; i < concurrency; i++ {
-		go func() {
-			var e Entry
-			e.Callback = &callbackStub{func(entry *Entry, mask EventMask) {
-				cnt++
-				if entry != &e {
-					t.Errorf("entry = %p, want %p", entry, &e)
-				}
-				if mask != EventIn {
-					t.Errorf("mask = %#x want %#x", mask, EventIn)
-				}
-			}}
-
-			// Wait for notification, then register.
-			<-ch1
-			q.EventRegister(&e, EventIn|EventErr)
-
-			// Tell main goroutine that we're done registering.
-			ch2 <- struct{}{}
-
-			// Wait for notification, then unregister.
-			<-ch3
-			q.EventUnregister(&e)
-
-			// Tell main goroutine that we're done unregistering.
-			ch2 <- struct{}{}
-		}()
-	}
-
-	// Let the goroutines register.
-	close(ch1)
-	for i := 0; i < concurrency; i++ {
-		<-ch2
-	}
-
-	// Issue a notification.
-	q.Notify(EventIn)
-	if cnt != concurrency {
-		t.Errorf("cnt = %d, want %d", cnt, concurrency)
-	}
-
-	// Let the goroutine unregister.
-	close(ch3)
-	for i := 0; i < concurrency; i++ {
-		<-ch2
-	}
-
-	// Issue a notification.
-	q.Notify(EventIn)
-	if cnt != concurrency {
-		t.Errorf("cnt = %d, want %d", cnt, concurrency)
-	}
-}
-
-func TestConcurrentNotification(t *testing.T) {
-	var q Queue
-	var cnt int32
-	const concurrency = 1000
-	const waiterCount = 1000
-
-	// Register waiters.
-	for i := 0; i < waiterCount; i++ {
-		var e Entry
-		e.Callback = &callbackStub{func(entry *Entry, mask EventMask) {
-			atomic.AddInt32(&cnt, 1)
-			if entry != &e {
-				t.Errorf("entry = %p, want %p", entry, &e)
-			}
-			if mask != EventIn {
-				t.Errorf("mask = %#x want %#x", mask, EventIn)
-			}
-		}}
-
-		q.EventRegister(&e, EventIn|EventErr)
-	}
-
-	// Launch notifiers.
-	ch1 := make(chan struct{})
-	ch2 := make(chan struct{})
-	for i := 0; i < concurrency; i++ {
-		go func() {
-			<-ch1
-			q.Notify(EventIn)
-			ch2 <- struct{}{}
-		}()
-	}
-
-	// Let notifiers go.
-	close(ch1)
-	for i := 0; i < concurrency; i++ {
-		<-ch2
-	}
-
-	// Check the count.
-	if cnt != concurrency*waiterCount {
-		t.Errorf("cnt = %d, want %d", cnt, concurrency*waiterCount)
-	}
-}