Support NATing ICMPv6 Echo packets

Updates #5915.

PiperOrigin-RevId: 415367088
This commit is contained in:
Ghanan Gowripalan
2021-12-09 15:18:02 -08:00
committed by gVisor bot
parent 46d3404d8e
commit 7f4b03068f
4 changed files with 172 additions and 11 deletions
+13
View File
@@ -240,6 +240,13 @@ func (b ICMPv6) SetIdent(ident uint16) {
binary.BigEndian.PutUint16(b[icmpv6IdentOffset:], ident)
}
// SetIdentWithChecksumUpdate sets the Ident field and updates the checksum.
func (b ICMPv6) SetIdentWithChecksumUpdate(new uint16) {
old := b.Ident()
b.SetIdent(new)
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
}
// Sequence retrieves the Sequence field from an ICMPv6 message.
func (b ICMPv6) Sequence() uint16 {
return binary.BigEndian.Uint16(b[icmpv6SequenceOffset:])
@@ -284,3 +291,9 @@ func ICMPv6Checksum(params ICMPv6ChecksumParams) uint16 {
return ^xsum
}
// UpdateChecksumPseudoHeaderAddress updates the checksum to reflect an
// updated address in the pseudo header.
func (b ICMPv6) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address) {
b.SetChecksum(^checksumUpdate2ByteAlignedAddress(^b.Checksum(), old, new))
}
+27 -3
View File
@@ -300,14 +300,16 @@ func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Trans
if tcpHeader := header.TCP(pkt.TransportHeader().View()); len(tcpHeader) >= header.TCPMinimumSize {
return pkt.Network(), tcpHeader, false, true
}
return nil, nil, false, false
case header.UDPProtocolNumber:
if udpHeader := header.UDP(pkt.TransportHeader().View()); len(udpHeader) >= header.UDPMinimumSize {
return pkt.Network(), udpHeader, false, true
}
return nil, nil, false, false
case header.ICMPv4ProtocolNumber:
icmpHeader := header.ICMPv4(pkt.TransportHeader().View())
if len(icmpHeader) < header.ICMPv4MinimumSize {
break
return nil, nil, false, false
}
switch icmpType := icmpHeader.Type(); icmpType {
@@ -331,7 +333,21 @@ func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Trans
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv4MinimumSize, v4NetAndTransHdr, pkt.tuple.id().transProto); ok {
return netHdr, transHdr, true, true
}
return nil, nil, false, false
case header.ICMPv6ProtocolNumber:
icmpHeader := header.ICMPv6(pkt.TransportHeader().View())
if len(icmpHeader) < header.ICMPv6MinimumSize {
return nil, nil, false, false
}
switch icmpType := icmpHeader.Type(); icmpType {
case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
return pkt.Network(), icmpHeader, false, true
case header.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem:
default:
panic(fmt.Sprintf("unexpected ICMPv6 type = %d", icmpType))
}
h, ok := pkt.Data().PullUp(header.IPv6MinimumSize)
if !ok {
panic(fmt.Sprintf("should have a valid IPv6 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv6MinimumSize))
@@ -349,9 +365,10 @@ func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Trans
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv6MinimumSize, v6NetAndTransHdr, transProto); ok {
return netHdr, transHdr, true, true
}
return nil, nil, false, false
default:
panic(fmt.Sprintf("unexpected transport protocol = %d", pkt.TransportProtocolNumber))
}
return nil, nil, false, false
}
func getTupleIDForRegularPacket(netHdr header.Network, netProto tcpip.NetworkProtocolNumber, transHdr header.Transport, transProto tcpip.TransportProtocolNumber) tupleID {
@@ -458,6 +475,13 @@ func getTupleID(pkt *PacketBuffer) (tupleID, getTupleIDDisposition) {
}
switch icmp.Type() {
case header.ICMPv6EchoRequest:
return getTupleIDForEchoPacket(pkt, icmp.Ident(), true /* request */), getTupleIDOKAndAllowNewConn
case header.ICMPv6EchoReply:
// 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.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem:
default:
return tupleID{}, getTupleIDNotOK
+30 -8
View File
@@ -208,7 +208,7 @@ func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcp
if port == 0 {
portsOrIdents = targetPortRangeForTCPAndUDP(header.TCP(pkt.TransportHeader().View()).SourcePort())
}
case header.ICMPv4ProtocolNumber:
case header.ICMPv4ProtocolNumber, header.ICMPv6ProtocolNumber:
// Allow NAT-ing to any 16-bit value for ICMP's Ident field to match Linux
// behaviour.
//
@@ -289,20 +289,20 @@ 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.Transport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPort uint16, newAddr tcpip.Address) {
func rewritePacket(n header.Network, t header.Transport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPortOrIdent uint16, newAddr tcpip.Address) {
switch t := t.(type) {
case header.ChecksummableTransport:
if updateSRCFields {
if fullChecksum {
t.SetSourcePortWithChecksumUpdate(newPort)
t.SetSourcePortWithChecksumUpdate(newPortOrIdent)
} else {
t.SetSourcePort(newPort)
t.SetSourcePort(newPortOrIdent)
}
} else {
if fullChecksum {
t.SetDestinationPortWithChecksumUpdate(newPort)
t.SetDestinationPortWithChecksumUpdate(newPortOrIdent)
} else {
t.SetDestinationPort(newPort)
t.SetDestinationPort(newPortOrIdent)
}
}
@@ -320,15 +320,37 @@ func rewritePacket(n header.Network, t header.Transport, updateSRCFields, fullCh
switch icmpType := t.Type(); icmpType {
case header.ICMPv4Echo:
if updateSRCFields {
t.SetIdentWithChecksumUpdate(newPort)
t.SetIdentWithChecksumUpdate(newPortOrIdent)
}
case header.ICMPv4EchoReply:
if !updateSRCFields {
t.SetIdentWithChecksumUpdate(newPort)
t.SetIdentWithChecksumUpdate(newPortOrIdent)
}
default:
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
}
case header.ICMPv6:
switch icmpType := t.Type(); icmpType {
case header.ICMPv6EchoRequest:
if updateSRCFields {
t.SetIdentWithChecksumUpdate(newPortOrIdent)
}
case header.ICMPv6EchoReply:
if !updateSRCFields {
t.SetIdentWithChecksumUpdate(newPortOrIdent)
}
default:
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
}
var oldAddr tcpip.Address
if updateSRCFields {
oldAddr = n.SourceAddress()
} else {
oldAddr = n.DestinationAddress()
}
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr)
default:
panic(fmt.Sprintf("unhandled transport = %#v", t))
}
@@ -1372,6 +1372,32 @@ func TestNATEcho(t *testing.T) {
)
}
v6EchoPkt := func(srcAddr, dstAddr tcpip.Address, reply bool) buffer.View {
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
@@ -1422,6 +1448,40 @@ func TestNATEcho(t *testing.T) {
},
},
},
{
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 {
@@ -2057,6 +2117,27 @@ func tcpv6Packet(srcAddr, dstAddr tcpip.Address, srcPort, dstPort uint16, dataSi
return hdr.View()
}
func icmpv6Packet(srcAddr, dstAddr tcpip.Address, icmpType header.ICMPv6Type, ident uint16) buffer.View {
hdr := buffer.NewPrependable(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
@@ -2655,6 +2736,27 @@ func TestSNATHandlePortOrIdentConflicts(t *testing.T) {
},
srcPortOrIdentRanges: srcPortRanges,
},
{
name: "ICMP Echo",
proto: header.ICMPv6ProtocolNumber,
buf: func(srcAddr tcpip.Address, ident uint16) buffer.View {
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).Payload()).Ident(), originalIdent, firstPacket, expectedRange)
}
},
srcPortOrIdentRanges: identRanges,
},
},
},
}