diff options
author | Ghanan Gowripalan <ghanan@google.com> | 2021-02-08 19:03:54 -0800 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-02-08 19:05:45 -0800 |
commit | 39251f31cb92d6c2b053416d04e195e290b106f2 (patch) | |
tree | bf3c80dc631655f48fc0b9686cfe2af2e6a4ab74 /pkg/tcpip/network/ipv6/ndp.go | |
parent | cfa4633c3d206aa2f9abdaac60d053162244ee6d (diff) |
Support performing DAD for any address
...as long as the network protocol supports duplicate address detection.
This CL provides the facilities for a netstack integrator to perform
DAD.
DHCP recommends that clients effectively perform DAD before accepting an
offer. As per RFC 2131 section 4.4.1 pg 38,
The client SHOULD perform a check on the suggested address to ensure
that the address is not already in use. For example, if the client
is on a network that supports ARP, the client may issue an ARP request
for the suggested request.
The implementation of ARP-based IPv4 DAD effectively operates the same
as IPv6's NDP DAD - using ARP requests and responses in place of
NDP neighbour solicitations and advertisements, respectively.
DAD performed by calls to (*Stack).CheckDuplicateAddress don't interfere
with DAD performed when a new IPv6 address is added. This is so that
integrator requests to check for duplicate addresses aren't unexpectedly
aborted when addresses are removed.
A network package internal package provides protocol agnostic DAD state
management that specific protocols that provide DAD can use.
Fixes #4550.
Tests:
- internal/ip_test.*
- integration_test.TestDAD
- arp_test.TestDADARPRequestPacket
- ipv6.TestCheckDuplicateAddress
PiperOrigin-RevId: 356405593
Diffstat (limited to 'pkg/tcpip/network/ipv6/ndp.go')
-rw-r--r-- | pkg/tcpip/network/ipv6/ndp.go | 253 |
1 files changed, 77 insertions, 176 deletions
diff --git a/pkg/tcpip/network/ipv6/ndp.go b/pkg/tcpip/network/ipv6/ndp.go index 411a6c862..a55330b7e 100644 --- a/pkg/tcpip/network/ipv6/ndp.go +++ b/pkg/tcpip/network/ipv6/ndp.go @@ -23,34 +23,11 @@ import ( "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip" "gvisor.dev/gvisor/pkg/tcpip/stack" ) const ( - // defaultRetransmitTimer is the default amount of time to wait between - // sending reachability probes. - // - // Default taken from RETRANS_TIMER of RFC 4861 section 10. - defaultRetransmitTimer = time.Second - - // minimumRetransmitTimer is the minimum amount of time to wait between - // sending reachability probes. - // - // Note, RFC 4861 does not impose a minimum Retransmit Timer, but we do here - // to make sure the messages are not sent all at once. We also come to this - // value because in the RetransmitTimer field of a Router Advertisement, a - // value of 0 means unspecified, so the smallest valid value is 1. Note, the - // unit of the RetransmitTimer field in the Router Advertisement is - // milliseconds. - minimumRetransmitTimer = time.Millisecond - - // defaultDupAddrDetectTransmits is the default number of NDP Neighbor - // Solicitation messages to send when doing Duplicate Address Detection - // for a tentative address. - // - // Default = 1 (from RFC 4862 section 5.1) - defaultDupAddrDetectTransmits = 1 - // defaultMaxRtrSolicitations is the default number of Router // Solicitation messages to send when an IPv6 endpoint becomes enabled. // @@ -330,18 +307,6 @@ type NDPDispatcher interface { // NDPConfigurations is the NDP configurations for the netstack. type NDPConfigurations struct { - // The number of Neighbor Solicitation messages to send when doing - // Duplicate Address Detection for a tentative address. - // - // Note, a value of zero effectively disables DAD. - DupAddrDetectTransmits uint8 - - // The amount of time to wait between sending Neighbor solicitation - // messages. - // - // Must be greater than or equal to 1ms. - RetransmitTimer time.Duration - // The number of Router Solicitation messages to send when the IPv6 endpoint // becomes enabled. MaxRtrSolicitations uint8 @@ -413,8 +378,6 @@ type NDPConfigurations struct { // default values. func DefaultNDPConfigurations() NDPConfigurations { return NDPConfigurations{ - DupAddrDetectTransmits: defaultDupAddrDetectTransmits, - RetransmitTimer: defaultRetransmitTimer, MaxRtrSolicitations: defaultMaxRtrSolicitations, RtrSolicitationInterval: defaultRtrSolicitationInterval, MaxRtrSolicitationDelay: defaultMaxRtrSolicitationDelay, @@ -432,10 +395,6 @@ func DefaultNDPConfigurations() NDPConfigurations { // validate modifies an NDPConfigurations with valid values. If invalid values // are present in c, the corresponding default values are used instead. func (c *NDPConfigurations) validate() { - if c.RetransmitTimer < minimumRetransmitTimer { - c.RetransmitTimer = defaultRetransmitTimer - } - if c.RtrSolicitationInterval < minimumRtrSolicitationInterval { c.RtrSolicitationInterval = defaultRtrSolicitationInterval } @@ -476,7 +435,7 @@ type ndpState struct { configs NDPConfigurations // The DAD timers to send the next NS message, or resolve the address. - dad map[tcpip.Address]timer + dad ip.DAD // The default routers discovered through Router Advertisements. defaultRouters map[tcpip.Address]defaultRouterState @@ -635,130 +594,46 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE panic(fmt.Sprintf("ndpdad: addr %s is not tentative on NIC(%d)", addr, ndp.ep.nic.ID())) } - // Should not attempt to perform DAD on an address that is currently in the - // DAD process. - if _, ok := ndp.dad[addr]; ok { - // Should never happen because we should only ever call this function for - // newly created addresses. If we attemped to "add" an address that already - // existed, we would get an error since we attempted to add a duplicate - // address, or its reference count would have been increased without doing - // the work that would have been done for an address that was brand new. - // See endpoint.addAddressLocked. - panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.ep.nic.ID())) - } + ret := ndp.dad.CheckDuplicateAddressLocked(addr, func(r stack.DADResult) { + if addressEndpoint.GetKind() != stack.PermanentTentative { + // The endpoint should still be marked as tentative since we are still + // performing DAD on it. + panic(fmt.Sprintf("ndpdad: addr %s is no longer tentative on NIC(%d)", addr, ndp.ep.nic.ID())) + } - if ndp.configs.DupAddrDetectTransmits == 0 { - addressEndpoint.SetKind(stack.Permanent) + if r.Resolved { + addressEndpoint.SetKind(stack.Permanent) + } - // Consider DAD to have resolved even if no DAD messages were actually - // transmitted. if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, true, nil) + ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, r.Resolved, r.Err) } - ndp.ep.onAddressAssignedLocked(addr) - return nil - } - - var remaining remainingCounter - remaining.init(ndp.configs.DupAddrDetectTransmits) - // We initially start a timer to fire immediately because some of the DAD work - // cannot be done while holding the IPv6 endpoint's lock. This is effectively - // the same as starting a goroutine but we use a timer that fires immediately - // so we can reset it for the next DAD iteration. - - // Protected by ndp.ep.mu. - done := false - - ndp.dad[addr] = timer{ - done: &done, - timer: ndp.ep.protocol.stack.Clock().AfterFunc(0, func() { - // Okay to hold this lock while writing packets since we use a different - // lock per DAD timer so there will not be any lock contention. - remaining.mu.Lock() - defer remaining.mu.Unlock() - - var err tcpip.Error - dadDone := remaining.mu.remaining == 0 - if !dadDone { - snmc := header.SolicitedNodeAddr(addr) - - icmp := header.ICMPv6(buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize)) - icmp.SetType(header.ICMPv6NeighborSolicit) - ns := header.NDPNeighborSolicit(icmp.MessageBody()) - ns.SetTargetAddress(addr) - icmp.SetChecksum(header.ICMPv6Checksum(icmp, header.IPv6Any, snmc, buffer.VectorisedView{})) - - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: int(ndp.ep.MaxHeaderLength()), - Data: buffer.View(icmp).ToVectorisedView(), - }) - - sent := ndp.ep.stats.icmp.packetsSent - if err := addIPHeader(header.IPv6Any, snmc, pkt, stack.NetworkHeaderParams{ - Protocol: header.ICMPv6ProtocolNumber, - TTL: header.NDPHopLimit, - }, nil /* extensionHeaders */); err != nil { - panic(fmt.Sprintf("failed to add IP header: %s", err)) - } - - err = ndp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(snmc), nil /* gso */, ProtocolNumber, pkt) - if err != nil { - sent.dropped.Increment() - } else { - sent.neighborSolicit.Increment() - } + if r.Resolved { + if addressEndpoint.ConfigType() == stack.AddressConfigSlaac { + // Reset the generation attempts counter as we are starting the + // generation of a new address for the SLAAC prefix. + ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */) } - ndp.ep.mu.Lock() - defer ndp.ep.mu.Unlock() - - if done { - // DAD was stopped. - return - } - - timer, ok := ndp.dad[addr] - if !ok { - panic(fmt.Sprintf("ndpdad: DAD timer fired but missing state for %s on NIC(%d)", addr, ndp.ep.nic.ID())) - } - - if addressEndpoint.GetKind() != stack.PermanentTentative { - // The endpoint should still be marked as tentative since we are still - // performing DAD on it. - panic(fmt.Sprintf("ndpdad: addr %s is no longer tentative on NIC(%d)", addr, ndp.ep.nic.ID())) - } - - if dadDone { - // DAD has resolved. - addressEndpoint.SetKind(stack.Permanent) - } else if err == nil { - // DAD is not done and we had no errors when sending the last NDP NS, - // schedule the next DAD timer. - remaining.mu.remaining-- - timer.timer.Reset(ndp.configs.RetransmitTimer) - return - } - - // At this point we know that either DAD is done or we hit an error - // sending the last NDP NS. Either way, clean up addr's DAD state and let - // the integrator know DAD has completed. - delete(ndp.dad, addr) + ndp.ep.onAddressAssignedLocked(addr) + } + }) - if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, dadDone, err) - } + switch ret { + case stack.DADStarting: + case stack.DADAlreadyRunning: + panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.ep.nic.ID())) + case stack.DADDisabled: + addressEndpoint.SetKind(stack.Permanent) - if dadDone { - if addressEndpoint.ConfigType() == stack.AddressConfigSlaac { - // Reset the generation attempts counter as we are starting the - // generation of a new address for the SLAAC prefix. - ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */) - } + // Consider DAD to have resolved even if no DAD messages were actually + // transmitted. + if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { + ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, true, nil) + } - ndp.ep.onAddressAssignedLocked(addr) - } - }), + ndp.ep.onAddressAssignedLocked(addr) } return nil @@ -772,21 +647,8 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE // of this function to handle such a scenario. // // The IPv6 endpoint that ndp belongs to MUST be locked. -func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address) { - timer, ok := ndp.dad[addr] - if !ok { - // Not currently performing DAD on addr, just return. - return - } - - timer.timer.Stop() - *timer.done = true - delete(ndp.dad, addr) - - // Let the integrator know DAD did not resolve. - if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, false, nil) - } +func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address, failed bool) { + ndp.dad.StopLocked(addr, !failed) } // handleRA handles a Router Advertisement message that arrived on the NIC @@ -1651,7 +1513,7 @@ func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefi if addressEndpoint := state.stableAddr.addressEndpoint; addressEndpoint != nil { // Since we are already invalidating the prefix, do not invalidate the // prefix when removing the address. - if err := ndp.ep.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */); err != nil { + if err := ndp.ep.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */, false /* dadFailure */); err != nil { panic(fmt.Sprintf("ndp: error removing stable SLAAC address %s: %s", addressEndpoint.AddressWithPrefix(), err)) } } @@ -1710,7 +1572,7 @@ func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaa func (ndp *ndpState) invalidateTempSLAACAddr(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) { // Since we are already invalidating the address, do not invalidate the // address when removing the address. - if err := ndp.ep.removePermanentEndpointLocked(tempAddrState.addressEndpoint, false /* allowSLAACInvalidation */); err != nil { + if err := ndp.ep.removePermanentEndpointLocked(tempAddrState.addressEndpoint, false /* allowSLAACInvalidation */, false /* dadFailure */); err != nil { panic(fmt.Sprintf("error removing temporary SLAAC address %s: %s", tempAddrState.addressEndpoint.AddressWithPrefix(), err)) } @@ -1942,13 +1804,17 @@ func (ndp *ndpState) stopSolicitingRouters() { } func (ndp *ndpState) init(ep *endpoint) { - if ndp.dad != nil { + if ndp.defaultRouters != nil { panic("attempted to initialize NDP state twice") } ndp.ep = ep ndp.configs = ep.protocol.options.NDPConfigs - ndp.dad = make(map[tcpip.Address]timer) + ndp.dad.Init(&ndp.ep.mu, ep.protocol.options.DADConfigs, ip.DADOptions{ + Clock: ep.protocol.stack.Clock(), + Protocol: ndp, + NICID: ep.nic.ID(), + }) ndp.defaultRouters = make(map[tcpip.Address]defaultRouterState) ndp.onLinkPrefixes = make(map[tcpip.Subnet]onLinkPrefixState) ndp.slaacPrefixes = make(map[tcpip.Subnet]slaacPrefixState) @@ -1958,3 +1824,38 @@ func (ndp *ndpState) init(ep *endpoint) { ndp.temporaryAddressDesyncFactor = time.Duration(rand.Int63n(int64(MaxDesyncFactor))) } } + +func (ndp *ndpState) SendDADMessage(addr tcpip.Address) tcpip.Error { + snmc := header.SolicitedNodeAddr(addr) + return ndp.ep.sendNDPNS(header.IPv6Any, snmc, addr, header.EthernetAddressFromMulticastIPv6Address(snmc), nil /* opts */) +} + +func (e *endpoint) sendNDPNS(srcAddr, dstAddr, targetAddr tcpip.Address, remoteLinkAddr tcpip.LinkAddress, opts header.NDPOptionsSerializer) tcpip.Error { + icmp := header.ICMPv6(buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize + opts.Length())) + icmp.SetType(header.ICMPv6NeighborSolicit) + ns := header.NDPNeighborSolicit(icmp.MessageBody()) + ns.SetTargetAddress(targetAddr) + ns.Options().Serialize(opts) + icmp.SetChecksum(header.ICMPv6Checksum(icmp, srcAddr, dstAddr, buffer.VectorisedView{})) + + pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + ReserveHeaderBytes: int(e.MaxHeaderLength()), + Data: buffer.View(icmp).ToVectorisedView(), + }) + + if err := addIPHeader(srcAddr, dstAddr, pkt, stack.NetworkHeaderParams{ + Protocol: header.ICMPv6ProtocolNumber, + TTL: header.NDPHopLimit, + }, nil /* extensionHeaders */); err != nil { + panic(fmt.Sprintf("failed to add IP header: %s", err)) + } + + sent := e.stats.icmp.packetsSent + err := e.nic.WritePacketToRemote(remoteLinkAddr, nil /* gso */, ProtocolNumber, pkt) + if err != nil { + sent.dropped.Increment() + } else { + sent.neighborSolicit.Increment() + } + return err +} |