diff options
author | Ghanan Gowripalan <ghanan@google.com> | 2021-10-28 19:33:32 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-10-28 19:36:02 -0700 |
commit | 1953d2ad28d405a3ab028feba7b6fca18339e9be (patch) | |
tree | e8a45f77a46b20bc4b6d847ff51ce1d91d338a10 | |
parent | ca55c18a31789b8f2541d5d3b90e2af012d3e6ef (diff) |
NAT ICMPv6 errors
...so a NAT-ed connection's socket can handle ICMP errors.
Updates #5916.
PiperOrigin-RevId: 406270868
-rw-r--r-- | pkg/tcpip/stack/conntrack.go | 156 | ||||
-rw-r--r-- | pkg/tcpip/tests/integration/iptables_test.go | 155 |
2 files changed, 273 insertions, 38 deletions
diff --git a/pkg/tcpip/stack/conntrack.go b/pkg/tcpip/stack/conntrack.go index f5e990f03..c489506bb 100644 --- a/pkg/tcpip/stack/conntrack.go +++ b/pkg/tcpip/stack/conntrack.go @@ -209,6 +209,22 @@ type bucket struct { tuples tupleList } +func getEmbeddedNetAndTransHeaders(pkt *PacketBuffer, netHdrLength int, netHdrFunc func([]byte) header.Network) (header.Network, header.ChecksummableTransport, bool) { + switch pkt.tuple.id().transProto { + case header.TCPProtocolNumber: + if netAndTransHeader, ok := pkt.Data().PullUp(netHdrLength + header.TCPMinimumSize); ok { + netHeader := netHdrFunc(netAndTransHeader) + return netHeader, header.TCP(netHeader.Payload()), true + } + case header.UDPProtocolNumber: + if netAndTransHeader, ok := pkt.Data().PullUp(netHdrLength + header.UDPMinimumSize); ok { + netHeader := netHdrFunc(netAndTransHeader) + return netHeader, header.UDP(netHeader.Payload()), true + } + } + return nil, nil, false +} + func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.ChecksummableTransport, isICMPError bool, ok bool) { switch pkt.TransportProtocolNumber { case header.TCPProtocolNumber: @@ -225,29 +241,31 @@ func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Check panic(fmt.Sprintf("should have a valid IPv4 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv4MinimumSize)) } - ipv4 := header.IPv4(h) - if ipv4.HeaderLength() > header.IPv4MinimumSize { + if header.IPv4(h).HeaderLength() > header.IPv4MinimumSize { // TODO(https://gvisor.dev/issue/6765): Handle IPv4 options. panic("should have dropped packets with IPv4 options") } - switch pkt.tuple.id().transProto { - case header.TCPProtocolNumber: - // TODO(https://gvisor.dev/issue/6765): Handle IPv4 options. - netAndTransHeader, ok := pkt.Data().PullUp(header.IPv4MinimumSize + header.TCPMinimumSize) - if !ok { - return nil, nil, false, false - } - netHeader := header.IPv4(netAndTransHeader) - return netHeader, header.TCP(netHeader.Payload()), true, true - case header.UDPProtocolNumber: - // TODO(https://gvisor.dev/issue/6765): Handle IPv4 options. - netAndTransHeader, ok := pkt.Data().PullUp(header.IPv4MinimumSize + header.UDPMinimumSize) - if !ok { - return nil, nil, false, false - } - netHeader := header.IPv4(netAndTransHeader) - return netHeader, header.UDP(netHeader.Payload()), true, true + if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv4MinimumSize, func(b []byte) header.Network { return header.IPv4(b) }); ok { + return netHdr, transHdr, true, true + } + case header.ICMPv6ProtocolNumber: + 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)) + } + + // We do not support extension headers in ICMP errors so the next header + // in the IPv6 packet should be a tracked protocol if we reach this point. + // + // TODO(https://gvisor.dev/issue/6789): Support extension headers. + transProto := pkt.tuple.id().transProto + if got := header.IPv6(h).TransportProtocol(); got != transProto { + panic(fmt.Sprintf("got TransportProtocol() = %d, want = %d", got, transProto)) + } + + if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv6MinimumSize, func(b []byte) header.Network { return header.IPv6(b) }); ok { + return netHdr, transHdr, true, true } } @@ -265,15 +283,37 @@ func getTupleIDForRegularPacket(netHdr header.Network, netProto tcpip.NetworkPro } } -func getTupleIDForPacketInICMPError(netHdr header.Network, netProto tcpip.NetworkProtocolNumber, transHdr header.Transport, transProto tcpip.TransportProtocolNumber) tupleID { - return tupleID{ - srcAddr: netHdr.DestinationAddress(), - srcPort: transHdr.DestinationPort(), - dstAddr: netHdr.SourceAddress(), - dstPort: transHdr.SourcePort(), - transProto: transProto, - netProto: netProto, +func getTupleIDForPacketInICMPError(pkt *PacketBuffer, netHdrFunc func([]byte) header.Network, netProto tcpip.NetworkProtocolNumber, netLen int, transProto tcpip.TransportProtocolNumber) (tupleID, bool) { + switch transProto { + case header.TCPProtocolNumber: + if netAndTransHeader, ok := pkt.Data().PullUp(netLen + header.TCPMinimumSize); ok { + netHdr := netHdrFunc(netAndTransHeader) + transHdr := header.TCP(netHdr.Payload()) + return tupleID{ + srcAddr: netHdr.DestinationAddress(), + srcPort: transHdr.DestinationPort(), + dstAddr: netHdr.SourceAddress(), + dstPort: transHdr.SourcePort(), + transProto: transProto, + netProto: netProto, + }, true + } + case header.UDPProtocolNumber: + if netAndTransHeader, ok := pkt.Data().PullUp(netLen + header.UDPMinimumSize); ok { + netHdr := netHdrFunc(netAndTransHeader) + transHdr := header.UDP(netHdr.Payload()) + return tupleID{ + srcAddr: netHdr.DestinationAddress(), + srcPort: transHdr.DestinationPort(), + dstAddr: netHdr.SourceAddress(), + dstPort: transHdr.SourcePort(), + transProto: transProto, + netProto: netProto, + }, true + } } + + return tupleID{}, false } func getTupleID(pkt *PacketBuffer) (tid tupleID, isICMPError bool, ok bool) { @@ -308,17 +348,30 @@ func getTupleID(pkt *PacketBuffer) (tid tupleID, isICMPError bool, ok bool) { // TODO(https://gvisor.dev/issue/6765): Handle IPv4 options. return tupleID{}, false, false } - switch ipv4.TransportProtocol() { - case header.TCPProtocolNumber: - if netAndTransHeader, ok := pkt.Data().PullUp(header.IPv4MinimumSize + header.TCPMinimumSize); ok { - netHdr := header.IPv4(netAndTransHeader) - return getTupleIDForPacketInICMPError(netHdr, header.IPv4ProtocolNumber, header.TCP(netHdr.Payload()), header.TCPProtocolNumber), true, true - } - case header.UDPProtocolNumber: - if netAndTransHeader, ok := pkt.Data().PullUp(header.IPv4MinimumSize + header.UDPMinimumSize); ok { - netHdr := header.IPv4(netAndTransHeader) - return getTupleIDForPacketInICMPError(netHdr, header.IPv4ProtocolNumber, header.UDP(netHdr.Payload()), header.UDPProtocolNumber), true, true - } + + if tid, ok := getTupleIDForPacketInICMPError(pkt, func(b []byte) header.Network { return header.IPv4(b) }, header.IPv4ProtocolNumber, header.IPv4MinimumSize, ipv4.TransportProtocol()); ok { + return tid, true, true + } + case header.ICMPv6ProtocolNumber: + icmp := header.ICMPv6(pkt.TransportHeader().View()) + if len(icmp) < header.ICMPv6MinimumSize { + return tupleID{}, false, false + } + + switch icmp.Type() { + case header.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem: + default: + return tupleID{}, false, false + } + + h, ok := pkt.Data().PullUp(header.IPv6MinimumSize) + if !ok { + return tupleID{}, false, false + } + + // TODO(https://gvisor.dev/issue/6789): Handle extension headers. + if tid, ok := getTupleIDForPacketInICMPError(pkt, func(b []byte) header.Network { return header.IPv6(b) }, header.IPv6ProtocolNumber, header.IPv6MinimumSize, header.IPv6(h).TransportProtocol()); ok { + return tid, true, true } } @@ -624,6 +677,33 @@ func (cn *conn) handlePacket(pkt *PacketBuffer, hook Hook, rt *Route) bool { } else { network.SetSourceAddressWithChecksumUpdate(tid.dstAddr) } + case header.ICMPv6ProtocolNumber: + network := header.IPv6(pkt.NetworkHeader().View()) + srcAddr := network.SourceAddress() + dstAddr := network.DestinationAddress() + if dnat { + dstAddr = tid.srcAddr + } else { + srcAddr = tid.dstAddr + } + + icmp := header.ICMPv6(pkt.TransportHeader().View()) + // TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum. + icmp.SetChecksum(0) + payload := pkt.Data() + icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ + Header: icmp, + Src: srcAddr, + Dst: dstAddr, + PayloadCsum: payload.AsRange().Checksum(), + PayloadLen: payload.Size(), + })) + + if dnat { + network.SetDestinationAddress(dstAddr) + } else { + network.SetSourceAddress(srcAddr) + } } return true diff --git a/pkg/tcpip/tests/integration/iptables_test.go b/pkg/tcpip/tests/integration/iptables_test.go index 9e00a6350..7fe3b29d9 100644 --- a/pkg/tcpip/tests/integration/iptables_test.go +++ b/pkg/tcpip/tests/integration/iptables_test.go @@ -1809,6 +1809,17 @@ func TestNATICMPError(t *testing.T) { ip.SetChecksum(^ip.CalculateChecksum()) } + ip6Hdr := func(v buffer.View, 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, + }) + } + tests := []struct { name string netProto tcpip.NetworkProtocolNumber @@ -1960,6 +1971,150 @@ func TestNATICMPError(t *testing.T) { }, }, }, + { + name: "IPv6", + netProto: ipv6.ProtocolNumber, + host1Addr: utils.Host1IPv6Addr.AddressWithPrefix.Address, + icmpError: func(t *testing.T, original buffer.View, icmpType uint8) buffer.View { + payloadLen := header.ICMPv6MinimumSize + len(original) + hdr := buffer.NewPrependable(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, + })) + ip6Hdr(hdr.Prepend(header.IPv6MinimumSize), + payloadLen, + header.ICMPv6ProtocolNumber, + utils.Host1IPv6Addr.AddressWithPrefix.Address, + utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address, + ) + return hdr.View() + }, + decrementTTL: func(v buffer.View) { + ip := header.IPv6(v) + ip.SetHopLimit(ip.HopLimit() - 1) + }, + checkNATedError: func(t *testing.T, v buffer.View, original buffer.View, 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() buffer.View { + hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.UDPMinimumSize) + udp := header.UDP(hdr.Prepend(header.UDPMinimumSize)) + udp.SetSourcePort(srcPort) + udp.SetDestinationPort(dstPort) + udp.SetChecksum(0) + udp.SetChecksum(^udp.CalculateChecksum(header.PseudoHeaderChecksum( + header.UDPProtocolNumber, + utils.Host2IPv6Addr.AddressWithPrefix.Address, + utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address, + uint16(len(udp)), + ))) + ip6Hdr(hdr.Prepend(header.IPv6MinimumSize), + header.UDPMinimumSize, + header.UDPProtocolNumber, + utils.Host2IPv6Addr.AddressWithPrefix.Address, + utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address, + ) + return hdr.View() + }(), + 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() buffer.View { + hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.TCPMinimumSize) + tcp := header.TCP(hdr.Prepend(header.TCPMinimumSize)) + tcp.SetSourcePort(srcPort) + tcp.SetDestinationPort(dstPort) + tcp.SetDataOffset(header.TCPMinimumSize) + tcp.SetChecksum(0) + tcp.SetChecksum(^tcp.CalculateChecksum(header.PseudoHeaderChecksum( + header.TCPProtocolNumber, + utils.Host2IPv6Addr.AddressWithPrefix.Address, + utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address, + uint16(len(tcp)), + ))) + ip6Hdr(hdr.Prepend(header.IPv6MinimumSize), + header.TCPMinimumSize, + header.TCPProtocolNumber, + utils.Host2IPv6Addr.AddressWithPrefix.Address, + utils.RouterNIC2IPv6Addr.AddressWithPrefix.Address, + ) + return hdr.View() + }(), + 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, + }, + }, + }, } for _, test := range tests { |