Attempt SLAAC address regeneration on DAD conflicts

As per RFC 7217 section 6, attempt to regenerate IPv6 SLAAC address in response
to a DAD conflict if the address was generated with an opaque IID as outlined in
RFC 7217 section 5.

Test:
- stack_test.TestAutoGenAddrWithOpaqueIIDDADRetries
- stack_test.TestAutoGenAddrWithEUI64IIDNoDADRetries
- stack_test.TestAutoGenAddrContinuesLifetimesAfterRetry
PiperOrigin-RevId: 306555645
This commit is contained in:
Ghanan Gowripalan
2020-04-14 18:32:35 -07:00
committed by gVisor bot
parent 2dd6384de8
commit 36fbaac520
4 changed files with 707 additions and 111 deletions
+40 -26
View File
@@ -34,6 +34,7 @@ const (
// The least significant 3 bytes are the same as addr2 so both addr2 and
// addr3 will have the same solicited-node address.
addr3 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x02"
addr4 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x03"
// Tests use the extension header identifier values as uint8 instead of
// header.IPv6ExtensionHeaderIdentifier.
@@ -167,6 +168,8 @@ func TestReceiveOnAllNodesMulticastAddr(t *testing.T) {
// packets destined to the IPv6 solicited-node address of an assigned IPv6
// address.
func TestReceiveOnSolicitedNodeAddr(t *testing.T) {
const nicID = 1
tests := []struct {
name string
protocolFactory stack.TransportProtocol
@@ -184,50 +187,61 @@ func TestReceiveOnSolicitedNodeAddr(t *testing.T) {
NetworkProtocols: []stack.NetworkProtocol{NewProtocol()},
TransportProtocols: []stack.TransportProtocol{test.protocolFactory},
})
e := channel.New(10, 1280, linkAddr1)
if err := s.CreateNIC(1, e); err != nil {
t.Fatalf("CreateNIC(_) = %s", err)
e := channel.New(1, 1280, linkAddr1)
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
// Should not receive a packet destined to the solicited
// node address of addr2/addr3 yet as we haven't added
// those addresses.
s.SetRouteTable([]tcpip.Route{
tcpip.Route{
Destination: header.IPv6EmptySubnet,
NIC: nicID,
},
})
// Should not receive a packet destined to the solicited node address of
// addr2/addr3 yet as we haven't added those addresses.
test.rxf(t, s, e, addr1, snmc, 0)
if err := s.AddAddress(1, ProtocolNumber, addr2); err != nil {
t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, addr2, err)
if err := s.AddAddress(nicID, ProtocolNumber, addr2); err != nil {
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, addr2, err)
}
// Should receive a packet destined to the solicited
// node address of addr2/addr3 now that we have added
// added addr2.
// Should receive a packet destined to the solicited node address of
// addr2/addr3 now that we have added added addr2.
test.rxf(t, s, e, addr1, snmc, 1)
if err := s.AddAddress(1, ProtocolNumber, addr3); err != nil {
t.Fatalf("AddAddress(_, %d, %s) = %s", ProtocolNumber, addr3, err)
if err := s.AddAddress(nicID, ProtocolNumber, addr3); err != nil {
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, addr3, err)
}
// Should still receive a packet destined to the
// solicited node address of addr2/addr3 now that we
// have added addr3.
// Should still receive a packet destined to the solicited node address of
// addr2/addr3 now that we have added addr3.
test.rxf(t, s, e, addr1, snmc, 2)
if err := s.RemoveAddress(1, addr2); err != nil {
t.Fatalf("RemoveAddress(_, %s) = %s", addr2, err)
if err := s.RemoveAddress(nicID, addr2); err != nil {
t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr2, err)
}
// Should still receive a packet destined to the
// solicited node address of addr2/addr3 now that we
// have removed addr2.
// Should still receive a packet destined to the solicited node address of
// addr2/addr3 now that we have removed addr2.
test.rxf(t, s, e, addr1, snmc, 3)
if err := s.RemoveAddress(1, addr3); err != nil {
t.Fatalf("RemoveAddress(_, %s) = %s", addr3, err)
// Make sure addr3's endpoint does not get removed from the NIC by
// incrementing its reference count with a route.
r, err := s.FindRoute(nicID, addr3, addr4, ProtocolNumber, false)
if err != nil {
t.Fatalf("FindRoute(%d, %s, %s, %d, false): %s", nicID, addr3, addr4, ProtocolNumber, err)
}
defer r.Release()
if err := s.RemoveAddress(nicID, addr3); err != nil {
t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr3, err)
}
// Should not receive a packet destined to the solicited
// node address of addr2/addr3 yet as both of them got
// removed.
// Should not receive a packet destined to the solicited node address of
// addr2/addr3 yet as both of them got removed, even though a route using
// addr3 exists.
test.rxf(t, s, e, addr1, snmc, 3)
})
}
+157 -61
View File
@@ -305,6 +305,15 @@ type NDPConfigurations struct {
// lifetime(s) of the generated address changes; this option only
// affects the generation of new addresses as part of SLAAC.
AutoGenGlobalAddresses bool
// AutoGenAddressConflictRetries determines how many times to attempt to retry
// generation of a permanent auto-generated address in response to DAD
// conflicts.
//
// If the method used to generate the address does not support creating
// alternative addresses (e.g. IIDs based on the modified EUI64 of a NIC's
// MAC address), then no attempt will be made to resolve the conflict.
AutoGenAddressConflictRetries uint8
}
// DefaultNDPConfigurations returns an NDPConfigurations populated with
@@ -411,8 +420,23 @@ type slaacPrefixState struct {
// Nonzero only when the address is not valid forever.
validUntil time.Time
// Nonzero only when the address is not preferred forever.
preferredUntil time.Time
// The prefix's permanent address endpoint.
//
// May only be nil when a SLAAC address is being (re-)generated. Otherwise,
// must not be nil as all SLAAC prefixes must have a SLAAC address.
ref *referencedNetworkEndpoint
// The number of times a permanent address has been generated for the prefix.
//
// Addresses may be regenerated in reseponse to a DAD conflicts.
generationAttempts uint8
// The maximum number of times to attempt regeneration of a permanent SLAAC
// address in response to DAD conflicts.
maxGenerationAttempts uint8
}
// startDuplicateAddressDetection performs Duplicate Address Detection.
@@ -935,60 +959,83 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) {
return
}
// If the preferred lifetime is zero, then the prefix should be considered
// deprecated.
deprecated := pl == 0
ref := ndp.addSLAACAddr(prefix, deprecated)
if ref == nil {
// We were unable to generate a permanent address for prefix so do nothing
// further as there is no reason to maintain state for a SLAAC prefix we
// cannot generate a permanent address for.
return
}
state := slaacPrefixState{
deprecationTimer: tcpip.MakeCancellableTimer(&ndp.nic.mu, func() {
prefixState, ok := ndp.slaacPrefixes[prefix]
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the SLAAC prefix %s", prefix))
panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the deprecated SLAAC prefix %s", prefix))
}
ndp.deprecateSLAACAddress(prefixState.ref)
ndp.deprecateSLAACAddress(state.ref)
}),
invalidationTimer: tcpip.MakeCancellableTimer(&ndp.nic.mu, func() {
ndp.invalidateSLAACPrefix(prefix, true)
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)
}),
ref: ref,
maxGenerationAttempts: ndp.configs.AutoGenAddressConflictRetries + 1,
}
now := time.Now()
// The time an address is preferred until is needed to properly generate the
// address.
if pl < header.NDPInfiniteLifetime {
state.preferredUntil = now.Add(pl)
}
if !ndp.generateSLAACAddr(prefix, &state) {
// We were unable to generate an address for the prefix, we do not nothing
// further as there is no reason to maintain state or timers for a prefix we
// do not have an address for.
return
}
// Setup the initial timers to deprecate and invalidate prefix.
if !deprecated && pl < header.NDPInfiniteLifetime {
if pl < header.NDPInfiniteLifetime && pl != 0 {
state.deprecationTimer.Reset(pl)
}
if vl < header.NDPInfiniteLifetime {
state.invalidationTimer.Reset(vl)
state.validUntil = time.Now().Add(vl)
state.validUntil = now.Add(vl)
}
ndp.slaacPrefixes[prefix] = state
}
// addSLAACAddr adds a SLAAC address for prefix.
// generateSLAACAddr generates a SLAAC address for prefix.
//
// Returns true if an address was successfully generated.
//
// Panics if the prefix is not a SLAAC prefix or it already has an address.
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) addSLAACAddr(prefix tcpip.Subnet, deprecated bool) *referencedNetworkEndpoint {
func (ndp *ndpState) generateSLAACAddr(prefix tcpip.Subnet, state *slaacPrefixState) bool {
if r := state.ref; r != nil {
panic(fmt.Sprintf("ndp: SLAAC prefix %s already has a permenant address %s", prefix, r.addrWithPrefix()))
}
// If we have already reached the maximum address generation attempts for the
// prefix, do not generate another address.
if state.generationAttempts == state.maxGenerationAttempts {
return false
}
addrBytes := []byte(prefix.ID())
if oIID := ndp.nic.stack.opaqueIIDOpts; oIID.NICNameFromID != nil {
addrBytes = header.AppendOpaqueInterfaceIdentifier(
addrBytes[:header.IIDOffsetInIPv6Address],
prefix,
oIID.NICNameFromID(ndp.nic.ID(), ndp.nic.name),
0, /* dadCounter */
state.generationAttempts,
oIID.SecretKey,
)
} else {
} else if state.generationAttempts == 0 {
// Only attempt to generate an interface-specific IID if we have a valid
// link address.
//
@@ -996,12 +1043,16 @@ func (ndp *ndpState) addSLAACAddr(prefix tcpip.Subnet, deprecated bool) *referen
// LinkEndpoint.LinkAddress) before reaching this point.
linkAddr := ndp.nic.linkEP.LinkAddress()
if !header.IsValidUnicastEthernetAddress(linkAddr) {
return nil
return false
}
// Generate an address within prefix from the modified EUI-64 of ndp's NIC's
// Ethernet MAC address.
header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, addrBytes[header.IIDOffsetInIPv6Address:])
} else {
// We have no way to regenerate an address when addresses are not generated
// with opaque IIDs.
return false
}
generatedAddr := tcpip.ProtocolAddress{
@@ -1014,26 +1065,52 @@ func (ndp *ndpState) addSLAACAddr(prefix tcpip.Subnet, deprecated bool) *referen
// If the nic already has this address, do nothing further.
if ndp.nic.hasPermanentAddrLocked(generatedAddr.AddressWithPrefix.Address) {
return nil
return false
}
// Inform the integrator that we have a new SLAAC address.
ndpDisp := ndp.nic.stack.ndpDisp
if ndpDisp == nil {
return nil
return false
}
if !ndpDisp.OnAutoGenAddress(ndp.nic.ID(), generatedAddr.AddressWithPrefix) {
// Informed by the integrator not to add the address.
return nil
return false
}
deprecated := time.Since(state.preferredUntil) >= 0
ref, err := ndp.nic.addAddressLocked(generatedAddr, FirstPrimaryEndpoint, permanent, slaac, deprecated)
if err != nil {
panic(fmt.Sprintf("ndp: error when adding address %+v: %s", generatedAddr, err))
}
return ref
state.generationAttempts++
state.ref = ref
return true
}
// regenerateSLAACAddr regenerates an address for a SLAAC prefix.
//
// If generating a new address for the prefix fails, the prefix will be
// invalidated.
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) regenerateSLAACAddr(prefix tcpip.Subnet) {
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
panic(fmt.Sprintf("ndp: SLAAC prefix state not found to regenerate address for %s", prefix))
}
if ndp.generateSLAACAddr(prefix, &state) {
ndp.slaacPrefixes[prefix] = state
return
}
// We were unable to generate a permanent address for the SLAAC prefix so
// invalidate the prefix as there is no reason to maintain state for a
// SLAAC prefix we do not have an address for.
ndp.invalidateSLAACPrefix(prefix, state)
}
// refreshSLAACPrefixLifetimes refreshes the lifetimes of a SLAAC prefix.
@@ -1060,9 +1137,16 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, pl, vl tim
// deprecation timer so it can be reset.
prefixState.deprecationTimer.StopLocked()
now := time.Now()
// Reset the deprecation timer if prefix has a finite preferred lifetime.
if !deprecated && pl < header.NDPInfiniteLifetime {
prefixState.deprecationTimer.Reset(pl)
if pl < header.NDPInfiniteLifetime {
if !deprecated {
prefixState.deprecationTimer.Reset(pl)
}
prefixState.preferredUntil = now.Add(pl)
} else {
prefixState.preferredUntil = time.Time{}
}
// As per RFC 4862 section 5.5.3.e, update the valid lifetime for prefix:
@@ -1105,7 +1189,7 @@ func (ndp *ndpState) refreshSLAACPrefixLifetimes(prefix tcpip.Subnet, pl, vl tim
prefixState.invalidationTimer.StopLocked()
prefixState.invalidationTimer.Reset(effectiveVl)
prefixState.validUntil = time.Now().Add(effectiveVl)
prefixState.validUntil = now.Add(effectiveVl)
}
// deprecateSLAACAddress marks ref as deprecated and notifies the stack's NDP
@@ -1121,48 +1205,60 @@ func (ndp *ndpState) deprecateSLAACAddress(ref *referencedNetworkEndpoint) {
ref.deprecated = true
if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil {
ndpDisp.OnAutoGenAddressDeprecated(ndp.nic.ID(), tcpip.AddressWithPrefix{
Address: ref.ep.ID().LocalAddress,
PrefixLen: ref.ep.PrefixLen(),
})
ndpDisp.OnAutoGenAddressDeprecated(ndp.nic.ID(), ref.addrWithPrefix())
}
}
// invalidateSLAACPrefix invalidates a SLAAC prefix.
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, removeAddr bool) {
state, ok := ndp.slaacPrefixes[prefix]
if !ok {
return
}
state.deprecationTimer.StopLocked()
state.invalidationTimer.StopLocked()
delete(ndp.slaacPrefixes, prefix)
addr := state.ref.ep.ID().LocalAddress
if removeAddr {
if err := ndp.nic.removePermanentAddressLocked(addr); err != nil {
panic(fmt.Sprintf("ndp: removePermanentAddressLocked(%s): %s", addr, err))
func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefixState) {
if r := state.ref; r != nil {
// Since we are already invalidating the prefix, do not invalidate the
// prefix when removing the address.
if err := ndp.nic.removePermanentIPv6EndpointLocked(r, false /* allowSLAACPrefixInvalidation */); err != nil {
panic(fmt.Sprintf("ndp: removePermanentIPv6EndpointLocked(%s, false): %s", r.addrWithPrefix(), err))
}
}
if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil {
ndpDisp.OnAutoGenAddressInvalidated(ndp.nic.ID(), tcpip.AddressWithPrefix{
Address: addr,
PrefixLen: state.ref.ep.PrefixLen(),
})
}
ndp.cleanupSLAACPrefixResources(prefix, state)
}
// cleanupSLAACAddrResourcesAndNotify cleans up an invalidated SLAAC
// address's resources from ndp.
// cleanupSLAACAddrResourcesAndNotify cleans up an invalidated SLAAC address's
// resources.
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) cleanupSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPrefix) {
ndp.invalidateSLAACPrefix(addr.Subnet(), false)
func (ndp *ndpState) cleanupSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPrefix, invalidatePrefix bool) {
if ndpDisp := ndp.nic.stack.ndpDisp; ndpDisp != nil {
ndpDisp.OnAutoGenAddressInvalidated(ndp.nic.ID(), addr)
}
prefix := addr.Subnet()
state, ok := ndp.slaacPrefixes[prefix]
if !ok || state.ref == nil || addr.Address != state.ref.ep.ID().LocalAddress {
return
}
if !invalidatePrefix {
// If the prefix is not being invalidated, disassociate the address from the
// prefix and do nothing further.
state.ref = nil
ndp.slaacPrefixes[prefix] = state
return
}
ndp.cleanupSLAACPrefixResources(prefix, state)
}
// cleanupSLAACPrefixResources cleansup a SLAAC prefix's timers and entry.
//
// Panics if the SLAAC prefix is not known.
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaacPrefixState) {
state.deprecationTimer.StopLocked()
state.invalidationTimer.StopLocked()
delete(ndp.slaacPrefixes, prefix)
}
// cleanupState cleans up ndp's state.
@@ -1181,7 +1277,7 @@ func (ndp *ndpState) cleanupSLAACAddrResourcesAndNotify(addr tcpip.AddressWithPr
func (ndp *ndpState) cleanupState(hostOnly bool) {
linkLocalSubnet := header.IPv6LinkLocalPrefix.Subnet()
linkLocalPrefixes := 0
for prefix := range ndp.slaacPrefixes {
for prefix, state := range ndp.slaacPrefixes {
// RFC 4862 section 5 states that routers are also expected to generate a
// link-local address so we do not invalidate them if we are cleaning up
// host-only state.
@@ -1190,7 +1286,7 @@ func (ndp *ndpState) cleanupState(hostOnly bool) {
continue
}
ndp.invalidateSLAACPrefix(prefix, true)
ndp.invalidateSLAACPrefix(prefix, state)
}
if got := len(ndp.slaacPrefixes); got != linkLocalPrefixes {
+461
View File
@@ -623,6 +623,12 @@ func TestDADFail(t *testing.T) {
if want := (tcpip.AddressWithPrefix{}); addr != want {
t.Fatalf("got stack.GetMainNICAddress(%d, %d) = (%s, nil), want = (%s, nil)", nicID, header.IPv6ProtocolNumber, addr, want)
}
// Attempting to add the address again should not fail if the address's
// state was cleaned up when DAD failed.
if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr1); err != nil {
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr1, err)
}
})
}
}
@@ -2783,6 +2789,461 @@ func TestAutoGenAddrWithOpaqueIID(t *testing.T) {
}
}
// TestAutoGenAddrWithOpaqueIIDDADRetries tests the regeneration of an
// auto-generated IPv6 address in response to a DAD conflict.
func TestAutoGenAddrWithOpaqueIIDDADRetries(t *testing.T) {
const nicID = 1
const nicName = "nic"
const dadTransmits = 1
const retransmitTimer = time.Second
const maxMaxRetries = 3
const lifetimeSeconds = 10
var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes]byte
secretKey := secretKeyBuf[:]
n, err := rand.Read(secretKey)
if err != nil {
t.Fatalf("rand.Read(_): %s", err)
}
if n != header.OpaqueIIDSecretKeyMinBytes {
t.Fatalf("got rand.Read(_) = (%d, _), want = (%d, _)", n, header.OpaqueIIDSecretKeyMinBytes)
}
prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
for maxRetries := uint8(0); maxRetries <= maxMaxRetries; maxRetries++ {
for numFailures := uint8(0); numFailures <= maxRetries+1; numFailures++ {
addrTypes := []struct {
name string
ndpConfigs stack.NDPConfigurations
autoGenLinkLocal bool
subnet tcpip.Subnet
triggerSLAACFn func(e *channel.Endpoint)
}{
{
name: "Global address",
ndpConfigs: stack.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenAddressConflictRetries: maxRetries,
},
subnet: subnet,
triggerSLAACFn: func(e *channel.Endpoint) {
// Receive an RA with prefix1 in a PI.
e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
},
},
{
name: "LinkLocal address",
ndpConfigs: stack.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
AutoGenAddressConflictRetries: maxRetries,
},
autoGenLinkLocal: true,
subnet: header.IPv6LinkLocalPrefix.Subnet(),
triggerSLAACFn: func(e *channel.Endpoint) {},
},
}
for _, addrType := range addrTypes {
maxRetries := maxRetries
numFailures := numFailures
addrType := addrType
t.Run(fmt.Sprintf("%s with %d max retries and %d failures", addrType.name, maxRetries, numFailures), func(t *testing.T) {
t.Parallel()
ndpDisp := ndpDispatcher{
dadC: make(chan ndpDADEvent, 1),
autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
}
e := channel.New(0, 1280, linkAddr1)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
AutoGenIPv6LinkLocal: addrType.autoGenLinkLocal,
NDPConfigs: addrType.ndpConfigs,
NDPDisp: &ndpDisp,
OpaqueIIDOpts: stack.OpaqueInterfaceIdentifierOptions{
NICNameFromID: func(_ tcpip.NICID, nicName string) string {
return nicName
},
SecretKey: secretKey,
},
})
opts := stack.NICOptions{Name: nicName}
if err := s.CreateNICWithOptions(nicID, e, opts); err != nil {
t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, 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")
}
}
addrType.triggerSLAACFn(e)
// Simulate DAD conflicts so the address is regenerated.
for i := uint8(0); i < numFailures; i++ {
addrBytes := []byte(addrType.subnet.ID())
addr := tcpip.AddressWithPrefix{
Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], addrType.subnet, nicName, i, secretKey)),
PrefixLen: 64,
}
expectAutoGenAddrEvent(addr, newAddr)
// Should not have any addresses assigned to the NIC.
mainAddr, err := s.GetMainNICAddress(nicID, header.IPv6ProtocolNumber)
if err != nil {
t.Fatalf("stack.GetMainNICAddress(%d, _) err = %s", nicID, err)
}
if want := (tcpip.AddressWithPrefix{}); mainAddr != want {
t.Fatalf("got stack.GetMainNICAddress(_, _) = (%s, nil), want = (%s, nil)", mainAddr, want)
}
// Simulate a DAD conflict.
if err := s.DupTentativeAddrDetected(nicID, addr.Address); err != nil {
t.Fatalf("s.DupTentativeAddrDetected(%d, %s): %s", nicID, addr.Address, err)
}
expectAutoGenAddrEvent(addr, invalidatedAddr)
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")
}
// Attempting to add the address manually should not fail if the
// address's state was cleaned up when DAD failed.
if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr.Address); err != nil {
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr.Address, err)
}
if err := s.RemoveAddress(nicID, addr.Address); err != nil {
t.Fatalf("RemoveAddress(%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")
}
}
// Should not have any addresses assigned to the NIC.
mainAddr, err := s.GetMainNICAddress(nicID, header.IPv6ProtocolNumber)
if err != nil {
t.Fatalf("stack.GetMainNICAddress(%d, _) err = %s", nicID, err)
}
if want := (tcpip.AddressWithPrefix{}); mainAddr != want {
t.Fatalf("got stack.GetMainNICAddress(_, _) = (%s, nil), want = (%s, nil)", mainAddr, want)
}
// If we had less failures than generation attempts, we should have an
// address after DAD resolves.
if maxRetries+1 > numFailures {
addrBytes := []byte(addrType.subnet.ID())
addr := tcpip.AddressWithPrefix{
Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], addrType.subnet, nicName, numFailures, secretKey)),
PrefixLen: 64,
}
expectAutoGenAddrEvent(addr, newAddr)
select {
case e := <-ndpDisp.dadC:
if diff := checkDADEvent(e, nicID, addr.Address, true, nil); diff != "" {
t.Errorf("dad event mismatch (-want +got):\n%s", diff)
}
case <-time.After(dadTransmits*retransmitTimer + defaultAsyncEventTimeout):
t.Fatal("timed out waiting for DAD event")
}
mainAddr, err := s.GetMainNICAddress(nicID, header.IPv6ProtocolNumber)
if err != nil {
t.Fatalf("stack.GetMainNICAddress(%d, _) err = %s", nicID, err)
}
if mainAddr != addr {
t.Fatalf("got stack.GetMainNICAddress(_, _) = (%s, nil), want = (%s, nil)", mainAddr, addr)
}
}
// Should not attempt address regeneration again.
select {
case e := <-ndpDisp.autoGenAddrC:
t.Fatalf("unexpectedly got an auto-generated address event = %+v", e)
case <-time.After(defaultAsyncEventTimeout):
}
})
}
}
}
}
// TestAutoGenAddrWithEUI64IIDNoDADRetries tests that a regeneration attempt is
// not made for SLAAC addresses generated with an IID based on the NIC's link
// address.
func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) {
const nicID = 1
const dadTransmits = 1
const retransmitTimer = time.Second
const maxRetries = 3
const lifetimeSeconds = 10
prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
addrTypes := []struct {
name string
ndpConfigs stack.NDPConfigurations
autoGenLinkLocal bool
subnet tcpip.Subnet
triggerSLAACFn func(e *channel.Endpoint)
}{
{
name: "Global address",
ndpConfigs: stack.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenAddressConflictRetries: maxRetries,
},
subnet: subnet,
triggerSLAACFn: func(e *channel.Endpoint) {
// Receive an RA with prefix1 in a PI.
e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
},
},
{
name: "LinkLocal address",
ndpConfigs: stack.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
AutoGenAddressConflictRetries: maxRetries,
},
autoGenLinkLocal: true,
subnet: header.IPv6LinkLocalPrefix.Subnet(),
triggerSLAACFn: func(e *channel.Endpoint) {},
},
}
for _, addrType := range addrTypes {
addrType := addrType
t.Run(addrType.name, func(t *testing.T) {
t.Parallel()
ndpDisp := ndpDispatcher{
dadC: make(chan ndpDADEvent, 1),
autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
}
e := channel.New(0, 1280, linkAddr1)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
AutoGenIPv6LinkLocal: addrType.autoGenLinkLocal,
NDPConfigs: addrType.ndpConfigs,
NDPDisp: &ndpDisp,
})
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, 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")
}
}
addrType.triggerSLAACFn(e)
addrBytes := []byte(addrType.subnet.ID())
header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr1, addrBytes[header.IIDOffsetInIPv6Address:])
addr := tcpip.AddressWithPrefix{
Address: tcpip.Address(addrBytes),
PrefixLen: 64,
}
expectAutoGenAddrEvent(addr, newAddr)
// Simulate a DAD conflict.
if err := s.DupTentativeAddrDetected(nicID, addr.Address); err != nil {
t.Fatalf("s.DupTentativeAddrDetected(%d, %s): %s", nicID, addr.Address, err)
}
expectAutoGenAddrEvent(addr, invalidatedAddr)
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")
}
// Should not attempt address regeneration.
select {
case e := <-ndpDisp.autoGenAddrC:
t.Fatalf("unexpectedly got an auto-generated address event = %+v", e)
case <-time.After(defaultAsyncEventTimeout):
}
})
}
}
// TestAutoGenAddrContinuesLifetimesAfterRetry tests that retrying address
// generation in response to DAD conflicts does not refresh the lifetimes.
func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) {
const nicID = 1
const nicName = "nic"
const dadTransmits = 1
const retransmitTimer = 2 * time.Second
const failureTimer = time.Second
const maxRetries = 1
const lifetimeSeconds = 5
var secretKeyBuf [header.OpaqueIIDSecretKeyMinBytes]byte
secretKey := secretKeyBuf[:]
n, err := rand.Read(secretKey)
if err != nil {
t.Fatalf("rand.Read(_): %s", err)
}
if n != header.OpaqueIIDSecretKeyMinBytes {
t.Fatalf("got rand.Read(_) = (%d, _), want = (%d, _)", n, header.OpaqueIIDSecretKeyMinBytes)
}
prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1)
ndpDisp := ndpDispatcher{
dadC: make(chan ndpDADEvent, 1),
autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
}
e := channel.New(0, 1280, linkAddr1)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NDPConfigs: stack.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenAddressConflictRetries: maxRetries,
},
NDPDisp: &ndpDisp,
OpaqueIIDOpts: stack.OpaqueInterfaceIdentifierOptions{
NICNameFromID: func(_ tcpip.NICID, nicName string) string {
return nicName
},
SecretKey: secretKey,
},
})
opts := stack.NICOptions{Name: nicName}
if err := s.CreateNICWithOptions(nicID, e, opts); err != nil {
t.Fatalf("CreateNICWithOptions(%d, _, %+v) = %s", nicID, opts, 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")
}
}
// Receive an RA with prefix in a PI.
e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds))
addrBytes := []byte(subnet.ID())
addr := tcpip.AddressWithPrefix{
Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, 0, secretKey)),
PrefixLen: 64,
}
expectAutoGenAddrEvent(addr, newAddr)
// Simulate a DAD conflict after some time has passed.
time.Sleep(failureTimer)
if err := s.DupTentativeAddrDetected(nicID, addr.Address); err != nil {
t.Fatalf("s.DupTentativeAddrDetected(%d, %s): %s", nicID, addr.Address, err)
}
expectAutoGenAddrEvent(addr, invalidatedAddr)
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")
}
// Let the next address resolve.
addr.Address = tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, 1, secretKey))
expectAutoGenAddrEvent(addr, newAddr)
select {
case e := <-ndpDisp.dadC:
if diff := checkDADEvent(e, nicID, addr.Address, true, nil); diff != "" {
t.Errorf("dad event mismatch (-want +got):\n%s", diff)
}
case <-time.After(dadTransmits*retransmitTimer + defaultAsyncEventTimeout):
t.Fatal("timed out waiting for DAD event")
}
// Address should be deprecated/invalidated after the lifetime expires.
//
// Note, the remaining lifetime is calculated from when the PI was first
// processed. Since we wait for some time before simulating a DAD conflict
// and more time for the new address to resolve, the new address is only
// expected to be valid for the remaining time. The DAD conflict should
// not have reset the lifetimes.
//
// We expect either just the invalidation event or the deprecation event
// followed by the invalidation event.
select {
case e := <-ndpDisp.autoGenAddrC:
if e.eventType == deprecatedAddr {
if diff := checkAutoGenAddrEvent(e, addr, deprecatedAddr); diff != "" {
t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
}
select {
case e := <-ndpDisp.autoGenAddrC:
if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
}
case <-time.After(defaultAsyncEventTimeout):
t.Fatal("timed out waiting for invalidated auto gen addr event after deprecation")
}
} else {
if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" {
t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff)
}
}
case <-time.After(lifetimeSeconds*time.Second - failureTimer - dadTransmits*retransmitTimer + defaultAsyncEventTimeout):
t.Fatal("timed out waiting for auto gen addr event")
}
}
// TestNDPRecursiveDNSServerDispatch tests that we properly dispatch an event
// to the integrator when an RA is received with the NDP Recursive DNS Server
// option with at least one valid address.
+49 -24
View File
@@ -1012,29 +1012,31 @@ func (n *NIC) removePermanentAddressLocked(addr tcpip.Address) *tcpip.Error {
return tcpip.ErrBadLocalAddress
}
isIPv6Unicast := r.protocol == header.IPv6ProtocolNumber && header.IsV6UnicastAddress(addr)
switch r.protocol {
case header.IPv6ProtocolNumber:
return n.removePermanentIPv6EndpointLocked(r, true /* allowSLAAPrefixInvalidation */)
default:
r.expireLocked()
return nil
}
}
func (n *NIC) removePermanentIPv6EndpointLocked(r *referencedNetworkEndpoint, allowSLAACPrefixInvalidation bool) *tcpip.Error {
addr := r.addrWithPrefix()
isIPv6Unicast := header.IsV6UnicastAddress(addr.Address)
if isIPv6Unicast {
// If we are removing a tentative IPv6 unicast address, stop DAD.
if kind == permanentTentative {
n.mu.ndp.stopDuplicateAddressDetection(addr)
}
n.mu.ndp.stopDuplicateAddressDetection(addr.Address)
// If we are removing an address generated via SLAAC, cleanup
// its SLAAC resources and notify the integrator.
if r.configType == slaac {
n.mu.ndp.cleanupSLAACAddrResourcesAndNotify(tcpip.AddressWithPrefix{
Address: addr,
PrefixLen: r.ep.PrefixLen(),
})
n.mu.ndp.cleanupSLAACAddrResourcesAndNotify(addr, allowSLAACPrefixInvalidation)
}
}
r.setKind(permanentExpired)
if !r.decRefLocked() {
// The endpoint still has references to it.
return nil
}
r.expireLocked()
// At this point the endpoint is deleted.
@@ -1044,7 +1046,7 @@ func (n *NIC) removePermanentAddressLocked(addr tcpip.Address) *tcpip.Error {
// We ignore the tcpip.ErrBadLocalAddress error because the solicited-node
// multicast group may be left by user action.
if isIPv6Unicast {
snmc := header.SolicitedNodeAddr(addr)
snmc := header.SolicitedNodeAddr(addr.Address)
if err := n.leaveGroupLocked(snmc, false /* force */); err != nil && err != tcpip.ErrBadLocalAddress {
return err
}
@@ -1425,10 +1427,12 @@ func (n *NIC) isAddrTentative(addr tcpip.Address) bool {
return ref.getKind() == permanentTentative
}
// dupTentativeAddrDetected attempts to inform n that a tentative addr
// is a duplicate on a link.
// dupTentativeAddrDetected attempts to inform n that a tentative addr is a
// duplicate on a link.
//
// dupTentativeAddrDetected will delete the tentative address if it exists.
// dupTentativeAddrDetected will remove the tentative address if it exists. If
// the address was generated via SLAAC, an attempt will be made to generate a
// new address.
func (n *NIC) dupTentativeAddrDetected(addr tcpip.Address) *tcpip.Error {
n.mu.Lock()
defer n.mu.Unlock()
@@ -1442,7 +1446,17 @@ func (n *NIC) dupTentativeAddrDetected(addr tcpip.Address) *tcpip.Error {
return tcpip.ErrInvalidEndpointState
}
return n.removePermanentAddressLocked(addr)
// If the address is a SLAAC address, do not invalidate its SLAAC prefix as a
// new address will be generated for it.
if err := n.removePermanentIPv6EndpointLocked(ref, false /* allowSLAACPrefixInvalidation */); err != nil {
return err
}
if ref.configType == slaac {
n.mu.ndp.regenerateSLAACAddr(ref.addrWithPrefix().Subnet())
}
return nil
}
// setNDPConfigs sets the NDP configurations for n.
@@ -1570,6 +1584,13 @@ type referencedNetworkEndpoint struct {
deprecated bool
}
func (r *referencedNetworkEndpoint) addrWithPrefix() tcpip.AddressWithPrefix {
return tcpip.AddressWithPrefix{
Address: r.ep.ID().LocalAddress,
PrefixLen: r.ep.PrefixLen(),
}
}
func (r *referencedNetworkEndpoint) getKind() networkEndpointKind {
return networkEndpointKind(atomic.LoadInt32((*int32)(&r.kind)))
}
@@ -1597,6 +1618,13 @@ func (r *referencedNetworkEndpoint) isValidForOutgoingRLocked() bool {
return r.nic.mu.enabled && (r.getKind() != permanentExpired || r.nic.mu.spoofing)
}
// expireLocked decrements the reference count and marks the permanent endpoint
// as expired.
func (r *referencedNetworkEndpoint) expireLocked() {
r.setKind(permanentExpired)
r.decRefLocked()
}
// decRef decrements the ref count and cleans up the endpoint once it reaches
// zero.
func (r *referencedNetworkEndpoint) decRef() {
@@ -1606,14 +1634,11 @@ func (r *referencedNetworkEndpoint) decRef() {
}
// decRefLocked is the same as decRef but assumes that the NIC.mu mutex is
// locked. Returns true if the endpoint was removed.
func (r *referencedNetworkEndpoint) decRefLocked() bool {
// locked.
func (r *referencedNetworkEndpoint) decRefLocked() {
if atomic.AddInt32(&r.refs, -1) == 0 {
r.nic.removeEndpointLocked(r)
return true
}
return false
}
// incRef increments the ref count. It must only be called when the caller is