Support NATing ICMPv4 Echo packets

Updates #5915.

PiperOrigin-RevId: 413738470
This commit is contained in:
Ghanan Gowripalan
2021-12-02 12:11:59 -08:00
committed by gVisor bot
parent b2f8b495ad
commit c0d0937bb0
5 changed files with 458 additions and 158 deletions
+7
View File
@@ -185,6 +185,13 @@ func (b ICMPv4) SetIdent(ident uint16) {
binary.BigEndian.PutUint16(b[icmpv4IdentOffset:], ident)
}
// SetIdentWithChecksumUpdate sets the Ident field and updates the checksum.
func (b ICMPv4) SetIdentWithChecksumUpdate(new uint16) {
old := b.Ident()
b.SetIdent(new)
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
}
// Sequence retrieves the Sequence field from an ICMPv4 message.
func (b ICMPv4) Sequence() uint16 {
return binary.BigEndian.Uint16(b[icmpv4SequenceOffset:])
+120 -57
View File
@@ -17,6 +17,7 @@ package stack
import (
"encoding/binary"
"fmt"
"math"
"math/rand"
"sync"
"sync/atomic"
@@ -70,29 +71,38 @@ func (t *tuple) id() tupleID {
return t.tupleID
}
// tupleID uniquely identifies a connection in one direction. It currently
// contains enough information to distinguish between any TCP or UDP
// connection, and will need to be extended to support other protocols.
// tupleID uniquely identifies a trackable connection in one direction.
//
// +stateify savable
type tupleID struct {
srcAddr tcpip.Address
srcPort uint16
dstAddr tcpip.Address
dstPort uint16
transProto tcpip.TransportProtocolNumber
netProto tcpip.NetworkProtocolNumber
srcAddr tcpip.Address
// The source port of a packet in the original direction is overloaded with
// the ident of an Echo Request packet.
//
// This also matches the behaviour of sending packets on Linux where the
// socket's source port value is used for the source port of outgoing packets
// for TCP/UDP and the ident field for outgoing Echo Requests on Ping sockets:
//
// IPv4: https://github.com/torvalds/linux/blob/c5c17547b778975b3d83a73c8d84e8fb5ecf3ba5/net/ipv4/ping.c#L810
// IPv6: https://github.com/torvalds/linux/blob/c5c17547b778975b3d83a73c8d84e8fb5ecf3ba5/net/ipv6/ping.c#L133
srcPortOrEchoRequestIdent uint16
dstAddr tcpip.Address
// The opposite of srcPortOrEchoRequestIdent; the destination port of a packet
// in the reply direction is overloaded with the ident of an Echo Reply.
dstPortOrEchoReplyIdent uint16
transProto tcpip.TransportProtocolNumber
netProto tcpip.NetworkProtocolNumber
}
// reply creates the reply tupleID.
func (ti tupleID) reply() tupleID {
return tupleID{
srcAddr: ti.dstAddr,
srcPort: ti.dstPort,
dstAddr: ti.srcAddr,
dstPort: ti.srcPort,
transProto: ti.transProto,
netProto: ti.netProto,
srcAddr: ti.dstAddr,
srcPortOrEchoRequestIdent: ti.dstPortOrEchoReplyIdent,
dstAddr: ti.srcAddr,
dstPortOrEchoReplyIdent: ti.srcPortOrEchoRequestIdent,
transProto: ti.transProto,
netProto: ti.netProto,
}
}
@@ -284,7 +294,7 @@ func getEmbeddedNetAndTransHeaders(pkt *PacketBuffer, netHdrLength int, getNetAn
return nil, nil, false
}
func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.ChecksummableTransport, isICMPError bool, ok bool) {
func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Transport, isICMPError bool, ok bool) {
switch pkt.TransportProtocolNumber {
case header.TCPProtocolNumber:
if tcpHeader := header.TCP(pkt.TransportHeader().View()); len(tcpHeader) >= header.TCPMinimumSize {
@@ -295,6 +305,19 @@ func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Check
return pkt.Network(), udpHeader, false, true
}
case header.ICMPv4ProtocolNumber:
icmpHeader := header.ICMPv4(pkt.TransportHeader().View())
if len(icmpHeader) < header.ICMPv4MinimumSize {
break
}
switch icmpType := icmpHeader.Type(); icmpType {
case header.ICMPv4Echo, header.ICMPv4EchoReply:
return pkt.Network(), icmpHeader, false, true
case header.ICMPv4DstUnreachable, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
default:
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
}
h, ok := pkt.Data().PullUp(header.IPv4MinimumSize)
if !ok {
panic(fmt.Sprintf("should have a valid IPv4 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv4MinimumSize))
@@ -333,24 +356,24 @@ func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Check
func getTupleIDForRegularPacket(netHdr header.Network, netProto tcpip.NetworkProtocolNumber, transHdr header.Transport, transProto tcpip.TransportProtocolNumber) tupleID {
return tupleID{
srcAddr: netHdr.SourceAddress(),
srcPort: transHdr.SourcePort(),
dstAddr: netHdr.DestinationAddress(),
dstPort: transHdr.DestinationPort(),
transProto: transProto,
netProto: netProto,
srcAddr: netHdr.SourceAddress(),
srcPortOrEchoRequestIdent: transHdr.SourcePort(),
dstAddr: netHdr.DestinationAddress(),
dstPortOrEchoReplyIdent: transHdr.DestinationPort(),
transProto: transProto,
netProto: netProto,
}
}
func getTupleIDForPacketInICMPError(pkt *PacketBuffer, getNetAndTransHdr netAndTransHeadersFunc, netProto tcpip.NetworkProtocolNumber, netLen int, transProto tcpip.TransportProtocolNumber) (tupleID, bool) {
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, netLen, getNetAndTransHdr, transProto); ok {
return tupleID{
srcAddr: netHdr.DestinationAddress(),
srcPort: transHdr.DestinationPort(),
dstAddr: netHdr.SourceAddress(),
dstPort: transHdr.SourcePort(),
transProto: transProto,
netProto: netProto,
srcAddr: netHdr.DestinationAddress(),
srcPortOrEchoRequestIdent: transHdr.DestinationPort(),
dstAddr: netHdr.SourceAddress(),
dstPortOrEchoReplyIdent: transHdr.SourcePort(),
transProto: transProto,
netProto: netProto,
}, true
}
@@ -365,6 +388,24 @@ const (
getTupleIDOKAndDontAllowNewConn
)
func getTupleIDForEchoPacket(pkt *PacketBuffer, ident uint16, request bool) tupleID {
netHdr := pkt.Network()
tid := tupleID{
srcAddr: netHdr.SourceAddress(),
dstAddr: netHdr.DestinationAddress(),
transProto: pkt.TransportProtocolNumber,
netProto: pkt.NetworkProtocolNumber,
}
if request {
tid.srcPortOrEchoRequestIdent = ident
} else {
tid.dstPortOrEchoReplyIdent = ident
}
return tid
}
func getTupleID(pkt *PacketBuffer) (tupleID, getTupleIDDisposition) {
switch pkt.TransportProtocolNumber {
case header.TCPProtocolNumber:
@@ -382,8 +423,16 @@ func getTupleID(pkt *PacketBuffer) (tupleID, getTupleIDDisposition) {
}
switch icmp.Type() {
case header.ICMPv4Echo:
return getTupleIDForEchoPacket(pkt, icmp.Ident(), true /* request */), getTupleIDOKAndAllowNewConn
case header.ICMPv4EchoReply:
// Do not create a new connection in response to a reply packet as only
// the first packet of a connection should create a conntrack entry but
// a reply is never the first packet sent for a connection.
return getTupleIDForEchoPacket(pkt, icmp.Ident(), false /* request */), getTupleIDOKAndDontAllowNewConn
case header.ICMPv4DstUnreachable, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
default:
// Unsupported ICMP type for NAT-ing.
return tupleID{}, getTupleIDNotOK
}
@@ -614,9 +663,9 @@ func (cn *conn) maybePerformNoopNAT(dnat bool) {
}
}
type portRange struct {
type portOrIdentRange struct {
start uint16
size uint16
size uint32
}
// performNAT setups up the connection for the specified NAT and rewrites the
@@ -628,7 +677,15 @@ type portRange struct {
//
// Generally, only the first packet of a connection reaches this method; other
// packets will be manipulated without needing to modify the connection.
func (cn *conn) performNAT(pkt *PacketBuffer, hook Hook, r *Route, ports portRange, natAddress tcpip.Address, dnat bool) {
func (cn *conn) performNAT(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents portOrIdentRange, natAddress tcpip.Address, dnat bool) {
lastPortOrIdent := func() uint16 {
lastPortOrIdent := uint32(portsOrIdents.start) + portsOrIdents.size - 1
if lastPortOrIdent > math.MaxUint16 {
panic(fmt.Sprintf("got lastPortOrIdent = %d, want <= MaxUint16(=%d); portsOrIdents=%#v", lastPortOrIdent, math.MaxUint16, portsOrIdents))
}
return uint16(lastPortOrIdent)
}()
// Make sure the packet is re-written after performing NAT.
defer func() {
// handlePacket returns true if the packet may skip the NAT table as the
@@ -645,15 +702,15 @@ func (cn *conn) performNAT(pkt *PacketBuffer, hook Hook, r *Route, ports portRan
var manip *manipType
var address *tcpip.Address
var port *uint16
var portOrIdent *uint16
if dnat {
manip = &cn.destinationManip
address = &cn.reply.tupleID.srcAddr
port = &cn.reply.tupleID.srcPort
portOrIdent = &cn.reply.tupleID.srcPortOrEchoRequestIdent
} else {
manip = &cn.sourceManip
address = &cn.reply.tupleID.dstAddr
port = &cn.reply.tupleID.dstPort
portOrIdent = &cn.reply.tupleID.dstPortOrEchoReplyIdent
}
if *manip != manipNotPerformed {
@@ -662,8 +719,8 @@ func (cn *conn) performNAT(pkt *PacketBuffer, hook Hook, r *Route, ports portRan
*manip = manipPerformed
*address = natAddress
// Does the current port fit in the range?
if end := ports.start + ports.size - 1; *port >= ports.start && *port <= end {
// Does the current port/ident fit in the range?
if portsOrIdents.start <= *portOrIdent && *portOrIdent <= lastPortOrIdent {
// Yes, is the current reply tuple unique?
if other := cn.ct.connForTID(cn.reply.tupleID); other == nil {
// Yes! No need to change the port.
@@ -671,27 +728,32 @@ func (cn *conn) performNAT(pkt *PacketBuffer, hook Hook, r *Route, ports portRan
}
}
// Try our best to find a port that results in a unique reply tuple.
// Try our best to find a port/ident that results in a unique reply tuple.
//
// We limit the number of attempts to find a unique tuple to not waste a lot
// of time looking for a unique tuple.
//
// Matches linux behaviour introduced in
// https://github.com/torvalds/linux/commit/a504b703bb1da526a01593da0e4be2af9d9f5fa8.
const maxAttemptsForInitialRound uint16 = 128
const maxAttemptsForInitialRound uint32 = 128
const minAttemptsToContinue = 16
allowedInitialAttempts := maxAttemptsForInitialRound
if allowedInitialAttempts > ports.size {
allowedInitialAttempts = ports.size
if allowedInitialAttempts > portsOrIdents.size {
allowedInitialAttempts = portsOrIdents.size
}
for maxAttempts := allowedInitialAttempts; ; maxAttempts /= 2 {
// Start reach round with a random initial port in the range.
initial := ports.start + uint16(cn.ct.rand.Uint32())%ports.size
// Start reach round with a random initial port/ident offset.
randOffset := cn.ct.rand.Uint32()
for i := uint16(0); i < maxAttempts; i++ {
*port = initial + i%ports.size
for i := uint32(0); i < maxAttempts; i++ {
newPortOrIdentU32 := uint32(portsOrIdents.start) + (randOffset+i)%portsOrIdents.size
if newPortOrIdentU32 > math.MaxUint16 {
panic(fmt.Sprintf("got newPortOrIdentU32 = %d, want <= MaxUint16(=%d); portsOrIdents=%#v, randOffset=%d", newPortOrIdentU32, math.MaxUint16, portsOrIdents, randOffset))
}
*portOrIdent = uint16(newPortOrIdentU32)
if other := cn.ct.connForTID(cn.reply.tupleID); other == nil {
// We found a unique tuple!
@@ -699,8 +761,9 @@ func (cn *conn) performNAT(pkt *PacketBuffer, hook Hook, r *Route, ports portRan
}
}
if maxAttempts == ports.size {
// We already tried all the ports in the range so no need to keep trying.
if maxAttempts == portsOrIdents.size {
// We already tried all the ports/idents in the range so no need to keep
// trying.
return
}
@@ -795,10 +858,10 @@ func (cn *conn) handlePacket(pkt *PacketBuffer, hook Hook, rt *Route) bool {
panic(fmt.Sprintf("unhandled manip = %d", manip))
}
newPort := tid.dstPort
newPort := tid.dstPortOrEchoReplyIdent
newAddr := tid.dstAddr
if dnat {
newPort = tid.srcPort
newPort = tid.srcPortOrEchoRequestIdent
newAddr = tid.srcAddr
}
@@ -871,9 +934,9 @@ func (ct *ConnTrack) bucket(id tupleID) int {
h.Write([]byte(id.srcAddr))
h.Write([]byte(id.dstAddr))
shortBuf := make([]byte, 2)
binary.LittleEndian.PutUint16(shortBuf, id.srcPort)
binary.LittleEndian.PutUint16(shortBuf, id.srcPortOrEchoRequestIdent)
h.Write([]byte(shortBuf))
binary.LittleEndian.PutUint16(shortBuf, id.dstPort)
binary.LittleEndian.PutUint16(shortBuf, id.dstPortOrEchoReplyIdent)
h.Write([]byte(shortBuf))
binary.LittleEndian.PutUint16(shortBuf, uint16(id.transProto))
h.Write([]byte(shortBuf))
@@ -998,12 +1061,12 @@ func (ct *ConnTrack) originalDst(epID TransportEndpointID, netProto tcpip.Networ
// Lookup the connection. The reply's original destination
// describes the original address.
tid := tupleID{
srcAddr: epID.LocalAddress,
srcPort: epID.LocalPort,
dstAddr: epID.RemoteAddress,
dstPort: epID.RemotePort,
transProto: transProto,
netProto: netProto,
srcAddr: epID.LocalAddress,
srcPortOrEchoRequestIdent: epID.LocalPort,
dstAddr: epID.RemoteAddress,
dstPortOrEchoReplyIdent: epID.RemotePort,
transProto: transProto,
netProto: netProto,
}
t := ct.connForTID(tid)
if t == nil {
@@ -1019,5 +1082,5 @@ func (ct *ConnTrack) originalDst(epID TransportEndpointID, netProto tcpip.Networ
}
id := t.conn.original.id()
return id.dstAddr, id.dstPort, nil
return id.dstAddr, id.dstPortOrEchoReplyIdent, nil
}
+74 -47
View File
@@ -176,48 +176,57 @@ type SNATTarget struct {
}
func dnatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address) (RuleVerdict, int) {
return natAction(pkt, hook, r, portRange{start: port, size: 1}, address, true /* dnat */)
return natAction(pkt, hook, r, portOrIdentRange{start: port, size: 1}, address, true /* dnat */)
}
func targetPortRangeForTCPAndUDP(originalSrcPort uint16) portOrIdentRange {
// As per iptables(8),
//
// If no port range is specified, then source ports below 512 will be
// mapped to other ports below 512: those between 512 and 1023 inclusive
// will be mapped to ports below 1024, and other ports will be mapped to
// 1024 or above.
switch {
case originalSrcPort < 512:
return portOrIdentRange{start: 1, size: 511}
case originalSrcPort < 1024:
return portOrIdentRange{start: 1, size: 1023}
default:
return portOrIdentRange{start: 1024, size: math.MaxUint16 - 1023}
}
}
func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address) (RuleVerdict, int) {
ports := portRange{start: port, size: 1}
if port == 0 {
// As per iptables(8),
//
// If no port range is specified, then source ports below 512 will be
// mapped to other ports below 512: those between 512 and 1023 inclusive
// will be mapped to ports below 1024, and other ports will be mapped to
// 1024 or above.
switch protocol := pkt.TransportProtocolNumber; protocol {
case header.UDPProtocolNumber:
port = header.UDP(pkt.TransportHeader().View()).SourcePort()
case header.TCPProtocolNumber:
port = header.TCP(pkt.TransportHeader().View()).SourcePort()
default:
panic(fmt.Sprintf("unsupported transport protocol = %d", pkt.TransportProtocolNumber))
}
portsOrIdents := portOrIdentRange{start: port, size: 1}
switch {
case port < 512:
ports = portRange{start: 1, size: 511}
case port < 1024:
ports = portRange{start: 1, size: 1023}
default:
ports = portRange{start: 1024, size: math.MaxUint16 - 1023}
switch pkt.TransportProtocolNumber {
case header.UDPProtocolNumber:
if port == 0 {
portsOrIdents = targetPortRangeForTCPAndUDP(header.UDP(pkt.TransportHeader().View()).SourcePort())
}
case header.TCPProtocolNumber:
if port == 0 {
portsOrIdents = targetPortRangeForTCPAndUDP(header.TCP(pkt.TransportHeader().View()).SourcePort())
}
case header.ICMPv4ProtocolNumber:
// Allow NAT-ing to any 16-bit value for ICMP's Ident field to match Linux
// behaviour.
//
// https://github.com/torvalds/linux/blob/58e1100fdc5990b0cc0d4beaf2562a92e621ac7d/net/netfilter/nf_nat_core.c#L391
portsOrIdents = portOrIdentRange{start: 0, size: math.MaxUint16 + 1}
}
return natAction(pkt, hook, r, ports, address, false /* dnat */)
return natAction(pkt, hook, r, portsOrIdents, address, false /* dnat */)
}
func natAction(pkt *PacketBuffer, hook Hook, r *Route, ports portRange, address tcpip.Address, dnat bool) (RuleVerdict, int) {
func natAction(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents portOrIdentRange, address tcpip.Address, dnat bool) (RuleVerdict, int) {
// Drop the packet if network and transport header are not set.
if pkt.NetworkHeader().View().IsEmpty() || pkt.TransportHeader().View().IsEmpty() {
return RuleDrop, 0
}
if t := pkt.tuple; t != nil {
t.conn.performNAT(pkt, hook, r, ports, address, dnat)
t.conn.performNAT(pkt, hook, r, portsOrIdents, address, dnat)
return RuleAccept, 0
}
@@ -280,30 +289,48 @@ func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addre
return snatAction(pkt, hook, r, 0 /* port */, address)
}
func rewritePacket(n header.Network, t header.ChecksummableTransport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPort uint16, newAddr tcpip.Address) {
if updateSRCFields {
if fullChecksum {
t.SetSourcePortWithChecksumUpdate(newPort)
} else {
t.SetSourcePort(newPort)
}
} else {
if fullChecksum {
t.SetDestinationPortWithChecksumUpdate(newPort)
} else {
t.SetDestinationPort(newPort)
}
}
if updatePseudoHeader {
var oldAddr tcpip.Address
func rewritePacket(n header.Network, t header.Transport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPort uint16, newAddr tcpip.Address) {
switch t := t.(type) {
case header.ChecksummableTransport:
if updateSRCFields {
oldAddr = n.SourceAddress()
if fullChecksum {
t.SetSourcePortWithChecksumUpdate(newPort)
} else {
t.SetSourcePort(newPort)
}
} else {
oldAddr = n.DestinationAddress()
if fullChecksum {
t.SetDestinationPortWithChecksumUpdate(newPort)
} else {
t.SetDestinationPort(newPort)
}
}
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr, fullChecksum)
if updatePseudoHeader {
var oldAddr tcpip.Address
if updateSRCFields {
oldAddr = n.SourceAddress()
} else {
oldAddr = n.DestinationAddress()
}
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr, fullChecksum)
}
case header.ICMPv4:
switch icmpType := t.Type(); icmpType {
case header.ICMPv4Echo:
if updateSRCFields {
t.SetIdentWithChecksumUpdate(newPort)
}
case header.ICMPv4EchoReply:
if !updateSRCFields {
t.SetIdentWithChecksumUpdate(newPort)
}
default:
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
}
default:
panic(fmt.Sprintf("unhandled transport = %#v", t))
}
if checksummableNetHeader, ok := n.(header.ChecksummableNetwork); ok {
+1
View File
@@ -40,6 +40,7 @@ go_test(
"//pkg/tcpip/stack",
"//pkg/tcpip/tests/utils",
"//pkg/tcpip/testutil",
"//pkg/tcpip/transport/icmp",
"//pkg/tcpip/transport/tcp",
"//pkg/tcpip/transport/udp",
"//pkg/waiter",
+256 -54
View File
@@ -32,6 +32,7 @@ import (
"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"
@@ -1300,6 +1301,15 @@ var (
},
}
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})
},
}
dnatTypes = []natType{
{
name: "Redirect",
@@ -1309,14 +1319,7 @@ var (
setupDNAT(t, s, netProto, transProto, &stack.RedirectTarget{NetworkProtocol: netProto, Port: dnatPort})
},
},
{
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})
},
},
dnatTarget,
}
twiceNATTypes = []natType{
@@ -1339,6 +1342,140 @@ var (
}
)
func TestNATEcho(t *testing.T) {
const ident = 1
v4EchoPkt := func(srcAddr, dstAddr tcpip.Address, reply bool) buffer.View {
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(),
),
)
}
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) buffer.View
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,
},
},
},
}
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},
})
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{
Data: test.echoPkt(natTypeTest.requestSrc, natTypeTest.requestDst, false /* reply */).ToVectorisedView(),
}))
pkt, ok := ep1.Read()
if !ok {
t.Fatal("expected to read a packet on ep1")
}
test.checkEchoPkt(t, stack.PayloadSince(pkt.Pkt.NetworkHeader()), natTypeTest.expectedRequestSrc, natTypeTest.expectedRequestDst, false /* reply */)
}
if t.Failed() {
t.FailNow()
}
// Send and check the Echo Reply.
{
ep1.InjectInbound(test.netProto, stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: test.echoPkt(natTypeTest.expectedRequestDst, natTypeTest.expectedRequestSrc, true /* reply */).ToVectorisedView(),
}))
pkt, ok := ep2.Read()
if !ok {
t.Fatal("expected to read a packet on ep2")
}
test.checkEchoPkt(t, stack.PayloadSince(pkt.Pkt.NetworkHeader()), natTypeTest.requestDst, natTypeTest.requestSrc, true /* reply */)
}
})
}
})
}
})
}
}
func TestNAT(t *testing.T) {
const listenPort uint16 = 8080
@@ -1856,6 +1993,23 @@ func tcpv4Packet(srcAddr, dstAddr tcpip.Address, srcPort, dstPort uint16, dataSi
return hdr.View()
}
func icmpv4Packet(srcAddr, dstAddr tcpip.Address, icmpType header.ICMPv4Type, ident uint16) buffer.View {
hdr := buffer.NewPrependable(header.IPv4MinimumSize + header.ICMPv4MinimumSize)
icmp := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize))
icmp.SetType(icmpType)
icmp.SetIdent(ident)
icmp.SetChecksum(0)
icmp.SetChecksum(^header.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) buffer.View {
udpSize := header.UDPMinimumSize + dataSize
hdr := buffer.NewPrependable(header.IPv6MinimumSize + udpSize)
@@ -2292,33 +2446,77 @@ func TestNATICMPError(t *testing.T) {
}
}
func TestSNATHandlePortConflicts(t *testing.T) {
func TestSNATHandlePortOrIdentConflicts(t *testing.T) {
const dstPort = 5432
type portRange struct {
type portOrIdentRange struct {
first uint16
last uint16
}
type transportTypeTest struct {
name string
proto tcpip.TransportProtocolNumber
buf func(tcpip.Address, uint16) buffer.View
checkNATed func(*testing.T, buffer.View, uint16, bool, portRange)
type srcPortOrIdentRangeTest struct {
name string
originalRange portOrIdentRange
targetRange portOrIdentRange
}
compareSrcPort := func(t *testing.T, gotPort uint16, originalSrcPort uint16, firstPacket bool, expectedRange portRange) {
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) buffer.View
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 = %d, want = %d", gotPort, originalSrcPort)
t.Errorf("got port/ident = %d, want = %d", gotPort, originalSrcPort)
}
return
}
if gotPort < expectedRange.first || gotPort > expectedRange.last {
t.Errorf("got port = %d, want in range [%d, %d]", gotPort, expectedRange.first, expectedRange.last)
t.Errorf("got port/ident = %d, want in range [%d, %d]", gotPort, expectedRange.first, expectedRange.last)
}
}
@@ -2345,7 +2543,7 @@ func TestSNATHandlePortConflicts(t *testing.T) {
buf: func(srcAddr tcpip.Address, srcPort uint16) buffer.View {
return udpv4Packet(srcAddr, utils.Host1IPv4Addr.AddressWithPrefix.Address, srcPort, dstPort, 0 /* dataSize */)
},
checkNATed: func(t *testing.T, v buffer.View, originalSrcPort uint16, firstPacket bool, expectedRange portRange) {
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),
@@ -2355,9 +2553,10 @@ func TestSNATHandlePortConflicts(t *testing.T) {
)
if !t.Failed() {
compareSrcPort(t, header.UDP(header.IPv4(v).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
compareSrcPortOrIdent(t, header.UDP(header.IPv4(v).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
}
},
srcPortOrIdentRanges: srcPortRanges,
},
{
name: "TCP",
@@ -2365,7 +2564,7 @@ func TestSNATHandlePortConflicts(t *testing.T) {
buf: func(srcAddr tcpip.Address, srcPort uint16) buffer.View {
return tcpv4Packet(srcAddr, utils.Host1IPv4Addr.AddressWithPrefix.Address, srcPort, dstPort, 0 /* dataSize */)
},
checkNATed: func(t *testing.T, v buffer.View, originalSrcPort uint16, firstPacket bool, expectedRange portRange) {
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),
@@ -2375,9 +2574,32 @@ func TestSNATHandlePortConflicts(t *testing.T) {
)
if !t.Failed() {
compareSrcPort(t, header.TCP(header.IPv4(v).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
compareSrcPortOrIdent(t, header.TCP(header.IPv4(v).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
}
},
srcPortOrIdentRanges: srcPortRanges,
},
{
name: "ICMP Echo",
proto: header.ICMPv4ProtocolNumber,
buf: func(srcAddr tcpip.Address, ident uint16) buffer.View {
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).Payload()).Ident(), originalIdent, firstPacket, expectedRange)
}
},
srcPortOrIdentRanges: identRanges,
},
},
},
@@ -2397,7 +2619,7 @@ func TestSNATHandlePortConflicts(t *testing.T) {
buf: func(srcAddr tcpip.Address, srcPort uint16) buffer.View {
return udpv6Packet(srcAddr, utils.Host1IPv6Addr.AddressWithPrefix.Address, srcPort, dstPort, 0 /* dataSize */)
},
checkNATed: func(t *testing.T, v buffer.View, originalSrcPort uint16, firstPacket bool, expectedRange portRange) {
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),
@@ -2407,9 +2629,10 @@ func TestSNATHandlePortConflicts(t *testing.T) {
)
if !t.Failed() {
compareSrcPort(t, header.UDP(header.IPv6(v).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
compareSrcPortOrIdent(t, header.UDP(header.IPv6(v).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
}
},
srcPortOrIdentRanges: srcPortRanges,
},
{
name: "TCP",
@@ -2417,7 +2640,7 @@ func TestSNATHandlePortConflicts(t *testing.T) {
buf: func(srcAddr tcpip.Address, srcPort uint16) buffer.View {
return tcpv6Packet(srcAddr, utils.Host1IPv6Addr.AddressWithPrefix.Address, srcPort, dstPort, 0 /* dataSize */)
},
checkNATed: func(t *testing.T, v buffer.View, originalSrcPort uint16, firstPacket bool, expectedRange portRange) {
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),
@@ -2427,9 +2650,10 @@ func TestSNATHandlePortConflicts(t *testing.T) {
)
if !t.Failed() {
compareSrcPort(t, header.TCP(header.IPv6(v).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
compareSrcPortOrIdent(t, header.TCP(header.IPv6(v).Payload()).SourcePort(), originalSrcPort, firstPacket, expectedRange)
}
},
srcPortOrIdentRanges: srcPortRanges,
},
},
},
@@ -2453,38 +2677,16 @@ func TestSNATHandlePortConflicts(t *testing.T) {
},
}
srcPortRanges := []struct {
name string
originalRange portRange
targetRange portRange
}{
{
name: "Less than 512",
originalRange: portRange{first: 1, last: 511},
targetRange: portRange{first: 1, last: 511},
},
{
name: "Greater than or equal to 512 but less than 1024",
originalRange: portRange{first: 512, last: 1023},
targetRange: portRange{first: 1, last: 1023},
},
{
name: "Greater than or equal to 1024",
originalRange: portRange{first: 1024, last: math.MaxUint16},
targetRange: portRange{first: 1024, last: math.MaxUint16},
},
}
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 _, srcPortRange := range srcPortRanges {
t.Run(srcPortRange.name, func(t *testing.T) {
for _, srcPort := range [2]uint16{srcPortRange.originalRange.first, srcPortRange.originalRange.last} {
t.Run(fmt.Sprintf("OriginalSrcPort=%d", srcPort), 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},
@@ -2548,7 +2750,7 @@ func TestSNATHandlePortConflicts(t *testing.T) {
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{
Data: transportType.buf(srcAddr, srcPort).ToVectorisedView(),
Data: transportType.buf(srcAddr, srcPortOrIdent).ToVectorisedView(),
}))
pkt, ok := ep1.Read()
@@ -2556,7 +2758,7 @@ func TestSNATHandlePortConflicts(t *testing.T) {
t.Fatal("expected to read a packet on ep1")
}
pktView := stack.PayloadSince(pkt.Pkt.NetworkHeader())
transportType.checkNATed(t, pktView, srcPort, i == 0, srcPortRange.targetRange)
transportType.checkNATed(t, pktView, srcPortOrIdent, i == 0, srcPortOrIdentRange.targetRange)
})
}
})