diff options
Diffstat (limited to 'pkg/tcpip/stack/ndp.go')
-rw-r--r-- | pkg/tcpip/stack/ndp.go | 1973 |
1 files changed, 0 insertions, 1973 deletions
diff --git a/pkg/tcpip/stack/ndp.go b/pkg/tcpip/stack/ndp.go deleted file mode 100644 index 97ca00d16..000000000 --- a/pkg/tcpip/stack/ndp.go +++ /dev/null @@ -1,1973 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stack - -import ( - "fmt" - "log" - "math/rand" - "time" - - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/buffer" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -const ( - // defaultDupAddrDetectTransmits is the default number of NDP Neighbor - // Solicitation messages to send when doing Duplicate Address Detection - // for a tentative address. - // - // Default = 1 (from RFC 4862 section 5.1) - defaultDupAddrDetectTransmits = 1 - - // defaultMaxRtrSolicitations is the default number of Router - // Solicitation messages to send when a NIC becomes enabled. - // - // Default = 3 (from RFC 4861 section 10). - defaultMaxRtrSolicitations = 3 - - // defaultRtrSolicitationInterval is the default amount of time between - // sending Router Solicitation messages. - // - // Default = 4s (from 4861 section 10). - defaultRtrSolicitationInterval = 4 * time.Second - - // defaultMaxRtrSolicitationDelay is the default maximum amount of time - // to wait before sending the first Router Solicitation message. - // - // Default = 1s (from 4861 section 10). - defaultMaxRtrSolicitationDelay = time.Second - - // defaultHandleRAs is the default configuration for whether or not to - // handle incoming Router Advertisements as a host. - defaultHandleRAs = true - - // defaultDiscoverDefaultRouters is the default configuration for - // whether or not to discover default routers from incoming Router - // Advertisements, as a host. - defaultDiscoverDefaultRouters = true - - // defaultDiscoverOnLinkPrefixes is the default configuration for - // whether or not to discover on-link prefixes from incoming Router - // Advertisements' Prefix Information option, as a host. - defaultDiscoverOnLinkPrefixes = true - - // defaultAutoGenGlobalAddresses is the default configuration for - // whether or not to generate global IPv6 addresses in response to - // receiving a new Prefix Information option with its Autonomous - // Address AutoConfiguration flag set, as a host. - // - // Default = true. - defaultAutoGenGlobalAddresses = true - - // minimumRtrSolicitationInterval is the minimum amount of time to wait - // between sending Router Solicitation messages. This limit is imposed - // to make sure that Router Solicitation messages are not sent all at - // once, defeating the purpose of sending the initial few messages. - minimumRtrSolicitationInterval = 500 * time.Millisecond - - // minimumMaxRtrSolicitationDelay is the minimum amount of time to wait - // before sending the first Router Solicitation message. It is 0 because - // we cannot have a negative delay. - minimumMaxRtrSolicitationDelay = 0 - - // MaxDiscoveredDefaultRouters is the maximum number of discovered - // default routers. The stack should stop discovering new routers after - // discovering MaxDiscoveredDefaultRouters routers. - // - // This value MUST be at minimum 2 as per RFC 4861 section 6.3.4, and - // SHOULD be more. - MaxDiscoveredDefaultRouters = 10 - - // MaxDiscoveredOnLinkPrefixes is the maximum number of discovered - // on-link prefixes. The stack should stop discovering new on-link - // prefixes after discovering MaxDiscoveredOnLinkPrefixes on-link - // prefixes. - MaxDiscoveredOnLinkPrefixes = 10 - - // validPrefixLenForAutoGen is the expected prefix length that an - // address can be generated for. Must be 64 bits as the interface - // identifier (IID) is 64 bits and an IPv6 address is 128 bits, so - // 128 - 64 = 64. - validPrefixLenForAutoGen = 64 - - // defaultAutoGenTempGlobalAddresses is the default configuration for whether - // or not to generate temporary SLAAC addresses. - defaultAutoGenTempGlobalAddresses = true - - // defaultMaxTempAddrValidLifetime is the default maximum valid lifetime - // for temporary SLAAC addresses generated as part of RFC 4941. - // - // Default = 7 days (from RFC 4941 section 5). - defaultMaxTempAddrValidLifetime = 7 * 24 * time.Hour - - // defaultMaxTempAddrPreferredLifetime is the default preferred lifetime - // for temporary SLAAC addresses generated as part of RFC 4941. - // - // Default = 1 day (from RFC 4941 section 5). - defaultMaxTempAddrPreferredLifetime = 24 * time.Hour - - // defaultRegenAdvanceDuration is the default duration before the deprecation - // of a temporary address when a new address will be generated. - // - // Default = 5s (from RFC 4941 section 5). - defaultRegenAdvanceDuration = 5 * time.Second - - // minRegenAdvanceDuration is the minimum duration before the deprecation - // of a temporary address when a new address will be generated. - minRegenAdvanceDuration = time.Duration(0) - - // maxSLAACAddrLocalRegenAttempts is the maximum number of times to attempt - // SLAAC address regenerations in response to a NIC-local conflict. - maxSLAACAddrLocalRegenAttempts = 10 -) - -var ( - // MinPrefixInformationValidLifetimeForUpdate is the minimum Valid - // Lifetime to update the valid lifetime of a generated address by - // SLAAC. - // - // This is exported as a variable (instead of a constant) so tests - // can update it to a smaller value. - // - // Min = 2hrs. - MinPrefixInformationValidLifetimeForUpdate = 2 * time.Hour - - // MaxDesyncFactor is the upper bound for the preferred lifetime's desync - // factor for temporary SLAAC addresses. - // - // This is exported as a variable (instead of a constant) so tests - // can update it to a smaller value. - // - // Must be greater than 0. - // - // Max = 10m (from RFC 4941 section 5). - MaxDesyncFactor = 10 * time.Minute - - // MinMaxTempAddrPreferredLifetime is the minimum value allowed for the - // maximum preferred lifetime for temporary SLAAC addresses. - // - // This is exported as a variable (instead of a constant) so tests - // can update it to a smaller value. - // - // This value guarantees that a temporary address will be preferred for at - // least 1hr if the SLAAC prefix is valid for at least that time. - MinMaxTempAddrPreferredLifetime = defaultRegenAdvanceDuration + MaxDesyncFactor + time.Hour - - // MinMaxTempAddrValidLifetime is the minimum value allowed for the - // maximum valid lifetime for temporary SLAAC addresses. - // - // This is exported as a variable (instead of a constant) so tests - // can update it to a smaller value. - // - // This value guarantees that a temporary address will be valid for at least - // 2hrs if the SLAAC prefix is valid for at least that time. - MinMaxTempAddrValidLifetime = 2 * time.Hour -) - -// DHCPv6ConfigurationFromNDPRA is a configuration available via DHCPv6 that an -// NDP Router Advertisement informed the Stack about. -type DHCPv6ConfigurationFromNDPRA int - -const ( - _ DHCPv6ConfigurationFromNDPRA = iota - - // DHCPv6NoConfiguration indicates that no configurations are available via - // DHCPv6. - DHCPv6NoConfiguration - - // DHCPv6ManagedAddress indicates that addresses are available via DHCPv6. - // - // DHCPv6ManagedAddress also implies DHCPv6OtherConfigurations because DHCPv6 - // will return all available configuration information. - DHCPv6ManagedAddress - - // DHCPv6OtherConfigurations indicates that other configuration information is - // available via DHCPv6. - // - // Other configurations are configurations other than addresses. Examples of - // other configurations are recursive DNS server list, DNS search lists and - // default gateway. - DHCPv6OtherConfigurations -) - -// NDPDispatcher is the interface integrators of netstack must implement to -// receive and handle NDP related events. -type NDPDispatcher interface { - // OnDuplicateAddressDetectionStatus will be called when the DAD process - // for an address (addr) on a NIC (with ID nicID) completes. resolved - // will be set to true if DAD completed successfully (no duplicate addr - // detected); false otherwise (addr was detected to be a duplicate on - // the link the NIC is a part of, or it was stopped for some other - // reason, such as the address being removed). If an error occured - // during DAD, err will be set and resolved must be ignored. - // - // This function is not permitted to block indefinitely. This function - // is also not permitted to call into the stack. - OnDuplicateAddressDetectionStatus(nicID tcpip.NICID, addr tcpip.Address, resolved bool, err *tcpip.Error) - - // OnDefaultRouterDiscovered will be called when a new default router is - // discovered. Implementations must return true if the newly discovered - // router should be remembered. - // - // This function is not permitted to block indefinitely. This function - // is also not permitted to call into the stack. - OnDefaultRouterDiscovered(nicID tcpip.NICID, addr tcpip.Address) bool - - // OnDefaultRouterInvalidated will be called when a discovered default - // router that was remembered is invalidated. - // - // This function is not permitted to block indefinitely. This function - // is also not permitted to call into the stack. - OnDefaultRouterInvalidated(nicID tcpip.NICID, addr tcpip.Address) - - // OnOnLinkPrefixDiscovered will be called when a new on-link prefix is - // discovered. Implementations must return true if the newly discovered - // on-link prefix should be remembered. - // - // This function is not permitted to block indefinitely. This function - // is also not permitted to call into the stack. - OnOnLinkPrefixDiscovered(nicID tcpip.NICID, prefix tcpip.Subnet) bool - - // OnOnLinkPrefixInvalidated will be called when a discovered on-link - // prefix that was remembered is invalidated. - // - // This function is not permitted to block indefinitely. This function - // is also not permitted to call into the stack. - OnOnLinkPrefixInvalidated(nicID tcpip.NICID, prefix tcpip.Subnet) - - // OnAutoGenAddress will be called when a new prefix with its - // autonomous address-configuration flag set has been received and SLAAC - // has been performed. Implementations may prevent the stack from - // assigning the address to the NIC by returning false. - // - // This function is not permitted to block indefinitely. It must not - // call functions on the stack itself. - OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) bool - - // OnAutoGenAddressDeprecated will be called when an auto-generated - // address (as part of SLAAC) has been deprecated, but is still - // considered valid. Note, if an address is invalidated at the same - // time it is deprecated, the deprecation event MAY be omitted. - // - // This function is not permitted to block indefinitely. It must not - // call functions on the stack itself. - OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) - - // OnAutoGenAddressInvalidated will be called when an auto-generated - // address (as part of SLAAC) has been invalidated. - // - // This function is not permitted to block indefinitely. It must not - // call functions on the stack itself. - OnAutoGenAddressInvalidated(tcpip.NICID, tcpip.AddressWithPrefix) - - // OnRecursiveDNSServerOption will be called when an NDP option with - // recursive DNS servers has been received. Note, addrs may contain - // link-local addresses. - // - // It is up to the caller to use the DNS Servers only for their valid - // lifetime. OnRecursiveDNSServerOption may be called for new or - // already known DNS servers. If called with known DNS servers, their - // valid lifetimes must be refreshed to lifetime (it may be increased, - // decreased, or completely invalidated when lifetime = 0). - // - // This function is not permitted to block indefinitely. It must not - // call functions on the stack itself. - OnRecursiveDNSServerOption(nicID tcpip.NICID, addrs []tcpip.Address, lifetime time.Duration) - - // OnDNSSearchListOption will be called when an NDP option with a DNS - // search list has been received. - // - // It is up to the caller to use the domain names in the search list - // for only their valid lifetime. OnDNSSearchListOption may be called - // with new or already known domain names. If called with known domain - // names, their valid lifetimes must be refreshed to lifetime (it may - // be increased, decreased or completely invalidated when lifetime = 0. - OnDNSSearchListOption(nicID tcpip.NICID, domainNames []string, lifetime time.Duration) - - // OnDHCPv6Configuration will be called with an updated configuration that is - // available via DHCPv6 for a specified NIC. - // - // This function is not permitted to block indefinitely. It must not - // call functions on the stack itself. - OnDHCPv6Configuration(tcpip.NICID, DHCPv6ConfigurationFromNDPRA) -} - -// NDPConfigurations is the NDP configurations for the netstack. -type NDPConfigurations struct { - // The number of Neighbor Solicitation messages to send when doing - // Duplicate Address Detection for a tentative address. - // - // Note, a value of zero effectively disables DAD. - DupAddrDetectTransmits uint8 - - // The amount of time to wait between sending Neighbor solicitation - // messages. - // - // Must be greater than or equal to 1ms. - RetransmitTimer time.Duration - - // The number of Router Solicitation messages to send when the NIC - // becomes enabled. - MaxRtrSolicitations uint8 - - // The amount of time between transmitting Router Solicitation messages. - // - // Must be greater than or equal to 0.5s. - RtrSolicitationInterval time.Duration - - // The maximum amount of time before transmitting the first Router - // Solicitation message. - // - // Must be greater than or equal to 0s. - MaxRtrSolicitationDelay time.Duration - - // HandleRAs determines whether or not Router Advertisements will be - // processed. - HandleRAs bool - - // DiscoverDefaultRouters determines whether or not default routers will - // be discovered from Router Advertisements. This configuration is - // ignored if HandleRAs is false. - DiscoverDefaultRouters bool - - // DiscoverOnLinkPrefixes determines whether or not on-link prefixes - // will be discovered from Router Advertisements' Prefix Information - // option. This configuration is ignored if HandleRAs is false. - DiscoverOnLinkPrefixes bool - - // AutoGenGlobalAddresses determines whether or not global IPv6 - // addresses will be generated for a NIC in response to receiving a new - // Prefix Information option with its Autonomous Address - // AutoConfiguration flag set, as a host, as per RFC 4862 (SLAAC). - // - // Note, if an address was already generated for some unique prefix, as - // part of SLAAC, this option does not affect whether or not the - // lifetime(s) of the generated address changes; this option only - // affects the generation of new addresses as part of SLAAC. - AutoGenGlobalAddresses bool - - // AutoGenAddressConflictRetries determines how many times to attempt to retry - // generation of a permanent auto-generated address in response to DAD - // conflicts. - // - // If the method used to generate the address does not support creating - // alternative addresses (e.g. IIDs based on the modified EUI64 of a NIC's - // MAC address), then no attempt will be made to resolve the conflict. - AutoGenAddressConflictRetries uint8 - - // AutoGenTempGlobalAddresses determines whether or not temporary SLAAC - // addresses will be generated for a NIC as part of SLAAC privacy extensions, - // RFC 4941. - // - // Ignored if AutoGenGlobalAddresses is false. - AutoGenTempGlobalAddresses bool - - // MaxTempAddrValidLifetime is the maximum valid lifetime for temporary - // SLAAC addresses. - MaxTempAddrValidLifetime time.Duration - - // MaxTempAddrPreferredLifetime is the maximum preferred lifetime for - // temporary SLAAC addresses. - MaxTempAddrPreferredLifetime time.Duration - - // RegenAdvanceDuration is the duration before the deprecation of a temporary - // address when a new address will be generated. - RegenAdvanceDuration time.Duration -} - -// DefaultNDPConfigurations returns an NDPConfigurations populated with -// default values. -func DefaultNDPConfigurations() NDPConfigurations { - return NDPConfigurations{ - DupAddrDetectTransmits: defaultDupAddrDetectTransmits, - RetransmitTimer: defaultRetransmitTimer, - MaxRtrSolicitations: defaultMaxRtrSolicitations, - RtrSolicitationInterval: defaultRtrSolicitationInterval, - MaxRtrSolicitationDelay: defaultMaxRtrSolicitationDelay, - HandleRAs: defaultHandleRAs, - DiscoverDefaultRouters: defaultDiscoverDefaultRouters, - DiscoverOnLinkPrefixes: defaultDiscoverOnLinkPrefixes, - AutoGenGlobalAddresses: defaultAutoGenGlobalAddresses, - AutoGenTempGlobalAddresses: defaultAutoGenTempGlobalAddresses, - MaxTempAddrValidLifetime: defaultMaxTempAddrValidLifetime, - MaxTempAddrPreferredLifetime: defaultMaxTempAddrPreferredLifetime, - RegenAdvanceDuration: defaultRegenAdvanceDuration, - } -} - -// validate modifies an NDPConfigurations with valid values. If invalid values -// are present in c, the corresponding default values will be used instead. -func (c *NDPConfigurations) validate() { - if c.RetransmitTimer < minimumRetransmitTimer { - c.RetransmitTimer = defaultRetransmitTimer - } - - if c.RtrSolicitationInterval < minimumRtrSolicitationInterval { - c.RtrSolicitationInterval = defaultRtrSolicitationInterval - } - - if c.MaxRtrSolicitationDelay < minimumMaxRtrSolicitationDelay { - c.MaxRtrSolicitationDelay = defaultMaxRtrSolicitationDelay - } - - if c.MaxTempAddrValidLifetime < MinMaxTempAddrValidLifetime { - c.MaxTempAddrValidLifetime = MinMaxTempAddrValidLifetime - } - - if c.MaxTempAddrPreferredLifetime < MinMaxTempAddrPreferredLifetime || c.MaxTempAddrPreferredLifetime > c.MaxTempAddrValidLifetime { - c.MaxTempAddrPreferredLifetime = MinMaxTempAddrPreferredLifetime - } - - if c.RegenAdvanceDuration < minRegenAdvanceDuration { - c.RegenAdvanceDuration = minRegenAdvanceDuration - } -} - -// ndpState is the per-interface NDP state. -type ndpState struct { - // The NIC this ndpState is for. - nic *NIC - - // configs is the per-interface NDP configurations. - configs NDPConfigurations - - // The DAD state to send the next NS message, or resolve the address. - dad map[tcpip.Address]dadState - - // The default routers discovered through Router Advertisements. - defaultRouters map[tcpip.Address]defaultRouterState - - rtrSolicit struct { - // The timer used to send the next router solicitation message. - timer tcpip.Timer - - // Used to let the Router Solicitation timer know that it has been stopped. - // - // Must only be read from or written to while protected by the lock of - // the NIC this ndpState is associated with. MUST be set when the timer is - // set. - done *bool - } - - // The on-link prefixes discovered through Router Advertisements' Prefix - // Information option. - onLinkPrefixes map[tcpip.Subnet]onLinkPrefixState - - // The SLAAC prefixes discovered through Router Advertisements' Prefix - // Information option. - slaacPrefixes map[tcpip.Subnet]slaacPrefixState - - // The last learned DHCPv6 configuration from an NDP RA. - dhcpv6Configuration DHCPv6ConfigurationFromNDPRA - - // temporaryIIDHistory is the history value used to generate a new temporary - // IID. - temporaryIIDHistory [header.IIDSize]byte - - // temporaryAddressDesyncFactor is the preferred lifetime's desync factor for - // temporary SLAAC addresses. - temporaryAddressDesyncFactor time.Duration -} - -// dadState holds the Duplicate Address Detection timer and channel to signal -// to the DAD goroutine that DAD should stop. -type dadState struct { - // The DAD timer to send the next NS message, or resolve the address. - timer tcpip.Timer - - // Used to let the DAD timer know that it has been stopped. - // - // Must only be read from or written to while protected by the lock of - // the NIC this dadState is associated with. - done *bool -} - -// defaultRouterState holds data associated with a default router discovered by -// a Router Advertisement (RA). -type defaultRouterState struct { - // Job to invalidate the default router. - // - // Must not be nil. - invalidationJob *tcpip.Job -} - -// onLinkPrefixState holds data associated with an on-link prefix discovered by -// a Router Advertisement's Prefix Information option (PI) when the NDP -// configurations was configured to do so. -type onLinkPrefixState struct { - // Job to invalidate the on-link prefix. - // - // Must not be nil. - invalidationJob *tcpip.Job -} - -// tempSLAACAddrState holds state associated with a temporary SLAAC address. -type tempSLAACAddrState struct { - // Job to deprecate the temporary SLAAC address. - // - // Must not be nil. - deprecationJob *tcpip.Job - - // Job to invalidate the temporary SLAAC address. - // - // Must not be nil. - invalidationJob *tcpip.Job - - // Job to regenerate the temporary SLAAC address. - // - // Must not be nil. - regenJob *tcpip.Job - - createdAt time.Time - - // The address's endpoint. - // - // Must not be nil. - ref *referencedNetworkEndpoint - - // Has a new temporary SLAAC address already been regenerated? - regenerated bool -} - -// slaacPrefixState holds state associated with a SLAAC prefix. -type slaacPrefixState struct { - // Job to deprecate the prefix. - // - // Must not be nil. - deprecationJob *tcpip.Job - - // Job to invalidate the prefix. - // - // Must not be nil. - invalidationJob *tcpip.Job - - // Nonzero only when the address is not valid forever. - validUntil time.Time - - // Nonzero only when the address is not preferred forever. - preferredUntil time.Time - - // State associated with the stable address generated for the prefix. - stableAddr struct { - // The address's endpoint. - // - // May only be nil when the address is being (re-)generated. Otherwise, - // must not be nil as all SLAAC prefixes must have a stable address. - ref *referencedNetworkEndpoint - - // The number of times an address has been generated locally where the NIC - // already had the generated address. - localGenerationFailures uint8 - } - - // The temporary (short-lived) addresses generated for the SLAAC prefix. - tempAddrs map[tcpip.Address]tempSLAACAddrState - - // The next two fields are used by both stable and temporary addresses - // generated for a SLAAC prefix. This is safe as only 1 address will be - // in the generation and DAD process at any time. That is, no two addresses - // will be generated at the same time for a given SLAAC prefix. - - // The number of times an address has been generated and added to the NIC. - // - // Addresses may be regenerated in reseponse to a DAD conflicts. - generationAttempts uint8 - - // The maximum number of times to attempt regeneration of a SLAAC address - // in response to DAD conflicts. - maxGenerationAttempts uint8 -} - -// startDuplicateAddressDetection performs Duplicate Address Detection. -// -// This function must only be called by IPv6 addresses that are currently -// tentative. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, ref *referencedNetworkEndpoint) *tcpip.Error { - // addr must be a valid unicast IPv6 address. - if !header.IsV6UnicastAddress(addr) { - return tcpip.ErrAddressFamilyNotSupported - } - - if ref.getKind() != permanentTentative { - // The endpoint should be marked as tentative since we are starting DAD. - panic(fmt.Sprintf("ndpdad: addr %s is not tentative on NIC(%d)", addr, ndp.nic.ID())) - } - - // Should not attempt to perform DAD on an address that is currently in the - // DAD process. - if _, ok := ndp.dad[addr]; ok { - // Should never happen because we should only ever call this function for - // newly created addresses. If we attemped to "add" an address that already - // existed, we would get an error since we attempted to add a duplicate - // address, or its reference count would have been increased without doing - // the work that would have been done for an address that was brand new. - // See NIC.addAddressLocked. - panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.nic.ID())) - } - - remaining := ndp.configs.DupAddrDetectTransmits - if remaining == 0 { - ref.setKind(permanent) - - // Consider DAD to have resolved even if no DAD messages were actually - // transmitted. - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, true, nil) - } - - return nil - } - - var done bool - var timer tcpip.Timer - // We initially start a timer to fire immediately because some of the DAD work - // cannot be done while holding the NIC's lock. This is effectively the same - // as starting a goroutine but we use a timer that fires immediately so we can - // reset it for the next DAD iteration. - timer = ndp.nic.stack.Clock().AfterFunc(0, func() { - ndp.nic.mu.Lock() - defer ndp.nic.mu.Unlock() - - if done { - // If we reach this point, it means that the DAD timer fired after - // another goroutine already obtained the NIC lock and stopped DAD - // before this function obtained the NIC lock. Simply return here and do - // nothing further. - return - } - - if ref.getKind() != permanentTentative { - // The endpoint should still be marked as tentative since we are still - // performing DAD on it. - panic(fmt.Sprintf("ndpdad: addr %s is no longer tentative on NIC(%d)", addr, ndp.nic.ID())) - } - - dadDone := remaining == 0 - - var err *tcpip.Error - if !dadDone { - // Use the unspecified address as the source address when performing DAD. - ref := ndp.nic.getRefOrCreateTempLocked(header.IPv6ProtocolNumber, header.IPv6Any, NeverPrimaryEndpoint) - - // Do not hold the lock when sending packets which may be a long running - // task or may block link address resolution. We know this is safe - // because immediately after obtaining the lock again, we check if DAD - // has been stopped before doing any work with the NIC. Note, DAD would be - // stopped if the NIC was disabled or removed, or if the address was - // removed. - ndp.nic.mu.Unlock() - err = ndp.sendDADPacket(addr, ref) - ndp.nic.mu.Lock() - } - - if done { - // If we reach this point, it means that DAD was stopped after we released - // the NIC's read lock and before we obtained the write lock. - return - } - - if dadDone { - // DAD has resolved. - ref.setKind(permanent) - } else if err == nil { - // DAD is not done and we had no errors when sending the last NDP NS, - // schedule the next DAD timer. - remaining-- - timer.Reset(ndp.nic.stack.ndpConfigs.RetransmitTimer) - return - } - - // At this point we know that either DAD is done or we hit an error sending - // the last NDP NS. Either way, clean up addr's DAD state and let the - // integrator know DAD has completed. - delete(ndp.dad, addr) - - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, dadDone, err) - } - - // If DAD resolved for a stable SLAAC address, attempt generation of a - // temporary SLAAC address. - if dadDone && ref.configType == slaac { - // Reset the generation attempts counter as we are starting the generation - // of a new address for the SLAAC prefix. - ndp.regenerateTempSLAACAddr(ref.addrWithPrefix().Subnet(), true /* resetGenAttempts */) - } - }) - - ndp.dad[addr] = dadState{ - timer: timer, - done: &done, - } - - return nil -} - -// sendDADPacket sends a NS message to see if any nodes on ndp's NIC's link owns -// addr. -// -// addr must be a tentative IPv6 address on ndp's NIC. -// -// The NIC ndp belongs to MUST NOT be locked. -func (ndp *ndpState) sendDADPacket(addr tcpip.Address, ref *referencedNetworkEndpoint) *tcpip.Error { - snmc := header.SolicitedNodeAddr(addr) - - r := makeRoute(header.IPv6ProtocolNumber, ref.address(), snmc, ndp.nic.linkEP.LinkAddress(), ref, false, false) - defer r.Release() - - // Route should resolve immediately since snmc is a multicast address so a - // remote link address can be calculated without a resolution process. - if c, err := r.Resolve(nil); err != nil { - // Do not consider the NIC being unknown or disabled as a fatal error. - // Since this method is required to be called when the NIC is not locked, - // the NIC could have been disabled or removed by another goroutine. - if err == tcpip.ErrUnknownNICID || err != tcpip.ErrInvalidEndpointState { - return err - } - - panic(fmt.Sprintf("ndp: error when resolving route to send NDP NS for DAD (%s -> %s on NIC(%d)): %s", header.IPv6Any, snmc, ndp.nic.ID(), err)) - } else if c != nil { - panic(fmt.Sprintf("ndp: route resolution not immediate for route to send NDP NS for DAD (%s -> %s on NIC(%d))", header.IPv6Any, snmc, ndp.nic.ID())) - } - - icmpData := header.ICMPv6(buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize)) - icmpData.SetType(header.ICMPv6NeighborSolicit) - ns := header.NDPNeighborSolicit(icmpData.NDPPayload()) - ns.SetTargetAddress(addr) - icmpData.SetChecksum(header.ICMPv6Checksum(icmpData, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{})) - - pkt := NewPacketBuffer(PacketBufferOptions{ - ReserveHeaderBytes: int(r.MaxHeaderLength()), - Data: buffer.View(icmpData).ToVectorisedView(), - }) - - sent := r.Stats().ICMP.V6PacketsSent - if err := r.WritePacket(nil, - NetworkHeaderParams{ - Protocol: header.ICMPv6ProtocolNumber, - TTL: header.NDPHopLimit, - TOS: DefaultTOS, - }, pkt, - ); err != nil { - sent.Dropped.Increment() - return err - } - sent.NeighborSolicit.Increment() - - return nil -} - -// stopDuplicateAddressDetection ends a running Duplicate Address Detection -// process. Note, this may leave the DAD process for a tentative address in -// such a state forever, unless some other external event resolves the DAD -// process (receiving an NA from the true owner of addr, or an NS for addr -// (implying another node is attempting to use addr)). It is up to the caller -// of this function to handle such a scenario. Normally, addr will be removed -// from n right after this function returns or the address successfully -// resolved. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address) { - dad, ok := ndp.dad[addr] - if !ok { - // Not currently performing DAD on addr, just return. - return - } - - if dad.timer != nil { - dad.timer.Stop() - dad.timer = nil - - *dad.done = true - dad.done = nil - } - - delete(ndp.dad, addr) - - // Let the integrator know DAD did not resolve. - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, false, nil) - } -} - -// handleRA handles a Router Advertisement message that arrived on the NIC -// this ndp is for. Does nothing if the NIC is configured to not handle RAs. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) { - // Is the NIC configured to handle RAs at all? - // - // Currently, the stack does not determine router interface status on a - // per-interface basis; it is a stack-wide configuration, so we check - // stack's forwarding flag to determine if the NIC is a routing - // interface. - if !ndp.configs.HandleRAs || ndp.nic.stack.Forwarding(header.IPv6ProtocolNumber) { - return - } - - // Only worry about the DHCPv6 configuration if we have an NDPDispatcher as we - // only inform the dispatcher on configuration changes. We do nothing else - // with the information. - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - var configuration DHCPv6ConfigurationFromNDPRA - switch { - case ra.ManagedAddrConfFlag(): - configuration = DHCPv6ManagedAddress - - case ra.OtherConfFlag(): - configuration = DHCPv6OtherConfigurations - - default: - configuration = DHCPv6NoConfiguration - } - - if ndp.dhcpv6Configuration != configuration { - ndp.dhcpv6Configuration = configuration - ndpDisp.OnDHCPv6Configuration(ndp.nic.ID(), configuration) - } - } - - // Is the NIC configured to discover default routers? - if ndp.configs.DiscoverDefaultRouters { - rtr, ok := ndp.defaultRouters[ip] - rl := ra.RouterLifetime() - switch { - case !ok && rl != 0: - // This is a new default router we are discovering. - // - // Only remember it if we currently know about less than - // MaxDiscoveredDefaultRouters routers. - if len(ndp.defaultRouters) < MaxDiscoveredDefaultRouters { - ndp.rememberDefaultRouter(ip, rl) - } - - case ok && rl != 0: - // This is an already discovered default router. Update - // the invalidation job. - rtr.invalidationJob.Cancel() - rtr.invalidationJob.Schedule(rl) - ndp.defaultRouters[ip] = rtr - - case ok && rl == 0: - // We know about the router but it is no longer to be - // used as a default router so invalidate it. - ndp.invalidateDefaultRouter(ip) - } - } - - // TODO(b/141556115): Do (RetransTimer, ReachableTime)) Parameter - // Discovery. - - // We know the options is valid as far as wire format is concerned since - // we got the Router Advertisement, as documented by this fn. Given this - // we do not check the iterator for errors on calls to Next. - it, _ := ra.Options().Iter(false) - for opt, done, _ := it.Next(); !done; opt, done, _ = it.Next() { - switch opt := opt.(type) { - case header.NDPRecursiveDNSServer: - if ndp.nic.stack.ndpDisp == nil { - continue - } - - addrs, _ := opt.Addresses() - ndp.nic.stack.ndpDisp.OnRecursiveDNSServerOption(ndp.nic.ID(), addrs, opt.Lifetime()) - - case header.NDPDNSSearchList: - if ndp.nic.stack.ndpDisp == nil { - continue - } - - domainNames, _ := opt.DomainNames() - ndp.nic.stack.ndpDisp.OnDNSSearchListOption(ndp.nic.ID(), domainNames, opt.Lifetime()) - - case header.NDPPrefixInformation: - prefix := opt.Subnet() - - // Is the prefix a link-local? - if header.IsV6LinkLocalAddress(prefix.ID()) { - // ...Yes, skip as per RFC 4861 section 6.3.4, - // and RFC 4862 section 5.5.3.b (for SLAAC). - continue - } - - // Is the Prefix Length 0? - if prefix.Prefix() == 0 { - // ...Yes, skip as this is an invalid prefix - // as all IPv6 addresses cannot be on-link. - continue - } - - if opt.OnLinkFlag() { - ndp.handleOnLinkPrefixInformation(opt) - } - - if opt.AutonomousAddressConfigurationFlag() { - ndp.handleAutonomousPrefixInformation(opt) - } - } - - // TODO(b/141556115): Do (MTU) Parameter Discovery. - } -} - -// invalidateDefaultRouter invalidates a discovered default router. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) invalidateDefaultRouter(ip tcpip.Address) { - rtr, ok := ndp.defaultRouters[ip] - - // Is the router still discovered? - if !ok { - // ...Nope, do nothing further. - return - } - - rtr.invalidationJob.Cancel() - delete(ndp.defaultRouters, ip) - - // Let the integrator know a discovered default router is invalidated. - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - ndpDisp.OnDefaultRouterInvalidated(ndp.nic.ID(), ip) - } -} - -// rememberDefaultRouter remembers a newly discovered default router with IPv6 -// link-local address ip with lifetime rl. -// -// The router identified by ip MUST NOT already be known by the NIC. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) rememberDefaultRouter(ip tcpip.Address, rl time.Duration) { - ndpDisp := ndp.nic.stack.ndpDisp - if ndpDisp == nil { - return - } - - // Inform the integrator when we discovered a default router. - if !ndpDisp.OnDefaultRouterDiscovered(ndp.nic.ID(), ip) { - // Informed by the integrator to not remember the router, do - // nothing further. - return - } - - state := defaultRouterState{ - invalidationJob: ndp.nic.stack.newJob(&ndp.nic.mu, func() { - ndp.invalidateDefaultRouter(ip) - }), - } - - state.invalidationJob.Schedule(rl) - - ndp.defaultRouters[ip] = state -} - -// rememberOnLinkPrefix remembers a newly discovered on-link prefix with IPv6 -// address with prefix prefix with lifetime l. -// -// The prefix identified by prefix MUST NOT already be known. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) rememberOnLinkPrefix(prefix tcpip.Subnet, l time.Duration) { - ndpDisp := ndp.nic.stack.ndpDisp - if ndpDisp == nil { - return - } - - // Inform the integrator when we discovered an on-link prefix. - if !ndpDisp.OnOnLinkPrefixDiscovered(ndp.nic.ID(), prefix) { - // Informed by the integrator to not remember the prefix, do - // nothing further. - return - } - - state := onLinkPrefixState{ - invalidationJob: ndp.nic.stack.newJob(&ndp.nic.mu, func() { - ndp.invalidateOnLinkPrefix(prefix) - }), - } - - if l < header.NDPInfiniteLifetime { - state.invalidationJob.Schedule(l) - } - - ndp.onLinkPrefixes[prefix] = state -} - -// invalidateOnLinkPrefix invalidates a discovered on-link prefix. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) invalidateOnLinkPrefix(prefix tcpip.Subnet) { - s, ok := ndp.onLinkPrefixes[prefix] - - // Is the on-link prefix still discovered? - if !ok { - // ...Nope, do nothing further. - return - } - - s.invalidationJob.Cancel() - delete(ndp.onLinkPrefixes, prefix) - - // Let the integrator know a discovered on-link prefix is invalidated. - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - ndpDisp.OnOnLinkPrefixInvalidated(ndp.nic.ID(), prefix) - } -} - -// handleOnLinkPrefixInformation handles a Prefix Information option with -// its on-link flag set, as per RFC 4861 section 6.3.4. -// -// handleOnLinkPrefixInformation assumes that the prefix this pi is for is -// not the link-local prefix and the on-link flag is set. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) handleOnLinkPrefixInformation(pi header.NDPPrefixInformation) { - prefix := pi.Subnet() - prefixState, ok := ndp.onLinkPrefixes[prefix] - vl := pi.ValidLifetime() - - if !ok && vl == 0 { - // Don't know about this prefix but it has a zero valid - // lifetime, so just ignore. - return - } - - if !ok && vl != 0 { - // This is a new on-link prefix we are discovering - // - // Only remember it if we currently know about less than - // MaxDiscoveredOnLinkPrefixes on-link prefixes. - if ndp.configs.DiscoverOnLinkPrefixes && len(ndp.onLinkPrefixes) < MaxDiscoveredOnLinkPrefixes { - ndp.rememberOnLinkPrefix(prefix, vl) - } - return - } - - if ok && vl == 0 { - // We know about the on-link prefix, but it is - // no longer to be considered on-link, so - // invalidate it. - ndp.invalidateOnLinkPrefix(prefix) - return - } - - // This is an already discovered on-link prefix with a - // new non-zero valid lifetime. - // - // Update the invalidation job. - - prefixState.invalidationJob.Cancel() - - if vl < header.NDPInfiniteLifetime { - // Prefix is valid for a finite lifetime, schedule the job to execute after - // the new valid lifetime. - prefixState.invalidationJob.Schedule(vl) - } - - ndp.onLinkPrefixes[prefix] = prefixState -} - -// handleAutonomousPrefixInformation handles a Prefix Information option with -// its autonomous flag set, as per RFC 4862 section 5.5.3. -// -// handleAutonomousPrefixInformation assumes that the prefix this pi is for is -// not the link-local prefix and the autonomous flag is set. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) handleAutonomousPrefixInformation(pi header.NDPPrefixInformation) { - vl := pi.ValidLifetime() - pl := pi.PreferredLifetime() - - // If the preferred lifetime is greater than the valid lifetime, - // silently ignore the Prefix Information option, as per RFC 4862 - // section 5.5.3.c. - if pl > vl { - return - } - - prefix := pi.Subnet() - - // Check if we already maintain SLAAC state for prefix. - if state, ok := ndp.slaacPrefixes[prefix]; ok { - // As per RFC 4862 section 5.5.3.e, refresh prefix's SLAAC lifetimes. - ndp.refreshSLAACPrefixLifetimes(prefix, &state, pl, vl) - ndp.slaacPrefixes[prefix] = state - return - } - - // prefix is a new SLAAC prefix. Do the work as outlined by RFC 4862 section - // 5.5.3.d if ndp is configured to auto-generate new addresses via SLAAC. - if !ndp.configs.AutoGenGlobalAddresses { - return - } - - ndp.doSLAAC(prefix, pl, vl) -} - -// doSLAAC generates a new SLAAC address with the provided lifetimes -// for prefix. -// -// pl is the new preferred lifetime. vl is the new valid lifetime. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) { - // If we do not already have an address for this prefix and the valid - // lifetime is 0, no need to do anything further, as per RFC 4862 - // section 5.5.3.d. - if vl == 0 { - return - } - - // Make sure the prefix is valid (as far as its length is concerned) to - // generate a valid IPv6 address from an interface identifier (IID), as - // per RFC 4862 sectiion 5.5.3.d. - if prefix.Prefix() != validPrefixLenForAutoGen { - return - } - - state := slaacPrefixState{ - deprecationJob: ndp.nic.stack.newJob(&ndp.nic.mu, func() { - state, ok := ndp.slaacPrefixes[prefix] - if !ok { - panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the deprecated SLAAC prefix %s", prefix)) - } - - ndp.deprecateSLAACAddress(state.stableAddr.ref) - }), - invalidationJob: ndp.nic.stack.newJob(&ndp.nic.mu, func() { - state, ok := ndp.slaacPrefixes[prefix] - if !ok { - panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the invalidated SLAAC prefix %s", prefix)) - } - - ndp.invalidateSLAACPrefix(prefix, state) - }), - tempAddrs: make(map[tcpip.Address]tempSLAACAddrState), - maxGenerationAttempts: ndp.configs.AutoGenAddressConflictRetries + 1, - } - - now := time.Now() - - // The time an address is preferred until is needed to properly generate the - // address. - if pl < header.NDPInfiniteLifetime { - state.preferredUntil = now.Add(pl) - } - - if !ndp.generateSLAACAddr(prefix, &state) { - // We were unable to generate an address for the prefix, we do not nothing - // further as there is no reason to maintain state or jobs for a prefix we - // do not have an address for. - return - } - - // Setup the initial jobs to deprecate and invalidate prefix. - - if pl < header.NDPInfiniteLifetime && pl != 0 { - state.deprecationJob.Schedule(pl) - } - - if vl < header.NDPInfiniteLifetime { - state.invalidationJob.Schedule(vl) - state.validUntil = now.Add(vl) - } - - // If the address is assigned (DAD resolved), generate a temporary address. - if state.stableAddr.ref.getKind() == permanent { - // Reset the generation attempts counter as we are starting the generation - // of a new address for the SLAAC prefix. - ndp.generateTempSLAACAddr(prefix, &state, true /* resetGenAttempts */) - } - - ndp.slaacPrefixes[prefix] = state -} - -// addSLAACAddr adds a SLAAC address to the NIC. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) addSLAACAddr(addr tcpip.AddressWithPrefix, configType networkEndpointConfigType, deprecated bool) *referencedNetworkEndpoint { - // Inform the integrator that we have a new SLAAC address. - ndpDisp := ndp.nic.stack.ndpDisp - if ndpDisp == nil { - return nil - } - - if !ndpDisp.OnAutoGenAddress(ndp.nic.ID(), addr) { - // Informed by the integrator not to add the address. - return nil - } - - protocolAddr := tcpip.ProtocolAddress{ - Protocol: header.IPv6ProtocolNumber, - AddressWithPrefix: addr, - } - - ref, err := ndp.nic.addAddressLocked(protocolAddr, FirstPrimaryEndpoint, permanent, configType, deprecated) - if err != nil { - panic(fmt.Sprintf("ndp: error when adding SLAAC address %+v: %s", protocolAddr, err)) - } - - return ref -} - -// generateSLAACAddr generates a SLAAC address for prefix. -// -// Returns true if an address was successfully generated. -// -// Panics if the prefix is not a SLAAC prefix or it already has an address. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixState) bool { - if r := state.stableAddr.ref; r != nil { - panic(fmt.Sprintf("ndp: SLAAC prefix %s already has a permenant address %s", prefix, r.addrWithPrefix())) - } - - // If we have already reached the maximum address generation attempts for the - // prefix, do not generate another address. - if state.generationAttempts == state.maxGenerationAttempts { - return false - } - - var generatedAddr tcpip.AddressWithPrefix - addrBytes := []byte(prefix.ID()) - - for i := 0; ; i++ { - // If we were unable to generate an address after the maximum SLAAC address - // local regeneration attempts, do nothing further. - if i == maxSLAACAddrLocalRegenAttempts { - return false - } - - dadCounter := state.generationAttempts + state.stableAddr.localGenerationFailures - if oIID := ndp.nic.stack.opaqueIIDOpts; oIID.NICNameFromID != nil { - addrBytes = header.AppendOpaqueInterfaceIdentifier( - addrBytes[:header.IIDOffsetInIPv6Address], - prefix, - oIID.NICNameFromID(ndp.nic.ID(), ndp.nic.name), - dadCounter, - oIID.SecretKey, - ) - } else if dadCounter == 0 { - // Modified-EUI64 based IIDs have no way to resolve DAD conflicts, so if - // the DAD counter is non-zero, we cannot use this method. - // - // Only attempt to generate an interface-specific IID if we have a valid - // link address. - // - // TODO(b/141011931): Validate a LinkEndpoint's link address (provided by - // LinkEndpoint.LinkAddress) before reaching this point. - linkAddr := ndp.nic.linkEP.LinkAddress() - if !header.IsValidUnicastEthernetAddress(linkAddr) { - return false - } - - // Generate an address within prefix from the modified EUI-64 of ndp's - // NIC's Ethernet MAC address. - header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, addrBytes[header.IIDOffsetInIPv6Address:]) - } else { - // We have no way to regenerate an address in response to an address - // conflict when addresses are not generated with opaque IIDs. - return false - } - - generatedAddr = tcpip.AddressWithPrefix{ - Address: tcpip.Address(addrBytes), - PrefixLen: validPrefixLenForAutoGen, - } - - if !ndp.nic.hasPermanentAddrLocked(generatedAddr.Address) { - break - } - - state.stableAddr.localGenerationFailures++ - } - - if ref := ndp.addSLAACAddr(generatedAddr, slaac, time.Since(state.preferredUntil) >= 0 /* deprecated */); ref != nil { - state.stableAddr.ref = ref - state.generationAttempts++ - return true - } - - return false -} - -// regenerateSLAACAddr regenerates an address for a SLAAC prefix. -// -// If generating a new address for the prefix fails, the prefix will be -// invalidated. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) regenerateSLAACAddr(prefix tcpip.Subnet) { - state, ok := ndp.slaacPrefixes[prefix] - if !ok { - panic(fmt.Sprintf("ndp: SLAAC prefix state not found to regenerate address for %s", prefix)) - } - - if ndp.generateSLAACAddr(prefix, &state) { - ndp.slaacPrefixes[prefix] = state - return - } - - // We were unable to generate a permanent address for the SLAAC prefix so - // invalidate the prefix as there is no reason to maintain state for a - // SLAAC prefix we do not have an address for. - ndp.invalidateSLAACPrefix(prefix, state) -} - -// generateTempSLAACAddr generates a new temporary SLAAC address. -// -// If resetGenAttempts is true, the prefix's generation counter will be reset. -// -// Returns true if a new address was generated. -func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *slaacPrefixState, resetGenAttempts bool) bool { - // Are we configured to auto-generate new temporary global addresses for the - // prefix? - if !ndp.configs.AutoGenTempGlobalAddresses || prefix == header.IPv6LinkLocalPrefix.Subnet() { - return false - } - - if resetGenAttempts { - prefixState.generationAttempts = 0 - prefixState.maxGenerationAttempts = ndp.configs.AutoGenAddressConflictRetries + 1 - } - - // If we have already reached the maximum address generation attempts for the - // prefix, do not generate another address. - if prefixState.generationAttempts == prefixState.maxGenerationAttempts { - return false - } - - stableAddr := prefixState.stableAddr.ref.address() - now := time.Now() - - // As per RFC 4941 section 3.3 step 4, the valid lifetime of a temporary - // address is the lower of the valid lifetime of the stable address or the - // maximum temporary address valid lifetime. - vl := ndp.configs.MaxTempAddrValidLifetime - if prefixState.validUntil != (time.Time{}) { - if prefixVL := prefixState.validUntil.Sub(now); vl > prefixVL { - vl = prefixVL - } - } - - if vl <= 0 { - // Cannot create an address without a valid lifetime. - return false - } - - // As per RFC 4941 section 3.3 step 4, the preferred lifetime of a temporary - // address is the lower of the preferred lifetime of the stable address or the - // maximum temporary address preferred lifetime - the temporary address desync - // factor. - pl := ndp.configs.MaxTempAddrPreferredLifetime - ndp.temporaryAddressDesyncFactor - if prefixState.preferredUntil != (time.Time{}) { - if prefixPL := prefixState.preferredUntil.Sub(now); pl > prefixPL { - // Respect the preferred lifetime of the prefix, as per RFC 4941 section - // 3.3 step 4. - pl = prefixPL - } - } - - // As per RFC 4941 section 3.3 step 5, a temporary address is created only if - // the calculated preferred lifetime is greater than the advance regeneration - // duration. In particular, we MUST NOT create a temporary address with a zero - // Preferred Lifetime. - if pl <= ndp.configs.RegenAdvanceDuration { - return false - } - - // Attempt to generate a new address that is not already assigned to the NIC. - var generatedAddr tcpip.AddressWithPrefix - for i := 0; ; i++ { - // If we were unable to generate an address after the maximum SLAAC address - // local regeneration attempts, do nothing further. - if i == maxSLAACAddrLocalRegenAttempts { - return false - } - - generatedAddr = header.GenerateTempIPv6SLAACAddr(ndp.temporaryIIDHistory[:], stableAddr) - if !ndp.nic.hasPermanentAddrLocked(generatedAddr.Address) { - break - } - } - - // 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. - ref := ndp.addSLAACAddr(generatedAddr, slaacTemp, false /* deprecated */) - if ref == nil { - return false - } - - state := tempSLAACAddrState{ - deprecationJob: ndp.nic.stack.newJob(&ndp.nic.mu, func() { - prefixState, ok := ndp.slaacPrefixes[prefix] - if !ok { - panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for %s to deprecate temporary address %s", prefix, generatedAddr)) - } - - tempAddrState, ok := prefixState.tempAddrs[generatedAddr.Address] - if !ok { - panic(fmt.Sprintf("ndp: must have a tempAddr entry to deprecate temporary address %s", generatedAddr)) - } - - ndp.deprecateSLAACAddress(tempAddrState.ref) - }), - invalidationJob: ndp.nic.stack.newJob(&ndp.nic.mu, func() { - prefixState, ok := ndp.slaacPrefixes[prefix] - if !ok { - panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for %s to invalidate temporary address %s", prefix, generatedAddr)) - } - - tempAddrState, ok := prefixState.tempAddrs[generatedAddr.Address] - if !ok { - panic(fmt.Sprintf("ndp: must have a tempAddr entry to invalidate temporary address %s", generatedAddr)) - } - - ndp.invalidateTempSLAACAddr(prefixState.tempAddrs, generatedAddr.Address, tempAddrState) - }), - regenJob: ndp.nic.stack.newJob(&ndp.nic.mu, func() { - prefixState, ok := ndp.slaacPrefixes[prefix] - if !ok { - panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for %s to regenerate temporary address after %s", prefix, generatedAddr)) - } - - tempAddrState, ok := prefixState.tempAddrs[generatedAddr.Address] - if !ok { - panic(fmt.Sprintf("ndp: must have a tempAddr entry to regenerate temporary address after %s", generatedAddr)) - } - - // If an address has already been regenerated for this address, don't - // regenerate another address. - if tempAddrState.regenerated { - return - } - - // Reset the generation attempts counter as we are starting the generation - // of a new address for the SLAAC prefix. - tempAddrState.regenerated = ndp.generateTempSLAACAddr(prefix, &prefixState, true /* resetGenAttempts */) - prefixState.tempAddrs[generatedAddr.Address] = tempAddrState - ndp.slaacPrefixes[prefix] = prefixState - }), - createdAt: now, - ref: ref, - } - - state.deprecationJob.Schedule(pl) - state.invalidationJob.Schedule(vl) - state.regenJob.Schedule(pl - ndp.configs.RegenAdvanceDuration) - - prefixState.generationAttempts++ - prefixState.tempAddrs[generatedAddr.Address] = state - - return true -} - -// regenerateTempSLAACAddr regenerates a temporary address for a SLAAC prefix. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) regenerateTempSLAACAddr(prefix tcpip.Subnet, resetGenAttempts bool) { - state, ok := ndp.slaacPrefixes[prefix] - if !ok { - panic(fmt.Sprintf("ndp: SLAAC prefix state not found to regenerate temporary address for %s", prefix)) - } - - ndp.generateTempSLAACAddr(prefix, &state, resetGenAttempts) - ndp.slaacPrefixes[prefix] = state -} - -// refreshSLAACPrefixLifetimes refreshes the lifetimes of a SLAAC prefix. -// -// pl is the new preferred lifetime. vl is the new valid lifetime. -// -// The NIC 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.ref) - } else { - prefixState.stableAddr.ref.deprecated = false - } - - // If prefix was preferred for some finite lifetime before, cancel the - // deprecation job so it can be reset. - prefixState.deprecationJob.Cancel() - - now := time.Now() - - // Schedule the deprecation job if prefix has a finite preferred lifetime. - if pl < header.NDPInfiniteLifetime { - if !deprecated { - prefixState.deprecationJob.Schedule(pl) - } - prefixState.preferredUntil = now.Add(pl) - } else { - prefixState.preferredUntil = time.Time{} - } - - // As per RFC 4862 section 5.5.3.e, update the valid lifetime for prefix: - // - // 1) If the received Valid Lifetime is greater than 2 hours or greater than - // RemainingLifetime, set the valid lifetime of the prefix to the - // advertised Valid Lifetime. - // - // 2) If RemainingLifetime is less than or equal to 2 hours, ignore the - // advertised Valid Lifetime. - // - // 3) Otherwise, reset the valid lifetime of the prefix to 2 hours. - - if vl >= header.NDPInfiniteLifetime { - // Handle the infinite valid lifetime separately as we do not schedule a - // job in this case. - prefixState.invalidationJob.Cancel() - prefixState.validUntil = time.Time{} - } else { - var effectiveVl time.Duration - var rl time.Duration - - // If the prefix was originally set to be valid forever, assume the - // remaining time to be the maximum possible value. - if prefixState.validUntil == (time.Time{}) { - rl = header.NDPInfiniteLifetime - } else { - rl = time.Until(prefixState.validUntil) - } - - if vl > MinPrefixInformationValidLifetimeForUpdate || vl > rl { - effectiveVl = vl - } else if rl > MinPrefixInformationValidLifetimeForUpdate { - effectiveVl = MinPrefixInformationValidLifetimeForUpdate - } - - if effectiveVl != 0 { - prefixState.invalidationJob.Cancel() - prefixState.invalidationJob.Schedule(effectiveVl) - prefixState.validUntil = now.Add(effectiveVl) - } - } - - // If DAD is not yet complete on the stable address, there is no need to do - // work with temporary addresses. - if prefixState.stableAddr.ref.getKind() != permanent { - return - } - - // Note, we do not need to update the entries in the temporary address map - // after updating the jobs because the jobs are held as pointers. - var regenForAddr tcpip.Address - allAddressesRegenerated := true - for tempAddr, tempAddrState := range prefixState.tempAddrs { - // As per RFC 4941 section 3.3 step 4, the valid lifetime of a temporary - // address is the lower of the valid lifetime of the stable address or the - // maximum temporary address valid lifetime. Note, the valid lifetime of a - // temporary address is relative to the address's creation time. - validUntil := tempAddrState.createdAt.Add(ndp.configs.MaxTempAddrValidLifetime) - if prefixState.validUntil != (time.Time{}) && validUntil.Sub(prefixState.validUntil) > 0 { - validUntil = prefixState.validUntil - } - - // If the address is no longer valid, invalidate it immediately. Otherwise, - // reset the invalidation job. - newValidLifetime := validUntil.Sub(now) - if newValidLifetime <= 0 { - ndp.invalidateTempSLAACAddr(prefixState.tempAddrs, tempAddr, tempAddrState) - continue - } - tempAddrState.invalidationJob.Cancel() - tempAddrState.invalidationJob.Schedule(newValidLifetime) - - // As per RFC 4941 section 3.3 step 4, the preferred lifetime of a temporary - // address is the lower of the preferred lifetime of the stable address or - // the maximum temporary address preferred lifetime - the temporary address - // desync factor. Note, the preferred lifetime of a temporary address is - // relative to the address's creation time. - preferredUntil := tempAddrState.createdAt.Add(ndp.configs.MaxTempAddrPreferredLifetime - ndp.temporaryAddressDesyncFactor) - if prefixState.preferredUntil != (time.Time{}) && preferredUntil.Sub(prefixState.preferredUntil) > 0 { - preferredUntil = prefixState.preferredUntil - } - - // If the address is no longer preferred, deprecate it immediately. - // Otherwise, schedule the deprecation job again. - newPreferredLifetime := preferredUntil.Sub(now) - tempAddrState.deprecationJob.Cancel() - if newPreferredLifetime <= 0 { - ndp.deprecateSLAACAddress(tempAddrState.ref) - } else { - tempAddrState.ref.deprecated = false - tempAddrState.deprecationJob.Schedule(newPreferredLifetime) - } - - tempAddrState.regenJob.Cancel() - if tempAddrState.regenerated { - } else { - allAddressesRegenerated = false - - if newPreferredLifetime <= ndp.configs.RegenAdvanceDuration { - // The new preferred lifetime is less than the advance regeneration - // duration so regenerate an address for this temporary address - // immediately after we finish iterating over the temporary addresses. - regenForAddr = tempAddr - } else { - tempAddrState.regenJob.Schedule(newPreferredLifetime - ndp.configs.RegenAdvanceDuration) - } - } - } - - // Generate a new temporary address if all of the existing temporary addresses - // have been regenerated, or we need to immediately regenerate an address - // due to an update in preferred lifetime. - // - // If each temporay address has already been regenerated, no new temporary - // address will be generated. To ensure continuation of temporary SLAAC - // addresses, we manually try to regenerate an address here. - if len(regenForAddr) != 0 || allAddressesRegenerated { - // Reset the generation attempts counter as we are starting the generation - // of a new address for the SLAAC prefix. - if state, ok := prefixState.tempAddrs[regenForAddr]; ndp.generateTempSLAACAddr(prefix, prefixState, true /* resetGenAttempts */) && ok { - state.regenerated = true - prefixState.tempAddrs[regenForAddr] = state - } - } -} - -// deprecateSLAACAddress marks ref as deprecated and notifies the stack's NDP -// dispatcher that ref has been deprecated. -// -// deprecateSLAACAddress does nothing if ref is already deprecated. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) deprecateSLAACAddress(ref *referencedNetworkEndpoint) { - if ref.deprecated { - return - } - - ref.deprecated = true - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - ndpDisp.OnAutoGenAddressDeprecated(ndp.nic.ID(), ref.addrWithPrefix()) - } -} - -// invalidateSLAACPrefix invalidates a SLAAC prefix. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefixState) { - if r := state.stableAddr.ref; r != nil { - // Since we are already invalidating the prefix, do not invalidate the - // prefix when removing the address. - if err := ndp.nic.removePermanentIPv6EndpointLocked(r, false /* allowSLAACInvalidation */); err != nil { - panic(fmt.Sprintf("ndp: error removing stable SLAAC address %s: %s", r.addrWithPrefix(), err)) - } - } - - ndp.cleanupSLAACPrefixResources(prefix, state) -} - -// cleanupSLAACAddrResourcesAndNotify cleans up an invalidated SLAAC address's -// resources. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) cleanupSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPrefix, invalidatePrefix bool) { - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - ndpDisp.OnAutoGenAddressInvalidated(ndp.nic.ID(), addr) - } - - prefix := addr.Subnet() - state, ok := ndp.slaacPrefixes[prefix] - if !ok || state.stableAddr.ref == nil || addr.Address != state.stableAddr.ref.address() { - return - } - - if !invalidatePrefix { - // If the prefix is not being invalidated, disassociate the address from the - // prefix and do nothing further. - state.stableAddr.ref = nil - ndp.slaacPrefixes[prefix] = state - return - } - - ndp.cleanupSLAACPrefixResources(prefix, state) -} - -// cleanupSLAACPrefixResources cleans up a SLAAC prefix's jobs and entry. -// -// Panics if the SLAAC prefix is not known. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaacPrefixState) { - // Invalidate all temporary addresses. - for tempAddr, tempAddrState := range state.tempAddrs { - ndp.invalidateTempSLAACAddr(state.tempAddrs, tempAddr, tempAddrState) - } - - state.stableAddr.ref = nil - state.deprecationJob.Cancel() - state.invalidationJob.Cancel() - delete(ndp.slaacPrefixes, prefix) -} - -// invalidateTempSLAACAddr invalidates a temporary SLAAC address. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) invalidateTempSLAACAddr(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) { - // Since we are already invalidating the address, do not invalidate the - // address when removing the address. - if err := ndp.nic.removePermanentIPv6EndpointLocked(tempAddrState.ref, false /* allowSLAACInvalidation */); err != nil { - panic(fmt.Sprintf("error removing temporary SLAAC address %s: %s", tempAddrState.ref.addrWithPrefix(), err)) - } - - ndp.cleanupTempSLAACAddrResources(tempAddrs, tempAddr, tempAddrState) -} - -// cleanupTempSLAACAddrResourcesAndNotify cleans up an invalidated temporary -// SLAAC address's resources from ndp. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) cleanupTempSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPrefix, invalidateAddr bool) { - if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { - ndpDisp.OnAutoGenAddressInvalidated(ndp.nic.ID(), addr) - } - - if !invalidateAddr { - return - } - - prefix := addr.Subnet() - state, ok := ndp.slaacPrefixes[prefix] - if !ok { - panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry to clean up temp addr %s resources", addr)) - } - - tempAddrState, ok := state.tempAddrs[addr.Address] - if !ok { - panic(fmt.Sprintf("ndp: must have a tempAddr entry to clean up temp addr %s resources", addr)) - } - - ndp.cleanupTempSLAACAddrResources(state.tempAddrs, addr.Address, tempAddrState) -} - -// cleanupTempSLAACAddrResourcesAndNotify cleans up a temporary SLAAC address's -// jobs and entry. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) cleanupTempSLAACAddrResources(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) { - tempAddrState.deprecationJob.Cancel() - tempAddrState.invalidationJob.Cancel() - tempAddrState.regenJob.Cancel() - delete(tempAddrs, tempAddr) -} - -// cleanupState cleans up ndp's state. -// -// If hostOnly is true, then only host-specific state will be cleaned up. -// -// cleanupState MUST be called with hostOnly set to true when ndp's NIC is -// transitioning from a host to a router. This function will invalidate all -// discovered on-link prefixes, discovered routers, and auto-generated -// addresses. -// -// If hostOnly is true, then the link-local auto-generated address will not be -// invalidated as routers are also expected to generate a link-local address. -// -// The NIC that ndp belongs to MUST be locked. -func (ndp *ndpState) cleanupState(hostOnly bool) { - linkLocalSubnet := header.IPv6LinkLocalPrefix.Subnet() - linkLocalPrefixes := 0 - for prefix, state := range ndp.slaacPrefixes { - // RFC 4862 section 5 states that routers are also expected to generate a - // link-local address so we do not invalidate them if we are cleaning up - // host-only state. - if hostOnly && prefix == linkLocalSubnet { - linkLocalPrefixes++ - continue - } - - ndp.invalidateSLAACPrefix(prefix, state) - } - - if got := len(ndp.slaacPrefixes); got != linkLocalPrefixes { - panic(fmt.Sprintf("ndp: still have non-linklocal SLAAC prefixes after cleaning up; found = %d prefixes, of which %d are link-local", got, linkLocalPrefixes)) - } - - for prefix := range ndp.onLinkPrefixes { - ndp.invalidateOnLinkPrefix(prefix) - } - - if got := len(ndp.onLinkPrefixes); got != 0 { - panic(fmt.Sprintf("ndp: still have discovered on-link prefixes after cleaning up; found = %d", got)) - } - - for router := range ndp.defaultRouters { - ndp.invalidateDefaultRouter(router) - } - - if got := len(ndp.defaultRouters); got != 0 { - panic(fmt.Sprintf("ndp: still have discovered default routers after cleaning up; found = %d", got)) - } - - ndp.dhcpv6Configuration = 0 -} - -// startSolicitingRouters starts soliciting routers, as per RFC 4861 section -// 6.3.7. If routers are already being solicited, this function does nothing. -// -// The NIC ndp belongs to MUST be locked. -func (ndp *ndpState) startSolicitingRouters() { - if ndp.rtrSolicit.timer != nil { - // We are already soliciting routers. - return - } - - remaining := ndp.configs.MaxRtrSolicitations - if remaining == 0 { - return - } - - // Calculate the random delay before sending our first RS, as per RFC - // 4861 section 6.3.7. - var delay time.Duration - if ndp.configs.MaxRtrSolicitationDelay > 0 { - delay = time.Duration(rand.Int63n(int64(ndp.configs.MaxRtrSolicitationDelay))) - } - - var done bool - ndp.rtrSolicit.done = &done - ndp.rtrSolicit.timer = ndp.nic.stack.Clock().AfterFunc(delay, func() { - ndp.nic.mu.Lock() - if done { - // If we reach this point, it means that the RS timer fired after another - // goroutine already obtained the NIC lock and stopped solicitations. - // Simply return here and do nothing further. - ndp.nic.mu.Unlock() - return - } - - // As per RFC 4861 section 4.1, the source of the RS is an address assigned - // to the sending interface, or the unspecified address if no address is - // assigned to the sending interface. - ref := ndp.nic.primaryIPv6EndpointRLocked(header.IPv6AllRoutersMulticastAddress) - if ref == nil { - ref = ndp.nic.getRefOrCreateTempLocked(header.IPv6ProtocolNumber, header.IPv6Any, NeverPrimaryEndpoint) - } - ndp.nic.mu.Unlock() - - localAddr := ref.address() - r := makeRoute(header.IPv6ProtocolNumber, localAddr, header.IPv6AllRoutersMulticastAddress, ndp.nic.linkEP.LinkAddress(), ref, false, false) - defer r.Release() - - // Route should resolve immediately since - // header.IPv6AllRoutersMulticastAddress is a multicast address so a - // remote link address can be calculated without a resolution process. - if c, err := r.Resolve(nil); err != nil { - // Do not consider the NIC being unknown or disabled as a fatal error. - // Since this method is required to be called when the NIC is not locked, - // the NIC could have been disabled or removed by another goroutine. - if err == tcpip.ErrUnknownNICID || err == tcpip.ErrInvalidEndpointState { - return - } - - panic(fmt.Sprintf("ndp: error when resolving route to send NDP RS (%s -> %s on NIC(%d)): %s", header.IPv6Any, header.IPv6AllRoutersMulticastAddress, ndp.nic.ID(), err)) - } else if c != nil { - panic(fmt.Sprintf("ndp: route resolution not immediate for route to send NDP RS (%s -> %s on NIC(%d))", header.IPv6Any, header.IPv6AllRoutersMulticastAddress, ndp.nic.ID())) - } - - // As per RFC 4861 section 4.1, an NDP RS SHOULD include the source - // link-layer address option if the source address of the NDP RS is - // specified. This option MUST NOT be included if the source address is - // unspecified. - // - // TODO(b/141011931): Validate a LinkEndpoint's link address (provided by - // LinkEndpoint.LinkAddress) before reaching this point. - var optsSerializer header.NDPOptionsSerializer - if localAddr != header.IPv6Any && header.IsValidUnicastEthernetAddress(r.LocalLinkAddress) { - optsSerializer = header.NDPOptionsSerializer{ - header.NDPSourceLinkLayerAddressOption(r.LocalLinkAddress), - } - } - payloadSize := header.ICMPv6HeaderSize + header.NDPRSMinimumSize + int(optsSerializer.Length()) - icmpData := header.ICMPv6(buffer.NewView(payloadSize)) - icmpData.SetType(header.ICMPv6RouterSolicit) - rs := header.NDPRouterSolicit(icmpData.NDPPayload()) - rs.Options().Serialize(optsSerializer) - icmpData.SetChecksum(header.ICMPv6Checksum(icmpData, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{})) - - pkt := NewPacketBuffer(PacketBufferOptions{ - ReserveHeaderBytes: int(r.MaxHeaderLength()), - Data: buffer.View(icmpData).ToVectorisedView(), - }) - - sent := r.Stats().ICMP.V6PacketsSent - if err := r.WritePacket(nil, - NetworkHeaderParams{ - Protocol: header.ICMPv6ProtocolNumber, - TTL: header.NDPHopLimit, - TOS: DefaultTOS, - }, pkt, - ); err != nil { - sent.Dropped.Increment() - log.Printf("startSolicitingRouters: error writing NDP router solicit message on NIC(%d); err = %s", ndp.nic.ID(), err) - // Don't send any more messages if we had an error. - remaining = 0 - } else { - sent.RouterSolicit.Increment() - remaining-- - } - - ndp.nic.mu.Lock() - if done || remaining == 0 { - ndp.rtrSolicit.timer = nil - ndp.rtrSolicit.done = nil - } else if ndp.rtrSolicit.timer != nil { - // Note, we need to explicitly check to make sure that - // the timer field is not nil because if it was nil but - // we still reached this point, then we know the NIC - // was requested to stop soliciting routers so we don't - // need to send the next Router Solicitation message. - ndp.rtrSolicit.timer.Reset(ndp.configs.RtrSolicitationInterval) - } - ndp.nic.mu.Unlock() - }) - -} - -// stopSolicitingRouters stops soliciting routers. If routers are not currently -// being solicited, this function does nothing. -// -// The NIC ndp belongs to MUST be locked. -func (ndp *ndpState) stopSolicitingRouters() { - if ndp.rtrSolicit.timer == nil { - // Nothing to do. - return - } - - *ndp.rtrSolicit.done = true - ndp.rtrSolicit.timer.Stop() - ndp.rtrSolicit.timer = nil - ndp.rtrSolicit.done = nil -} - -// initializeTempAddrState initializes state related to temporary SLAAC -// addresses. -func (ndp *ndpState) initializeTempAddrState() { - header.InitialTempIID(ndp.temporaryIIDHistory[:], ndp.nic.stack.tempIIDSeed, ndp.nic.ID()) - - if MaxDesyncFactor != 0 { - ndp.temporaryAddressDesyncFactor = time.Duration(rand.Int63n(int64(MaxDesyncFactor))) - } -} |