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) {}