diff options
author | Julian Elischer <jrelis@google.com> | 2021-02-09 01:00:40 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-02-09 01:02:43 -0800 |
commit | 3f802e7180cfe703ecb97d8f2b6ba32257cf6216 (patch) | |
tree | da741ecec72e36a68b4e4868dbcb5b68a1ac3d5f /pkg/tcpip/network/ipv4/ipv4.go | |
parent | 6671a42d605d681e6aa63b610617523ab632e95a (diff) |
add IPv4 options processing for forwarding and reassembly
IPv4 forwarding and reassembly needs support for option processing
and regular processing also needs options to be processed before
being passed to the transport layer. This patch extends option processing
to those cases and provides additional testing. A small change to the ICMP
error generation API code was required to allow it to know when a packet was
being forwarded or not.
Updates #4586
PiperOrigin-RevId: 356446681
Diffstat (limited to 'pkg/tcpip/network/ipv4/ipv4.go')
-rw-r--r-- | pkg/tcpip/network/ipv4/ipv4.go | 114 |
1 files changed, 95 insertions, 19 deletions
diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index 7de438fe3..c5e4034ce 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -561,6 +561,34 @@ func (e *endpoint) forwardPacket(pkt *stack.PacketBuffer) tcpip.Error { return e.protocol.returnError(&icmpReasonTTLExceeded{}, pkt) } + if opts := h.Options(); len(opts) != 0 { + newOpts, optProblem := e.processIPOptions(pkt, opts, &optionUsageForward{}) + if optProblem != nil { + if optProblem.NeedICMP { + _ = e.protocol.returnError(&icmpReasonParamProblem{ + pointer: optProblem.Pointer, + forwarding: true, + }, pkt) + e.protocol.stack.Stats().MalformedRcvdPackets.Increment() + e.stats.ip.MalformedPacketsReceived.Increment() + } + return nil // option problems are not reported locally. + } + copied := copy(opts, newOpts) + if copied != len(newOpts) { + panic(fmt.Sprintf("copied %d bytes of new options, expected %d bytes", copied, len(newOpts))) + } + // Since in forwarding we handle all options, including copying those we + // do not recognise, the options region should remain the same size which + // simplifies processing. As we MAY receive a packet with a lot of padded + // bytes after the "end of options list" byte, make sure we copy + // them as the legal padding value (0). + for i := copied; i < len(opts); i++ { + // Pad with 0 (EOL). RFC 791 page 23 says "The padding is zero". + opts[i] = byte(header.IPv4OptionListEndType) + } + } + dstAddr := h.DestinationAddress() // Check if the destination is owned by the stack. @@ -711,8 +739,9 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { } } - // The destination address should be an address we own or a group we joined - // for us to receive the packet. Otherwise, attempt to forward the packet. + // Before we do any processing, note if the packet was received as some + // sort of broadcast. The destination address should be an address we own + // or a group we joined. if addressEndpoint := e.AcquireAssignedAddress(dstAddr, e.nic.Promiscuous(), stack.CanBePrimaryEndpoint); addressEndpoint != nil { subnet := addressEndpoint.AddressWithPrefix().Subnet() addressEndpoint.DecRef() @@ -722,7 +751,6 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { stats.ip.InvalidDestinationAddressesReceived.Increment() return } - _ = e.forwardPacket(pkt) return } @@ -744,6 +772,21 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { stats.ip.MalformedFragmentsReceived.Increment() return } + if opts := h.Options(); len(opts) != 0 { + // If there are options we need to check them before we do assembly + // or we could be assembling errant packets. However we do not change the + // options as that could lead to double processing later. + if _, optProblem := e.processIPOptions(pkt, opts, &optionUsageVerify{}); optProblem != nil { + if optProblem.NeedICMP { + _ = e.protocol.returnError(&icmpReasonParamProblem{ + pointer: optProblem.Pointer, + }, pkt) + e.protocol.stack.Stats().MalformedRcvdPackets.Increment() + e.stats.ip.MalformedPacketsReceived.Increment() + } + return + } + } // The packet is a fragment, let's try to reassemble it. start := h.FragmentOffset() // Drop the fragment if the size of the reassembled payload would exceed the @@ -802,17 +845,10 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { e.handleICMP(pkt) return } - if p == header.IGMPProtocolNumber { - e.mu.Lock() - e.mu.igmp.handleIGMP(pkt) - e.mu.Unlock() - return - } + // ICMP handles options itself but do it here for all remaining destinations. if opts := h.Options(); len(opts) != 0 { - // TODO(gvisor.dev/issue/4586): - // When we add forwarding support we should use the verified options - // rather than just throwing them away. - if _, optProblem := e.processIPOptions(pkt, opts, &optionUsageReceive{}); optProblem != nil { + newOpts, optProblem := e.processIPOptions(pkt, opts, &optionUsageReceive{}) + if optProblem != nil { if optProblem.NeedICMP { _ = e.protocol.returnError(&icmpReasonParamProblem{ pointer: optProblem.Pointer, @@ -822,6 +858,20 @@ func (e *endpoint) handlePacket(pkt *stack.PacketBuffer) { } return } + copied := copy(opts, newOpts) + if copied != len(newOpts) { + panic(fmt.Sprintf("copied %d bytes of new options, expected %d bytes", copied, len(newOpts))) + } + for i := copied; i < len(opts); i++ { + // Pad with 0 (EOL). RFC 791 page 23 says "The padding is zero". + opts[i] = byte(header.IPv4OptionListEndType) + } + } + if p == header.IGMPProtocolNumber { + e.mu.Lock() + e.mu.igmp.handleIGMP(pkt) + e.mu.Unlock() + return } switch res := e.dispatcher.DeliverTransportPacket(p, pkt); res { @@ -1254,23 +1304,49 @@ type optionsUsage interface { actions() optionActions } -// optionUsageReceive implements optionsUsage for received packets. -type optionUsageReceive struct{} +// optionUsageVerify implements optionsUsage for when we just want to check +// fragments. Don't change anything, just check and reject if bad. No +// replacement options are generated. +type optionUsageVerify struct{} // actions implements optionsUsage. -func (*optionUsageReceive) actions() optionActions { +func (*optionUsageVerify) actions() optionActions { return optionActions{ timestamp: optionVerify, recordRoute: optionVerify, + unknown: optionRemove, + } +} + +// optionUsageReceive implements optionsUsage for packets we will pass +// to the transport layer (with the exception of Echo requests). +type optionUsageReceive struct{} + +// actions implements optionsUsage. +func (*optionUsageReceive) actions() optionActions { + return optionActions{ + timestamp: optionProcess, + recordRoute: optionProcess, unknown: optionPass, } } -// TODO(gvisor.dev/issue/4586): Add an entry here for forwarding when it -// is enabled (Process, Process, Pass) and for fragmenting (Process, Process, -// Pass for frag1, but Remove,Remove,Remove for all other frags). +// optionUsageForward implements optionsUsage for packets about to be forwarded. +// All options are passed on regardless of whether we recognise them, however +// we do process the Timestamp and Record Route options. +type optionUsageForward struct{} + +// actions implements optionsUsage. +func (*optionUsageForward) actions() optionActions { + return optionActions{ + timestamp: optionProcess, + recordRoute: optionProcess, + unknown: optionPass, + } +} // optionUsageEcho implements optionsUsage for echo packet processing. +// Only Timestamp and RecordRoute are processed and sent back. type optionUsageEcho struct{} // actions implements optionsUsage. |