mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
The change was originally motivated by ticket references (cl/450976957), which were never implemented. PiperOrigin-RevId: 617480960
3731 lines
119 KiB
Go
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})
|
|
}
|