Support deprecating SLAAC addresses after the preferred lifetime

Support deprecating network endpoints on a NIC. If an endpoint is deprecated, it
should not be used for new connections unless a more preferred endpoint is not
available, or unless the deprecated endpoint was explicitly requested.

Test: Test that deprecated endpoints are only returned when more preferred
endpoints are not available and SLAAC addresses are deprecated after its
preferred lifetime
PiperOrigin-RevId: 288562705
This commit is contained in:
Ghanan Gowripalan
2020-01-07 13:42:08 -08:00
committed by gVisor bot
parent 2031cc4701
commit 4e19d165cc
4 changed files with 827 additions and 109 deletions
+196 -93
View File
@@ -169,6 +169,15 @@ type NDPDispatcher interface {
// call functions on the stack itself.
OnAutoGenAddress(tcpip.NICID, tcpip.AddressWithPrefix) bool
// OnAutoGenAddressDeprecated will be called when an auto-generated
// address (as part of SLAAC) has been deprecated, but is still
// considered valid. Note, if an address is invalidated at the same
// time it is deprecated, the deprecation event MAY be omitted.
//
// This function is not permitted to block indefinitely. It must not
// call functions on the stack itself.
OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix)
// OnAutoGenAddressInvalidated will be called when an auto-generated
// address (as part of SLAAC) has been invalidated.
//
@@ -335,6 +344,17 @@ type onLinkPrefixState struct {
// autoGenAddressState holds data associated with an address generated via
// SLAAC.
type autoGenAddressState struct {
// A reference to the referencedNetworkEndpoint that this autoGenAddressState
// is holding state for.
ref *referencedNetworkEndpoint
deprecationTimer *time.Timer
// Used to signal the timer not to deprecate the SLAAC address in a race
// condition. Used for the same reason as doNotInvalidate, but for deprecating
// an address.
doNotDeprecate *bool
invalidationTimer *time.Timer
// Used to signal the timer not to invalidate the SLAAC address (A) in
@@ -912,103 +932,30 @@ func (ndp *ndpState) handleAutonomousPrefixInformation(pi header.NDPPrefixInform
prefix := pi.Subnet()
// Check if we already have an auto-generated address for prefix.
for _, ref := range ndp.nic.endpoints {
if ref.protocol != header.IPv6ProtocolNumber {
continue
}
if ref.configType != slaac {
continue
}
addr := ref.ep.ID().LocalAddress
refAddrWithPrefix := tcpip.AddressWithPrefix{Address: addr, PrefixLen: ref.ep.PrefixLen()}
for addr, addrState := range ndp.autoGenAddresses {
refAddrWithPrefix := tcpip.AddressWithPrefix{Address: addr, PrefixLen: addrState.ref.ep.PrefixLen()}
if refAddrWithPrefix.Subnet() != prefix {
continue
}
//
// At this point, we know we are refreshing a SLAAC generated
// IPv6 address with the prefix, prefix. Do the work as outlined
// by RFC 4862 section 5.5.3.e.
//
addrState, ok := ndp.autoGenAddresses[addr]
if !ok {
panic(fmt.Sprintf("must have an autoGenAddressess entry for the SLAAC generated IPv6 address %s", addr))
}
// TODO(b/143713887): Handle deprecating auto-generated address
// after the preferred lifetime.
// As per RFC 4862 section 5.5.3.e, the valid lifetime of the
// address generated by SLAAC is as follows:
//
// 1) If the received Valid Lifetime is greater than 2 hours or
// greater than RemainingLifetime, set the valid lifetime of
// the address 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 address to 2
// hours.
// Handle the infinite valid lifetime separately as we do not
// keep a timer in this case.
if vl >= header.NDPInfiniteLifetime {
if addrState.invalidationTimer != nil {
// Valid lifetime was finite before, but now it
// is valid forever.
if !addrState.invalidationTimer.Stop() {
*addrState.doNotInvalidate = true
}
addrState.invalidationTimer = nil
addrState.validUntil = time.Time{}
ndp.autoGenAddresses[addr] = addrState
}
return
}
var effectiveVl time.Duration
var rl time.Duration
// If the address was originally set to be valid forever,
// assume the remaining time to be the maximum possible value.
if addrState.invalidationTimer == nil {
rl = header.NDPInfiniteLifetime
} else {
rl = time.Until(addrState.validUntil)
}
if vl > MinPrefixInformationValidLifetimeForUpdate || vl > rl {
effectiveVl = vl
} else if rl <= MinPrefixInformationValidLifetimeForUpdate {
ndp.autoGenAddresses[addr] = addrState
return
} else {
effectiveVl = MinPrefixInformationValidLifetimeForUpdate
}
if addrState.invalidationTimer == nil {
addrState.invalidationTimer = ndp.autoGenAddrInvalidationTimer(addr, effectiveVl, addrState.doNotInvalidate)
} else {
if !addrState.invalidationTimer.Stop() {
*addrState.doNotInvalidate = true
}
addrState.invalidationTimer.Reset(effectiveVl)
}
addrState.validUntil = time.Now().Add(effectiveVl)
ndp.autoGenAddresses[addr] = addrState
// At this point, we know we are refreshing a SLAAC generated IPv6 address
// with the prefix prefix. Do the work as outlined by RFC 4862 section
// 5.5.3.e.
ndp.refreshAutoGenAddressLifetimes(addr, pl, vl)
return
}
// We do not already have an address within the prefix, prefix. Do the
// work as outlined by RFC 4862 section 5.5.3.d if n is configured
// to auto-generated global addresses by SLAAC.
ndp.newAutoGenAddress(prefix, pl, vl)
}
// newAutoGenAddress generates a new SLAAC address with the provided lifetimes
// for prefix.
//
// pl is the new preferred lifetime. vl is the new valid lifetime.
func (ndp *ndpState) newAutoGenAddress(prefix tcpip.Subnet, pl, vl time.Duration) {
// Are we configured to auto-generate new global addresses?
if !ndp.configs.AutoGenGlobalAddresses {
return
@@ -1067,18 +1014,25 @@ func (ndp *ndpState) handleAutonomousPrefixInformation(pi header.NDPPrefixInform
return
}
if _, err := ndp.nic.addAddressLocked(tcpip.ProtocolAddress{
protocolAddr := tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: addrWithPrefix,
}, FirstPrimaryEndpoint, permanent, slaac); err != nil {
panic(err)
}
// If the preferred lifetime is zero, then the address should be considered
// deprecated.
deprecated := pl == 0
ref, err := ndp.nic.addAddressLocked(protocolAddr, FirstPrimaryEndpoint, permanent, slaac, deprecated)
if err != nil {
log.Fatalf("ndp: error when adding address %s: %s", protocolAddr, err)
}
// Setup the timers to deprecate and invalidate this newly generated
// address.
// Setup the timers to deprecate and invalidate this newly generated address.
// TODO(b/143713887): Handle deprecating auto-generated addresses
// after the preferred lifetime.
var doNotDeprecate bool
var pTimer *time.Timer
if !deprecated && pl < header.NDPInfiniteLifetime {
pTimer = ndp.autoGenAddrDeprecationTimer(addr, pl, &doNotDeprecate)
}
var doNotInvalidate bool
var vTimer *time.Timer
@@ -1087,12 +1041,126 @@ func (ndp *ndpState) handleAutonomousPrefixInformation(pi header.NDPPrefixInform
}
ndp.autoGenAddresses[addr] = autoGenAddressState{
ref: ref,
deprecationTimer: pTimer,
doNotDeprecate: &doNotDeprecate,
invalidationTimer: vTimer,
doNotInvalidate: &doNotInvalidate,
validUntil: time.Now().Add(vl),
}
}
// refreshAutoGenAddressLifetimes refreshes the lifetime of a SLAAC generated
// address addr.
//
// pl is the new preferred lifetime. vl is the new valid lifetime.
func (ndp *ndpState) refreshAutoGenAddressLifetimes(addr tcpip.Address, pl, vl time.Duration) {
addrState, ok := ndp.autoGenAddresses[addr]
if !ok {
log.Fatalf("ndp: SLAAC state not found to refresh lifetimes for %s", addr)
}
defer func() { ndp.autoGenAddresses[addr] = addrState }()
// If the preferred lifetime is zero, then the address should be considered
// deprecated.
deprecated := pl == 0
wasDeprecated := addrState.ref.deprecated
addrState.ref.deprecated = deprecated
// Only send the deprecation event if the deprecated status for addr just
// changed from non-deprecated to deprecated.
if !wasDeprecated && deprecated {
ndp.notifyAutoGenAddressDeprecated(addr)
}
// If addr was preferred for some finite lifetime before, stop the deprecation
// timer so it can be reset.
if t := addrState.deprecationTimer; t != nil && !t.Stop() {
*addrState.doNotDeprecate = true
}
// Reset the deprecation timer.
if pl >= header.NDPInfiniteLifetime || deprecated {
// If addr is preferred forever or it has been deprecated already, there is
// no need for a deprecation timer.
addrState.deprecationTimer = nil
} else if addrState.deprecationTimer == nil {
// addr is now preferred for a finite lifetime.
addrState.deprecationTimer = ndp.autoGenAddrDeprecationTimer(addr, pl, addrState.doNotDeprecate)
} else {
// addr continues to be preferred for a finite lifetime.
addrState.deprecationTimer.Reset(pl)
}
// As per RFC 4862 section 5.5.3.e, the valid lifetime of the address
//
//
// 1) If the received Valid Lifetime is greater than 2 hours or greater than
// RemainingLifetime, set the valid lifetime of the address 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 address to 2 hours.
// Handle the infinite valid lifetime separately as we do not keep a timer in
// this case.
if vl >= header.NDPInfiniteLifetime {
if addrState.invalidationTimer != nil {
// Valid lifetime was finite before, but now it is valid forever.
if !addrState.invalidationTimer.Stop() {
*addrState.doNotInvalidate = true
}
addrState.invalidationTimer = nil
addrState.validUntil = time.Time{}
}
return
}
var effectiveVl time.Duration
var rl time.Duration
// If the address was originally set to be valid forever, assume the remaining
// time to be the maximum possible value.
if addrState.invalidationTimer == nil {
rl = header.NDPInfiniteLifetime
} else {
rl = time.Until(addrState.validUntil)
}
if vl > MinPrefixInformationValidLifetimeForUpdate || vl > rl {
effectiveVl = vl
} else if rl <= MinPrefixInformationValidLifetimeForUpdate {
return
} else {
effectiveVl = MinPrefixInformationValidLifetimeForUpdate
}
if addrState.invalidationTimer == nil {
addrState.invalidationTimer = ndp.autoGenAddrInvalidationTimer(addr, effectiveVl, addrState.doNotInvalidate)
} else {
if !addrState.invalidationTimer.Stop() {
*addrState.doNotInvalidate = true
}
addrState.invalidationTimer.Reset(effectiveVl)
}
addrState.validUntil = time.Now().Add(effectiveVl)
}
// notifyAutoGenAddressDeprecated notifies the stack's NDP dispatcher that addr
// has been deprecated.
func (ndp *ndpState) notifyAutoGenAddressDeprecated(addr tcpip.Address) {
if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil {
ndpDisp.OnAutoGenAddressDeprecated(ndp.nic.ID(), tcpip.AddressWithPrefix{
Address: addr,
PrefixLen: validPrefixLenForAutoGen,
})
}
}
// invalidateAutoGenAddress invalidates an auto-generated address.
//
// The NIC that ndp belongs to MUST be locked.
@@ -1118,6 +1186,14 @@ func (ndp *ndpState) cleanupAutoGenAddrResourcesAndNotify(addr tcpip.Address) bo
return false
}
if state.deprecationTimer != nil {
state.deprecationTimer.Stop()
state.deprecationTimer = nil
*state.doNotDeprecate = true
}
state.doNotDeprecate = nil
if state.invalidationTimer != nil {
state.invalidationTimer.Stop()
state.invalidationTimer = nil
@@ -1138,6 +1214,33 @@ func (ndp *ndpState) cleanupAutoGenAddrResourcesAndNotify(addr tcpip.Address) bo
return true
}
// autoGenAddrDeprecationTimer returns a new deprecation timer for an
// auto-generated address that fires after pl.
//
// doNotDeprecate is used to inform the timer when it fires at the same time
// that an auto-generated address's preferred lifetime gets refreshed. See
// autoGenAddrState.doNotDeprecate for more details.
func (ndp *ndpState) autoGenAddrDeprecationTimer(addr tcpip.Address, pl time.Duration, doNotDeprecate *bool) *time.Timer {
return time.AfterFunc(pl, func() {
ndp.nic.mu.Lock()
defer ndp.nic.mu.Unlock()
if *doNotDeprecate {
*doNotDeprecate = false
return
}
addrState, ok := ndp.autoGenAddresses[addr]
if !ok {
log.Fatalf("ndp: must have an autoGenAddressess entry for the SLAAC generated IPv6 address %s", addr)
}
addrState.ref.deprecated = true
ndp.notifyAutoGenAddressDeprecated(addr)
addrState.deprecationTimer = nil
ndp.autoGenAddresses[addr] = addrState
})
}
// autoGenAddrInvalidationTimer returns a new invalidation timer for an
// auto-generated address that fires after vl.
//
File diff suppressed because it is too large Load Diff
+87 -6
View File
@@ -249,17 +249,47 @@ func (n *NIC) setSpoofing(enable bool) {
// primaryEndpoint returns the primary endpoint of n for the given network
// protocol.
//
// primaryEndpoint will return the first non-deprecated endpoint if such an
// endpoint exists. If no non-deprecated endpoint exists, the first deprecated
// endpoint will be returned.
func (n *NIC) primaryEndpoint(protocol tcpip.NetworkProtocolNumber) *referencedNetworkEndpoint {
n.mu.RLock()
defer n.mu.RUnlock()
var deprecatedEndpoint *referencedNetworkEndpoint
for _, r := range n.primary[protocol] {
if r.isValidForOutgoing() && r.tryIncRef() {
return r
if !r.isValidForOutgoing() {
continue
}
if !r.deprecated {
if r.tryIncRef() {
// r is not deprecated, so return it immediately.
//
// If we kept track of a deprecated endpoint, decrement its reference
// count since it was incremented when we decided to keep track of it.
if deprecatedEndpoint != nil {
deprecatedEndpoint.decRefLocked()
deprecatedEndpoint = nil
}
return r
}
} else if deprecatedEndpoint == nil && r.tryIncRef() {
// We prefer an endpoint that is not deprecated, but we keep track of r in
// case n doesn't have any non-deprecated endpoints.
//
// If we end up finding a more preferred endpoint, r's reference count
// will be decremented when such an endpoint is found.
deprecatedEndpoint = r
}
}
return nil
// n doesn't have any valid non-deprecated endpoints, so return
// deprecatedEndpoint (which may be nil if n doesn't have any valid deprecated
// endpoints either).
return deprecatedEndpoint
}
// hasPermanentAddrLocked returns true if n has a permanent (including currently
@@ -367,7 +397,7 @@ func (n *NIC) getRefOrCreateTemp(protocol tcpip.NetworkProtocolNumber, address t
Address: address,
PrefixLen: netProto.DefaultPrefixLen(),
},
}, peb, temporary, static)
}, peb, temporary, static, false)
n.mu.Unlock()
return ref
@@ -416,10 +446,10 @@ func (n *NIC) addPermanentAddressLocked(protocolAddress tcpip.ProtocolAddress, p
}
}
return n.addAddressLocked(protocolAddress, peb, permanent, static)
return n.addAddressLocked(protocolAddress, peb, permanent, static, false)
}
func (n *NIC) addAddressLocked(protocolAddress tcpip.ProtocolAddress, peb PrimaryEndpointBehavior, kind networkEndpointKind, configType networkEndpointConfigType) (*referencedNetworkEndpoint, *tcpip.Error) {
func (n *NIC) addAddressLocked(protocolAddress tcpip.ProtocolAddress, peb PrimaryEndpointBehavior, kind networkEndpointKind, configType networkEndpointConfigType, deprecated bool) (*referencedNetworkEndpoint, *tcpip.Error) {
// TODO(b/141022673): Validate IP address before adding them.
// Sanity check.
@@ -455,6 +485,7 @@ func (n *NIC) addAddressLocked(protocolAddress tcpip.ProtocolAddress, peb Primar
protocol: protocolAddress.Protocol,
kind: kind,
configType: configType,
deprecated: deprecated,
}
// Set up cache if link address resolution exists for this protocol.
@@ -553,6 +584,51 @@ func (n *NIC) PrimaryAddresses() []tcpip.ProtocolAddress {
return addrs
}
// primaryAddress returns the primary address associated with this NIC.
//
// primaryAddress will return the first non-deprecated address if such an
// address exists. If no non-deprecated address exists, the first deprecated
// address will be returned.
func (n *NIC) primaryAddress(proto tcpip.NetworkProtocolNumber) tcpip.AddressWithPrefix {
n.mu.RLock()
defer n.mu.RUnlock()
list, ok := n.primary[proto]
if !ok {
return tcpip.AddressWithPrefix{}
}
var deprecatedEndpoint *referencedNetworkEndpoint
for _, ref := range list {
// Don't include tentative, expired or tempory endpoints to avoid confusion
// and prevent the caller from using those.
switch ref.getKind() {
case permanentTentative, permanentExpired, temporary:
continue
}
if !ref.deprecated {
return tcpip.AddressWithPrefix{
Address: ref.ep.ID().LocalAddress,
PrefixLen: ref.ep.PrefixLen(),
}
}
if deprecatedEndpoint == nil {
deprecatedEndpoint = ref
}
}
if deprecatedEndpoint != nil {
return tcpip.AddressWithPrefix{
Address: deprecatedEndpoint.ep.ID().LocalAddress,
PrefixLen: deprecatedEndpoint.ep.PrefixLen(),
}
}
return tcpip.AddressWithPrefix{}
}
// AddAddressRange adds a range of addresses to n, so that it starts accepting
// packets targeted at the given addresses and network protocol. The range is
// given by a subnet address, and all addresses contained in the subnet are
@@ -1109,6 +1185,11 @@ type referencedNetworkEndpoint struct {
// configType is the method that was used to configure this endpoint.
// This must never change after the endpoint is added to a NIC.
configType networkEndpointConfigType
// deprecated indicates whether or not the endpoint should be considered
// deprecated. That is, when deprecated is true, other endpoints that are not
// deprecated should be preferred.
deprecated bool
}
func (r *referencedNetworkEndpoint) getKind() networkEndpointKind {
+6 -9
View File
@@ -1036,9 +1036,11 @@ func (s *Stack) AllAddresses() map[tcpip.NICID][]tcpip.ProtocolAddress {
return nics
}
// GetMainNICAddress returns the first primary address and prefix for the given
// NIC and protocol. Returns an error if the NIC doesn't exist and an empty
// value if the NIC doesn't have a primary address for the given protocol.
// GetMainNICAddress returns the first non-deprecated primary address and prefix
// for the given NIC and protocol. If no non-deprecated primary address exists,
// a deprecated primary address and prefix will be returned. Returns an error if
// the NIC doesn't exist and an empty value if the NIC doesn't have a primary
// address for the given protocol.
func (s *Stack) GetMainNICAddress(id tcpip.NICID, protocol tcpip.NetworkProtocolNumber) (tcpip.AddressWithPrefix, *tcpip.Error) {
s.mu.RLock()
defer s.mu.RUnlock()
@@ -1048,12 +1050,7 @@ func (s *Stack) GetMainNICAddress(id tcpip.NICID, protocol tcpip.NetworkProtocol
return tcpip.AddressWithPrefix{}, tcpip.ErrUnknownNICID
}
for _, a := range nic.PrimaryAddresses() {
if a.Protocol == protocol {
return a.AddressWithPrefix, nil
}
}
return tcpip.AddressWithPrefix{}, nil
return nic.primaryAddress(protocol), nil
}
func (s *Stack) getRefEP(nic *NIC, localAddr tcpip.Address, netProto tcpip.NetworkProtocolNumber) (ref *referencedNetworkEndpoint) {