Files
Kevin KrakauerandgVisor bot 3c75945fd6 netstack: remove PacketBuffer.IsNil()
The change was originally motivated by ticket references (cl/450976957), which
were never implemented.

PiperOrigin-RevId: 617480960
2024-03-20 05:31:48 -07:00

3731 lines
119 KiB
Go

// 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()
}
})
}
})
}
})
}
}
// 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))
})
}
}
func genTCP4(offset int8) *stack.PacketBuffer {
pktSize := header.IPv4MinimumSize + header.TCPMinimumSize
hdr := prependable.New(pktSize)
tcp := header.TCP(hdr.Prepend(header.TCPMinimumSize))
tcp.Encode(&header.TCPFields{
SeqNum: 0,
AckNum: 0,
DataOffset: header.TCPMinimumSize + uint8(offset)*4, // DataOffset must be a multiple of 4.
Flags: header.TCPFlagSyn,
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.TCPProtocolNumber),
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 genTCP6(offset int8) *stack.PacketBuffer {
pktSize := header.IPv6MinimumSize + header.TCPMinimumSize
hdr := prependable.New(pktSize)
tcp := header.TCP(hdr.Prepend(header.TCPMinimumSize))
tcp.Encode(&header.TCPFields{
SeqNum: 0,
AckNum: 0,
DataOffset: header.TCPMinimumSize + uint8(offset)*4, // DataOffset must be a multiple of 4.
Flags: header.TCPFlagSyn,
Checksum: 0,
})
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: header.TCPMinimumSize,
TransportProtocol: header.TCPProtocolNumber,
HopLimit: 255,
SrcAddr: srcAddrV6,
DstAddr: dstAddrV6,
})
buf := buffer.MakeWithData(append([]byte{}, hdr.View()...))
return stack.NewPacketBuffer(stack.PacketBufferOptions{Payload: buf})
}
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})
}