From 7f4b03068f03fa20e1f152b3ba4dc66f16a7d7ce Mon Sep 17 00:00:00 2001 From: Ghanan Gowripalan Date: Thu, 9 Dec 2021 15:14:03 -0800 Subject: [PATCH] Support NATing ICMPv6 Echo packets Updates #5915. PiperOrigin-RevId: 415367088 --- pkg/tcpip/header/icmpv6.go | 13 +++ pkg/tcpip/stack/conntrack.go | 30 +++++- pkg/tcpip/stack/iptables_targets.go | 38 +++++-- pkg/tcpip/tests/integration/iptables_test.go | 102 +++++++++++++++++++ 4 files changed, 172 insertions(+), 11 deletions(-) diff --git a/pkg/tcpip/header/icmpv6.go b/pkg/tcpip/header/icmpv6.go index 668da623a..954b77948 100644 --- a/pkg/tcpip/header/icmpv6.go +++ b/pkg/tcpip/header/icmpv6.go @@ -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)) +} diff --git a/pkg/tcpip/stack/conntrack.go b/pkg/tcpip/stack/conntrack.go index caa846565..af283fcd6 100644 --- a/pkg/tcpip/stack/conntrack.go +++ b/pkg/tcpip/stack/conntrack.go @@ -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 diff --git a/pkg/tcpip/stack/iptables_targets.go b/pkg/tcpip/stack/iptables_targets.go index 23b16c94c..13d3a93a9 100644 --- a/pkg/tcpip/stack/iptables_targets.go +++ b/pkg/tcpip/stack/iptables_targets.go @@ -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)) } diff --git a/pkg/tcpip/tests/integration/iptables_test.go b/pkg/tcpip/tests/integration/iptables_test.go index 0cfa25885..3ae95aa95 100644 --- a/pkg/tcpip/tests/integration/iptables_test.go +++ b/pkg/tcpip/tests/integration/iptables_test.go @@ -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, + }, }, }, }