Support upgrading expired/removed IPv6 addresses to permanent SLAAC addresses

If a previously added IPv6 address (statically or via SLAAC) was removed, it
would be left in an expired state waiting to be cleaned up if any references to
it were still held. During this time, the same address could be regenerated via
SLAAC, which should be allowed. This change supports this scenario.

When upgrading an endpoint from temporary or permanentExpired to permanent,
respect the new configuration type (static or SLAAC) and deprecated status,
along with the new PrimaryEndpointBehavior (which was already supported).

Test: stack.TestAutoGenAddrAfterRemoval
PiperOrigin-RevId: 289990168
This commit is contained in:
Ghanan Gowripalan
2020-01-15 20:23:06 -08:00
committed by gVisor bot
parent 815df2959a
commit a7a1f00425
3 changed files with 142 additions and 15 deletions
+1 -1
View File
@@ -994,7 +994,7 @@ func (ndp *ndpState) newAutoGenAddress(prefix tcpip.Subnet, pl, vl time.Duration
// 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)
ref, err := ndp.nic.addPermanentAddressLocked(protocolAddr, FirstPrimaryEndpoint, slaac, deprecated)
if err != nil {
log.Fatalf("ndp: error when adding address %s: %s", protocolAddr, err)
}
+119 -6
View File
@@ -35,12 +35,12 @@ import (
)
const (
addr1 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
addr2 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
addr3 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03"
linkAddr1 = "\x02\x02\x03\x04\x05\x06"
linkAddr2 = "\x02\x02\x03\x04\x05\x07"
linkAddr3 = "\x02\x02\x03\x04\x05\x08"
addr1 = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
addr2 = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02")
addr3 = tcpip.Address("\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03")
linkAddr1 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x06")
linkAddr2 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x07")
linkAddr3 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x08")
defaultTimeout = 100 * time.Millisecond
)
@@ -2445,6 +2445,119 @@ func TestAutoGenAddrRemoval(t *testing.T) {
}
}
// TestAutoGenAddrAfterRemoval tests adding a SLAAC address that was previously
// assigned to the NIC but is in the permanentExpired state.
func TestAutoGenAddrAfterRemoval(t *testing.T) {
t.Parallel()
const nicID = 1
prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1)
prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1)
ndpDisp, e, s := stackAndNdpDispatcherWithDefaultRoute(t, nicID)
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")
}
}
expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) {
t.Helper()
if got, err := s.GetMainNICAddress(nicID, header.IPv6ProtocolNumber); err != nil {
t.Fatalf("s.GetMainNICAddress(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
} else if got != addr {
t.Errorf("got s.GetMainNICAddress(%d, %d) = %s, want = %s", nicID, header.IPv6ProtocolNumber, got, addr)
}
if got := addrForNewConnection(t, s); got != addr.Address {
t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address)
}
}
// Receive a PI to auto-generate addr1 with a large valid and preferred
// lifetime.
const largeLifetimeSeconds = 999
e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix1, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
expectAutoGenAddrEvent(addr1, newAddr)
expectPrimaryAddr(addr1)
// Add addr2 as a static address.
protoAddr2 := tcpip.ProtocolAddress{
Protocol: header.IPv6ProtocolNumber,
AddressWithPrefix: addr2,
}
if err := s.AddProtocolAddressWithOptions(nicID, protoAddr2, stack.FirstPrimaryEndpoint); err != nil {
t.Fatalf("AddProtocolAddressWithOptions(%d, %+v, %d, %s) = %s", nicID, protoAddr2, stack.FirstPrimaryEndpoint, err)
}
// addr2 should be more preferred now since it is at the front of the primary
// list.
expectPrimaryAddr(addr2)
// Get a route using addr2 to increment its reference count then remove it
// to leave it in the permanentExpired state.
r, err := s.FindRoute(nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, false)
if err != nil {
t.Fatalf("FindRoute(%d, %s, %s, %d, false): %s", nicID, addr2.Address, addr3, header.IPv6ProtocolNumber, err)
}
defer r.Release()
if err := s.RemoveAddress(nicID, addr2.Address); err != nil {
t.Fatalf("s.RemoveAddress(%d, %s): %s", nicID, addr2.Address, err)
}
// addr1 should be preferred again since addr2 is in the expired state.
expectPrimaryAddr(addr1)
// Receive a PI to auto-generate addr2 as valid and preferred.
e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
expectAutoGenAddrEvent(addr2, newAddr)
// addr2 should be more preferred now that it is closer to the front of the
// primary list and not deprecated.
expectPrimaryAddr(addr2)
// Removing the address should result in an invalidation event immediately.
// It should still be in the permanentExpired state because r is still held.
//
// We remove addr2 here to make sure addr2 was marked as a SLAAC address
// (it was previously marked as a static address).
if err := s.RemoveAddress(1, addr2.Address); err != nil {
t.Fatalf("RemoveAddress(_, %s) = %s", addr2.Address, err)
}
expectAutoGenAddrEvent(addr2, invalidatedAddr)
// addr1 should be more preferred since addr2 is in the expired state.
expectPrimaryAddr(addr1)
// Receive a PI to auto-generate addr2 as valid and deprecated.
e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, 0))
expectAutoGenAddrEvent(addr2, newAddr)
// addr1 should still be more preferred since addr2 is deprecated, even though
// it is closer to the front of the primary list.
expectPrimaryAddr(addr1)
// Receive a PI to refresh addr2's preferred lifetime.
e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, 0, prefix2, true, true, largeLifetimeSeconds, largeLifetimeSeconds))
select {
case <-ndpDisp.autoGenAddrC:
t.Fatal("unexpectedly got an auto gen addr event")
default:
}
// addr2 should be more preferred now that it is not deprecated.
expectPrimaryAddr(addr2)
if err := s.RemoveAddress(1, addr2.Address); err != nil {
t.Fatalf("RemoveAddress(_, %s) = %s", addr2.Address, err)
}
expectAutoGenAddrEvent(addr2, invalidatedAddr)
expectPrimaryAddr(addr1)
}
// TestAutoGenAddrStaticConflict tests that if SLAAC generates an address that
// is already assigned to the NIC, the static address remains.
func TestAutoGenAddrStaticConflict(t *testing.T) {
+22 -8
View File
@@ -202,7 +202,7 @@ func (n *NIC) enable() *tcpip.Error {
Address: addr,
PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen,
},
}, CanBePrimaryEndpoint); err != nil {
}, CanBePrimaryEndpoint, static, false /* deprecated */); err != nil {
return err
}
}
@@ -533,7 +533,12 @@ func (n *NIC) getRefOrCreateTemp(protocol tcpip.NetworkProtocolNumber, address t
return ref
}
func (n *NIC) addPermanentAddressLocked(protocolAddress tcpip.ProtocolAddress, peb PrimaryEndpointBehavior) (*referencedNetworkEndpoint, *tcpip.Error) {
// addPermanentAddressLocked adds a permanent address to n.
//
// If n already has the address in a non-permanent state,
// addPermanentAddressLocked will promote it to permanent and update the
// endpoint with the properties provided.
func (n *NIC) addPermanentAddressLocked(protocolAddress tcpip.ProtocolAddress, peb PrimaryEndpointBehavior, configType networkEndpointConfigType, deprecated bool) (*referencedNetworkEndpoint, *tcpip.Error) {
id := NetworkEndpointID{protocolAddress.AddressWithPrefix.Address}
if ref, ok := n.endpoints[id]; ok {
switch ref.getKind() {
@@ -541,10 +546,14 @@ func (n *NIC) addPermanentAddressLocked(protocolAddress tcpip.ProtocolAddress, p
// The NIC already have a permanent endpoint with that address.
return nil, tcpip.ErrDuplicateAddress
case permanentExpired, temporary:
// Promote the endpoint to become permanent and respect
// the new peb.
// Promote the endpoint to become permanent and respect the new peb,
// configType and deprecated status.
if ref.tryIncRef() {
// TODO(b/147748385): Perform Duplicate Address Detection when promoting
// an IPv6 endpoint to permanent.
ref.setKind(permanent)
ref.deprecated = deprecated
ref.configType = configType
refs := n.primary[ref.protocol]
for i, r := range refs {
@@ -576,9 +585,13 @@ func (n *NIC) addPermanentAddressLocked(protocolAddress tcpip.ProtocolAddress, p
}
}
return n.addAddressLocked(protocolAddress, peb, permanent, static, false)
return n.addAddressLocked(protocolAddress, peb, permanent, configType, deprecated)
}
// addAddressLocked adds a new protocolAddress to n.
//
// If the address is already known by n (irrespective of the state it is in),
// addAddressLocked does nothing and returns tcpip.ErrDuplicateAddress.
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.
@@ -653,7 +666,7 @@ func (n *NIC) addAddressLocked(protocolAddress tcpip.ProtocolAddress, peb Primar
func (n *NIC) AddAddress(protocolAddress tcpip.ProtocolAddress, peb PrimaryEndpointBehavior) *tcpip.Error {
// Add the endpoint.
n.mu.Lock()
_, err := n.addPermanentAddressLocked(protocolAddress, peb)
_, err := n.addPermanentAddressLocked(protocolAddress, peb, static, false /* deprecated */)
n.mu.Unlock()
return err
@@ -935,7 +948,7 @@ func (n *NIC) joinGroupLocked(protocol tcpip.NetworkProtocolNumber, addr tcpip.A
Address: addr,
PrefixLen: netProto.DefaultPrefixLen(),
},
}, NeverPrimaryEndpoint); err != nil {
}, NeverPrimaryEndpoint, static, false /* deprecated */); err != nil {
return err
}
}
@@ -1313,7 +1326,8 @@ type referencedNetworkEndpoint struct {
kind networkEndpointKind
// configType is the method that was used to configure this endpoint.
// This must never change after the endpoint is added to a NIC.
// This must never change except during endpoint creation and promotion to
// permanent.
configType networkEndpointConfigType
// deprecated indicates whether or not the endpoint should be considered