Regenerate SLAAC address on conflicts with the NIC

If the NIC already has a generated SLAAC address, regenerate a new SLAAC
address until one is generated that does not conflict with the NIC's
existing addresses, up to a maximum of 10 attempts.

This applies to both stable and temporary SLAAC addresses.

Test: stack_test.TestMixedSLAACAddrConflictRegen
PiperOrigin-RevId: 309495628
This commit is contained in:
Ghanan Gowripalan 2020-05-01 16:32:15 -07:00 committed by gVisor bot
parent 5e1e61fbcb
commit 40d6aae122
2 changed files with 299 additions and 51 deletions

View File

@ -145,6 +145,10 @@ const (
// minRegenAdvanceDuration is the minimum duration before the deprecation
// of a temporary address when a new address will be generated.
minRegenAdvanceDuration = time.Duration(0)
// maxSLAACAddrLocalRegenAttempts is the maximum number of times to attempt
// SLAAC address regenerations in response to a NIC-local conflict.
maxSLAACAddrLocalRegenAttempts = 10
)
var (
@ -565,11 +569,18 @@ type slaacPrefixState struct {
// Nonzero only when the address is not preferred forever.
preferredUntil time.Time
// The endpoint for the stable address generated for a SLAAC prefix.
//
// May only be nil when a SLAAC address is being (re-)generated. Otherwise,
// must not be nil as all SLAAC prefixes must have a stable address.
stableAddrRef *referencedNetworkEndpoint
// State associated with the stable address generated for the prefix.
stableAddr struct {
// The address's endpoint.
//
// May only be nil when the address is being (re-)generated. Otherwise,
// must not be nil as all SLAAC prefixes must have a stable address.
ref *referencedNetworkEndpoint
// The number of times an address has been generated locally where the NIC
// already had the generated address.
localGenerationFailures uint8
}
// The temporary (short-lived) addresses generated for the SLAAC prefix.
tempAddrs map[tcpip.Address]tempSLAACAddrState
@ -579,7 +590,7 @@ type slaacPrefixState struct {
// in the generation and DAD process at any time. That is, no two addresses
// will be generated at the same time for a given SLAAC prefix.
// The number of times an address has been generated.
// The number of times an address has been generated and added to the NIC.
//
// Addresses may be regenerated in reseponse to a DAD conflicts.
generationAttempts uint8
@ -1125,7 +1136,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the deprecated SLAAC prefix %s", prefix))
}
ndp.deprecateSLAACAddress(state.stableAddrRef)
ndp.deprecateSLAACAddress(state.stableAddr.ref)
}),
invalidationTimer: tcpip.NewCancellableTimer(&ndp.nic.mu, func() {
state, ok := ndp.slaacPrefixes[prefix]
@ -1166,7 +1177,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
}
// If the address is assigned (DAD resolved), generate a temporary address.
if state.stableAddrRef.getKind() == permanent {
if state.stableAddr.ref.getKind() == permanent {
// Reset the generation attempts counter as we are starting the generation
// of a new address for the SLAAC prefix.
ndp.generateTempSLAACAddr(prefix, &state, true /* resetGenAttempts */)
@ -1179,11 +1190,6 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) addSLAACAddr(addr tcpip.AddressWithPrefix, configType networkEndpointConfigType, deprecated bool) *referencedNetworkEndpoint {
// If the nic already has this address, do nothing further.
if ndp.nic.hasPermanentAddrLocked(addr.Address) {
return nil
}
// Inform the integrator that we have a new SLAAC address.
ndpDisp := ndp.nic.stack.ndpDisp
if ndpDisp == nil {
@ -1216,7 +1222,7 @@ func (ndp *ndpState) addSLAACAddr(addr tcpip.AddressWithPrefix, configType netwo
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixState) bool {
if r := state.stableAddrRef; r != nil {
if r := state.stableAddr.ref; r != nil {
panic(fmt.Sprintf("ndp: SLAAC prefix %s already has a permenant address %s", prefix, r.addrWithPrefix()))
}
@ -1226,42 +1232,62 @@ func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixSt
return false
}
var generatedAddr tcpip.AddressWithPrefix
addrBytes := []byte(prefix.ID())
if oIID := ndp.nic.stack.opaqueIIDOpts; oIID.NICNameFromID != nil {
addrBytes = header.AppendOpaqueInterfaceIdentifier(
addrBytes[:header.IIDOffsetInIPv6Address],
prefix,
oIID.NICNameFromID(ndp.nic.ID(), ndp.nic.name),
state.generationAttempts,
oIID.SecretKey,
)
} else if state.generationAttempts == 0 {
// Only attempt to generate an interface-specific IID if we have a valid
// link address.
//
// TODO(b/141011931): Validate a LinkEndpoint's link address (provided by
// LinkEndpoint.LinkAddress) before reaching this point.
linkAddr := ndp.nic.linkEP.LinkAddress()
if !header.IsValidUnicastEthernetAddress(linkAddr) {
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
}
// Generate an address within prefix from the modified EUI-64 of ndp's NIC's
// Ethernet MAC address.
header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, addrBytes[header.IIDOffsetInIPv6Address:])
} else {
// We have no way to regenerate an address when addresses are not generated
// with opaque IIDs.
return false
}
dadCounter := state.generationAttempts + state.stableAddr.localGenerationFailures
if oIID := ndp.nic.stack.opaqueIIDOpts; oIID.NICNameFromID != nil {
addrBytes = header.AppendOpaqueInterfaceIdentifier(
addrBytes[:header.IIDOffsetInIPv6Address],
prefix,
oIID.NICNameFromID(ndp.nic.ID(), ndp.nic.name),
dadCounter,
oIID.SecretKey,
)
} else if dadCounter == 0 {
// Modified-EUI64 based IIDs have no way to resolve DAD conflicts, so if
// the DAD counter is non-zero, we cannot use this method.
//
// Only attempt to generate an interface-specific IID if we have a valid
// link address.
//
// TODO(b/141011931): Validate a LinkEndpoint's link address (provided by
// LinkEndpoint.LinkAddress) before reaching this point.
linkAddr := ndp.nic.linkEP.LinkAddress()
if !header.IsValidUnicastEthernetAddress(linkAddr) {
return false
}
generatedAddr := tcpip.AddressWithPrefix{
Address: tcpip.Address(addrBytes),
PrefixLen: validPrefixLenForAutoGen,
// Generate an address within prefix from the modified EUI-64 of ndp's
// NIC's Ethernet MAC address.
header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, addrBytes[header.IIDOffsetInIPv6Address:])
} else {
// We have no way to regenerate an address in response to an address
// conflict when addresses are not generated with opaque IIDs.
return false
}
generatedAddr = tcpip.AddressWithPrefix{
Address: tcpip.Address(addrBytes),
PrefixLen: validPrefixLenForAutoGen,
}
if !ndp.nic.hasPermanentAddrLocked(generatedAddr.Address) {
break
}
state.stableAddr.localGenerationFailures++
}
if ref := ndp.addSLAACAddr(generatedAddr, slaac, time.Since(state.preferredUntil) >= 0 /* deprecated */); ref != nil {
state.stableAddrRef = ref
state.stableAddr.ref = ref
state.generationAttempts++
return true
}
@ -1315,7 +1341,7 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
return false
}
stableAddr := prefixState.stableAddrRef.ep.ID().LocalAddress
stableAddr := prefixState.stableAddr.ref.ep.ID().LocalAddress
now := time.Now()
// As per RFC 4941 section 3.3 step 4, the valid lifetime of a temporary
@ -1354,7 +1380,20 @@ func (ndp *ndpState) generateTempSLAACAddr(prefix tcpip.Subnet, prefixState *sla
return false
}
generatedAddr := header.GenerateTempIPv6SLAACAddr(ndp.temporaryIIDHistory[:], stableAddr)
// Attempt to generate a new address that is not already assigned to the NIC.
var generatedAddr tcpip.AddressWithPrefix
for i := 0; ; i++ {
// If we were unable to generate an address after the maximum SLAAC address
// local regeneration attempts, do nothing further.
if i == maxSLAACAddrLocalRegenAttempts {
return false
}
generatedAddr = header.GenerateTempIPv6SLAACAddr(ndp.temporaryIIDHistory[:], stableAddr)
if !ndp.nic.hasPermanentAddrLocked(generatedAddr.Address) {
break
}
}
// As per RFC RFC 4941 section 3.3 step 5, we MUST NOT create a temporary
// address with a zero preferred lifetime. The checks above ensure this
@ -1450,9 +1489,9 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat
// If the preferred lifetime is zero, then the prefix should be deprecated.
deprecated := pl == 0
if deprecated {
ndp.deprecateSLAACAddress(prefixState.stableAddrRef)
ndp.deprecateSLAACAddress(prefixState.stableAddr.ref)
} else {
prefixState.stableAddrRef.deprecated = false
prefixState.stableAddr.ref.deprecated = false
}
// If prefix was preferred for some finite lifetime before, stop the
@ -1514,7 +1553,7 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, prefixStat
// If DAD is not yet complete on the stable address, there is no need to do
// work with temporary addresses.
if prefixState.stableAddrRef.getKind() != permanent {
if prefixState.stableAddr.ref.getKind() != permanent {
return
}
@ -1617,7 +1656,7 @@ func (ndp *ndpState) deprecateSLAACAddress(ref *referencedNetworkEndpoint) {
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefixState) {
if r := state.stableAddrRef; r != nil {
if r := state.stableAddr.ref; r != nil {
// Since we are already invalidating the prefix, do not invalidate the
// prefix when removing the address.
if err := ndp.nic.removePermanentIPv6EndpointLocked(r, false /* allowSLAACInvalidation */); err != nil {
@ -1639,14 +1678,14 @@ func (ndp *ndpState) cleanupSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPr
prefix := addr.Subnet()
state, ok := ndp.slaacPrefixes[prefix]
if !ok || state.stableAddrRef == nil || addr.Address != state.stableAddrRef.ep.ID().LocalAddress {
if !ok || state.stableAddr.ref == nil || addr.Address != state.stableAddr.ref.ep.ID().LocalAddress {
return
}
if !invalidatePrefix {
// If the prefix is not being invalidated, disassociate the address from the
// prefix and do nothing further.
state.stableAddrRef = nil
state.stableAddr.ref = nil
ndp.slaacPrefixes[prefix] = state
return
}
@ -1665,7 +1704,7 @@ func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaa
ndp.invalidateTempSLAACAddr(state.tempAddrs, tempAddr, tempAddrState)
}
state.stableAddrRef = nil
state.stableAddr.ref = nil
state.deprecationTimer.StopLocked()
state.invalidationTimer.StopLocked()
delete(ndp.slaacPrefixes, prefix)

View File

@ -2521,6 +2521,215 @@ func TestAutoGenTempAddrRegenTimerUpdates(t *testing.T) {
expectAutoGenAddrEventAsync(tempAddr3, newAddr, regenAfter+defaultAsyncEventTimeout)
}
// TestMixedSLAACAddrConflictRegen tests SLAAC address regeneration in response
// to a mix of DAD conflicts and NIC-local conflicts.
func TestMixedSLAACAddrConflictRegen(t *testing.T) {
const (
nicID = 1
nicName = "nic"
lifetimeSeconds = 9999
// From stack.maxSLAACAddrLocalRegenAttempts
maxSLAACAddrLocalRegenAttempts = 10
// We use 2 more addreses than the maximum local regeneration attempts
// because we want to also trigger regeneration in response to a DAD
// conflicts for this test.
maxAddrs = maxSLAACAddrLocalRegenAttempts + 2
dupAddrTransmits = 1
retransmitTimer = time.Second
)
var tempIIDHistoryWithModifiedEUI64 [header.IIDSize]byte
header.InitialTempIID(tempIIDHistoryWithModifiedEUI64[:], nil, nicID)
var tempIIDHistoryWithOpaqueIID [header.IIDSize]byte
header.InitialTempIID(tempIIDHistoryWithOpaqueIID[:], nil, nicID)
prefix, subnet, stableAddrWithModifiedEUI64 := prefixSubnetAddr(0, linkAddr1)
var stableAddrsWithOpaqueIID [maxAddrs]tcpip.AddressWithPrefix
var tempAddrsWithOpaqueIID [maxAddrs]tcpip.AddressWithPrefix
var tempAddrsWithModifiedEUI64 [maxAddrs]tcpip.AddressWithPrefix
addrBytes := []byte(subnet.ID())
for i := 0; i < maxAddrs; i++ {
stableAddrsWithOpaqueIID[i] = tcpip.AddressWithPrefix{
Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, uint8(i), nil)),
PrefixLen: header.IIDOffsetInIPv6Address * 8,
}
// When generating temporary addresses, the resolved stable address for the
// SLAAC prefix will be the first address stable address generated for the
// prefix as we will not simulate address conflicts for the stable addresses
// in tests involving temporary addresses. Address conflicts for stable
// addresses will be done in their own tests.
tempAddrsWithOpaqueIID[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistoryWithOpaqueIID[:], stableAddrsWithOpaqueIID[0].Address)
tempAddrsWithModifiedEUI64[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistoryWithModifiedEUI64[:], stableAddrWithModifiedEUI64.Address)
}
tests := []struct {
name string
addrs []tcpip.AddressWithPrefix
tempAddrs bool
initialExpect tcpip.AddressWithPrefix
nicNameFromID func(tcpip.NICID, string) string
}{
{
name: "Stable addresses with opaque IIDs",
addrs: stableAddrsWithOpaqueIID[:],
nicNameFromID: func(tcpip.NICID, string) string {
return nicName
},
},
{
name: "Temporary addresses with opaque IIDs",
addrs: tempAddrsWithOpaqueIID[:],
tempAddrs: true,
initialExpect: stableAddrsWithOpaqueIID[0],
nicNameFromID: func(tcpip.NICID, string) string {
return nicName
},
},
{
name: "Temporary addresses with modified EUI64",
addrs: tempAddrsWithModifiedEUI64[:],
tempAddrs: true,
initialExpect: stableAddrWithModifiedEUI64,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
ndpDisp := ndpDispatcher{
autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
}
e := channel.New(0, 1280, linkAddr1)
ndpConfigs := stack.NDPConfigurations{
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenTempGlobalAddresses: test.tempAddrs,
AutoGenAddressConflictRetries: 1,
}
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
TransportProtocols: []stack.TransportProtocol{udp.NewProtocol()},
NDPConfigs: ndpConfigs,
NDPDisp: &ndpDisp,
OpaqueIIDOpts: stack.OpaqueInterfaceIdentifierOptions{
NICNameFromID: test.nicNameFromID,
},
})
s.SetRouteTable([]tcpip.Route{{
Destination: header.IPv6EmptySubnet,
Gateway: llAddr2,
NIC: nicID,
}})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
for j := 0; j < len(test.addrs)-1; j++ {
// The NIC will not attempt to generate an address in response to a
// NIC-local conflict after some maximum number of attempts. We skip
// creating a conflict for the address that would be generated as part
// of the last attempt so we can simulate a DAD conflict for this
// address and restart the NIC-local generation process.
if j == maxSLAACAddrLocalRegenAttempts-1 {
continue
}
if err := s.AddAddress(nicID, ipv6.ProtocolNumber, test.addrs[j].Address); err != nil {
t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, test.addrs[j].Address, err)
}
}
expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
t.Helper()
select {
case e := <-ndpDisp.autoGenAddrC:
if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
}
default:
t.Fatal("expected addr auto gen event")
}
}
expectAutoGenAddrAsyncEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) {
t.Helper()
select {
case e := <-ndpDisp.autoGenAddrC:
if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" {
t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
}
case <-time.After(defaultAsyncEventTimeout):
t.Fatal("timed out waiting for addr auto gen event")
}
}
expectDADEventAsync := func(addr tcpip.Address) {
t.Helper()
select {
case e := <-ndpDisp.dadC:
if diff := checkDADEvent(e, nicID, addr, true, nil); diff != "" {
t.Errorf("dad event mismatch (-want +got):\n%s", diff)
}
case <-time.After(dupAddrTransmits*retransmitTimer + defaultAsyncEventTimeout):
t.Fatal("timed out waiting for DAD event")
}
}
// Enable DAD.
ndpDisp.dadC = make(chan ndpDADEvent, 2)
ndpConfigs.DupAddrDetectTransmits = dupAddrTransmits
ndpConfigs.RetransmitTimer = retransmitTimer
if err := s.SetNDPConfigurations(nicID, ndpConfigs); err != nil {
t.Fatalf("s.SetNDPConfigurations(%d, _): %s", nicID, err)
}
// Do SLAAC for prefix.
e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
if test.initialExpect != (tcpip.AddressWithPrefix{}) {
expectAutoGenAddrEvent(test.initialExpect, newAddr)
expectDADEventAsync(test.initialExpect.Address)
}
// The last local generation attempt should succeed, but we introduce a
// DAD failure to restart the local generation process.
addr := test.addrs[maxSLAACAddrLocalRegenAttempts-1]
expectAutoGenAddrAsyncEvent(addr, newAddr)
if err := s.DupTentativeAddrDetected(nicID, addr.Address); err != nil {
t.Fatalf("s.DupTentativeAddrDetected(%d, %s): %s", nicID, addr.Address, err)
}
select {
case e := <-ndpDisp.dadC:
if diff := checkDADEvent(e, nicID, addr.Address, false, nil); diff != "" {
t.Errorf("dad event mismatch (-want +got):\n%s", diff)
}
default:
t.Fatal("expected DAD event")
}
expectAutoGenAddrEvent(addr, invalidatedAddr)
// The last address generated should resolve DAD.
addr = test.addrs[len(test.addrs)-1]
expectAutoGenAddrAsyncEvent(addr, newAddr)
expectDADEventAsync(addr.Address)
select {
case e := <-ndpDisp.autoGenAddrC:
t.Fatalf("unexpected auto gen addr event = %+v", e)
default:
}
})
}
}
// stackAndNdpDispatcherWithDefaultRoute returns an ndpDispatcher,
// channel.Endpoint and stack.Stack.
//