Clear any host-specific NDP state when becoming a router

This change supports clearing all host-only NDP state when NICs become routers.
All discovered routers, discovered on-link prefixes and auto-generated addresses
will be invalidated when becoming a router. This is because normally, routers do
not process Router Advertisements to discover routers or on-link prefixes, and
do not do SLAAC.

Tests: Unittest to make sure that all discovered routers, discovered prefixes
and auto-generated addresses get invalidated when transitioning from a host to
a router.
PiperOrigin-RevId: 286902309
This commit is contained in:
Ghanan Gowripalan
2019-12-23 08:55:25 -08:00
committed by gVisor bot
parent d1528df715
commit 5bc4ae9d57
4 changed files with 338 additions and 14 deletions
+35
View File
@@ -1155,3 +1155,38 @@ func (ndp *ndpState) autoGenAddrInvalidationTimer(addr tcpip.Address, vl time.Du
ndp.invalidateAutoGenAddress(addr)
})
}
// cleanupHostOnlyState cleans up any state that is only useful for hosts.
//
// cleanupHostOnlyState MUST be called when ndp's NIC is transitioning from a
// host to a router. This function will invalidate all discovered on-link
// prefixes, discovered routers, and auto-generated addresses as routers do not
// normally process Router Advertisements to discover default routers and
// on-link prefixes, and auto-generate addresses via SLAAC.
//
// The NIC that ndp belongs to MUST be locked.
func (ndp *ndpState) cleanupHostOnlyState() {
for addr, _ := range ndp.autoGenAddresses {
ndp.invalidateAutoGenAddress(addr)
}
if got := len(ndp.autoGenAddresses); got != 0 {
log.Fatalf("ndp: still have auto-generated addresses after cleaning up, found = %d", got)
}
for prefix, _ := range ndp.onLinkPrefixes {
ndp.invalidateOnLinkPrefix(prefix)
}
if got := len(ndp.onLinkPrefixes); got != 0 {
log.Fatalf("ndp: still have discovered on-link prefixes after cleaning up, found = %d", got)
}
for router, _ := range ndp.defaultRouters {
ndp.invalidateDefaultRouter(router)
}
if got := len(ndp.defaultRouters); got != 0 {
log.Fatalf("ndp: still have discovered default routers after cleaning up, found = %d", got)
}
}
+270 -13
View File
@@ -47,6 +47,19 @@ var (
llAddr3 = header.LinkLocalAddr(linkAddr3)
)
func addrForSubnet(subnet tcpip.Subnet, linkAddr tcpip.LinkAddress) tcpip.AddressWithPrefix {
if !header.IsValidUnicastEthernetAddress(linkAddr) {
return tcpip.AddressWithPrefix{}
}
addrBytes := []byte(subnet.ID())
header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, addrBytes[header.IIDOffsetInIPv6Address:])
return tcpip.AddressWithPrefix{
Address: tcpip.Address(addrBytes),
PrefixLen: 64,
}
}
// prefixSubnetAddr returns a prefix (Address + Length), the prefix's equivalent
// tcpip.Subnet, and an address where the lower half of the address is composed
// of the EUI-64 of linkAddr if it is a valid unicast ethernet address.
@@ -59,17 +72,7 @@ func prefixSubnetAddr(offset uint8, linkAddr tcpip.LinkAddress) (tcpip.AddressWi
subnet := prefix.Subnet()
var addr tcpip.AddressWithPrefix
if header.IsValidUnicastEthernetAddress(linkAddr) {
addrBytes := []byte(subnet.ID())
header.EthernetAdddressToModifiedEUI64IntoBuf(linkAddr, addrBytes[header.IIDOffsetInIPv6Address:])
addr = tcpip.AddressWithPrefix{
Address: tcpip.Address(addrBytes),
PrefixLen: 64,
}
}
return prefix, subnet, addr
return prefix, subnet, addrForSubnet(subnet, linkAddr)
}
// TestDADDisabled tests that an address successfully resolves immediately
@@ -1772,7 +1775,7 @@ func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) {
// test.evl.
select {
case <-ndpDisp.autoGenAddrC:
t.Fatalf("unexpectedly received an auto gen addr event")
t.Fatal("unexpectedly received an auto gen addr event")
case <-time.After(time.Duration(test.evl)*time.Second - delta):
}
@@ -1846,7 +1849,7 @@ func TestAutoGenAddrRemoval(t *testing.T) {
// got stopped/cleaned up.
select {
case <-ndpDisp.autoGenAddrC:
t.Fatalf("unexpectedly received an auto gen addr event")
t.Fatal("unexpectedly received an auto gen addr event")
case <-time.After(lifetimeSeconds*time.Second + defaultTimeout):
}
}
@@ -2055,3 +2058,257 @@ func TestNDPRecursiveDNSServerDispatch(t *testing.T) {
})
}
}
// TestCleanupHostOnlyStateOnBecomingRouter tests that all discovered routers
// and prefixes, and auto-generated addresses get invalidated when a NIC
// becomes a router.
func TestCleanupHostOnlyStateOnBecomingRouter(t *testing.T) {
t.Parallel()
const (
lifetimeSeconds = 5
maxEvents = 4
nicID1 = 1
nicID2 = 2
)
prefix1, subnet1, e1Addr1 := prefixSubnetAddr(0, linkAddr1)
prefix2, subnet2, e1Addr2 := prefixSubnetAddr(1, linkAddr1)
e2Addr1 := addrForSubnet(subnet1, linkAddr2)
e2Addr2 := addrForSubnet(subnet2, linkAddr2)
ndpDisp := ndpDispatcher{
routerC: make(chan ndpRouterEvent, maxEvents),
rememberRouter: true,
prefixC: make(chan ndpPrefixEvent, maxEvents),
rememberPrefix: true,
autoGenAddrC: make(chan ndpAutoGenAddrEvent, maxEvents),
}
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv6.NewProtocol()},
NDPConfigs: stack.NDPConfigurations{
HandleRAs: true,
DiscoverDefaultRouters: true,
DiscoverOnLinkPrefixes: true,
AutoGenGlobalAddresses: true,
},
NDPDisp: &ndpDisp,
})
e1 := channel.New(0, 1280, linkAddr1)
if err := s.CreateNIC(nicID1, e1); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID1, err)
}
e2 := channel.New(0, 1280, linkAddr2)
if err := s.CreateNIC(nicID2, e2); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err)
}
expectRouterEvent := func() (bool, ndpRouterEvent) {
select {
case e := <-ndpDisp.routerC:
return true, e
default:
}
return false, ndpRouterEvent{}
}
expectPrefixEvent := func() (bool, ndpPrefixEvent) {
select {
case e := <-ndpDisp.prefixC:
return true, e
default:
}
return false, ndpPrefixEvent{}
}
expectAutoGenAddrEvent := func() (bool, ndpAutoGenAddrEvent) {
select {
case e := <-ndpDisp.autoGenAddrC:
return true, e
default:
}
return false, ndpAutoGenAddrEvent{}
}
// Receive RAs on NIC(1) and NIC(2) from default routers (llAddr1 and
// llAddr2) w/ PI (for prefix1 in RA from llAddr1 and prefix2 in RA from
// llAddr2) to discover multiple routers and prefixes, and auto-gen
// multiple addresses.
e1.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr1, lifetimeSeconds, prefix1, true, true, lifetimeSeconds, lifetimeSeconds))
// We have other tests that make sure we receive the *correct* events
// on normal discovery of routers/prefixes, and auto-generated
// addresses. Here we just make sure we get an event and let other tests
// handle the correctness check.
if ok, _ := expectRouterEvent(); !ok {
t.Errorf("expected router event for %s on NIC(%d)", llAddr1, nicID1)
}
if ok, _ := expectPrefixEvent(); !ok {
t.Errorf("expected prefix event for %s on NIC(%d)", prefix1, nicID1)
}
if ok, _ := expectAutoGenAddrEvent(); !ok {
t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr1, nicID1)
}
e1.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, lifetimeSeconds, prefix2, true, true, lifetimeSeconds, lifetimeSeconds))
if ok, _ := expectRouterEvent(); !ok {
t.Errorf("expected router event for %s on NIC(%d)", llAddr2, nicID1)
}
if ok, _ := expectPrefixEvent(); !ok {
t.Errorf("expected prefix event for %s on NIC(%d)", prefix2, nicID1)
}
if ok, _ := expectAutoGenAddrEvent(); !ok {
t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr2, nicID1)
}
e2.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr1, lifetimeSeconds, prefix1, true, true, lifetimeSeconds, lifetimeSeconds))
if ok, _ := expectRouterEvent(); !ok {
t.Errorf("expected router event for %s on NIC(%d)", llAddr1, nicID2)
}
if ok, _ := expectPrefixEvent(); !ok {
t.Errorf("expected prefix event for %s on NIC(%d)", prefix1, nicID2)
}
if ok, _ := expectAutoGenAddrEvent(); !ok {
t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e1Addr2, nicID2)
}
e2.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, lifetimeSeconds, prefix2, true, true, lifetimeSeconds, lifetimeSeconds))
if ok, _ := expectRouterEvent(); !ok {
t.Errorf("expected router event for %s on NIC(%d)", llAddr2, nicID2)
}
if ok, _ := expectPrefixEvent(); !ok {
t.Errorf("expected prefix event for %s on NIC(%d)", prefix2, nicID2)
}
if ok, _ := expectAutoGenAddrEvent(); !ok {
t.Errorf("expected auto-gen addr event for %s on NIC(%d)", e2Addr2, nicID2)
}
// We should have the auto-generated addresses added.
nicinfo := s.NICInfo()
nic1Addrs := nicinfo[nicID1].ProtocolAddresses
nic2Addrs := nicinfo[nicID2].ProtocolAddresses
if !contains(nic1Addrs, e1Addr1) {
t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e1Addr1, nicID1, nic1Addrs)
}
if !contains(nic1Addrs, e1Addr2) {
t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e1Addr2, nicID1, nic1Addrs)
}
if !contains(nic2Addrs, e2Addr1) {
t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e2Addr1, nicID2, nic2Addrs)
}
if !contains(nic2Addrs, e2Addr2) {
t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e2Addr2, nicID2, nic2Addrs)
}
// We can't proceed any further if we already failed the test (missing
// some discovery/auto-generated address events or addresses).
if t.Failed() {
t.FailNow()
}
s.SetForwarding(true)
// Collect invalidation events after becoming a router
gotRouterEvents := make(map[ndpRouterEvent]int)
for i := 0; i < maxEvents; i++ {
ok, e := expectRouterEvent()
if !ok {
t.Errorf("expected %d router events after becoming a router; got = %d", maxEvents, i)
break
}
gotRouterEvents[e]++
}
gotPrefixEvents := make(map[ndpPrefixEvent]int)
for i := 0; i < maxEvents; i++ {
ok, e := expectPrefixEvent()
if !ok {
t.Errorf("expected %d prefix events after becoming a router; got = %d", maxEvents, i)
break
}
gotPrefixEvents[e]++
}
gotAutoGenAddrEvents := make(map[ndpAutoGenAddrEvent]int)
for i := 0; i < maxEvents; i++ {
ok, e := expectAutoGenAddrEvent()
if !ok {
t.Errorf("expected %d auto-generated address events after becoming a router; got = %d", maxEvents, i)
break
}
gotAutoGenAddrEvents[e]++
}
// No need to proceed any further if we already failed the test (missing
// some invalidation events).
if t.Failed() {
t.FailNow()
}
expectedRouterEvents := map[ndpRouterEvent]int{
{nicID: nicID1, addr: llAddr1, discovered: false}: 1,
{nicID: nicID1, addr: llAddr2, discovered: false}: 1,
{nicID: nicID2, addr: llAddr1, discovered: false}: 1,
{nicID: nicID2, addr: llAddr2, discovered: false}: 1,
}
if diff := cmp.Diff(expectedRouterEvents, gotRouterEvents); diff != "" {
t.Errorf("router events mismatch (-want +got):\n%s", diff)
}
expectedPrefixEvents := map[ndpPrefixEvent]int{
{nicID: nicID1, prefix: subnet1, discovered: false}: 1,
{nicID: nicID1, prefix: subnet2, discovered: false}: 1,
{nicID: nicID2, prefix: subnet1, discovered: false}: 1,
{nicID: nicID2, prefix: subnet2, discovered: false}: 1,
}
if diff := cmp.Diff(expectedPrefixEvents, gotPrefixEvents); diff != "" {
t.Errorf("prefix events mismatch (-want +got):\n%s", diff)
}
expectedAutoGenAddrEvents := map[ndpAutoGenAddrEvent]int{
{nicID: nicID1, addr: e1Addr1, eventType: invalidatedAddr}: 1,
{nicID: nicID1, addr: e1Addr2, eventType: invalidatedAddr}: 1,
{nicID: nicID2, addr: e2Addr1, eventType: invalidatedAddr}: 1,
{nicID: nicID2, addr: e2Addr2, eventType: invalidatedAddr}: 1,
}
if diff := cmp.Diff(expectedAutoGenAddrEvents, gotAutoGenAddrEvents); diff != "" {
t.Errorf("auto-generated address events mismatch (-want +got):\n%s", diff)
}
// Make sure the auto-generated addresses got removed.
nicinfo = s.NICInfo()
nic1Addrs = nicinfo[nicID1].ProtocolAddresses
nic2Addrs = nicinfo[nicID2].ProtocolAddresses
if contains(nic1Addrs, e1Addr1) {
t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e1Addr1, nicID1, nic1Addrs)
}
if contains(nic1Addrs, e1Addr2) {
t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e1Addr2, nicID1, nic1Addrs)
}
if contains(nic2Addrs, e2Addr1) {
t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e2Addr1, nicID2, nic2Addrs)
}
if contains(nic2Addrs, e2Addr2) {
t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e2Addr2, nicID2, nic2Addrs)
}
// Should not get any more events (invalidation timers should have been
// cancelled when we transitioned into a router).
time.Sleep(lifetimeSeconds*time.Second + defaultTimeout)
select {
case <-ndpDisp.routerC:
t.Error("unexpected router event")
default:
}
select {
case <-ndpDisp.prefixC:
t.Error("unexpected prefix event")
default:
}
select {
case <-ndpDisp.autoGenAddrC:
t.Error("unexpected auto-generated address event")
default:
}
}
+12
View File
@@ -203,6 +203,18 @@ func (n *NIC) enable() *tcpip.Error {
return err
}
// becomeIPv6Router transitions n into an IPv6 router.
//
// When transitioning into an IPv6 router, host-only state (NDP discovered
// routers, discovered on-link prefixes, and auto-generated addresses) will
// be cleaned up/invalidated.
func (n *NIC) becomeIPv6Router() {
n.mu.Lock()
defer n.mu.Unlock()
n.ndp.cleanupHostOnlyState()
}
// attachLinkEndpoint attaches the NIC to the endpoint, which will enable it
// to start delivering packets.
func (n *NIC) attachLinkEndpoint() {
+21 -1
View File
@@ -662,11 +662,31 @@ func (s *Stack) Stats() tcpip.Stats {
}
// SetForwarding enables or disables the packet forwarding between NICs.
//
// When forwarding becomes enabled, any host-only state on all NICs will be
// cleaned up.
func (s *Stack) SetForwarding(enable bool) {
// TODO(igudger, bgeffon): Expose via /proc/sys/net/ipv4/ip_forward.
s.mu.Lock()
defer s.mu.Unlock()
// If forwarding status didn't change, do nothing further.
if s.forwarding == enable {
return
}
s.forwarding = enable
s.mu.Unlock()
// If this stack does not support IPv6, do nothing further.
if _, ok := s.networkProtocols[header.IPv6ProtocolNumber]; !ok {
return
}
if enable {
for _, nic := range s.nics {
nic.becomeIPv6Router()
}
}
}
// Forwarding returns if the packet forwarding between NICs is enabled.