diff options
Diffstat (limited to 'pkg/tcpip/network')
-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(), |