diff options
author | Julian Elischer <jrelis@google.com> | 2020-10-05 20:41:50 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2020-10-05 20:43:55 -0700 |
commit | 798cc6b04dc1206538b2cedb1af427e0d5468b46 (patch) | |
tree | c45c33e2bd35ccf9001a4b127af58cabf11c1228 /pkg/tcpip/network/ipv4 | |
parent | a1df7f2ed1be5cb170218c3e127a9b5f51a314fd (diff) |
Fix IPv4 ICMP echo handler to copy options
The IPv4 RFCs are specific (though obtuse) that an echo response
packet needs to contain all the options from the echo request,
much as if it been routed back to the sender, though apparently
with a new TTL. They suggest copying the incoming packet header
to achieve this so that is what this patch does.
PiperOrigin-RevId: 335559176
Diffstat (limited to 'pkg/tcpip/network/ipv4')
-rw-r--r-- | pkg/tcpip/network/ipv4/icmp.go | 84 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4.go | 2 | ||||
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4_test.go | 78 |
3 files changed, 107 insertions, 57 deletions
diff --git a/pkg/tcpip/network/ipv4/icmp.go b/pkg/tcpip/network/ipv4/icmp.go index a8985ff5d..eab9a530c 100644 --- a/pkg/tcpip/network/ipv4/icmp.go +++ b/pkg/tcpip/network/ipv4/icmp.go @@ -79,27 +79,27 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer) { received.Echo.Increment() // Only send a reply if the checksum is valid. - wantChecksum := h.Checksum() - // Reset the checksum field to 0 to can calculate the proper - // checksum. We'll have to reset this before we hand the packet - // off. + headerChecksum := h.Checksum() h.SetChecksum(0) - gotChecksum := ^header.ChecksumVV(pkt.Data, 0 /* initial */) - if gotChecksum != wantChecksum { - // It's possible that a raw socket expects to receive this. - h.SetChecksum(wantChecksum) + calculatedChecksum := ^header.ChecksumVV(pkt.Data, 0 /* initial */) + h.SetChecksum(headerChecksum) + if calculatedChecksum != headerChecksum { + // It's possible that a raw socket still expects to receive this. e.dispatcher.DeliverTransportPacket(r, header.ICMPv4ProtocolNumber, pkt) received.Invalid.Increment() return } - // Make a copy of data before pkt gets sent to raw socket. - // DeliverTransportPacket will take ownership of pkt. - replyData := pkt.Data.Clone(nil) - replyData.TrimFront(header.ICMPv4MinimumSize) + // DeliverTransportPacket will take ownership of pkt so don't use it beyond + // this point. Make a deep copy of the data before pkt gets sent as we will + // be modifying fields. + // + // TODO(gvisor.dev/issue/4399): The copy may not be needed if there are no + // waiting endpoints. Consider moving responsibility for doing the copy to + // DeliverTransportPacket so that is is only done when needed. + replyData := pkt.Data.ToOwnedView() + replyIPHdr := header.IPv4(append(buffer.View(nil), pkt.NetworkHeader().View()...)) - // It's possible that a raw socket expects to receive this. - h.SetChecksum(wantChecksum) e.dispatcher.DeliverTransportPacket(r, header.ICMPv4ProtocolNumber, pkt) remoteLinkAddr := r.RemoteLinkAddress @@ -122,29 +122,49 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer) { // Use the remote link address from the incoming packet. r.ResolveWith(remoteLinkAddr) - // Prepare a reply packet. - icmpHdr := make(header.ICMPv4, header.ICMPv4MinimumSize) - copy(icmpHdr, h) - icmpHdr.SetType(header.ICMPv4EchoReply) - icmpHdr.SetChecksum(0) - icmpHdr.SetChecksum(^header.Checksum(icmpHdr, header.ChecksumVV(replyData, 0))) - dataVV := buffer.View(icmpHdr).ToVectorisedView() - dataVV.Append(replyData) + // TODO(gvisor.dev/issue/3810:) When adding protocol numbers into the + // header information, we may have to change this code to handle the + // ICMP header no longer being in the data buffer. + + // Because IP and ICMP are so closely intertwined, we need to handcraft our + // IP header to be able to follow RFC 792. The wording on page 13 is as + // follows: + // IP Fields: + // Addresses + // The address of the source in an echo message will be the + // destination of the echo reply message. To form an echo reply + // message, the source and destination addresses are simply reversed, + // the type code changed to 0, and the checksum recomputed. + // + // This was interpreted by early implementors to mean that all options must + // be copied from the echo request IP header to the echo reply IP header + // and this behaviour is still relied upon by some applications. + // + // Create a copy of the IP header we received, options and all, and change + // The fields we need to alter. + // + // We need to produce the entire packet in the data segment in order to + // use WriteHeaderIncludedPacket(). + replyIPHdr.SetSourceAddress(r.LocalAddress) + replyIPHdr.SetDestinationAddress(r.RemoteAddress) + replyIPHdr.SetTTL(r.DefaultTTL()) + + replyICMPHdr := header.ICMPv4(replyData) + replyICMPHdr.SetType(header.ICMPv4EchoReply) + replyICMPHdr.SetChecksum(0) + replyICMPHdr.SetChecksum(^header.Checksum(replyData, 0)) + + replyVV := buffer.View(replyIPHdr).ToVectorisedView() + replyVV.AppendView(replyData) replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ ReserveHeaderBytes: int(r.MaxHeaderLength()), - Data: dataVV, + Data: replyVV, }) - // TODO(gvisor.dev/issue/3810): When adding protocol numbers into the header - // information we will have to change this code to handle the ICMP header - // no longer being in the data buffer. replyPkt.TransportProtocolNumber = header.ICMPv4ProtocolNumber - // Send out the reply packet. + + // The checksum will be calculated so we don't need to do it here. sent := stats.ICMP.V4PacketsSent - if err := r.WritePacket(nil /* gso */, stack.NetworkHeaderParams{ - Protocol: header.ICMPv4ProtocolNumber, - TTL: r.DefaultTTL(), - TOS: stack.DefaultTOS, - }, replyPkt); err != nil { + if err := r.WriteHeaderIncludedPacket(replyPkt); err != nil { sent.Dropped.Increment() return } diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index 1f6e14c3f..a2be64fb8 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -174,7 +174,7 @@ func (e *endpoint) MTU() uint32 { // MaxHeaderLength returns the maximum length needed by ipv4 headers (and // underlying protocols). func (e *endpoint) MaxHeaderLength() uint16 { - return e.linkEP.MaxHeaderLength() + header.IPv4MinimumSize + return e.linkEP.MaxHeaderLength() + header.IPv4MaximumHeaderSize } // GSOMaxSize returns the maximum GSO packet size. diff --git a/pkg/tcpip/network/ipv4/ipv4_test.go b/pkg/tcpip/network/ipv4/ipv4_test.go index 33cd5a3eb..712fbb861 100644 --- a/pkg/tcpip/network/ipv4/ipv4_test.go +++ b/pkg/tcpip/network/ipv4/ipv4_test.go @@ -115,22 +115,21 @@ func TestIPv4Sanity(t *testing.T) { tests := []struct { name string - headerLength uint8 - minTotalLength uint16 + headerLength uint8 // value of 0 means "use correct size" + maxTotalLength uint16 transportProtocol uint8 TTL uint8 shouldFail bool expectICMP bool ICMPType header.ICMPv4Type ICMPCode header.ICMPv4Code + options []byte }{ { name: "valid", - headerLength: header.IPv4MinimumSize, - minTotalLength: defaultMTU, + maxTotalLength: defaultMTU, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, - shouldFail: false, }, // The TTL tests check that we are not rejecting an incoming packet // with a zero or one TTL, which has been a point of confusion in the @@ -145,24 +144,43 @@ func TestIPv4Sanity(t *testing.T) { // received with TTL less than 2. { name: "zero TTL", - headerLength: header.IPv4MinimumSize, - minTotalLength: defaultMTU, + maxTotalLength: defaultMTU, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: 0, shouldFail: false, }, { name: "one TTL", - headerLength: header.IPv4MinimumSize, - minTotalLength: defaultMTU, + maxTotalLength: defaultMTU, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: 1, shouldFail: false, }, { + name: "End options", + maxTotalLength: defaultMTU, + transportProtocol: uint8(header.ICMPv4ProtocolNumber), + TTL: ttl, + options: []byte{0, 0, 0, 0}, + }, + { + name: "NOP options", + maxTotalLength: defaultMTU, + transportProtocol: uint8(header.ICMPv4ProtocolNumber), + TTL: ttl, + options: []byte{1, 1, 1, 1}, + }, + { + name: "NOP and End options", + maxTotalLength: defaultMTU, + transportProtocol: uint8(header.ICMPv4ProtocolNumber), + TTL: ttl, + options: []byte{1, 1, 0, 0}, + }, + { name: "bad header length", headerLength: header.IPv4MinimumSize - 1, - minTotalLength: defaultMTU, + maxTotalLength: defaultMTU, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, shouldFail: true, @@ -170,8 +188,7 @@ func TestIPv4Sanity(t *testing.T) { }, { name: "bad total length (0)", - headerLength: header.IPv4MinimumSize, - minTotalLength: 0, + maxTotalLength: 0, transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, shouldFail: true, @@ -179,8 +196,7 @@ func TestIPv4Sanity(t *testing.T) { }, { name: "bad total length (ip - 1)", - headerLength: header.IPv4MinimumSize, - minTotalLength: uint16(header.IPv4MinimumSize - 1), + maxTotalLength: uint16(header.IPv4MinimumSize - 1), transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, shouldFail: true, @@ -188,8 +204,7 @@ func TestIPv4Sanity(t *testing.T) { }, { name: "bad total length (ip + icmp - 1)", - headerLength: header.IPv4MinimumSize, - minTotalLength: uint16(header.IPv4MinimumSize + header.ICMPv4MinimumSize - 1), + maxTotalLength: uint16(header.IPv4MinimumSize + header.ICMPv4MinimumSize - 1), transportProtocol: uint8(header.ICMPv4ProtocolNumber), TTL: ttl, shouldFail: true, @@ -197,8 +212,7 @@ func TestIPv4Sanity(t *testing.T) { }, { name: "bad protocol", - headerLength: header.IPv4MinimumSize, - minTotalLength: defaultMTU, + maxTotalLength: defaultMTU, transportProtocol: 99, TTL: ttl, shouldFail: true, @@ -233,7 +247,15 @@ func TestIPv4Sanity(t *testing.T) { }, }) - ipHeaderLength := header.IPv4MinimumSize + // Round up the header size to the next multiple of 4 as RFC 791, page 11 + // says: "Internet Header Length is the length of the internet header + // in 32 bit words..." and on page 23: "The internet header padding is + // used to ensure that the internet header ends on a 32 bit boundary." + ipHeaderLength := ((header.IPv4MinimumSize + len(test.options)) + header.IPv4IHLStride - 1) & ^(header.IPv4IHLStride - 1) + + if ipHeaderLength > header.IPv4MaximumHeaderSize { + t.Fatalf("too many bytes in options: got = %d, want <= %d ", ipHeaderLength, header.IPv4MaximumHeaderSize) + } totalLen := uint16(ipHeaderLength + header.ICMPv4MinimumSize) hdr := buffer.NewPrependable(int(totalLen)) icmp := header.ICMPv4(hdr.Prepend(header.ICMPv4MinimumSize)) @@ -246,17 +268,24 @@ func TestIPv4Sanity(t *testing.T) { icmp.SetChecksum(0) icmp.SetChecksum(^header.Checksum(icmp, 0)) ip := header.IPv4(hdr.Prepend(ipHeaderLength)) - if test.minTotalLength < totalLen { - totalLen = test.minTotalLength + if test.maxTotalLength < totalLen { + totalLen = test.maxTotalLength } ip.Encode(&header.IPv4Fields{ - IHL: test.headerLength, + IHL: uint8(ipHeaderLength), TotalLength: totalLen, Protocol: test.transportProtocol, TTL: test.TTL, SrcAddr: remoteIPv4Addr, DstAddr: ipv4Addr.Address, }) + if n := copy(ip.Options(), test.options); n != len(test.options) { + t.Fatalf("options larger than available space: copied %d/%d bytes", n, len(test.options)) + } + // Override the correct value if the test case specified one. + if test.headerLength != 0 { + ip.SetHeaderLength(test.headerLength) + } requestPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ Data: hdr.View().ToVectorisedView(), }) @@ -301,7 +330,7 @@ func TestIPv4Sanity(t *testing.T) { case header.ICMPv4DstUnreachable: checker.IPv4(t, replyIPHeader, checker.IPFullLength(uint16(header.IPv4MinimumSize+header.ICMPv4MinimumSize+requestPkt.Size())), - checker.IPv4HeaderLength(ipHeaderLength), + checker.IPv4HeaderLength(header.IPv4MinimumSize), checker.ICMPv4( checker.ICMPv4Code(test.ICMPCode), checker.ICMPv4Checksum(), @@ -316,9 +345,10 @@ func TestIPv4Sanity(t *testing.T) { case header.ICMPv4EchoReply: checker.IPv4(t, replyIPHeader, checker.IPv4HeaderLength(ipHeaderLength), + checker.IPv4Options(test.options), checker.IPFullLength(uint16(requestPkt.Size())), checker.ICMPv4( - checker.ICMPv4Code(test.ICMPCode), + checker.ICMPv4Code(header.ICMPv4UnusedCode), checker.ICMPv4Seq(randomSequence), checker.ICMPv4Ident(randomIdent), checker.ICMPv4Checksum(), |