// 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 // defaultRetransmitTimer is the default amount of time to wait between // sending NDP Neighbor solicitation messages. // // Default = 1s (from RFC 4861 section 10). defaultRetransmitTimer = time.Second // 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 // minimumRetransmitTimer is the minimum amount of time to wait between // sending NDP Neighbor solicitation messages. Note, RFC 4861 does // not impose a minimum Retransmit Timer, but we do here to make sure // the messages are not sent all at once. We also come to this value // because in the RetransmitTimer field of a Router Advertisement, a // value of 0 means unspecified, so the smallest valid value is 1. // Note, the unit of the RetransmitTimer field in the Router // Advertisement is milliseconds. minimumRetransmitTimer = time.Millisecond // 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 ) 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 ) // DHCPv6ConfigurationFromNDPRA is a configuration available via DHCPv6 that an // NDP Router Advertisement informed the Stack about. type DHCPv6ConfigurationFromNDPRA int const ( // DHCPv6NoConfiguration indicates that no configurations are available via // DHCPv6. DHCPv6NoConfiguration DHCPv6ConfigurationFromNDPRA = iota // 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. // // NDPDispatcher assumes that the initial configuration available by DHCPv6 is // DHCPv6NoConfiguration. // // 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 } // 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, } } // validate modifies an NDPConfigurations with valid values. If invalid values // are present in c, the corresponding default values will be used instead. // // If RetransmitTimer is less than minimumRetransmitTimer, then a value of // defaultRetransmitTimer will be used. // // If RtrSolicitationInterval is less than minimumRtrSolicitationInterval, then // a value of defaultRtrSolicitationInterval will be used. // // If MaxRtrSolicitationDelay is less than minimumMaxRtrSolicitationDelay, then // a value of defaultMaxRtrSolicitationDelay will be used. 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 } } // 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 // The timer used to send the next router solicitation message. rtrSolicitTimer *time.Timer // 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 } // 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 *time.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 { // Timer to invalidate the default router. // // May not be nil. invalidationTimer *tcpip.CancellableTimer } // 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 { // Timer to invalidate the on-link prefix. // // May not be nil. invalidationTimer *tcpip.CancellableTimer } // slaacPrefixState holds state associated with a SLAAC prefix. type slaacPrefixState struct { // Timer to deprecate the prefix. // // May not be nil. deprecationTimer *tcpip.CancellableTimer // Timer to invalidate the prefix. // // May not be nil. invalidationTimer *tcpip.CancellableTimer // Nonzero only when the address is not valid forever. validUntil time.Time // Nonzero only when the address is not preferred forever. preferredUntil time.Time // The prefix's permanent address endpoint. // // May only be nil when a SLAAC address is being (re-)generated. Otherwise, // must not be nil as all SLAAC prefixes must have a SLAAC address. ref *referencedNetworkEndpoint // The number of times a permanent address has been generated for the prefix. // // Addresses may be regenerated in reseponse to a DAD conflicts. generationAttempts uint8 // The maximum number of times to attempt regeneration of a permanent 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 *time.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 = time.AfterFunc(0, func() { ndp.nic.mu.RLock() 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. ndp.nic.mu.RUnlock() 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 ndp.nic.mu.RUnlock() var err *tcpip.Error if !dadDone { err = ndp.sendDADPacket(addr) } 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. ndp.nic.mu.Unlock() 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) ndp.nic.mu.Unlock() return } // At this point we know that either DAD is done or we hit an error sending // the last NDP NS. Either way, clean up addr's DAD state and let the // integrator know DAD has completed. delete(ndp.dad, addr) ndp.nic.mu.Unlock() if err != nil { log.Printf("ndpdad: error occured during DAD iteration for addr (%s) on NIC(%d); err = %s", addr, ndp.nic.ID(), err) } if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil { ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, dadDone, err) } }) 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. func (ndp *ndpState) sendDADPacket(addr tcpip.Address) *tcpip.Error { snmc := header.SolicitedNodeAddr(addr) // Use the unspecified address as the source address when performing DAD. ref := ndp.nic.getRefOrCreateTemp(header.IPv6ProtocolNumber, header.IPv6Any, NeverPrimaryEndpoint, forceSpoofing) r := makeRoute(header.IPv6ProtocolNumber, header.IPv6Any, 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 { 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())) } hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.ICMPv6NeighborSolicitMinimumSize) pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborSolicitMinimumSize)) pkt.SetType(header.ICMPv6NeighborSolicit) ns := header.NDPNeighborSolicit(pkt.NDPPayload()) ns.SetTargetAddress(addr) pkt.SetChecksum(header.ICMPv6Checksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{})) sent := r.Stats().ICMP.V6PacketsSent if err := r.WritePacket(nil, NetworkHeaderParams{ Protocol: header.ICMPv6ProtocolNumber, TTL: header.NDPHopLimit, TOS: DefaultTOS, }, PacketBuffer{Header: hdr}, ); 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 { 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 timer. rtr.invalidationTimer.StopLocked() rtr.invalidationTimer.Reset(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.invalidationTimer.StopLocked() 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{ invalidationTimer: tcpip.NewCancellableTimer(&ndp.nic.mu, func() { ndp.invalidateDefaultRouter(ip) }), } state.invalidationTimer.Reset(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{ invalidationTimer: tcpip.NewCancellableTimer(&ndp.nic.mu, func() { ndp.invalidateOnLinkPrefix(prefix) }), } if l < header.NDPInfiniteLifetime { state.invalidationTimer.Reset(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.invalidationTimer.StopLocked() 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 timer. prefixState.invalidationTimer.StopLocked() if vl < header.NDPInfiniteLifetime { // Prefix is valid for a finite lifetime, reset the timer to expire after // the new valid lifetime. prefixState.invalidationTimer.Reset(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 _, ok := ndp.slaacPrefixes[prefix]; ok { // As per RFC 4862 section 5.5.3.e, refresh prefix's SLAAC lifetimes. ndp.refreshSLAACPrefixLifetimes(prefix, pl, vl) 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{ deprecationTimer: tcpip.NewCancellableTimer(&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.ref) }), invalidationTimer: tcpip.NewCancellableTimer(&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) }), 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 timers for a prefix we // do not have an address for. return } // Setup the initial timers to deprecate and invalidate prefix. if pl < header.NDPInfiniteLifetime && pl != 0 { state.deprecationTimer.Reset(pl) } if vl < header.NDPInfiniteLifetime { state.invalidationTimer.Reset(vl) state.validUntil = now.Add(vl) } ndp.slaacPrefixes[prefix] = state } // 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.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 } addrBytes := []byte(prefix.ID()) if oIID := ndp.nic.stack.opaqueIIDOpts; oIID.NICNameFromID != nil { addrBytes = header.AppendOpaqueInterfaceIdentifier( addrBytes[:header.IIDOffsetInIPv6Address], prefix, oIID.NICNameFromID(ndp.nic.ID(), ndp.nic.name), state.generationAttempts, oIID.SecretKey, ) } else if state.generationAttempts == 0 { // 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 when addresses are not generated // with opaque IIDs. return false } generatedAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: tcpip.AddressWithPrefix{ Address: tcpip.Address(addrBytes), PrefixLen: validPrefixLenForAutoGen, }, } // If the nic already has this address, do nothing further. if ndp.nic.hasPermanentAddrLocked(generatedAddr.AddressWithPrefix.Address) { return false } // Inform the integrator that we have a new SLAAC address. ndpDisp := ndp.nic.stack.ndpDisp if ndpDisp == nil { return false } if !ndpDisp.OnAutoGenAddress(ndp.nic.ID(), generatedAddr.AddressWithPrefix) { // Informed by the integrator not to add the address. return false } deprecated := time.Since(state.preferredUntil) >= 0 ref, err := ndp.nic.addAddressLocked(generatedAddr, FirstPrimaryEndpoint, permanent, slaac, deprecated) if err != nil { panic(fmt.Sprintf("ndp: error when adding address %+v: %s", generatedAddr, err)) } state.generationAttempts++ state.ref = ref return true } // 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) } // 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, pl, vl time.Duration) { prefixState, ok := ndp.slaacPrefixes[prefix] if !ok { panic(fmt.Sprintf("ndp: SLAAC prefix state not found to refresh lifetimes for %s", prefix)) } defer func() { ndp.slaacPrefixes[prefix] = prefixState }() // If the preferred lifetime is zero, then the prefix should be deprecated. deprecated := pl == 0 if deprecated { ndp.deprecateSLAACAddress(prefixState.ref) } else { prefixState.ref.deprecated = false } // If prefix was preferred for some finite lifetime before, stop the // deprecation timer so it can be reset. prefixState.deprecationTimer.StopLocked() now := time.Now() // Reset the deprecation timer if prefix has a finite preferred lifetime. if pl < header.NDPInfiniteLifetime { if !deprecated { prefixState.deprecationTimer.Reset(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. // Handle the infinite valid lifetime separately as we do not keep a timer in // this case. if vl >= header.NDPInfiniteLifetime { prefixState.invalidationTimer.StopLocked() prefixState.validUntil = time.Time{} return } 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 { return } else { effectiveVl = MinPrefixInformationValidLifetimeForUpdate } prefixState.invalidationTimer.StopLocked() prefixState.invalidationTimer.Reset(effectiveVl) prefixState.validUntil = now.Add(effectiveVl) } // 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.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 /* allowSLAACPrefixInvalidation */); err != nil { panic(fmt.Sprintf("ndp: removePermanentIPv6EndpointLocked(%s, false): %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.ref == nil || addr.Address != state.ref.ep.ID().LocalAddress { return } if !invalidatePrefix { // If the prefix is not being invalidated, disassociate the address from the // prefix and do nothing further. state.ref = nil ndp.slaacPrefixes[prefix] = state return } ndp.cleanupSLAACPrefixResources(prefix, state) } // cleanupSLAACPrefixResources cleansup a SLAAC prefix's timers 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) { state.deprecationTimer.StopLocked() state.invalidationTimer.StopLocked() delete(ndp.slaacPrefixes, prefix) } // 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)) } } // 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.rtrSolicitTimer != 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))) } ndp.rtrSolicitTimer = time.AfterFunc(delay, func() { // 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.primaryIPv6Endpoint(header.IPv6AllRoutersMulticastAddress) if ref == nil { ref = ndp.nic.getRefOrCreateTemp(header.IPv6ProtocolNumber, header.IPv6Any, NeverPrimaryEndpoint, forceSpoofing) } localAddr := ref.ep.ID().LocalAddress 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 { 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()) hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + payloadSize) pkt := header.ICMPv6(hdr.Prepend(payloadSize)) pkt.SetType(header.ICMPv6RouterSolicit) rs := header.NDPRouterSolicit(pkt.NDPPayload()) rs.Options().Serialize(optsSerializer) pkt.SetChecksum(header.ICMPv6Checksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{})) sent := r.Stats().ICMP.V6PacketsSent if err := r.WritePacket(nil, NetworkHeaderParams{ Protocol: header.ICMPv6ProtocolNumber, TTL: header.NDPHopLimit, TOS: DefaultTOS, }, PacketBuffer{Header: hdr}, ); 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() defer ndp.nic.mu.Unlock() if remaining == 0 { ndp.rtrSolicitTimer = nil } else if ndp.rtrSolicitTimer != 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.rtrSolicitTimer.Reset(ndp.configs.RtrSolicitationInterval) } }) } // 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.rtrSolicitTimer == nil { // Nothing to do. return } ndp.rtrSolicitTimer.Stop() ndp.rtrSolicitTimer = nil }