Files
2024-08-29 18:08:52 -07:00

2034 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
}
// +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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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 */)
}
ndp.ep.onAddressAssignedLocked(addr)
}
})
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// Precondition: ndp.ep.mu must be locked.
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)
}),
}
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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)
}),
}
if l < header.NDPInfiniteLifetime {
state.invalidationJob.Schedule(l)
}
ndp.onLinkPrefixes[prefix] = state
}
// invalidateOnLinkPrefix invalidates a discovered on-link prefix.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) invalidateOnLinkPrefix(prefix tcpip.Subnet) {
s, ok := ndp.onLinkPrefixes[prefix]
// Is the on-link prefix still discovered?
if !ok {
// ...Nope, do nothing further.
return
}
s.invalidationJob.Cancel()
delete(ndp.onLinkPrefixes, prefix)
// Let the integrator know a discovered on-link prefix is invalidated.
if ndpDisp := ndp.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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) handleOnLinkPrefixInformation(pi header.NDPPrefixInformation) {
prefix := pi.Subnet()
prefixState, ok := ndp.onLinkPrefixes[prefix]
vl := pi.ValidLifetime()
if !ok && vl == 0 {
// Don't know about this prefix but it has a zero valid
// lifetime, so just ignore.
return
}
if !ok && vl != 0 {
// This is a new on-link prefix we are discovering
//
// Only remember it if we currently know about less than
// MaxDiscoveredOnLinkPrefixes on-link prefixes.
if ndp.configs.DiscoverOnLinkPrefixes && len(ndp.onLinkPrefixes) < MaxDiscoveredOnLinkPrefixes {
ndp.rememberOnLinkPrefix(prefix, vl)
}
return
}
if ok && vl == 0 {
// We know about the on-link prefix, but it is
// no longer to be considered on-link, so
// invalidate it.
ndp.invalidateOnLinkPrefix(prefix)
return
}
// This is an already discovered on-link prefix with a
// new non-zero valid lifetime.
//
// Update the invalidation job.
prefixState.invalidationJob.Cancel()
if vl < header.NDPInfiniteLifetime {
// Prefix is valid for a finite lifetime, schedule the job to execute after
// the new valid lifetime.
prefixState.invalidationJob.Schedule(vl)
}
ndp.onLinkPrefixes[prefix] = prefixState
}
// handleAutonomousPrefixInformation handles a Prefix Information option with
// its autonomous flag set, as per RFC 4862 section 5.5.3.
//
// handleAutonomousPrefixInformation assumes that the prefix this pi is for is
// not the link-local prefix and the autonomous flag is set.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) handleAutonomousPrefixInformation(pi header.NDPPrefixInformation) {
vl := pi.ValidLifetime()
pl := pi.PreferredLifetime()
// If the preferred lifetime is greater than the valid lifetime,
// silently ignore the Prefix Information option, as per RFC 4862
// section 5.5.3.c.
if pl > vl {
return
}
prefix := pi.Subnet()
// Check if we already maintain SLAAC state for prefix.
if state, ok := ndp.slaacPrefixes[prefix]; ok {
// As per RFC 4862 section 5.5.3.e, refresh prefix's SLAAC lifetimes.
ndp.refreshSLAACPrefixLifetimes(prefix, &state, pl, vl)
ndp.slaacPrefixes[prefix] = state
return
}
// prefix is a new SLAAC prefix. Do the work as outlined by RFC 4862 section
// 5.5.3.d if ndp is configured to auto-generate new addresses via SLAAC.
if !ndp.configs.AutoGenGlobalAddresses {
return
}
// 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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
// If we do not already have an address for this prefix and the valid
// lifetime is 0, no need to do anything further, as per RFC 4862
// section 5.5.3.d.
if vl == 0 {
return
}
// Make sure the prefix is valid (as far as its length is concerned) to
// generate a valid IPv6 address from an interface identifier (IID), as
// per RFC 4862 sectiion 5.5.3.d.
if prefix.Prefix() != validPrefixLenForAutoGen {
return
}
state := slaacPrefixState{
deprecationJob: 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)
}),
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)
}),
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) regenerateSLAACAddr(prefix tcpip.Subnet) {
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: SLAAC prefix state not found to regenerate address for %s", prefix))
}
if ndp.generateSLAACAddr(prefix, &state) {
ndp.slaacPrefixes[prefix] = state
return
}
// We were unable to generate a permanent address for the SLAAC prefix so
// invalidate the prefix as there is no reason to maintain state for a
// SLAAC prefix we do not have an address for.
ndp.invalidateSLAACPrefix(prefix, state)
}
// generateTempSLAACAddr generates a new temporary SLAAC address.
//
// If resetGenAttempts is true, the prefix's generation counter is reset.
//
// Returns true if a new address was generated.
func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *slaacPrefixState, resetGenAttempts bool) bool {
// Are we configured to auto-generate new temporary global addresses for the
// prefix?
if !ndp.configs.AutoGenTempGlobalAddresses || prefix == header.IPv6LinkLocalPrefix.Subnet() {
return false
}
if resetGenAttempts {
prefixState.generationAttempts = 0
prefixState.maxGenerationAttempts = ndp.configs.AutoGenAddressConflictRetries + 1
}
// If we have already reached the maximum address generation attempts for the
// prefix, do not generate another address.
if prefixState.generationAttempts == prefixState.maxGenerationAttempts {
return false
}
stableAddr := prefixState.stableAddr.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)
}),
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)
}),
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 */)
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) regenerateTempSLAACAddr(prefix tcpip.Subnet, resetGenAttempts bool) {
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: SLAAC prefix state not found to regenerate temporary address for %s", prefix))
}
ndp.generateTempSLAACAddr(prefix, &state, resetGenAttempts)
ndp.slaacPrefixes[prefix] = state
}
// refreshSLAACPrefixLifetimes refreshes the lifetimes of a SLAAC prefix.
//
// pl is the new preferred lifetime. vl is the new valid lifetime.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaacPrefixState) {
// Invalidate all temporary addresses.
for tempAddr, tempAddrState := range state.tempAddrs {
ndp.invalidateTempSLAACAddr(state.tempAddrs, tempAddr, tempAddrState)
}
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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.
//
// Precondition: ndp.ep.mu must be locked.
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.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
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
}