// 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 iptables_test

import (
	"bytes"
	"fmt"
	"math"
	"testing"

	"github.com/google/go-cmp/cmp"
	"gvisor.dev/gvisor/pkg/buffer"
	"gvisor.dev/gvisor/pkg/tcpip"
	"gvisor.dev/gvisor/pkg/tcpip/checker"
	"gvisor.dev/gvisor/pkg/tcpip/checksum"
	"gvisor.dev/gvisor/pkg/tcpip/header"
	"gvisor.dev/gvisor/pkg/tcpip/link/channel"
	"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
	"gvisor.dev/gvisor/pkg/tcpip/network/arp"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
	"gvisor.dev/gvisor/pkg/tcpip/prependable"
	"gvisor.dev/gvisor/pkg/tcpip/stack"
	"gvisor.dev/gvisor/pkg/tcpip/tests/utils"
	"gvisor.dev/gvisor/pkg/tcpip/testutil"
	"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
	"gvisor.dev/gvisor/pkg/waiter"
)

type inputIfNameMatcher struct {
	name string
}

var _ stack.Matcher = (*inputIfNameMatcher)(nil)

func (*inputIfNameMatcher) Name() string {
	return "inputIfNameMatcher"
}

func (im *inputIfNameMatcher) Match(hook stack.Hook, _ *stack.PacketBuffer, inNicName, _ string) (bool, bool) {
	return (hook == stack.Input && im.name != "" && im.name == inNicName), false
}

const (
	nicID          = 1
	nicName        = "nic1"
	anotherNicName = "nic2"
	linkAddr       = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
	payloadSize    = 20
)

var (
	srcAddrV4 = tcpip.AddrFromSlice([]byte("\x0a\x00\x00\x01"))
	dstAddrV4 = tcpip.AddrFromSlice([]byte("\x0a\x00\x00\x02"))
	srcAddrV6 = tcpip.AddrFromSlice([]byte("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"))
	dstAddrV6 = tcpip.AddrFromSlice([]byte("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"))
)

func genStackV6(t *testing.T) (*stack.Stack, *channel.Endpoint) {
	t.Helper()
	s := stack.New(stack.Options{
		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv6.NewProtocol},
		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
	})
	e := channel.New(0, header.IPv6MinimumMTU, linkAddr)
	nicOpts := stack.NICOptions{Name: nicName}
	if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
		t.Fatalf("CreateNICWithOptions(%d, _, %#v) = %s", nicID, nicOpts, err)
	}
	protocolAddr := tcpip.ProtocolAddress{
		Protocol:          header.IPv6ProtocolNumber,
		AddressWithPrefix: dstAddrV6.WithPrefix(),
	}
	if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
	}
	return s, e
}

func genStackV4(t *testing.T) (*stack.Stack, *channel.Endpoint) {
	t.Helper()
	s := stack.New(stack.Options{
		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol},
		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
	})
	e := channel.New(0, header.IPv4MinimumMTU, linkAddr)
	nicOpts := stack.NICOptions{Name: nicName}
	if err := s.CreateNICWithOptions(nicID, e, nicOpts); err != nil {
		t.Fatalf("CreateNICWithOptions(%d, _, %#v) = %s", nicID, nicOpts, err)
	}
	protocolAddr := tcpip.ProtocolAddress{
		Protocol:          header.IPv4ProtocolNumber,
		AddressWithPrefix: dstAddrV4.WithPrefix(),
	}
	if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
		t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
	}
	return s, e
}

func genPacketV6() *stack.PacketBuffer {
	pktSize := header.IPv6MinimumSize + payloadSize
	hdr := prependable.New(pktSize)
	ip := header.IPv6(hdr.Prepend(pktSize))
	ip.Encode(&header.IPv6Fields{
		PayloadLength:     payloadSize,
		TransportProtocol: 99,
		HopLimit:          255,
		SrcAddr:           srcAddrV6,
		DstAddr:           dstAddrV6,
	})
	buf := buffer.MakeWithData(hdr.View())
	return stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buf})
}

func genPacketV4() *stack.PacketBuffer {
	pktSize := header.IPv4MinimumSize + payloadSize
	hdr := prependable.New(pktSize)
	ip := header.IPv4(hdr.Prepend(pktSize))
	ip.Encode(&header.IPv4Fields{
		TOS:            0,
		TotalLength:    uint16(pktSize),
		ID:             1,
		Flags:          0,
		FragmentOffset: 16,
		TTL:            48,
		Protocol:       99,
		SrcAddr:        srcAddrV4,
		DstAddr:        dstAddrV4,
	})
	ip.SetChecksum(0)
	ip.SetChecksum(^ip.CalculateChecksum())
	buf := buffer.MakeWithData(hdr.View())
	return stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buf})
}

func TestIPTablesStatsForInput(t *testing.T) {
	tests := []struct {
		name               string
		setupStack         func(*testing.T) (*stack.Stack, *channel.Endpoint)
		setupFilter        func(*testing.T, *stack.Stack)
		genPacket          func() *stack.PacketBuffer
		proto              tcpip.NetworkProtocolNumber
		expectReceived     int
		expectInputDropped int
	}{
		{
			name:               "IPv6 Accept",
			setupStack:         genStackV6,
			setupFilter:        func(*testing.T, *stack.Stack) { /* no filter */ },
			genPacket:          genPacketV6,
			proto:              header.IPv6ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 0,
		},
		{
			name:               "IPv4 Accept",
			setupStack:         genStackV4,
			setupFilter:        func(*testing.T, *stack.Stack) { /* no filter */ },
			genPacket:          genPacketV4,
			proto:              header.IPv4ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 0,
		},
		{
			name:       "IPv6 Drop (input interface matches)",
			setupStack: genStackV6,
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, true /* ipv6 */)
				ruleIdx := filter.BuiltinChains[stack.Input]
				filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{InputInterface: nicName}
				filter.Rules[ruleIdx].Target = &stack.DropTarget{}
				filter.Rules[ruleIdx].Matchers = []stack.Matcher{&inputIfNameMatcher{nicName}}
				// Make sure the packet is not dropped by the next rule.
				filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
				ipt.ForceReplaceTable(stack.FilterID, filter, true /* ipv6 */)
			},
			genPacket:          genPacketV6,
			proto:              header.IPv6ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 1,
		},
		{
			name:       "IPv4 Drop (input interface matches)",
			setupStack: genStackV4,
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, false /* ipv6 */)
				ruleIdx := filter.BuiltinChains[stack.Input]
				filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{InputInterface: nicName}
				filter.Rules[ruleIdx].Target = &stack.DropTarget{}
				filter.Rules[ruleIdx].Matchers = []stack.Matcher{&inputIfNameMatcher{nicName}}
				filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
				ipt.ForceReplaceTable(stack.FilterID, filter, false /* ipv6 */)
			},
			genPacket:          genPacketV4,
			proto:              header.IPv4ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 1,
		},
		{
			name:       "IPv6 Accept (input interface does not match)",
			setupStack: genStackV6,
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, true /* ipv6 */)
				ruleIdx := filter.BuiltinChains[stack.Input]
				filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{InputInterface: anotherNicName}
				filter.Rules[ruleIdx].Target = &stack.DropTarget{}
				filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
				ipt.ForceReplaceTable(stack.FilterID, filter, true /* ipv6 */)
			},
			genPacket:          genPacketV6,
			proto:              header.IPv6ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 0,
		},
		{
			name:       "IPv4 Accept (input interface does not match)",
			setupStack: genStackV4,
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, false /* ipv6 */)
				ruleIdx := filter.BuiltinChains[stack.Input]
				filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{InputInterface: anotherNicName}
				filter.Rules[ruleIdx].Target = &stack.DropTarget{}
				filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
				ipt.ForceReplaceTable(stack.FilterID, filter, false /* ipv6 */)
			},
			genPacket:          genPacketV4,
			proto:              header.IPv4ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 0,
		},
		{
			name:       "IPv6 Drop (input interface does not match but invert is true)",
			setupStack: genStackV6,
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, true /* ipv6 */)
				ruleIdx := filter.BuiltinChains[stack.Input]
				filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{
					InputInterface:       anotherNicName,
					InputInterfaceInvert: true,
				}
				filter.Rules[ruleIdx].Target = &stack.DropTarget{}
				filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
				ipt.ForceReplaceTable(stack.FilterID, filter, true /* ipv6 */)
			},
			genPacket:          genPacketV6,
			proto:              header.IPv6ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 1,
		},
		{
			name:       "IPv4 Drop (input interface does not match but invert is true)",
			setupStack: genStackV4,
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, false /* ipv6 */)
				ruleIdx := filter.BuiltinChains[stack.Input]
				filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{
					InputInterface:       anotherNicName,
					InputInterfaceInvert: true,
				}
				filter.Rules[ruleIdx].Target = &stack.DropTarget{}
				filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
				ipt.ForceReplaceTable(stack.FilterID, filter, false /* ipv6 */)
			},
			genPacket:          genPacketV4,
			proto:              header.IPv4ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 1,
		},
		{
			name:       "IPv6 Accept (input interface does not match using a matcher)",
			setupStack: genStackV6,
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, true /* ipv6 */)
				ruleIdx := filter.BuiltinChains[stack.Input]
				filter.Rules[ruleIdx].Target = &stack.DropTarget{}
				filter.Rules[ruleIdx].Matchers = []stack.Matcher{&inputIfNameMatcher{anotherNicName}}
				filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
				ipt.ForceReplaceTable(stack.FilterID, filter, true /* ipv6 */)
			},
			genPacket:          genPacketV6,
			proto:              header.IPv6ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 0,
		},
		{
			name:       "IPv4 Accept (input interface does not match using a matcher)",
			setupStack: genStackV4,
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, false /* ipv6 */)
				ruleIdx := filter.BuiltinChains[stack.Input]
				filter.Rules[ruleIdx].Target = &stack.DropTarget{}
				filter.Rules[ruleIdx].Matchers = []stack.Matcher{&inputIfNameMatcher{anotherNicName}}
				filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
				ipt.ForceReplaceTable(stack.FilterID, filter, false /* ipv6 */)
			},
			genPacket:          genPacketV4,
			proto:              header.IPv4ProtocolNumber,
			expectReceived:     1,
			expectInputDropped: 0,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s, e := test.setupStack(t)
			defer s.Destroy()
			test.setupFilter(t, s)
			e.InjectInbound(test.proto, test.genPacket())

			if got := int(s.Stats().IP.PacketsReceived.Value()); got != test.expectReceived {
				t.Errorf("got PacketReceived = %d, want = %d", got, test.expectReceived)
			}
			if got := int(s.Stats().IP.IPTablesInputDropped.Value()); got != test.expectInputDropped {
				t.Errorf("got IPTablesInputDropped = %d, want = %d", got, test.expectInputDropped)
			}
		})
	}
}

var _ stack.LinkEndpoint = (*channelEndpoint)(nil)

type channelEndpoint struct {
	*channel.Endpoint

	t *testing.T
}

var _ stack.Matcher = (*udpSourcePortMatcher)(nil)

type udpSourcePortMatcher struct {
	port uint16
}

func (*udpSourcePortMatcher) Name() string {
	return "udpSourcePortMatcher"
}

func (m *udpSourcePortMatcher) Match(_ stack.Hook, pkt *stack.PacketBuffer, _, _ string) (matches, hotdrop bool) {
	udp := header.UDP(pkt.TransportHeader().Slice())
	if len(udp) < header.UDPMinimumSize {
		// Drop immediately as the packet is invalid.
		return false, true
	}

	return udp.SourcePort() == m.port, false
}

func TestIPTableWritePackets(t *testing.T) {
	const (
		nicID = 1

		dropLocalPort = utils.LocalPort - 1
		acceptPackets = 2
		dropPackets   = 3
	)

	udpHdr := func(hdr []byte, srcAddr, dstAddr tcpip.Address, srcPort, dstPort uint16) {
		u := header.UDP(hdr)
		u.Encode(&header.UDPFields{
			SrcPort: srcPort,
			DstPort: dstPort,
			Length:  header.UDPMinimumSize,
		})
		sum := header.PseudoHeaderChecksum(udp.ProtocolNumber, srcAddr, dstAddr, header.UDPMinimumSize)
		sum = checksum.Checksum(hdr, sum)
		u.SetChecksum(^u.CalculateChecksum(sum))
	}

	tests := []struct {
		name                string
		setupFilter         func(*testing.T, *stack.Stack)
		genPacket           func(*stack.Route) stack.PacketBufferList
		proto               tcpip.NetworkProtocolNumber
		remoteAddr          tcpip.Address
		expectSent          uint64
		expectOutputDropped uint64
	}{
		{
			name:        "IPv4 Accept",
			setupFilter: func(*testing.T, *stack.Stack) { /* no filter */ },
			genPacket: func(r *stack.Route) stack.PacketBufferList {
				var pkts stack.PacketBufferList

				pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
					ReserveHeaderBytes: int(r.MaxHeaderLength() + header.UDPMinimumSize),
				})
				hdr := pkt.TransportHeader().Push(header.UDPMinimumSize)
				udpHdr(hdr, r.LocalAddress(), r.RemoteAddress(), utils.LocalPort, utils.RemotePort)
				pkts.PushBack(pkt)

				return pkts
			},
			proto:               header.IPv4ProtocolNumber,
			remoteAddr:          dstAddrV4,
			expectSent:          1,
			expectOutputDropped: 0,
		},
		{
			name: "IPv4 Drop Other Port",
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()

				table := stack.Table{
					Rules: []stack.Rule{
						{
							Target: &stack.AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber},
						},
						{
							Target: &stack.AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber},
						},
						{
							Matchers: []stack.Matcher{&udpSourcePortMatcher{port: dropLocalPort}},
							Target:   &stack.DropTarget{NetworkProtocol: header.IPv4ProtocolNumber},
						},
						{
							Target: &stack.AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber},
						},
						{
							Target: &stack.ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber},
						},
					},
					BuiltinChains: [stack.NumHooks]int{
						stack.Prerouting:  stack.HookUnset,
						stack.Input:       0,
						stack.Forward:     1,
						stack.Output:      2,
						stack.Postrouting: stack.HookUnset,
					},
					Underflows: [stack.NumHooks]int{
						stack.Prerouting:  stack.HookUnset,
						stack.Input:       0,
						stack.Forward:     1,
						stack.Output:      2,
						stack.Postrouting: stack.HookUnset,
					},
				}

				s.IPTables().ForceReplaceTable(stack.FilterID, table, false /* ipv4 */)
			},
			genPacket: func(r *stack.Route) stack.PacketBufferList {
				var pkts stack.PacketBufferList

				for i := 0; i < acceptPackets; i++ {
					pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
						ReserveHeaderBytes: int(r.MaxHeaderLength() + header.UDPMinimumSize),
					})
					hdr := pkt.TransportHeader().Push(header.UDPMinimumSize)
					udpHdr(hdr, r.LocalAddress(), r.RemoteAddress(), utils.LocalPort, utils.RemotePort)
					pkts.PushBack(pkt)
				}
				for i := 0; i < dropPackets; i++ {
					pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
						ReserveHeaderBytes: int(r.MaxHeaderLength() + header.UDPMinimumSize),
					})
					hdr := pkt.TransportHeader().Push(header.UDPMinimumSize)
					udpHdr(hdr, r.LocalAddress(), r.RemoteAddress(), dropLocalPort, utils.RemotePort)
					pkts.PushBack(pkt)
				}

				return pkts
			},
			proto:               header.IPv4ProtocolNumber,
			remoteAddr:          dstAddrV4,
			expectSent:          acceptPackets,
			expectOutputDropped: dropPackets,
		},
		{
			name:        "IPv6 Accept",
			setupFilter: func(*testing.T, *stack.Stack) { /* no filter */ },
			genPacket: func(r *stack.Route) stack.PacketBufferList {
				var pkts stack.PacketBufferList

				pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
					ReserveHeaderBytes: int(r.MaxHeaderLength() + header.UDPMinimumSize),
				})
				hdr := pkt.TransportHeader().Push(header.UDPMinimumSize)
				udpHdr(hdr, r.LocalAddress(), r.RemoteAddress(), utils.LocalPort, utils.RemotePort)
				pkts.PushBack(pkt)

				return pkts
			},
			proto:               header.IPv6ProtocolNumber,
			remoteAddr:          dstAddrV6,
			expectSent:          1,
			expectOutputDropped: 0,
		},
		{
			name: "IPv6 Drop Other Port",
			setupFilter: func(t *testing.T, s *stack.Stack) {
				t.Helper()

				table := stack.Table{
					Rules: []stack.Rule{
						{
							Target: &stack.AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber},
						},
						{
							Target: &stack.AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber},
						},
						{
							Matchers: []stack.Matcher{&udpSourcePortMatcher{port: dropLocalPort}},
							Target:   &stack.DropTarget{NetworkProtocol: header.IPv6ProtocolNumber},
						},
						{
							Target: &stack.AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber},
						},
						{
							Target: &stack.ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber},
						},
					},
					BuiltinChains: [stack.NumHooks]int{
						stack.Prerouting:  stack.HookUnset,
						stack.Input:       0,
						stack.Forward:     1,
						stack.Output:      2,
						stack.Postrouting: stack.HookUnset,
					},
					Underflows: [stack.NumHooks]int{
						stack.Prerouting:  stack.HookUnset,
						stack.Input:       0,
						stack.Forward:     1,
						stack.Output:      2,
						stack.Postrouting: stack.HookUnset,
					},
				}

				s.IPTables().ForceReplaceTable(stack.FilterID, table, true /* ipv6 */)
			},
			genPacket: func(r *stack.Route) stack.PacketBufferList {
				var pkts stack.PacketBufferList

				for i := 0; i < acceptPackets; i++ {
					pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
						ReserveHeaderBytes: int(r.MaxHeaderLength() + header.UDPMinimumSize),
					})
					hdr := pkt.TransportHeader().Push(header.UDPMinimumSize)
					udpHdr(hdr, r.LocalAddress(), r.RemoteAddress(), utils.LocalPort, utils.RemotePort)
					pkts.PushBack(pkt)
				}
				for i := 0; i < dropPackets; i++ {
					pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
						ReserveHeaderBytes: int(r.MaxHeaderLength() + header.UDPMinimumSize),
					})
					hdr := pkt.TransportHeader().Push(header.UDPMinimumSize)
					udpHdr(hdr, r.LocalAddress(), r.RemoteAddress(), dropLocalPort, utils.RemotePort)
					pkts.PushBack(pkt)
				}

				return pkts
			},
			proto:               header.IPv6ProtocolNumber,
			remoteAddr:          dstAddrV6,
			expectSent:          acceptPackets,
			expectOutputDropped: dropPackets,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
			})
			defer s.Destroy()
			e := channelEndpoint{
				Endpoint: channel.New(4, header.IPv6MinimumMTU, linkAddr),
				t:        t,
			}
			if err := s.CreateNIC(nicID, &e); err != nil {
				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
			}
			protocolAddrV6 := tcpip.ProtocolAddress{
				Protocol:          header.IPv6ProtocolNumber,
				AddressWithPrefix: srcAddrV6.WithPrefix(),
			}
			if err := s.AddProtocolAddress(nicID, protocolAddrV6, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddrV6, err)
			}
			protocolAddrV4 := tcpip.ProtocolAddress{
				Protocol:          header.IPv4ProtocolNumber,
				AddressWithPrefix: srcAddrV4.WithPrefix(),
			}
			if err := s.AddProtocolAddress(nicID, protocolAddrV4, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddrV4, err)
			}

			s.SetRouteTable([]tcpip.Route{
				{
					Destination: header.IPv4EmptySubnet,
					NIC:         nicID,
				},
				{
					Destination: header.IPv6EmptySubnet,
					NIC:         nicID,
				},
			})

			test.setupFilter(t, s)

			r, err := s.FindRoute(nicID, tcpip.Address{}, test.remoteAddr, test.proto, false)
			if err != nil {
				t.Fatalf("FindRoute(%d, '', %s, %d, false): %s", nicID, test.remoteAddr, test.proto, err)
			}
			defer r.Release()

			pkts := test.genPacket(r)
			for _, pkt := range pkts.AsSlice() {
				if err := r.WritePacket(stack.NetworkHeaderParams{
					Protocol: header.UDPProtocolNumber,
					TTL:      64,
				}, pkt); err != nil {
					t.Fatalf("WritePacket(...): %s", err)
				}
				pkt.DecRef()
			}

			if got := s.Stats().IP.PacketsSent.Value(); got != test.expectSent {
				t.Errorf("got PacketSent = %d, want = %d", got, test.expectSent)
			}
			if got := s.Stats().IP.IPTablesOutputDropped.Value(); got != test.expectOutputDropped {
				t.Errorf("got IPTablesOutputDropped = %d, want = %d", got, test.expectOutputDropped)
			}
		})
	}
}

const ttl = 64

var (
	ipv4GlobalMulticastAddr = testutil.MustParse4("224.0.1.10")
	ipv6GlobalMulticastAddr = testutil.MustParse6("ff0e::a")
)

func rxICMPv4EchoReply(e *channel.Endpoint, src, dst tcpip.Address) {
	utils.RxICMPv4EchoReply(e, src, dst, ttl)
}

func rxICMPv6EchoReply(e *channel.Endpoint, src, dst tcpip.Address) {
	utils.RxICMPv6EchoReply(e, src, dst, ttl)
}

func forwardedICMPv4EchoReplyChecker(t *testing.T, v *buffer.View, src, dst tcpip.Address) {
	checker.IPv4(t, v,
		checker.SrcAddr(src),
		checker.DstAddr(dst),
		checker.TTL(ttl-1),
		checker.ICMPv4(
			checker.ICMPv4Type(header.ICMPv4EchoReply)))
}

func forwardedICMPv6EchoReplyChecker(t *testing.T, v *buffer.View, src, dst tcpip.Address) {
	checker.IPv6(t, v,
		checker.SrcAddr(src),
		checker.DstAddr(dst),
		checker.TTL(ttl-1),
		checker.ICMPv6(
			checker.ICMPv6Type(header.ICMPv6EchoReply)))
}

func boolToInt(v bool) uint64 {
	if v {
		return 1
	}
	return 0
}

func setupDropFilter(hook stack.Hook, f stack.IPHeaderFilter) func(*testing.T, *stack.Stack, tcpip.NetworkProtocolNumber) {
	return func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber) {
		t.Helper()

		ipv6 := netProto == ipv6.ProtocolNumber

		ipt := s.IPTables()
		filter := ipt.GetTable(stack.FilterID, ipv6)
		ruleIdx := filter.BuiltinChains[hook]
		filter.Rules[ruleIdx].Filter = f
		filter.Rules[ruleIdx].Target = &stack.DropTarget{NetworkProtocol: netProto}
		// Make sure the packet is not dropped by the next rule.
		filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{NetworkProtocol: netProto}
		ipt.ForceReplaceTable(stack.FilterID, filter, ipv6)
	}
}

func TestForwardingHook(t *testing.T) {
	const (
		nicID1 = 1
		nicID2 = 2

		nic1Name = "nic1"
		nic2Name = "nic2"

		otherNICName = "otherNIC"
	)

	tests := []struct {
		name             string
		netProto         tcpip.NetworkProtocolNumber
		local            bool
		srcAddr, dstAddr tcpip.Address
		rx               func(*channel.Endpoint, tcpip.Address, tcpip.Address)
		checker          func(*testing.T, *buffer.View)
	}{
		{
			name:     "IPv4 remote",
			netProto: ipv4.ProtocolNumber,
			local:    false,
			srcAddr:  utils.RemoteIPv4Addr,
			dstAddr:  utils.Ipv4Addr2.AddressWithPrefix.Address,
			rx:       rxICMPv4EchoReply,
			checker: func(t *testing.T, v *buffer.View) {
				forwardedICMPv4EchoReplyChecker(t, v, utils.RemoteIPv4Addr, utils.Ipv4Addr2.AddressWithPrefix.Address)
			},
		},
		{
			name:     "IPv4 local",
			netProto: ipv4.ProtocolNumber,
			local:    true,
			srcAddr:  utils.RemoteIPv4Addr,
			dstAddr:  utils.Ipv4Addr.Address,
			rx:       rxICMPv4EchoReply,
		},
		{
			name:     "IPv6 remote",
			netProto: ipv6.ProtocolNumber,
			local:    false,
			srcAddr:  utils.RemoteIPv6Addr,
			dstAddr:  utils.Ipv6Addr2.AddressWithPrefix.Address,
			rx:       rxICMPv6EchoReply,
			checker: func(t *testing.T, v *buffer.View) {
				forwardedICMPv6EchoReplyChecker(t, v, utils.RemoteIPv6Addr, utils.Ipv6Addr2.AddressWithPrefix.Address)
			},
		},
		{
			name:     "IPv6 local",
			netProto: ipv6.ProtocolNumber,
			local:    true,
			srcAddr:  utils.RemoteIPv6Addr,
			dstAddr:  utils.Ipv6Addr.Address,
			rx:       rxICMPv6EchoReply,
		},
	}

	subTests := []struct {
		name          string
		setupFilter   func(*testing.T, *stack.Stack, tcpip.NetworkProtocolNumber)
		expectForward bool
	}{
		{
			name:          "Accept",
			setupFilter:   func(*testing.T, *stack.Stack, tcpip.NetworkProtocolNumber) { /* no filter */ },
			expectForward: true,
		},

		{
			name:          "Drop",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{}),
			expectForward: false,
		},
		{
			name:          "Drop with input NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{InputInterface: nic1Name}),
			expectForward: false,
		},
		{
			name:          "Drop with output NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{OutputInterface: nic2Name}),
			expectForward: false,
		},
		{
			name:          "Drop with input and output NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{InputInterface: nic1Name, OutputInterface: nic2Name}),
			expectForward: false,
		},

		{
			name:          "Drop with other input NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{InputInterface: otherNICName}),
			expectForward: true,
		},
		{
			name:          "Drop with other output NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{OutputInterface: otherNICName}),
			expectForward: true,
		},
		{
			name:          "Drop with other input and output NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{InputInterface: otherNICName, OutputInterface: nic2Name}),
			expectForward: true,
		},
		{
			name:          "Drop with input and other output NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{InputInterface: nic1Name, OutputInterface: otherNICName}),
			expectForward: true,
		},
		{
			name:          "Drop with other input and other output NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{InputInterface: otherNICName, OutputInterface: otherNICName}),
			expectForward: true,
		},

		{
			name:          "Drop with inverted input NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{InputInterface: nic1Name, InputInterfaceInvert: true}),
			expectForward: true,
		},
		{
			name:          "Drop with inverted output NIC filtering",
			setupFilter:   setupDropFilter(stack.Forward, stack.IPHeaderFilter{OutputInterface: nic2Name, OutputInterfaceInvert: true}),
			expectForward: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, subTest := range subTests {
				t.Run(subTest.name, func(t *testing.T) {
					s := stack.New(stack.Options{
						NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
					})
					defer s.Destroy()

					subTest.setupFilter(t, s, test.netProto)

					e1 := channel.New(1, header.IPv6MinimumMTU, "")
					if err := s.CreateNICWithOptions(nicID1, e1, stack.NICOptions{Name: nic1Name}); err != nil {
						t.Fatalf("s.CreateNICWithOptions(%d, _, _): %s", nicID1, err)
					}

					e2 := channel.New(1, header.IPv6MinimumMTU, "")
					if err := s.CreateNICWithOptions(nicID2, e2, stack.NICOptions{Name: nic2Name}); err != nil {
						t.Fatalf("s.CreateNICWithOptions(%d, _, _): %s", nicID2, err)
					}

					protocolAddrV4 := tcpip.ProtocolAddress{
						Protocol:          ipv4.ProtocolNumber,
						AddressWithPrefix: utils.Ipv4Addr.Address.WithPrefix(),
					}
					if err := s.AddProtocolAddress(nicID2, protocolAddrV4, stack.AddressProperties{}); err != nil {
						t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddrV4, err)
					}
					protocolAddrV6 := tcpip.ProtocolAddress{
						Protocol:          ipv6.ProtocolNumber,
						AddressWithPrefix: utils.Ipv6Addr.Address.WithPrefix(),
					}
					if err := s.AddProtocolAddress(nicID2, protocolAddrV6, stack.AddressProperties{}); err != nil {
						t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID2, protocolAddrV6, err)
					}

					if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil {
						t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv4.ProtocolNumber, err)
					}
					if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil {
						t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err)
					}

					s.SetRouteTable([]tcpip.Route{
						{
							Destination: header.IPv4EmptySubnet,
							NIC:         nicID2,
						},
						{
							Destination: header.IPv6EmptySubnet,
							NIC:         nicID2,
						},
					})

					test.rx(e1, test.srcAddr, test.dstAddr)

					expectTransmitPacket := subTest.expectForward && !test.local

					ep1, err := s.GetNetworkEndpoint(nicID1, test.netProto)
					if err != nil {
						t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID1, test.netProto, err)
					}
					ep1Stats := ep1.Stats()
					ipEP1Stats, ok := ep1Stats.(stack.IPNetworkEndpointStats)
					if !ok {
						t.Fatalf("got ep1Stats = %T, want = stack.IPNetworkEndpointStats", ep1Stats)
					}
					ip1Stats := ipEP1Stats.IPStats()

					if got := ip1Stats.PacketsReceived.Value(); got != 1 {
						t.Errorf("got ip1Stats.PacketsReceived.Value() = %d, want = 1", got)
					}
					if got := ip1Stats.ValidPacketsReceived.Value(); got != 1 {
						t.Errorf("got ip1Stats.ValidPacketsReceived.Value() = %d, want = 1", got)
					}
					if got, want := ip1Stats.IPTablesForwardDropped.Value(), boolToInt(!subTest.expectForward); got != want {
						t.Errorf("got ip1Stats.IPTablesForwardDropped.Value() = %d, want = %d", got, want)
					}
					if got := ip1Stats.PacketsSent.Value(); got != 0 {
						t.Errorf("got ip1Stats.PacketsSent.Value() = %d, want = 0", got)
					}

					ep2, err := s.GetNetworkEndpoint(nicID2, test.netProto)
					if err != nil {
						t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID2, test.netProto, err)
					}
					ep2Stats := ep2.Stats()
					ipEP2Stats, ok := ep2Stats.(stack.IPNetworkEndpointStats)
					if !ok {
						t.Fatalf("got ep2Stats = %T, want = stack.IPNetworkEndpointStats", ep2Stats)
					}
					ip2Stats := ipEP2Stats.IPStats()
					if got := ip2Stats.PacketsReceived.Value(); got != 0 {
						t.Errorf("got ip2Stats.PacketsReceived.Value() = %d, want = 0", got)
					}
					if got, want := ip2Stats.ValidPacketsReceived.Value(), boolToInt(subTest.expectForward && test.local); got != want {
						t.Errorf("got ip2Stats.ValidPacketsReceived.Value() = %d, want = %d", got, want)
					}
					if got, want := ip2Stats.PacketsSent.Value(), boolToInt(expectTransmitPacket); got != want {
						t.Errorf("got ip2Stats.PacketsSent.Value() = %d, want = %d", got, want)
					}

					p := e2.Read()
					if (p != nil) != expectTransmitPacket {
						t.Fatalf("got e2.Read() = %#v, want = (_ == nil) = %t", p, expectTransmitPacket)
					}
					if expectTransmitPacket {
						payload := stack.PayloadSince(p.NetworkHeader())
						defer payload.Release()
						test.checker(t, payload)
						p.DecRef()
					}
				})
			}
		})
	}
}

func TestFilteringEchoPacketsWithLocalForwarding(t *testing.T) {
	const (
		nicID1 = 1
		nicID2 = 2

		nic1Name = "nic1"
		nic2Name = "nic2"

		otherNICName = "otherNIC"
	)

	tests := []struct {
		name     string
		netProto tcpip.NetworkProtocolNumber
		rx       func(*channel.Endpoint)
		checker  func(*testing.T, *buffer.View)
	}{
		{
			name:     "IPv4",
			netProto: ipv4.ProtocolNumber,
			rx: func(e *channel.Endpoint) {
				utils.RxICMPv4EchoRequest(e, utils.RemoteIPv4Addr, utils.Ipv4Addr2.AddressWithPrefix.Address, ttl)
			},
			checker: func(t *testing.T, v *buffer.View) {
				checker.IPv4(t, v,
					checker.SrcAddr(utils.Ipv4Addr2.AddressWithPrefix.Address),
					checker.DstAddr(utils.RemoteIPv4Addr),
					checker.ICMPv4(
						checker.ICMPv4Type(header.ICMPv4EchoReply)))
			},
		},
		{
			name:     "IPv6",
			netProto: ipv6.ProtocolNumber,
			rx: func(e *channel.Endpoint) {
				utils.RxICMPv6EchoRequest(e, utils.RemoteIPv6Addr, utils.Ipv6Addr2.AddressWithPrefix.Address, ttl)
			},
			checker: func(t *testing.T, v *buffer.View) {
				checker.IPv6(t, v,
					checker.SrcAddr(utils.Ipv6Addr2.AddressWithPrefix.Address),
					checker.DstAddr(utils.RemoteIPv6Addr),
					checker.ICMPv6(
						checker.ICMPv6Type(header.ICMPv6EchoReply)))
			},
		},
	}

	type droppedEcho int
	const (
		_ droppedEcho = iota
		noneDropped
		echoRequestDroppedAtInput
		echoRequestDroppedAtForward
		echoReplyDropped
	)

	subTests := []struct {
		name         string
		setupFilter  func(*testing.T, *stack.Stack, tcpip.NetworkProtocolNumber)
		expectResult droppedEcho
	}{
		{
			name:         "Accept",
			setupFilter:  func(*testing.T, *stack.Stack, tcpip.NetworkProtocolNumber) { /* no filter */ },
			expectResult: noneDropped,
		},

		{
			name:         "Input Drop",
			setupFilter:  setupDropFilter(stack.Input, stack.IPHeaderFilter{}),
			expectResult: echoRequestDroppedAtInput,
		},
		{
			name:         "Input Drop with input NIC filtering on arrival NIC",
			setupFilter:  setupDropFilter(stack.Input, stack.IPHeaderFilter{InputInterface: nic1Name}),
			expectResult: echoRequestDroppedAtInput,
		},
		{
			name:         "Input Drop with input NIC filtering on delivered NIC",
			setupFilter:  setupDropFilter(stack.Input, stack.IPHeaderFilter{InputInterface: nic2Name}),
			expectResult: noneDropped,
		},

		{
			name:         "Input Drop with input NIC filtering on other NIC",
			setupFilter:  setupDropFilter(stack.Input, stack.IPHeaderFilter{InputInterface: otherNICName}),
			expectResult: noneDropped,
		},

		{
			name:         "Forward Drop",
			setupFilter:  setupDropFilter(stack.Forward, stack.IPHeaderFilter{}),
			expectResult: echoRequestDroppedAtForward,
		},

		{
			name:         "Output Drop",
			setupFilter:  setupDropFilter(stack.Output, stack.IPHeaderFilter{}),
			expectResult: echoReplyDropped,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, subTest := range subTests {
				t.Run(subTest.name, func(t *testing.T) {
					s := stack.New(stack.Options{
						NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
					})
					defer s.Destroy()

					subTest.setupFilter(t, s, test.netProto)

					e1 := channel.New(1, header.IPv6MinimumMTU, "")
					if err := s.CreateNICWithOptions(nicID1, e1, stack.NICOptions{Name: nic1Name}); err != nil {
						t.Fatalf("s.CreateNICWithOptions(%d, _, _): %s", nicID1, err)
					}
					if err := s.AddProtocolAddress(nicID1, utils.Ipv4Addr1, stack.AddressProperties{}); err != nil {
						t.Fatalf("s.AddProtocolAddress(%d, %+v, {}): %s", nicID1, utils.Ipv4Addr1, err)
					}
					if err := s.AddProtocolAddress(nicID1, utils.Ipv6Addr1, stack.AddressProperties{}); err != nil {
						t.Fatalf("s.AddProtocolAddress(%d, %+v, {}): %s", nicID1, utils.Ipv6Addr1, err)
					}

					e2 := channel.New(1, header.IPv6MinimumMTU, "")
					if err := s.CreateNICWithOptions(nicID2, e2, stack.NICOptions{Name: nic2Name}); err != nil {
						t.Fatalf("s.CreateNICWithOptions(%d, _, _): %s", nicID2, err)
					}
					if err := s.AddProtocolAddress(nicID2, utils.Ipv4Addr2, stack.AddressProperties{}); err != nil {
						t.Fatalf("s.AddProtocolAddress(%d, %+v, {}): %s", nicID2, utils.Ipv4Addr2, err)
					}
					if err := s.AddProtocolAddress(nicID2, utils.Ipv6Addr2, stack.AddressProperties{}); err != nil {
						t.Fatalf("s.AddProtocolAddress(%d, %+v, {}): %s", nicID2, utils.Ipv6Addr2, err)
					}

					if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil {
						t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv4.ProtocolNumber, err)
					}
					if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil {
						t.Fatalf("s.SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err)
					}

					s.SetRouteTable([]tcpip.Route{
						{
							Destination: header.IPv4EmptySubnet,
							NIC:         nicID1,
						},
						{
							Destination: header.IPv6EmptySubnet,
							NIC:         nicID1,
						},
					})

					test.rx(e1)

					ep1, err := s.GetNetworkEndpoint(nicID1, test.netProto)
					if err != nil {
						t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID1, test.netProto, err)
					}
					ep1Stats := ep1.Stats()
					ipEP1Stats, ok := ep1Stats.(stack.IPNetworkEndpointStats)
					if !ok {
						t.Fatalf("got ep1Stats = %T, want = stack.IPNetworkEndpointStats", ep1Stats)
					}
					ip1Stats := ipEP1Stats.IPStats()

					if got := ip1Stats.PacketsReceived.Value(); got != 1 {
						t.Errorf("got ip1Stats.PacketsReceived.Value() = %d, want = 1", got)
					}
					if got := ip1Stats.ValidPacketsReceived.Value(); got != 1 {
						t.Errorf("got ip1Stats.ValidPacketsReceived.Value() = %d, want = 1", got)
					}

					expectedIP1StatIPTablesForawrdDropped := uint64(0)
					expectedIP1StatIPTablesOutputDropped := uint64(0)
					expectedIP1StatPacketsSent := uint64(0)
					expectedIP2StatValidPacketsReceived := uint64(1)
					expectedIP2StatIPTablesInputDropped := uint64(0)
					switch subTest.expectResult {
					case noneDropped:
						expectedIP1StatPacketsSent = 1
					case echoRequestDroppedAtInput:
						expectedIP2StatIPTablesInputDropped = 1
					case echoRequestDroppedAtForward:
						expectedIP1StatIPTablesForawrdDropped = 1
						expectedIP2StatValidPacketsReceived = 0
					case echoReplyDropped:
						expectedIP1StatIPTablesOutputDropped = 1
					default:
						t.Fatalf("unhandled expectResult = %d", subTest.expectResult)
					}

					if got := ip1Stats.IPTablesForwardDropped.Value(); got != expectedIP1StatIPTablesForawrdDropped {
						t.Errorf("got ip1Stats.IPTablesForwardDropped.Value() = %d, want = %d", got, expectedIP1StatIPTablesForawrdDropped)
					}
					if got := ip1Stats.IPTablesOutputDropped.Value(); got != expectedIP1StatIPTablesOutputDropped {
						t.Errorf("got ip1Stats.IPTablesOutputDropped.Value() = %d, want = %d", got, expectedIP1StatIPTablesOutputDropped)
					}
					if got := ip1Stats.PacketsSent.Value(); got != expectedIP1StatPacketsSent {
						t.Errorf("got ip1Stats.PacketsSent.Value() = %d, want = %d", got, expectedIP1StatPacketsSent)
					}

					ep2, err := s.GetNetworkEndpoint(nicID2, test.netProto)
					if err != nil {
						t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID2, test.netProto, err)
					}
					ep2Stats := ep2.Stats()
					ipEP2Stats, ok := ep2Stats.(stack.IPNetworkEndpointStats)
					if !ok {
						t.Fatalf("got ep2Stats = %T, want = stack.IPNetworkEndpointStats", ep2Stats)
					}
					ip2Stats := ipEP2Stats.IPStats()
					if got := ip2Stats.PacketsReceived.Value(); got != 0 {
						t.Errorf("got ip2Stats.PacketsReceived.Value() = %d, want = 0", got)
					}
					if got := ip2Stats.ValidPacketsReceived.Value(); got != expectedIP2StatValidPacketsReceived {
						t.Errorf("got ip2Stats.ValidPacketsReceived.Value() = %d, want = %d", got, expectedIP2StatValidPacketsReceived)
					}
					if got := ip2Stats.IPTablesInputDropped.Value(); got != expectedIP2StatIPTablesInputDropped {
						t.Errorf("got ip2Stats.IPTablesInputDropped.Value() = %d, want = %d", got, expectedIP2StatIPTablesInputDropped)
					}
					if got := ip2Stats.PacketsSent.Value(); got != 0 {
						t.Errorf("got ip2Stats.PacketsSent.Value() = %d, want = 0", got)
					}

					expectPacket := subTest.expectResult == noneDropped
					p := e1.Read()
					if (p != nil) != expectPacket {
						t.Errorf("got e1.Read() = %#v, want = (_ == nil) = %t", p, expectPacket)
					}
					if p != nil {
						payload := stack.PayloadSince(p.NetworkHeader())
						defer payload.Release()
						test.checker(t, payload)
						p.DecRef()
					}
					if p := e2.Read(); p != nil {
						t.Errorf("got e1.Read() = %#v, want = nil)", p)
						p.DecRef()
					}
				})
			}
		})
	}
}

func setupNAT(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, hook stack.Hook, filter stack.IPHeaderFilter, target stack.Target) {
	t.Helper()

	ipv6 := netProto == ipv6.ProtocolNumber
	ipt := s.IPTables()
	table := ipt.GetTable(stack.NATID, ipv6)
	ruleIdx := table.BuiltinChains[hook]
	table.Rules[ruleIdx].Filter = filter
	table.Rules[ruleIdx].Target = target
	// Make sure the packet is not dropped by the next rule.
	table.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
	ipt.ForceReplaceTable(stack.NATID, table, ipv6)
}

func setupDNAT(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, target stack.Target) {
	t.Helper()

	setupNAT(
		t,
		s,
		netProto,
		stack.Prerouting,
		stack.IPHeaderFilter{
			Protocol:       transProto,
			CheckProtocol:  true,
			InputInterface: utils.RouterNIC2Name,
		},
		target)
}

func setupSNAT(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, target stack.Target) {
	t.Helper()

	setupNAT(
		t,
		s,
		netProto,
		stack.Postrouting,
		stack.IPHeaderFilter{
			Protocol:        transProto,
			CheckProtocol:   true,
			OutputInterface: utils.RouterNIC1Name,
		},
		target)
}

func setupTwiceNAT(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, dnatAddr tcpip.Address, dnatTarget, snatTarget stack.Target) {
	t.Helper()

	ipv6 := netProto == ipv6.ProtocolNumber
	ipt := s.IPTables()

	table := stack.Table{
		Rules: []stack.Rule{
			// Prerouting
			{
				Filter: stack.IPHeaderFilter{
					Protocol:       transProto,
					CheckProtocol:  true,
					InputInterface: utils.RouterNIC2Name,
				},
				Target: dnatTarget,
			},
			{
				Target: &stack.AcceptTarget{},
			},

			// Input
			{
				Target: &stack.AcceptTarget{},
			},

			// Forward
			{
				Target: &stack.AcceptTarget{},
			},

			// Output
			{
				Target: &stack.AcceptTarget{},
			},

			// Postrouting
			{
				Filter: stack.IPHeaderFilter{
					Protocol:        transProto,
					CheckProtocol:   true,
					OutputInterface: utils.RouterNIC1Name,
				},
				Target: snatTarget,
			},
			{
				Target: &stack.AcceptTarget{},
			},
		},
		BuiltinChains: [stack.NumHooks]int{
			stack.Prerouting:  0,
			stack.Input:       2,
			stack.Forward:     3,
			stack.Output:      4,
			stack.Postrouting: 5,
		},
	}

	ipt.ForceReplaceTable(stack.NATID, table, ipv6)
}

type natType struct {
	name     string
	setupNAT func(_ *testing.T, _ *stack.Stack, _ tcpip.NetworkProtocolNumber, _ tcpip.TransportProtocolNumber, snatAddr, dnatAddr tcpip.Address, dnatPort uint16)
}

var (
	snatTypes = []natType{
		{
			name: "SNAT",
			setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, snatAddr, _ tcpip.Address, _ uint16) {
				t.Helper()

				setupSNAT(t, s, netProto, transProto, &stack.SNATTarget{NetworkProtocol: netProto, Addr: snatAddr, ChangeAddress: true, ChangePort: true})
			},
		},
		{
			name: "Masquerade",
			setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, _, _ tcpip.Address, _ uint16) {
				t.Helper()

				setupSNAT(t, s, netProto, transProto, &stack.MasqueradeTarget{NetworkProtocol: netProto})
			},
		},
	}

	dnatTarget = natType{
		name: "DNAT",
		setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, _, dnatAddr tcpip.Address, dnatPort uint16) {
			t.Helper()

			setupDNAT(t, s, netProto, transProto, &stack.DNATTarget{NetworkProtocol: netProto, Addr: dnatAddr, Port: dnatPort, ChangeAddress: true, ChangePort: true})
		},
	}

	dnatTypes = []natType{
		{
			name: "Redirect",
			setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, _, _ tcpip.Address, dnatPort uint16) {
				t.Helper()

				setupDNAT(t, s, netProto, transProto, &stack.RedirectTarget{NetworkProtocol: netProto, Port: dnatPort})
			},
		},
		dnatTarget,
	}

	twiceNATTypes = []natType{
		{
			name: "DNAT-Masquerade",
			setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, snatAddr, dnatAddr tcpip.Address, dnatPort uint16) {
				t.Helper()

				setupTwiceNAT(t, s, netProto, transProto, dnatAddr, &stack.DNATTarget{NetworkProtocol: netProto, Addr: dnatAddr, Port: dnatPort, ChangeAddress: true, ChangePort: true}, &stack.MasqueradeTarget{NetworkProtocol: netProto})
			},
		},
		{
			name: "DNAT-SNAT",
			setupNAT: func(t *testing.T, s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, snatAddr, dnatAddr tcpip.Address, dnatPort uint16) {
				t.Helper()

				setupTwiceNAT(t, s, netProto, transProto, dnatAddr, &stack.DNATTarget{NetworkProtocol: netProto, Addr: dnatAddr, Port: dnatPort, ChangeAddress: true, ChangePort: true}, &stack.SNATTarget{NetworkProtocol: netProto, Addr: snatAddr, ChangeAddress: true, ChangePort: true})
			},
		},
	}
)

func TestNATEcho(t *testing.T) {
	const ident = 1

	v4EchoPkt := func(srcAddr, dstAddr tcpip.Address, reply bool) []byte {
		icmpType := header.ICMPv4Echo
		if reply {
			icmpType = header.ICMPv4EchoReply
		}

		return icmpv4Packet(srcAddr, dstAddr, icmpType, ident)
	}

	checkV4EchoPkt := func(t *testing.T, v *buffer.View, srcAddr, dstAddr tcpip.Address, reply bool) {
		t.Helper()

		icmpType := header.ICMPv4Echo
		if reply {
			icmpType = header.ICMPv4EchoReply
		}

		checker.IPv4(t, v,
			checker.SrcAddr(srcAddr),
			checker.DstAddr(dstAddr),
			checker.ICMPv4(
				checker.ICMPv4Type(icmpType),
				checker.ICMPv4Checksum(),
			),
		)
	}

	v6EchoPkt := func(srcAddr, dstAddr tcpip.Address, reply bool) []byte {
		icmpType := header.ICMPv6EchoRequest
		if reply {
			icmpType = header.ICMPv6EchoReply
		}

		return icmpv6Packet(srcAddr, dstAddr, icmpType, ident)
	}

	checkV6EchoPkt := func(t *testing.T, v *buffer.View, srcAddr, dstAddr tcpip.Address, reply bool) {
		t.Helper()

		icmpType := header.ICMPv6EchoRequest
		if reply {
			icmpType = header.ICMPv6EchoReply
		}

		checker.IPv6(t, v,
			checker.SrcAddr(srcAddr),
			checker.DstAddr(dstAddr),
			checker.ICMPv6(
				checker.ICMPv6Type(icmpType),
			),
		)
	}

	type natTypeTest struct {
		name                                   string
		natTypes                               []natType
		requestSrc, requestDst                 tcpip.Address
		expectedRequestSrc, expectedRequestDst tcpip.Address
	}

	tests := []struct {
		name         string
		netProto     tcpip.NetworkProtocolNumber
		transProto   tcpip.TransportProtocolNumber
		echoPkt      func(srcAddr, dstAddr tcpip.Address, reply bool) []byte
		checkEchoPkt func(t *testing.T, v *buffer.View, srcAddr, dstAddr tcpip.Address, reply bool)

		natTypes []natTypeTest
	}{
		{
			name:         "IPv4",
			netProto:     header.IPv4ProtocolNumber,
			transProto:   header.ICMPv4ProtocolNumber,
			echoPkt:      v4EchoPkt,
			checkEchoPkt: checkV4EchoPkt,

			natTypes: []natTypeTest{
				{
					name:               "SNAT",
					natTypes:           snatTypes,
					requestSrc:         utils.Host2IPv4Addr.AddressWithPrefix.Address,
					requestDst:         utils.Host1IPv4Addr.AddressWithPrefix.Address,
					expectedRequestSrc: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
					expectedRequestDst: utils.Host1IPv4Addr.AddressWithPrefix.Address,
				},
				{
					name:               "DNAT",
					natTypes:           []natType{dnatTarget},
					requestSrc:         utils.Host2IPv4Addr.AddressWithPrefix.Address,
					requestDst:         utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address,
					expectedRequestSrc: utils.Host2IPv4Addr.AddressWithPrefix.Address,
					expectedRequestDst: utils.Host1IPv4Addr.AddressWithPrefix.Address,
				},
				{
					name:               "Twice-NAT",
					natTypes:           twiceNATTypes,
					requestSrc:         utils.Host2IPv4Addr.AddressWithPrefix.Address,
					requestDst:         utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address,
					expectedRequestSrc: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
					expectedRequestDst: utils.Host1IPv4Addr.AddressWithPrefix.Address,
				},
			},
		},
		{
			name:         "IPv6",
			netProto:     header.IPv6ProtocolNumber,
			transProto:   header.ICMPv6ProtocolNumber,
			echoPkt:      v6EchoPkt,
			checkEchoPkt: checkV6EchoPkt,

			natTypes: []natTypeTest{
				{
					name:               "SNAT",
					natTypes:           snatTypes,
					requestSrc:         utils.Host2IPv6Addr.AddressWithPrefix.Address,
					requestDst:         utils.Host1IPv6Addr.AddressWithPrefix.Address,
					expectedRequestSrc: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
					expectedRequestDst: utils.Host1IPv6Addr.AddressWithPrefix.Address,
				},
				{
					name:               "DNAT",
					natTypes:           []natType{dnatTarget},
					requestSrc:         utils.Host2IPv6Addr.AddressWithPrefix.Address,
					requestDst:         utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address,
					expectedRequestSrc: utils.Host2IPv6Addr.AddressWithPrefix.Address,
					expectedRequestDst: utils.Host1IPv6Addr.AddressWithPrefix.Address,
				},
				{
					name:               "Twice-NAT",
					natTypes:           twiceNATTypes,
					requestSrc:         utils.Host2IPv6Addr.AddressWithPrefix.Address,
					requestDst:         utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address,
					expectedRequestSrc: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
					expectedRequestDst: utils.Host1IPv6Addr.AddressWithPrefix.Address,
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, natTypeTest := range test.natTypes {
				t.Run(natTypeTest.name, func(t *testing.T) {
					for _, natType := range natTypeTest.natTypes {
						t.Run(natType.name, func(t *testing.T) {
							s := stack.New(stack.Options{
								NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
								TransportProtocols: []stack.TransportProtocolFactory{icmp.NewProtocol4, icmp.NewProtocol6},
							})
							defer s.Destroy()

							ep1 := channel.New(1, header.IPv6MinimumMTU, "")
							ep2 := channel.New(1, header.IPv6MinimumMTU, "")
							utils.SetupRouterStack(t, s, ep1, ep2)

							natType.setupNAT(t, s, test.netProto, test.transProto, natTypeTest.expectedRequestSrc, natTypeTest.expectedRequestDst, 0 /* dnatPort */)

							// Send and check the Echo Request.
							{
								ep2.InjectInbound(test.netProto, stack.NewPacketBuffer(stack.PacketBufferOptions{
									Payload: buffer.MakeWithData(test.echoPkt(natTypeTest.requestSrc, natTypeTest.requestDst, false /* reply */)),
								}))
								pkt := ep1.Read()
								if pkt == nil {
									t.Fatal("expected to read a packet on ep1")
								}
								payload := stack.PayloadSince(pkt.NetworkHeader())
								defer payload.Release()
								test.checkEchoPkt(t, payload, natTypeTest.expectedRequestSrc, natTypeTest.expectedRequestDst, false /* reply */)
								pkt.DecRef()
							}

							if t.Failed() {
								t.FailNow()
							}

							// Send and check the Echo Reply.
							{
								ep1.InjectInbound(test.netProto, stack.NewPacketBuffer(stack.PacketBufferOptions{
									Payload: buffer.MakeWithData(test.echoPkt(natTypeTest.expectedRequestDst, natTypeTest.expectedRequestSrc, true /* reply */)),
								}))
								pkt := ep2.Read()
								if pkt == nil {
									t.Fatal("expected to read a packet on ep2")
								}
								payload := stack.PayloadSince(pkt.NetworkHeader())
								defer payload.Release()
								test.checkEchoPkt(t, payload, natTypeTest.requestDst, natTypeTest.requestSrc, true /* reply */)
								pkt.DecRef()
							}
						})
					}
				})
			}
		})
	}
}

func TestNAT(t *testing.T) {
	const listenPort uint16 = 8080

	type endpointAndAddresses struct {
		serverEP          tcpip.Endpoint
		serverAddr        tcpip.FullAddress
		serverReadableCH  chan struct{}
		serverConnectAddr tcpip.Address

		clientEP          tcpip.Endpoint
		clientAddr        tcpip.Address
		clientReadableCH  chan struct{}
		clientConnectAddr tcpip.FullAddress
	}

	newEP := func(t *testing.T, s *stack.Stack, transProto tcpip.TransportProtocolNumber, netProto tcpip.NetworkProtocolNumber) (tcpip.Endpoint, chan struct{}) {
		t.Helper()
		var wq waiter.Queue
		we, ch := waiter.NewChannelEntry(waiter.ReadableEvents)
		wq.EventRegister(&we)
		t.Cleanup(func() {
			wq.EventUnregister(&we)
		})

		ep, err := s.NewEndpoint(transProto, netProto, &wq)
		if err != nil {
			t.Fatalf("s.NewEndpoint(%d, %d, _): %s", transProto, netProto, err)
		}
		t.Cleanup(ep.Close)

		return ep, ch
	}

	tests := []struct {
		name     string
		netProto tcpip.NetworkProtocolNumber
		// Setups up the stacks in such a way that:
		//
		//	- Host2 is the client for all tests.
		//	- When performing SNAT only:
		//   + Host1 is the server.
		//   + NAT will transform client-originating packets' source addresses to
		//     the router's NIC1's address before reaching Host1.
		//	- When performing DNAT only:
		//   + Router is the server.
		//   + Client will send packets directed to Host1.
		//   + NAT will transform client-originating packets' destination addresses
		//     to the router's NIC2's address.
		//	- When performing Twice-NAT:
		//   + Host1 is the server.
		//   + Client will send packets directed to router's NIC2.
		//   + NAT will transform client originating packets' destination addresses
		//     to Host1's address.
		//   + NAT will transform client-originating packets' source addresses to
		//     the router's NIC1's address before reaching Host1.
		epAndAddrs func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses
		natTypes   []natType
	}{
		{
			name:     "IPv4 SNAT",
			netProto: ipv4.ProtocolNumber,
			epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
				t.Helper()

				listenerStack := host1Stack
				serverAddr := tcpip.FullAddress{
					Addr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
					Port: listenPort,
				}
				serverConnectAddr := utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address
				clientConnectPort := serverAddr.Port
				ep1, ep1WECH := newEP(t, listenerStack, proto, ipv4.ProtocolNumber)
				ep2, ep2WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber)
				return endpointAndAddresses{
					serverEP:          ep1,
					serverAddr:        serverAddr,
					serverReadableCH:  ep1WECH,
					serverConnectAddr: serverConnectAddr,

					clientEP:         ep2,
					clientAddr:       utils.Host2IPv4Addr.AddressWithPrefix.Address,
					clientReadableCH: ep2WECH,
					clientConnectAddr: tcpip.FullAddress{
						Addr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
						Port: clientConnectPort,
					},
				}
			},
			natTypes: snatTypes,
		},
		{
			name:     "IPv4 DNAT",
			netProto: ipv4.ProtocolNumber,
			epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
				t.Helper()

				// If we are performing DNAT, then the packet will be redirected
				// to the router.
				listenerStack := routerStack
				serverAddr := tcpip.FullAddress{
					Addr: utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address,
					Port: listenPort,
				}
				serverConnectAddr := utils.Host2IPv4Addr.AddressWithPrefix.Address
				// DNAT will update the destination port to what the server is
				// bound to.
				clientConnectPort := serverAddr.Port + 1
				ep1, ep1WECH := newEP(t, listenerStack, proto, ipv4.ProtocolNumber)
				ep2, ep2WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber)
				return endpointAndAddresses{
					serverEP:          ep1,
					serverAddr:        serverAddr,
					serverReadableCH:  ep1WECH,
					serverConnectAddr: serverConnectAddr,

					clientEP:         ep2,
					clientAddr:       utils.Host2IPv4Addr.AddressWithPrefix.Address,
					clientReadableCH: ep2WECH,
					clientConnectAddr: tcpip.FullAddress{
						Addr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
						Port: clientConnectPort,
					},
				}
			},
			natTypes: dnatTypes,
		},
		{
			name:     "IPv4 Twice-NAT",
			netProto: ipv4.ProtocolNumber,
			epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
				t.Helper()

				listenerStack := host1Stack
				serverAddr := tcpip.FullAddress{
					Addr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
					Port: listenPort,
				}
				serverConnectAddr := utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address
				clientConnectPort := serverAddr.Port
				ep1, ep1WECH := newEP(t, listenerStack, proto, ipv4.ProtocolNumber)
				ep2, ep2WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber)
				return endpointAndAddresses{
					serverEP:          ep1,
					serverAddr:        serverAddr,
					serverReadableCH:  ep1WECH,
					serverConnectAddr: serverConnectAddr,

					clientEP:         ep2,
					clientAddr:       utils.Host2IPv4Addr.AddressWithPrefix.Address,
					clientReadableCH: ep2WECH,
					clientConnectAddr: tcpip.FullAddress{
						Addr: utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address,
						Port: clientConnectPort,
					},
				}
			},
			natTypes: twiceNATTypes,
		},
		{
			name:     "IPv6 SNAT",
			netProto: ipv6.ProtocolNumber,
			epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
				t.Helper()

				listenerStack := host1Stack
				serverAddr := tcpip.FullAddress{
					Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
					Port: listenPort,
				}
				serverConnectAddr := utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address
				clientConnectPort := serverAddr.Port
				ep1, ep1WECH := newEP(t, listenerStack, proto, ipv6.ProtocolNumber)
				ep2, ep2WECH := newEP(t, host2Stack, proto, ipv6.ProtocolNumber)
				return endpointAndAddresses{
					serverEP:          ep1,
					serverAddr:        serverAddr,
					serverReadableCH:  ep1WECH,
					serverConnectAddr: serverConnectAddr,

					clientEP:         ep2,
					clientAddr:       utils.Host2IPv6Addr.AddressWithPrefix.Address,
					clientReadableCH: ep2WECH,
					clientConnectAddr: tcpip.FullAddress{
						Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
						Port: clientConnectPort,
					},
				}
			},
			natTypes: snatTypes,
		},
		{
			name:     "IPv6 DNAT",
			netProto: ipv6.ProtocolNumber,
			epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
				t.Helper()

				// If we are performing DNAT, then the packet will be redirected
				// to the router.
				listenerStack := routerStack
				serverAddr := tcpip.FullAddress{
					Addr: utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address,
					Port: listenPort,
				}
				serverConnectAddr := utils.Host2IPv6Addr.AddressWithPrefix.Address
				// DNAT will update the destination port to what the server is
				// bound to.
				clientConnectPort := serverAddr.Port + 1
				ep1, ep1WECH := newEP(t, listenerStack, proto, ipv6.ProtocolNumber)
				ep2, ep2WECH := newEP(t, host2Stack, proto, ipv6.ProtocolNumber)
				return endpointAndAddresses{
					serverEP:          ep1,
					serverAddr:        serverAddr,
					serverReadableCH:  ep1WECH,
					serverConnectAddr: serverConnectAddr,

					clientEP:         ep2,
					clientAddr:       utils.Host2IPv6Addr.AddressWithPrefix.Address,
					clientReadableCH: ep2WECH,
					clientConnectAddr: tcpip.FullAddress{
						Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
						Port: clientConnectPort,
					},
				}
			},
			natTypes: dnatTypes,
		},
		{
			name:     "IPv6 Twice-NAT",
			netProto: ipv6.ProtocolNumber,
			epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
				t.Helper()

				listenerStack := host1Stack
				serverAddr := tcpip.FullAddress{
					Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
					Port: listenPort,
				}
				serverConnectAddr := utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address
				clientConnectPort := serverAddr.Port
				ep1, ep1WECH := newEP(t, listenerStack, proto, ipv6.ProtocolNumber)
				ep2, ep2WECH := newEP(t, host2Stack, proto, ipv6.ProtocolNumber)
				return endpointAndAddresses{
					serverEP:          ep1,
					serverAddr:        serverAddr,
					serverReadableCH:  ep1WECH,
					serverConnectAddr: serverConnectAddr,

					clientEP:         ep2,
					clientAddr:       utils.Host2IPv6Addr.AddressWithPrefix.Address,
					clientReadableCH: ep2WECH,
					clientConnectAddr: tcpip.FullAddress{
						Addr: utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address,
						Port: clientConnectPort,
					},
				}
			},
			natTypes: twiceNATTypes,
		},
	}

	subTests := []struct {
		name               string
		proto              tcpip.TransportProtocolNumber
		expectedConnectErr tcpip.Error
		setupServer        func(t *testing.T, ep tcpip.Endpoint)
		setupServerConn    func(t *testing.T, ep tcpip.Endpoint, ch <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{})
		needRemoteAddr     bool
	}{
		{
			name:               "UDP",
			proto:              udp.ProtocolNumber,
			expectedConnectErr: nil,
			setupServerConn: func(t *testing.T, ep tcpip.Endpoint, _ <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{}) {
				t.Helper()

				if err := ep.Connect(clientAddr); err != nil {
					t.Fatalf("ep.Connect(%#v): %s", clientAddr, err)
				}
				return nil, nil
			},
			needRemoteAddr: true,
		},
		{
			name:               "TCP",
			proto:              tcp.ProtocolNumber,
			expectedConnectErr: &tcpip.ErrConnectStarted{},
			setupServer: func(t *testing.T, ep tcpip.Endpoint) {
				t.Helper()

				if err := ep.Listen(1); err != nil {
					t.Fatalf("ep.Listen(1): %s", err)
				}
			},
			setupServerConn: func(t *testing.T, ep tcpip.Endpoint, ch <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{}) {
				t.Helper()

				var addr tcpip.FullAddress
				for {
					newEP, wq, err := ep.Accept(&addr)
					if _, ok := err.(*tcpip.ErrWouldBlock); ok {
						<-ch
						continue
					}
					if err != nil {
						t.Fatalf("ep.Accept(_): %s", err)
					}
					if diff := cmp.Diff(clientAddr, addr, checker.IgnoreCmpPath(
						"NIC",
					)); diff != "" {
						t.Errorf("accepted address mismatch (-want +got):\n%s", diff)
					}

					we, newCH := waiter.NewChannelEntry(waiter.ReadableEvents)
					wq.EventRegister(&we)
					return newEP, newCH
				}
			},
			needRemoteAddr: false,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, subTest := range subTests {
				t.Run(subTest.name, func(t *testing.T) {
					for _, natType := range test.natTypes {
						t.Run(natType.name, func(t *testing.T) {
							stackOpts := stack.Options{
								NetworkProtocols:   []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
								TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
							}

							host1Stack := stack.New(stackOpts)
							defer host1Stack.Destroy()
							routerStack := stack.New(stackOpts)
							defer routerStack.Destroy()
							host2Stack := stack.New(stackOpts)
							defer host2Stack.Destroy()
							utils.SetupRoutedStacks(t, host1Stack, routerStack, host2Stack)

							epsAndAddrs := test.epAndAddrs(t, host1Stack, routerStack, host2Stack, subTest.proto)
							natType.setupNAT(t, routerStack, test.netProto, subTest.proto, epsAndAddrs.serverConnectAddr, epsAndAddrs.serverAddr.Addr, listenPort)

							if err := epsAndAddrs.serverEP.Bind(epsAndAddrs.serverAddr); err != nil {
								t.Fatalf("epsAndAddrs.serverEP.Bind(%#v): %s", epsAndAddrs.serverAddr, err)
							}
							clientAddr := tcpip.FullAddress{Addr: epsAndAddrs.clientAddr}
							if err := epsAndAddrs.clientEP.Bind(clientAddr); err != nil {
								t.Fatalf("epsAndAddrs.clientEP.Bind(%#v): %s", clientAddr, err)
							}

							if subTest.setupServer != nil {
								subTest.setupServer(t, epsAndAddrs.serverEP)
							}
							{
								err := epsAndAddrs.clientEP.Connect(epsAndAddrs.clientConnectAddr)
								if diff := cmp.Diff(subTest.expectedConnectErr, err); diff != "" {
									t.Fatalf("unexpected error from epsAndAddrs.clientEP.Connect(%#v), (-want, +got):\n%s", epsAndAddrs.clientConnectAddr, diff)
								}
							}
							serverConnectAddr := tcpip.FullAddress{Addr: epsAndAddrs.serverConnectAddr}
							if addr, err := epsAndAddrs.clientEP.GetLocalAddress(); err != nil {
								t.Fatalf("epsAndAddrs.clientEP.GetLocalAddress(): %s", err)
							} else {
								serverConnectAddr.Port = addr.Port
							}

							serverEP := epsAndAddrs.serverEP
							serverCH := epsAndAddrs.serverReadableCH
							if ep, ch := subTest.setupServerConn(t, serverEP, serverCH, serverConnectAddr); ep != nil {
								defer ep.Close()
								serverEP = ep
								serverCH = ch
							}

							write := func(ep tcpip.Endpoint, data []byte) {
								t.Helper()

								var r bytes.Reader
								r.Reset(data)
								var wOpts tcpip.WriteOptions
								n, err := ep.Write(&r, wOpts)
								if err != nil {
									t.Fatalf("ep.Write(_, %#v): %s", wOpts, err)
								}
								if want := int64(len(data)); n != want {
									t.Fatalf("got ep.Write(_, %#v) = (%d, _), want = (%d, _)", wOpts, n, want)
								}
							}

							read := func(ch chan struct{}, ep tcpip.Endpoint, data []byte, expectedFrom tcpip.FullAddress) {
								t.Helper()

								var buf bytes.Buffer
								var res tcpip.ReadResult
								for {
									var err tcpip.Error
									opts := tcpip.ReadOptions{NeedRemoteAddr: subTest.needRemoteAddr}
									res, err = ep.Read(&buf, opts)
									if _, ok := err.(*tcpip.ErrWouldBlock); ok {
										<-ch
										continue
									}
									if err != nil {
										t.Fatalf("ep.Read(_, %d, %#v): %s", len(data), opts, err)
									}
									break
								}

								readResult := tcpip.ReadResult{
									Count: len(data),
									Total: len(data),
								}
								if subTest.needRemoteAddr {
									readResult.RemoteAddr = expectedFrom
								}
								if diff := cmp.Diff(readResult, res, checker.IgnoreCmpPath(
									"ControlMessages",
									"RemoteAddr.NIC",
								)); diff != "" {
									t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
								}
								if diff := cmp.Diff(buf.Bytes(), data); diff != "" {
									t.Errorf("received data mismatch (-want +got):\n%s", diff)
								}

								if t.Failed() {
									t.FailNow()
								}
							}

							{
								data := []byte{1, 2, 3, 4}
								write(epsAndAddrs.clientEP, data)
								read(serverCH, serverEP, data, serverConnectAddr)
							}

							{
								data := []byte{5, 6, 7, 8, 9, 10, 11, 12}
								write(serverEP, data)
								read(epsAndAddrs.clientReadableCH, epsAndAddrs.clientEP, data, epsAndAddrs.clientConnectAddr)
							}
						})
					}
				})
			}
		})
	}
}

func encodeIPv4Header(v []byte, totalLen int, transProto tcpip.TransportProtocolNumber, srcAddr, dstAddr tcpip.Address) {
	ip := header.IPv4(v)
	ip.Encode(&header.IPv4Fields{
		TotalLength: uint16(totalLen),
		Protocol:    uint8(transProto),
		TTL:         64,
		SrcAddr:     srcAddr,
		DstAddr:     dstAddr,
	})
	ip.SetChecksum(^ip.CalculateChecksum())
}

func encodeIPv6Header(v []byte, payloadLen int, transProto tcpip.TransportProtocolNumber, srcAddr, dstAddr tcpip.Address) {
	ip := header.IPv6(v)
	ip.Encode(&header.IPv6Fields{
		PayloadLength:     uint16(payloadLen),
		TransportProtocol: transProto,
		HopLimit:          64,
		SrcAddr:           srcAddr,
		DstAddr:           dstAddr,
	})
}

func udpv4Packet(srcAddr, dstAddr tcpip.Address, srcPort, dstPort uint16, dataSize int) []byte {
	udpSize := header.UDPMinimumSize + dataSize
	hdr := prependable.New(header.IPv4MinimumSize + udpSize)
	udp := header.UDP(hdr.Prepend(udpSize))
	udp.SetSourcePort(srcPort)
	udp.SetDestinationPort(dstPort)
	udp.SetLength(uint16(udpSize))
	udp.SetChecksum(0)
	udp.SetChecksum(^udp.CalculateChecksum(header.PseudoHeaderChecksum(
		header.UDPProtocolNumber,
		srcAddr,
		dstAddr,
		uint16(len(udp)),
	)))
	encodeIPv4Header(
		hdr.Prepend(header.IPv4MinimumSize),
		hdr.UsedLength(),
		header.UDPProtocolNumber,
		srcAddr,
		dstAddr,
	)
	return hdr.View()
}

func tcpv4Packet(srcAddr, dstAddr tcpip.Address, srcPort, dstPort uint16, dataSize int) []byte {
	tcpSize := header.TCPMinimumSize + dataSize
	hdr := prependable.New(header.IPv4MinimumSize + tcpSize)
	tcp := header.TCP(hdr.Prepend(tcpSize))
	tcp.SetSourcePort(srcPort)
	tcp.SetDestinationPort(dstPort)
	tcp.SetDataOffset(header.TCPMinimumSize)
	tcp.SetChecksum(0)
	tcp.SetChecksum(^tcp.CalculateChecksum(header.PseudoHeaderChecksum(
		header.TCPProtocolNumber,
		srcAddr,
		dstAddr,
		uint16(len(tcp)),
	)))
	encodeIPv4Header(
		hdr.Prepend(header.IPv4MinimumSize),
		hdr.UsedLength(),
		header.TCPProtocolNumber,
		srcAddr,
		dstAddr,
	)
	return hdr.View()
}

func icmpv4Packet(srcAddr, dstAddr tcpip.Address, icmpType header.ICMPv4Type, ident uint16) []byte {
	hdr := prependable.New(header.IPv4MinimumSize + header.ICMPv4MinimumSize)
	icmp := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
	icmp.SetType(icmpType)
	icmp.SetIdent(ident)
	icmp.SetChecksum(0)
	icmp.SetChecksum(^checksum.Checksum(icmp, 0))
	encodeIPv4Header(
		hdr.Prepend(header.IPv4MinimumSize),
		hdr.UsedLength(),
		header.ICMPv4ProtocolNumber,
		srcAddr,
		dstAddr,
	)
	return hdr.View()
}

func udpv6Packet(srcAddr, dstAddr tcpip.Address, srcPort, dstPort uint16, dataSize int) []byte {
	udpSize := header.UDPMinimumSize + dataSize
	hdr := prependable.New(header.IPv6MinimumSize + udpSize)
	udp := header.UDP(hdr.Prepend(udpSize))
	udp.SetSourcePort(srcPort)
	udp.SetDestinationPort(dstPort)
	udp.SetLength(uint16(udpSize))
	udp.SetChecksum(0)
	udp.SetChecksum(^udp.CalculateChecksum(header.PseudoHeaderChecksum(
		header.UDPProtocolNumber,
		srcAddr,
		dstAddr,
		uint16(len(udp)),
	)))
	encodeIPv6Header(
		hdr.Prepend(header.IPv6MinimumSize),
		len(udp),
		header.UDPProtocolNumber,
		srcAddr,
		dstAddr,
	)
	return hdr.View()
}

func tcpv6Packet(srcAddr, dstAddr tcpip.Address, srcPort, dstPort uint16, dataSize int) []byte {
	tcpSize := header.TCPMinimumSize + dataSize
	hdr := prependable.New(header.IPv6MinimumSize + tcpSize)
	tcp := header.TCP(hdr.Prepend(tcpSize))
	tcp.SetSourcePort(srcPort)
	tcp.SetDestinationPort(dstPort)
	tcp.SetDataOffset(header.TCPMinimumSize)
	tcp.SetChecksum(0)
	tcp.SetChecksum(^tcp.CalculateChecksum(header.PseudoHeaderChecksum(
		header.TCPProtocolNumber,
		srcAddr,
		dstAddr,
		uint16(len(tcp)),
	)))
	encodeIPv6Header(
		hdr.Prepend(header.IPv6MinimumSize),
		len(tcp),
		header.TCPProtocolNumber,
		srcAddr,
		dstAddr,
	)
	return hdr.View()
}

func icmpv6Packet(srcAddr, dstAddr tcpip.Address, icmpType header.ICMPv6Type, ident uint16) []byte {
	hdr := prependable.New(header.IPv6MinimumSize + header.ICMPv6MinimumSize)
	icmp := header.ICMPv6(hdr.Prepend(header.ICMPv6MinimumSize))
	icmp.SetType(icmpType)
	icmp.SetIdent(ident)
	icmp.SetChecksum(0)
	icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
		Header: icmp,
		Src:    srcAddr,
		Dst:    dstAddr,
	}))
	encodeIPv6Header(
		hdr.Prepend(header.IPv6MinimumSize),
		len(icmp),
		header.ICMPv6ProtocolNumber,
		srcAddr,
		dstAddr,
	)
	return hdr.View()
}

func TestNATICMPError(t *testing.T) {
	const (
		srcPort  = 1234
		dstPort  = 5432
		dataSize = 4
	)

	type icmpTypeTest struct {
		name           string
		val            uint8
		expectResponse bool
	}

	type transportTypeTest struct {
		name       string
		proto      tcpip.TransportProtocolNumber
		buf        []byte
		checkNATed func(*testing.T, *buffer.View)
	}

	tests := []struct {
		name            string
		netProto        tcpip.NetworkProtocolNumber
		host1Addr       tcpip.Address
		icmpError       func(*testing.T, []byte, uint8) []byte
		decrementTTL    func([]byte)
		checkNATedError func(*testing.T, *buffer.View, []byte, uint8)

		transportTypes []transportTypeTest
		icmpTypes      []icmpTypeTest
	}{
		{
			name:      "IPv4",
			netProto:  ipv4.ProtocolNumber,
			host1Addr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
			icmpError: func(t *testing.T, original []byte, icmpType uint8) []byte {
				hdr := prependable.New(header.IPv4MinimumSize + header.ICMPv4MinimumSize + len(original))
				if n := copy(hdr.Prepend(len(original)), original); n != len(original) {
					t.Fatalf("got copy(...) = %d, want = %d", n, len(original))
				}
				icmp := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
				icmp.SetType(header.ICMPv4Type(icmpType))
				icmp.SetChecksum(0)
				icmp.SetChecksum(header.ICMPv4Checksum(icmp, 0))
				encodeIPv4Header(
					hdr.Prepend(header.IPv4MinimumSize),
					hdr.UsedLength(),
					header.ICMPv4ProtocolNumber,
					utils.Host1IPv4Addr.AddressWithPrefix.Address,
					utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
				)
				return hdr.View()
			},
			decrementTTL: func(v []byte) {
				ip := header.IPv4(v)
				ip.SetTTL(ip.TTL() - 1)
				ip.SetChecksum(0)
				ip.SetChecksum(^ip.CalculateChecksum())
			},
			checkNATedError: func(t *testing.T, v *buffer.View, original []byte, icmpType uint8) {
				checker.IPv4(t, v,
					checker.SrcAddr(utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address),
					checker.DstAddr(utils.Host2IPv4Addr.AddressWithPrefix.Address),
					checker.ICMPv4(
						checker.ICMPv4Type(header.ICMPv4Type(icmpType)),
						checker.ICMPv4Checksum(),
						checker.ICMPv4Payload(original),
					),
				)
			},
			transportTypes: []transportTypeTest{
				{
					name:  "UDP",
					proto: header.UDPProtocolNumber,
					buf: func() []byte {
						return udpv4Packet(utils.Host2IPv4Addr.AddressWithPrefix.Address, utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address, srcPort, dstPort, dataSize)
					}(),
					checkNATed: func(t *testing.T, v *buffer.View) {
						checker.IPv4(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv4Addr.AddressWithPrefix.Address),
							checker.UDP(
								checker.SrcPort(srcPort),
								checker.DstPort(dstPort),
							),
						)
					},
				},
				{
					name:  "TCP",
					proto: header.TCPProtocolNumber,
					buf: func() []byte {
						return tcpv4Packet(utils.Host2IPv4Addr.AddressWithPrefix.Address, utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address, srcPort, dstPort, dataSize)
					}(),
					checkNATed: func(t *testing.T, v *buffer.View) {
						checker.IPv4(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv4Addr.AddressWithPrefix.Address),
							checker.TCP(
								checker.SrcPort(srcPort),
								checker.DstPort(dstPort),
							),
						)
					},
				},
			},
			icmpTypes: []icmpTypeTest{
				{
					name:           "Destination Unreachable",
					val:            uint8(header.ICMPv4DstUnreachable),
					expectResponse: true,
				},
				{
					name:           "Time Exceeded",
					val:            uint8(header.ICMPv4TimeExceeded),
					expectResponse: true,
				},
				{
					name:           "Parameter Problem",
					val:            uint8(header.ICMPv4ParamProblem),
					expectResponse: true,
				},
				{
					name:           "Echo Request",
					val:            uint8(header.ICMPv4Echo),
					expectResponse: false,
				},
				{
					name:           "Echo Reply",
					val:            uint8(header.ICMPv4EchoReply),
					expectResponse: false,
				},
			},
		},
		{
			name:      "IPv6",
			netProto:  ipv6.ProtocolNumber,
			host1Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
			icmpError: func(t *testing.T, original []byte, icmpType uint8) []byte {
				payloadLen := header.ICMPv6MinimumSize + len(original)
				hdr := prependable.New(header.IPv6MinimumSize + payloadLen)
				icmp := header.ICMPv6(hdr.Prepend(payloadLen))
				icmp.SetType(header.ICMPv6Type(icmpType))
				if n := copy(icmp.Payload(), original); n != len(original) {
					t.Fatalf("got copy(...) = %d, want = %d", n, len(original))
				}
				icmp.SetChecksum(0)
				icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
					Header: icmp,
					Src:    utils.Host1IPv6Addr.AddressWithPrefix.Address,
					Dst:    utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
				}))
				encodeIPv6Header(
					hdr.Prepend(header.IPv6MinimumSize),
					payloadLen,
					header.ICMPv6ProtocolNumber,
					utils.Host1IPv6Addr.AddressWithPrefix.Address,
					utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
				)
				return hdr.View()
			},
			decrementTTL: func(v []byte) {
				ip := header.IPv6(v)
				ip.SetHopLimit(ip.HopLimit() - 1)
			},
			checkNATedError: func(t *testing.T, v *buffer.View, original []byte, icmpType uint8) {
				checker.IPv6(t, v,
					checker.SrcAddr(utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address),
					checker.DstAddr(utils.Host2IPv6Addr.AddressWithPrefix.Address),
					checker.ICMPv6(
						checker.ICMPv6Type(header.ICMPv6Type(icmpType)),
						checker.ICMPv6Payload(original),
					),
				)
			},
			transportTypes: []transportTypeTest{
				{
					name:  "UDP",
					proto: header.UDPProtocolNumber,
					buf: func() []byte {
						return udpv6Packet(utils.Host2IPv6Addr.AddressWithPrefix.Address, utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address, srcPort, dstPort, dataSize)
					}(),
					checkNATed: func(t *testing.T, v *buffer.View) {
						checker.IPv6(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv6Addr.AddressWithPrefix.Address),
							checker.UDP(
								checker.SrcPort(srcPort),
								checker.DstPort(dstPort),
							),
						)
					},
				},
				{
					name:  "TCP",
					proto: header.TCPProtocolNumber,
					buf: func() []byte {
						return tcpv6Packet(utils.Host2IPv6Addr.AddressWithPrefix.Address, utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address, srcPort, dstPort, dataSize)
					}(),
					checkNATed: func(t *testing.T, v *buffer.View) {
						checker.IPv6(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv6Addr.AddressWithPrefix.Address),
							checker.TCP(
								checker.SrcPort(srcPort),
								checker.DstPort(dstPort),
							),
						)
					},
				},
			},
			icmpTypes: []icmpTypeTest{
				{
					name:           "Destination Unreachable",
					val:            uint8(header.ICMPv6DstUnreachable),
					expectResponse: true,
				},
				{
					name:           "Packet Too Big",
					val:            uint8(header.ICMPv6PacketTooBig),
					expectResponse: true,
				},
				{
					name:           "Time Exceeded",
					val:            uint8(header.ICMPv6TimeExceeded),
					expectResponse: true,
				},
				{
					name:           "Parameter Problem",
					val:            uint8(header.ICMPv6ParamProblem),
					expectResponse: true,
				},
				{
					name:           "Echo Request",
					val:            uint8(header.ICMPv6EchoRequest),
					expectResponse: false,
				},
				{
					name:           "Echo Reply",
					val:            uint8(header.ICMPv6EchoReply),
					expectResponse: false,
				},
			},
		},
	}

	trimTests := []struct {
		name            string
		trimLen         int
		expectNATedICMP bool
	}{
		{
			name:            "Trim nothing",
			trimLen:         0,
			expectNATedICMP: true,
		},
		{
			name:            "Trim data",
			trimLen:         dataSize,
			expectNATedICMP: true,
		},
		{
			name:            "Trim data and transport header",
			trimLen:         dataSize + 1,
			expectNATedICMP: false,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, transportType := range test.transportTypes {
				t.Run(transportType.name, func(t *testing.T) {
					for _, icmpType := range test.icmpTypes {
						t.Run(icmpType.name, func(t *testing.T) {
							for _, trimTest := range trimTests {
								t.Run(trimTest.name, func(t *testing.T) {
									s := stack.New(stack.Options{
										NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
										TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
									})
									defer s.Destroy()

									ep1 := channel.New(1, header.IPv6MinimumMTU, "")
									ep2 := channel.New(1, header.IPv6MinimumMTU, "")
									utils.SetupRouterStack(t, s, ep1, ep2)

									ipv6 := test.netProto == ipv6.ProtocolNumber
									ipt := s.IPTables()

									table := stack.Table{
										Rules: []stack.Rule{
											// Prerouting
											{
												Filter: stack.IPHeaderFilter{
													Protocol:       transportType.proto,
													CheckProtocol:  true,
													InputInterface: utils.RouterNIC2Name,
												},
												Target: &stack.DNATTarget{NetworkProtocol: test.netProto, Addr: test.host1Addr, Port: dstPort, ChangeAddress: true, ChangePort: true},
											},
											{
												Target: &stack.AcceptTarget{},
											},

											// Input
											{
												Target: &stack.AcceptTarget{},
											},

											// Forward
											{
												Target: &stack.AcceptTarget{},
											},

											// Output
											{
												Target: &stack.AcceptTarget{},
											},

											// Postrouting
											{
												Filter: stack.IPHeaderFilter{
													Protocol:        transportType.proto,
													CheckProtocol:   true,
													OutputInterface: utils.RouterNIC1Name,
												},
												Target: &stack.MasqueradeTarget{NetworkProtocol: test.netProto},
											},
											{
												Target: &stack.AcceptTarget{},
											},
										},
										BuiltinChains: [stack.NumHooks]int{
											stack.Prerouting:  0,
											stack.Input:       2,
											stack.Forward:     3,
											stack.Output:      4,
											stack.Postrouting: 5,
										},
									}

									ipt.ForceReplaceTable(stack.NATID, table, ipv6)

									buf := transportType.buf

									ep2.InjectInbound(test.netProto, stack.NewPacketBuffer(stack.PacketBufferOptions{
										Payload: buffer.MakeWithData(append([]byte{}, buf...)),
									}))

									{
										pkt := ep1.Read()
										if pkt == nil {
											t.Fatal("expected to read a packet on ep1")
										}
										pktView := stack.PayloadSince(pkt.NetworkHeader())
										defer pktView.Release()
										pkt.DecRef()
										transportType.checkNATed(t, pktView)
										if t.Failed() {
											t.FailNow()
										}

										pktSlice := pktView.AsSlice()[:pktView.Size()-trimTest.trimLen]
										buf = buf[:len(buf)-trimTest.trimLen]

										ep1.InjectInbound(test.netProto, stack.NewPacketBuffer(stack.PacketBufferOptions{
											Payload: buffer.MakeWithData(test.icmpError(t, pktSlice, icmpType.val)),
										}))
									}

									pkt := ep2.Read()
									expectResponse := icmpType.expectResponse && trimTest.expectNATedICMP
									if (pkt != nil) != expectResponse {
										t.Fatalf("got ep2.Read() = %#v, want = (_ == nil) = %t", pkt, expectResponse)
									}
									if !expectResponse {
										return
									}
									test.decrementTTL(buf)
									payload := stack.PayloadSince(pkt.NetworkHeader())
									defer payload.Release()
									test.checkNATedError(t, payload, buf, icmpType.val)
									pkt.DecRef()
								})
							}
						})
					}
				})
			}
		})
	}
}

func TestSNATHandlePortOrIdentConflicts(t *testing.T) {
	const dstPort = 5432

	type portOrIdentRange struct {
		first uint16
		last  uint16
	}

	type srcPortOrIdentRangeTest struct {
		name          string
		originalRange portOrIdentRange
		targetRange   portOrIdentRange
	}

	srcPortRanges := []srcPortOrIdentRangeTest{
		{
			name:          "Less than 512",
			originalRange: portOrIdentRange{first: 1, last: 511},
			targetRange:   portOrIdentRange{first: 1, last: 511},
		},
		{
			name:          "Greater than or equal to 512 but less than 1024",
			originalRange: portOrIdentRange{first: 512, last: 1023},
			targetRange:   portOrIdentRange{first: 1, last: 1023},
		},
		{
			name:          "Greater than or equal to 1024",
			originalRange: portOrIdentRange{first: 1024, last: math.MaxUint16},
			targetRange:   portOrIdentRange{first: 1024, last: math.MaxUint16},
		},
	}

	// Unlike TCP/UDP, the Ident may be mapped to any 16-bit value.
	identRanges := []srcPortOrIdentRangeTest{
		{
			name:          "Less than 512",
			originalRange: portOrIdentRange{first: 0, last: 511},
			targetRange:   portOrIdentRange{first: 0, last: math.MaxUint16},
		},
		{
			name:          "Greater than or equal to 512 but less than 1024",
			originalRange: portOrIdentRange{first: 512, last: 1023},
			targetRange:   portOrIdentRange{first: 0, last: math.MaxUint16},
		},
		{
			name:          "Greater than or equal to 1024",
			originalRange: portOrIdentRange{first: 1024, last: math.MaxUint16},
			targetRange:   portOrIdentRange{first: 0, last: math.MaxUint16},
		},
	}

	type transportTypeTest struct {
		name                 string
		proto                tcpip.TransportProtocolNumber
		buf                  func(tcpip.Address, uint16) []byte
		checkNATed           func(*testing.T, *buffer.View, uint16, bool, portOrIdentRange)
		srcPortOrIdentRanges []srcPortOrIdentRangeTest
	}

	compareSrcPortOrIdent := func(t *testing.T, gotPort uint16, originalSrcPort uint16, firstPacket bool, expectedRange portOrIdentRange) {
		t.Helper()

		if firstPacket {
			if gotPort != originalSrcPort {
				t.Errorf("got port/ident = %d, want = %d", gotPort, originalSrcPort)
			}
			return
		}

		if gotPort < expectedRange.first || gotPort > expectedRange.last {
			t.Errorf("got port/ident = %d, want in range [%d, %d]", gotPort, expectedRange.first, expectedRange.last)
		}
	}

	tests := []struct {
		name           string
		netProto       tcpip.NetworkProtocolNumber
		routerNIC1Addr tcpip.Address
		srcAddrs       []tcpip.Address
		transportTypes []transportTypeTest
	}{
		{
			name:           "IPv4",
			netProto:       ipv4.ProtocolNumber,
			routerNIC1Addr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
			srcAddrs: []tcpip.Address{
				utils.Ipv4Addr1.AddressWithPrefix.Address,
				utils.Ipv4Addr2.AddressWithPrefix.Address,
				utils.Ipv4Addr3.AddressWithPrefix.Address,
			},
			transportTypes: []transportTypeTest{
				{
					name:  "UDP",
					proto: header.UDPProtocolNumber,
					buf: func(srcAddr tcpip.Address, srcPort uint16) []byte {
						return udpv4Packet(srcAddr, utils.Host1IPv4Addr.AddressWithPrefix.Address, srcPort, dstPort, 0 /* dataSize */)
					},
					checkNATed: func(t *testing.T, v *buffer.View, originalSrcPort uint16, firstPacket bool, expectedRange portOrIdentRange) {
						checker.IPv4(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv4Addr.AddressWithPrefix.Address),
							checker.UDP(
								checker.DstPort(dstPort),
							),
						)

						if !t.Failed() {
							compareSrcPortOrIdent(t, header.UDP(header.IPv4(v.AsSlice()).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
						}
					},
					srcPortOrIdentRanges: srcPortRanges,
				},
				{
					name:  "TCP",
					proto: header.TCPProtocolNumber,
					buf: func(srcAddr tcpip.Address, srcPort uint16) []byte {
						return tcpv4Packet(srcAddr, utils.Host1IPv4Addr.AddressWithPrefix.Address, srcPort, dstPort, 0 /* dataSize */)
					},
					checkNATed: func(t *testing.T, v *buffer.View, originalSrcPort uint16, firstPacket bool, expectedRange portOrIdentRange) {
						checker.IPv4(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv4Addr.AddressWithPrefix.Address),
							checker.TCP(
								checker.DstPort(dstPort),
							),
						)

						if !t.Failed() {
							compareSrcPortOrIdent(t, header.TCP(header.IPv4(v.AsSlice()).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
						}
					},
					srcPortOrIdentRanges: srcPortRanges,
				},
				{
					name:  "ICMP Echo",
					proto: header.ICMPv4ProtocolNumber,
					buf: func(srcAddr tcpip.Address, ident uint16) []byte {
						return icmpv4Packet(srcAddr, utils.Host1IPv4Addr.AddressWithPrefix.Address, header.ICMPv4Echo, ident)
					},
					checkNATed: func(t *testing.T, v *buffer.View, originalIdent uint16, firstPacket bool, expectedRange portOrIdentRange) {
						checker.IPv4(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv4Addr.AddressWithPrefix.Address),
							checker.ICMPv4(
								checker.ICMPv4Type(header.ICMPv4Echo),
								checker.ICMPv4Checksum(),
							),
						)

						if !t.Failed() {
							compareSrcPortOrIdent(t, header.ICMPv4(header.IPv4(v.AsSlice()).Payload()).Ident(), originalIdent, firstPacket, expectedRange)
						}
					},
					srcPortOrIdentRanges: identRanges,
				},
			},
		},
		{
			name:           "IPv6",
			netProto:       ipv6.ProtocolNumber,
			routerNIC1Addr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
			srcAddrs: []tcpip.Address{
				utils.Ipv6Addr1.AddressWithPrefix.Address,
				utils.Ipv6Addr2.AddressWithPrefix.Address,
				utils.Ipv6Addr2.AddressWithPrefix.Address,
			},
			transportTypes: []transportTypeTest{
				{
					name:  "UDP",
					proto: header.UDPProtocolNumber,
					buf: func(srcAddr tcpip.Address, srcPort uint16) []byte {
						return udpv6Packet(srcAddr, utils.Host1IPv6Addr.AddressWithPrefix.Address, srcPort, dstPort, 0 /* dataSize */)
					},
					checkNATed: func(t *testing.T, v *buffer.View, originalSrcPort uint16, firstPacket bool, expectedRange portOrIdentRange) {
						checker.IPv6(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv6Addr.AddressWithPrefix.Address),
							checker.UDP(
								checker.DstPort(dstPort),
							),
						)

						if !t.Failed() {
							compareSrcPortOrIdent(t, header.UDP(header.IPv6(v.AsSlice()).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
						}
					},
					srcPortOrIdentRanges: srcPortRanges,
				},
				{
					name:  "TCP",
					proto: header.TCPProtocolNumber,
					buf: func(srcAddr tcpip.Address, srcPort uint16) []byte {
						return tcpv6Packet(srcAddr, utils.Host1IPv6Addr.AddressWithPrefix.Address, srcPort, dstPort, 0 /* dataSize */)
					},
					checkNATed: func(t *testing.T, v *buffer.View, originalSrcPort uint16, firstPacket bool, expectedRange portOrIdentRange) {
						checker.IPv6(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv6Addr.AddressWithPrefix.Address),
							checker.TCP(
								checker.DstPort(dstPort),
							),
						)

						if !t.Failed() {
							compareSrcPortOrIdent(t, header.TCP(header.IPv6(v.AsSlice()).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
						}
					},
					srcPortOrIdentRanges: srcPortRanges,
				},
				{
					name:  "ICMP Echo",
					proto: header.ICMPv6ProtocolNumber,
					buf: func(srcAddr tcpip.Address, ident uint16) []byte {
						return icmpv6Packet(srcAddr, utils.Host1IPv6Addr.AddressWithPrefix.Address, header.ICMPv6EchoRequest, ident)
					},
					checkNATed: func(t *testing.T, v *buffer.View, originalIdent uint16, firstPacket bool, expectedRange portOrIdentRange) {
						checker.IPv6(t, v,
							checker.SrcAddr(utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address),
							checker.DstAddr(utils.Host1IPv6Addr.AddressWithPrefix.Address),
							checker.ICMPv6(
								checker.ICMPv6Type(header.ICMPv6EchoRequest),
							),
						)

						if !t.Failed() {
							compareSrcPortOrIdent(t, header.ICMPv6(header.IPv6(v.AsSlice()).Payload()).Ident(), originalIdent, firstPacket, expectedRange)
						}
					},
					srcPortOrIdentRanges: identRanges,
				},
			},
		},
	}

	natTypes := []struct {
		name   string
		target func(tcpip.NetworkProtocolNumber, tcpip.Address) stack.Target
	}{
		{
			name: "Masquerade",
			target: func(netProto tcpip.NetworkProtocolNumber, _ tcpip.Address) stack.Target {
				return &stack.MasqueradeTarget{NetworkProtocol: netProto}
			},
		},
		{
			name: "SNAT",
			target: func(netProto tcpip.NetworkProtocolNumber, addr tcpip.Address) stack.Target {
				return &stack.SNATTarget{NetworkProtocol: netProto, Addr: addr, ChangeAddress: true, ChangePort: true}
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, transportType := range test.transportTypes {
				t.Run(transportType.name, func(t *testing.T) {
					for _, natType := range natTypes {
						t.Run(natType.name, func(t *testing.T) {
							for _, srcPortOrIdentRange := range transportType.srcPortOrIdentRanges {
								t.Run(srcPortOrIdentRange.name, func(t *testing.T) {
									for _, srcPortOrIdent := range [2]uint16{srcPortOrIdentRange.originalRange.first, srcPortOrIdentRange.originalRange.last} {
										t.Run(fmt.Sprintf("OriginalSrcPortOrIdent=%d", srcPortOrIdent), func(t *testing.T) {
											s := stack.New(stack.Options{
												NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
												TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
											})
											defer s.Destroy()

											ep1 := channel.New(1, header.IPv6MinimumMTU, "")
											ep2 := channel.New(1, header.IPv6MinimumMTU, "")
											utils.SetupRouterStack(t, s, ep1, ep2)

											ipv6 := test.netProto == ipv6.ProtocolNumber
											ipt := s.IPTables()

											table := stack.Table{
												Rules: []stack.Rule{
													// Prerouting
													{
														Target: &stack.AcceptTarget{},
													},

													// Input
													{
														Target: &stack.AcceptTarget{},
													},

													// Forward
													{
														Target: &stack.AcceptTarget{},
													},

													// Output
													{
														Target: &stack.AcceptTarget{},
													},

													// Postrouting
													{
														Filter: stack.IPHeaderFilter{
															Protocol:        transportType.proto,
															CheckProtocol:   true,
															OutputInterface: utils.RouterNIC1Name,
														},
														Target: natType.target(test.netProto, test.routerNIC1Addr),
													},
													{
														Target: &stack.AcceptTarget{},
													},
												},
												BuiltinChains: [stack.NumHooks]int{
													stack.Prerouting:  0,
													stack.Input:       1,
													stack.Forward:     2,
													stack.Output:      3,
													stack.Postrouting: 4,
												},
											}

											ipt.ForceReplaceTable(stack.NATID, table, ipv6)

											for i, srcAddr := range test.srcAddrs {
												t.Run(fmt.Sprintf("Packet#%d", i), func(t *testing.T) {
													ep2.InjectInbound(test.netProto, stack.NewPacketBuffer(stack.PacketBufferOptions{
														Payload: buffer.MakeWithData(transportType.buf(srcAddr, srcPortOrIdent)),
													}))

													pkt := ep1.Read()
													if pkt == nil {
														t.Fatal("expected to read a packet on ep1")
													}
													pktView := stack.PayloadSince(pkt.NetworkHeader())
													defer pktView.Release()
													pkt.DecRef()
													transportType.checkNATed(t, pktView, srcPortOrIdent, i == 0, srcPortOrIdentRange.targetRange)
												})
											}
										})
									}
								})
							}
						})
					}
				})
			}
		})
	}
}

func TestSNATLocallyGeneratedTrafficPorts(t *testing.T) {
	s := stack.New(stack.Options{
		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
		TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
	})
	defer s.Destroy()

	ep1 := channel.New(1, header.IPv4MinimumMTU, "")
	ep2 := channel.New(1, header.IPv4MinimumMTU, "")
	utils.SetupRouterStack(t, s, ep1, ep2)

	// Configure Masquerade NAT on the router stack.
	ipt := s.IPTables()
	table := stack.Table{
		Rules: []stack.Rule{
			// Prerouting
			{
				Target: &stack.AcceptTarget{},
			},

			// Input
			{
				Target: &stack.AcceptTarget{},
			},

			// Forward
			{
				Target: &stack.AcceptTarget{},
			},

			// Output
			{
				Target: &stack.AcceptTarget{},
			},

			// Postrouting
			{
				Filter: stack.IPHeaderFilter{
					Protocol:        udp.ProtocolNumber,
					CheckProtocol:   true,
					OutputInterface: utils.RouterNIC2Name,
				},
				Target: &stack.MasqueradeTarget{NetworkProtocol: ipv4.ProtocolNumber},
			},
			{
				Target: &stack.AcceptTarget{},
			},
		},
		BuiltinChains: [stack.NumHooks]int{
			stack.Prerouting:  0,
			stack.Input:       1,
			stack.Forward:     2,
			stack.Output:      3,
			stack.Postrouting: 4,
		},
	}
	ipt.ForceReplaceTable(stack.NATID, table, false /* ipv6 */)

	routerNIC2Addr := utils.RouterNIC2IPv4Addr.AddressWithPrefix.Address
	ep1Addr := utils.Host1IPv4Addr.AddressWithPrefix.Address
	var ep1Port uint16 = 1234
	ep2Addr := utils.Host2IPv4Addr.AddressWithPrefix.Address
	var ep2Port uint16 = 2345

	// Inject an incoming packet on NIC1 destined to an address that will be
	// routed out of NIC2. Expect that we can read the packet on ep2 coming from
	// the stack's address assigned on NIC2, because it should have performed
	// Masquerade NAT on the forwarded traffic.
	ep1.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(udpv4Packet(ep1Addr, ep2Addr, ep1Port, ep2Port, 0 /* dataSize */)),
	}))
	pkt := ep2.Read()
	if pkt == nil {
		t.Fatal("expected to read a packet on ep2")
	}
	pktView := stack.PayloadSince(pkt.NetworkHeader())
	defer pktView.Release()
	pkt.DecRef()
	checker.IPv4(t, pktView,
		checker.SrcAddr(routerNIC2Addr),
		checker.DstAddr(ep2Addr),
		checker.UDP(
			checker.SrcPort(ep1Port),
			checker.DstPort(ep2Port),
		),
	)

	// Now bind a UDP socket on the stack itself to the same port used by the
	// previous packet, and send a packet to the same address.
	var wq waiter.Queue
	we, ch := waiter.NewChannelEntry(waiter.ReadableEvents)
	wq.EventRegister(&we)
	defer wq.EventUnregister(&we)

	ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
	if err != nil {
		t.Fatalf("s.NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, ipv4.ProtocolNumber, err)
	}
	defer ep.Close()

	srcAddr := tcpip.FullAddress{Addr: routerNIC2Addr, Port: ep1Port}
	if err := ep.Bind(srcAddr); err != nil {
		t.Fatalf("ep.Bind(%#v): %s", srcAddr, err)
	}
	dstAddr := tcpip.FullAddress{Addr: ep2Addr, Port: ep2Port}
	if err := ep.Connect(dstAddr); err != nil {
		t.Fatalf("ep.Connect(%#v): %s", dstAddr, err)
	}

	data := []byte{1, 2, 3, 4}
	var r bytes.Reader
	r.Reset(data)
	var wOpts tcpip.WriteOptions
	n, err := ep.Write(&r, wOpts)
	if err != nil {
		t.Fatalf("ep.Write(_, %#v): %s", wOpts, err)
	}
	if want := int64(len(data)); n != want {
		t.Fatalf("got ep.Write(_, %#v) = (%d, _), want = (%d, _)", wOpts, n, want)
	}

	// The router should perform source port remapping for the locally generated
	// traffic so that it does not conflict with the existing conntrack entry, so
	// ep2 should observe the traffic as coming from the router's address, but
	// *not* from the same port as the traffic from ep1 before.
	pkt = ep2.Read()
	if pkt == nil {
		t.Fatal("expected to read a packet on ep2")
	}
	pktView = stack.PayloadSince(pkt.NetworkHeader())
	defer pktView.Release()
	pkt.DecRef()
	checker.IPv4(t, pktView,
		checker.SrcAddr(routerNIC2Addr),
		checker.DstAddr(ep2Addr),
		checker.UDP(
			checker.DstPort(ep2Port),
			checker.Payload(data),
		),
	)
	gotPort := header.UDP(header.IPv4(pktView.AsSlice()).Payload()).SourcePort()
	if gotPort == ep1Port {
		t.Errorf("got src port == ep1Port (%d), should be remapped to avoid conflict", gotPort)
	}

	// We should also be able to reply on either connection, by injecting inbound
	// traffic on ep2 destined to the router.
	//
	// Traffic destined to the port originally used in the traffic injected on ep1
	// should go to ep1.
	ep2.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(udpv4Packet(ep2Addr, routerNIC2Addr, ep2Port, ep1Port, 0 /* dataSize */)),
	}))
	pkt = ep1.Read()
	if pkt == nil {
		t.Fatal("expected to read a packet on ep2")
	}
	pktView = stack.PayloadSince(pkt.NetworkHeader())
	defer pktView.Release()
	pkt.DecRef()
	checker.IPv4(t, pktView,
		checker.SrcAddr(ep2Addr),
		checker.DstAddr(ep1Addr),
		checker.UDP(
			checker.SrcPort(ep2Port),
			checker.DstPort(ep1Port),
		),
	)

	// And traffic destined to the remapped source port chosen by conntrack for
	// the socket bound on the stack should go to the socket.
	reply := udpv4Packet(ep2Addr, routerNIC2Addr, ep2Port, gotPort, 0 /* dataSize */)
	reply = append(reply, data...)
	ep2.InjectInbound(ipv4.ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
		Payload: buffer.MakeWithData(reply),
	}))
	var buf bytes.Buffer
	var res tcpip.ReadResult
	for {
		var err tcpip.Error
		res, err = ep.Read(&buf, tcpip.ReadOptions{})
		if _, ok := err.(*tcpip.ErrWouldBlock); ok {
			<-ch
			continue
		}
		if err != nil {
			t.Fatalf("ep.Read(_, {}): %s", err)
		}
		break
	}
	if diff := cmp.Diff(
		tcpip.ReadResult{
			Count: 0,
			Total: 0,
		},
		res,
		checker.IgnoreCmpPath("ControlMessages"),
	); diff != "" {
		t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
	}
}

func TestLocallyRoutedPackets(t *testing.T) {
	const nicID = 1

	tests := []struct {
		name     string
		netProto tcpip.NetworkProtocolNumber
		addr     tcpip.Address
	}{
		{
			name:     "IPv4",
			netProto: ipv4.ProtocolNumber,
			addr:     utils.Host1IPv4Addr.AddressWithPrefix.Address,
		},
		{
			name:     "IPv6",
			netProto: ipv6.ProtocolNumber,
			addr:     utils.Host1IPv6Addr.AddressWithPrefix.Address,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
			})
			defer s.Destroy()

			if err := s.CreateNIC(nicID, loopback.New()); err != nil {
				t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
			}
			protocolAddr := tcpip.ProtocolAddress{
				Protocol:          test.netProto,
				AddressWithPrefix: test.addr.WithPrefix(),
			}
			if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
				t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
			}

			s.SetRouteTable([]tcpip.Route{
				{
					Destination: protocolAddr.AddressWithPrefix.Subnet(),
					NIC:         nicID,
				},
			})

			// Set IPTables so we create entries in the conntrack table.
			{
				ipv6 := test.netProto == ipv6.ProtocolNumber
				ipt := s.IPTables()
				filter := ipt.GetTable(stack.FilterID, ipv6)
				ipt.ForceReplaceTable(stack.FilterID, filter, ipv6)
			}

			var wq waiter.Queue
			we, ch := waiter.NewChannelEntry(waiter.ReadableEvents)
			wq.EventRegister(&we)
			defer wq.EventUnregister(&we)

			ep, err := s.NewEndpoint(udp.ProtocolNumber, test.netProto, &wq)
			if err != nil {
				t.Fatalf("s.NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, test.netProto, err)
			}
			defer ep.Close()

			fullAddr := tcpip.FullAddress{Addr: test.addr, Port: 1234}
			if err := ep.Bind(fullAddr); err != nil {
				t.Fatalf("ep.Bind(%#v): %s", fullAddr, err)
			}
			if err := ep.Connect(fullAddr); err != nil {
				t.Fatalf("ep.Connect(%#v): %s", fullAddr, err)
			}

			data := []byte{1, 2, 3, 4}

			var r bytes.Reader
			r.Reset(data)
			var wOpts tcpip.WriteOptions
			n, err := ep.Write(&r, wOpts)
			if err != nil {
				t.Fatalf("ep.Write(_, %#v): %s", wOpts, err)
			}
			if want := int64(len(data)); n != want {
				t.Fatalf("got ep.Write(_, %#v) = (%d, _), want = (%d, _)", wOpts, n, want)
			}

			var buf bytes.Buffer
			var res tcpip.ReadResult
			for {
				var err tcpip.Error
				res, err = ep.Read(&buf, tcpip.ReadOptions{})
				if _, ok := err.(*tcpip.ErrWouldBlock); ok {
					<-ch
					continue
				}
				if err != nil {
					t.Fatalf("ep.Read(_, {}): %s", err)
				}
				break
			}
			if diff := cmp.Diff(
				tcpip.ReadResult{
					Count: len(data),
					Total: len(data),
				},
				res,
				checker.IgnoreCmpPath("ControlMessages"),
			); diff != "" {
				t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
			}
			if diff := cmp.Diff(buf.Bytes(), data); diff != "" {
				t.Errorf("received data mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

type icmpv4Matcher struct {
	icmpType header.ICMPv4Type
}

func (m *icmpv4Matcher) Match(_ stack.Hook, pkt *stack.PacketBuffer, _, _ string) (matches bool, hotdrop bool) {
	if pkt.NetworkProtocolNumber != header.IPv4ProtocolNumber {
		return false, false
	}

	if pkt.TransportProtocolNumber != header.ICMPv4ProtocolNumber {
		return false, false
	}

	return header.ICMPv4(pkt.TransportHeader().Slice()).Type() == m.icmpType, false
}

type icmpv6Matcher struct {
	icmpType header.ICMPv6Type
}

func (m *icmpv6Matcher) Match(_ stack.Hook, pkt *stack.PacketBuffer, _, _ string) (matches bool, hotdrop bool) {
	if pkt.NetworkProtocolNumber != header.IPv6ProtocolNumber {
		return false, false
	}

	if pkt.TransportProtocolNumber != header.ICMPv6ProtocolNumber {
		return false, false
	}

	return header.ICMPv6(pkt.TransportHeader().Slice()).Type() == m.icmpType, false
}

func TestRejectWith(t *testing.T) {
	type natHook struct {
		hook    stack.Hook
		dstAddr tcpip.Address
		matcher stack.Matcher

		errorICMPDstAddr tcpip.Address
		errorICMPPayload []byte
	}

	type rejectWithVal struct {
		name          string
		val           int
		errorICMPCode uint8
	}

	rxICMPv4EchoRequest := func(dst tcpip.Address) []byte {
		return utils.ICMPv4Echo(utils.Host1IPv4Addr.AddressWithPrefix.Address, dst, ttl, header.ICMPv4Echo)
	}

	rxICMPv6EchoRequest := func(dst tcpip.Address) []byte {
		return utils.ICMPv6Echo(utils.Host1IPv6Addr.AddressWithPrefix.Address, dst, ttl, header.ICMPv6EchoRequest)
	}

	tests := []struct {
		name              string
		netProto          tcpip.NetworkProtocolNumber
		rxICMPEchoRequest func(tcpip.Address) []byte
		icmpChecker       func(*testing.T, *buffer.View, tcpip.Address, uint8, uint8, []byte)

		natHooks []natHook

		rejectTarget   func(*testing.T, stack.NetworkProtocol, int) stack.Target
		rejectWithVals []rejectWithVal
		errorICMPType  uint8
	}{
		{
			name:              "IPv4",
			netProto:          header.IPv4ProtocolNumber,
			rxICMPEchoRequest: rxICMPv4EchoRequest,

			icmpChecker: func(t *testing.T, v *buffer.View, dstAddr tcpip.Address, icmpType, icmpCode uint8, origPayload []byte) {
				t.Helper()

				checker.IPv4(t, v,
					checker.SrcAddr(utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address),
					checker.DstAddr(dstAddr),
					checker.ICMPv4(
						checker.ICMPv4Checksum(),
						checker.ICMPv4Type(header.ICMPv4Type(icmpType)),
						checker.ICMPv4Code(header.ICMPv4Code(icmpCode)),
						checker.ICMPv4Payload(origPayload),
					),
				)
			},
			natHooks: []natHook{
				{
					hook:             stack.Input,
					dstAddr:          utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
					matcher:          &icmpv4Matcher{icmpType: header.ICMPv4Echo},
					errorICMPDstAddr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
					errorICMPPayload: rxICMPv4EchoRequest(utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address),
				},
				{
					hook:             stack.Forward,
					dstAddr:          utils.Host2IPv4Addr.AddressWithPrefix.Address,
					matcher:          &icmpv4Matcher{icmpType: header.ICMPv4Echo},
					errorICMPDstAddr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
					errorICMPPayload: rxICMPv4EchoRequest(utils.Host2IPv4Addr.AddressWithPrefix.Address),
				},
				{
					hook:             stack.Output,
					dstAddr:          utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
					matcher:          &icmpv4Matcher{icmpType: header.ICMPv4EchoReply},
					errorICMPDstAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
					errorICMPPayload: utils.ICMPv4Echo(utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address, utils.Host1IPv4Addr.AddressWithPrefix.Address, ttl, header.ICMPv4EchoReply),
				},
			},
			rejectTarget: func(t *testing.T, netProto stack.NetworkProtocol, rejectWith int) stack.Target {
				handler, ok := netProto.(stack.RejectIPv4WithHandler)
				if !ok {
					t.Fatalf("expected %T to implement %T", netProto, handler)
				}

				return &stack.RejectIPv4Target{
					Handler:    handler,
					RejectWith: stack.RejectIPv4WithICMPType(rejectWith),
				}
			},
			rejectWithVals: []rejectWithVal{
				{
					name:          "ICMP Network Unreachable",
					val:           int(stack.RejectIPv4WithICMPNetUnreachable),
					errorICMPCode: uint8(header.ICMPv4NetUnreachable),
				},
				{
					name:          "ICMP Host Unreachable",
					val:           int(stack.RejectIPv4WithICMPHostUnreachable),
					errorICMPCode: uint8(header.ICMPv4HostUnreachable),
				},
				{
					name:          "ICMP Port Unreachable",
					val:           int(stack.RejectIPv4WithICMPPortUnreachable),
					errorICMPCode: uint8(header.ICMPv4PortUnreachable),
				},
				{
					name:          "ICMP Network Prohibited",
					val:           int(stack.RejectIPv4WithICMPNetProhibited),
					errorICMPCode: uint8(header.ICMPv4NetProhibited),
				},
				{
					name:          "ICMP Host Prohibited",
					val:           int(stack.RejectIPv4WithICMPHostProhibited),
					errorICMPCode: uint8(header.ICMPv4HostProhibited),
				},
				{
					name:          "ICMP Administratively Prohibited",
					val:           int(stack.RejectIPv4WithICMPAdminProhibited),
					errorICMPCode: uint8(header.ICMPv4AdminProhibited),
				},
			},
			errorICMPType: uint8(header.ICMPv4DstUnreachable),
		},
		{
			name:              "IPv6",
			netProto:          header.IPv6ProtocolNumber,
			rxICMPEchoRequest: rxICMPv6EchoRequest,

			icmpChecker: func(t *testing.T, v *buffer.View, dstAddr tcpip.Address, icmpType, icmpCode uint8, origPayload []byte) {
				t.Helper()

				checker.IPv6(t, v,
					checker.SrcAddr(utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address),
					checker.DstAddr(dstAddr),
					checker.ICMPv6(
						checker.ICMPv6Type(header.ICMPv6Type(icmpType)),
						checker.ICMPv6Code(header.ICMPv6Code(icmpCode)),
						checker.ICMPv6Payload(origPayload),
					),
				)
			},
			natHooks: []natHook{
				{
					hook:             stack.Input,
					dstAddr:          utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
					matcher:          &icmpv6Matcher{icmpType: header.ICMPv6EchoRequest},
					errorICMPDstAddr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
					errorICMPPayload: rxICMPv6EchoRequest(utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address),
				},
				{
					hook:             stack.Forward,
					dstAddr:          utils.Host2IPv6Addr.AddressWithPrefix.Address,
					matcher:          &icmpv6Matcher{icmpType: header.ICMPv6EchoRequest},
					errorICMPDstAddr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
					errorICMPPayload: rxICMPv6EchoRequest(utils.Host2IPv6Addr.AddressWithPrefix.Address),
				},
				{
					hook:             stack.Output,
					dstAddr:          utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
					matcher:          &icmpv6Matcher{icmpType: header.ICMPv6EchoReply},
					errorICMPDstAddr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
					errorICMPPayload: utils.ICMPv6Echo(utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address, utils.Host1IPv6Addr.AddressWithPrefix.Address, ttl, header.ICMPv6EchoReply),
				},
			},
			rejectTarget: func(t *testing.T, netProto stack.NetworkProtocol, rejectWith int) stack.Target {
				handler, ok := netProto.(stack.RejectIPv6WithHandler)
				if !ok {
					t.Fatalf("expected %T to implement %T", netProto, handler)
				}

				return &stack.RejectIPv6Target{
					Handler:    handler,
					RejectWith: stack.RejectIPv6WithICMPType(rejectWith),
				}
			},
			rejectWithVals: []rejectWithVal{
				{
					name:          "ICMP No Route",
					val:           int(stack.RejectIPv6WithICMPNoRoute),
					errorICMPCode: uint8(header.ICMPv6NetworkUnreachable),
				},
				{
					name:          "ICMP Address Unreachable",
					val:           int(stack.RejectIPv6WithICMPAddrUnreachable),
					errorICMPCode: uint8(header.ICMPv6AddressUnreachable),
				},
				{
					name:          "ICMP Port Unreachable",
					val:           int(stack.RejectIPv6WithICMPPortUnreachable),
					errorICMPCode: uint8(header.ICMPv6PortUnreachable),
				},
				{
					name:          "ICMP Administratively Prohibited",
					val:           int(stack.RejectIPv6WithICMPAdminProhibited),
					errorICMPCode: uint8(header.ICMPv6Prohibited),
				},
			},
			errorICMPType: uint8(header.ICMPv6DstUnreachable),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			for _, natHook := range test.natHooks {
				t.Run(natHook.hook.String(), func(t *testing.T) {
					for _, rejectWith := range test.rejectWithVals {
						t.Run(rejectWith.name, func(t *testing.T) {
							s := stack.New(stack.Options{
								NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
								TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
							})
							defer s.Destroy()

							ep1 := channel.New(1, header.IPv6MinimumMTU, "")
							ep2 := channel.New(1, header.IPv6MinimumMTU, "")
							utils.SetupRouterStack(t, s, ep1, ep2)

							{
								ipv6 := test.netProto == ipv6.ProtocolNumber
								ipt := s.IPTables()
								filter := ipt.GetTable(stack.FilterID, ipv6)
								ruleIdx := filter.BuiltinChains[natHook.hook]
								filter.Rules[ruleIdx].Matchers = []stack.Matcher{natHook.matcher}
								filter.Rules[ruleIdx].Target = test.rejectTarget(t, s.NetworkProtocolInstance(test.netProto), rejectWith.val)
								// Make sure the packet is not dropped by the next rule.
								filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
								ipt.ForceReplaceTable(stack.FilterID, filter, ipv6)
							}

							func() {
								pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
									Payload: buffer.MakeWithData(test.rxICMPEchoRequest(natHook.dstAddr)),
								})
								defer pkt.DecRef()
								ep1.InjectInbound(test.netProto, pkt)
							}()

							{
								pkt := ep1.Read()
								if pkt == nil {
									t.Fatal("expected to read a packet on ep1")
								}
								payload := stack.PayloadSince(pkt.NetworkHeader())
								defer payload.Release()
								test.icmpChecker(
									t,
									payload,
									natHook.errorICMPDstAddr,
									test.errorICMPType,
									rejectWith.errorICMPCode,
									natHook.errorICMPPayload,
								)
								pkt.DecRef()
							}
						})
					}
				})
			}
		})
	}
}

// Validates that the matched packets get actively aborted
// (with a valid TCP Reset packet).
// Unmatched packets (e.g. wrong protocols, subsequent fragments) are silently
// ignored and generate no TCP Reset packet.
func TestRejectWithTCPReset(t *testing.T) {
	const seq = 100
	tests := []struct {
		name       string
		netProto   tcpip.NetworkProtocolNumber
		srcAddr    tcpip.Address
		dstAddr    tcpip.Address
		rejectWith int
		genTCP     func(src, dst tcpip.Address) *stack.PacketBuffer
		wantReset  bool
	}{
		{
			name:       "IPv4",
			netProto:   header.IPv4ProtocolNumber,
			srcAddr:    utils.Host1IPv4Addr.AddressWithPrefix.Address,
			dstAddr:    utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
			rejectWith: int(stack.RejectIPv4WithTCPReset),
			genTCP: func(src, dst tcpip.Address) *stack.PacketBuffer {
				return genTCP4WithOpts(tcpOpts{
					srcAddr:    src,
					dstAddr:    dst,
					seq:        seq,
					flags:      header.TCPFlagSyn,
					pktSize:    header.IPv4MinimumSize + header.TCPMinimumSize,
					dataOffset: header.TCPMinimumSize,
				})
			},
			wantReset: true,
		},
		{
			name:       "IPv6",
			netProto:   header.IPv6ProtocolNumber,
			srcAddr:    utils.Host1IPv6Addr.AddressWithPrefix.Address,
			dstAddr:    utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
			rejectWith: int(stack.RejectIPv6WithTCPReset),
			genTCP: func(src, dst tcpip.Address) *stack.PacketBuffer {
				return genTCP6WithOpts(tcpOpts{
					srcAddr:    src,
					dstAddr:    dst,
					seq:        seq,
					flags:      header.TCPFlagSyn,
					pktSize:    header.IPv6MinimumSize + header.TCPMinimumSize,
					dataOffset: header.TCPMinimumSize,
				})
			},
			wantReset: true,
		},
		{
			name:       "IPv6 First Fragment",
			netProto:   header.IPv6ProtocolNumber,
			srcAddr:    utils.Host1IPv6Addr.AddressWithPrefix.Address,
			dstAddr:    utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
			rejectWith: int(stack.RejectIPv6WithTCPReset),
			genTCP: func(src, dst tcpip.Address) *stack.PacketBuffer {
				return genTCP6WithOpts(tcpOpts{
					srcAddr:    src,
					dstAddr:    dst,
					seq:        seq,
					flags:      header.TCPFlagSyn,
					pktSize:    header.IPv6MinimumSize + header.IPv6FragmentExtHdrLength + header.TCPMinimumSize,
					dataOffset: header.TCPMinimumSize,
					more:       true,
				})
			},
			wantReset: true,
		},
		{
			name:       "IPv6 Subsequent Fragment",
			netProto:   header.IPv6ProtocolNumber,
			srcAddr:    utils.Host1IPv6Addr.AddressWithPrefix.Address,
			dstAddr:    utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
			rejectWith: int(stack.RejectIPv6WithTCPReset),
			genTCP: func(src, dst tcpip.Address) *stack.PacketBuffer {
				return genTCP6WithOpts(tcpOpts{
					srcAddr:        src,
					dstAddr:        dst,
					seq:            seq,
					flags:          header.TCPFlagSyn,
					pktSize:        header.IPv6MinimumSize + header.IPv6FragmentExtHdrLength + header.TCPMinimumSize,
					dataOffset:     header.TCPMinimumSize,
					fragmentOffset: 8,
					more:           true,
				})
			},
			wantReset: false,
		},
		{
			name:       "IPv4 Subsequent Fragment",
			netProto:   header.IPv4ProtocolNumber,
			srcAddr:    utils.Host1IPv4Addr.AddressWithPrefix.Address,
			dstAddr:    utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
			rejectWith: int(stack.RejectIPv4WithTCPReset),
			genTCP: func(src, dst tcpip.Address) *stack.PacketBuffer {
				return genTCP4WithOpts(tcpOpts{
					srcAddr:        src,
					dstAddr:        dst,
					seq:            seq,
					flags:          header.TCPFlagSyn,
					pktSize:        header.IPv4MinimumSize + header.TCPMinimumSize,
					dataOffset:     header.TCPMinimumSize,
					fragmentOffset: 8,
				})
			},
			wantReset: false,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s := stack.New(stack.Options{
				NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
				TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
			})
			defer s.Destroy()

			ep1 := channel.New(1, header.IPv6MinimumMTU, "")
			ep1.LinkEPCapabilities = stack.CapabilityRXChecksumOffload
			ep2 := channel.New(1, header.IPv6MinimumMTU, "")
			utils.SetupRouterStack(t, s, ep1, ep2)

			// Add a rule in the INPUT chain to reject TCP packets.
			ipv6 := test.netProto == ipv6.ProtocolNumber
			ipt := s.IPTables()
			filter := ipt.GetTable(stack.FilterID, ipv6)
			ruleIdx := filter.BuiltinChains[stack.Input]

			// Setup reject target.
			var rejectTarget stack.Target
			netInst := s.NetworkProtocolInstance(test.netProto)
			if test.netProto == ipv4.ProtocolNumber {
				rejectTarget = &stack.RejectIPv4Target{
					Handler:    netInst.(stack.RejectIPv4WithHandler),
					RejectWith: stack.RejectIPv4WithICMPType(test.rejectWith),
				}
			} else {
				rejectTarget = &stack.RejectIPv6Target{
					Handler:    netInst.(stack.RejectIPv6WithHandler),
					RejectWith: stack.RejectIPv6WithICMPType(test.rejectWith),
				}
			}
			filter.Rules[ruleIdx].Target = rejectTarget

			// Make sure the packet is not dropped by the next rule.
			filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
			ipt.ForceReplaceTable(stack.FilterID, filter, ipv6)

			// Inject a TCP packet.
			pkt := test.genTCP(test.srcAddr, test.dstAddr)
			pkt.RXChecksumValidated = true
			defer pkt.DecRef()
			ep1.InjectInbound(test.netProto, pkt)

			// Read the reset packet.
			rstPkt := ep1.Read()
			if !test.wantReset {
				if rstPkt != nil {
					t.Fatalf("expected no reset packet, but got one: %#v", rstPkt)
				}
				return
			}
			if rstPkt == nil {
				t.Fatal("expected to read a TCP Reset packet on ep1")
			}
			defer rstPkt.DecRef()

			payload := stack.PayloadSince(rstPkt.NetworkHeader())
			defer payload.Release()

			if test.netProto == ipv4.ProtocolNumber {
				checker.IPv4(t, payload,
					checker.SrcAddr(test.dstAddr),
					checker.DstAddr(test.srcAddr),
					checker.TCP(
						checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
						checker.TCPAckNum(101),
						checker.TCPSeqNum(0),
					),
				)
			} else {
				checker.IPv6(t, payload,
					checker.SrcAddr(test.dstAddr),
					checker.DstAddr(test.srcAddr),
					checker.TCP(
						checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
						checker.TCPAckNum(101),
						checker.TCPSeqNum(0),
					),
				)
			}
		})
	}
}

// TestInvalidTransportHeader tests that bad transport headers (with a bad
// length/offset field) don't panic.
func TestInvalidTransportHeader(t *testing.T) {
	tests := []struct {
		name       string
		setupStack func(*testing.T) (*stack.Stack, *channel.Endpoint)
		genPacket  func(int8) *stack.PacketBuffer
		offset     int8
	}{
		{
			name:       "TCP4 offset small",
			setupStack: genStackV4,
			genPacket:  genTCP4,
			offset:     -1,
		},
		{
			name:       "TCP4 offset large",
			setupStack: genStackV4,
			genPacket:  genTCP4,
			offset:     1,
		},
		{
			name:       "UDP4 offset small",
			setupStack: genStackV4,
			genPacket:  genUDP4,
			offset:     -1,
		},
		{
			name:       "UDP4 offset large",
			setupStack: genStackV4,
			genPacket:  genUDP4,
			offset:     1,
		},
		{
			name:       "TCP6 offset small",
			setupStack: genStackV6,
			genPacket:  genTCP6,
			offset:     -1,
		},
		{
			name:       "TCP6 offset large",
			setupStack: genStackV6,
			genPacket:  genTCP6,
			offset:     1,
		},
		{
			name:       "UDP6 offset small",
			setupStack: genStackV6,
			genPacket:  genUDP6,
			offset:     -1,
		},
		{
			name:       "UDP6 offset large",
			setupStack: genStackV6,
			genPacket:  genUDP6,
			offset:     1,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			s, e := test.setupStack(t)

			// Enable iptables and conntrack.
			ipt := s.IPTables()
			filter := ipt.GetTable(stack.FilterID, false /* ipv6 */)
			ipt.ForceReplaceTable(stack.FilterID, filter, false /* ipv6 */)

			// This can panic if conntrack isn't checking lengths.
			e.InjectInbound(header.IPv4ProtocolNumber, test.genPacket(test.offset))
		})
	}
}

type tcpOpts struct {
	offset         int8
	flags          header.TCPFlags
	seq            uint32
	ack            uint32
	checksum       *uint16
	srcAddr        tcpip.Address
	dstAddr        tcpip.Address
	pktSize        int
	dataOffset     uint8
	fragmentOffset uint16
	more           bool
}

func genTCP4WithOpts(opts tcpOpts) *stack.PacketBuffer {
	pktSize := opts.pktSize
	hdr := prependable.New(pktSize)

	tcp := header.TCP(hdr.Prepend(header.TCPMinimumSize))
	tcp.Encode(&header.TCPFields{
		SeqNum:     opts.seq,
		AckNum:     opts.ack,
		DataOffset: opts.dataOffset, // DataOffset must be a multiple of 4.
		Flags:      opts.flags,
	})
	if opts.checksum != nil {
		tcp.SetChecksum(*opts.checksum)
	} else {
		xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, opts.srcAddr, opts.dstAddr, header.TCPMinimumSize)
		tcp.SetChecksum(0)
		tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
	}

	ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
	ip.Encode(&header.IPv4Fields{
		TotalLength:    uint16(pktSize),
		ID:             1,
		FragmentOffset: opts.fragmentOffset,
		TTL:            48,
		Protocol:       uint8(header.TCPProtocolNumber),
		SrcAddr:        opts.srcAddr,
		DstAddr:        opts.dstAddr,
	})
	ip.SetChecksum(0)
	ip.SetChecksum(^ip.CalculateChecksum())

	buf := buffer.MakeWithData(append([]byte{}, hdr.View()...))
	return stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buf})
}

func genTCP6WithOpts(opts tcpOpts) *stack.PacketBuffer {
	pktSize := opts.pktSize
	hdr := prependable.New(pktSize)

	tcp := header.TCP(hdr.Prepend(header.TCPMinimumSize))
	tcp.Encode(&header.TCPFields{
		SeqNum:     opts.seq,
		AckNum:     opts.ack,
		DataOffset: opts.dataOffset, // DataOffset must be a multiple of 4.
		Flags:      opts.flags,
	})
	if opts.checksum != nil {
		tcp.SetChecksum(*opts.checksum)
	} else {
		xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, opts.srcAddr, opts.dstAddr, header.TCPMinimumSize)
		tcp.SetChecksum(0)
		tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
	}

	nextHeader := header.TCPProtocolNumber
	if opts.fragmentOffset > 0 || opts.more {
		s := header.IPv6ExtHdrSerializer{&header.IPv6SerializableFragmentExtHdr{
			FragmentOffset: opts.fragmentOffset,
			M:              opts.more,
			Identification: 12345,
		}}
		extBuf := hdr.Prepend(s.Length())
		s.Serialize(header.TCPProtocolNumber, extBuf)
		nextHeader = header.IPv6FragmentHeader
	}

	ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
	ip.Encode(&header.IPv6Fields{
		PayloadLength:     uint16(pktSize - header.IPv6MinimumSize),
		TransportProtocol: nextHeader,
		HopLimit:          64,
		SrcAddr:           opts.srcAddr,
		DstAddr:           opts.dstAddr,
	})

	buf := buffer.MakeWithData(append([]byte{}, hdr.View()...))
	return stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buf})
}

func genTCP4(offset int8) *stack.PacketBuffer {
	checksum := uint16(0)
	return genTCP4WithOpts(tcpOpts{
		pktSize:    header.IPv4MinimumSize + header.TCPMinimumSize,
		srcAddr:    srcAddrV4,
		dstAddr:    dstAddrV4,
		dataOffset: header.TCPMinimumSize + uint8(offset)*4,
		flags:      header.TCPFlagSyn,
		checksum:   &checksum,
	})
}

func genTCP6(offset int8) *stack.PacketBuffer {
	checksum := uint16(0)
	return genTCP6WithOpts(tcpOpts{
		pktSize:    header.IPv6MinimumSize + header.TCPMinimumSize,
		srcAddr:    srcAddrV6,
		dstAddr:    dstAddrV6,
		dataOffset: header.TCPMinimumSize + uint8(offset)*4,
		flags:      header.TCPFlagSyn,
		checksum:   &checksum,
	})
}

func genUDP4(offset int8) *stack.PacketBuffer {
	pktSize := header.IPv4MinimumSize + header.UDPMinimumSize
	hdr := prependable.New(pktSize)

	udp := header.UDP(hdr.Prepend(header.UDPMinimumSize))
	udp.Encode(&header.UDPFields{
		SrcPort:  343,
		DstPort:  2401,
		Length:   header.UDPMinimumSize + uint16(offset),
		Checksum: 0,
	})

	ip := header.IPv4(hdr.Prepend(header.IPv4MinimumSize))
	ip.Encode(&header.IPv4Fields{
		TOS:            0,
		TotalLength:    uint16(pktSize),
		ID:             1,
		Flags:          0,
		FragmentOffset: 0,
		TTL:            48,
		Protocol:       uint8(header.UDPProtocolNumber),
		SrcAddr:        srcAddrV4,
		DstAddr:        dstAddrV4,
	})
	ip.SetChecksum(0)
	ip.SetChecksum(^ip.CalculateChecksum())

	buf := buffer.MakeWithData(append([]byte{}, hdr.View()...))
	return stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buf})
}

func genUDP6(offset int8) *stack.PacketBuffer {
	pktSize := header.IPv6MinimumSize + header.UDPMinimumSize
	hdr := prependable.New(pktSize)

	udp := header.UDP(hdr.Prepend(header.UDPMinimumSize))
	udp.Encode(&header.UDPFields{
		SrcPort:  343,
		DstPort:  2401,
		Length:   header.UDPMinimumSize + uint16(offset),
		Checksum: 0,
	})

	ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
	ip.Encode(&header.IPv6Fields{
		PayloadLength:     header.UDPMinimumSize,
		TransportProtocol: header.UDPProtocolNumber,
		HopLimit:          255,
		SrcAddr:           srcAddrV6,
		DstAddr:           dstAddrV6,
	})

	buf := buffer.MakeWithData(append([]byte{}, hdr.View()...))
	return stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buf})
}
