mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
IPv6 endpoint, DAD, and protocol state lack checked mutex ownership. Annotate the fields and NDP/MLD helper contracts, adapting #7060 to the current named state and embedded mutexes. Alias the endpoint mutex paths through the initialized NDP and MLD backpointers. Record narrow checklocks exceptions where synchronous iteration preserves the caller's lock or DAD and job callbacks run under the supplied endpoint lock. Require a read lock for address acquisition, matching its callers and the addressable state implementation that owns exclusive creation. Co-authored-by: Hanzo Dev <[email protected]>
2036 lines
71 KiB
Go
2036 lines
71 KiB
Go
// 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 ipv6
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/internal/ip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
)
|
|
|
|
const (
|
|
// defaultMaxRtrSolicitations is the default number of Router
|
|
// Solicitation messages to send when an IPv6 endpoint 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 = HandlingRAsEnabledWhenForwardingDisabled
|
|
|
|
// defaultDiscoverDefaultRouters is the default configuration for
|
|
// whether or not to discover default routers from incoming Router
|
|
// Advertisements, as a host.
|
|
defaultDiscoverDefaultRouters = true
|
|
|
|
// defaultDiscoverMoreSpecificRoutes is the default configuration for
|
|
// whether or not to discover more-specific routes from incoming Router
|
|
// Advertisements, as a host.
|
|
defaultDiscoverMoreSpecificRoutes = 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
|
|
|
|
// MaxDiscoveredOffLinkRoutes is the maximum number of discovered off-link
|
|
// routes. The stack should stop discovering new off-link routes after
|
|
// this limit is reached.
|
|
//
|
|
// This value MUST be at minimum 2 as per RFC 4861 section 6.3.4, and
|
|
// SHOULD be more.
|
|
MaxDiscoveredOffLinkRoutes = 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
|
|
|
|
// MaxDiscoveredSLAACPrefixes is the maximum number of discovered
|
|
// SLAAC prefixes. The stack will stop discovering new SLAAC
|
|
// prefixes after discovering MaxDiscoveredSLAACPrefixes SLAAC prefixes.
|
|
MaxDiscoveredSLAACPrefixes = 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 an IPv6 endpoint-local conflict.
|
|
maxSLAACAddrLocalRegenAttempts = 10
|
|
|
|
// MinPrefixInformationValidLifetimeForUpdate is the minimum Valid
|
|
// Lifetime to update the valid lifetime of a generated address by
|
|
// SLAAC.
|
|
//
|
|
// Min = 2hrs.
|
|
MinPrefixInformationValidLifetimeForUpdate = 2 * time.Hour
|
|
|
|
// MaxDesyncFactor is the upper bound for the preferred lifetime's desync
|
|
// factor for temporary SLAAC addresses.
|
|
//
|
|
// 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 value guarantees that a temporary address is 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 value guarantees that a temporary address is valid for at least
|
|
// 2hrs if the SLAAC prefix is valid for at least that time.
|
|
MinMaxTempAddrValidLifetime = 2 * time.Hour
|
|
)
|
|
|
|
// NDPEndpoint is an endpoint that supports NDP.
|
|
type NDPEndpoint interface {
|
|
// SetNDPConfigurations sets the NDP configurations.
|
|
SetNDPConfigurations(NDPConfigurations)
|
|
|
|
// NDPConfigurations returns the NDP configurations.
|
|
NDPConfigurations() NDPConfigurations
|
|
}
|
|
|
|
// 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
|
|
// returns all available configuration information when serving addresses.
|
|
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 {
|
|
// OnDuplicateAddressDetectionResult is called when the DAD process for an
|
|
// address on a NIC completes.
|
|
//
|
|
// This function is not permitted to block indefinitely. This function
|
|
// is also not permitted to call into the stack.
|
|
OnDuplicateAddressDetectionResult(tcpip.NICID, tcpip.Address, stack.DADResult)
|
|
|
|
// OnOffLinkRouteUpdated is called when an off-link route is updated.
|
|
//
|
|
// This function is not permitted to block indefinitely. This function
|
|
// is also not permitted to call into the stack.
|
|
OnOffLinkRouteUpdated(tcpip.NICID, tcpip.Subnet, tcpip.Address, header.NDPRoutePreference)
|
|
|
|
// OnOffLinkRouteInvalidated is called when an off-link route is invalidated.
|
|
//
|
|
// This function is not permitted to block indefinitely. This function
|
|
// is also not permitted to call into the stack.
|
|
OnOffLinkRouteInvalidated(tcpip.NICID, tcpip.Subnet, tcpip.Address)
|
|
|
|
// OnOnLinkPrefixDiscovered is called when a new on-link prefix is discovered.
|
|
//
|
|
// This function is not permitted to block indefinitely. This function
|
|
// is also not permitted to call into the stack.
|
|
OnOnLinkPrefixDiscovered(tcpip.NICID, tcpip.Subnet)
|
|
|
|
// OnOnLinkPrefixInvalidated is 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(tcpip.NICID, tcpip.Subnet)
|
|
|
|
// OnAutoGenAddress is called when a new prefix with its autonomous address-
|
|
// configuration flag set is received and SLAAC was performed.
|
|
//
|
|
// This function is not permitted to block indefinitely. It must not
|
|
// call functions on the stack itself.
|
|
//
|
|
// If a non-nil AddressDispatcher is returned, events related to the address
|
|
// will be sent to the dispatcher.
|
|
OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) stack.AddressDispatcher
|
|
|
|
// OnAutoGenAddressDeprecated is called when an auto-generated address (SLAAC)
|
|
// is deprecated, but is still considered valid. Note, if an address is
|
|
// invalidated at the same time it is deprecated, the deprecation event may
|
|
// not be received.
|
|
//
|
|
// This function is not permitted to block indefinitely. It must not
|
|
// call functions on the stack itself.
|
|
OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix)
|
|
|
|
// OnAutoGenAddressInvalidated is called when an auto-generated address
|
|
// (SLAAC) is invalidated.
|
|
//
|
|
// This function is not permitted to block indefinitely. It must not
|
|
// call functions on the stack itself.
|
|
OnAutoGenAddressInvalidated(tcpip.NICID, tcpip.AddressWithPrefix)
|
|
|
|
// OnRecursiveDNSServerOption is called when the stack learns of DNS servers
|
|
// through NDP. Note, the addresses 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 the lifetime (it may be increased,
|
|
// decreased, or completely invalidated when the lifetime = 0).
|
|
//
|
|
// This function is not permitted to block indefinitely. It must not
|
|
// call functions on the stack itself.
|
|
OnRecursiveDNSServerOption(tcpip.NICID, []tcpip.Address, time.Duration)
|
|
|
|
// OnDNSSearchListOption is called when the stack learns of DNS search lists
|
|
// through NDP.
|
|
//
|
|
// 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 the lifetime (it may
|
|
// be increased, decreased or completely invalidated when the lifetime = 0.
|
|
OnDNSSearchListOption(tcpip.NICID, []string, time.Duration)
|
|
|
|
// OnDHCPv6Configuration is called with an updated configuration that is
|
|
// available via DHCPv6 for the passed NIC.
|
|
//
|
|
// This function is not permitted to block indefinitely. It must not
|
|
// call functions on the stack itself.
|
|
OnDHCPv6Configuration(tcpip.NICID, DHCPv6ConfigurationFromNDPRA)
|
|
}
|
|
|
|
var _ fmt.Stringer = HandleRAsConfiguration(0)
|
|
|
|
// HandleRAsConfiguration enumerates when RAs may be handled.
|
|
type HandleRAsConfiguration int
|
|
|
|
const (
|
|
// HandlingRAsDisabled indicates that Router Advertisements will not be
|
|
// handled.
|
|
HandlingRAsDisabled HandleRAsConfiguration = iota
|
|
|
|
// HandlingRAsEnabledWhenForwardingDisabled indicates that router
|
|
// advertisements will only be handled when forwarding is disabled.
|
|
HandlingRAsEnabledWhenForwardingDisabled
|
|
|
|
// HandlingRAsAlwaysEnabled indicates that Router Advertisements will always
|
|
// be handled, even when forwarding is enabled.
|
|
HandlingRAsAlwaysEnabled
|
|
)
|
|
|
|
// String implements fmt.Stringer.
|
|
func (c HandleRAsConfiguration) String() string {
|
|
switch c {
|
|
case HandlingRAsDisabled:
|
|
return "HandlingRAsDisabled"
|
|
case HandlingRAsEnabledWhenForwardingDisabled:
|
|
return "HandlingRAsEnabledWhenForwardingDisabled"
|
|
case HandlingRAsAlwaysEnabled:
|
|
return "HandlingRAsAlwaysEnabled"
|
|
default:
|
|
return fmt.Sprintf("HandleRAsConfiguration(%d)", c)
|
|
}
|
|
}
|
|
|
|
// enabled returns true iff Router Advertisements may be handled given the
|
|
// specified forwarding status.
|
|
func (c HandleRAsConfiguration) enabled(forwarding bool) bool {
|
|
switch c {
|
|
case HandlingRAsDisabled:
|
|
return false
|
|
case HandlingRAsEnabledWhenForwardingDisabled:
|
|
return !forwarding
|
|
case HandlingRAsAlwaysEnabled:
|
|
return true
|
|
default:
|
|
panic(fmt.Sprintf("unhandled HandleRAsConfiguration = %d", c))
|
|
}
|
|
}
|
|
|
|
// NDPConfigurations is the NDP configurations for the netstack.
|
|
//
|
|
// +stateify savable
|
|
type NDPConfigurations struct {
|
|
// The number of Router Solicitation messages to send when the IPv6 endpoint
|
|
// becomes enabled.
|
|
//
|
|
// Ignored unless configured to handle Router Advertisements.
|
|
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 is the configuration for when Router Advertisements should be
|
|
// handled.
|
|
HandleRAs HandleRAsConfiguration
|
|
|
|
// DiscoverDefaultRouters determines whether or not default routers are
|
|
// discovered from Router Advertisements, as per RFC 4861 section 6. This
|
|
// configuration is ignored if RAs will not be processed (see HandleRAs).
|
|
DiscoverDefaultRouters bool
|
|
|
|
// DiscoverMoreSpecificRoutes determines whether or not more specific routes
|
|
// are discovered from Router Advertisements, as per RFC 4191. This
|
|
// configuration is ignored if RAs will not be processed (see HandleRAs).
|
|
DiscoverMoreSpecificRoutes bool
|
|
|
|
// DiscoverOnLinkPrefixes determines whether or not on-link prefixes are
|
|
// discovered from Router Advertisements' Prefix Information option, as per
|
|
// RFC 4861 section 6. This configuration is ignored if RAs will not be
|
|
// processed (see HandleRAs).
|
|
DiscoverOnLinkPrefixes bool
|
|
|
|
// AutoGenGlobalAddresses determines whether or not an IPv6 endpoint performs
|
|
// SLAAC to auto-generate global SLAAC addresses in response to Prefix
|
|
// Information options, as per RFC 4862.
|
|
//
|
|
// 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 is made to resolve the conflict.
|
|
AutoGenAddressConflictRetries uint8
|
|
|
|
// AutoGenTempGlobalAddresses determines whether or not temporary SLAAC
|
|
// addresses are generated for an IPv6 endpoint as part of SLAAC privacy
|
|
// extensions, as per 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{
|
|
MaxRtrSolicitations: defaultMaxRtrSolicitations,
|
|
RtrSolicitationInterval: defaultRtrSolicitationInterval,
|
|
MaxRtrSolicitationDelay: defaultMaxRtrSolicitationDelay,
|
|
HandleRAs: defaultHandleRAs,
|
|
DiscoverDefaultRouters: defaultDiscoverDefaultRouters,
|
|
DiscoverMoreSpecificRoutes: defaultDiscoverMoreSpecificRoutes,
|
|
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 are used instead.
|
|
func (c *NDPConfigurations) validate() {
|
|
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
|
|
}
|
|
}
|
|
|
|
// +stateify savable
|
|
type timer struct {
|
|
// done indicates to the timer that the timer was stopped.
|
|
done *bool
|
|
|
|
timer tcpip.Timer `state:"nosave"`
|
|
}
|
|
|
|
// +stateify savable
|
|
type offLinkRoute struct {
|
|
dest tcpip.Subnet
|
|
router tcpip.Address
|
|
}
|
|
|
|
// ndpState is the per-Interface NDP state.
|
|
//
|
|
// +stateify savable
|
|
type ndpState struct {
|
|
// Do not allow overwriting this state.
|
|
_ sync.NoCopy `state:"nosave"`
|
|
|
|
// The IPv6 endpoint this ndpState is for.
|
|
ep *endpoint
|
|
|
|
// configs is the per-interface NDP configurations.
|
|
configs NDPConfigurations
|
|
|
|
// The DAD timers to send the next NS message, or resolve the address.
|
|
dad ip.DAD
|
|
|
|
// The off-link routes discovered through Router Advertisements.
|
|
offLinkRoutes map[offLinkRoute]offLinkRouteState
|
|
|
|
// rtrSolicitTimer is the timer used to send the next router solicitation
|
|
// message.
|
|
//
|
|
// rtrSolicitTimer is the zero value when NDP is not soliciting routers.
|
|
rtrSolicitTimer 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
|
|
|
|
// 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
|
|
}
|
|
|
|
// offLinkRouteState holds data associated with an off-link route discovered by
|
|
// a Router Advertisement (RA).
|
|
//
|
|
// +stateify savable
|
|
type offLinkRouteState struct {
|
|
prf header.NDPRoutePreference
|
|
|
|
// Job to invalidate the route.
|
|
//
|
|
// 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.
|
|
//
|
|
// +stateify savable
|
|
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.
|
|
//
|
|
// +stateify savable
|
|
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 tcpip.MonotonicTime
|
|
|
|
// The address's endpoint.
|
|
//
|
|
// Must not be nil.
|
|
addressEndpoint stack.AddressEndpoint
|
|
|
|
// Has a new temporary SLAAC address already been regenerated?
|
|
regenerated bool
|
|
}
|
|
|
|
// +stateify savable
|
|
type stableAddrState 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.
|
|
addressEndpoint stack.AddressEndpoint
|
|
|
|
// The number of times an address has been generated locally where the IPv6
|
|
// endpoint already had the generated address.
|
|
localGenerationFailures uint8
|
|
}
|
|
|
|
// slaacPrefixState holds state associated with a SLAAC prefix.
|
|
//
|
|
// +stateify savable
|
|
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
|
|
|
|
// nil iff the address is valid forever.
|
|
validUntil *tcpip.MonotonicTime
|
|
|
|
// nil iff the address is preferred forever.
|
|
preferredUntil *tcpip.MonotonicTime
|
|
|
|
// State associated with the stable address generated for the prefix.
|
|
stableAddr stableAddrState
|
|
|
|
// 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 is in the
|
|
// generation and DAD process at any time. That is, no two addresses are
|
|
// generated at the same time for a given SLAAC prefix.
|
|
|
|
// The number of times an address has been generated and added to the IPv6
|
|
// endpoint.
|
|
//
|
|
// 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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressEndpoint stack.AddressEndpoint) tcpip.Error {
|
|
// addr must be a valid unicast IPv6 address.
|
|
if !header.IsV6UnicastAddress(addr) {
|
|
return &tcpip.ErrAddressFamilyNotSupported{}
|
|
}
|
|
|
|
if addressEndpoint.GetKind() != stack.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.ep.nic.ID()))
|
|
}
|
|
|
|
ret := ndp.dad.CheckDuplicateAddressLocked(addr, func(r stack.DADResult) {
|
|
if addressEndpoint.GetKind() != stack.PermanentTentative {
|
|
// The endpoint should still be marked as tentative since we are still
|
|
// performing DAD on it.
|
|
panic(fmt.Sprintf("ndpdad: addr %s is no longer tentative on NIC(%d)", addr, ndp.ep.nic.ID()))
|
|
}
|
|
|
|
var dadSucceeded bool
|
|
switch r.(type) {
|
|
case *stack.DADAborted, *stack.DADError, *stack.DADDupAddrDetected:
|
|
dadSucceeded = false
|
|
case *stack.DADSucceeded:
|
|
dadSucceeded = true
|
|
default:
|
|
panic(fmt.Sprintf("unrecognized DAD result = %T", r))
|
|
}
|
|
|
|
if dadSucceeded {
|
|
addressEndpoint.SetKind(stack.Permanent)
|
|
}
|
|
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnDuplicateAddressDetectionResult(ndp.ep.nic.ID(), addr, r)
|
|
}
|
|
|
|
if dadSucceeded {
|
|
if addressEndpoint.ConfigType() == stack.AddressConfigSlaac && !addressEndpoint.Temporary() {
|
|
// Reset the generation attempts counter as we are starting the
|
|
// generation of a new address for the SLAAC prefix.
|
|
ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */) // +checklocksforce: DAD calls its handlers with ndp.ep.mu held.
|
|
}
|
|
ndp.ep.onAddressAssignedLocked(addr) // +checklocksforce: DAD calls its handlers with ndp.ep.mu held.
|
|
}
|
|
})
|
|
|
|
switch ret {
|
|
case stack.DADStarting:
|
|
case stack.DADAlreadyRunning:
|
|
panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.ep.nic.ID()))
|
|
case stack.DADDisabled:
|
|
addressEndpoint.SetKind(stack.Permanent)
|
|
|
|
// Consider DAD to have resolved even if no DAD messages were actually
|
|
// transmitted.
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnDuplicateAddressDetectionResult(ndp.ep.nic.ID(), addr, &stack.DADSucceeded{})
|
|
}
|
|
|
|
ndp.ep.onAddressAssignedLocked(addr)
|
|
}
|
|
|
|
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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address, reason stack.DADResult) {
|
|
ndp.dad.StopLocked(addr, reason)
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
|
|
// Is the IPv6 endpoint configured to handle RAs at all?
|
|
//
|
|
// Currently, the stack does not determine router interface status on a
|
|
// per-interface basis; it is a protocol-wide configuration, so we check the
|
|
// protocol's forwarding flag to determine if the IPv6 endpoint is forwarding
|
|
// packets.
|
|
if !ndp.configs.HandleRAs.enabled(ndp.ep.Forwarding()) {
|
|
ndp.ep.stats.localStats.UnhandledRouterAdvertisements.Increment()
|
|
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.ep.protocol.options.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.ep.nic.ID(), configuration)
|
|
}
|
|
}
|
|
|
|
// Is the IPv6 endpoint configured to discover default routers?
|
|
if ndp.configs.DiscoverDefaultRouters {
|
|
prf := ra.DefaultRouterPreference()
|
|
if prf == header.ReservedRoutePreference {
|
|
// As per RFC 4191 section 2.2,
|
|
//
|
|
// Prf (Default Router Preference)
|
|
//
|
|
// If the Reserved (10) value is received, the receiver MUST treat the
|
|
// value as if it were (00).
|
|
//
|
|
// Note that the value 00 is the medium (default) router preference value.
|
|
prf = header.MediumRoutePreference
|
|
}
|
|
|
|
// We represent default routers with a default (off-link) route through the
|
|
// router.
|
|
ndp.handleOffLinkRouteDiscovery(offLinkRoute{dest: header.IPv6EmptySubnet, router: ip}, ra.RouterLifetime(), prf)
|
|
}
|
|
|
|
// 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.ep.protocol.options.NDPDisp == nil {
|
|
continue
|
|
}
|
|
|
|
addrs, _ := opt.Addresses()
|
|
ndp.ep.protocol.options.NDPDisp.OnRecursiveDNSServerOption(ndp.ep.nic.ID(), addrs, opt.Lifetime())
|
|
|
|
case header.NDPDNSSearchList:
|
|
if ndp.ep.protocol.options.NDPDisp == nil {
|
|
continue
|
|
}
|
|
|
|
domainNames, _ := opt.DomainNames()
|
|
ndp.ep.protocol.options.NDPDisp.OnDNSSearchListOption(ndp.ep.nic.ID(), domainNames, opt.Lifetime())
|
|
|
|
case header.NDPPrefixInformation:
|
|
prefix := opt.Subnet()
|
|
|
|
// Is the prefix a link-local?
|
|
if header.IsV6LinkLocalUnicastAddress(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)
|
|
}
|
|
|
|
case header.NDPRouteInformation:
|
|
if !ndp.configs.DiscoverMoreSpecificRoutes {
|
|
continue
|
|
}
|
|
|
|
dest, err := opt.Prefix()
|
|
if err != nil {
|
|
panic(fmt.Sprintf("%T.Prefix(): %s", opt, err))
|
|
}
|
|
|
|
prf := opt.RoutePreference()
|
|
if prf == header.ReservedRoutePreference {
|
|
// As per RFC 4191 section 2.3,
|
|
//
|
|
// Prf (Route Preference)
|
|
// 2-bit signed integer. The Route Preference indicates
|
|
// whether to prefer the router associated with this prefix
|
|
// over others, when multiple identical prefixes (for
|
|
// different routers) have been received. If the Reserved
|
|
// (10) value is received, the Route Information Option MUST
|
|
// be ignored.
|
|
continue
|
|
}
|
|
|
|
ndp.handleOffLinkRouteDiscovery(offLinkRoute{dest: dest, router: ip}, opt.RouteLifetime(), prf)
|
|
}
|
|
|
|
// TODO(b/141556115): Do (MTU) Parameter Discovery.
|
|
}
|
|
}
|
|
|
|
// invalidateOffLinkRoute invalidates a discovered off-link route.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) invalidateOffLinkRoute(route offLinkRoute) {
|
|
state, ok := ndp.offLinkRoutes[route]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
state.invalidationJob.Cancel()
|
|
delete(ndp.offLinkRoutes, route)
|
|
|
|
// Let the integrator know a discovered off-link route is invalidated.
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnOffLinkRouteInvalidated(ndp.ep.nic.ID(), route.dest, route.router)
|
|
}
|
|
}
|
|
|
|
// handleOffLinkRouteDiscovery handles the discovery of an off-link route.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) handleOffLinkRouteDiscovery(route offLinkRoute, lifetime time.Duration, prf header.NDPRoutePreference) {
|
|
ndpDisp := ndp.ep.protocol.options.NDPDisp
|
|
if ndpDisp == nil {
|
|
return
|
|
}
|
|
|
|
state, ok := ndp.offLinkRoutes[route]
|
|
switch {
|
|
case !ok && lifetime != 0:
|
|
// This is a new route we are discovering.
|
|
//
|
|
// Only remember it if we currently know about less than
|
|
// MaxDiscoveredOffLinkRoutes routers.
|
|
if len(ndp.offLinkRoutes) < MaxDiscoveredOffLinkRoutes {
|
|
// Inform the integrator when we discovered an off-link route.
|
|
ndpDisp.OnOffLinkRouteUpdated(ndp.ep.nic.ID(), route.dest, route.router, prf)
|
|
|
|
state := offLinkRouteState{
|
|
prf: prf,
|
|
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
|
|
ndp.invalidateOffLinkRoute(route) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
|
|
}),
|
|
}
|
|
|
|
state.invalidationJob.Schedule(lifetime)
|
|
|
|
ndp.offLinkRoutes[route] = state
|
|
}
|
|
|
|
case ok && lifetime != 0:
|
|
// This is an already discovered off-link route. Update the lifetime.
|
|
state.invalidationJob.Cancel()
|
|
state.invalidationJob.Schedule(lifetime)
|
|
|
|
if prf != state.prf {
|
|
state.prf = prf
|
|
|
|
// Inform the integrator about route preference updates.
|
|
ndpDisp.OnOffLinkRouteUpdated(ndp.ep.nic.ID(), route.dest, route.router, prf)
|
|
}
|
|
|
|
ndp.offLinkRoutes[route] = state
|
|
|
|
case ok && lifetime == 0:
|
|
// The already discovered off-link route is no longer considered valid so we
|
|
// invalidate it immediately.
|
|
ndp.invalidateOffLinkRoute(route)
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) rememberOnLinkPrefix(prefix tcpip.Subnet, l time.Duration) {
|
|
ndpDisp := ndp.ep.protocol.options.NDPDisp
|
|
if ndpDisp == nil {
|
|
return
|
|
}
|
|
|
|
// Inform the integrator when we discovered an on-link prefix.
|
|
ndpDisp.OnOnLinkPrefixDiscovered(ndp.ep.nic.ID(), prefix)
|
|
|
|
state := onLinkPrefixState{
|
|
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.mu, func() {
|
|
ndp.invalidateOnLinkPrefix(prefix) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
|
|
}),
|
|
}
|
|
|
|
if l < header.NDPInfiniteLifetime {
|
|
state.invalidationJob.Schedule(l)
|
|
}
|
|
|
|
ndp.onLinkPrefixes[prefix] = state
|
|
}
|
|
|
|
// invalidateOnLinkPrefix invalidates a discovered on-link prefix.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
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.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnOnLinkPrefixInvalidated(ndp.ep.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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
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
|
|
}
|
|
|
|
// Limit the number of discovered SLAAC prefixes.
|
|
if len(ndp.slaacPrefixes) == MaxDiscoveredSLAACPrefixes {
|
|
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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
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: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.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.addressEndpoint) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
|
|
}),
|
|
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.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) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
|
|
}),
|
|
tempAddrs: make(map[tcpip.Address]tempSLAACAddrState),
|
|
maxGenerationAttempts: ndp.configs.AutoGenAddressConflictRetries + 1,
|
|
}
|
|
|
|
now := ndp.ep.protocol.stack.Clock().NowMonotonic()
|
|
|
|
// The time an address is preferred until is needed to properly generate the
|
|
// address.
|
|
if pl < header.NDPInfiniteLifetime {
|
|
t := now.Add(pl)
|
|
state.preferredUntil = &t
|
|
}
|
|
// The time at which an address is invalidated is exposed as a property of the
|
|
// address.
|
|
if vl < header.NDPInfiniteLifetime {
|
|
t := now.Add(vl)
|
|
state.validUntil = &t
|
|
}
|
|
|
|
if !ndp.generateSLAACAddr(prefix, &state) {
|
|
// We were unable to generate an address for the prefix, we do not nothing
|
|
// 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)
|
|
}
|
|
|
|
// If the address is assigned (DAD resolved), generate a temporary address.
|
|
if state.stableAddr.addressEndpoint.GetKind() == stack.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
|
|
}
|
|
|
|
// addAndAcquireSLAACAddr adds a SLAAC address to the IPv6 endpoint.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) addAndAcquireSLAACAddr(addr tcpip.AddressWithPrefix, temporary bool, lifetimes stack.AddressLifetimes) stack.AddressEndpoint {
|
|
addressEndpoint, err := ndp.ep.addAndAcquirePermanentAddressLocked(addr, stack.AddressProperties{
|
|
PEB: stack.FirstPrimaryEndpoint,
|
|
ConfigType: stack.AddressConfigSlaac,
|
|
Lifetimes: lifetimes,
|
|
Temporary: temporary,
|
|
})
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ndp: error when adding SLAAC address %+v: %s", addr, err))
|
|
}
|
|
|
|
// Inform the integrator that we have a new SLAAC address.
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
if disp := ndpDisp.OnAutoGenAddress(ndp.ep.nic.ID(), addr); disp != nil {
|
|
addressEndpoint.RegisterDispatcher(disp)
|
|
}
|
|
}
|
|
|
|
return addressEndpoint
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixState) bool {
|
|
if addressEndpoint := state.stableAddr.addressEndpoint; addressEndpoint != nil {
|
|
panic(fmt.Sprintf("ndp: SLAAC prefix %s already has a permanent address %s", prefix, addressEndpoint.AddressWithPrefix()))
|
|
}
|
|
|
|
// 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
|
|
prefixID := prefix.ID()
|
|
addrBytes := prefixID.AsSlice()
|
|
|
|
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.ep.protocol.options.OpaqueIIDOpts; oIID.NICNameFromID != nil {
|
|
addrBytes = header.AppendOpaqueInterfaceIdentifier(
|
|
addrBytes[:header.IIDOffsetInIPv6Address],
|
|
prefix,
|
|
oIID.NICNameFromID(ndp.ep.nic.ID(), ndp.ep.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.ep.nic.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.AddrFrom16Slice(addrBytes),
|
|
PrefixLen: validPrefixLenForAutoGen,
|
|
}
|
|
|
|
if !ndp.ep.hasPermanentAddressRLocked(generatedAddr.Address) {
|
|
break
|
|
}
|
|
|
|
state.stableAddr.localGenerationFailures++
|
|
}
|
|
|
|
deprecated := state.preferredUntil != nil && !state.preferredUntil.After(ndp.ep.protocol.stack.Clock().NowMonotonic())
|
|
var preferredUntil tcpip.MonotonicTime
|
|
if !deprecated {
|
|
if state.preferredUntil != nil {
|
|
preferredUntil = *state.preferredUntil
|
|
} else {
|
|
preferredUntil = tcpip.MonotonicTimeInfinite()
|
|
}
|
|
}
|
|
validUntil := tcpip.MonotonicTimeInfinite()
|
|
if state.validUntil != nil {
|
|
validUntil = *state.validUntil
|
|
}
|
|
if addressEndpoint := ndp.addAndAcquireSLAACAddr(generatedAddr, false /* temporary */, stack.AddressLifetimes{
|
|
Deprecated: deprecated,
|
|
PreferredUntil: preferredUntil,
|
|
ValidUntil: validUntil,
|
|
}); addressEndpoint != nil {
|
|
state.stableAddr.addressEndpoint = addressEndpoint
|
|
state.generationAttempts++
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// regenerateSLAACAddr regenerates an address for a SLAAC prefix.
|
|
//
|
|
// If generating a new address for the prefix fails, the prefix is invalidated.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
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 is reset.
|
|
//
|
|
// Returns true if a new address was generated.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
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.addressEndpoint.AddressWithPrefix().Address
|
|
now := ndp.ep.protocol.stack.Clock().NowMonotonic()
|
|
|
|
// 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 != nil {
|
|
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 != nil {
|
|
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 IPv6
|
|
// endpoint.
|
|
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.ep.hasPermanentAddressRLocked(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.
|
|
addressEndpoint := ndp.addAndAcquireSLAACAddr(generatedAddr, true /* temporary */, stack.AddressLifetimes{
|
|
Deprecated: false,
|
|
PreferredUntil: now.Add(pl),
|
|
ValidUntil: now.Add(vl),
|
|
})
|
|
if addressEndpoint == nil {
|
|
return false
|
|
}
|
|
|
|
state := tempSLAACAddrState{
|
|
deprecationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.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.addressEndpoint) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
|
|
}),
|
|
invalidationJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.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) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
|
|
}),
|
|
regenJob: tcpip.NewJob(ndp.ep.protocol.stack.Clock(), &ndp.ep.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 */) // +checklocksforce: NewJob calls back with ndp.ep.mu held.
|
|
prefixState.tempAddrs[generatedAddr.Address] = tempAddrState
|
|
ndp.slaacPrefixes[prefix] = prefixState
|
|
}),
|
|
createdAt: now,
|
|
addressEndpoint: addressEndpoint,
|
|
}
|
|
|
|
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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixState *slaacPrefixState, pl, vl time.Duration) {
|
|
// If prefix was preferred for some finite lifetime before, cancel the
|
|
// deprecation job so it can be reset.
|
|
prefixState.deprecationJob.Cancel()
|
|
|
|
now := ndp.ep.protocol.stack.Clock().NowMonotonic()
|
|
|
|
// Schedule the deprecation job if prefix has a finite preferred lifetime.
|
|
deprecated := pl == 0
|
|
if pl < header.NDPInfiniteLifetime {
|
|
if !deprecated {
|
|
prefixState.deprecationJob.Schedule(pl)
|
|
}
|
|
t := now.Add(pl)
|
|
prefixState.preferredUntil = &t
|
|
} else {
|
|
prefixState.preferredUntil = nil
|
|
}
|
|
|
|
// 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 = nil
|
|
} 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 == nil {
|
|
rl = header.NDPInfiniteLifetime
|
|
} else {
|
|
rl = prefixState.validUntil.Sub(now)
|
|
}
|
|
|
|
if vl > MinPrefixInformationValidLifetimeForUpdate || vl > rl {
|
|
effectiveVl = vl
|
|
} else if rl > MinPrefixInformationValidLifetimeForUpdate {
|
|
effectiveVl = MinPrefixInformationValidLifetimeForUpdate
|
|
}
|
|
|
|
if effectiveVl != 0 {
|
|
prefixState.invalidationJob.Cancel()
|
|
prefixState.invalidationJob.Schedule(effectiveVl)
|
|
t := now.Add(effectiveVl)
|
|
prefixState.validUntil = &t
|
|
}
|
|
}
|
|
|
|
// If the preferred lifetime is zero, then the prefix should be deprecated.
|
|
{
|
|
var preferredUntil tcpip.MonotonicTime
|
|
if !deprecated {
|
|
if prefixState.preferredUntil == nil {
|
|
preferredUntil = tcpip.MonotonicTimeInfinite()
|
|
} else {
|
|
preferredUntil = *prefixState.preferredUntil
|
|
}
|
|
}
|
|
validUntil := tcpip.MonotonicTimeInfinite()
|
|
if prefixState.validUntil != nil {
|
|
validUntil = *prefixState.validUntil
|
|
}
|
|
if addressEndpoint := prefixState.stableAddr.addressEndpoint; !addressEndpoint.Deprecated() && deprecated {
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnAutoGenAddressDeprecated(ndp.ep.nic.ID(), addressEndpoint.AddressWithPrefix())
|
|
}
|
|
}
|
|
prefixState.stableAddr.addressEndpoint.SetLifetimes(stack.AddressLifetimes{
|
|
Deprecated: deprecated,
|
|
PreferredUntil: preferredUntil,
|
|
ValidUntil: validUntil,
|
|
})
|
|
}
|
|
|
|
// If DAD is not yet complete on the stable address, there is no need to do
|
|
// work with temporary addresses.
|
|
if prefixState.stableAddr.addressEndpoint.GetKind() != stack.Permanent {
|
|
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 != nil && prefixState.validUntil.Before(validUntil) {
|
|
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 != nil && prefixState.preferredUntil.Before(preferredUntil) {
|
|
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()
|
|
deprecated := newPreferredLifetime <= 0
|
|
if !deprecated {
|
|
tempAddrState.deprecationJob.Schedule(newPreferredLifetime)
|
|
}
|
|
|
|
if addressEndpoint := tempAddrState.addressEndpoint; !addressEndpoint.Deprecated() && deprecated {
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnAutoGenAddressDeprecated(ndp.ep.nic.ID(), addressEndpoint.AddressWithPrefix())
|
|
}
|
|
}
|
|
tempAddrState.addressEndpoint.SetLifetimes(stack.AddressLifetimes{
|
|
Deprecated: deprecated,
|
|
ValidUntil: validUntil,
|
|
PreferredUntil: preferredUntil,
|
|
})
|
|
|
|
tempAddrState.regenJob.Cancel()
|
|
if tempAddrState.regenerated {
|
|
} else {
|
|
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 temporary address has already been regenerated, no new temporary
|
|
// address is generated. To ensure continuation of temporary SLAAC addresses,
|
|
// we manually try to regenerate an address here.
|
|
if regenForAddr.BitLen() != 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 the address as deprecated and notifies the NDP
|
|
// dispatcher that address has been deprecated.
|
|
//
|
|
// deprecateSLAACAddress does nothing if the address is already deprecated.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) deprecateSLAACAddress(addressEndpoint stack.AddressEndpoint) {
|
|
if addressEndpoint.Deprecated() {
|
|
return
|
|
}
|
|
|
|
addressEndpoint.SetDeprecated(true)
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnAutoGenAddressDeprecated(ndp.ep.nic.ID(), addressEndpoint.AddressWithPrefix())
|
|
}
|
|
}
|
|
|
|
// invalidateSLAACPrefix invalidates a SLAAC prefix.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefixState) {
|
|
ndp.cleanupSLAACPrefixResources(prefix, state)
|
|
|
|
if addressEndpoint := state.stableAddr.addressEndpoint; addressEndpoint != nil {
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnAutoGenAddressInvalidated(ndp.ep.nic.ID(), addressEndpoint.AddressWithPrefix())
|
|
}
|
|
|
|
if err := ndp.ep.removePermanentEndpointInnerLocked(addressEndpoint, stack.AddressRemovalInvalidated, &stack.DADAborted{}); err != nil {
|
|
panic(fmt.Sprintf("ndp: error removing stable SLAAC address %s: %s", addressEndpoint.AddressWithPrefix(), err))
|
|
}
|
|
}
|
|
}
|
|
|
|
// cleanupSLAACAddrResourcesAndNotify cleans up an invalidated SLAAC address's
|
|
// resources.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) cleanupSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPrefix, invalidatePrefix bool) {
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnAutoGenAddressInvalidated(ndp.ep.nic.ID(), addr)
|
|
}
|
|
|
|
prefix := addr.Subnet()
|
|
state, ok := ndp.slaacPrefixes[prefix]
|
|
if !ok || state.stableAddr.addressEndpoint == nil || addr.Address != state.stableAddr.addressEndpoint.AddressWithPrefix().Address {
|
|
return
|
|
}
|
|
|
|
if !invalidatePrefix {
|
|
// If the prefix is not being invalidated, disassociate the address from the
|
|
// prefix and do nothing further.
|
|
state.stableAddr.addressEndpoint.DecRef()
|
|
state.stableAddr.addressEndpoint = 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.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
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)
|
|
}
|
|
|
|
if state.stableAddr.addressEndpoint != nil {
|
|
state.stableAddr.addressEndpoint.DecRef()
|
|
state.stableAddr.addressEndpoint = nil
|
|
}
|
|
state.deprecationJob.Cancel()
|
|
state.invalidationJob.Cancel()
|
|
delete(ndp.slaacPrefixes, prefix)
|
|
}
|
|
|
|
// invalidateTempSLAACAddr invalidates a temporary SLAAC address.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) invalidateTempSLAACAddr(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) {
|
|
ndp.cleanupTempSLAACAddrResourcesAndNotifyInner(tempAddrs, tempAddr, tempAddrState)
|
|
|
|
if err := ndp.ep.removePermanentEndpointInnerLocked(tempAddrState.addressEndpoint, stack.AddressRemovalInvalidated, &stack.DADAborted{}); err != nil {
|
|
panic(fmt.Sprintf("error removing temporary SLAAC address %s: %s", tempAddrState.addressEndpoint.AddressWithPrefix(), err))
|
|
}
|
|
}
|
|
|
|
// cleanupTempSLAACAddrResourcesAndNotify cleans up an invalidated temporary
|
|
// SLAAC address's resources from ndp and notifies the NDP dispatcher that the
|
|
// address was invalidated.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) cleanupTempSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPrefix) {
|
|
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.cleanupTempSLAACAddrResourcesAndNotifyInner(state.tempAddrs, addr.Address, tempAddrState)
|
|
}
|
|
|
|
// cleanupTempSLAACAddrResourcesAndNotifyInner is like
|
|
// cleanupTempSLAACAddrResourcesAndNotify except it does not lookup the
|
|
// temporary address's state in ndp - it assumes the passed state is valid.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) cleanupTempSLAACAddrResourcesAndNotifyInner(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) {
|
|
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
|
|
ndpDisp.OnAutoGenAddressInvalidated(ndp.ep.nic.ID(), tempAddrState.addressEndpoint.AddressWithPrefix())
|
|
}
|
|
|
|
tempAddrState.addressEndpoint.DecRef()
|
|
tempAddrState.addressEndpoint = nil
|
|
tempAddrState.deprecationJob.Cancel()
|
|
tempAddrState.invalidationJob.Cancel()
|
|
tempAddrState.regenJob.Cancel()
|
|
delete(tempAddrs, tempAddr)
|
|
}
|
|
|
|
// cleanupState cleans up ndp's state.
|
|
//
|
|
// This function invalidates all discovered on-link prefixes, discovered
|
|
// routers, and auto-generated addresses.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) cleanupState() {
|
|
for prefix, state := range ndp.slaacPrefixes {
|
|
ndp.invalidateSLAACPrefix(prefix, state)
|
|
}
|
|
|
|
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 route := range ndp.offLinkRoutes {
|
|
ndp.invalidateOffLinkRoute(route)
|
|
}
|
|
|
|
if got := len(ndp.offLinkRoutes); got != 0 {
|
|
panic(fmt.Sprintf("ndp: still have discovered off-link routes 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.
|
|
//
|
|
// If ndp is not configured to handle Router Advertisements, routers will not
|
|
// be solicited as there is no point soliciting routers if we don't handle their
|
|
// advertisements.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) startSolicitingRouters() {
|
|
if ndp.rtrSolicitTimer.timer != nil {
|
|
// We are already soliciting routers.
|
|
return
|
|
}
|
|
|
|
remaining := ndp.configs.MaxRtrSolicitations
|
|
if remaining == 0 {
|
|
return
|
|
}
|
|
|
|
if !ndp.configs.HandleRAs.enabled(ndp.ep.Forwarding()) {
|
|
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(ndp.ep.protocol.stack.InsecureRNG().Int63n(int64(ndp.configs.MaxRtrSolicitationDelay)))
|
|
}
|
|
|
|
// Protected by ndp.ep.mu.
|
|
done := false
|
|
|
|
ndp.rtrSolicitTimer = timer{
|
|
done: &done,
|
|
timer: ndp.ep.protocol.stack.Clock().AfterFunc(delay, func() {
|
|
// As per RFC 4861 section 4.1:
|
|
//
|
|
// IP Fields:
|
|
// Source Address
|
|
// An IP address assigned to the sending interface, or
|
|
// the unspecified address if no address is assigned
|
|
// to the sending interface.
|
|
localAddr := header.IPv6Any
|
|
if addressEndpoint := ndp.ep.AcquireOutgoingPrimaryAddress(header.IPv6AllRoutersLinkLocalMulticastAddress, tcpip.Address{} /* srcHint */, false); addressEndpoint != nil {
|
|
localAddr = addressEndpoint.AddressWithPrefix().Address
|
|
addressEndpoint.DecRef()
|
|
}
|
|
|
|
// 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
|
|
linkAddress := ndp.ep.nic.LinkAddress()
|
|
if localAddr != header.IPv6Any && header.IsValidUnicastEthernetAddress(linkAddress) {
|
|
optsSerializer = header.NDPOptionsSerializer{
|
|
header.NDPSourceLinkLayerAddressOption(linkAddress),
|
|
}
|
|
}
|
|
payloadSize := header.ICMPv6HeaderSize + header.NDPRSMinimumSize + optsSerializer.Length()
|
|
icmpView := buffer.NewView(payloadSize)
|
|
icmpView.Grow(payloadSize)
|
|
icmpData := header.ICMPv6(icmpView.AsSlice())
|
|
icmpData.SetType(header.ICMPv6RouterSolicit)
|
|
rs := header.NDPRouterSolicit(icmpData.MessageBody())
|
|
rs.Options().Serialize(optsSerializer)
|
|
icmpData.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
|
Header: icmpData,
|
|
Src: localAddr,
|
|
Dst: header.IPv6AllRoutersLinkLocalMulticastAddress,
|
|
}))
|
|
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
ReserveHeaderBytes: int(ndp.ep.MaxHeaderLength()),
|
|
Payload: buffer.MakeWithView(icmpView),
|
|
})
|
|
defer pkt.DecRef()
|
|
|
|
sent := ndp.ep.stats.icmp.packetsSent
|
|
if err := addIPHeader(localAddr, header.IPv6AllRoutersLinkLocalMulticastAddress, pkt, stack.NetworkHeaderParams{
|
|
Protocol: header.ICMPv6ProtocolNumber,
|
|
TTL: header.NDPHopLimit,
|
|
}, nil /* extensionHeaders */); err != nil {
|
|
panic(fmt.Sprintf("failed to add IP header: %s", err))
|
|
}
|
|
|
|
if err := ndp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersLinkLocalMulticastAddress), pkt); err != nil {
|
|
sent.dropped.Increment()
|
|
// Don't send any more messages if we had an error.
|
|
remaining = 0
|
|
} else {
|
|
sent.routerSolicit.Increment()
|
|
remaining--
|
|
}
|
|
|
|
ndp.ep.mu.Lock()
|
|
defer ndp.ep.mu.Unlock()
|
|
|
|
if done {
|
|
// Router solicitation was stopped.
|
|
return
|
|
}
|
|
|
|
if remaining == 0 {
|
|
// We are done soliciting routers.
|
|
ndp.stopSolicitingRouters()
|
|
return
|
|
}
|
|
|
|
ndp.rtrSolicitTimer.timer.Reset(ndp.configs.RtrSolicitationInterval)
|
|
}),
|
|
}
|
|
}
|
|
|
|
// forwardingChanged handles a change in forwarding configuration.
|
|
//
|
|
// If transitioning to a host, router solicitation will be started. Otherwise,
|
|
// router solicitation will be stopped if NDP is not configured to handle RAs
|
|
// as a router.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) forwardingChanged(forwarding bool) {
|
|
if forwarding {
|
|
if ndp.configs.HandleRAs.enabled(forwarding) {
|
|
return
|
|
}
|
|
|
|
ndp.stopSolicitingRouters()
|
|
return
|
|
}
|
|
|
|
// Solicit routers when transitioning to a host.
|
|
//
|
|
// If the endpoint is not currently enabled, routers will be solicited when
|
|
// the endpoint becomes enabled (if it is still a host).
|
|
if ndp.ep.Enabled() {
|
|
ndp.startSolicitingRouters()
|
|
}
|
|
}
|
|
|
|
// stopSolicitingRouters stops soliciting routers. If routers are not currently
|
|
// being solicited, this function does nothing.
|
|
//
|
|
// +checklocks:ndp.ep.mu.RWMutex
|
|
func (ndp *ndpState) stopSolicitingRouters() {
|
|
if ndp.rtrSolicitTimer.timer == nil {
|
|
// Nothing to do.
|
|
return
|
|
}
|
|
|
|
ndp.rtrSolicitTimer.timer.Stop()
|
|
*ndp.rtrSolicitTimer.done = true
|
|
ndp.rtrSolicitTimer = timer{}
|
|
}
|
|
|
|
func (ndp *ndpState) init(ep *endpoint, dadOptions ip.DADOptions) {
|
|
if ndp.offLinkRoutes != nil {
|
|
panic("attempted to initialize NDP state twice")
|
|
}
|
|
|
|
ndp.ep = ep
|
|
ndp.configs = ep.protocol.options.NDPConfigs
|
|
ndp.dad.Init(&ndp.ep.mu, ep.protocol.options.DADConfigs, dadOptions)
|
|
ndp.offLinkRoutes = make(map[offLinkRoute]offLinkRouteState)
|
|
ndp.onLinkPrefixes = make(map[tcpip.Subnet]onLinkPrefixState)
|
|
ndp.slaacPrefixes = make(map[tcpip.Subnet]slaacPrefixState)
|
|
|
|
header.InitialTempIID(ndp.temporaryIIDHistory[:], ndp.ep.protocol.options.TempIIDSeed, ndp.ep.nic.ID())
|
|
ndp.temporaryAddressDesyncFactor = time.Duration(ep.protocol.stack.InsecureRNG().Int63n(int64(MaxDesyncFactor)))
|
|
}
|
|
|
|
func (ndp *ndpState) SendDADMessage(addr tcpip.Address, nonce []byte) tcpip.Error {
|
|
snmc := header.SolicitedNodeAddr(addr)
|
|
return ndp.ep.sendNDPNS(header.IPv6Any, snmc, addr, header.EthernetAddressFromMulticastIPv6Address(snmc), header.NDPOptionsSerializer{
|
|
header.NDPNonceOption(nonce),
|
|
})
|
|
}
|
|
|
|
func (e *endpoint) sendNDPNS(srcAddr, dstAddr, targetAddr tcpip.Address, remoteLinkAddr tcpip.LinkAddress, opts header.NDPOptionsSerializer) tcpip.Error {
|
|
icmpView := buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize + opts.Length())
|
|
icmpView.Grow(header.ICMPv6NeighborSolicitMinimumSize + opts.Length())
|
|
icmp := header.ICMPv6(icmpView.AsSlice())
|
|
icmp.SetType(header.ICMPv6NeighborSolicit)
|
|
ns := header.NDPNeighborSolicit(icmp.MessageBody())
|
|
ns.SetTargetAddress(targetAddr)
|
|
ns.Options().Serialize(opts)
|
|
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
|
Header: icmp,
|
|
Src: srcAddr,
|
|
Dst: dstAddr,
|
|
}))
|
|
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
|
ReserveHeaderBytes: int(e.MaxHeaderLength()),
|
|
Payload: buffer.MakeWithView(icmpView),
|
|
})
|
|
defer pkt.DecRef()
|
|
|
|
if err := addIPHeader(srcAddr, dstAddr, pkt, stack.NetworkHeaderParams{
|
|
Protocol: header.ICMPv6ProtocolNumber,
|
|
TTL: header.NDPHopLimit,
|
|
}, nil /* extensionHeaders */); err != nil {
|
|
panic(fmt.Sprintf("failed to add IP header: %s", err))
|
|
}
|
|
|
|
sent := e.stats.icmp.packetsSent
|
|
err := e.nic.WritePacketToRemote(remoteLinkAddr, pkt)
|
|
if err != nil {
|
|
sent.dropped.Increment()
|
|
} else {
|
|
sent.neighborSolicit.Increment()
|
|
}
|
|
return err
|
|
}
|