| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //go:build !build_with_native_toolchain |
| |
| package netstack |
| |
| import ( |
| "context" |
| "fmt" |
| "testing" |
| "time" |
| |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/dns" |
| "go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/util" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/faketime" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" |
| "gvisor.dev/gvisor/pkg/tcpip/stack" |
| ) |
| |
| const ( |
| shortLifetime = time.Nanosecond |
| shortLifetimeTimeout = time.Second |
| middleLifetime = 500 * time.Millisecond |
| middleLifetimeTimeout = middleLifetime * time.Second |
| incrementalTimeout = 100 * time.Millisecond |
| defaultLifetime = time.Hour |
| ) |
| |
| var ( |
| mask = util.Parse("ffff:ffff::") |
| subnet1 = newSubnet(util.Parse("abcd:1234::"), tcpip.MaskFromBytes(mask.AsSlice())) |
| subnet2 = newSubnet(util.Parse("abcd:1236::"), tcpip.MaskFromBytes(mask.AsSlice())) |
| testProtocolAddr1 = tcpip.ProtocolAddress{ |
| Protocol: ipv6.ProtocolNumber, |
| AddressWithPrefix: tcpip.AddressWithPrefix{ |
| Address: util.Parse("abcd:ee00::1"), |
| PrefixLen: 64, |
| }, |
| } |
| testProtocolAddr2 = tcpip.ProtocolAddress{ |
| Protocol: ipv6.ProtocolNumber, |
| AddressWithPrefix: tcpip.AddressWithPrefix{ |
| Address: util.Parse("abcd:ef00::1"), |
| PrefixLen: 64, |
| }, |
| } |
| ) |
| |
| func newSubnet(addr tcpip.Address, mask tcpip.AddressMask) tcpip.Subnet { |
| subnet, err := tcpip.NewSubnet(addr, mask) |
| if err != nil { |
| panic(fmt.Sprintf("NewSubnet(%s, %s): %s", addr, mask, err)) |
| } |
| return subnet |
| } |
| |
| // newNDPDispatcherForTest returns a new ndpDispatcher with a channel used to |
| // notify tests when its event queue is emptied. |
| func newNDPDispatcherForTest() *ndpDispatcher { |
| n := newNDPDispatcher() |
| n.testNotifyCh = make(chan struct{}, 1) |
| return n |
| } |
| |
| // waitForEmptyQueue returns after the event queue is emptied. |
| // |
| // If n's event queue is empty when waitForEmptyQueue is called, then |
| // waitForEmptyQueue returns immediately. |
| func waitForEmptyQueue(n *ndpDispatcher) { |
| // Wait for an empty event queue. |
| for { |
| n.mu.Lock() |
| empty := len(n.mu.events) == 0 |
| n.mu.Unlock() |
| if empty { |
| break |
| } |
| <-n.testNotifyCh |
| } |
| } |
| |
| // Test that attempting to invalidate an off-link route which we do not have a |
| // route for is not an issue. |
| func TestNDPInvalidateUnknownOffLinkRoute(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| ndpDisp := newNDPDispatcherForTest() |
| ns, _ := newNetstack(t, netstackTestOptions{ndpDisp: ndpDisp}) |
| ndpDisp.start(ctx) |
| |
| ifs := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs.RemoveByUser) |
| if err := ifs.Up(); err != nil { |
| t.Fatalf("ifs.Up(): %s", err) |
| } |
| |
| // Invalidate a route we do not have in our route table. |
| rt := tcpip.Route{NIC: ifs.nicid, Destination: subnet1, Gateway: testLinkLocalV6Addr1} |
| ndpDisp.OnOffLinkRouteInvalidated(rt.NIC, rt.Destination, rt.Gateway) |
| waitForEmptyQueue(ndpDisp) |
| if rts := ns.stack.GetRouteTable(); containsRoute(rts, rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", rt, rts) |
| } |
| } |
| |
| func TestNDPIPv6OffLinkRoutePreferences(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| ndpDisp := newNDPDispatcherForTest() |
| ns, _ := newNetstack(t, netstackTestOptions{ndpDisp: ndpDisp}) |
| ndpDisp.start(ctx) |
| |
| ifs1 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs1.RemoveByUser) |
| if err := ifs1.Up(); err != nil { |
| t.Fatalf("ifs1.Up(): %s", err) |
| } |
| ifs2 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs2.RemoveByUser) |
| if err := ifs2.Up(); err != nil { |
| t.Fatalf("ifs2.Up(): %s", err) |
| } |
| |
| dest := tcpip.AddressWithPrefix{Address: util.Parse("abcd:ee00::"), PrefixLen: 32}.Subnet() |
| r1 := tcpip.Route{Destination: dest, NIC: ifs1.nicid, Gateway: testLinkLocalV6Addr1} |
| r2 := tcpip.Route{Destination: dest, NIC: ifs2.nicid, Gateway: testLinkLocalV6Addr1} |
| expectedRouteTable := []tcpip.Route{ |
| {Destination: ipv4MulticastSubnet().Subnet(), NIC: ifs1.nicid}, |
| {Destination: ipv4MulticastSubnet().Subnet(), NIC: ifs2.nicid}, |
| |
| ipv6LinkLocalOnLinkRoute(ifs1.nicid), |
| ipv6LinkLocalOnLinkRoute(ifs2.nicid), |
| |
| r1, |
| r2, |
| |
| {Destination: ipv6MulticastSubnet().Subnet(), NIC: ifs1.nicid}, |
| {Destination: ipv6MulticastSubnet().Subnet(), NIC: ifs2.nicid}, |
| } |
| |
| ndpDisp.OnOffLinkRouteUpdated(r2.NIC, r2.Destination, r2.Gateway, header.LowRoutePreference) |
| ndpDisp.OnOffLinkRouteUpdated(r1.NIC, r1.Destination, r1.Gateway, header.HighRoutePreference) |
| waitForEmptyQueue(ndpDisp) |
| if diff := cmp.Diff(expectedRouteTable, ns.stack.GetRouteTable()); diff != "" { |
| t.Errorf("route table mismatch (-want +got):\n%s", diff) |
| } |
| |
| // Flip the preferences of r1 and r2. |
| expectedRouteTable[4], expectedRouteTable[5] = expectedRouteTable[5], expectedRouteTable[4] |
| ndpDisp.OnOffLinkRouteUpdated(r2.NIC, r2.Destination, r2.Gateway, header.HighRoutePreference) |
| ndpDisp.OnOffLinkRouteUpdated(r1.NIC, r1.Destination, r1.Gateway, header.LowRoutePreference) |
| waitForEmptyQueue(ndpDisp) |
| if diff := cmp.Diff(expectedRouteTable, ns.stack.GetRouteTable()); diff != "" { |
| t.Errorf("route table mismatch (-want +got):\n%s", diff) |
| } |
| |
| } |
| |
| // Test that ndpDispatcher properly handles the discovery and invalidation of |
| // off-link routes. |
| func TestNDPIPv6OffLinkRouteDiscovery(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| ndpDisp := newNDPDispatcherForTest() |
| ns, _ := newNetstack(t, netstackTestOptions{ndpDisp: ndpDisp}) |
| ndpDisp.start(ctx) |
| |
| ifs1 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs1.RemoveByUser) |
| if err := ifs1.Up(); err != nil { |
| t.Fatalf("ifs1.Up(): %s", err) |
| } |
| ifs2 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs2.RemoveByUser) |
| if err := ifs2.Up(); err != nil { |
| t.Fatalf("ifs2.Up(): %s", err) |
| } |
| |
| // Test discovering a new default router on eth1. |
| nic1Rtr1Rt := tcpip.Route{Destination: header.IPv6EmptySubnet, Gateway: testLinkLocalV6Addr1, NIC: ifs1.nicid} |
| ndpDisp.OnOffLinkRouteUpdated(nic1Rtr1Rt.NIC, nic1Rtr1Rt.Destination, nic1Rtr1Rt.Gateway, header.MediumRoutePreference) |
| waitForEmptyQueue(ndpDisp) |
| if rts := ns.stack.GetRouteTable(); !containsRoute(rts, nic1Rtr1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Rtr1Rt, rts) |
| } |
| |
| // Test discovering a new default router on eth2 (with the same |
| // link-local IP as the one discovered as eth1). |
| nic2Rtr1Rt := tcpip.Route{Destination: header.IPv6EmptySubnet, Gateway: testLinkLocalV6Addr1, NIC: ifs2.nicid} |
| ndpDisp.OnOffLinkRouteUpdated(nic2Rtr1Rt.NIC, nic2Rtr1Rt.Destination, nic2Rtr1Rt.Gateway, header.MediumRoutePreference) |
| waitForEmptyQueue(ndpDisp) |
| rts := ns.stack.GetRouteTable() |
| if !containsRoute(rts, nic2Rtr1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr1Rt, rts) |
| } |
| // Should still have the route from before. |
| if !containsRoute(rts, nic1Rtr1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Rtr1Rt, rts) |
| } |
| |
| // Test discovering another default router on eth2. |
| nic2Rtr2Rt := tcpip.Route{Destination: header.IPv6EmptySubnet, Gateway: testLinkLocalV6Addr2, NIC: ifs2.nicid} |
| ndpDisp.OnOffLinkRouteUpdated(nic2Rtr2Rt.NIC, nic2Rtr2Rt.Destination, nic2Rtr2Rt.Gateway, header.MediumRoutePreference) |
| waitForEmptyQueue(ndpDisp) |
| rts = ns.stack.GetRouteTable() |
| if !containsRoute(rts, nic2Rtr2Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr2Rt, rts) |
| } |
| // Should still have the routes from before. |
| if !containsRoute(rts, nic2Rtr1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr1Rt, rts) |
| } |
| if !containsRoute(rts, nic1Rtr1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Rtr1Rt, rts) |
| } |
| |
| // Test discovering a more specific route on eth2. |
| dest := tcpip.AddressWithPrefix{Address: util.Parse("abcd:ee00::"), PrefixLen: 64}.Subnet() |
| ndpDisp.OnOffLinkRouteUpdated(ifs2.nicid, dest, testLinkLocalV6Addr1, header.MediumRoutePreference) |
| waitForEmptyQueue(ndpDisp) |
| nic2Rtr2MoreSpecificRt := tcpip.Route{Destination: dest, Gateway: testLinkLocalV6Addr1, NIC: ifs2.nicid} |
| rts = ns.stack.GetRouteTable() |
| if !containsRoute(rts, nic2Rtr2MoreSpecificRt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr2MoreSpecificRt, rts) |
| } |
| // Should still have the routes from before. |
| if !containsRoute(rts, nic2Rtr2Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr2Rt, rts) |
| } |
| if !containsRoute(rts, nic2Rtr1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr1Rt, rts) |
| } |
| if !containsRoute(rts, nic1Rtr1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Rtr1Rt, rts) |
| } |
| |
| // Invalidate the default router with IP testLinkLocalV6Addr1 from eth2. |
| ndpDisp.OnOffLinkRouteInvalidated(ifs2.nicid, header.IPv6EmptySubnet, testLinkLocalV6Addr1) |
| waitForEmptyQueue(ndpDisp) |
| rts = ns.stack.GetRouteTable() |
| if containsRoute(rts, nic2Rtr1Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic2Rtr1Rt, rts) |
| } |
| // Should still have default routes through the non-invalidated |
| // routers and the more specific route. |
| if !containsRoute(rts, nic2Rtr2MoreSpecificRt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr2MoreSpecificRt, rts) |
| } |
| if !containsRoute(rts, nic2Rtr2Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr2Rt, rts) |
| } |
| if !containsRoute(rts, nic1Rtr1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Rtr1Rt, rts) |
| } |
| |
| // Invalidate the default router with IP testLinkLocalV6Addr1 from eth1. |
| ndpDisp.OnOffLinkRouteInvalidated(ifs1.nicid, header.IPv6EmptySubnet, testLinkLocalV6Addr1) |
| waitForEmptyQueue(ndpDisp) |
| rts = ns.stack.GetRouteTable() |
| if containsRoute(rts, nic1Rtr1Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic1Rtr1Rt, rts) |
| } |
| // Should still have the more specific route and default routes through the |
| // non-invalidated router. |
| if !containsRoute(rts, nic2Rtr2Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr2Rt, rts) |
| } |
| if !containsRoute(rts, nic2Rtr2MoreSpecificRt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr2MoreSpecificRt, rts) |
| } |
| |
| // Invalidate the default router with IP testLinkLocalV6Addr2 from eth2. |
| ndpDisp.OnOffLinkRouteInvalidated(ifs2.nicid, header.IPv6EmptySubnet, testLinkLocalV6Addr2) |
| waitForEmptyQueue(ndpDisp) |
| rts = ns.stack.GetRouteTable() |
| if containsRoute(rts, nic2Rtr2Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic2Rtr2Rt, rts) |
| } |
| // Should still have the more specific route. |
| if !containsRoute(rts, nic2Rtr2MoreSpecificRt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Rtr2MoreSpecificRt, rts) |
| } |
| |
| // Invalidate the more specific route. |
| ndpDisp.OnOffLinkRouteInvalidated(ifs2.nicid, dest, testLinkLocalV6Addr1) |
| waitForEmptyQueue(ndpDisp) |
| rts = ns.stack.GetRouteTable() |
| // Should not have the more specific route. |
| if containsRoute(rts, nic2Rtr2MoreSpecificRt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic2Rtr2MoreSpecificRt, rts) |
| } |
| } |
| |
| // Test that attempting to invalidate an on-link prefix which we do not have a |
| // route for is not an issue. |
| func TestNDPInvalidateUnknownIPv6Prefix(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| ndpDisp := newNDPDispatcherForTest() |
| ns, _ := newNetstack(t, netstackTestOptions{ndpDisp: ndpDisp}) |
| ndpDisp.start(ctx) |
| |
| ifs := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs.RemoveByUser) |
| if err := ifs.Up(); err != nil { |
| t.Fatalf("ifs.Up(): %s", err) |
| } |
| |
| // Invalidate the prefix subnet1 from eth (even though we do not yet know |
| // about it). |
| ndpDisp.OnOnLinkPrefixInvalidated(ifs.nicid, subnet1) |
| waitForEmptyQueue(ndpDisp) |
| if rt, rts := onLinkV6Route(ifs.nicid, subnet1), ns.stack.GetRouteTable(); containsRoute(rts, rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", rt, rts) |
| } |
| } |
| |
| // Test that ndpDispatcher properly handles the discovery and invalidation of |
| // on-link IPv6 prefixes. |
| func TestNDPIPv6PrefixDiscovery(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| ndpDisp := newNDPDispatcherForTest() |
| ns, _ := newNetstack(t, netstackTestOptions{ndpDisp: ndpDisp}) |
| ndpDisp.start(ctx) |
| |
| ifs1 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs1.RemoveByUser) |
| if err := ifs1.Up(); err != nil { |
| t.Fatalf("ifs1.Up(): %s", err) |
| } |
| ifs2 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs2.RemoveByUser) |
| if err := ifs2.Up(); err != nil { |
| t.Fatalf("ifs2.Up(): %s", err) |
| } |
| |
| // Test discovering a new on-link prefix on eth1. |
| ndpDisp.OnOnLinkPrefixDiscovered(ifs1.nicid, subnet1) |
| waitForEmptyQueue(ndpDisp) |
| nic1Sub1Rt := onLinkV6Route(ifs1.nicid, subnet1) |
| if rts := ns.stack.GetRouteTable(); !containsRoute(rts, nic1Sub1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Sub1Rt, rts) |
| } |
| |
| // Test discovering the same on-link prefix on eth2. |
| ndpDisp.OnOnLinkPrefixDiscovered(ifs2.nicid, subnet1) |
| waitForEmptyQueue(ndpDisp) |
| nic2Sub1Rt := onLinkV6Route(ifs2.nicid, subnet1) |
| rts := ns.stack.GetRouteTable() |
| if !containsRoute(rts, nic2Sub1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Sub1Rt, rts) |
| } |
| // Should still have the route from before. |
| if !containsRoute(rts, nic1Sub1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Sub1Rt, rts) |
| } |
| |
| // Test discovering another on-link prefix on eth2. |
| ndpDisp.OnOnLinkPrefixDiscovered(ifs2.nicid, subnet2) |
| waitForEmptyQueue(ndpDisp) |
| nic2Sub2Rt := onLinkV6Route(ifs2.nicid, subnet2) |
| rts = ns.stack.GetRouteTable() |
| if !containsRoute(rts, nic2Sub2Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Sub2Rt, rts) |
| } |
| // Should still have the routes from before. |
| if !containsRoute(rts, nic2Sub1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Sub1Rt, rts) |
| } |
| if !containsRoute(rts, nic1Sub1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Sub1Rt, rts) |
| } |
| |
| // Invalidate the prefix subnet1 from eth2. |
| ndpDisp.OnOnLinkPrefixInvalidated(ifs2.nicid, subnet1) |
| waitForEmptyQueue(ndpDisp) |
| rts = ns.stack.GetRouteTable() |
| if containsRoute(rts, nic2Sub1Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic2Sub1Rt, rts) |
| } |
| // Should still have default routes through the non-invalidated |
| // routers. |
| if !containsRoute(rts, nic2Sub2Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Sub2Rt, rts) |
| } |
| if !containsRoute(rts, nic1Sub1Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic1Sub1Rt, rts) |
| } |
| |
| // Invalidate the prefix subnet1 from eth1. |
| ndpDisp.OnOnLinkPrefixInvalidated(ifs1.nicid, subnet1) |
| waitForEmptyQueue(ndpDisp) |
| rts = ns.stack.GetRouteTable() |
| if containsRoute(rts, nic1Sub1Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic1Sub1Rt, rts) |
| } |
| // Should still not have the other invalidated route. |
| if containsRoute(rts, nic2Sub1Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic2Sub1Rt, rts) |
| } |
| // Should still have default route through the non-invalidated router. |
| if !containsRoute(rts, nic2Sub2Rt) { |
| t.Fatalf("missing route = %s from route table, got = %s", nic2Sub2Rt, rts) |
| } |
| |
| // Invalidate the prefix subnet2 from eth2. |
| ndpDisp.OnOnLinkPrefixInvalidated(ifs2.nicid, subnet2) |
| waitForEmptyQueue(ndpDisp) |
| rts = ns.stack.GetRouteTable() |
| if containsRoute(rts, nic2Sub2Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic2Sub2Rt, rts) |
| } |
| // Should still not have the other invalidated route. |
| if containsRoute(rts, nic1Sub1Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic1Sub1Rt, rts) |
| } |
| if containsRoute(rts, nic2Sub1Rt) { |
| t.Fatalf("should not have route = %s in the route table, got = %s", nic2Sub1Rt, rts) |
| } |
| } |
| |
| // TestLinkDown tests that Recursive DNS Servers learned from NDP are |
| // invalidated when a NIC is brought down. |
| func TestLinkDown(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| ndpDisp := newNDPDispatcherForTest() |
| ns, _ := newNetstack(t, netstackTestOptions{ndpDisp: ndpDisp}) |
| ndpDisp.start(ctx) |
| |
| ifs1 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs1.RemoveByUser) |
| if err := ifs1.Up(); err != nil { |
| t.Fatalf("ifs1.Up(): %s", err) |
| } |
| ifs2 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs2.RemoveByUser) |
| if err := ifs2.Up(); err != nil { |
| t.Fatalf("ifs2.Up(): %s", err) |
| } |
| |
| addr1NIC1 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::1"), |
| Port: 53, |
| NIC: ifs1.nicid, |
| } |
| addr1NIC2 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::1"), |
| Port: 53, |
| NIC: ifs2.nicid, |
| } |
| addr2NIC1 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::2"), |
| Port: 53, |
| NIC: ifs1.nicid, |
| } |
| addr3NIC2 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::3"), |
| Port: 53, |
| NIC: ifs2.nicid, |
| } |
| |
| ndpDisp.OnRecursiveDNSServerOption(ifs1.nicid, []tcpip.Address{addr1NIC1.Addr, addr2NIC1.Addr}, defaultLifetime) |
| ndpDisp.OnRecursiveDNSServerOption(ifs2.nicid, []tcpip.Address{addr1NIC2.Addr, addr3NIC2.Addr}, defaultLifetime) |
| waitForEmptyQueue(ndpDisp) |
| { |
| want := []tcpip.FullAddress{addr1NIC1, addr2NIC1, addr1NIC2, addr3NIC2} |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Fatalf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| // Bring eth2 down and make sure the DNS servers learned from that NIC are |
| // invalidated. |
| if err := ifs2.Down(); err != nil { |
| t.Fatalf("ifs2.Down(): %s", err) |
| } |
| waitForEmptyQueue(ndpDisp) |
| { |
| want := []tcpip.FullAddress{addr1NIC1, addr2NIC1} |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Fatalf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| // Bring eth2 up and make sure the DNS servers learned from that NIC do not |
| // reappear. |
| if err := ifs2.Up(); err != nil { |
| t.Fatalf("ifs2.Up(): %s", err) |
| } |
| waitForEmptyQueue(ndpDisp) |
| { |
| want := []tcpip.FullAddress{addr1NIC1, addr2NIC1} |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Errorf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| } |
| |
| var dnsServerTcpIpFullAddressOpts = []cmp.Option{ |
| // Asymmetric application of transformers is not supported in go-cmp. IOW we |
| // write this asymmetric transformer of dns.Server -> tcpip.FullAddress as a |
| // symmetric transformer that handles both input types; go-cmp internally |
| // upcasts invariant operands to interface{}. |
| cmp.FilterValues(func(x, y interface{}) bool { |
| for _, v := range []interface{}{x, y} { |
| switch v.(type) { |
| case []dns.Server: |
| case []tcpip.FullAddress: |
| default: |
| return false |
| } |
| } |
| return true |
| }, cmp.Transformer("ToTcpIpAddress", func(v interface{}) []tcpip.FullAddress { |
| switch v := v.(type) { |
| case []dns.Server: |
| if v == nil { |
| return nil |
| } |
| out := make([]tcpip.FullAddress, len(v)) |
| for i := range v { |
| out[i] = v[i].Address |
| } |
| return out |
| case []tcpip.FullAddress: |
| return v |
| default: |
| panic(fmt.Sprintf("value of unexpected type %#v", v)) |
| } |
| })), |
| cmpopts.SortSlices(func(left, right tcpip.FullAddress) bool { |
| if left, right := left.NIC, right.NIC; left != right { |
| return left < right |
| } |
| if left, right := left.Addr, right.Addr; left != right { |
| return string(left.AsSlice()) < string(right.AsSlice()) |
| } |
| if left, right := left.Port, right.Port; left != right { |
| return left < right |
| } |
| return false |
| }), |
| } |
| |
| func TestRecursiveDNSServers(t *testing.T) { |
| t.Parallel() |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| ndpDisp := newNDPDispatcherForTest() |
| ns, clock := newNetstack(t, netstackTestOptions{ndpDisp: ndpDisp}) |
| ndpDisp.start(ctx) |
| |
| ifs1 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs1.RemoveByUser) |
| if err := ifs1.Up(); err != nil { |
| t.Fatalf("ifs1.Up(): %s", err) |
| } |
| ifs2 := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs2.RemoveByUser) |
| if err := ifs2.Up(); err != nil { |
| t.Fatalf("ifs2.Up(): %s", err) |
| } |
| |
| addr1NIC1 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::1"), |
| Port: 53, |
| NIC: ifs1.nicid, |
| } |
| addr1NIC2 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::1"), |
| Port: 53, |
| NIC: ifs2.nicid, |
| } |
| addr2NIC1 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::2"), |
| Port: 53, |
| NIC: ifs1.nicid, |
| } |
| addr3NIC2 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::3"), |
| Port: 53, |
| NIC: ifs2.nicid, |
| } |
| |
| ndpDisp.OnRecursiveDNSServerOption(ifs1.nicid, []tcpip.Address{addr1NIC1.Addr, addr2NIC1.Addr}, 0) |
| waitForEmptyQueue(ndpDisp) |
| { |
| want := []tcpip.FullAddress(nil) |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Errorf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| ndpDisp.OnRecursiveDNSServerOption(ifs1.nicid, []tcpip.Address{addr1NIC1.Addr, addr2NIC1.Addr}, defaultLifetime) |
| waitForEmptyQueue(ndpDisp) |
| { |
| want := []tcpip.FullAddress{addr1NIC1, addr2NIC1} |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Errorf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| ndpDisp.OnRecursiveDNSServerOption(ifs2.nicid, []tcpip.Address{addr1NIC2.Addr, addr3NIC2.Addr}, defaultLifetime) |
| waitForEmptyQueue(ndpDisp) |
| { |
| want := []tcpip.FullAddress{addr1NIC1, addr2NIC1, addr1NIC2, addr3NIC2} |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Errorf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| ndpDisp.OnRecursiveDNSServerOption(ifs2.nicid, []tcpip.Address{addr1NIC2.Addr, addr3NIC2.Addr}, 0) |
| waitForEmptyQueue(ndpDisp) |
| { |
| want := []tcpip.FullAddress{addr1NIC1, addr2NIC1} |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Errorf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| ndpDisp.OnRecursiveDNSServerOption(ifs1.nicid, []tcpip.Address{addr1NIC1.Addr, addr2NIC1.Addr}, shortLifetime) |
| waitForEmptyQueue(ndpDisp) |
| for elapsedTime := time.Duration(0); elapsedTime <= shortLifetimeTimeout; elapsedTime += incrementalTimeout { |
| clock.Advance(incrementalTimeout) |
| want := []tcpip.FullAddress(nil) |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| if elapsedTime < shortLifetimeTimeout { |
| continue |
| } |
| |
| t.Errorf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| |
| break |
| } |
| } |
| |
| func TestRecursiveDNSServersWithInfiniteLifetime(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| ndpDisp := newNDPDispatcherForTest() |
| ns, clock := newNetstack(t, netstackTestOptions{ndpDisp: ndpDisp}) |
| ndpDisp.start(ctx) |
| |
| ifs := addNoopEndpoint(t, ns, "") |
| t.Cleanup(ifs.RemoveByUser) |
| if err := ifs.Up(); err != nil { |
| t.Fatalf("ifs.Up(): %s", err) |
| } |
| |
| addr1 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::1"), |
| Port: 53, |
| NIC: ifs.nicid, |
| } |
| addr2 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::2"), |
| Port: 53, |
| NIC: ifs.nicid, |
| } |
| addr3 := tcpip.FullAddress{ |
| Addr: util.Parse("fe80::3"), |
| Port: 53, |
| NIC: ifs.nicid, |
| } |
| ndpDisp.OnRecursiveDNSServerOption(ifs.nicid, []tcpip.Address{addr1.Addr, addr2.Addr, addr3.Addr}, header.NDPInfiniteLifetime) |
| waitForEmptyQueue(ndpDisp) |
| { |
| want := []tcpip.FullAddress{addr1, addr2, addr3} |
| got := ns.dnsConfig.GetServersCache() |
| if diff := cmp.Diff(want, got, dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Fatalf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| // All addresses to expire after middleLifetime. |
| ndpDisp.OnRecursiveDNSServerOption(ifs.nicid, []tcpip.Address{addr1.Addr, addr2.Addr, addr3.Addr}, middleLifetime) |
| waitForEmptyQueue(ndpDisp) |
| if diff := cmp.Diff([]tcpip.FullAddress{addr1, addr2, addr3}, ns.dnsConfig.GetServersCache(), dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Fatalf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| |
| // Update addr2 and addr3 to be valid forever. |
| ndpDisp.OnRecursiveDNSServerOption(ifs.nicid, []tcpip.Address{addr2.Addr, addr3.Addr}, header.NDPInfiniteLifetime) |
| waitForEmptyQueue(ndpDisp) |
| if diff := cmp.Diff([]tcpip.FullAddress{addr1, addr2, addr3}, ns.dnsConfig.GetServersCache(), dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Fatalf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| |
| // addr1 should expire after middleLifetime. |
| clock.Advance(middleLifetimeTimeout) |
| if diff := cmp.Diff([]tcpip.FullAddress{addr2, addr3}, ns.dnsConfig.GetServersCache(), dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Fatalf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| |
| // addr2 and addr3 should not expire after header.NDPInfiniteLifetime (since |
| // it represents infinity). |
| clock.Advance(header.NDPInfiniteLifetime) |
| if diff := cmp.Diff([]tcpip.FullAddress{addr2, addr3}, ns.dnsConfig.GetServersCache(), dnsServerTcpIpFullAddressOpts...); diff != "" { |
| t.Fatalf("GetServerCache() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| func TestDHCPv6Stats(t *testing.T) { |
| type statsSnapshot struct { |
| NoConfiguration uint64 |
| ManagedAddress uint64 |
| OtherConfiguration uint64 |
| } |
| |
| type step struct { |
| run func(*ndpDispatcher) |
| want statsSnapshot |
| } |
| |
| getSnapshot := func(ns *Netstack) statsSnapshot { |
| d := ns.stats.DHCPv6 |
| return statsSnapshot{ |
| NoConfiguration: d.NoConfiguration.Value(), |
| ManagedAddress: d.ManagedAddress.Value(), |
| OtherConfiguration: d.OtherConfiguration.Value(), |
| } |
| } |
| |
| tests := []struct { |
| name string |
| steps []step |
| }{ |
| { |
| name: "one configuration", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher) { |
| ndpDisp.OnDHCPv6Configuration(0, ipv6.DHCPv6NoConfiguration) |
| }, |
| want: statsSnapshot{ |
| NoConfiguration: 1, |
| ManagedAddress: 0, |
| OtherConfiguration: 0, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "multiple configurations", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher) { |
| ndpDisp.OnDHCPv6Configuration(0, ipv6.DHCPv6NoConfiguration) |
| ndpDisp.OnDHCPv6Configuration(0, ipv6.DHCPv6ManagedAddress) |
| }, |
| want: statsSnapshot{ |
| NoConfiguration: 1, |
| ManagedAddress: 1, |
| OtherConfiguration: 0, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "pull between configurations", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher) { |
| ndpDisp.OnDHCPv6Configuration(0, ipv6.DHCPv6NoConfiguration) |
| }, |
| want: statsSnapshot{ |
| NoConfiguration: 1, |
| ManagedAddress: 0, |
| OtherConfiguration: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher) { |
| ndpDisp.OnDHCPv6Configuration(0, ipv6.DHCPv6NoConfiguration) |
| }, |
| want: statsSnapshot{ |
| NoConfiguration: 2, |
| ManagedAddress: 0, |
| OtherConfiguration: 0, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "duplicated configurations", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher) { |
| ndpDisp.OnDHCPv6Configuration(0, ipv6.DHCPv6NoConfiguration) |
| ndpDisp.OnDHCPv6Configuration(0, ipv6.DHCPv6NoConfiguration) |
| }, |
| want: statsSnapshot{ |
| NoConfiguration: 2, |
| ManagedAddress: 0, |
| OtherConfiguration: 0, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ns := &Netstack{stack: stack.New(stack.Options{Clock: &faketime.NullClock{}})} |
| ndpDisp := newNDPDispatcher() |
| ndpDisp.ns = ns |
| ndpDisp.dynamicAddressSourceTracker.init(ns) |
| |
| for i, step := range test.steps { |
| step.run(ndpDisp) |
| if diff := cmp.Diff(step.want, getSnapshot(ns)); diff != "" { |
| t.Errorf("%d-th step: mismatch (-want +got):\n%s", i, diff) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestIPv6AddressConfigTracker(t *testing.T) { |
| const ( |
| nicID1 = 1 |
| nicID2 = 2 |
| ) |
| |
| linkLocalAddr := tcpip.AddressWithPrefix{ |
| Address: util.Parse("fe80::1"), |
| PrefixLen: 64, |
| } |
| |
| type statsSnapshot struct { |
| NoGlobalSLAACOrDHCPv6ManagedAddress uint64 |
| GlobalSLAACOnly uint64 |
| DHCPv6ManagedAddressOnly uint64 |
| GlobalSLAACAndDHCPv6ManagedAddress uint64 |
| } |
| |
| type step struct { |
| run func(*ndpDispatcher, *faketime.ManualClock) |
| want statsSnapshot |
| } |
| |
| getSnapshot := func(ns *Netstack) statsSnapshot { |
| c := ns.stats.IPv6AddressConfig |
| return statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: c.NoGlobalSLAACOrDHCPv6ManagedAddress.Value(), |
| GlobalSLAACOnly: c.GlobalSLAACOnly.Value(), |
| DHCPv6ManagedAddressOnly: c.DHCPv6ManagedAddressOnly.Value(), |
| GlobalSLAACAndDHCPv6ManagedAddress: c.GlobalSLAACAndDHCPv6ManagedAddress.Value(), |
| } |
| } |
| |
| tests := []struct { |
| name string |
| steps []step |
| }{ |
| { |
| name: "dynamic address config with no config", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| clock.Advance(ipv6AddressConfigTrackerInitialDelay) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 0, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| // Link local addresses should not increment global SLAAC address |
| // count. |
| ndpDisp.OnAutoGenAddress(nicID1, linkLocalAddr) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 0, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnDHCPv6Configuration(nicID1, ipv6.DHCPv6NoConfiguration) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 1, |
| GlobalSLAACOnly: 0, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnDHCPv6Configuration(nicID1, ipv6.DHCPv6OtherConfigurations) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 2, |
| GlobalSLAACOnly: 0, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnAutoGenAddress(nicID1, testProtocolAddr1.AddressWithPrefix) |
| ndpDisp.OnAutoGenAddressInvalidated(nicID1, testProtocolAddr1.AddressWithPrefix) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 3, |
| GlobalSLAACOnly: 0, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "dynamic address config with slaac only", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnAutoGenAddress(nicID1, testProtocolAddr1.AddressWithPrefix) |
| clock.Advance(ipv6AddressConfigTrackerInitialDelay) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 1, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| // Link local addresses should not decrement global SLAAC address |
| // count. |
| ndpDisp.OnAutoGenAddressInvalidated(nicID1, linkLocalAddr) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 2, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnAutoGenAddress(nicID1, testProtocolAddr2.AddressWithPrefix) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 3, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnAutoGenAddressInvalidated(nicID1, testProtocolAddr2.AddressWithPrefix) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 4, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "dynamic address config with dhcpv6 only", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnDHCPv6Configuration(nicID1, ipv6.DHCPv6ManagedAddress) |
| clock.Advance(ipv6AddressConfigTrackerInitialDelay) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 0, |
| DHCPv6ManagedAddressOnly: 1, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| // Only the last configuration should be used. |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnDHCPv6Configuration(nicID1, ipv6.DHCPv6NoConfiguration) |
| ndpDisp.OnDHCPv6Configuration(nicID1, ipv6.DHCPv6ManagedAddress) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 0, |
| DHCPv6ManagedAddressOnly: 2, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "dynamic address config with dhcpv6 and slaac", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnDHCPv6Configuration(nicID1, ipv6.DHCPv6ManagedAddress) |
| ndpDisp.OnAutoGenAddress(nicID1, testProtocolAddr2.AddressWithPrefix) |
| clock.Advance(ipv6AddressConfigTrackerInitialDelay) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 0, |
| DHCPv6ManagedAddressOnly: 0, |
| GlobalSLAACAndDHCPv6ManagedAddress: 1, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "dynamic address config with dhcpv6 and slaac on different NICs", |
| steps: []step{ |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.OnDHCPv6Configuration(nicID1, ipv6.DHCPv6ManagedAddress) |
| ndpDisp.OnAutoGenAddress(nicID2, testProtocolAddr2.AddressWithPrefix) |
| clock.Advance(ipv6AddressConfigTrackerInitialDelay) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 1, |
| DHCPv6ManagedAddressOnly: 1, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.dynamicAddressSourceTracker.RemovedNIC(nicID1) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 2, |
| DHCPv6ManagedAddressOnly: 1, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| { |
| run: func(ndpDisp *ndpDispatcher, clock *faketime.ManualClock) { |
| ndpDisp.dynamicAddressSourceTracker.RemovedNIC(nicID2) |
| clock.Advance(ipv6AddressConfigTrackerInterval) |
| }, |
| want: statsSnapshot{ |
| NoGlobalSLAACOrDHCPv6ManagedAddress: 0, |
| GlobalSLAACOnly: 2, |
| DHCPv6ManagedAddressOnly: 1, |
| GlobalSLAACAndDHCPv6ManagedAddress: 0, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| clock := faketime.NewManualClock() |
| ns := &Netstack{stack: stack.New(stack.Options{Clock: clock})} |
| ndpDisp := newNDPDispatcher() |
| ndpDisp.ns = ns |
| ndpDisp.dynamicAddressSourceTracker.init(ns) |
| |
| for i, step := range test.steps { |
| step.run(ndpDisp, clock) |
| if diff := cmp.Diff(step.want, getSnapshot(ns)); diff != "" { |
| t.Errorf("%d-th step: mismatch (-want +got):\n%s", i, diff) |
| } |
| } |
| }) |
| } |
| } |