From be6ffa78e4df78df13d004a17f2a8833305285c4 Mon Sep 17 00:00:00 2001 From: Tony Gong Date: Thu, 7 Jul 2022 19:56:11 -0700 Subject: [PATCH] Introduce and Implement AddressDispatcher Introduce the AddressDispatcher interface which integrators can provide an implementation at the time of adding an address to receive callbacks when address properties change and when the address is removed. Modify `NDPDispatcher`'s callback when a SLAAC address is added to receive an implementation of `AddressDispatcher`. Added informational preferred and valid lifetime fields to `AddressProperties` so they can be set when adding the address; and a way to update said lifetimes. Added a means to disable an `AddressableEndpointState` and each individual `addressState`, so that the `AddressDisabled` assignment state can be reported to integrators. Added a configurable option to `AddressableEndpointState` which determines whether addresses of kind `PermanentDisabled` are included in the return value of `PrimaryAddresses` and `PermanentAddresses`. This option is set such that IPv4 addresses are returned, while IPv6 addresses are hidden, when the NIC is disabled. This is a change in behavior for IPv6, but is consistent with behavior on Linux. Modified tests in `ndp_test` to use the new AddressDispatcher. Fixed some bugs along the way. PiperOrigin-RevId: 459658009 --- pkg/tcpip/network/ipv4/ipv4.go | 17 +- pkg/tcpip/network/ipv6/ipv6.go | 128 +- pkg/tcpip/network/ipv6/ndp.go | 106 +- pkg/tcpip/network/ipv6/ndp_test.go | 3 +- pkg/tcpip/stack/addressable_endpoint_state.go | 239 ++- .../stack/addressable_endpoint_state_test.go | 2 +- pkg/tcpip/stack/forwarding_test.go | 2 +- pkg/tcpip/stack/ndp_test.go | 1416 +++++++++++------ pkg/tcpip/stack/nic.go | 4 +- pkg/tcpip/stack/nic_test.go | 2 +- pkg/tcpip/stack/registration.go | 148 +- pkg/tcpip/stack/stack.go | 7 +- pkg/tcpip/stack/stack_test.go | 132 +- pkg/tcpip/tcpip.go | 7 + pkg/tcpip/tests/integration/loopback_test.go | 3 +- 15 files changed, 1551 insertions(+), 665 deletions(-) diff --git a/pkg/tcpip/network/ipv4/ipv4.go b/pkg/tcpip/network/ipv4/ipv4.go index 481a170dc..cd94f8409 100644 --- a/pkg/tcpip/network/ipv4/ipv4.go +++ b/pkg/tcpip/network/ipv4/ipv4.go @@ -142,7 +142,7 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.Tran protocol: p, } e.mu.Lock() - e.addressableEndpointState.Init(e) + e.addressableEndpointState.Init(e, stack.AddressableEndpointStateOptions{HiddenWhileDisabled: false}) e.igmp.init(e) e.mu.Unlock() @@ -276,6 +276,9 @@ func (e *endpoint) enableLocked() tcpip.Error { return nil } + // Must be called after Enabled has already been set. + e.addressableEndpointState.OnNetworkEndpointEnabledChanged() + // Create an endpoint to receive broadcast packets on this interface. ep, err := e.addressableEndpointState.AddAndAcquirePermanentAddress(ipv4BroadcastAddr, stack.AddressProperties{PEB: stack.NeverPrimaryEndpoint}) if err != nil { @@ -364,6 +367,9 @@ func (e *endpoint) disableLocked() { if !e.setEnabled(false) { panic("should have only done work to disable the endpoint if it was enabled") } + + // Must be called after Enabled has been set. + e.addressableEndpointState.OnNetworkEndpointEnabledChanged() } // emitMulticastEvent emits a multicast forwarding event using the provided @@ -1277,7 +1283,7 @@ func (e *endpoint) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, p e.mu.RLock() defer e.mu.RUnlock() - ep, err := e.addressableEndpointState.AddAndAcquirePermanentAddress(addr, properties) + ep, err := e.addressableEndpointState.AddAndAcquireAddress(addr, properties, stack.Permanent) if err == nil { e.sendQueuedReports() } @@ -1306,6 +1312,13 @@ func (e *endpoint) SetDeprecated(addr tcpip.Address, deprecated bool) tcpip.Erro return e.addressableEndpointState.SetDeprecated(addr, deprecated) } +// SetLifetimes implements stack.AddressableEndpoint. +func (e *endpoint) SetLifetimes(addr tcpip.Address, lifetimes stack.AddressLifetimes) tcpip.Error { + e.mu.RLock() + defer e.mu.RUnlock() + return e.addressableEndpointState.SetLifetimes(addr, lifetimes) +} + // MainAddress implements stack.AddressableEndpoint. func (e *endpoint) MainAddress() tcpip.AddressWithPrefix { e.mu.RLock() diff --git a/pkg/tcpip/network/ipv6/ipv6.go b/pkg/tcpip/network/ipv6/ipv6.go index 40d6bd511..aca36a87c 100644 --- a/pkg/tcpip/network/ipv6/ipv6.go +++ b/pkg/tcpip/network/ipv6/ipv6.go @@ -411,7 +411,7 @@ func (e *endpoint) dupTentativeAddrDetected(addr tcpip.Address, holderLinkAddr t case ip.NonceNotEqual: // If the address is a SLAAC address, do not invalidate its SLAAC prefix as an // attempt will be made to generate a new address for it. - if err := e.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */, &stack.DADDupAddrDetected{HolderLinkAddress: holderLinkAddr}); err != nil { + if err := e.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */, stack.AddressRemovalDADFailed, &stack.DADDupAddrDetected{HolderLinkAddress: holderLinkAddr}); err != nil { return err } @@ -541,6 +541,40 @@ func (e *endpoint) Enable() tcpip.Error { return nil } + // Perform DAD on the all the unicast IPv6 endpoints that are in the permanent + // state. + // + // Addresses may have already completed DAD but in the time since the endpoint + // was last enabled, other devices may have acquired the same addresses. + var err tcpip.Error + e.mu.addressableEndpointState.ForEachEndpoint(func(addressEndpoint stack.AddressEndpoint) bool { + addr := addressEndpoint.AddressWithPrefix().Address + if !header.IsV6UnicastAddress(addr) { + return true + } + + switch kind := addressEndpoint.GetKind(); kind { + case stack.Permanent: + addressEndpoint.SetKind(stack.PermanentTentative) + fallthrough + case stack.PermanentTentative: + err = e.mu.ndp.startDuplicateAddressDetection(addr, addressEndpoint) + return err == nil + case stack.Temporary, stack.PermanentExpired: + return true + default: + panic(fmt.Sprintf("address %s has unknown kind %d", addressEndpoint.AddressWithPrefix(), kind)) + } + }) + // It is important to enable after starting DAD on all the addresses so that + // if DAD is disabled, the Tentative state is not observed. + // + // Must be called after Enabled has been set. + e.mu.addressableEndpointState.OnNetworkEndpointEnabledChanged() + if err != nil { + return err + } + // Groups may have been joined when the endpoint was disabled, or the // endpoint may have left groups from the perspective of MLD when the // endpoint was disabled. Either way, we need to let routers know to @@ -570,33 +604,6 @@ func (e *endpoint) Enable() tcpip.Error { panic(fmt.Sprintf("e.joinGroupLocked(%s): %s", header.IPv6AllNodesMulticastAddress, err)) } - // Perform DAD on the all the unicast IPv6 endpoints that are in the permanent - // state. - // - // Addresses may have already completed DAD but in the time since the endpoint - // was last enabled, other devices may have acquired the same addresses. - var err tcpip.Error - e.mu.addressableEndpointState.ForEachEndpoint(func(addressEndpoint stack.AddressEndpoint) bool { - addr := addressEndpoint.AddressWithPrefix().Address - if !header.IsV6UnicastAddress(addr) { - return true - } - - switch addressEndpoint.GetKind() { - case stack.Permanent: - addressEndpoint.SetKind(stack.PermanentTentative) - fallthrough - case stack.PermanentTentative: - err = e.mu.ndp.startDuplicateAddressDetection(addr, addressEndpoint) - return err == nil - default: - return true - } - }) - if err != nil { - return err - } - // Do not auto-generate an IPv6 link-local address for loopback devices. if e.protocol.options.AutoGenLinkLocal && !e.nic.IsLoopback() { // The valid and preferred lifetime is infinite for the auto-generated @@ -642,19 +649,6 @@ func (e *endpoint) disableLocked() { } e.mu.ndp.stopSolicitingRouters() - // Stop DAD for all the tentative unicast addresses. - e.mu.addressableEndpointState.ForEachEndpoint(func(addressEndpoint stack.AddressEndpoint) bool { - if addressEndpoint.GetKind() != stack.PermanentTentative { - return true - } - - addr := addressEndpoint.AddressWithPrefix().Address - if header.IsV6UnicastAddress(addr) { - e.mu.ndp.stopDuplicateAddressDetection(addr, &stack.DADAborted{}) - } - - return true - }) e.mu.ndp.cleanupState() // The endpoint may have already left the multicast group. @@ -668,9 +662,27 @@ func (e *endpoint) disableLocked() { // we are no longer interested in the group. e.mu.mld.softLeaveAll() + // Stop DAD for all the tentative unicast addresses. + e.mu.addressableEndpointState.ForEachEndpoint(func(addressEndpoint stack.AddressEndpoint) bool { + addrWithPrefix := addressEndpoint.AddressWithPrefix() + switch kind := addressEndpoint.GetKind(); kind { + case stack.Permanent, stack.PermanentTentative: + if header.IsV6UnicastAddress(addrWithPrefix.Address) { + e.mu.ndp.stopDuplicateAddressDetection(addrWithPrefix.Address, &stack.DADAborted{}) + } + case stack.Temporary, stack.PermanentExpired: + default: + panic(fmt.Sprintf("address %s has unknown address kind %d", addrWithPrefix, kind)) + } + return true + }) + if !e.setEnabled(false) { panic("should have only done work to disable the endpoint if it was enabled") } + + // Must be called after Enabled has been set. + e.mu.addressableEndpointState.OnNetworkEndpointEnabledChanged() } // DefaultTTL is the default hop limit for this endpoint. @@ -1763,7 +1775,16 @@ func (e *endpoint) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, p // an empty address. e.mu.Lock() defer e.mu.Unlock() - return e.addAndAcquirePermanentAddressLocked(addr, properties) + + // The dance of registering the dispatcher after adding the address makes it + // so that the tentative state is skipped if DAD is disabled. + addrDisp := properties.Disp + properties.Disp = nil + addressEndpoint, err := e.addAndAcquirePermanentAddressLocked(addr, properties) + if addrDisp != nil && err == nil { + addressEndpoint.RegisterDispatcher(addrDisp) + } + return addressEndpoint, err } // addAndAcquirePermanentAddressLocked is like AddAndAcquirePermanentAddress but @@ -1774,7 +1795,7 @@ func (e *endpoint) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, p // // Precondition: e.mu must be write locked. func (e *endpoint) addAndAcquirePermanentAddressLocked(addr tcpip.AddressWithPrefix, properties stack.AddressProperties) (stack.AddressEndpoint, tcpip.Error) { - addressEndpoint, err := e.mu.addressableEndpointState.AddAndAcquirePermanentAddress(addr, properties) + addressEndpoint, err := e.mu.addressableEndpointState.AddAndAcquireAddress(addr, properties, stack.PermanentTentative) if err != nil { return nil, err } @@ -1783,8 +1804,6 @@ func (e *endpoint) addAndAcquirePermanentAddressLocked(addr tcpip.AddressWithPre return addressEndpoint, nil } - addressEndpoint.SetKind(stack.PermanentTentative) - if e.Enabled() { if err := e.mu.ndp.startDuplicateAddressDetection(addr.Address, addressEndpoint); err != nil { return nil, err @@ -1811,14 +1830,14 @@ func (e *endpoint) RemovePermanentAddress(addr tcpip.Address) tcpip.Error { return &tcpip.ErrBadLocalAddress{} } - return e.removePermanentEndpointLocked(addressEndpoint, true /* allowSLAACInvalidation */, &stack.DADAborted{}) + return e.removePermanentEndpointLocked(addressEndpoint, true /* allowSLAACInvalidation */, stack.AddressRemovalManualAction, &stack.DADAborted{}) } // removePermanentEndpointLocked is like removePermanentAddressLocked except // it works with a stack.AddressEndpoint. // // Precondition: e.mu must be write locked. -func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEndpoint, allowSLAACInvalidation bool, dadResult stack.DADResult) tcpip.Error { +func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEndpoint, allowSLAACInvalidation bool, reason stack.AddressRemovalReason, dadResult stack.DADResult) tcpip.Error { addr := addressEndpoint.AddressWithPrefix() // If we are removing an address generated via SLAAC, cleanup // its SLAAC resources and notify the integrator. @@ -1830,18 +1849,18 @@ func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEn } } - return e.removePermanentEndpointInnerLocked(addressEndpoint, dadResult) + return e.removePermanentEndpointInnerLocked(addressEndpoint, reason, dadResult) } // removePermanentEndpointInnerLocked is like removePermanentEndpointLocked // except it does not cleanup SLAAC address state. // // Precondition: e.mu must be write locked. -func (e *endpoint) removePermanentEndpointInnerLocked(addressEndpoint stack.AddressEndpoint, dadResult stack.DADResult) tcpip.Error { +func (e *endpoint) removePermanentEndpointInnerLocked(addressEndpoint stack.AddressEndpoint, reason stack.AddressRemovalReason, dadResult stack.DADResult) tcpip.Error { addr := addressEndpoint.AddressWithPrefix() e.mu.ndp.stopDuplicateAddressDetection(addr.Address, dadResult) - if err := e.mu.addressableEndpointState.RemovePermanentEndpoint(addressEndpoint); err != nil { + if err := e.mu.addressableEndpointState.RemovePermanentEndpoint(addressEndpoint, reason); err != nil { return err } @@ -1880,6 +1899,13 @@ func (e *endpoint) SetDeprecated(addr tcpip.Address, deprecated bool) tcpip.Erro return e.mu.addressableEndpointState.SetDeprecated(addr, deprecated) } +// SetLifetimes implements stack.AddressableEndpoint. +func (e *endpoint) SetLifetimes(addr tcpip.Address, lifetimes stack.AddressLifetimes) tcpip.Error { + e.mu.RLock() + defer e.mu.RUnlock() + return e.mu.addressableEndpointState.SetLifetimes(addr, lifetimes) +} + // MainAddress implements stack.AddressableEndpoint. func (e *endpoint) MainAddress() tcpip.AddressWithPrefix { e.mu.RLock() @@ -2198,7 +2224,7 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.Tran } e.mu.Lock() - e.mu.addressableEndpointState.Init(e) + e.mu.addressableEndpointState.Init(e, stack.AddressableEndpointStateOptions{HiddenWhileDisabled: true}) e.mu.ndp.init(e, dadOptions) e.mu.mld.init(e) e.dad.mu.Lock() diff --git a/pkg/tcpip/network/ipv6/ndp.go b/pkg/tcpip/network/ipv6/ndp.go index 09cf3672a..2ca49b3e8 100644 --- a/pkg/tcpip/network/ipv6/ndp.go +++ b/pkg/tcpip/network/ipv6/ndp.go @@ -235,12 +235,15 @@ type NDPDispatcher interface { // // This function is not permitted to block indefinitely. It must not // call functions on the stack itself. - OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) + // + // If a non-nil AddressDispatcher is returned, events related to the address + // will be sent to the dispatcher. + OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) stack.AddressDispatcher // OnAutoGenAddressDeprecated is called when an auto-generated address (SLAAC) // is deprecated, but is still considered valid. Note, if an address is - // invalidated at the same ime it is deprecated, the deprecation event may not - // be received. + // invalidated at the same time it is deprecated, the deprecation event may + // not be received. // // This function is not permitted to block indefinitely. It must not // call functions on the stack itself. @@ -1089,6 +1092,12 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) { t := now.Add(pl) state.preferredUntil = &t } + // The time at which an address is invalidated is exposed as a property of the + // address. + if vl < header.NDPInfiniteLifetime { + t := now.Add(vl) + state.validUntil = &t + } if !ndp.generateSLAACAddr(prefix, &state) { // We were unable to generate an address for the prefix, we do not nothing @@ -1105,8 +1114,6 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) { if vl < header.NDPInfiniteLifetime { state.invalidationJob.Schedule(vl) - t := now.Add(vl) - state.validUntil = &t } // If the address is assigned (DAD resolved), generate a temporary address. @@ -1122,7 +1129,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) { // addAndAcquireSLAACAddr adds a SLAAC address to the IPv6 endpoint. // // The IPv6 endpoint that ndp belongs to MUST be locked. -func (ndp *ndpState) addAndAcquireSLAACAddr(addr tcpip.AddressWithPrefix, temporary bool, deprecated bool) stack.AddressEndpoint { +func (ndp *ndpState) addAndAcquireSLAACAddr(addr tcpip.AddressWithPrefix, temporary bool, lifetimes stack.AddressLifetimes) stack.AddressEndpoint { // Inform the integrator that we have a new SLAAC address. ndpDisp := ndp.ep.protocol.options.NDPDisp if ndpDisp == nil { @@ -1132,14 +1139,16 @@ func (ndp *ndpState) addAndAcquireSLAACAddr(addr tcpip.AddressWithPrefix, tempor addressEndpoint, err := ndp.ep.addAndAcquirePermanentAddressLocked(addr, stack.AddressProperties{ PEB: stack.FirstPrimaryEndpoint, ConfigType: stack.AddressConfigSlaac, - Deprecated: deprecated, + Lifetimes: lifetimes, Temporary: temporary, }) if err != nil { panic(fmt.Sprintf("ndp: error when adding SLAAC address %+v: %s", addr, err)) } - ndpDisp.OnAutoGenAddress(ndp.ep.nic.ID(), addr) + if disp := ndpDisp.OnAutoGenAddress(ndp.ep.nic.ID(), addr); disp != nil { + addressEndpoint.RegisterDispatcher(disp) + } return addressEndpoint } @@ -1217,7 +1226,23 @@ func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixSt } deprecated := state.preferredUntil != nil && !state.preferredUntil.After(ndp.ep.protocol.stack.Clock().NowMonotonic()) - if addressEndpoint := ndp.addAndAcquireSLAACAddr(generatedAddr, false /* temporary */, deprecated); addressEndpoint != nil { + var preferredUntil tcpip.MonotonicTime + if !deprecated { + if state.preferredUntil != nil { + preferredUntil = *state.preferredUntil + } else { + preferredUntil = tcpip.MonotonicTimeInfinite() + } + } + validUntil := tcpip.MonotonicTimeInfinite() + if state.validUntil != nil { + validUntil = *state.validUntil + } + if addressEndpoint := ndp.addAndAcquireSLAACAddr(generatedAddr, false /* temporary */, stack.AddressLifetimes{ + Deprecated: deprecated, + PreferredUntil: preferredUntil, + ValidUntil: validUntil, + }); addressEndpoint != nil { state.stableAddr.addressEndpoint = addressEndpoint state.generationAttempts++ return true @@ -1329,7 +1354,11 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla // As per RFC RFC 4941 section 3.3 step 5, we MUST NOT create a temporary // address with a zero preferred lifetime. The checks above ensure this // so we know the address is not deprecated. - addressEndpoint := ndp.addAndAcquireSLAACAddr(generatedAddr, true /* temporary */, false /* deprecated */) + addressEndpoint := ndp.addAndAcquireSLAACAddr(generatedAddr, true /* temporary */, stack.AddressLifetimes{ + Deprecated: false, + PreferredUntil: now.Add(pl), + ValidUntil: now.Add(vl), + }) if addressEndpoint == nil { return false } @@ -1417,14 +1446,6 @@ func (ndp *ndpState) regenerateTempSLAACAddr(prefix tcpip.Subnet, resetGenAttemp // // The IPv6 endpoint that ndp belongs to MUST be locked. func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixState *slaacPrefixState, pl, vl time.Duration) { - // If the preferred lifetime is zero, then the prefix should be deprecated. - deprecated := pl == 0 - if deprecated { - ndp.deprecateSLAACAddress(prefixState.stableAddr.addressEndpoint) - } else { - prefixState.stableAddr.addressEndpoint.SetDeprecated(false) - } - // If prefix was preferred for some finite lifetime before, cancel the // deprecation job so it can be reset. prefixState.deprecationJob.Cancel() @@ -1432,6 +1453,7 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat now := ndp.ep.protocol.stack.Clock().NowMonotonic() // Schedule the deprecation job if prefix has a finite preferred lifetime. + deprecated := pl == 0 if pl < header.NDPInfiniteLifetime { if !deprecated { prefixState.deprecationJob.Schedule(pl) @@ -1484,6 +1506,32 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat } } + // If the preferred lifetime is zero, then the prefix should be deprecated. + { + var preferredUntil tcpip.MonotonicTime + if !deprecated { + if prefixState.preferredUntil == nil { + preferredUntil = tcpip.MonotonicTimeInfinite() + } else { + preferredUntil = *prefixState.preferredUntil + } + } + validUntil := tcpip.MonotonicTimeInfinite() + if prefixState.validUntil != nil { + validUntil = *prefixState.validUntil + } + if addressEndpoint := prefixState.stableAddr.addressEndpoint; !addressEndpoint.Deprecated() && deprecated { + if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { + ndpDisp.OnAutoGenAddressDeprecated(ndp.ep.nic.ID(), addressEndpoint.AddressWithPrefix()) + } + } + prefixState.stableAddr.addressEndpoint.SetLifetimes(stack.AddressLifetimes{ + Deprecated: deprecated, + PreferredUntil: preferredUntil, + ValidUntil: validUntil, + }) + } + // If DAD is not yet complete on the stable address, there is no need to do // work with temporary addresses. if prefixState.stableAddr.addressEndpoint.GetKind() != stack.Permanent { @@ -1528,14 +1576,22 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat // Otherwise, schedule the deprecation job again. newPreferredLifetime := preferredUntil.Sub(now) tempAddrState.deprecationJob.Cancel() - - if newPreferredLifetime <= 0 { - ndp.deprecateSLAACAddress(tempAddrState.addressEndpoint) - } else { - tempAddrState.addressEndpoint.SetDeprecated(false) + deprecated := newPreferredLifetime <= 0 + if !deprecated { tempAddrState.deprecationJob.Schedule(newPreferredLifetime) } + if addressEndpoint := tempAddrState.addressEndpoint; !addressEndpoint.Deprecated() && deprecated { + if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { + ndpDisp.OnAutoGenAddressDeprecated(ndp.ep.nic.ID(), addressEndpoint.AddressWithPrefix()) + } + } + tempAddrState.addressEndpoint.SetLifetimes(stack.AddressLifetimes{ + Deprecated: deprecated, + ValidUntil: validUntil, + PreferredUntil: preferredUntil, + }) + tempAddrState.regenJob.Cancel() if tempAddrState.regenerated { } else { @@ -1597,7 +1653,7 @@ func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefi ndpDisp.OnAutoGenAddressInvalidated(ndp.ep.nic.ID(), addressEndpoint.AddressWithPrefix()) } - if err := ndp.ep.removePermanentEndpointInnerLocked(addressEndpoint, &stack.DADAborted{}); err != nil { + if err := ndp.ep.removePermanentEndpointInnerLocked(addressEndpoint, stack.AddressRemovalInvalidated, &stack.DADAborted{}); err != nil { panic(fmt.Sprintf("ndp: error removing stable SLAAC address %s: %s", addressEndpoint.AddressWithPrefix(), err)) } } @@ -1656,7 +1712,7 @@ func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaa func (ndp *ndpState) invalidateTempSLAACAddr(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) { ndp.cleanupTempSLAACAddrResourcesAndNotifyInner(tempAddrs, tempAddr, tempAddrState) - if err := ndp.ep.removePermanentEndpointInnerLocked(tempAddrState.addressEndpoint, &stack.DADAborted{}); err != nil { + if err := ndp.ep.removePermanentEndpointInnerLocked(tempAddrState.addressEndpoint, stack.AddressRemovalInvalidated, &stack.DADAborted{}); err != nil { panic(fmt.Sprintf("error removing temporary SLAAC address %s: %s", tempAddrState.addressEndpoint.AddressWithPrefix(), err)) } } diff --git a/pkg/tcpip/network/ipv6/ndp_test.go b/pkg/tcpip/network/ipv6/ndp_test.go index 93ad732ed..cf51155d7 100644 --- a/pkg/tcpip/network/ipv6/ndp_test.go +++ b/pkg/tcpip/network/ipv6/ndp_test.go @@ -57,7 +57,8 @@ func (*testNDPDispatcher) OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet) { func (*testNDPDispatcher) OnOnLinkPrefixInvalidated(tcpip.NICID, tcpip.Subnet) { } -func (*testNDPDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) { +func (*testNDPDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) stack.AddressDispatcher { + return nil } func (*testNDPDispatcher) OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) { diff --git a/pkg/tcpip/stack/addressable_endpoint_state.go b/pkg/tcpip/stack/addressable_endpoint_state.go index 0107bc074..df9953375 100644 --- a/pkg/tcpip/stack/addressable_endpoint_state.go +++ b/pkg/tcpip/stack/addressable_endpoint_state.go @@ -21,11 +21,18 @@ import ( "gvisor.dev/gvisor/pkg/tcpip" ) +func (lifetimes *AddressLifetimes) sanitize() { + if lifetimes.Deprecated { + lifetimes.PreferredUntil = tcpip.MonotonicTime{} + } +} + var _ AddressableEndpoint = (*AddressableEndpointState)(nil) // AddressableEndpointState is an implementation of an AddressableEndpoint. type AddressableEndpointState struct { networkEndpoint NetworkEndpoint + options AddressableEndpointStateOptions // Lock ordering (from outer to inner lock ordering): // @@ -38,17 +45,42 @@ type AddressableEndpointState struct { primary []*addressState } +// AddressableEndpointStateOptions contains options used to configure an +// AddressableEndpointState. +type AddressableEndpointStateOptions struct { + // HiddenWhileDisabled determines whether addresses should be returned to + // callers while the NetworkEndpoint this AddressableEndpointState belongs + // to is disabled. + HiddenWhileDisabled bool +} + // Init initializes the AddressableEndpointState with networkEndpoint. // // Must be called before calling any other function on m. -func (a *AddressableEndpointState) Init(networkEndpoint NetworkEndpoint) { +func (a *AddressableEndpointState) Init(networkEndpoint NetworkEndpoint, options AddressableEndpointStateOptions) { a.networkEndpoint = networkEndpoint + a.options = options a.mu.Lock() defer a.mu.Unlock() a.endpoints = make(map[tcpip.Address]*addressState) } +// OnNetworkEndpointEnabledChanged must be called every time the +// NetworkEndpoint this AddressableEndpointState belongs to is enabled or +// disabled so that any AddressDispatchers can be notified of the NIC enabled +// change. +func (a *AddressableEndpointState) OnNetworkEndpointEnabledChanged() { + a.mu.RLock() + defer a.mu.RUnlock() + + for _, ep := range a.endpoints { + ep.mu.Lock() + ep.notifyChangedLocked() + ep.mu.Unlock() + } +} + // GetAddress returns the AddressEndpoint for the passed address. // // GetAddress does not increment the address's reference count or check if the @@ -118,27 +150,7 @@ func (a *AddressableEndpointState) releaseAddressStateLocked(addrState *addressS // AddAndAcquirePermanentAddress implements AddressableEndpoint. func (a *AddressableEndpointState) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, properties AddressProperties) (AddressEndpoint, tcpip.Error) { - a.mu.Lock() - defer a.mu.Unlock() - ep, err := a.addAndAcquireAddressLocked(addr, properties, true /* permanent */) - // From https://golang.org/doc/faq#nil_error: - // - // Under the covers, interfaces are implemented as two elements, a type T and - // a value V. - // - // An interface value is nil only if the V and T are both unset, (T=nil, V is - // not set), In particular, a nil interface will always hold a nil type. If we - // store a nil pointer of type *int inside an interface value, the inner type - // will be *int regardless of the value of the pointer: (T=*int, V=nil). Such - // an interface value will therefore be non-nil even when the pointer value V - // inside is nil. - // - // Since addAndAcquireAddressLocked returns a nil value with a non-nil type, - // we need to explicitly return nil below if ep is (a typed) nil. - if ep == nil { - return nil, err - } - return ep, err + return a.AddAndAcquireAddress(addr, properties, Permanent) } // AddAndAcquireTemporaryAddress adds a temporary address. @@ -147,9 +159,16 @@ func (a *AddressableEndpointState) AddAndAcquirePermanentAddress(addr tcpip.Addr // // The temporary address's endpoint is acquired and returned. func (a *AddressableEndpointState) AddAndAcquireTemporaryAddress(addr tcpip.AddressWithPrefix, peb PrimaryEndpointBehavior) (AddressEndpoint, tcpip.Error) { + return a.AddAndAcquireAddress(addr, AddressProperties{PEB: peb}, Temporary) +} + +// AddAndAcquireAddress adds an address with the specified kind. +// +// Returns *tcpip.ErrDuplicateAddress if the address exists. +func (a *AddressableEndpointState) AddAndAcquireAddress(addr tcpip.AddressWithPrefix, properties AddressProperties, kind AddressKind) (AddressEndpoint, tcpip.Error) { a.mu.Lock() defer a.mu.Unlock() - ep, err := a.addAndAcquireAddressLocked(addr, AddressProperties{PEB: peb}, false /* permanent */) + ep, err := a.addAndAcquireAddressLocked(addr, properties, kind) // From https://golang.org/doc/faq#nil_error: // // Under the covers, interfaces are implemented as two elements, a type T and @@ -180,7 +199,17 @@ func (a *AddressableEndpointState) AddAndAcquireTemporaryAddress(addr tcpip.Addr // returned, regardless the kind of address that is being added. // // +checklocks:a.mu -func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.AddressWithPrefix, properties AddressProperties, permanent bool) (*addressState, tcpip.Error) { +func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.AddressWithPrefix, properties AddressProperties, kind AddressKind) (*addressState, tcpip.Error) { + var permanent bool + switch kind { + case PermanentExpired: + panic(fmt.Sprintf("cannot add address %s in PermanentExpired state", addr)) + case Permanent, PermanentTentative: + permanent = true + case Temporary: + default: + panic(fmt.Sprintf("unknown address kind: %d", kind)) + } // attemptAddToPrimary is false when the address is already in the primary // address list. attemptAddToPrimary := true @@ -241,6 +270,7 @@ func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.Address // We never promote an address to temporary - it can only be added as such. // If we are actually adding a permanent address, it is promoted below. addrState.kind = Temporary + addrState.disp = properties.Disp } // At this point we have an address we are either promoting from an expired or @@ -258,12 +288,14 @@ func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.Address // Primary addresses are biased by 1. addrState.refs++ - addrState.kind = Permanent + addrState.kind = kind } // Acquire the address before returning it. addrState.refs++ - addrState.deprecated = properties.Deprecated addrState.configType = properties.ConfigType + lifetimes := properties.Lifetimes + lifetimes.sanitize() + addrState.lifetimes = lifetimes if attemptAddToPrimary { switch properties.PEB { @@ -289,6 +321,7 @@ func (a *AddressableEndpointState) addAndAcquireAddressLocked(addr tcpip.Address } } + addrState.notifyChangedLocked() return addrState, nil } @@ -309,12 +342,12 @@ func (a *AddressableEndpointState) removePermanentAddressLocked(addr tcpip.Addre return &tcpip.ErrBadLocalAddress{} } - return a.removePermanentEndpointLocked(addrState) + return a.removePermanentEndpointLocked(addrState, AddressRemovalManualAction) } // RemovePermanentEndpoint removes the passed endpoint if it is associated with // a and permanent. -func (a *AddressableEndpointState) RemovePermanentEndpoint(ep AddressEndpoint) tcpip.Error { +func (a *AddressableEndpointState) RemovePermanentEndpoint(ep AddressEndpoint, reason AddressRemovalReason) tcpip.Error { addrState, ok := ep.(*addressState) if !ok || addrState.addressableEndpointState != a { return &tcpip.ErrInvalidEndpointState{} @@ -322,19 +355,19 @@ func (a *AddressableEndpointState) RemovePermanentEndpoint(ep AddressEndpoint) t a.mu.Lock() defer a.mu.Unlock() - return a.removePermanentEndpointLocked(addrState) + return a.removePermanentEndpointLocked(addrState, reason) } // removePermanentAddressLocked is like RemovePermanentAddress but with locking // requirements. // // +checklocks:a.mu -func (a *AddressableEndpointState) removePermanentEndpointLocked(addrState *addressState) tcpip.Error { +func (a *AddressableEndpointState) removePermanentEndpointLocked(addrState *addressState, reason AddressRemovalReason) tcpip.Error { if !addrState.GetKind().IsPermanent() { return &tcpip.ErrBadLocalAddress{} } - addrState.SetKind(PermanentExpired) + addrState.remove(reason) a.decAddressRefLocked(addrState) return nil } @@ -386,6 +419,19 @@ func (a *AddressableEndpointState) SetDeprecated(addr tcpip.Address, deprecated return nil } +// SetLifetimes implements stack.AddressableEndpoint. +func (a *AddressableEndpointState) SetLifetimes(addr tcpip.Address, lifetimes AddressLifetimes) tcpip.Error { + a.mu.RLock() + defer a.mu.RUnlock() + + addrState, ok := a.endpoints[addr] + if !ok { + return &tcpip.ErrBadLocalAddress{} + } + addrState.SetLifetimes(lifetimes) + return nil +} + // MainAddress implements AddressableEndpoint. func (a *AddressableEndpointState) MainAddress() tcpip.AddressWithPrefix { a.mu.RLock() @@ -394,7 +440,7 @@ func (a *AddressableEndpointState) MainAddress() tcpip.AddressWithPrefix { ep := a.acquirePrimaryAddressRLocked(func(ep *addressState) bool { switch kind := ep.GetKind(); kind { case Permanent: - return true + return a.networkEndpoint.Enabled() || !a.options.HiddenWhileDisabled case PermanentTentative, PermanentExpired, Temporary: return false default: @@ -519,7 +565,7 @@ func (a *AddressableEndpointState) AcquireAssignedAddressOrMatching(localAddr tc // Proceed to add a new temporary endpoint. addr := localAddr.WithPrefix() - ep, err := a.addAndAcquireAddressLocked(addr, AddressProperties{PEB: tempPEB}, false /* permanent */) + ep, err := a.addAndAcquireAddressLocked(addr, AddressProperties{PEB: tempPEB}, Temporary) if err != nil { // addAndAcquireAddressLocked only returns an error if the address is // already assigned but we just checked above if the address exists so we @@ -588,6 +634,9 @@ func (a *AddressableEndpointState) PrimaryAddresses() []tcpip.AddressWithPrefix defer a.mu.RUnlock() var addrs []tcpip.AddressWithPrefix + if a.options.HiddenWhileDisabled && !a.networkEndpoint.Enabled() { + return addrs + } for _, ep := range a.primary { switch kind := ep.GetKind(); kind { // Don't include tentative, expired or temporary endpoints @@ -631,7 +680,7 @@ func (a *AddressableEndpointState) Cleanup() { for _, ep := range a.endpoints { // removePermanentEndpointLocked returns *tcpip.ErrBadLocalAddress if ep is // not a permanent address. - switch err := a.removePermanentEndpointLocked(ep); err.(type) { + switch err := a.removePermanentEndpointLocked(ep, AddressRemovalInterfaceRemoved); err.(type) { case nil, *tcpip.ErrBadLocalAddress: default: panic(fmt.Sprintf("unexpected error from removePermanentEndpointLocked(%s): %s", ep.addr, err)) @@ -659,8 +708,19 @@ type addressState struct { kind AddressKind // checklocks:mu configType AddressConfigType + // lifetimes holds this address' lifetimes. + // + // Invariant: if lifetimes.deprecated is true, then lifetimes.PreferredUntil + // must be the zero value. Note that the converse does not need to be + // upheld! + // // checklocks:mu - deprecated bool + lifetimes AddressLifetimes + // The enclosing mutex must be write-locked before calling methods on the + // dispatcher. + // + // checklocks:mu + disp AddressDispatcher } // AddressWithPrefix implements AddressEndpoint. @@ -684,22 +744,45 @@ func (a *addressState) GetKind() AddressKind { func (a *addressState) SetKind(kind AddressKind) { a.mu.Lock() defer a.mu.Unlock() + + prevKind := a.kind a.kind = kind + if kind == PermanentExpired { + a.notifyRemovedLocked(AddressRemovalManualAction) + } else if prevKind != kind && a.addressableEndpointState.networkEndpoint.Enabled() { + a.notifyChangedLocked() + } +} + +// notifyRemovedLocked notifies integrators of address removal. +// +// +checklocks:a.mu +func (a *addressState) notifyRemovedLocked(reason AddressRemovalReason) { + if disp := a.disp; disp != nil { + a.disp.OnRemoved(reason) + a.disp = nil + } +} + +func (a *addressState) remove(reason AddressRemovalReason) { + a.mu.Lock() + defer a.mu.Unlock() + + a.kind = PermanentExpired + a.notifyRemovedLocked(reason) } // IsAssigned implements AddressEndpoint. func (a *addressState) IsAssigned(allowExpired bool) bool { - if !a.addressableEndpointState.networkEndpoint.Enabled() { - return false - } - - switch a.GetKind() { + switch kind := a.GetKind(); kind { case PermanentTentative: return false case PermanentExpired: return allowExpired - default: + case Permanent, Temporary: return true + default: + panic(fmt.Sprintf("address %s has unknown kind %d", a.AddressWithPrefix(), kind)) } } @@ -742,21 +825,91 @@ func (a *addressState) ConfigType() AddressConfigType { return a.configType } +// notifyChangedLocked notifies integrators of address property changes. +// +// +checklocks:a.mu +func (a *addressState) notifyChangedLocked() { + if a.disp == nil { + return + } + + state := AddressDisabled + if a.addressableEndpointState.networkEndpoint.Enabled() { + switch a.kind { + case Permanent: + state = AddressAssigned + case PermanentTentative: + state = AddressTentative + case Temporary, PermanentExpired: + return + default: + panic(fmt.Sprintf("unrecognized address kind = %d", a.kind)) + } + } + + a.disp.OnChanged(a.lifetimes, state) +} + // SetDeprecated implements AddressEndpoint. func (a *addressState) SetDeprecated(d bool) { a.mu.Lock() defer a.mu.Unlock() - a.deprecated = d + + var changed bool + if a.lifetimes.Deprecated != d { + a.lifetimes.Deprecated = d + changed = true + } + if d { + a.lifetimes.PreferredUntil = tcpip.MonotonicTime{} + } + if changed { + a.notifyChangedLocked() + } } // Deprecated implements AddressEndpoint. func (a *addressState) Deprecated() bool { a.mu.RLock() defer a.mu.RUnlock() - return a.deprecated + return a.lifetimes.Deprecated +} + +// SetLifetimes implements AddressEndpoint. +func (a *addressState) SetLifetimes(lifetimes AddressLifetimes) { + a.mu.Lock() + defer a.mu.Unlock() + + lifetimes.sanitize() + + var changed bool + if a.lifetimes != lifetimes { + changed = true + } + a.lifetimes = lifetimes + if changed { + a.notifyChangedLocked() + } +} + +// Lifetimes implements AddressEndpoint. +func (a *addressState) Lifetimes() AddressLifetimes { + a.mu.RLock() + defer a.mu.RUnlock() + return a.lifetimes } // Temporary implements AddressEndpoint. func (a *addressState) Temporary() bool { return a.temporary } + +// RegisterDispatcher implements AddressEndpoint. +func (a *addressState) RegisterDispatcher(disp AddressDispatcher) { + a.mu.Lock() + defer a.mu.Unlock() + if disp != nil { + a.disp = disp + a.notifyChangedLocked() + } +} diff --git a/pkg/tcpip/stack/addressable_endpoint_state_test.go b/pkg/tcpip/stack/addressable_endpoint_state_test.go index 371f354ae..55c4b0432 100644 --- a/pkg/tcpip/stack/addressable_endpoint_state_test.go +++ b/pkg/tcpip/stack/addressable_endpoint_state_test.go @@ -30,7 +30,7 @@ func TestAddressableEndpointStateCleanup(t *testing.T) { } var s stack.AddressableEndpointState - s.Init(&ep) + s.Init(&ep, stack.AddressableEndpointStateOptions{HiddenWhileDisabled: false}) addr := tcpip.AddressWithPrefix{ Address: "\x01", diff --git a/pkg/tcpip/stack/forwarding_test.go b/pkg/tcpip/stack/forwarding_test.go index 239f1f431..396a87304 100644 --- a/pkg/tcpip/stack/forwarding_test.go +++ b/pkg/tcpip/stack/forwarding_test.go @@ -195,7 +195,7 @@ func (f *fwdTestNetworkProtocol) NewEndpoint(nic NetworkInterface, dispatcher Tr proto: f, dispatcher: dispatcher, } - e.AddressableEndpointState.Init(e) + e.AddressableEndpointState.Init(e, AddressableEndpointStateOptions{HiddenWhileDisabled: false}) return e } diff --git a/pkg/tcpip/stack/ndp_test.go b/pkg/tcpip/stack/ndp_test.go index 5ec8150d9..52e011345 100644 --- a/pkg/tcpip/stack/ndp_test.go +++ b/pkg/tcpip/stack/ndp_test.go @@ -18,6 +18,7 @@ import ( "bytes" "encoding/binary" "fmt" + "math" "math/rand" "testing" "time" @@ -52,7 +53,8 @@ const ( linkAddr3 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x08") linkAddr4 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x09") - defaultPrefixLen = 128 + defaultPrefixLen = 128 + infiniteVLSeconds = math.MaxUint32 ) var ( @@ -118,11 +120,16 @@ type ndpPrefixEvent struct { discovered bool } +type ndpAutoGenAddrNewEvent struct { + nicID tcpip.NICID + addr tcpip.AddressWithPrefix + addrDisp *addressDispatcher +} + type ndpAutoGenAddrEventType int const ( - newAddr ndpAutoGenAddrEventType = iota - deprecatedAddr + deprecatedAddr ndpAutoGenAddrEventType = iota invalidatedAddr ) @@ -162,10 +169,14 @@ var _ ipv6.NDPDispatcher = (*ndpDispatcher)(nil) // ndpDispatcher implements NDPDispatcher so tests can know when various NDP // related events happen for test purposes. type ndpDispatcher struct { - dadC chan ndpDADEvent - offLinkRouteC chan ndpOffLinkRouteEvent - prefixC chan ndpPrefixEvent - autoGenAddrC chan ndpAutoGenAddrEvent + dadC chan ndpDADEvent + offLinkRouteC chan ndpOffLinkRouteEvent + prefixC chan ndpPrefixEvent + autoGenAddrC chan ndpAutoGenAddrEvent + autoGenAddrNewC chan ndpAutoGenAddrNewEvent + // autoGenInstallDisp controls whether address dispatchers are installed for + // new auto-generated addresses. + autoGenInstallDisp bool rdnssC chan ndpRDNSSEvent dnsslC chan ndpDNSSLEvent routeTable []tcpip.Route @@ -232,14 +243,27 @@ func (n *ndpDispatcher) OnOnLinkPrefixInvalidated(nicID tcpip.NICID, prefix tcpi } } -func (n *ndpDispatcher) OnAutoGenAddress(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) { - if c := n.autoGenAddrC; c != nil { - c <- ndpAutoGenAddrEvent{ +func (n *ndpDispatcher) OnAutoGenAddress(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) stack.AddressDispatcher { + if c := n.autoGenAddrNewC; c != nil { + e := ndpAutoGenAddrNewEvent{ nicID, addr, - newAddr, + nil, + } + if n.autoGenInstallDisp { + e.addrDisp = &addressDispatcher{ + changedCh: make(chan addressChangedEvent, 1), + removedCh: make(chan stack.AddressRemovalReason, 1), + nicid: nicID, + addr: addr, + } + } + c <- e + if n.autoGenInstallDisp { + return e.addrDisp } } + return nil } func (n *ndpDispatcher) OnAutoGenAddressDeprecated(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) { @@ -313,6 +337,31 @@ func checkDADEvent(e ndpDADEvent, nicID tcpip.NICID, addr tcpip.Address, res sta return cmp.Diff(ndpDADEvent{nicID: nicID, addr: addr, res: res}, e, cmp.AllowUnexported(e)) } +// addressLifetimes returns address lifetimes computed by adding pl and vl +// from the reference time. +// +// If pl is 0, the returned lifetimes will be deprecated and have a zero value +// for the PreferredUntil field. +// +// If vl is infinite, the returned lifetimes will contain a maximal ValidUntil +// value. +func addressLifetimes(received tcpip.MonotonicTime, pl, vl uint32) stack.AddressLifetimes { + var preferredUntil, validUntil tcpip.MonotonicTime + if pl > 0 { + preferredUntil = received.Add(time.Duration(pl) * time.Second) + } + if vl == math.MaxUint32 { + validUntil = tcpip.MonotonicTimeInfinite() + } else { + validUntil = received.Add(time.Duration(vl) * time.Second) + } + return stack.AddressLifetimes{ + Deprecated: pl == 0, + PreferredUntil: preferredUntil, + ValidUntil: validUntil, + } +} + // TestDADDisabled tests that an address successfully resolves immediately // when DAD is not enabled (the default for an empty stack.Options). func TestDADDisabled(t *testing.T) { @@ -338,8 +387,16 @@ func TestDADDisabled(t *testing.T) { Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix, } - if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, err) + addrDisp := &addressDispatcher{ + changedCh: make(chan addressChangedEvent, 1), + nicid: nicID, + addr: addrWithPrefix, + } + properties := stack.AddressProperties{ + Disp: addrDisp, + } + if err := s.AddProtocolAddress(nicID, protocolAddr, properties); err != nil { + t.Fatalf("AddProtocolAddress(%d, %+v, %#v) = %s", nicID, protocolAddr, properties, err) } // Should get the address immediately since we should not have performed @@ -352,6 +409,9 @@ func TestDADDisabled(t *testing.T) { default: t.Fatal("expected DAD event") } + if err := addrDisp.expectChanged(stack.AddressLifetimes{}, stack.AddressAssigned); err != nil { + t.Error(err) + } if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil { t.Fatal(err) } @@ -384,18 +444,30 @@ func TestDADResolveLoopback(t *testing.T) { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } - protocolAddr := tcpip.ProtocolAddress{ - Protocol: header.IPv6ProtocolNumber, - AddressWithPrefix: tcpip.AddressWithPrefix{ - Address: addr1, - PrefixLen: defaultPrefixLen, - }, + addrWithPrefix := tcpip.AddressWithPrefix{ + Address: addr1, + PrefixLen: defaultPrefixLen, } - if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, err) + addrDisp := &addressDispatcher{ + nicid: nicID, + addr: addrWithPrefix, + changedCh: make(chan addressChangedEvent, 1), + } + properties := stack.AddressProperties{ + Disp: addrDisp, + } + protocolAddr := tcpip.ProtocolAddress{ + Protocol: header.IPv6ProtocolNumber, + AddressWithPrefix: addrWithPrefix, + } + if err := s.AddProtocolAddress(nicID, protocolAddr, properties); err != nil { + t.Fatalf("AddProtocolAddress(%d, %+v, %#v) = %s", nicID, protocolAddr, properties, err) } // Address should not be considered bound to the NIC yet (DAD ongoing). + if err := addrDisp.expectChanged(stack.AddressLifetimes{}, stack.AddressTentative); err != nil { + t.Error(err) + } if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { t.Fatal(err) } @@ -423,6 +495,9 @@ func TestDADResolveLoopback(t *testing.T) { if diff := checkDADEvent(<-ndpDisp.dadC, nicID, addr1, &stack.DADSucceeded{}); diff != "" { t.Errorf("DAD event mismatch (-want +got):\n%s", diff) } + if err := addrDisp.expectStateChanged(stack.AddressAssigned); err != nil { + t.Error(err) + } } // TestDADResolve tests that an address successfully resolves after performing @@ -486,7 +561,6 @@ func TestDADResolve(t *testing.T) { ndpDisp := ndpDispatcher{ dadC: make(chan ndpDADEvent, 1), } - e := channelLinkWithHeaderLength{ Endpoint: channel.New(int(test.dupAddrDetectTransmits), 1280, linkAddr1), headerLength: test.linkHeaderLen, @@ -525,13 +599,21 @@ func TestDADResolve(t *testing.T) { Address: addr1, PrefixLen: defaultPrefixLen, } + addrDisp := &addressDispatcher{ + nicid: nicID, + addr: addrWithPrefix, + changedCh: make(chan addressChangedEvent, 1), + } protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix, } - if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { + if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{Disp: addrDisp}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, err) } + if err := addrDisp.expectChanged(stack.AddressLifetimes{}, stack.AddressTentative); err != nil { + t.Error(err) + } // Make sure the address does not resolve before the resolution time has // passed. @@ -575,6 +657,9 @@ func TestDADResolve(t *testing.T) { default: t.Fatalf("expected DAD event for %s on NIC(%d)", addr1, nicID) } + if err := addrDisp.expectStateChanged(stack.AddressAssigned); err != nil { + t.Error(err) + } if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil { t.Error(err) } @@ -753,12 +838,24 @@ func TestDADFail(t *testing.T) { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } + addrDisp := &addressDispatcher{ + changedCh: make(chan addressChangedEvent, 1), + removedCh: make(chan stack.AddressRemovalReason, 1), + nicid: nicID, + addr: addr1.WithPrefix(), + } + properties := stack.AddressProperties{ + Disp: addrDisp, + } protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr1.WithPrefix(), } - if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err) + if err := s.AddProtocolAddress(nicID, protocolAddr, properties); err != nil { + t.Fatalf("AddProtocolAddress(%d, %+v, %#v): %s", nicID, protocolAddr, properties, err) + } + if err := addrDisp.expectChanged(stack.AddressLifetimes{}, stack.AddressTentative); err != nil { + t.Fatal(err) } // Address should not be considered bound to the NIC yet @@ -789,6 +886,9 @@ func TestDADFail(t *testing.T) { // something is wrong. t.Fatal("timed out waiting for DAD failure") } + if err := addrDisp.expectRemoved(stack.AddressRemovalDADFailed); err != nil { + t.Fatal(err) + } if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { t.Fatal(err) } @@ -808,6 +908,7 @@ func TestDADStop(t *testing.T) { tests := []struct { name string stopFn func(t *testing.T, s *stack.Stack) + verifyFn func(t *testing.T, ad *addressDispatcher) skipFinalAddrCheck bool }{ // Tests to make sure that DAD stops when an address is removed. @@ -818,6 +919,11 @@ func TestDADStop(t *testing.T) { t.Fatalf("RemoveAddress(%d, %s): %s", nicID, addr1, err) } }, + verifyFn: func(t *testing.T, ad *addressDispatcher) { + if err := ad.expectRemoved(stack.AddressRemovalManualAction); err != nil { + t.Error(err) + } + }, }, // Tests to make sure that DAD stops when the NIC is disabled. @@ -828,6 +934,11 @@ func TestDADStop(t *testing.T) { t.Fatalf("DisableNIC(%d): %s", nicID, err) } }, + verifyFn: func(t *testing.T, ad *addressDispatcher) { + if err := ad.expectStateChanged(stack.AddressDisabled); err != nil { + t.Error(err) + } + }, }, // Tests to make sure that DAD stops when the NIC is removed. @@ -838,6 +949,11 @@ func TestDADStop(t *testing.T) { t.Fatalf("RemoveNIC(%d): %s", nicID, err) } }, + verifyFn: func(t *testing.T, ad *addressDispatcher) { + if err := ad.expectRemoved(stack.AddressRemovalInterfaceRemoved); err != nil { + t.Error(err) + } + }, // The NIC is removed so we can't check its addresses after calling // stopFn. skipFinalAddrCheck: true, @@ -868,12 +984,24 @@ func TestDADStop(t *testing.T) { t.Fatalf("CreateNIC(%d, _): %s", nicID, err) } + addrDisp := &addressDispatcher{ + nicid: nicID, + addr: addr1.WithPrefix(), + changedCh: make(chan addressChangedEvent, 1), + removedCh: make(chan stack.AddressRemovalReason, 1), + } + properties := stack.AddressProperties{ + Disp: addrDisp, + } protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr1.WithPrefix(), } - if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err) + if err := s.AddProtocolAddress(nicID, protocolAddr, properties); err != nil { + t.Fatalf("AddProtocolAddress(%d, %+v, %#v): %s", nicID, protocolAddr, properties, err) + } + if err := addrDisp.expectChanged(stack.AddressLifetimes{}, stack.AddressTentative); err != nil { + t.Fatal(err) } // Address should not be considered bound to the NIC yet (DAD ongoing). @@ -895,6 +1023,7 @@ func TestDADStop(t *testing.T) { // time + extra 1s buffer, something is wrong. t.Fatal("timed out waiting for DAD failure") } + test.verifyFn(t, addrDisp) if !test.skipFinalAddrCheck { if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { @@ -952,7 +1081,7 @@ func TestSetNDPConfigurations(t *testing.T) { Clock: clock, }) - expectDADEvent := func(nicID tcpip.NICID, addr tcpip.Address) { + expectDADSucceeded := func(nicID tcpip.NICID, addr tcpip.Address) { select { case e := <-ndpDisp.dadC: if diff := checkDADEvent(e, nicID, addr, &stack.DADSucceeded{}); diff != "" { @@ -1000,27 +1129,63 @@ func TestSetNDPConfigurations(t *testing.T) { Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix1, } - if err := s.AddProtocolAddress(nicID1, protocolAddr1, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID1, protocolAddr1, err) + addr1Disp := addressDispatcher{ + nicid: nicID1, + addr: addrWithPrefix1, + changedCh: make(chan addressChangedEvent, 1), + removedCh: make(chan stack.AddressRemovalReason, 1), + } + properties1 := stack.AddressProperties{ + Disp: &addr1Disp, + } + if err := s.AddProtocolAddress(nicID1, protocolAddr1, properties1); err != nil { + t.Fatalf("AddProtocolAddress(%d, %+v, %#v) = %s", nicID1, protocolAddr1, properties1, err) + } + if err := addr1Disp.expectChanged(stack.AddressLifetimes{}, stack.AddressTentative); err != nil { + t.Error(err) } addrWithPrefix2 := tcpip.AddressWithPrefix{Address: addr2, PrefixLen: defaultPrefixLen} protocolAddr2 := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix2, } - if err := s.AddProtocolAddress(nicID2, protocolAddr2, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID2, protocolAddr2, err) + addr2Disp := addressDispatcher{ + nicid: nicID2, + addr: addrWithPrefix2, + changedCh: make(chan addressChangedEvent, 1), + removedCh: make(chan stack.AddressRemovalReason, 1), + } + properties2 := stack.AddressProperties{ + Disp: &addr2Disp, + } + if err := s.AddProtocolAddress(nicID2, protocolAddr2, properties2); err != nil { + t.Fatalf("AddProtocolAddress(%d, %+v, %#v) = %s", nicID2, protocolAddr2, properties2, err) + } + expectDADSucceeded(nicID2, addr2) + if err := addr2Disp.expectChanged(stack.AddressLifetimes{}, stack.AddressAssigned); err != nil { + t.Error(err) } - expectDADEvent(nicID2, addr2) addrWithPrefix3 := tcpip.AddressWithPrefix{Address: addr3, PrefixLen: defaultPrefixLen} protocolAddr3 := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix3, } - if err := s.AddProtocolAddress(nicID3, protocolAddr3, stack.AddressProperties{}); err != nil { - t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID3, protocolAddr3, err) + addr3Disp := addressDispatcher{ + nicid: nicID3, + addr: addrWithPrefix3, + changedCh: make(chan addressChangedEvent, 1), + removedCh: make(chan stack.AddressRemovalReason, 1), + } + properties3 := stack.AddressProperties{ + Disp: &addr3Disp, + } + if err := s.AddProtocolAddress(nicID3, protocolAddr3, properties3); err != nil { + t.Fatalf("AddProtocolAddress(%d, %+v, %#v) = %s", nicID3, protocolAddr3, properties3, err) + } + expectDADSucceeded(nicID3, addr3) + if err := addr3Disp.expectChanged(stack.AddressLifetimes{}, stack.AddressAssigned); err != nil { + t.Error(err) } - expectDADEvent(nicID3, addr3) // Address should not be considered bound to NIC(1) yet // (DAD ongoing). @@ -1050,13 +1215,9 @@ func TestSetNDPConfigurations(t *testing.T) { // Wait for DAD to resolve. clock.Advance(delta) - select { - case e := <-ndpDisp.dadC: - if diff := checkDADEvent(e, nicID1, addr1, &stack.DADSucceeded{}); diff != "" { - t.Errorf("DAD event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for DAD resolution") + expectDADSucceeded(nicID1, addr1) + if err := addr1Disp.expectStateChanged(stack.AddressAssigned); err != nil { + t.Error(err) } if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, addrWithPrefix1); err != nil { t.Fatal(err) @@ -1887,12 +2048,54 @@ func containsV6Addr(list []tcpip.ProtocolAddress, item tcpip.AddressWithPrefix) // Check e to make sure that the event is for addr on nic with ID 1, and the // event type is set to eventType. func checkAutoGenAddrEvent(e ndpAutoGenAddrEvent, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) string { - return cmp.Diff(ndpAutoGenAddrEvent{nicID: 1, addr: addr, eventType: eventType}, e, cmp.AllowUnexported(e)) + return cmp.Diff( + ndpAutoGenAddrEvent{nicID: 1, addr: addr, eventType: eventType}, + e, + cmp.AllowUnexported(e), + ) } const minVLSeconds = uint32(ipv6.MinPrefixInformationValidLifetimeForUpdate / time.Second) const infiniteLifetimeSeconds = uint32(header.NDPInfiniteLifetime / time.Second) +func expectAutoGenAddrEvent(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { + t.Helper() + + select { + case e := <-ndpDisp.autoGenAddrC: + if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { + t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) + } + default: + t.Fatal("expected addr auto gen event") + } +} + +// expectAutoGenAddrNewEvent expects that a new auto-gen addr event is +// immediately available with addr. +// +// The return *addressDispatcher is non-nil iff ndpDisp.autoGenInstallDisp is +// true. +func expectAutoGenAddrNewEvent(ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix) (*addressDispatcher, error) { + select { + case e := <-ndpDisp.autoGenAddrNewC: + if diff := cmp.Diff( + ndpAutoGenAddrNewEvent{nicID: 1, addr: addr}, + e, + cmp.AllowUnexported(e), + cmp.FilterValues(func(*addressDispatcher, *addressDispatcher) bool { return true }, cmp.Ignore()), + ); diff != "" { + return nil, fmt.Errorf("new auto-gen addr event mismatch (-want +got):\n%s", diff) + } + if ndpDisp.autoGenInstallDisp != (e.addrDisp != nil) { + return nil, fmt.Errorf("install-disp=%t but addr-disp=%#v", ndpDisp.autoGenInstallDisp, e.addrDisp) + } + return e.addrDisp, nil + default: + return nil, fmt.Errorf("expected new auto-gen addr event") + } +} + // TestAutoGenAddr tests that an address is properly generated and invalidated // when configured to do so. func TestAutoGenAddr(t *testing.T) { @@ -1900,8 +2103,11 @@ func TestAutoGenAddr(t *testing.T) { prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1) testWithRAs(t, func(t *testing.T, handleRAs ipv6.HandleRAsConfiguration, forwarding bool) { + const autoGenAddrCount = 1 ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -1924,19 +2130,6 @@ func TestAutoGenAddr(t *testing.T) { t.Fatalf("CreateNIC(1) = %s", err) } - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - // Receive an RA with prefix1 in an NDP Prefix Information option (PI) // with zero valid lifetime. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 0, 0)) @@ -1948,8 +2141,17 @@ func TestAutoGenAddr(t *testing.T) { // Receive an RA with prefix1 in an NDP Prefix Information option (PI) // with non-zero lifetime. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) - expectAutoGenAddrEvent(addr1, newAddr) + var preferredLifetime1 uint32 + validLifetime1 := uint32(100) + received := clock.NowMonotonic() + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, validLifetime1, preferredLifetime1)) + addr1Disp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr1) + if err != nil { + t.Fatalf("error expecting prefix1 stable address generated event: %s", err) + } + if err := addr1Disp.expectChanged(addressLifetimes(received, preferredLifetime1, validLifetime1), stack.AddressAssigned); err != nil { + t.Error(err) + } if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) { t.Fatalf("Should have %s in the list of addresses", addr1) } @@ -1965,8 +2167,17 @@ func TestAutoGenAddr(t *testing.T) { // Receive an RA with prefix2 in a PI with a valid lifetime that exceeds // the minimum. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, minVLSeconds+1, 0)) - expectAutoGenAddrEvent(addr2, newAddr) + validLifetime2 := uint32(minVLSeconds + 1) + preferredLifetime2 := uint32(minVLSeconds + 1) + received = clock.NowMonotonic() + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, validLifetime2, preferredLifetime2)) + addr2Disp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr2) + if err != nil { + t.Fatalf("error expecting prefix2 stable address generated event: %s", err) + } + if err := addr2Disp.expectChanged(addressLifetimes(received, preferredLifetime2, validLifetime2), stack.AddressAssigned); err != nil { + t.Error(err) + } if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) { t.Fatalf("Should have %s in the list of addresses", addr1) } @@ -1975,7 +2186,7 @@ func TestAutoGenAddr(t *testing.T) { } // Refresh valid lifetime for addr of prefix1. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, validLifetime1, 0)) select { case <-ndpDisp.autoGenAddrC: t.Fatal("unexpectedly auto-generated an address when we already have an address for a prefix") @@ -1984,13 +2195,9 @@ func TestAutoGenAddr(t *testing.T) { // Wait for addr of prefix1 to be invalidated. clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate) - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for addr auto gen event") + expectAutoGenAddrEvent(t, &ndpDisp, addr1, invalidatedAddr) + if err := addr1Disp.expectRemoved(stack.AddressRemovalInvalidated); err != nil { + t.Fatal(err) } if containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) { t.Fatalf("Should not have %s in the list of addresses", addr1) @@ -2048,9 +2255,12 @@ func TestAutoGenTempAddr(t *testing.T) { return header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], stableAddr) } + const autoGenAddrCount = 2 ndpDisp := ndpDispatcher{ - dadC: make(chan ndpDADEvent, 2), - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), + dadC: make(chan ndpDADEvent, 2), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -2064,8 +2274,8 @@ func TestAutoGenTempAddr(t *testing.T) { HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, AutoGenTempGlobalAddresses: true, - MaxTempAddrValidLifetime: 2 * ipv6.MinPrefixInformationValidLifetimeForUpdate, - MaxTempAddrPreferredLifetime: 2 * ipv6.MinPrefixInformationValidLifetimeForUpdate, + MaxTempAddrValidLifetime: 3 * ipv6.MinPrefixInformationValidLifetimeForUpdate, + MaxTempAddrPreferredLifetime: 3 * ipv6.MinPrefixInformationValidLifetimeForUpdate, }, NDPDisp: &ndpDisp, TempIIDSeed: seed, @@ -2077,33 +2287,6 @@ func TestAutoGenTempAddr(t *testing.T) { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - - expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - clock.RunImmediatelyScheduledJobs() - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for addr auto gen event") - } - } - expectDADEventAsync := func(addr tcpip.Address) { t.Helper() @@ -2118,6 +2301,16 @@ func TestAutoGenTempAddr(t *testing.T) { } } + expectAddrDispatcherTentative := func(addrDisp *addressDispatcher, wantLifetimes stack.AddressLifetimes) { + t.Helper() + + if test.dupAddrTransmits != 0 { + if err := addrDisp.expectChanged(wantLifetimes, stack.AddressTentative); err != nil { + t.Error(err) + } + } + } + // Receive an RA with prefix1 in an NDP Prefix Information option (PI) // with zero valid lifetime. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 0, 0)) @@ -2129,9 +2322,19 @@ func TestAutoGenTempAddr(t *testing.T) { // Receive an RA with prefix1 in an NDP Prefix Information option (PI) // with non-zero valid lifetime. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) - expectAutoGenAddrEvent(addr1, newAddr) + prefix1VL := uint32(100) + var prefix1PL uint32 + received := clock.NowMonotonic() + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, prefix1VL, prefix1PL)) + addr1Disp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr1) + if err != nil { + t.Fatalf("error expecting prefix1 stable address generated event: %s", err) + } + expectAddrDispatcherTentative(addr1Disp, addressLifetimes(received, prefix1PL, prefix1VL)) expectDADEventAsync(addr1.Address) + if err := addr1Disp.expectChanged(addressLifetimes(received, prefix1PL, prefix1VL), stack.AddressAssigned); err != nil { + t.Error(err) + } select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpectedly got an auto gen addr event = %+v", e) @@ -2144,9 +2347,21 @@ func TestAutoGenTempAddr(t *testing.T) { // Receive an RA with prefix1 in an NDP Prefix Information option (PI) // with non-zero valid & preferred lifetimes. tempAddr1 := newTempAddr(addr1.Address) - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100)) - expectAutoGenAddrEvent(tempAddr1, newAddr) + prefix1PL = uint32(100) + received = clock.NowMonotonic() + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, prefix1VL, prefix1PL)) + if err := addr1Disp.expectLifetimesChanged(addressLifetimes(received, prefix1PL, prefix1VL)); err != nil { + t.Error(err) + } + tempAddr1Disp, err := expectAutoGenAddrNewEvent(&ndpDisp, tempAddr1) + if err != nil { + t.Fatalf("error expecting prefix1 temp address generated event: %s", err) + } + expectAddrDispatcherTentative(tempAddr1Disp, addressLifetimes(received, prefix1PL, prefix1VL)) expectDADEventAsync(tempAddr1.Address) + if err := tempAddr1Disp.expectChanged(addressLifetimes(received, prefix1PL, prefix1VL), stack.AddressAssigned); err != nil { + t.Error(err) + } if mismatch := addressCheck(s.NICInfo()[1].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1}, nil); mismatch != "" { t.Fatal(mismatch) } @@ -2166,33 +2381,79 @@ func TestAutoGenTempAddr(t *testing.T) { // Receive an RA with prefix2 in a PI with a valid lifetime that exceeds // the minimum and won't be reached in this test. tempAddr2 := newTempAddr(addr2.Address) - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 2*minVLSeconds, 2*minVLSeconds)) - expectAutoGenAddrEvent(addr2, newAddr) + lifetime2 := 2 * minVLSeconds + received2 := clock.NowMonotonic() + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, lifetime2, lifetime2)) + addr2Disp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr2) + if err != nil { + t.Fatalf("error expecting prefix2 stable address generated event: %s", err) + } + expectAddrDispatcherTentative(addr2Disp, addressLifetimes(received2, lifetime2, lifetime2)) expectDADEventAsync(addr2.Address) - expectAutoGenAddrEventAsync(tempAddr2, newAddr) + if err := addr2Disp.expectChanged(addressLifetimes(received2, lifetime2, lifetime2), stack.AddressAssigned); err != nil { + t.Error(err) + } + + clock.RunImmediatelyScheduledJobs() + tempAddr2Disp, err := expectAutoGenAddrNewEvent(&ndpDisp, tempAddr2) + if err != nil { + t.Fatalf("error expecting prefix2 temp address generated event: %s", err) + } + expectAddrDispatcherTentative(tempAddr2Disp, addressLifetimes(received2, lifetime2, lifetime2)) expectDADEventAsync(tempAddr2.Address) + if err := tempAddr2Disp.expectChanged(addressLifetimes(received2, lifetime2, lifetime2), stack.AddressAssigned); err != nil { + t.Error(err) + } if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { t.Fatal(mismatch) } // Deprecate prefix1. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) - expectAutoGenAddrEvent(addr1, deprecatedAddr) - expectAutoGenAddrEvent(tempAddr1, deprecatedAddr) - if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { - t.Fatal(mismatch) + { + prefix1VL := uint32(100) + received = clock.NowMonotonic() + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, prefix1VL, 0)) + expectAutoGenAddrEvent(t, &ndpDisp, addr1, deprecatedAddr) + if err := addr1Disp.expectLifetimesChanged(addressLifetimes(received, 0, prefix1VL)); err != nil { + t.Error(err) + } + expectAutoGenAddrEvent(t, &ndpDisp, tempAddr1, deprecatedAddr) + if err := tempAddr1Disp.expectLifetimesChanged(addressLifetimes(received, 0, prefix1VL)); err != nil { + t.Error(err) + } + if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { + t.Fatal(mismatch) + } } // Refresh lifetimes for prefix1. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100)) - if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { - t.Fatal(mismatch) + { + prefix1VL := uint32(100) + prefix1PL := uint32(100) + received := clock.NowMonotonic() + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, prefix1VL, prefix1PL)) + if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { + t.Fatal(mismatch) + } + if err := addr1Disp.expectLifetimesChanged(addressLifetimes(received, prefix1PL, prefix1VL)); err != nil { + t.Error(err) + } + if err := tempAddr1Disp.expectLifetimesChanged(addressLifetimes(received, prefix1PL, prefix1VL)); err != nil { + t.Error(err) + } } // Reduce valid lifetime and deprecate addresses of prefix1. + received = clock.NowMonotonic() e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, minVLSeconds, 0)) - expectAutoGenAddrEvent(addr1, deprecatedAddr) - expectAutoGenAddrEvent(tempAddr1, deprecatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, addr1, deprecatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, tempAddr1, deprecatedAddr) + if err := addr1Disp.expectLifetimesChanged(addressLifetimes(received, 0, minVLSeconds)); err != nil { + t.Error(err) + } + if err := tempAddr1Disp.expectLifetimesChanged(addressLifetimes(received, 0, minVLSeconds)); err != nil { + t.Error(err) + } if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { t.Fatal(mismatch) } @@ -2215,30 +2476,37 @@ func TestAutoGenTempAddr(t *testing.T) { nextAddr = addr1 } - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, nextAddr, invalidatedAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for addr auto gen event") - } + expectAutoGenAddrEvent(t, &ndpDisp, nextAddr, invalidatedAddr) default: t.Fatal("timed out waiting for addr auto gen event") } + if err := addr1Disp.expectRemoved(stack.AddressRemovalInvalidated); err != nil { + t.Error(err) + } + if err := tempAddr1Disp.expectRemoved(stack.AddressRemovalInvalidated); err != nil { + t.Error(err) + } if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr2, tempAddr2}, []tcpip.AddressWithPrefix{addr1, tempAddr1}); mismatch != "" { t.Fatal(mismatch) } // Receive an RA with prefix2 in a PI w/ 0 lifetimes. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 0, 0)) - expectAutoGenAddrEvent(addr2, deprecatedAddr) - expectAutoGenAddrEvent(tempAddr2, deprecatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, addr2, deprecatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, tempAddr2, deprecatedAddr) select { case e := <-ndpDisp.autoGenAddrC: t.Errorf("got unexpected auto gen addr event = %+v", e) default: } + // Addresses should be deprecated, but their valid-until should be untouched + // as their remaining valid lifetime is too low. + if err := addr2Disp.expectDeprecated(); err != nil { + t.Error(err) + } + if err := tempAddr2Disp.expectDeprecated(); err != nil { + t.Error(err) + } if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr2, tempAddr2}, []tcpip.AddressWithPrefix{addr1, tempAddr1}); mismatch != "" { t.Fatal(mismatch) } @@ -2268,9 +2536,12 @@ func TestNoAutoGenTempAddrForLinkLocal(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + const autoGenAddrCount = 1 ndpDisp := ndpDispatcher{ - dadC: make(chan ndpDADEvent, 1), - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + dadC: make(chan ndpDADEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -2279,6 +2550,10 @@ func TestNoAutoGenTempAddrForLinkLocal(t *testing.T) { NDPConfigs: ipv6.NDPConfigurations{ AutoGenTempGlobalAddresses: true, }, + DADConfigs: stack.DADConfigurations{ + DupAddrDetectTransmits: test.dupAddrTransmits, + RetransmitTimer: test.retransmitTimer, + }, NDPDisp: &ndpDisp, AutoGenLinkLocal: true, })}, @@ -2290,13 +2565,14 @@ func TestNoAutoGenTempAddrForLinkLocal(t *testing.T) { } // The stable link-local address should auto-generate and resolve DAD. - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, tcpip.AddressWithPrefix{Address: llAddr1, PrefixLen: header.IIDOffsetInIPv6Address * 8}, newAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) + addrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, tcpip.AddressWithPrefix{Address: llAddr1, PrefixLen: header.IIDOffsetInIPv6Address * 8}) + if err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } + if test.dupAddrTransmits > 0 { + if err := addrDisp.expectChanged(infiniteLifetimes(), stack.AddressTentative); err != nil { + t.Error(err) } - default: - t.Fatal("expected addr auto gen event") } clock.Advance(time.Duration(test.dupAddrTransmits) * test.retransmitTimer) select { @@ -2307,6 +2583,9 @@ func TestNoAutoGenTempAddrForLinkLocal(t *testing.T) { default: t.Fatal("timed out waiting for DAD event") } + if err := addrDisp.expectChanged(infiniteLifetimes(), stack.AddressAssigned); err != nil { + t.Error(err) + } // No new addresses should be generated. select { @@ -2333,9 +2612,12 @@ func TestNoAutoGenTempAddrWithoutStableAddr(t *testing.T) { header.InitialTempIID(tempIIDHistory[:], nil, nicID) tempAddr := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) + const autoGenAddrCount = 1 ndpDisp := ndpDispatcher{ - dadC: make(chan ndpDADEvent, 1), - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + dadC: make(chan ndpDADEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -2360,20 +2642,20 @@ func TestNoAutoGenTempAddrWithoutStableAddr(t *testing.T) { } // Receive an RA to trigger SLAAC for prefix. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") + received, pl, vl := clock.NowMonotonic(), uint32(100), uint32(100) + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, vl, pl)) + addrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr) + if err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } + if err := addrDisp.expectChanged(addressLifetimes(received, pl, vl), stack.AddressTentative); err != nil { + t.Error(err) } // DAD on the stable address for prefix has not yet completed. Receiving a new // RA that would refresh lifetimes should not generate a temporary SLAAC // address for the prefix. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, vl, pl)) select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpected auto gen addr event = %+v", e) @@ -2391,14 +2673,20 @@ func TestNoAutoGenTempAddrWithoutStableAddr(t *testing.T) { default: t.Fatal("timed out waiting for DAD event") } - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, tempAddr, newAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for addr auto gen event") + if err := addrDisp.expectStateChanged(stack.AddressAssigned); err != nil { + t.Error(err) } + tempAddrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, tempAddr) + if err != nil { + t.Fatalf("error expecting temp auto-gen address generated event: %s", err) + } + tempAddrDisp.disable() +} + +type tempAddrState struct { + addrWithPrefix tcpip.AddressWithPrefix + generated tcpip.MonotonicTime + disp *addressDispatcher } // TestAutoGenTempAddrRegen tests that temporary SLAAC addresses are @@ -2415,13 +2703,18 @@ func TestAutoGenTempAddrRegen(t *testing.T) { prefix, _, addr := prefixSubnetAddr(0, linkAddr1) var tempIIDHistory [header.IIDSize]byte header.InitialTempIID(tempIIDHistory[:], nil, nicID) - var tempAddrs [numTempAddrs]tcpip.AddressWithPrefix + var tempAddrs [numTempAddrs]tempAddrState for i := 0; i < len(tempAddrs); i++ { - tempAddrs[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) + tempAddrs[i] = tempAddrState{ + addrWithPrefix: header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address), + } } + const autoGenAddrCount = 2 ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, } e := channel.New(0, 1280, linkAddr1) ndpConfigs := ipv6.NDPConfigurations{ @@ -2449,59 +2742,93 @@ func TestAutoGenTempAddrRegen(t *testing.T) { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) { t.Helper() clock.Advance(timeout) - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for addr auto gen event") - } + expectAutoGenAddrEvent(t, &ndpDisp, addr, eventType) } tempDesyncFactor := time.Duration(randSource.lastInt63) % ipv6.MaxDesyncFactor effectiveMaxTempAddrPL := ipv6.MinPrefixInformationValidLifetimeForUpdate - tempDesyncFactor // The time since the last regeneration before a new temporary address is // generated. - tempAddrRegenenerationTime := effectiveMaxTempAddrPL - regenAdv + tempAddrRegenerationTime := effectiveMaxTempAddrPL - regenAdv // Receive an RA with prefix1 in an NDP Prefix Information option (PI) // with non-zero valid & preferred lifetimes. + received := clock.NowMonotonic() e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, minVLSeconds)) - expectAutoGenAddrEvent(addr, newAddr) - expectAutoGenAddrEvent(tempAddrs[0], newAddr) - if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0]}, nil); mismatch != "" { + addrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr) + if err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } + // Disable receiving events on the stable address since it's not of interest + // to this particular test. + addrDisp.disable() + tempAddrs[0].disp, err = expectAutoGenAddrNewEvent(&ndpDisp, tempAddrs[0].addrWithPrefix) + if err != nil { + t.Fatalf("error expecting temp auto-gen address generated event: %s", err) + } + tempAddrs[0].generated = clock.NowMonotonic() + // Since the max temporary address preferred lifetime is equal to the valid + // lifetime of the prefix, the temporary address generated is preferred + // until the max minus the desync factor. + if err := tempAddrs[0].disp.expectChanged(stack.AddressLifetimes{ + ValidUntil: received.Add(time.Duration(minVLSeconds) * time.Second), + PreferredUntil: received.Add(effectiveMaxTempAddrPL), + }, stack.AddressAssigned); err != nil { + t.Error(err) + } + if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0].addrWithPrefix}, nil); mismatch != "" { t.Fatal(mismatch) } // Wait for regeneration - expectAutoGenAddrEventAsync(tempAddrs[1], newAddr, tempAddrRegenenerationTime) + clock.Advance(tempAddrRegenerationTime) + tempAddrs[1].disp, err = expectAutoGenAddrNewEvent(&ndpDisp, tempAddrs[1].addrWithPrefix) + if err != nil { + t.Fatalf("error expecting new temp regenerated address event: %s", err) + } + tempAddrs[1].generated = clock.NowMonotonic() + // New temp address generated with lifetimes of the prefix. + if err := tempAddrs[1].disp.expectChanged(addressLifetimes(received, minVLSeconds, minVLSeconds), stack.AddressAssigned); err != nil { + t.Error(err) + } + received = clock.NowMonotonic() e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, minVLSeconds)) - if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0], tempAddrs[1]}, nil); mismatch != "" { + // The first temporary address only has valid lifetime refreshed. + if err := tempAddrs[0].disp.expectValidUntilChanged(received.Add(time.Duration(minVLSeconds) * time.Second)); err != nil { + t.Error(err) + } + if err := tempAddrs[1].disp.expectChanged(stack.AddressLifetimes{ + ValidUntil: received.Add(time.Duration(minVLSeconds) * time.Second), + PreferredUntil: received.Add(effectiveMaxTempAddrPL), + }, stack.AddressAssigned); err != nil { + t.Error(err) + } + if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0].addrWithPrefix, tempAddrs[1].addrWithPrefix}, nil); mismatch != "" { t.Fatal(mismatch) } - expectAutoGenAddrEventAsync(tempAddrs[0], deprecatedAddr, regenAdv) + expectAutoGenAddrEventAsync(tempAddrs[0].addrWithPrefix, deprecatedAddr, regenAdv) + if err := tempAddrs[0].disp.expectDeprecated(); err != nil { + t.Error(err) + } // Wait for regeneration - expectAutoGenAddrEventAsync(tempAddrs[2], newAddr, tempAddrRegenenerationTime-regenAdv) - expectAutoGenAddrEventAsync(tempAddrs[1], deprecatedAddr, regenAdv) + clock.Advance(tempAddrRegenerationTime - regenAdv) + tempAddrs[2].disp, err = expectAutoGenAddrNewEvent(&ndpDisp, tempAddrs[2].addrWithPrefix) + if err != nil { + t.Fatalf("error expecting new temp twice-regenerated address event: %s", err) + } + tempAddrs[2].generated = clock.NowMonotonic() + if err := tempAddrs[2].disp.expectChanged(addressLifetimes(received, minVLSeconds, minVLSeconds), stack.AddressAssigned); err != nil { + t.Error(err) + } + expectAutoGenAddrEventAsync(tempAddrs[1].addrWithPrefix, deprecatedAddr, regenAdv) + if err := tempAddrs[1].disp.expectDeprecated(); err != nil { + t.Error(err) + } // Stop generating temporary addresses ndpConfigs.AutoGenTempGlobalAddresses = false @@ -2513,8 +2840,28 @@ func TestAutoGenTempAddrRegen(t *testing.T) { } // Refresh lifetimes and wait for the last temporary address to be deprecated. + received = clock.NowMonotonic() e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, minVLSeconds)) - expectAutoGenAddrEventAsync(tempAddrs[2], deprecatedAddr, effectiveMaxTempAddrPL-regenAdv) + for i, tempAddrState := range tempAddrs { + if i == 2 { + if err := tempAddrState.disp.expectLifetimesChanged(stack.AddressLifetimes{ + ValidUntil: received.Add(time.Duration(minVLSeconds) * time.Second), + // The effective max preferred lifetime since address generation is used + // since it is less than the refreshed prefix preferred lifetime. + PreferredUntil: tempAddrState.generated.Add(effectiveMaxTempAddrPL), + }); err != nil { + t.Error(err) + } + } else { + if err := tempAddrState.disp.expectValidUntilChanged(received.Add(time.Duration(minVLSeconds) * time.Second)); err != nil { + t.Errorf("addr %d error: %s", i, err) + } + } + } + expectAutoGenAddrEventAsync(tempAddrs[2].addrWithPrefix, deprecatedAddr, effectiveMaxTempAddrPL-regenAdv) + if err := tempAddrs[2].disp.expectDeprecated(); err != nil { + t.Error(err) + } // Refresh lifetimes such that the prefix is valid and preferred forever. // @@ -2522,14 +2869,24 @@ func TestAutoGenTempAddrRegen(t *testing.T) { // are capped by the maximum valid and preferred lifetimes for temporary // addresses. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, infiniteLifetimeSeconds, infiniteLifetimeSeconds)) + for i, tempAddrState := range tempAddrs { + if err := tempAddrState.disp.expectValidUntilChanged(tempAddrState.generated.Add(maxTempAddrValidLifetime)); err != nil { + t.Errorf("addr %d error: %s", i, err) + } + } // Wait for all the temporary addresses to get invalidated. invalidateAfter := maxTempAddrValidLifetime - clock.NowMonotonic().Sub(tcpip.MonotonicTime{}) - for _, addr := range tempAddrs { - expectAutoGenAddrEventAsync(addr, invalidatedAddr, invalidateAfter) - invalidateAfter = tempAddrRegenenerationTime + var tempAddrWithPrefix [numTempAddrs]tcpip.AddressWithPrefix + for i, tempAddrState := range tempAddrs { + tempAddrWithPrefix[i] = tempAddrState.addrWithPrefix + expectAutoGenAddrEventAsync(tempAddrState.addrWithPrefix, invalidatedAddr, invalidateAfter) + invalidateAfter = tempAddrRegenerationTime + if err := tempAddrState.disp.expectRemoved(stack.AddressRemovalInvalidated); err != nil { + t.Errorf("addr %d error: %s", i, err) + } } - if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr}, tempAddrs[:]); mismatch != "" { + if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr}, tempAddrWithPrefix[:]); mismatch != "" { t.Fatal(mismatch) } } @@ -2549,13 +2906,18 @@ func TestAutoGenTempAddrRegenJobUpdates(t *testing.T) { prefix, _, addr := prefixSubnetAddr(0, linkAddr1) var tempIIDHistory [header.IIDSize]byte header.InitialTempIID(tempIIDHistory[:], nil, nicID) - var tempAddrs [numTempAddrs]tcpip.AddressWithPrefix + var tempAddrs [numTempAddrs]tempAddrState for i := 0; i < len(tempAddrs); i++ { - tempAddrs[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) + tempAddrs[i] = tempAddrState{ + addrWithPrefix: header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address), + } } + const autoGenAddrCount = 2 ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, } e := channel.New(0, 1280, linkAddr1) ndpConfigs := ipv6.NDPConfigurations{ @@ -2585,40 +2947,37 @@ func TestAutoGenTempAddrRegenJobUpdates(t *testing.T) { } tempDesyncFactor := time.Duration(randSource.lastInt63) % ipv6.MaxDesyncFactor - - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } + effectiveMaxTempAddrPL := maxTempAddrPreferredLifetime - tempDesyncFactor expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) { t.Helper() clock.Advance(timeout) - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for addr auto gen event") - } + expectAutoGenAddrEvent(t, &ndpDisp, addr, eventType) } // Receive an RA with prefix1 in an NDP Prefix Information option (PI) // with non-zero valid & preferred lifetimes. + received := clock.NowMonotonic() e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, maxTempAddrPreferredLifetimeSeconds, maxTempAddrPreferredLifetimeSeconds)) - expectAutoGenAddrEvent(addr, newAddr) - expectAutoGenAddrEvent(tempAddrs[0], newAddr) - if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0]}, nil); mismatch != "" { + addrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr) + if err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } + // Ignore events about the stable address, since that's not relevant to this test. + addrDisp.disable() + tempAddrs[0].disp, err = expectAutoGenAddrNewEvent(&ndpDisp, tempAddrs[0].addrWithPrefix) + if err != nil { + t.Fatalf("error expecting temp auto-gen address generated event: %s", err) + } + tempAddrs[0].generated = clock.NowMonotonic() + if err := tempAddrs[0].disp.expectChanged(stack.AddressLifetimes{ + ValidUntil: received.Add(maxTempAddrPreferredLifetime), + PreferredUntil: received.Add(effectiveMaxTempAddrPL), + }, stack.AddressAssigned); err != nil { + t.Error(err) + } + if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0].addrWithPrefix}, nil); mismatch != "" { t.Fatal(mismatch) } @@ -2627,15 +2986,17 @@ func TestAutoGenTempAddrRegenJobUpdates(t *testing.T) { // A new temporary address should be generated after the regeneration // time has passed since the prefix is deprecated. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, maxTempAddrPreferredLifetimeSeconds, 0)) - expectAutoGenAddrEvent(addr, deprecatedAddr) - expectAutoGenAddrEvent(tempAddrs[0], deprecatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, addr, deprecatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, tempAddrs[0].addrWithPrefix, deprecatedAddr) + if err := tempAddrs[0].disp.expectDeprecated(); err != nil { + t.Error(err) + } select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpected auto gen addr event = %#v", e) default: } - effectiveMaxTempAddrPL := maxTempAddrPreferredLifetime - tempDesyncFactor // The time since the last regeneration before a new temporary address is // generated. tempAddrRegenenerationTime := effectiveMaxTempAddrPL - regenAdv @@ -2654,10 +3015,31 @@ func TestAutoGenTempAddrRegenJobUpdates(t *testing.T) { // A new temporary address should immediately be generated since the // regeneration time has already passed since the last address was generated // - this regeneration does not depend on a job. + received = clock.NowMonotonic() e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, maxTempAddrPreferredLifetimeSeconds, maxTempAddrPreferredLifetimeSeconds)) - expectAutoGenAddrEvent(tempAddrs[1], newAddr) + if err := tempAddrs[0].disp.expectLifetimesChanged( + stack.AddressLifetimes{ + ValidUntil: received.Add(maxTempAddrPreferredLifetime), + PreferredUntil: tempAddrs[0].generated.Add(effectiveMaxTempAddrPL), + }); err != nil { + t.Error(err) + } + tempAddrs[1].disp, err = expectAutoGenAddrNewEvent(&ndpDisp, tempAddrs[1].addrWithPrefix) + if err != nil { + t.Fatalf("error expecting temp auto-gen address regenerated event: %s", err) + } + tempAddrs[1].generated = clock.NowMonotonic() + if err := tempAddrs[1].disp.expectChanged(stack.AddressLifetimes{ + ValidUntil: received.Add(maxTempAddrPreferredLifetime), + PreferredUntil: received.Add(effectiveMaxTempAddrPL), + }, stack.AddressAssigned); err != nil { + t.Error(err) + } // Wait for the first temporary address to be deprecated. - expectAutoGenAddrEventAsync(tempAddrs[0], deprecatedAddr, regenAdv) + expectAutoGenAddrEventAsync(tempAddrs[0].addrWithPrefix, deprecatedAddr, regenAdv) + if err := tempAddrs[0].disp.expectDeprecated(); err != nil { + t.Error(err) + } select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpected auto gen addr event = %s", e) @@ -2672,26 +3054,49 @@ func TestAutoGenTempAddrRegenJobUpdates(t *testing.T) { // the temporary addresses has increased, so it will take more time to // regenerate a new temporary address. Note, new addresses are only // regenerated after the preferred lifetime - the regenerate advance duration - // as paased. + // has passed. const largeLifetimeSeconds = minVLSeconds * 2 const largeLifetime = time.Duration(largeLifetimeSeconds) * time.Second ndpConfigs.MaxTempAddrValidLifetime = 2 * largeLifetime ndpConfigs.MaxTempAddrPreferredLifetime = largeLifetime - ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber) - if err != nil { - t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err) + ipv6Ep, tcpipErr := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber) + if tcpipErr != nil { + t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, tcpipErr) } ndpEP := ipv6Ep.(ipv6.NDPEndpoint) ndpEP.SetNDPConfigurations(ndpConfigs) + received = clock.NowMonotonic() e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, largeLifetimeSeconds, largeLifetimeSeconds)) + for i := 0; i <= 1; i++ { + if err := tempAddrs[i].disp.expectLifetimesChanged(stack.AddressLifetimes{ + ValidUntil: received.Add(largeLifetime), + PreferredUntil: tempAddrs[i].generated.Add(largeLifetime - tempDesyncFactor), + }); err != nil { + t.Errorf("addr %d dispatcher error: %s", i, err) + } + } timeSinceInitialTime := clock.NowMonotonic().Sub(initialTime) clock.Advance(largeLifetime - timeSinceInitialTime) - expectAutoGenAddrEvent(tempAddrs[0], deprecatedAddr) - // to offset the advement of time to test the first temporary address's + expectAutoGenAddrEvent(t, &ndpDisp, tempAddrs[0].addrWithPrefix, deprecatedAddr) + if err := tempAddrs[0].disp.expectDeprecated(); err != nil { + t.Error(err) + } + // to offset the advancement of time to test the first temporary address's // deprecation after the second was generated advLess := regenAdv - expectAutoGenAddrEventAsync(tempAddrs[2], newAddr, timeSinceInitialTime-advLess-(tempDesyncFactor+regenAdv)) - expectAutoGenAddrEventAsync(tempAddrs[1], deprecatedAddr, regenAdv) + clock.Advance(timeSinceInitialTime - advLess - (tempDesyncFactor + regenAdv)) + tempAddrs[2].disp, err = expectAutoGenAddrNewEvent(&ndpDisp, tempAddrs[2].addrWithPrefix) + if err != nil { + t.Fatalf("error expecting temp auto-gen address twice-regenerated event: %s", err) + } + tempAddrs[2].generated = clock.NowMonotonic() + if err := tempAddrs[2].disp.expectChanged(addressLifetimes(received, largeLifetimeSeconds, largeLifetimeSeconds), stack.AddressAssigned); err != nil { + t.Error(err) + } + expectAutoGenAddrEventAsync(tempAddrs[1].addrWithPrefix, deprecatedAddr, regenAdv) + if err := tempAddrs[1].disp.expectDeprecated(); err != nil { + t.Error(err) + } select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpected auto gen addr event = %+v", e) @@ -2779,6 +3184,7 @@ func TestMixedSLAACAddrConflictRegen(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { ndpDisp := ndpDispatcher{ + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, test.maxAddrs), // We may receive a deprecated and invalidated event for each SLAAC // address that is assigned. autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAddrs*2), @@ -2834,24 +3240,20 @@ func TestMixedSLAACAddrConflictRegen(t *testing.T) { manuallyAssignedAddresses[test.addrs[j].Address] = struct{}{} } - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { + expectAutoGenAddrNewEventAsync := func(addr tcpip.AddressWithPrefix) { t.Helper() - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") + e := <-ndpDisp.autoGenAddrNewC + if diff := cmp.Diff( + ndpAutoGenAddrNewEvent{nicID: 1, addr: addr}, + e, + cmp.AllowUnexported(e), + cmp.FilterValues(func(*addressDispatcher, *addressDispatcher) bool { return true }, cmp.Ignore()), + ); diff != "" { + t.Errorf("auto-gen new addr event mismatch (-want +got):\n%s", diff) } - } - - expectAutoGenAddrAsyncEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - if diff := checkAutoGenAddrEvent(<-ndpDisp.autoGenAddrC, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) + if e.addrDisp != nil { + t.Error("auto-gen new addr event unexpectedly contains address dispatcher") } } @@ -2879,14 +3281,16 @@ func TestMixedSLAACAddrConflictRegen(t *testing.T) { // Do SLAAC for prefix. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds)) if test.initialExpect != (tcpip.AddressWithPrefix{}) { - expectAutoGenAddrEvent(test.initialExpect, newAddr) + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, test.initialExpect); err != nil { + t.Fatalf("error expecting auto-gen address generated event: %s", err) + } expectDADEventAsync(test.initialExpect.Address) } // The last local generation attempt should succeed, but we introduce a // DAD failure to restart the local generation process. addr := test.addrs[maxSLAACAddrLocalRegenAttempts-1] - expectAutoGenAddrAsyncEvent(addr, newAddr) + expectAutoGenAddrNewEventAsync(addr) rxNDPSolicit(e, addr.Address) select { case e := <-ndpDisp.dadC: @@ -2896,11 +3300,11 @@ func TestMixedSLAACAddrConflictRegen(t *testing.T) { default: t.Fatal("expected DAD event") } - expectAutoGenAddrEvent(addr, invalidatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr) // The last address generated should resolve DAD. addr = test.addrs[len(test.addrs)-1] - expectAutoGenAddrAsyncEvent(addr, newAddr) + expectAutoGenAddrNewEventAsync(addr) expectDADEventAsync(addr.Address) select { @@ -2930,8 +3334,10 @@ func TestMixedSLAACAddrConflictRegen(t *testing.T) { // router. func stackAndNdpDispatcherWithDefaultRoute(t *testing.T, nicID tcpip.NICID) (*ndpDispatcher, *channel.Endpoint, *stack.Stack, *faketime.ManualClock) { t.Helper() + const autoGenAddrCount = 1 ndpDisp := &ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), } e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired @@ -3035,19 +3441,6 @@ func TestAutoGenAddrDeprecateFromPI(t *testing.T) { ndpDisp, e, s, _ := stackAndNdpDispatcherWithDefaultRoute(t, nicID) - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) { t.Helper() @@ -3062,7 +3455,9 @@ func TestAutoGenAddrDeprecateFromPI(t *testing.T) { // Receive PI for prefix1. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100)) - expectAutoGenAddrEvent(addr1, newAddr) + if _, err := expectAutoGenAddrNewEvent(ndpDisp, addr1); err != nil { + t.Fatalf("error expecting prefix1 stable auto-gen address generated event: %s", err) + } if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { t.Fatalf("should have %s in the list of addresses", addr1) } @@ -3070,7 +3465,7 @@ func TestAutoGenAddrDeprecateFromPI(t *testing.T) { // Deprecate addr for prefix1 immedaitely. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) - expectAutoGenAddrEvent(addr1, deprecatedAddr) + expectAutoGenAddrEvent(t, ndpDisp, addr1, deprecatedAddr) if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { t.Fatalf("should have %s in the list of addresses", addr1) } @@ -3088,7 +3483,9 @@ func TestAutoGenAddrDeprecateFromPI(t *testing.T) { // Receive PI for prefix2. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100)) - expectAutoGenAddrEvent(addr2, newAddr) + if _, err := expectAutoGenAddrNewEvent(ndpDisp, addr2); err != nil { + t.Fatalf("error expecting prefix2 stable auto-gen address generated event: %s", err) + } if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { t.Fatalf("should have %s in the list of addresses", addr2) } @@ -3096,7 +3493,7 @@ func TestAutoGenAddrDeprecateFromPI(t *testing.T) { // Deprecate addr for prefix2 immedaitely. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0)) - expectAutoGenAddrEvent(addr2, deprecatedAddr) + expectAutoGenAddrEvent(t, ndpDisp, addr2, deprecatedAddr) if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { t.Fatalf("should have %s in the list of addresses", addr2) } @@ -3142,31 +3539,11 @@ func TestAutoGenAddrJobDeprecation(t *testing.T) { ndpDisp, e, s, clock := stackAndNdpDispatcherWithDefaultRoute(t, nicID) - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - expectAutoGenAddrEventAfter := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) { t.Helper() clock.Advance(timeout) - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for addr auto gen event") - } + expectAutoGenAddrEvent(t, ndpDisp, addr, eventType) } expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) { @@ -3183,7 +3560,9 @@ func TestAutoGenAddrJobDeprecation(t *testing.T) { // Receive PI for prefix2. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, infiniteLifetimeSeconds, infiniteLifetimeSeconds)) - expectAutoGenAddrEvent(addr2, newAddr) + if _, err := expectAutoGenAddrNewEvent(ndpDisp, addr2); err != nil { + t.Fatalf("error expecting prefix2 stable auto-gen address generated event: %s", err) + } if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { t.Fatalf("should have %s in the list of addresses", addr2) } @@ -3191,7 +3570,9 @@ func TestAutoGenAddrJobDeprecation(t *testing.T) { // Receive a PI for prefix1. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 90)) - expectAutoGenAddrEvent(addr1, newAddr) + if _, err := expectAutoGenAddrNewEvent(ndpDisp, addr1); err != nil { + t.Fatalf("error expecting prefix1 stable auto-gen address generated event: %s", err) + } if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { t.Fatalf("should have %s in the list of addresses", addr1) } @@ -3358,81 +3739,59 @@ func TestAutoGenAddrJobDeprecation(t *testing.T) { // Tests transitioning a SLAAC address's valid lifetime between finite and // infinite values. func TestAutoGenAddrFiniteToInfiniteToFiniteVL(t *testing.T) { - const infiniteVLSeconds = 2 + const infiniteVLSeconds = math.MaxUint32 prefix, _, addr := prefixSubnetAddr(0, linkAddr1) - tests := []struct { - name string - infiniteVL uint32 - }{ - { - name: "EqualToInfiniteVL", - infiniteVL: infiniteVLSeconds, - }, - // Our implementation supports changing header.NDPInfiniteLifetime for tests - // such that a packet can be received where the lifetime field has a value - // greater than header.NDPInfiniteLifetime. Because of this, we test to make - // sure that receiving a value greater than header.NDPInfiniteLifetime is - // handled the same as when receiving a value equal to - // header.NDPInfiniteLifetime. - { - name: "MoreThanInfiniteVL", - infiniteVL: infiniteVLSeconds + 1, - }, + const autoGenAddrCount = 1 + ndpDisp := ndpDispatcher{ + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, + } + e := channel.New(0, 1280, linkAddr1) + clock := faketime.NewManualClock() + s := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ + NDPConfigs: ipv6.NDPConfigurations{ + HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, + AutoGenGlobalAddresses: true, + }, + NDPDisp: &ndpDisp, + })}, + Clock: clock, + }) + + if err := s.CreateNIC(1, e); err != nil { + t.Fatalf("CreateNIC(1) = %s", err) } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), - } - e := channel.New(0, 1280, linkAddr1) - clock := faketime.NewManualClock() - s := stack.New(stack.Options{ - NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ - NDPConfigs: ipv6.NDPConfigurations{ - HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, - AutoGenGlobalAddresses: true, - }, - NDPDisp: &ndpDisp, - })}, - Clock: clock, - }) + // Receive an RA with finite prefix. + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0)) + addrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr) + if err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } + if err := addrDisp.expectChanged(addressLifetimes(clock.NowMonotonic(), 0, minVLSeconds), stack.AddressAssigned); err != nil { + t.Error(err) + } - if err := s.CreateNIC(1, e); err != nil { - t.Fatalf("CreateNIC(1) = %s", err) - } + // Receive an new RA with prefix with infinite VL. + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, infiniteVLSeconds, 0)) + if err := addrDisp.expectLifetimesChanged(addressLifetimes(clock.NowMonotonic(), 0, infiniteVLSeconds)); err != nil { + t.Error(err) + } - // Receive an RA with finite prefix. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0)) - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } + // Receive a new RA with prefix with finite VL. + e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0)) + if err := addrDisp.expectLifetimesChanged(addressLifetimes(clock.NowMonotonic(), 0, minVLSeconds)); err != nil { + t.Error(err) + } - default: - t.Fatal("expected addr auto gen event") - } - - // Receive an new RA with prefix with infinite VL. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.infiniteVL, 0)) - - // Receive a new RA with prefix with finite VL. - e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0)) - - clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate) - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - - default: - t.Fatal("timeout waiting for addr auto gen event") - } - }) + clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate) + expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr) + if err := addrDisp.expectRemoved(stack.AddressRemovalInvalidated); err != nil { + t.Error(err) } } @@ -3440,8 +3799,6 @@ func TestAutoGenAddrFiniteToInfiniteToFiniteVL(t *testing.T) { // auto-generated address only gets updated when required to, as specified in // RFC 4862 section 5.5.3.e. func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) { - const infiniteVL = 4294967295 - prefix, _, addr := prefixSubnetAddr(0, linkAddr1) tests := []struct { @@ -3467,13 +3824,13 @@ func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) { }, { "InfiniteVLToVLLessThanMinVLForUpdate", - infiniteVL, + infiniteVLSeconds, 1, minVLSeconds, }, { "InfiniteVLTo0", - infiniteVL, + infiniteVLSeconds, 0, minVLSeconds, }, @@ -3511,8 +3868,11 @@ func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + const autoGenAddrCount = 10 ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 10), + autoGenInstallDisp: true, + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), } e := channel.New(10, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -3534,18 +3894,22 @@ func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) { // Receive an RA with prefix with initial VL, // test.ovl. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.ovl, 0)) - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") + addrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr) + if err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } + if err := addrDisp.expectChanged(addressLifetimes(clock.NowMonotonic(), 0, test.ovl), stack.AddressAssigned); err != nil { + t.Error(err) } // Receive an new RA with prefix with new VL, // test.nvl. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.nvl, 0)) + if test.evl != test.ovl { + if err := addrDisp.expectValidUntilChanged(clock.NowMonotonic().Add(time.Duration(test.evl) * time.Second)); err != nil { + t.Error(err) + } + } // // Validate that the VL for the address got set @@ -3561,6 +3925,9 @@ func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) { t.Fatal("unexpectedly received an auto gen addr event") default: } + if err := addrDisp.expectNoEvent(); err != nil { + t.Error(err) + } // Wait for the invalidation event. clock.Advance(delta) @@ -3572,6 +3939,9 @@ func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) { default: t.Fatal("timeout waiting for addr auto gen event") } + if err := addrDisp.expectRemoved(stack.AddressRemovalInvalidated); err != nil { + t.Error(err) + } }) } } @@ -3582,8 +3952,11 @@ func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) { func TestAutoGenAddrRemoval(t *testing.T) { prefix, _, addr := prefixSubnetAddr(0, linkAddr1) + const autoGenAddrCount = 1 ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -3602,30 +3975,26 @@ func TestAutoGenAddrRemoval(t *testing.T) { t.Fatalf("CreateNIC(1) = %s", err) } - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - // Receive a PI to auto-generate an address. const lifetimeSeconds = 1 e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, 0)) - expectAutoGenAddrEvent(addr, newAddr) + addrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr) + if err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } + if err := addrDisp.expectChanged(addressLifetimes(clock.NowMonotonic(), 0, lifetimeSeconds), stack.AddressAssigned); err != nil { + t.Error(err) + } // Removing the address should result in an invalidation event // immediately. if err := s.RemoveAddress(1, addr.Address); err != nil { t.Fatalf("RemoveAddress(_, %s) = %s", addr.Address, err) } - expectAutoGenAddrEvent(addr, invalidatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr) + if err := addrDisp.expectRemoved(stack.AddressRemovalManualAction); err != nil { + t.Error(err) + } // Wait for the original valid lifetime to make sure the original job got // cancelled/cleaned up. @@ -3635,6 +4004,9 @@ func TestAutoGenAddrRemoval(t *testing.T) { t.Fatal("unexpectedly received an auto gen addr event") default: } + if err := addrDisp.expectNoEvent(); err != nil { + t.Error(err) + } } // TestAutoGenAddrAfterRemoval tests adding a SLAAC address that was previously @@ -3644,20 +4016,8 @@ func TestAutoGenAddrAfterRemoval(t *testing.T) { prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1) prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1) - ndpDisp, e, s, _ := stackAndNdpDispatcherWithDefaultRoute(t, nicID) - - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } + ndpDisp, e, s, clock := stackAndNdpDispatcherWithDefaultRoute(t, nicID) + ndpDisp.autoGenInstallDisp = true expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) { t.Helper() @@ -3675,7 +4035,11 @@ func TestAutoGenAddrAfterRemoval(t *testing.T) { // lifetime. const largeLifetimeSeconds = 999 e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix1, true, true, largeLifetimeSeconds, largeLifetimeSeconds)) - expectAutoGenAddrEvent(addr1, newAddr) + if addrDisp, err := expectAutoGenAddrNewEvent(ndpDisp, addr1); err != nil { + t.Fatalf("error expecting prefix1 stable auto-gen address generated event: %s", err) + } else { + addrDisp.disable() + } expectPrimaryAddr(addr1) // Add addr2 as a static address. @@ -3693,11 +4057,11 @@ func TestAutoGenAddrAfterRemoval(t *testing.T) { // Get a route using addr2 to increment its reference count then remove it // to leave it in the permanentExpired state. - r, err := s.FindRoute(nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, false) - if err != nil { + if r, err := s.FindRoute(nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, false); err != nil { t.Fatalf("FindRoute(%d, %s, %s, %d, false): %s", nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, err) + } else { + defer r.Release() } - defer r.Release() if err := s.RemoveAddress(nicID, addr2.Address); err != nil { t.Fatalf("s.RemoveAddress(%d, %s): %s", nicID, addr2.Address, err) } @@ -3706,7 +4070,13 @@ func TestAutoGenAddrAfterRemoval(t *testing.T) { // Receive a PI to auto-generate addr2 as valid and preferred. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, largeLifetimeSeconds)) - expectAutoGenAddrEvent(addr2, newAddr) + addr2Disp, err := expectAutoGenAddrNewEvent(ndpDisp, addr2) + if err != nil { + t.Fatalf("error expecting prefix2 stable auto-gen address generated event: %s", err) + } + if err := addr2Disp.expectChanged(addressLifetimes(clock.NowMonotonic(), largeLifetimeSeconds, largeLifetimeSeconds), stack.AddressAssigned); err != nil { + t.Error(err) + } // addr2 should be more preferred now that it is closer to the front of the // primary list and not deprecated. expectPrimaryAddr(addr2) @@ -3719,13 +4089,22 @@ func TestAutoGenAddrAfterRemoval(t *testing.T) { if err := s.RemoveAddress(1, addr2.Address); err != nil { t.Fatalf("RemoveAddress(_, %s) = %s", addr2.Address, err) } - expectAutoGenAddrEvent(addr2, invalidatedAddr) + expectAutoGenAddrEvent(t, ndpDisp, addr2, invalidatedAddr) + if err := addr2Disp.expectRemoved(stack.AddressRemovalManualAction); err != nil { + t.Error(err) + } // addr1 should be more preferred since addr2 is in the expired state. expectPrimaryAddr(addr1) // Receive a PI to auto-generate addr2 as valid and deprecated. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, 0)) - expectAutoGenAddrEvent(addr2, newAddr) + addr2Disp, err = expectAutoGenAddrNewEvent(ndpDisp, addr2) + if err != nil { + t.Fatalf("error expecting prefix2 stable auto-gen address generated event after removing address and new PI: %s", err) + } + if err := addr2Disp.expectChanged(addressLifetimes(clock.NowMonotonic(), 0, largeLifetimeSeconds), stack.AddressAssigned); err != nil { + t.Error(err) + } // addr1 should still be more preferred since addr2 is deprecated, even though // it is closer to the front of the primary list. expectPrimaryAddr(addr1) @@ -3737,13 +4116,19 @@ func TestAutoGenAddrAfterRemoval(t *testing.T) { t.Fatal("unexpectedly got an auto gen addr event") default: } + if err := addr2Disp.expectChanged(addressLifetimes(clock.NowMonotonic(), largeLifetimeSeconds, largeLifetimeSeconds), stack.AddressAssigned); err != nil { + t.Error(err) + } // addr2 should be more preferred now that it is not deprecated. expectPrimaryAddr(addr2) if err := s.RemoveAddress(1, addr2.Address); err != nil { t.Fatalf("RemoveAddress(_, %s) = %s", addr2.Address, err) } - expectAutoGenAddrEvent(addr2, invalidatedAddr) + expectAutoGenAddrEvent(t, ndpDisp, addr2, invalidatedAddr) + if err := addr2Disp.expectRemoved(stack.AddressRemovalManualAction); err != nil { + t.Error(err) + } expectPrimaryAddr(addr1) } @@ -3752,8 +4137,10 @@ func TestAutoGenAddrAfterRemoval(t *testing.T) { func TestAutoGenAddrStaticConflict(t *testing.T) { prefix, _, addr := prefixSubnetAddr(0, linkAddr1) + const autoGenAddrCount = 1 ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -3843,8 +4230,10 @@ func TestAutoGenAddrWithOpaqueIID(t *testing.T) { PrefixLen: 64, } + const autoGenAddrCount = 1 ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -3869,30 +4258,21 @@ func TestAutoGenAddrWithOpaqueIID(t *testing.T) { t.Fatalf("CreateNICWithOptions(%d, _, %+v, _) = %s", nicID, opts, err) } - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - // Receive an RA with prefix1 in a PI. const validLifetimeSecondPrefix1 = 1 e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, validLifetimeSecondPrefix1, 0)) - expectAutoGenAddrEvent(addr1, newAddr) + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, addr1); err != nil { + t.Fatalf("error expecting prefix1 stable auto-gen address generated event: %s", err) + } if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { t.Fatalf("should have %s in the list of addresses", addr1) } // Receive an RA with prefix2 in a PI with a large valid lifetime. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0)) - expectAutoGenAddrEvent(addr2, newAddr) + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, addr2); err != nil { + t.Fatalf("error expecting prefix2 stable auto-gen address generated event: %s", err) + } if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { t.Fatalf("should have %s in the list of addresses", addr1) } @@ -3938,33 +4318,6 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) { } } - expectAutoGenAddrEvent := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - - expectAutoGenAddrEventAsync := func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - clock.RunImmediatelyScheduledJobs() - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("timed out waiting for addr auto gen event") - } - } - expectDADEvent := func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) { t.Helper() @@ -4042,7 +4395,9 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) { // Generate a stable SLAAC address so temporary addresses will be // generated. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) - expectAutoGenAddrEvent(t, ndpDisp, stableAddrForTempAddrTest, newAddr) + if _, err := expectAutoGenAddrNewEvent(ndpDisp, stableAddrForTempAddrTest); err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } expectDADEventAsync(t, clock, ndpDisp, stableAddrForTempAddrTest.Address, &stack.DADSucceeded{}) // The stable address will be assigned throughout the test. @@ -4063,9 +4418,11 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) { addrType := addrType t.Run(fmt.Sprintf("%d max retries and %d failures", maxRetries, numFailures), func(t *testing.T) { + const autoGenAddrCount = 2 ndpDisp := ndpDispatcher{ - dadC: make(chan ndpDADEvent, 1), - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), + dadC: make(chan ndpDADEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), } e := channel.New(0, 1280, linkAddr1) ndpConfigs := addrType.ndpConfigs @@ -4100,7 +4457,10 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) { // Simulate DAD conflicts so the address is regenerated. for i := uint8(0); i < numFailures; i++ { addr := addrType.addrGenFn(i, tempIIDHistory[:]) - expectAutoGenAddrEventAsync(t, clock, &ndpDisp, addr, newAddr) + clock.RunImmediatelyScheduledJobs() + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, addr); err != nil { + t.Fatalf("error expecting auto-gen address generated event after %d failure(s): %s", i, err) + } // Should not have any new addresses assigned to the NIC. if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, stableAddrs, nil); mismatch != "" { @@ -4136,7 +4496,10 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) { // an address after DAD resolves. if maxRetries+1 > numFailures { addr := addrType.addrGenFn(numFailures, tempIIDHistory[:]) - expectAutoGenAddrEventAsync(t, clock, &ndpDisp, addr, newAddr) + clock.RunImmediatelyScheduledJobs() + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, addr); err != nil { + t.Fatalf("error expecting final auto-gen address generated event: %s", err) + } expectDADEventAsync(t, clock, &ndpDisp, addr.Address, &stack.DADSucceeded{}) if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, append(stableAddrs, addr), nil); mismatch != "" { t.Fatal(mismatch) @@ -4204,9 +4567,11 @@ func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) { addrType := addrType t.Run(addrType.name, func(t *testing.T) { + const autoGenAddrCount = 2 ndpDisp := ndpDispatcher{ - dadC: make(chan ndpDADEvent, 1), - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), + dadC: make(chan ndpDADEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -4226,19 +4591,6 @@ func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - addrType.triggerSLAACFn(e) addrBytes := []byte(addrType.subnet.ID()) @@ -4247,11 +4599,13 @@ func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) { Address: tcpip.Address(addrBytes), PrefixLen: 64, } - expectAutoGenAddrEvent(addr, newAddr) + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, addr); err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } // Simulate a DAD conflict. rxNDPSolicit(e, addr.Address) - expectAutoGenAddrEvent(addr, invalidatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr) select { case e := <-ndpDisp.dadC: if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" { @@ -4286,9 +4640,12 @@ func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) { prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1) + const autoGenAddrCount = 2 ndpDisp := ndpDispatcher{ - dadC: make(chan ndpDADEvent, 1), - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), + dadC: make(chan ndpDADEvent, 1), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenInstallDisp: true, } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() @@ -4318,20 +4675,8 @@ func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) { t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, err) } - expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { - t.Helper() - - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") - } - } - // Receive an RA with prefix in a PI. + received := clock.NowMonotonic() e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds)) addrBytes := []byte(subnet.ID()) @@ -4339,12 +4684,21 @@ func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) { Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, 0, secretKey)), PrefixLen: 64, } - expectAutoGenAddrEvent(addr, newAddr) + addrDisp, err := expectAutoGenAddrNewEvent(&ndpDisp, addr) + if err != nil { + t.Fatalf("error expecting stable auto-gen address (DAD will not resolve) generated event: %s", err) + } + if err := addrDisp.expectChanged(addressLifetimes(received, lifetimeSeconds, lifetimeSeconds), stack.AddressTentative); err != nil { + t.Error(err) + } // Simulate a DAD conflict after some time has passed. clock.Advance(failureTimer) rxNDPSolicit(e, addr.Address) - expectAutoGenAddrEvent(addr, invalidatedAddr) + expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr) + if err := addrDisp.expectRemoved(stack.AddressRemovalDADFailed); err != nil { + t.Error(err) + } select { case e := <-ndpDisp.dadC: if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" { @@ -4356,7 +4710,13 @@ func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) { // Let the next address resolve. addr.Address = tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, 1, secretKey)) - expectAutoGenAddrEvent(addr, newAddr) + addrDisp, err = expectAutoGenAddrNewEvent(&ndpDisp, addr) + if err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) + } + if err := addrDisp.expectChanged(addressLifetimes(received, lifetimeSeconds, lifetimeSeconds), stack.AddressTentative); err != nil { + t.Error(err) + } clock.Advance(dadTransmits * retransmitTimer) select { case e := <-ndpDisp.dadC: @@ -4366,6 +4726,9 @@ func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) { default: t.Fatal("timed out waiting for DAD event") } + if err := addrDisp.expectStateChanged(stack.AddressAssigned); err != nil { + t.Error(err) + } // Address should be deprecated/invalidated after the lifetime expires. // @@ -4401,6 +4764,9 @@ func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) { default: t.Fatal("timed out waiting for auto gen addr event") } + if err := addrDisp.expectRemoved(stack.AddressRemovalInvalidated); err != nil { + t.Error(err) + } } // TestNDPRecursiveDNSServerDispatch tests that we properly dispatch an event @@ -4659,10 +5025,12 @@ func TestNoCleanupNDPStateWhenForwardingEnabled(t *testing.T) { nicID = 1 ) + const autoGenAddrCount = 1 ndpDisp := ndpDispatcher{ - offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1), - prefixC: make(chan ndpPrefixEvent, 1), - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1), + prefixC: make(chan ndpPrefixEvent, 1), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), } s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ @@ -4682,13 +5050,8 @@ func TestNoCleanupNDPStateWhenForwardingEnabled(t *testing.T) { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } llAddr := tcpip.AddressWithPrefix{Address: llAddr1, PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen} - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, llAddr, newAddr); diff != "" { - t.Errorf("auto-gen addr mismatch (-want +got):\n%s", diff) - } - default: - t.Errorf("expected auto-gen addr event for %s on NIC(%d)", llAddr, nicID) + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, llAddr); err != nil { + t.Fatalf("error expecting link-local auto-gen address generated event: %s", err) } prefix, subnet, addr := prefixSubnetAddr(0, linkAddr1) @@ -4720,13 +5083,8 @@ func TestNoCleanupNDPStateWhenForwardingEnabled(t *testing.T) { default: t.Errorf("expected prefix event for %s on NIC(%d)", prefix, nicID) } - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" { - t.Errorf("auto-gen addr mismatch (-want +got):\n%s", diff) - } - default: - t.Errorf("expected auto-gen addr event for %s on NIC(%d)", addr, nicID) + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, addr); err != nil { + t.Fatalf("error expecting stable auto-gen address generated event: %s", err) } // Enabling or disabling forwarding should not invalidate discovered prefixes @@ -4751,6 +5109,11 @@ func TestNoCleanupNDPStateWhenForwardingEnabled(t *testing.T) { t.Errorf("unexpected auto-gen addr event = %#v", e) default: } + select { + case e := <-ndpDisp.autoGenAddrNewC: + t.Errorf("unexpected new auto-gen addr event = %#v", e) + default: + } }) } } @@ -4824,9 +5187,10 @@ func TestCleanupNDPState(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { ndpDisp := ndpDispatcher{ - offLinkRouteC: make(chan ndpOffLinkRouteEvent, maxRouterAndPrefixEvents), - prefixC: make(chan ndpPrefixEvent, maxRouterAndPrefixEvents), - autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAutoGenAddrEvents), + offLinkRouteC: make(chan ndpOffLinkRouteEvent, maxRouterAndPrefixEvents), + prefixC: make(chan ndpPrefixEvent, maxRouterAndPrefixEvents), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, test.maxAutoGenAddrEvents), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAutoGenAddrEvents), } clock := faketime.NewManualClock() s := stack.New(stack.Options{ @@ -4873,6 +5237,15 @@ func TestCleanupNDPState(t *testing.T) { return false, ndpAutoGenAddrEvent{} } + expectAutoGenAddrNewEvent := func() (bool, ndpAutoGenAddrNewEvent) { + select { + case e := <-ndpDisp.autoGenAddrNewC: + return true, e + default: + } + return false, ndpAutoGenAddrNewEvent{} + } + e1 := channel.New(0, 1280, linkAddr1) if err := s.CreateNIC(nicID1, e1); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID1, err) @@ -4881,13 +5254,13 @@ func TestCleanupNDPState(t *testing.T) { // on normal discovery of routers/prefixes, and auto-generated // addresses. Here we just make sure we get an event and let other tests // handle the correctness check. - expectAutoGenAddrEvent() + expectAutoGenAddrNewEvent() e2 := channel.New(0, 1280, linkAddr2) if err := s.CreateNIC(nicID2, e2); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err) } - expectAutoGenAddrEvent() + expectAutoGenAddrNewEvent() // Receive RAs on NIC(1) and NIC(2) from default routers (llAddr3 and // llAddr4) w/ PI (for prefix1 in RA from llAddr3 and prefix2 in RA from @@ -4901,7 +5274,7 @@ func TestCleanupNDPState(t *testing.T) { if ok, _ := expectPrefixEvent(); !ok { t.Errorf("expected prefix event for %s on NIC(%d)", prefix1, nicID1) } - if ok, _ := expectAutoGenAddrEvent(); !ok { + if ok, _ := expectAutoGenAddrNewEvent(); !ok { t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr1, nicID1) } @@ -4912,7 +5285,7 @@ func TestCleanupNDPState(t *testing.T) { if ok, _ := expectPrefixEvent(); !ok { t.Errorf("expected prefix event for %s on NIC(%d)", prefix2, nicID1) } - if ok, _ := expectAutoGenAddrEvent(); !ok { + if ok, _ := expectAutoGenAddrNewEvent(); !ok { t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr2, nicID1) } @@ -4923,7 +5296,7 @@ func TestCleanupNDPState(t *testing.T) { if ok, _ := expectPrefixEvent(); !ok { t.Errorf("expected prefix event for %s on NIC(%d)", prefix1, nicID2) } - if ok, _ := expectAutoGenAddrEvent(); !ok { + if ok, _ := expectAutoGenAddrNewEvent(); !ok { t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr2, nicID2) } @@ -4934,7 +5307,7 @@ func TestCleanupNDPState(t *testing.T) { if ok, _ := expectPrefixEvent(); !ok { t.Errorf("expected prefix event for %s on NIC(%d)", prefix2, nicID2) } - if ok, _ := expectAutoGenAddrEvent(); !ok { + if ok, _ := expectAutoGenAddrNewEvent(); !ok { t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e2Addr2, nicID2) } @@ -5089,6 +5462,11 @@ func TestCleanupNDPState(t *testing.T) { t.Error("unexpected auto-generated address event") default: } + select { + case <-ndpDisp.autoGenAddrNewC: + t.Error("unexpected auto-generated address event") + default: + } }) } } diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index 889320477..311d4944d 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -597,14 +597,14 @@ func (n *nic) removeAddress(addr tcpip.Address) tcpip.Error { return &tcpip.ErrBadLocalAddress{} } -func (n *nic) setAddressDeprecated(addr tcpip.Address, deprecated bool) tcpip.Error { +func (n *nic) setAddressLifetimes(addr tcpip.Address, lifetimes AddressLifetimes) tcpip.Error { for _, ep := range n.networkEndpoints { ep, ok := ep.(AddressableEndpoint) if !ok { continue } - switch err := ep.SetDeprecated(addr, deprecated); err.(type) { + switch err := ep.SetLifetimes(addr, lifetimes); err.(type) { case *tcpip.ErrBadLocalAddress: continue default: diff --git a/pkg/tcpip/stack/nic_test.go b/pkg/tcpip/stack/nic_test.go index 90c017243..5dfbd6d81 100644 --- a/pkg/tcpip/stack/nic_test.go +++ b/pkg/tcpip/stack/nic_test.go @@ -134,7 +134,7 @@ func (p *testIPv6Protocol) NewEndpoint(nic NetworkInterface, _ TransportDispatch nic: nic, protocol: p, } - e.AddressableEndpointState.Init(e) + e.AddressableEndpointState.Init(e, AddressableEndpointStateOptions{HiddenWhileDisabled: false}) return e } diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index 3b09ab8d6..35c742e96 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -366,17 +366,143 @@ const ( AddressConfigSlaac ) +// AddressLifetimes encodes an address' preferred and valid lifetimes, as well +// as if the address is deprecated. +type AddressLifetimes struct { + // Deprecated is whether the address is deprecated. + Deprecated bool + + // PreferredUntil is the time at which the address will be deprecated. + // + // Note that for certain addresses, deprecating the address at the + // PreferredUntil time is not handled as a scheduled job by the stack, but + // is information provided by the owner as an indication of when it will + // deprecate the address. + // + // PreferredUntil should be ignored if Deprecated is true. If Deprecated + // is false, and PreferredUntil is the zero value, no information about + // the preferred lifetime can be inferred. + PreferredUntil tcpip.MonotonicTime + + // ValidUntil is the time at which the address will be invalidated. + // + // Note that for certain addresses, invalidating the address at the + // ValidUntil time is not handled as a scheduled job by the stack, but + // is information provided by the owner as an indication of when it will + // invalidate the address. + // + // If ValidUntil is the zero value, no information about the valid lifetime + // can be inferred. + ValidUntil tcpip.MonotonicTime +} + // AddressProperties contains additional properties that can be configured when // adding an address. type AddressProperties struct { PEB PrimaryEndpointBehavior ConfigType AddressConfigType - Deprecated bool + // Lifetimes encodes the address' lifetimes. + // + // Lifetimes.PreferredUntil and Lifetimes.ValidUntil are informational, i.e. + // the stack will not deprecated nor invalidate the address upon reaching + // these timestamps. + // + // If Lifetimes.Deprecated is true, the address will be added as deprecated. + Lifetimes AddressLifetimes // Temporary is as defined in RFC 4941, but applies not only to addresses // added via SLAAC, e.g. DHCPv6 can also add temporary addresses. Temporary // addresses are short-lived and are not to be valid (or preferred) // forever; hence the term temporary. Temporary bool + Disp AddressDispatcher +} + +// AddressAssignmentState is an address' assignment state. +type AddressAssignmentState int + +const ( + _ AddressAssignmentState = iota + + // AddressDisabled indicates the NIC the address is assigned to is disabled. + AddressDisabled + + // AddressTentative indicates an address is yet to pass DAD (IPv4 addresses + // are never tentative). + AddressTentative + + // AddressAssigned indicates an address is assigned. + AddressAssigned +) + +func (state AddressAssignmentState) String() string { + switch state { + case AddressDisabled: + return "Disabled" + case AddressTentative: + return "Tentative" + case AddressAssigned: + return "Assigned" + default: + panic(fmt.Sprintf("unknown address assignment state: %d", state)) + } +} + +// AddressRemovalReason is the reason an address was removed. +type AddressRemovalReason int + +const ( + _ AddressRemovalReason = iota + + // AddressRemovalManualAction indicates the address was removed explicitly + // using the stack API. + AddressRemovalManualAction + + // AddressRemovalInterfaceRemoved indicates the address was removed because + // the NIC it is assigned to was removed. + AddressRemovalInterfaceRemoved + + // AddressRemovalDADFailed indicates the address was removed because DAD + // failed. + AddressRemovalDADFailed + + // AddressRemovalInvalidated indicates the address was removed because it + // was invalidated. + AddressRemovalInvalidated +) + +func (reason AddressRemovalReason) String() string { + switch reason { + case AddressRemovalManualAction: + return "ManualAction" + case AddressRemovalInterfaceRemoved: + return "InterfaceRemoved" + case AddressRemovalDADFailed: + return "DADFailed" + case AddressRemovalInvalidated: + return "Invalidated" + default: + panic(fmt.Sprintf("unknown address removal reason: %d", reason)) + } +} + +// AddressDispatcher is the interface integrators can implement to receive +// address-related events. +type AddressDispatcher interface { + // OnChanged is called with an address' properties when they change. + // + // OnChanged is called once when the address is added with the initial state, + // and every time a property changes. + // + // The PreferredUntil and ValidUntil fields in AddressLifetimes must be + // considered informational, i.e. one must not consider an address to be + // deprecated/invalid even if the monotonic clock timestamp is past these + // deadlines. The Deprecated field indicates whether an address is + // preferred or not; and OnRemoved will be called when an address is + // removed due to invalidation. + OnChanged(AddressLifetimes, AddressAssignmentState) + + // OnRemoved is called when an address is removed with the removal reason. + OnRemoved(AddressRemovalReason) } // AssignableAddressEndpoint is a reference counted address endpoint that may be @@ -422,8 +548,23 @@ type AddressEndpoint interface { // SetDeprecated sets this endpoint's deprecated status. SetDeprecated(bool) + // Lifetimes returns this endpoint's lifetimes. + Lifetimes() AddressLifetimes + + // SetLifetimes sets this endpoint's lifetimes. + // + // Note that setting preferred-until and valid-until times do not result in + // deprecation/invalidation jobs to be scheduled by the stack. + SetLifetimes(AddressLifetimes) + // Temporary returns whether or not this endpoint is temporary. Temporary() bool + + // RegisterDispatcher registers an address dispatcher. + // + // OnChanged will be called immediately on the provided address dispatcher + // with this endpoint's current state. + RegisterDispatcher(AddressDispatcher) } // AddressKind is the kind of an address. @@ -497,11 +638,12 @@ type AddressableEndpoint interface { // permanent address. RemovePermanentAddress(addr tcpip.Address) tcpip.Error - // SetDeprecated sets whether the address should be deprecated or not. + // SetLifetimes sets an address' lifetimes (strictly informational) and + // whether it should be deprecated or preferred. // // Returns *tcpip.ErrBadLocalAddress if the endpoint does not have the passed // address. - SetDeprecated(addr tcpip.Address, deprecated bool) tcpip.Error + SetLifetimes(addr tcpip.Address, lifetimes AddressLifetimes) tcpip.Error // MainAddress returns the endpoint's primary permanent address. MainAddress() tcpip.AddressWithPrefix diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 8afd11958..dc0fa7acf 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -1096,13 +1096,14 @@ func (s *Stack) RemoveAddress(id tcpip.NICID, addr tcpip.Address) tcpip.Error { return &tcpip.ErrUnknownNICID{} } -// SetAddressDeprecated sets an address to be deprecated or preferred. -func (s *Stack) SetAddressDeprecated(id tcpip.NICID, addr tcpip.Address, deprecated bool) tcpip.Error { +// SetAddressLifetimes sets informational preferred and valid lifetimes, and +// whether the address should be preferred or deprecated. +func (s *Stack) SetAddressLifetimes(id tcpip.NICID, addr tcpip.Address, lifetimes AddressLifetimes) tcpip.Error { s.mu.RLock() defer s.mu.RUnlock() if nic, ok := s.nics[id]; ok { - return nic.setAddressDeprecated(addr, deprecated) + return nic.setAddressLifetimes(addr, lifetimes) } return &tcpip.ErrUnknownNICID{} diff --git a/pkg/tcpip/stack/stack_test.go b/pkg/tcpip/stack/stack_test.go index e8d289942..7a38c3f08 100644 --- a/pkg/tcpip/stack/stack_test.go +++ b/pkg/tcpip/stack/stack_test.go @@ -276,7 +276,7 @@ func (f *fakeNetworkProtocol) NewEndpoint(nic stack.NetworkInterface, dispatcher proto: f, dispatcher: dispatcher, } - e.AddressableEndpointState.Init(e) + e.AddressableEndpointState.Init(e, stack.AddressableEndpointStateOptions{HiddenWhileDisabled: false}) return e } @@ -424,6 +424,117 @@ func containsAddr(list []tcpip.ProtocolAddress, item tcpip.ProtocolAddress) bool return false } +type addressChangedEvent struct { + lifetimes stack.AddressLifetimes + state stack.AddressAssignmentState +} + +// An implementation of AddressDispatcher which forwards data from callbacks +// to channels to be asserted against in tests. +type addressDispatcher struct { + changedCh chan addressChangedEvent + removedCh chan stack.AddressRemovalReason + nicid tcpip.NICID + addr tcpip.AddressWithPrefix + lifetimes stack.AddressLifetimes + state stack.AddressAssignmentState +} + +var _ stack.AddressDispatcher = (*addressDispatcher)(nil) + +// OnChanged implements stack.AddressDispatcher. +func (ad *addressDispatcher) OnChanged(lifetimes stack.AddressLifetimes, state stack.AddressAssignmentState) { + if ad.changedCh != nil { + ad.changedCh <- addressChangedEvent{ + lifetimes: lifetimes, + state: state, + } + } +} + +// OnRemoved implements stack.AddressDispatcher. +func (ad *addressDispatcher) OnRemoved(reason stack.AddressRemovalReason) { + if ad.removedCh != nil { + ad.removedCh <- reason + } +} + +func (ad *addressDispatcher) disable() { + ad.changedCh = nil + ad.removedCh = nil +} + +func (ad *addressDispatcher) expectNoEvent() error { + select { + case e := <-ad.changedCh: + return fmt.Errorf("dispatcher for nic=%d addr=%s unexpectedly received changed event: %#v", ad.nicid, ad.addr, e) + case e := <-ad.removedCh: + return fmt.Errorf("dispatcher for nic=%d addr=%s unexpectedly received removed event: %#v", ad.nicid, ad.addr, e) + default: + return nil + } +} + +func (ad *addressDispatcher) expectChanged(lifetimes stack.AddressLifetimes, state stack.AddressAssignmentState) error { + select { + case e := <-ad.changedCh: + ad.lifetimes = e.lifetimes + ad.state = e.state + if diff := cmp.Diff(e, addressChangedEvent{ + lifetimes: lifetimes, + state: state, + }, cmp.AllowUnexported(e, tcpip.MonotonicTime{})); diff != "" { + return fmt.Errorf("dispatcher for nic=%d addr=%s address changed event mismatch (-got +want):\n%s", ad.nicid, ad.addr, diff) + } + default: + return fmt.Errorf("dispatcher for nic=%d addr=%s address changed event not immediately ready", ad.nicid, ad.addr) + } + return nil +} + +func (ad *addressDispatcher) expectDeprecated() error { + return ad.expectChanged(stack.AddressLifetimes{ + Deprecated: true, + ValidUntil: ad.lifetimes.ValidUntil, + }, ad.state) +} + +func (ad *addressDispatcher) expectValidUntilChanged(validUntil tcpip.MonotonicTime) error { + return ad.expectChanged(stack.AddressLifetimes{ + Deprecated: ad.lifetimes.Deprecated, + PreferredUntil: ad.lifetimes.PreferredUntil, + ValidUntil: validUntil, + }, ad.state) +} + +func (ad *addressDispatcher) expectLifetimesChanged(lifetimes stack.AddressLifetimes) error { + return ad.expectChanged(lifetimes, ad.state) +} + +func (ad *addressDispatcher) expectStateChanged(state stack.AddressAssignmentState) error { + return ad.expectChanged(ad.lifetimes, state) +} + +func (ad *addressDispatcher) expectRemoved(want stack.AddressRemovalReason) error { + select { + case got := <-ad.removedCh: + if want != got { + return fmt.Errorf("dispatcher for nic=%d addr=%s got removal reason = %s, want = %s", ad.nicid, ad.addr, got, want) + } + default: + return fmt.Errorf("dispatcher for nic=%d addr=%s address removed event not immediately ready", ad.nicid, ad.addr) + } + return nil +} + +func infiniteLifetimes() stack.AddressLifetimes { + return stack.AddressLifetimes{ + Deprecated: false, + ValidUntil: tcpip.MonotonicTimeInfinite(), + PreferredUntil: tcpip.MonotonicTimeInfinite(), + } +} + func TestNetworkReceive(t *testing.T) { // Create a stack with the fake network protocol, one nic, and two // addresses attached to it: 1 & 2. @@ -2297,7 +2408,7 @@ func TestAddProtocolAddress(t *testing.T) { properties := stack.AddressProperties{ PEB: behavior, ConfigType: configType, - Deprecated: deprecated, + Lifetimes: stack.AddressLifetimes{Deprecated: deprecated}, Temporary: temporary, } protocolAddr := tcpip.ProtocolAddress{ @@ -2687,8 +2798,10 @@ func TestNICAutoGenLinkLocalAddr(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { + const autoGenAddrCount = 1 ndpDisp := ndpDispatcher{ - autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), + autoGenAddrC: make(chan ndpAutoGenAddrEvent, autoGenAddrCount), + autoGenAddrNewC: make(chan ndpAutoGenAddrNewEvent, autoGenAddrCount), } opts := stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ @@ -2731,13 +2844,8 @@ func TestNICAutoGenLinkLocalAddr(t *testing.T) { // Should have auto-generated an address and resolved immediately (DAD // is disabled). - select { - case e := <-ndpDisp.autoGenAddrC: - if diff := checkAutoGenAddrEvent(e, expectedMainAddr, newAddr); diff != "" { - t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) - } - default: - t.Fatal("expected addr auto gen event") + if _, err := expectAutoGenAddrNewEvent(&ndpDisp, expectedMainAddr); err != nil { + t.Fatalf("error expecting link-local auto-gen address generated event: %s", err) } } else { // Should not have auto-generated an address. @@ -3190,7 +3298,7 @@ func TestIPv6SourceAddressSelectionScopeAndSameAddress(t *testing.T) { { addr: globalAddr2, properties: stack.AddressProperties{ - Deprecated: true, + Lifetimes: stack.AddressLifetimes{Deprecated: true}, }, }, }, @@ -3203,7 +3311,7 @@ func TestIPv6SourceAddressSelectionScopeAndSameAddress(t *testing.T) { { addr: globalAddr2, properties: stack.AddressProperties{ - Deprecated: true, + Lifetimes: stack.AddressLifetimes{Deprecated: true}, }, }, {addr: globalAddr1}, diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index 1ee9e02b8..20d97d59b 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -33,6 +33,7 @@ import ( "errors" "fmt" "io" + "math" "math/bits" "reflect" "strconv" @@ -76,6 +77,12 @@ func (mt MonotonicTime) String() string { return strconv.FormatInt(mt.nanoseconds, 10) } +// MonotonicTimeInfinite returns the monotonic timestamp as far away in the +// future as possible. +func MonotonicTimeInfinite() MonotonicTime { + return MonotonicTime{nanoseconds: math.MaxInt64} +} + // Before reports whether the monotonic clock reading mt is before u. func (mt MonotonicTime) Before(u MonotonicTime) bool { return mt.nanoseconds < u.nanoseconds diff --git a/pkg/tcpip/tests/integration/loopback_test.go b/pkg/tcpip/tests/integration/loopback_test.go index b77956499..80060544c 100644 --- a/pkg/tcpip/tests/integration/loopback_test.go +++ b/pkg/tcpip/tests/integration/loopback_test.go @@ -54,7 +54,8 @@ func (*ndpDispatcher) OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet) { func (*ndpDispatcher) OnOnLinkPrefixInvalidated(tcpip.NICID, tcpip.Subnet) {} -func (*ndpDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) { +func (*ndpDispatcher) OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) stack.AddressDispatcher { + return nil } func (*ndpDispatcher) OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) {}