Merge release-20210315.0-15-gacb4c6288 (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:        i