| // Copyright 2019 The gVisor Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package stack_test |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/binary" |
| "fmt" |
| "testing" |
| "time" |
| |
| "github.com/google/go-cmp/cmp" |
| "gvisor.dev/gvisor/pkg/rand" |
| "gvisor.dev/gvisor/pkg/tcpip" |
| "gvisor.dev/gvisor/pkg/tcpip/buffer" |
| "gvisor.dev/gvisor/pkg/tcpip/checker" |
| "gvisor.dev/gvisor/pkg/tcpip/faketime" |
| "gvisor.dev/gvisor/pkg/tcpip/header" |
| "gvisor.dev/gvisor/pkg/tcpip/link/channel" |
| "gvisor.dev/gvisor/pkg/tcpip/link/loopback" |
| "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" |
| "gvisor.dev/gvisor/pkg/tcpip/stack" |
| "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" |
| "gvisor.dev/gvisor/pkg/tcpip/transport/udp" |
| "gvisor.dev/gvisor/pkg/waiter" |
| ) |
| |
| const ( |
| 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") |
| linkAddr4 = tcpip.LinkAddress("\x02\x02\x03\x04\x05\x09") |
| |
| defaultPrefixLen = 128 |
| |
| // Extra time to use when waiting for an async event to occur. |
| defaultAsyncPositiveEventTimeout = 10 * time.Second |
| |
| // Extra time to use when waiting for an async event to not occur. |
| // |
| // Since a negative check is used to make sure an event did not happen, it is |
| // okay to use a smaller timeout compared to the positive case since execution |
| // stall in regards to the monotonic clock will not affect the expected |
| // outcome. |
| defaultAsyncNegativeEventTimeout = time.Second |
| ) |
| |
| var ( |
| llAddr1 = header.LinkLocalAddr(linkAddr1) |
| llAddr2 = header.LinkLocalAddr(linkAddr2) |
| llAddr3 = header.LinkLocalAddr(linkAddr3) |
| llAddr4 = header.LinkLocalAddr(linkAddr4) |
| dstAddr = tcpip.FullAddress{ |
| Addr: "\x0a\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", |
| Port: 25, |
| } |
| ) |
| |
| 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. |
| func prefixSubnetAddr(offset uint8, linkAddr tcpip.LinkAddress) (tcpip.AddressWithPrefix, tcpip.Subnet, tcpip.AddressWithPrefix) { |
| prefixBytes := []byte{1, 2, 3, 4, 5, 6, 7, 8 + offset, 0, 0, 0, 0, 0, 0, 0, 0} |
| prefix := tcpip.AddressWithPrefix{ |
| Address: tcpip.Address(prefixBytes), |
| PrefixLen: 64, |
| } |
| |
| subnet := prefix.Subnet() |
| |
| return prefix, subnet, addrForSubnet(subnet, linkAddr) |
| } |
| |
| // ndpDADEvent is a set of parameters that was passed to |
| // ndpDispatcher.OnDuplicateAddressDetectionResult. |
| type ndpDADEvent struct { |
| nicID tcpip.NICID |
| addr tcpip.Address |
| res stack.DADResult |
| } |
| |
| type ndpRouterEvent struct { |
| nicID tcpip.NICID |
| addr tcpip.Address |
| // true if router was discovered, false if invalidated. |
| discovered bool |
| } |
| |
| type ndpPrefixEvent struct { |
| nicID tcpip.NICID |
| prefix tcpip.Subnet |
| // true if prefix was discovered, false if invalidated. |
| discovered bool |
| } |
| |
| type ndpAutoGenAddrEventType int |
| |
| const ( |
| newAddr ndpAutoGenAddrEventType = iota |
| deprecatedAddr |
| invalidatedAddr |
| ) |
| |
| type ndpAutoGenAddrEvent struct { |
| nicID tcpip.NICID |
| addr tcpip.AddressWithPrefix |
| eventType ndpAutoGenAddrEventType |
| } |
| |
| type ndpRDNSS struct { |
| addrs []tcpip.Address |
| lifetime time.Duration |
| } |
| |
| type ndpRDNSSEvent struct { |
| nicID tcpip.NICID |
| rdnss ndpRDNSS |
| } |
| |
| type ndpDNSSLEvent struct { |
| nicID tcpip.NICID |
| domainNames []string |
| lifetime time.Duration |
| } |
| |
| type ndpDHCPv6Event struct { |
| nicID tcpip.NICID |
| configuration ipv6.DHCPv6ConfigurationFromNDPRA |
| } |
| |
| var _ ipv6.NDPDispatcher = (*ndpDispatcher)(nil) |
| |
| // ndpDispatcher implements NDPDispatcher so tests can know when various NDP |
| // related events happen for test purposes. |
| type ndpDispatcher struct { |
| dadC chan ndpDADEvent |
| routerC chan ndpRouterEvent |
| rememberRouter bool |
| prefixC chan ndpPrefixEvent |
| rememberPrefix bool |
| autoGenAddrC chan ndpAutoGenAddrEvent |
| rdnssC chan ndpRDNSSEvent |
| dnsslC chan ndpDNSSLEvent |
| routeTable []tcpip.Route |
| dhcpv6ConfigurationC chan ndpDHCPv6Event |
| } |
| |
| // Implements ipv6.NDPDispatcher.OnDuplicateAddressDetectionResult. |
| func (n *ndpDispatcher) OnDuplicateAddressDetectionResult(nicID tcpip.NICID, addr tcpip.Address, res stack.DADResult) { |
| if n.dadC != nil { |
| n.dadC <- ndpDADEvent{ |
| nicID, |
| addr, |
| res, |
| } |
| } |
| } |
| |
| // Implements ipv6.NDPDispatcher.OnDefaultRouterDiscovered. |
| func (n *ndpDispatcher) OnDefaultRouterDiscovered(nicID tcpip.NICID, addr tcpip.Address) bool { |
| if c := n.routerC; c != nil { |
| c <- ndpRouterEvent{ |
| nicID, |
| addr, |
| true, |
| } |
| } |
| |
| return n.rememberRouter |
| } |
| |
| // Implements ipv6.NDPDispatcher.OnDefaultRouterInvalidated. |
| func (n *ndpDispatcher) OnDefaultRouterInvalidated(nicID tcpip.NICID, addr tcpip.Address) { |
| if c := n.routerC; c != nil { |
| c <- ndpRouterEvent{ |
| nicID, |
| addr, |
| false, |
| } |
| } |
| } |
| |
| // Implements ipv6.NDPDispatcher.OnOnLinkPrefixDiscovered. |
| func (n *ndpDispatcher) OnOnLinkPrefixDiscovered(nicID tcpip.NICID, prefix tcpip.Subnet) bool { |
| if c := n.prefixC; c != nil { |
| c <- ndpPrefixEvent{ |
| nicID, |
| prefix, |
| true, |
| } |
| } |
| |
| return n.rememberPrefix |
| } |
| |
| // Implements ipv6.NDPDispatcher.OnOnLinkPrefixInvalidated. |
| func (n *ndpDispatcher) OnOnLinkPrefixInvalidated(nicID tcpip.NICID, prefix tcpip.Subnet) { |
| if c := n.prefixC; c != nil { |
| c <- ndpPrefixEvent{ |
| nicID, |
| prefix, |
| false, |
| } |
| } |
| } |
| |
| func (n *ndpDispatcher) OnAutoGenAddress(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) bool { |
| if c := n.autoGenAddrC; c != nil { |
| c <- ndpAutoGenAddrEvent{ |
| nicID, |
| addr, |
| newAddr, |
| } |
| } |
| return true |
| } |
| |
| func (n *ndpDispatcher) OnAutoGenAddressDeprecated(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) { |
| if c := n.autoGenAddrC; c != nil { |
| c <- ndpAutoGenAddrEvent{ |
| nicID, |
| addr, |
| deprecatedAddr, |
| } |
| } |
| } |
| |
| func (n *ndpDispatcher) OnAutoGenAddressInvalidated(nicID tcpip.NICID, addr tcpip.AddressWithPrefix) { |
| if c := n.autoGenAddrC; c != nil { |
| c <- ndpAutoGenAddrEvent{ |
| nicID, |
| addr, |
| invalidatedAddr, |
| } |
| } |
| } |
| |
| // Implements ipv6.NDPDispatcher.OnRecursiveDNSServerOption. |
| func (n *ndpDispatcher) OnRecursiveDNSServerOption(nicID tcpip.NICID, addrs []tcpip.Address, lifetime time.Duration) { |
| if c := n.rdnssC; c != nil { |
| c <- ndpRDNSSEvent{ |
| nicID, |
| ndpRDNSS{ |
| addrs, |
| lifetime, |
| }, |
| } |
| } |
| } |
| |
| // Implements ipv6.NDPDispatcher.OnDNSSearchListOption. |
| func (n *ndpDispatcher) OnDNSSearchListOption(nicID tcpip.NICID, domainNames []string, lifetime time.Duration) { |
| if n.dnsslC != nil { |
| n.dnsslC <- ndpDNSSLEvent{ |
| nicID, |
| domainNames, |
| lifetime, |
| } |
| } |
| } |
| |
| // Implements ipv6.NDPDispatcher.OnDHCPv6Configuration. |
| func (n *ndpDispatcher) OnDHCPv6Configuration(nicID tcpip.NICID, configuration ipv6.DHCPv6ConfigurationFromNDPRA) { |
| if c := n.dhcpv6ConfigurationC; c != nil { |
| c <- ndpDHCPv6Event{ |
| nicID, |
| configuration, |
| } |
| } |
| } |
| |
| // channelLinkWithHeaderLength is a channel.Endpoint with a configurable |
| // header length. |
| type channelLinkWithHeaderLength struct { |
| *channel.Endpoint |
| headerLength uint16 |
| } |
| |
| func (l *channelLinkWithHeaderLength) MaxHeaderLength() uint16 { |
| return l.headerLength |
| } |
| |
| // Check e to make sure that the event is for addr on nic with ID 1, and the |
| // resolved flag set to resolved with the specified err. |
| func checkDADEvent(e ndpDADEvent, nicID tcpip.NICID, addr tcpip.Address, res stack.DADResult) string { |
| return cmp.Diff(ndpDADEvent{nicID: nicID, addr: addr, res: res}, e, cmp.AllowUnexported(e)) |
| } |
| |
| // TestDADDisabled tests that an address successfully resolves immediately |
| // when DAD is not enabled (the default for an empty stack.Options). |
| func TestDADDisabled(t *testing.T) { |
| const nicID = 1 |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| addrWithPrefix := tcpip.AddressWithPrefix{ |
| Address: addr1, |
| PrefixLen: defaultPrefixLen, |
| } |
| if err := s.AddAddressWithPrefix(nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil { |
| t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addrWithPrefix, err) |
| } |
| |
| // Should get the address immediately since we should not have performed |
| // DAD on it. |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr1, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected DAD event") |
| } |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil { |
| t.Fatal(err) |
| } |
| |
| // We should not have sent any NDP NS messages. |
| if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got != 0 { |
| t.Fatalf("got NeighborSolicit = %d, want = 0", got) |
| } |
| } |
| |
| func TestDADResolveLoopback(t *testing.T) { |
| const nicID = 1 |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent, 1), |
| } |
| |
| dadConfigs := stack.DADConfigurations{ |
| RetransmitTimer: time.Second, |
| DupAddrDetectTransmits: 1, |
| } |
| clock := faketime.NewManualClock() |
| s := stack.New(stack.Options{ |
| Clock: clock, |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPDisp: &ndpDisp, |
| DADConfigs: dadConfigs, |
| })}, |
| }) |
| if err := s.CreateNIC(nicID, loopback.New()); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| addrWithPrefix := tcpip.AddressWithPrefix{ |
| Address: addr1, |
| PrefixLen: defaultPrefixLen, |
| } |
| if err := s.AddAddressWithPrefix(nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil { |
| t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addrWithPrefix, err) |
| } |
| |
| // Address should not be considered bound to the NIC yet (DAD ongoing). |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| |
| // DAD should not resolve after the normal resolution time since our DAD |
| // message was looped back - we should extend our DAD process. |
| dadResolutionTime := time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer |
| clock.Advance(dadResolutionTime) |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Error(err) |
| } |
| |
| // Make sure the address does not resolve before the extended resolution time |
| // has passed. |
| const delta = time.Nanosecond |
| // DAD will send extra NS probes if an NS message is looped back. |
| const extraTransmits = 3 |
| clock.Advance(dadResolutionTime*extraTransmits - delta) |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Error(err) |
| } |
| |
| // DAD should now resolve. |
| clock.Advance(delta) |
| if diff := checkDADEvent(<-ndpDisp.dadC, nicID, addr1, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| // TestDADResolve tests that an address successfully resolves after performing |
| // DAD for various values of DupAddrDetectTransmits and RetransmitTimer. |
| // Included in the subtests is a test to make sure that an invalid |
| // RetransmitTimer (<1ms) values get fixed to the default RetransmitTimer of 1s. |
| // This tests also validates the NDP NS packet that is transmitted. |
| func TestDADResolve(t *testing.T) { |
| const nicID = 1 |
| |
| tests := []struct { |
| name string |
| linkHeaderLen uint16 |
| dupAddrDetectTransmits uint8 |
| retransTimer time.Duration |
| expectedRetransmitTimer time.Duration |
| }{ |
| { |
| name: "1:1s:1s", |
| dupAddrDetectTransmits: 1, |
| retransTimer: time.Second, |
| expectedRetransmitTimer: time.Second, |
| }, |
| { |
| name: "2:1s:1s", |
| linkHeaderLen: 1, |
| dupAddrDetectTransmits: 2, |
| retransTimer: time.Second, |
| expectedRetransmitTimer: time.Second, |
| }, |
| { |
| name: "1:2s:2s", |
| linkHeaderLen: 2, |
| dupAddrDetectTransmits: 1, |
| retransTimer: 2 * time.Second, |
| expectedRetransmitTimer: 2 * time.Second, |
| }, |
| // 0s is an invalid RetransmitTimer timer and will be fixed to |
| // the default RetransmitTimer value of 1s. |
| { |
| name: "1:0s:1s", |
| linkHeaderLen: 3, |
| dupAddrDetectTransmits: 1, |
| retransTimer: 0, |
| expectedRetransmitTimer: time.Second, |
| }, |
| } |
| |
| nonces := [][]byte{ |
| {1, 2, 3, 4, 5, 6}, |
| {7, 8, 9, 10, 11, 12}, |
| } |
| |
| var secureRNGBytes []byte |
| for _, n := range nonces { |
| secureRNGBytes = append(secureRNGBytes, n...) |
| } |
| |
| for _, test := range tests { |
| test := test |
| |
| t.Run(test.name, func(t *testing.T) { |
| t.Parallel() |
| |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent), |
| } |
| |
| e := channelLinkWithHeaderLength{ |
| Endpoint: channel.New(int(test.dupAddrDetectTransmits), 1280, linkAddr1), |
| headerLength: test.linkHeaderLen, |
| } |
| e.Endpoint.LinkEPCapabilities |= stack.CapabilityResolutionRequired |
| |
| var secureRNG bytes.Reader |
| secureRNG.Reset(secureRNGBytes) |
| |
| s := stack.New(stack.Options{ |
| SecureRNG: &secureRNG, |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPDisp: &ndpDisp, |
| DADConfigs: stack.DADConfigurations{ |
| RetransmitTimer: test.retransTimer, |
| DupAddrDetectTransmits: test.dupAddrDetectTransmits, |
| }, |
| })}, |
| }) |
| if err := s.CreateNIC(nicID, &e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| // We add a default route so the call to FindRoute below will succeed |
| // once we have an assigned address. |
| s.SetRouteTable([]tcpip.Route{{ |
| Destination: header.IPv6EmptySubnet, |
| Gateway: addr3, |
| NIC: nicID, |
| }}) |
| |
| addrWithPrefix := tcpip.AddressWithPrefix{ |
| Address: addr1, |
| PrefixLen: defaultPrefixLen, |
| } |
| if err := s.AddAddressWithPrefix(nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil { |
| t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addrWithPrefix, err) |
| } |
| |
| // Address should not be considered bound to the NIC yet (DAD ongoing). |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Make sure the address does not resolve before the resolution time has |
| // passed. |
| time.Sleep(test.expectedRetransmitTimer*time.Duration(test.dupAddrDetectTransmits) - defaultAsyncNegativeEventTimeout) |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Error(err) |
| } |
| // Should not get a route even if we specify the local address as the |
| // tentative address. |
| { |
| r, err := s.FindRoute(nicID, "", addr2, header.IPv6ProtocolNumber, false) |
| if _, ok := err.(*tcpip.ErrNoRoute); !ok { |
| t.Errorf("got FindRoute(%d, '', %s, %d, false) = (%+v, %v), want = (_, %s)", nicID, addr2, header.IPv6ProtocolNumber, r, err, &tcpip.ErrNoRoute{}) |
| } |
| if r != nil { |
| r.Release() |
| } |
| } |
| { |
| r, err := s.FindRoute(nicID, addr1, addr2, header.IPv6ProtocolNumber, false) |
| if _, ok := err.(*tcpip.ErrNoRoute); !ok { |
| t.Errorf("got FindRoute(%d, %s, %s, %d, false) = (%+v, %v), want = (_, %s)", nicID, addr1, addr2, header.IPv6ProtocolNumber, r, err, &tcpip.ErrNoRoute{}) |
| } |
| if r != nil { |
| r.Release() |
| } |
| } |
| |
| if t.Failed() { |
| t.FailNow() |
| } |
| |
| // Wait for DAD to resolve. |
| select { |
| case <-time.After(defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for DAD resolution") |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr1, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| } |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil { |
| t.Error(err) |
| } |
| // Should get a route using the address now that it is resolved. |
| { |
| r, err := s.FindRoute(nicID, "", addr2, header.IPv6ProtocolNumber, false) |
| if err != nil { |
| t.Errorf("got FindRoute(%d, '', %s, %d, false): %s", nicID, addr2, header.IPv6ProtocolNumber, err) |
| } else if r.LocalAddress() != addr1 { |
| t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), addr1) |
| } |
| r.Release() |
| } |
| { |
| r, err := s.FindRoute(nicID, addr1, addr2, header.IPv6ProtocolNumber, false) |
| if err != nil { |
| t.Errorf("got FindRoute(%d, %s, %s, %d, false): %s", nicID, addr1, addr2, header.IPv6ProtocolNumber, err) |
| } else if r.LocalAddress() != addr1 { |
| t.Errorf("got r.LocalAddress() = %s, want = %s", r.LocalAddress(), addr1) |
| } |
| if r != nil { |
| r.Release() |
| } |
| } |
| |
| if t.Failed() { |
| t.FailNow() |
| } |
| |
| // Should not have sent any more NS messages. |
| if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got != uint64(test.dupAddrDetectTransmits) { |
| t.Fatalf("got NeighborSolicit = %d, want = %d", got, test.dupAddrDetectTransmits) |
| } |
| |
| // Validate the sent Neighbor Solicitation messages. |
| for i := uint8(0); i < test.dupAddrDetectTransmits; i++ { |
| p, _ := e.ReadContext(context.Background()) |
| |
| // Make sure its an IPv6 packet. |
| if p.Proto != header.IPv6ProtocolNumber { |
| t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber) |
| } |
| |
| // Make sure the right remote link address is used. |
| snmc := header.SolicitedNodeAddr(addr1) |
| if want := header.EthernetAddressFromMulticastIPv6Address(snmc); p.Route.RemoteLinkAddress != want { |
| t.Errorf("got remote link address = %s, want = %s", p.Route.RemoteLinkAddress, want) |
| } |
| |
| // Check NDP NS packet. |
| // |
| // As per RFC 4861 section 4.3, a possible option is the Source Link |
| // Layer option, but this option MUST NOT be included when the source |
| // address of the packet is the unspecified address. |
| checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()), |
| checker.SrcAddr(header.IPv6Any), |
| checker.DstAddr(snmc), |
| checker.TTL(header.NDPHopLimit), |
| checker.NDPNS( |
| checker.NDPNSTargetAddress(addr1), |
| checker.NDPNSOptions([]header.NDPOption{header.NDPNonceOption(nonces[i])}), |
| )) |
| |
| if l, want := p.Pkt.AvailableHeaderBytes(), int(test.linkHeaderLen); l != want { |
| t.Errorf("got p.Pkt.AvailableHeaderBytes() = %d; want = %d", l, want) |
| } |
| } |
| }) |
| } |
| } |
| |
| func rxNDPSolicit(e *channel.Endpoint, tgt tcpip.Address) { |
| hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.ICMPv6NeighborSolicitMinimumSize) |
| pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborSolicitMinimumSize)) |
| pkt.SetType(header.ICMPv6NeighborSolicit) |
| ns := header.NDPNeighborSolicit(pkt.MessageBody()) |
| ns.SetTargetAddress(tgt) |
| snmc := header.SolicitedNodeAddr(tgt) |
| pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ |
| Header: pkt, |
| Src: header.IPv6Any, |
| Dst: snmc, |
| })) |
| payloadLength := hdr.UsedLength() |
| ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize)) |
| ip.Encode(&header.IPv6Fields{ |
| PayloadLength: uint16(payloadLength), |
| TransportProtocol: icmp.ProtocolNumber6, |
| HopLimit: 255, |
| SrcAddr: header.IPv6Any, |
| DstAddr: snmc, |
| }) |
| e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{Data: hdr.View().ToVectorisedView()})) |
| } |
| |
| // TestDADFail tests to make sure that the DAD process fails if another node is |
| // detected to be performing DAD on the same address (receive an NS message from |
| // a node doing DAD for the same address), or if another node is detected to own |
| // the address already (receive an NA message for the tentative address). |
| func TestDADFail(t *testing.T) { |
| const nicID = 1 |
| |
| tests := []struct { |
| name string |
| rxPkt func(e *channel.Endpoint, tgt tcpip.Address) |
| getStat func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter |
| expectedHolderLinkAddress tcpip.LinkAddress |
| }{ |
| { |
| name: "RxSolicit", |
| rxPkt: rxNDPSolicit, |
| getStat: func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter { |
| return s.NeighborSolicit |
| }, |
| expectedHolderLinkAddress: "", |
| }, |
| { |
| name: "RxAdvert", |
| rxPkt: func(e *channel.Endpoint, tgt tcpip.Address) { |
| naSize := header.ICMPv6NeighborAdvertMinimumSize + header.NDPLinkLayerAddressSize |
| hdr := buffer.NewPrependable(header.IPv6MinimumSize + naSize) |
| pkt := header.ICMPv6(hdr.Prepend(naSize)) |
| pkt.SetType(header.ICMPv6NeighborAdvert) |
| na := header.NDPNeighborAdvert(pkt.MessageBody()) |
| na.SetSolicitedFlag(true) |
| na.SetOverrideFlag(true) |
| na.SetTargetAddress(tgt) |
| na.Options().Serialize(header.NDPOptionsSerializer{ |
| header.NDPTargetLinkLayerAddressOption(linkAddr1), |
| }) |
| pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ |
| Header: pkt, |
| Src: tgt, |
| Dst: header.IPv6AllNodesMulticastAddress, |
| })) |
| payloadLength := hdr.UsedLength() |
| ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize)) |
| ip.Encode(&header.IPv6Fields{ |
| PayloadLength: uint16(payloadLength), |
| TransportProtocol: icmp.ProtocolNumber6, |
| HopLimit: 255, |
| SrcAddr: tgt, |
| DstAddr: header.IPv6AllNodesMulticastAddress, |
| }) |
| e.InjectInbound(header.IPv6ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{Data: hdr.View().ToVectorisedView()})) |
| }, |
| getStat: func(s tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter { |
| return s.NeighborAdvert |
| }, |
| expectedHolderLinkAddress: linkAddr1, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent, 1), |
| } |
| dadConfigs := stack.DefaultDADConfigurations() |
| dadConfigs.RetransmitTimer = time.Second * 2 |
| |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPDisp: &ndpDisp, |
| DADConfigs: dadConfigs, |
| })}, |
| }) |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr1); err != nil { |
| t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr1, err) |
| } |
| |
| // Address should not be considered bound to the NIC yet |
| // (DAD ongoing). |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Receive a packet to simulate an address conflict. |
| test.rxPkt(e, addr1) |
| |
| stat := test.getStat(s.Stats().ICMP.V6.PacketsReceived) |
| if got := stat.Value(); got != 1 { |
| t.Fatalf("got stat = %d, want = 1", got) |
| } |
| |
| // Wait for DAD to fail and make sure the address did |
| // not get resolved. |
| select { |
| case <-time.After(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer + time.Second): |
| // If we don't get a failure event after the |
| // expected resolution time + extra 1s buffer, |
| // something is wrong. |
| t.Fatal("timed out waiting for DAD failure") |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr1, &stack.DADDupAddrDetected{HolderLinkAddress: test.expectedHolderLinkAddress}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| } |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| |
| // 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) |
| } |
| }) |
| } |
| } |
| |
| func TestDADStop(t *testing.T) { |
| const nicID = 1 |
| |
| tests := []struct { |
| name string |
| stopFn func(t *testing.T, s *stack.Stack) |
| skipFinalAddrCheck bool |
| }{ |
| // Tests to make sure that DAD stops when an address is removed. |
| { |
| name: "Remove address", |
| stopFn: func(t *testing.T, s *stack.Stack) { |
| if err := s.RemoveAddress(nicID, addr1); err != nil { |
| t.Fatalf("RemoveAddress(%d, %s): %s", nicID, addr1, err) |
| } |
| }, |
| }, |
| |
| // Tests to make sure that DAD stops when the NIC is disabled. |
| { |
| name: "Disable NIC", |
| stopFn: func(t *testing.T, s *stack.Stack) { |
| if err := s.DisableNIC(nicID); err != nil { |
| t.Fatalf("DisableNIC(%d): %s", nicID, err) |
| } |
| }, |
| }, |
| |
| // Tests to make sure that DAD stops when the NIC is removed. |
| { |
| name: "Remove NIC", |
| stopFn: func(t *testing.T, s *stack.Stack) { |
| if err := s.RemoveNIC(nicID); err != nil { |
| t.Fatalf("RemoveNIC(%d): %s", nicID, err) |
| } |
| }, |
| // The NIC is removed so we can't check its addresses after calling |
| // stopFn. |
| skipFinalAddrCheck: true, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent, 1), |
| } |
| |
| dadConfigs := stack.DADConfigurations{ |
| RetransmitTimer: time.Second, |
| DupAddrDetectTransmits: 2, |
| } |
| |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPDisp: &ndpDisp, |
| DADConfigs: dadConfigs, |
| })}, |
| }) |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _): %s", nicID, err) |
| } |
| |
| if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr1); err != nil { |
| t.Fatalf("AddAddress(%d, %d, %s): %s", nicID, header.IPv6ProtocolNumber, addr1, err) |
| } |
| |
| // Address should not be considered bound to the NIC yet (DAD ongoing). |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| |
| test.stopFn(t, s) |
| |
| // Wait for DAD to fail (since the address was removed during DAD). |
| select { |
| case <-time.After(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer + time.Second): |
| // If we don't get a failure event after the expected resolution |
| // time + extra 1s buffer, something is wrong. |
| t.Fatal("timed out waiting for DAD failure") |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr1, &stack.DADAborted{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| if !test.skipFinalAddrCheck { |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| // Should not have sent more than 1 NS message. |
| if got := s.Stats().ICMP.V6.PacketsSent.NeighborSolicit.Value(); got > 1 { |
| t.Errorf("got NeighborSolicit = %d, want <= 1", got) |
| } |
| }) |
| } |
| } |
| |
| // TestSetNDPConfigurations tests that we can update and use per-interface NDP |
| // configurations without affecting the default NDP configurations or other |
| // interfaces' configurations. |
| func TestSetNDPConfigurations(t *testing.T) { |
| const nicID1 = 1 |
| const nicID2 = 2 |
| const nicID3 = 3 |
| |
| tests := []struct { |
| name string |
| dupAddrDetectTransmits uint8 |
| retransmitTimer time.Duration |
| expectedRetransmitTimer time.Duration |
| }{ |
| { |
| "OK", |
| 1, |
| time.Second, |
| time.Second, |
| }, |
| { |
| "Invalid Retransmit Timer", |
| 1, |
| 0, |
| time.Second, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| expectDADEvent := func(nicID tcpip.NICID, addr tcpip.Address) { |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatalf("expected DAD event for %s", addr) |
| } |
| } |
| |
| // This NIC(1)'s NDP configurations will be updated to |
| // be different from the default. |
| if err := s.CreateNIC(nicID1, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID1, err) |
| } |
| |
| // Created before updating NIC(1)'s NDP configurations |
| // but updating NIC(1)'s NDP configurations should not |
| // affect other existing NICs. |
| if err := s.CreateNIC(nicID2, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err) |
| } |
| |
| // Update the configurations on NIC(1) to use DAD. |
| if ipv6Ep, err := s.GetNetworkEndpoint(nicID1, header.IPv6ProtocolNumber); err != nil { |
| t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID1, header.IPv6ProtocolNumber, err) |
| } else { |
| dad := ipv6Ep.(stack.DuplicateAddressDetector) |
| dad.SetDADConfigurations(stack.DADConfigurations{ |
| DupAddrDetectTransmits: test.dupAddrDetectTransmits, |
| RetransmitTimer: test.retransmitTimer, |
| }) |
| } |
| |
| // Created after updating NIC(1)'s NDP configurations |
| // but the stack's default NDP configurations should not |
| // have been updated. |
| if err := s.CreateNIC(nicID3, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID3, err) |
| } |
| |
| // Add addresses for each NIC. |
| addrWithPrefix1 := tcpip.AddressWithPrefix{Address: addr1, PrefixLen: defaultPrefixLen} |
| if err := s.AddAddressWithPrefix(nicID1, header.IPv6ProtocolNumber, addrWithPrefix1); err != nil { |
| t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID1, header.IPv6ProtocolNumber, addrWithPrefix1, err) |
| } |
| addrWithPrefix2 := tcpip.AddressWithPrefix{Address: addr2, PrefixLen: defaultPrefixLen} |
| if err := s.AddAddressWithPrefix(nicID2, header.IPv6ProtocolNumber, addrWithPrefix2); err != nil { |
| t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID2, header.IPv6ProtocolNumber, addrWithPrefix2, err) |
| } |
| expectDADEvent(nicID2, addr2) |
| addrWithPrefix3 := tcpip.AddressWithPrefix{Address: addr3, PrefixLen: defaultPrefixLen} |
| if err := s.AddAddressWithPrefix(nicID3, header.IPv6ProtocolNumber, addrWithPrefix3); err != nil { |
| t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID3, header.IPv6ProtocolNumber, addrWithPrefix3, err) |
| } |
| expectDADEvent(nicID3, addr3) |
| |
| // Address should not be considered bound to NIC(1) yet |
| // (DAD ongoing). |
| if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Should get the address on NIC(2) and NIC(3) |
| // immediately since we should not have performed DAD on |
| // it as the stack was configured to not do DAD by |
| // default and we only updated the NDP configurations on |
| // NIC(1). |
| if err := checkGetMainNICAddress(s, nicID2, header.IPv6ProtocolNumber, addrWithPrefix2); err != nil { |
| t.Fatal(err) |
| } |
| if err := checkGetMainNICAddress(s, nicID3, header.IPv6ProtocolNumber, addrWithPrefix3); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Sleep until right (500ms before) before resolution to |
| // make sure the address didn't resolve on NIC(1) yet. |
| const delta = 500 * time.Millisecond |
| time.Sleep(time.Duration(test.dupAddrDetectTransmits)*test.expectedRetransmitTimer - delta) |
| if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Wait for DAD to resolve. |
| select { |
| case <-time.After(2 * delta): |
| // We should get a resolution event after 500ms |
| // (delta) since we wait for 500ms less than the |
| // expected resolution time above to make sure |
| // that the address did not yet resolve. Waiting |
| // for 1s (2x delta) without a resolution event |
| // means something is wrong. |
| t.Fatal("timed out waiting for DAD resolution") |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID1, addr1, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| } |
| if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, addrWithPrefix1); err != nil { |
| t.Fatal(err) |
| } |
| }) |
| } |
| } |
| |
| // raBufWithOptsAndDHCPv6 returns a valid NDP Router Advertisement with options |
| // and DHCPv6 configurations specified. |
| func raBufWithOptsAndDHCPv6(ip tcpip.Address, rl uint16, managedAddress, otherConfigurations bool, optSer header.NDPOptionsSerializer) *stack.PacketBuffer { |
| icmpSize := header.ICMPv6HeaderSize + header.NDPRAMinimumSize + int(optSer.Length()) |
| hdr := buffer.NewPrependable(header.IPv6MinimumSize + icmpSize) |
| pkt := header.ICMPv6(hdr.Prepend(icmpSize)) |
| pkt.SetType(header.ICMPv6RouterAdvert) |
| pkt.SetCode(0) |
| raPayload := pkt.MessageBody() |
| ra := header.NDPRouterAdvert(raPayload) |
| // Populate the Router Lifetime. |
| binary.BigEndian.PutUint16(raPayload[2:], rl) |
| // Populate the Managed Address flag field. |
| if managedAddress { |
| // The Managed Addresses flag field is the 7th bit of byte #1 (0-indexing) |
| // of the RA payload. |
| raPayload[1] |= (1 << 7) |
| } |
| // Populate the Other Configurations flag field. |
| if otherConfigurations { |
| // The Other Configurations flag field is the 6th bit of byte #1 |
| // (0-indexing) of the RA payload. |
| raPayload[1] |= (1 << 6) |
| } |
| opts := ra.Options() |
| opts.Serialize(optSer) |
| pkt.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{ |
| Header: pkt, |
| Src: ip, |
| Dst: header.IPv6AllNodesMulticastAddress, |
| })) |
| payloadLength := hdr.UsedLength() |
| iph := header.IPv6(hdr.Prepend(header.IPv6MinimumSize)) |
| iph.Encode(&header.IPv6Fields{ |
| PayloadLength: uint16(payloadLength), |
| TransportProtocol: icmp.ProtocolNumber6, |
| HopLimit: header.NDPHopLimit, |
| SrcAddr: ip, |
| DstAddr: header.IPv6AllNodesMulticastAddress, |
| }) |
| |
| return stack.NewPacketBuffer(stack.PacketBufferOptions{ |
| Data: hdr.View().ToVectorisedView(), |
| }) |
| } |
| |
| // raBufWithOpts returns a valid NDP Router Advertisement with options. |
| // |
| // Note, raBufWithOpts does not populate any of the RA fields other than the |
| // Router Lifetime. |
| func raBufWithOpts(ip tcpip.Address, rl uint16, optSer header.NDPOptionsSerializer) *stack.PacketBuffer { |
| return raBufWithOptsAndDHCPv6(ip, rl, false, false, optSer) |
| } |
| |
| // raBufWithDHCPv6 returns a valid NDP Router Advertisement with DHCPv6 related |
| // fields set. |
| // |
| // Note, raBufWithDHCPv6 does not populate any of the RA fields other than the |
| // DHCPv6 related ones. |
| func raBufWithDHCPv6(ip tcpip.Address, managedAddresses, otherConfiguratiosns bool) *stack.PacketBuffer { |
| return raBufWithOptsAndDHCPv6(ip, 0, managedAddresses, otherConfiguratiosns, header.NDPOptionsSerializer{}) |
| } |
| |
| // raBuf returns a valid NDP Router Advertisement. |
| // |
| // Note, raBuf does not populate any of the RA fields other than the |
| // Router Lifetime. |
| func raBuf(ip tcpip.Address, rl uint16) *stack.PacketBuffer { |
| return raBufWithOpts(ip, rl, header.NDPOptionsSerializer{}) |
| } |
| |
| // raBufWithPI returns a valid NDP Router Advertisement with a single Prefix |
| // Information option. |
| // |
| // Note, raBufWithPI does not populate any of the RA fields other than the |
| // Router Lifetime. |
| func raBufWithPI(ip tcpip.Address, rl uint16, prefix tcpip.AddressWithPrefix, onLink, auto bool, vl, pl uint32) *stack.PacketBuffer { |
| flags := uint8(0) |
| if onLink { |
| // The OnLink flag is the 7th bit in the flags byte. |
| flags |= 1 << 7 |
| } |
| if auto { |
| // The Address Auto-Configuration flag is the 6th bit in the |
| // flags byte. |
| flags |= 1 << 6 |
| } |
| |
| // A valid header.NDPPrefixInformation must be 30 bytes. |
| buf := [30]byte{} |
| // The first byte in a header.NDPPrefixInformation is the Prefix Length |
| // field. |
| buf[0] = uint8(prefix.PrefixLen) |
| // The 2nd byte within a header.NDPPrefixInformation is the Flags field. |
| buf[1] = flags |
| // The Valid Lifetime field starts after the 2nd byte within a |
| // header.NDPPrefixInformation. |
| binary.BigEndian.PutUint32(buf[2:], vl) |
| // The Preferred Lifetime field starts after the 6th byte within a |
| // header.NDPPrefixInformation. |
| binary.BigEndian.PutUint32(buf[6:], pl) |
| // The Prefix Address field starts after the 14th byte within a |
| // header.NDPPrefixInformation. |
| copy(buf[14:], prefix.Address) |
| return raBufWithOpts(ip, rl, header.NDPOptionsSerializer{ |
| header.NDPPrefixInformation(buf[:]), |
| }) |
| } |
| |
| // TestNoRouterDiscovery tests that router discovery will not be performed if |
| // configured not to. |
| func TestNoRouterDiscovery(t *testing.T) { |
| // Being configured to discover routers means handle and |
| // discover are set to true and forwarding is set to false. |
| // This tests all possible combinations of the configurations, |
| // except for the configuration where handle = true, discover = |
| // true and forwarding = false (the required configuration to do |
| // router discovery) - that will done in other tests. |
| for i := 0; i < 7; i++ { |
| handle := i&1 != 0 |
| discover := i&2 != 0 |
| forwarding := i&4 == 0 |
| |
| t.Run(fmt.Sprintf("HandleRAs(%t), DiscoverDefaultRouters(%t), Forwarding(%t)", handle, discover, forwarding), func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| routerC: make(chan ndpRouterEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: handle, |
| DiscoverDefaultRouters: discover, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| s.SetForwarding(ipv6.ProtocolNumber, forwarding) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Rx an RA with non-zero lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 1000)) |
| select { |
| case <-ndpDisp.routerC: |
| t.Fatal("unexpectedly discovered a router when configured not to") |
| default: |
| } |
| }) |
| } |
| } |
| |
| // Check e to make sure that the event is for addr on nic with ID 1, and the |
| // discovered flag set to discovered. |
| func checkRouterEvent(e ndpRouterEvent, addr tcpip.Address, discovered bool) string { |
| return cmp.Diff(ndpRouterEvent{nicID: 1, addr: addr, discovered: discovered}, e, cmp.AllowUnexported(e)) |
| } |
| |
| // TestRouterDiscoveryDispatcherNoRemember tests that the stack does not |
| // remember a discovered router when the dispatcher asks it not to. |
| func TestRouterDiscoveryDispatcherNoRemember(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| routerC: make(chan ndpRouterEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| DiscoverDefaultRouters: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Receive an RA for a router we should not remember. |
| const lifetimeSeconds = 1 |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, lifetimeSeconds)) |
| select { |
| case e := <-ndpDisp.routerC: |
| if diff := checkRouterEvent(e, llAddr2, true); diff != "" { |
| t.Errorf("router event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected router discovery event") |
| } |
| |
| // Wait for the invalidation time plus some buffer to make sure we do |
| // not actually receive any invalidation events as we should not have |
| // remembered the router in the first place. |
| select { |
| case <-ndpDisp.routerC: |
| t.Fatal("should not have received any router events") |
| case <-time.After(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout): |
| } |
| } |
| |
| func TestRouterDiscovery(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| routerC: make(chan ndpRouterEvent, 1), |
| rememberRouter: true, |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| DiscoverDefaultRouters: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| expectRouterEvent := func(addr tcpip.Address, discovered bool) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.routerC: |
| if diff := checkRouterEvent(e, addr, discovered); diff != "" { |
| t.Errorf("router event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected router discovery event") |
| } |
| } |
| |
| expectAsyncRouterInvalidationEvent := func(addr tcpip.Address, timeout time.Duration) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.routerC: |
| if diff := checkRouterEvent(e, addr, false); diff != "" { |
| t.Errorf("router event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(timeout): |
| t.Fatal("timed out waiting for router discovery event") |
| } |
| } |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Rx an RA from lladdr2 with zero lifetime. It should not be |
| // remembered. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 0)) |
| select { |
| case <-ndpDisp.routerC: |
| t.Fatal("unexpectedly discovered a router with 0 lifetime") |
| default: |
| } |
| |
| // Rx an RA from lladdr2 with a huge lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 1000)) |
| expectRouterEvent(llAddr2, true) |
| |
| // Rx an RA from another router (lladdr3) with non-zero lifetime. |
| const l3LifetimeSeconds = 6 |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr3, l3LifetimeSeconds)) |
| expectRouterEvent(llAddr3, true) |
| |
| // Rx an RA from lladdr2 with lesser lifetime. |
| const l2LifetimeSeconds = 2 |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, l2LifetimeSeconds)) |
| select { |
| case <-ndpDisp.routerC: |
| t.Fatal("Should not receive a router event when updating lifetimes for known routers") |
| default: |
| } |
| |
| // Wait for lladdr2's router invalidation job to execute. The lifetime |
| // of the router should have been updated to the most recent (smaller) |
| // lifetime. |
| // |
| // Wait for the normal lifetime plus an extra bit for the |
| // router to get invalidated. If we don't get an invalidation |
| // event after this time, then something is wrong. |
| expectAsyncRouterInvalidationEvent(llAddr2, l2LifetimeSeconds*time.Second+defaultAsyncPositiveEventTimeout) |
| |
| // Rx an RA from lladdr2 with huge lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 1000)) |
| expectRouterEvent(llAddr2, true) |
| |
| // Rx an RA from lladdr2 with zero lifetime. It should be invalidated. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr2, 0)) |
| expectRouterEvent(llAddr2, false) |
| |
| // Wait for lladdr3's router invalidation job to execute. The lifetime |
| // of the router should have been updated to the most recent (smaller) |
| // lifetime. |
| // |
| // Wait for the normal lifetime plus an extra bit for the |
| // router to get invalidated. If we don't get an invalidation |
| // event after this time, then something is wrong. |
| expectAsyncRouterInvalidationEvent(llAddr3, l3LifetimeSeconds*time.Second+defaultAsyncPositiveEventTimeout) |
| } |
| |
| // TestRouterDiscoveryMaxRouters tests that only |
| // ipv6.MaxDiscoveredDefaultRouters discovered routers are remembered. |
| func TestRouterDiscoveryMaxRouters(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| routerC: make(chan ndpRouterEvent, 1), |
| rememberRouter: true, |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| DiscoverDefaultRouters: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Receive an RA from 2 more than the max number of discovered routers. |
| for i := 1; i <= ipv6.MaxDiscoveredDefaultRouters+2; i++ { |
| linkAddr := []byte{2, 2, 3, 4, 5, 0} |
| linkAddr[5] = byte(i) |
| llAddr := header.LinkLocalAddr(tcpip.LinkAddress(linkAddr)) |
| |
| e.InjectInbound(header.IPv6ProtocolNumber, raBuf(llAddr, 5)) |
| |
| if i <= ipv6.MaxDiscoveredDefaultRouters { |
| select { |
| case e := <-ndpDisp.routerC: |
| if diff := checkRouterEvent(e, llAddr, true); diff != "" { |
| t.Errorf("router event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected router discovery event") |
| } |
| |
| } else { |
| select { |
| case <-ndpDisp.routerC: |
| t.Fatal("should not have discovered a new router after we already discovered the max number of routers") |
| default: |
| } |
| } |
| } |
| } |
| |
| // TestNoPrefixDiscovery tests that prefix discovery will not be performed if |
| // configured not to. |
| func TestNoPrefixDiscovery(t *testing.T) { |
| prefix := tcpip.AddressWithPrefix{ |
| Address: tcpip.Address("\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00"), |
| PrefixLen: 64, |
| } |
| |
| // Being configured to discover prefixes means handle and |
| // discover are set to true and forwarding is set to false. |
| // This tests all possible combinations of the configurations, |
| // except for the configuration where handle = true, discover = |
| // true and forwarding = false (the required configuration to do |
| // prefix discovery) - that will done in other tests. |
| for i := 0; i < 7; i++ { |
| handle := i&1 != 0 |
| discover := i&2 != 0 |
| forwarding := i&4 == 0 |
| |
| t.Run(fmt.Sprintf("HandleRAs(%t), DiscoverOnLinkPrefixes(%t), Forwarding(%t)", handle, discover, forwarding), func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| prefixC: make(chan ndpPrefixEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: handle, |
| DiscoverOnLinkPrefixes: discover, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| s.SetForwarding(ipv6.ProtocolNumber, forwarding) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Rx an RA with prefix with non-zero lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, 10, 0)) |
| |
| select { |
| case <-ndpDisp.prefixC: |
| t.Fatal("unexpectedly discovered a prefix when configured not to") |
| default: |
| } |
| }) |
| } |
| } |
| |
| // Check e to make sure that the event is for prefix on nic with ID 1, and the |
| // discovered flag set to discovered. |
| func checkPrefixEvent(e ndpPrefixEvent, prefix tcpip.Subnet, discovered bool) string { |
| return cmp.Diff(ndpPrefixEvent{nicID: 1, prefix: prefix, discovered: discovered}, e, cmp.AllowUnexported(e)) |
| } |
| |
| // TestPrefixDiscoveryDispatcherNoRemember tests that the stack does not |
| // remember a discovered on-link prefix when the dispatcher asks it not to. |
| func TestPrefixDiscoveryDispatcherNoRemember(t *testing.T) { |
| prefix, subnet, _ := prefixSubnetAddr(0, "") |
| |
| ndpDisp := ndpDispatcher{ |
| prefixC: make(chan ndpPrefixEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| DiscoverDefaultRouters: false, |
| DiscoverOnLinkPrefixes: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Receive an RA with prefix that we should not remember. |
| const lifetimeSeconds = 1 |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, lifetimeSeconds, 0)) |
| select { |
| case e := <-ndpDisp.prefixC: |
| if diff := checkPrefixEvent(e, subnet, true); diff != "" { |
| t.Errorf("prefix event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected prefix discovery event") |
| } |
| |
| // Wait for the invalidation time plus some buffer to make sure we do |
| // not actually receive any invalidation events as we should not have |
| // remembered the prefix in the first place. |
| select { |
| case <-ndpDisp.prefixC: |
| t.Fatal("should not have received any prefix events") |
| case <-time.After(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout): |
| } |
| } |
| |
| func TestPrefixDiscovery(t *testing.T) { |
| prefix1, subnet1, _ := prefixSubnetAddr(0, "") |
| prefix2, subnet2, _ := prefixSubnetAddr(1, "") |
| prefix3, subnet3, _ := prefixSubnetAddr(2, "") |
| |
| ndpDisp := ndpDispatcher{ |
| prefixC: make(chan ndpPrefixEvent, 1), |
| rememberPrefix: true, |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| DiscoverOnLinkPrefixes: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| expectPrefixEvent := func(prefix tcpip.Subnet, discovered bool) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.prefixC: |
| if diff := checkPrefixEvent(e, prefix, discovered); diff != "" { |
| t.Errorf("prefix event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected prefix discovery event") |
| } |
| } |
| |
| // Receive an RA with prefix1 in an NDP Prefix Information option (PI) |
| // with zero valid lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 0, 0)) |
| select { |
| case <-ndpDisp.prefixC: |
| t.Fatal("unexpectedly discovered a prefix with 0 lifetime") |
| default: |
| } |
| |
| // Receive an RA with prefix1 in an NDP Prefix Information option (PI) |
| // with non-zero lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 100, 0)) |
| expectPrefixEvent(subnet1, true) |
| |
| // Receive an RA with prefix2 in a PI. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, false, 100, 0)) |
| expectPrefixEvent(subnet2, true) |
| |
| // Receive an RA with prefix3 in a PI. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix3, true, false, 100, 0)) |
| expectPrefixEvent(subnet3, true) |
| |
| // Receive an RA with prefix1 in a PI with lifetime = 0. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, false, 0, 0)) |
| expectPrefixEvent(subnet1, false) |
| |
| // Receive an RA with prefix2 in a PI with lesser lifetime. |
| lifetime := uint32(2) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, false, lifetime, 0)) |
| select { |
| case <-ndpDisp.prefixC: |
| t.Fatal("unexpectedly received prefix event when updating lifetime") |
| default: |
| } |
| |
| // Wait for prefix2's most recent invalidation job plus some buffer to |
| // expire. |
| select { |
| case e := <-ndpDisp.prefixC: |
| if diff := checkPrefixEvent(e, subnet2, false); diff != "" { |
| t.Errorf("prefix event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(time.Duration(lifetime)*time.Second + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for prefix discovery event") |
| } |
| |
| // Receive RA to invalidate prefix3. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix3, true, false, 0, 0)) |
| expectPrefixEvent(subnet3, false) |
| } |
| |
| func TestPrefixDiscoveryWithInfiniteLifetime(t *testing.T) { |
| // Update the infinite lifetime value to a smaller value so we can test |
| // that when we receive a PI with such a lifetime value, we do not |
| // invalidate the prefix. |
| const testInfiniteLifetimeSeconds = 2 |
| const testInfiniteLifetime = testInfiniteLifetimeSeconds * time.Second |
| saved := header.NDPInfiniteLifetime |
| header.NDPInfiniteLifetime = testInfiniteLifetime |
| defer func() { |
| header.NDPInfiniteLifetime = saved |
| }() |
| |
| prefix := tcpip.AddressWithPrefix{ |
| Address: tcpip.Address("\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x00"), |
| PrefixLen: 64, |
| } |
| subnet := prefix.Subnet() |
| |
| ndpDisp := ndpDispatcher{ |
| prefixC: make(chan ndpPrefixEvent, 1), |
| rememberPrefix: true, |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| DiscoverOnLinkPrefixes: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| expectPrefixEvent := func(prefix tcpip.Subnet, discovered bool) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.prefixC: |
| if diff := checkPrefixEvent(e, prefix, discovered); diff != "" { |
| t.Errorf("prefix event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected prefix discovery event") |
| } |
| } |
| |
| // Receive an RA with prefix in an NDP Prefix Information option (PI) |
| // with infinite valid lifetime which should not get invalidated. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds, 0)) |
| expectPrefixEvent(subnet, true) |
| select { |
| case <-ndpDisp.prefixC: |
| t.Fatal("unexpectedly invalidated a prefix with infinite lifetime") |
| case <-time.After(testInfiniteLifetime + defaultAsyncNegativeEventTimeout): |
| } |
| |
| // Receive an RA with finite lifetime. |
| // The prefix should get invalidated after 1s. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds-1, 0)) |
| select { |
| case e := <-ndpDisp.prefixC: |
| if diff := checkPrefixEvent(e, subnet, false); diff != "" { |
| t.Errorf("prefix event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(testInfiniteLifetime): |
| t.Fatal("timed out waiting for prefix discovery event") |
| } |
| |
| // Receive an RA with finite lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds-1, 0)) |
| expectPrefixEvent(subnet, true) |
| |
| // Receive an RA with prefix with an infinite lifetime. |
| // The prefix should not be invalidated. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds, 0)) |
| select { |
| case <-ndpDisp.prefixC: |
| t.Fatal("unexpectedly invalidated a prefix with infinite lifetime") |
| case <-time.After(testInfiniteLifetime + defaultAsyncNegativeEventTimeout): |
| } |
| |
| // Receive an RA with a prefix with a lifetime value greater than the |
| // set infinite lifetime value. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, testInfiniteLifetimeSeconds+1, 0)) |
| select { |
| case <-ndpDisp.prefixC: |
| t.Fatal("unexpectedly invalidated a prefix with infinite lifetime") |
| case <-time.After((testInfiniteLifetimeSeconds+1)*time.Second + defaultAsyncNegativeEventTimeout): |
| } |
| |
| // Receive an RA with 0 lifetime. |
| // The prefix should get invalidated. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, 0, 0)) |
| expectPrefixEvent(subnet, false) |
| } |
| |
| // TestPrefixDiscoveryMaxRouters tests that only |
| // ipv6.MaxDiscoveredOnLinkPrefixes discovered on-link prefixes are remembered. |
| func TestPrefixDiscoveryMaxOnLinkPrefixes(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| prefixC: make(chan ndpPrefixEvent, ipv6.MaxDiscoveredOnLinkPrefixes+3), |
| rememberPrefix: true, |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| DiscoverDefaultRouters: false, |
| DiscoverOnLinkPrefixes: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| optSer := make(header.NDPOptionsSerializer, ipv6.MaxDiscoveredOnLinkPrefixes+2) |
| prefixes := [ipv6.MaxDiscoveredOnLinkPrefixes + 2]tcpip.Subnet{} |
| |
| // Receive an RA with 2 more than the max number of discovered on-link |
| // prefixes. |
| for i := 0; i < ipv6.MaxDiscoveredOnLinkPrefixes+2; i++ { |
| prefixAddr := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0} |
| prefixAddr[7] = byte(i) |
| prefix := tcpip.AddressWithPrefix{ |
| Address: tcpip.Address(prefixAddr[:]), |
| PrefixLen: 64, |
| } |
| prefixes[i] = prefix.Subnet() |
| buf := [30]byte{} |
| buf[0] = uint8(prefix.PrefixLen) |
| buf[1] = 128 |
| binary.BigEndian.PutUint32(buf[2:], 10) |
| copy(buf[14:], prefix.Address) |
| |
| optSer[i] = header.NDPPrefixInformation(buf[:]) |
| } |
| |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, optSer)) |
| for i := 0; i < ipv6.MaxDiscoveredOnLinkPrefixes+2; i++ { |
| if i < ipv6.MaxDiscoveredOnLinkPrefixes { |
| select { |
| case e := <-ndpDisp.prefixC: |
| if diff := checkPrefixEvent(e, prefixes[i], true); diff != "" { |
| t.Errorf("prefix event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected prefix discovery event") |
| } |
| } else { |
| select { |
| case <-ndpDisp.prefixC: |
| t.Fatal("should not have discovered a new prefix after we already discovered the max number of prefixes") |
| default: |
| } |
| } |
| } |
| } |
| |
| // Checks to see if list contains an IPv6 address, item. |
| func containsV6Addr(list []tcpip.ProtocolAddress, item tcpip.AddressWithPrefix) bool { |
| protocolAddress := tcpip.ProtocolAddress{ |
| Protocol: header.IPv6ProtocolNumber, |
| AddressWithPrefix: item, |
| } |
| |
| return containsAddr(list, protocolAddress) |
| } |
| |
| // TestNoAutoGenAddr tests that SLAAC is not performed when configured not to. |
| func TestNoAutoGenAddr(t *testing.T) { |
| prefix, _, _ := prefixSubnetAddr(0, "") |
| |
| // Being configured to auto-generate addresses means handle and |
| // autogen are set to true and forwarding is set to false. |
| // This tests all possible combinations of the configurations, |
| // except for the configuration where handle = true, autogen = |
| // true and forwarding = false (the required configuration to do |
| // SLAAC) - that will done in other tests. |
| for i := 0; i < 7; i++ { |
| handle := i&1 != 0 |
| autogen := i&2 != 0 |
| forwarding := i&4 == 0 |
| |
| t.Run(fmt.Sprintf("HandleRAs(%t), AutoGenAddr(%t), Forwarding(%t)", handle, autogen, forwarding), func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: handle, |
| AutoGenGlobalAddresses: autogen, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| s.SetForwarding(ipv6.ProtocolNumber, forwarding) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Rx an RA with prefix with non-zero lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, false, true, 10, 0)) |
| |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly auto-generated an address when configured not to") |
| default: |
| } |
| }) |
| } |
| } |
| |
| // Check e to make sure that the event is for addr on nic with ID 1, and the |
| // event type is set to eventType. |
| func checkAutoGenAddrEvent(e ndpAutoGenAddrEvent, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) string { |
| return cmp.Diff(ndpAutoGenAddrEvent{nicID: 1, addr: addr, eventType: eventType}, e, cmp.AllowUnexported(e)) |
| } |
| |
| // TestAutoGenAddr tests that an address is properly generated and invalidated |
| // when configured to do so. |
| func TestAutoGenAddr2(t *testing.T) { |
| const newMinVL = 2 |
| newMinVLDuration := newMinVL * time.Second |
| saved := ipv6.MinPrefixInformationValidLifetimeForUpdate |
| defer func() { |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = saved |
| }() |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = newMinVLDuration |
| |
| prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1) |
| prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1) |
| |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", 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 prefix1 in an NDP Prefix Information option (PI) |
| // with zero valid lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 0, 0)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly auto-generated an address with 0 lifetime") |
| default: |
| } |
| |
| // Receive an RA with prefix1 in an NDP Prefix Information option (PI) |
| // with non-zero lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) |
| expectAutoGenAddrEvent(addr1, newAddr) |
| if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) { |
| t.Fatalf("Should have %s in the list of addresses", addr1) |
| } |
| |
| // Receive an RA with prefix2 in an NDP Prefix Information option (PI) |
| // with preferred lifetime > valid lifetime |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 5, 6)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly auto-generated an address with preferred lifetime > valid lifetime") |
| default: |
| } |
| |
| // Receive an RA with prefix2 in a PI. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0)) |
| expectAutoGenAddrEvent(addr2, newAddr) |
| if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) { |
| t.Fatalf("Should have %s in the list of addresses", addr1) |
| } |
| if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr2) { |
| t.Fatalf("Should have %s in the list of addresses", addr2) |
| } |
| |
| // Refresh valid lifetime for addr of prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, 0)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly auto-generated an address when we already have an address for a prefix") |
| default: |
| } |
| |
| // Wait for addr of prefix1 to be invalidated. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(newMinVLDuration + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| if containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr1) { |
| t.Fatalf("Should not have %s in the list of addresses", addr1) |
| } |
| if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr2) { |
| t.Fatalf("Should have %s in the list of addresses", addr2) |
| } |
| } |
| |
| func addressCheck(addrs []tcpip.ProtocolAddress, containList, notContainList []tcpip.AddressWithPrefix) string { |
| ret := "" |
| for _, c := range containList { |
| if !containsV6Addr(addrs, c) { |
| ret += fmt.Sprintf("should have %s in the list of addresses\n", c) |
| } |
| } |
| for _, c := range notContainList { |
| if containsV6Addr(addrs, c) { |
| ret += fmt.Sprintf("should not have %s in the list of addresses\n", c) |
| } |
| } |
| return ret |
| } |
| |
| // TestAutoGenTempAddr tests that temporary SLAAC addresses are generated when |
| // configured to do so as part of IPv6 Privacy Extensions. |
| func TestAutoGenTempAddr(t *testing.T) { |
| const ( |
| nicID = 1 |
| newMinVL = 5 |
| newMinVLDuration = newMinVL * time.Second |
| ) |
| |
| savedMinPrefixInformationValidLifetimeForUpdate := ipv6.MinPrefixInformationValidLifetimeForUpdate |
| savedMaxDesync := ipv6.MaxDesyncFactor |
| defer func() { |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = savedMinPrefixInformationValidLifetimeForUpdate |
| ipv6.MaxDesyncFactor = savedMaxDesync |
| }() |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = newMinVLDuration |
| ipv6.MaxDesyncFactor = time.Nanosecond |
| |
| prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1) |
| prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1) |
| |
| tests := []struct { |
| name string |
| dupAddrTransmits uint8 |
| retransmitTimer time.Duration |
| }{ |
| { |
| name: "DAD disabled", |
| }, |
| { |
| name: "DAD enabled", |
| dupAddrTransmits: 1, |
| retransmitTimer: time.Second, |
| }, |
| } |
| |
| // This Run will not return until the parallel tests finish. |
| // |
| // We need this because we need to do some teardown work after the |
| // parallel tests complete. |
| // |
| // See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for |
| // more details. |
| t.Run("group", func(t *testing.T) { |
| for i, test := range tests { |
| i := i |
| test := test |
| |
| t.Run(test.name, func(t *testing.T) { |
| t.Parallel() |
| |
| seed := []byte{uint8(i)} |
| var tempIIDHistory [header.IIDSize]byte |
| header.InitialTempIID(tempIIDHistory[:], seed, nicID) |
| newTempAddr := func(stableAddr tcpip.Address) tcpip.AddressWithPrefix { |
| return header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], stableAddr) |
| } |
| |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent, 2), |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| DADConfigs: stack.DADConfigurations{ |
| DupAddrDetectTransmits: test.dupAddrTransmits, |
| RetransmitTimer: test.retransmitTimer, |
| }, |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| AutoGenTempGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| TempIIDSeed: seed, |
| })}, |
| }) |
| |
| 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") |
| } |
| } |
| |
| expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| } |
| |
| expectDADEventAsync := func(addr tcpip.Address) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(time.Duration(test.dupAddrTransmits)*test.retransmitTimer + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for DAD event") |
| } |
| } |
| |
| // Receive an RA with prefix1 in an NDP Prefix Information option (PI) |
| // with zero valid lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 0, 0)) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpectedly auto-generated an address with 0 lifetime; event = %+v", e) |
| default: |
| } |
| |
| // Receive an RA with prefix1 in an NDP Prefix Information option (PI) |
| // with non-zero valid lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) |
| expectAutoGenAddrEvent(addr1, newAddr) |
| expectDADEventAsync(addr1.Address) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpectedly got an auto gen addr event = %+v", e) |
| default: |
| } |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Receive an RA with prefix1 in an NDP Prefix Information option (PI) |
| // with non-zero valid & preferred lifetimes. |
| tempAddr1 := newTempAddr(addr1.Address) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100)) |
| expectAutoGenAddrEvent(tempAddr1, newAddr) |
| expectDADEventAsync(tempAddr1.Address) |
| if mismatch := addressCheck(s.NICInfo()[1].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Receive an RA with prefix2 in an NDP Prefix Information option (PI) |
| // with preferred lifetime > valid lifetime |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 5, 6)) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpectedly auto-generated an address with preferred lifetime > valid lifetime; event = %+v", e) |
| default: |
| } |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Receive an RA with prefix2 in a PI w/ non-zero valid and preferred |
| // lifetimes. |
| tempAddr2 := newTempAddr(addr2.Address) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100)) |
| expectAutoGenAddrEvent(addr2, newAddr) |
| expectDADEventAsync(addr2.Address) |
| expectAutoGenAddrEventAsync(tempAddr2, newAddr) |
| expectDADEventAsync(tempAddr2.Address) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Deprecate prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) |
| expectAutoGenAddrEvent(addr1, deprecatedAddr) |
| expectAutoGenAddrEvent(tempAddr1, deprecatedAddr) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Refresh lifetimes for prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100)) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Reduce valid lifetime and deprecate addresses of prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, 0)) |
| expectAutoGenAddrEvent(addr1, deprecatedAddr) |
| expectAutoGenAddrEvent(tempAddr1, deprecatedAddr) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr1, tempAddr1, addr2, tempAddr2}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Wait for addrs of prefix1 to be invalidated. They should be |
| // invalidated at the same time. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| var nextAddr tcpip.AddressWithPrefix |
| if e.addr == addr1 { |
| if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| nextAddr = tempAddr1 |
| } else { |
| if diff := checkAutoGenAddrEvent(e, tempAddr1, invalidatedAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| nextAddr = addr1 |
| } |
| |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, nextAddr, invalidatedAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| case <-time.After(newMinVLDuration + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr2, tempAddr2}, []tcpip.AddressWithPrefix{addr1, tempAddr1}); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Receive an RA with prefix2 in a PI w/ 0 lifetimes. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 0, 0)) |
| expectAutoGenAddrEvent(addr2, deprecatedAddr) |
| expectAutoGenAddrEvent(tempAddr2, deprecatedAddr) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Errorf("got unexpected auto gen addr event = %+v", e) |
| default: |
| } |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr2, tempAddr2}, []tcpip.AddressWithPrefix{addr1, tempAddr1}); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| }) |
| } |
| }) |
| } |
| |
| // TestNoAutoGenTempAddrForLinkLocal test that temporary SLAAC addresses are not |
| // generated for auto generated link-local addresses. |
| func TestNoAutoGenTempAddrForLinkLocal(t *testing.T) { |
| const nicID = 1 |
| |
| savedMaxDesyncFactor := ipv6.MaxDesyncFactor |
| defer func() { |
| ipv6.MaxDesyncFactor = savedMaxDesyncFactor |
| }() |
| ipv6.MaxDesyncFactor = time.Nanosecond |
| |
| tests := []struct { |
| name string |
| dupAddrTransmits uint8 |
| retransmitTimer time.Duration |
| }{ |
| { |
| name: "DAD disabled", |
| }, |
| { |
| name: "DAD enabled", |
| dupAddrTransmits: 1, |
| retransmitTimer: time.Second, |
| }, |
| } |
| |
| // This Run will not return until the parallel tests finish. |
| // |
| // We need this because we need to do some teardown work after the |
| // parallel tests complete. |
| // |
| // See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for |
| // more details. |
| t.Run("group", func(t *testing.T) { |
| for _, test := range tests { |
| test := test |
| |
| t.Run(test.name, func(t *testing.T) { |
| t.Parallel() |
| |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent, 1), |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| AutoGenTempGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| AutoGenLinkLocal: true, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| // The stable link-local address should auto-generate and resolve DAD. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, tcpip.AddressWithPrefix{Address: llAddr1, PrefixLen: header.IIDOffsetInIPv6Address * 8}, newAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected addr auto gen event") |
| } |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, llAddr1, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(time.Duration(test.dupAddrTransmits)*test.retransmitTimer + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for DAD event") |
| } |
| |
| // No new addresses should be generated. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Errorf("got unxpected auto gen addr event = %+v", e) |
| case <-time.After(defaultAsyncNegativeEventTimeout): |
| } |
| }) |
| } |
| }) |
| } |
| |
| // TestNoAutoGenTempAddrWithoutStableAddr tests that a temporary SLAAC address |
| // will not be generated until after DAD completes, even if a new Router |
| // Advertisement is received to refresh lifetimes. |
| func TestNoAutoGenTempAddrWithoutStableAddr(t *testing.T) { |
| const ( |
| nicID = 1 |
| dadTransmits = 1 |
| retransmitTimer = 2 * time.Second |
| ) |
| |
| savedMaxDesyncFactor := ipv6.MaxDesyncFactor |
| defer func() { |
| ipv6.MaxDesyncFactor = savedMaxDesyncFactor |
| }() |
| ipv6.MaxDesyncFactor = 0 |
| |
| prefix, _, addr := prefixSubnetAddr(0, linkAddr1) |
| var tempIIDHistory [header.IIDSize]byte |
| header.InitialTempIID(tempIIDHistory[:], nil, nicID) |
| tempAddr := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) |
| |
| ndpDisp := ndpDispatcher{ |
| dadC: make(chan ndpDADEvent, 1), |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| DADConfigs: stack.DADConfigurations{ |
| DupAddrDetectTransmits: dadTransmits, |
| RetransmitTimer: retransmitTimer, |
| }, |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| AutoGenTempGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| // Receive an RA to trigger SLAAC for prefix. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected addr auto gen event") |
| } |
| |
| // DAD on the stable address for prefix has not yet completed. Receiving a new |
| // RA that would refresh lifetimes should not generate a temporary SLAAC |
| // address for the prefix. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpected auto gen addr event = %+v", e) |
| default: |
| } |
| |
| // Wait for DAD to complete for the stable address then expect the temporary |
| // address to be generated. |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(dadTransmits*retransmitTimer + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for DAD event") |
| } |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, tempAddr, newAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| } |
| |
| // TestAutoGenTempAddrRegen tests that temporary SLAAC addresses are |
| // regenerated. |
| func TestAutoGenTempAddrRegen(t *testing.T) { |
| const ( |
| nicID = 1 |
| regenAfter = 2 * time.Second |
| newMinVL = 10 |
| newMinVLDuration = newMinVL * time.Second |
| ) |
| |
| savedMaxDesyncFactor := ipv6.MaxDesyncFactor |
| savedMinMaxTempAddrPreferredLifetime := ipv6.MinMaxTempAddrPreferredLifetime |
| savedMinMaxTempAddrValidLifetime := ipv6.MinMaxTempAddrValidLifetime |
| defer func() { |
| ipv6.MaxDesyncFactor = savedMaxDesyncFactor |
| ipv6.MinMaxTempAddrPreferredLifetime = savedMinMaxTempAddrPreferredLifetime |
| ipv6.MinMaxTempAddrValidLifetime = savedMinMaxTempAddrValidLifetime |
| }() |
| ipv6.MaxDesyncFactor = 0 |
| ipv6.MinMaxTempAddrPreferredLifetime = newMinVLDuration |
| ipv6.MinMaxTempAddrValidLifetime = newMinVLDuration |
| |
| prefix, _, addr := prefixSubnetAddr(0, linkAddr1) |
| var tempIIDHistory [header.IIDSize]byte |
| header.InitialTempIID(tempIIDHistory[:], nil, nicID) |
| tempAddr1 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) |
| tempAddr2 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) |
| tempAddr3 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) |
| |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| ndpConfigs := ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| AutoGenTempGlobalAddresses: true, |
| RegenAdvanceDuration: newMinVLDuration - regenAfter, |
| } |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: 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") |
| } |
| } |
| |
| expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(timeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| } |
| |
| // Receive an RA with prefix1 in an NDP Prefix Information option (PI) |
| // with non-zero valid & preferred lifetimes. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) |
| expectAutoGenAddrEvent(addr, newAddr) |
| expectAutoGenAddrEvent(tempAddr1, newAddr) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddr1}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Wait for regeneration |
| expectAutoGenAddrEventAsync(tempAddr2, newAddr, regenAfter+defaultAsyncPositiveEventTimeout) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddr1, tempAddr2}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Wait for regeneration |
| expectAutoGenAddrEventAsync(tempAddr3, newAddr, regenAfter+defaultAsyncPositiveEventTimeout) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddr1, tempAddr2, tempAddr3}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Stop generating temporary addresses |
| ndpConfigs.AutoGenTempGlobalAddresses = false |
| if ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber); err != nil { |
| t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err) |
| } else { |
| ndpEP := ipv6Ep.(ipv6.NDPEndpoint) |
| ndpEP.SetNDPConfigurations(ndpConfigs) |
| } |
| |
| // Wait for all the temporary addresses to get invalidated. |
| tempAddrs := []tcpip.AddressWithPrefix{tempAddr1, tempAddr2, tempAddr3} |
| invalidateAfter := newMinVLDuration - 2*regenAfter |
| for _, addr := range tempAddrs { |
| // Wait for a deprecation then invalidation event, or just an invalidation |
| // event. We need to cover both cases but cannot deterministically hit both |
| // cases because the deprecation and invalidation jobs could execute in any |
| // order. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, deprecatedAddr); diff == "" { |
| // If we get a deprecation event first, we should get an invalidation |
| // event almost immediately after. |
| 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(defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| } else if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff == "" { |
| // If we get an invalidation event first, we shouldn't get a deprecation |
| // event after. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpectedly got an auto-generated event = %+v", e) |
| case <-time.After(defaultAsyncNegativeEventTimeout): |
| } |
| } else { |
| t.Fatalf("got unexpected auto-generated event = %+v", e) |
| } |
| case <-time.After(invalidateAfter + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| |
| invalidateAfter = regenAfter |
| } |
| if mismatch := addressCheck(s.NICInfo()[1].ProtocolAddresses, []tcpip.AddressWithPrefix{addr}, tempAddrs); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| } |
| |
| // TestAutoGenTempAddrRegenJobUpdates tests that a temporary address's |
| // regeneration job gets updated when refreshing the address's lifetimes. |
| func TestAutoGenTempAddrRegenJobUpdates(t *testing.T) { |
| const ( |
| nicID = 1 |
| regenAfter = 2 * time.Second |
| newMinVL = 10 |
| newMinVLDuration = newMinVL * time.Second |
| ) |
| |
| savedMaxDesyncFactor := ipv6.MaxDesyncFactor |
| savedMinMaxTempAddrPreferredLifetime := ipv6.MinMaxTempAddrPreferredLifetime |
| savedMinMaxTempAddrValidLifetime := ipv6.MinMaxTempAddrValidLifetime |
| defer func() { |
| ipv6.MaxDesyncFactor = savedMaxDesyncFactor |
| ipv6.MinMaxTempAddrPreferredLifetime = savedMinMaxTempAddrPreferredLifetime |
| ipv6.MinMaxTempAddrValidLifetime = savedMinMaxTempAddrValidLifetime |
| }() |
| ipv6.MaxDesyncFactor = 0 |
| ipv6.MinMaxTempAddrPreferredLifetime = newMinVLDuration |
| ipv6.MinMaxTempAddrValidLifetime = newMinVLDuration |
| |
| prefix, _, addr := prefixSubnetAddr(0, linkAddr1) |
| var tempIIDHistory [header.IIDSize]byte |
| header.InitialTempIID(tempIIDHistory[:], nil, nicID) |
| tempAddr1 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) |
| tempAddr2 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) |
| tempAddr3 := header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) |
| |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| ndpConfigs := ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| AutoGenTempGlobalAddresses: true, |
| RegenAdvanceDuration: newMinVLDuration - regenAfter, |
| } |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: 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") |
| } |
| } |
| |
| expectAutoGenAddrEventAsync := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(timeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| } |
| |
| // Receive an RA with prefix1 in an NDP Prefix Information option (PI) |
| // with non-zero valid & preferred lifetimes. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) |
| expectAutoGenAddrEvent(addr, newAddr) |
| expectAutoGenAddrEvent(tempAddr1, newAddr) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddr1}, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Deprecate the prefix. |
| // |
| // A new temporary address should be generated after the regeneration |
| // time has passed since the prefix is deprecated. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 0)) |
| expectAutoGenAddrEvent(addr, deprecatedAddr) |
| expectAutoGenAddrEvent(tempAddr1, deprecatedAddr) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpected auto gen addr event = %+v", e) |
| case <-time.After(regenAfter + defaultAsyncNegativeEventTimeout): |
| } |
| |
| // Prefer the prefix again. |
| // |
| // A new temporary address should immediately be generated since the |
| // regeneration time has already passed since the last address was generated |
| // - this regeneration does not depend on a job. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) |
| expectAutoGenAddrEvent(tempAddr2, newAddr) |
| |
| // Increase the maximum lifetimes for temporary addresses to large values |
| // then refresh the lifetimes of the prefix. |
| // |
| // A new address should not be generated after the regeneration time that was |
| // expected for the previous check. This is because the preferred lifetime for |
| // the temporary addresses has increased, so it will take more time to |
| // regenerate a new temporary address. Note, new addresses are only |
| // regenerated after the preferred lifetime - the regenerate advance duration |
| // as paased. |
| ndpConfigs.MaxTempAddrValidLifetime = 100 * time.Second |
| ndpConfigs.MaxTempAddrPreferredLifetime = 100 * time.Second |
| ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber) |
| if err != nil { |
| t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err) |
| } |
| ndpEP := ipv6Ep.(ipv6.NDPEndpoint) |
| ndpEP.SetNDPConfigurations(ndpConfigs) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpected auto gen addr event = %+v", e) |
| case <-time.After(regenAfter + defaultAsyncNegativeEventTimeout): |
| } |
| |
| // Set the maximum lifetimes for temporary addresses such that on the next |
| // RA, the regeneration job gets scheduled again. |
| // |
| // The maximum lifetime is the sum of the minimum lifetimes for temporary |
| // addresses + the time that has already passed since the last address was |
| // generated so that the regeneration job is needed to generate the next |
| // address. |
| newLifetimes := newMinVLDuration + regenAfter + defaultAsyncNegativeEventTimeout |
| ndpConfigs.MaxTempAddrValidLifetime = newLifetimes |
| ndpConfigs.MaxTempAddrPreferredLifetime = newLifetimes |
| ndpEP.SetNDPConfigurations(ndpConfigs) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) |
| expectAutoGenAddrEventAsync(tempAddr3, newAddr, regenAfter+defaultAsyncPositiveEventTimeout) |
| } |
| |
| // TestMixedSLAACAddrConflictRegen tests SLAAC address regeneration in response |
| // to a mix of DAD conflicts and NIC-local conflicts. |
| func TestMixedSLAACAddrConflictRegen(t *testing.T) { |
| const ( |
| nicID = 1 |
| nicName = "nic" |
| lifetimeSeconds = 9999 |
| // From stack.maxSLAACAddrLocalRegenAttempts |
| maxSLAACAddrLocalRegenAttempts = 10 |
| // We use 2 more addreses than the maximum local regeneration attempts |
| // because we want to also trigger regeneration in response to a DAD |
| // conflicts for this test. |
| maxAddrs = maxSLAACAddrLocalRegenAttempts + 2 |
| dupAddrTransmits = 1 |
| retransmitTimer = time.Second |
| ) |
| |
| var tempIIDHistoryWithModifiedEUI64 [header.IIDSize]byte |
| header.InitialTempIID(tempIIDHistoryWithModifiedEUI64[:], nil, nicID) |
| |
| var tempIIDHistoryWithOpaqueIID [header.IIDSize]byte |
| header.InitialTempIID(tempIIDHistoryWithOpaqueIID[:], nil, nicID) |
| |
| prefix, subnet, stableAddrWithModifiedEUI64 := prefixSubnetAddr(0, linkAddr1) |
| var stableAddrsWithOpaqueIID [maxAddrs]tcpip.AddressWithPrefix |
| var tempAddrsWithOpaqueIID [maxAddrs]tcpip.AddressWithPrefix |
| var tempAddrsWithModifiedEUI64 [maxAddrs]tcpip.AddressWithPrefix |
| addrBytes := []byte(subnet.ID()) |
| for i := 0; i < maxAddrs; i++ { |
| stableAddrsWithOpaqueIID[i] = tcpip.AddressWithPrefix{ |
| Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, uint8(i), nil)), |
| PrefixLen: header.IIDOffsetInIPv6Address * 8, |
| } |
| // When generating temporary addresses, the resolved stable address for the |
| // SLAAC prefix will be the first address stable address generated for the |
| // prefix as we will not simulate address conflicts for the stable addresses |
| // in tests involving temporary addresses. Address conflicts for stable |
| // addresses will be done in their own tests. |
| tempAddrsWithOpaqueIID[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistoryWithOpaqueIID[:], stableAddrsWithOpaqueIID[0].Address) |
| tempAddrsWithModifiedEUI64[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistoryWithModifiedEUI64[:], stableAddrWithModifiedEUI64.Address) |
| } |
| |
| tests := []struct { |
| name string |
| addrs []tcpip.AddressWithPrefix |
| tempAddrs bool |
| initialExpect tcpip.AddressWithPrefix |
| maxAddrs int |
| nicNameFromID func(tcpip.NICID, string) string |
| }{ |
| { |
| name: "Stable addresses with opaque IIDs", |
| addrs: stableAddrsWithOpaqueIID[:], |
| maxAddrs: 1, |
| nicNameFromID: func(tcpip.NICID, string) string { |
| return nicName |
| }, |
| }, |
| { |
| name: "Temporary addresses with opaque IIDs", |
| addrs: tempAddrsWithOpaqueIID[:], |
| tempAddrs: true, |
| initialExpect: stableAddrsWithOpaqueIID[0], |
| maxAddrs: 1 /* initial (stable) address */ + maxSLAACAddrLocalRegenAttempts, |
| nicNameFromID: func(tcpip.NICID, string) string { |
| return nicName |
| }, |
| }, |
| { |
| name: "Temporary addresses with modified EUI64", |
| addrs: tempAddrsWithModifiedEUI64[:], |
| tempAddrs: true, |
| maxAddrs: 1 /* initial (stable) address */ + maxSLAACAddrLocalRegenAttempts, |
| initialExpect: stableAddrWithModifiedEUI64, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| // We may receive a deprecated and invalidated event for each SLAAC |
| // address that is assigned. |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAddrs*2), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| clock := faketime.NewManualClock() |
| s := stack.New(stack.Options{ |
| Clock: clock, |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| AutoGenTempGlobalAddresses: test.tempAddrs, |
| AutoGenAddressConflictRetries: 1, |
| }, |
| NDPDisp: &ndpDisp, |
| OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{ |
| NICNameFromID: test.nicNameFromID, |
| }, |
| })}, |
| TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol}, |
| }) |
| |
| s.SetRouteTable([]tcpip.Route{{ |
| Destination: header.IPv6EmptySubnet, |
| Gateway: llAddr2, |
| NIC: nicID, |
| }}) |
| |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| manuallyAssignedAddresses := make(map[tcpip.Address]struct{}) |
| for j := 0; j < len(test.addrs)-1; j++ { |
| // The NIC will not attempt to generate an address in response to a |
| // NIC-local conflict after some maximum number of attempts. We skip |
| // creating a conflict for the address that would be generated as part |
| // of the last attempt so we can simulate a DAD conflict for this |
| // address and restart the NIC-local generation process. |
| if j == maxSLAACAddrLocalRegenAttempts-1 { |
| continue |
| } |
| |
| if err := s.AddAddress(nicID, ipv6.ProtocolNumber, test.addrs[j].Address); err != nil { |
| t.Fatalf("s.AddAddress(%d, %d, %s): %s", nicID, ipv6.ProtocolNumber, test.addrs[j].Address, err) |
| } |
| |
| manuallyAssignedAddresses[test.addrs[j].Address] = struct{}{} |
| } |
| |
| expectAutoGenAddrEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected addr auto gen event") |
| } |
| } |
| |
| expectAutoGenAddrAsyncEvent := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { |
| t.Helper() |
| |
| if diff := checkAutoGenAddrEvent(<-ndpDisp.autoGenAddrC, addr, eventType); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| expectDADEventAsync := func(addr tcpip.Address) { |
| t.Helper() |
| |
| clock.Advance(dupAddrTransmits * retransmitTimer) |
| if diff := checkDADEvent(<-ndpDisp.dadC, nicID, addr, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| } |
| |
| // Enable DAD. |
| ndpDisp.dadC = make(chan ndpDADEvent, 2) |
| if ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber); err != nil { |
| t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err) |
| } else { |
| ndpEP := ipv6Ep.(stack.DuplicateAddressDetector) |
| ndpEP.SetDADConfigurations(stack.DADConfigurations{ |
| DupAddrDetectTransmits: dupAddrTransmits, |
| RetransmitTimer: retransmitTimer, |
| }) |
| } |
| |
| // Do SLAAC for prefix. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds)) |
| if test.initialExpect != (tcpip.AddressWithPrefix{}) { |
| expectAutoGenAddrEvent(test.initialExpect, newAddr) |
| expectDADEventAsync(test.initialExpect.Address) |
| } |
| |
| // The last local generation attempt should succeed, but we introduce a |
| // DAD failure to restart the local generation process. |
| addr := test.addrs[maxSLAACAddrLocalRegenAttempts-1] |
| expectAutoGenAddrAsyncEvent(addr, newAddr) |
| rxNDPSolicit(e, addr.Address) |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected DAD event") |
| } |
| expectAutoGenAddrEvent(addr, invalidatedAddr) |
| |
| // The last address generated should resolve DAD. |
| addr = test.addrs[len(test.addrs)-1] |
| expectAutoGenAddrAsyncEvent(addr, newAddr) |
| expectDADEventAsync(addr.Address) |
| |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpected auto gen addr event = %+v", e) |
| default: |
| } |
| |
| // Wait for all the SLAAC addresses to be invalidated. |
| clock.Advance(lifetimeSeconds * time.Second) |
| gotAddresses := make(map[tcpip.Address]struct{}) |
| for _, a := range s.NICInfo()[nicID].ProtocolAddresses { |
| gotAddresses[a.AddressWithPrefix.Address] = struct{}{} |
| } |
| if diff := cmp.Diff(manuallyAssignedAddresses, gotAddresses); diff != "" { |
| t.Fatalf("assigned addresses mismatch (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| // stackAndNdpDispatcherWithDefaultRoute returns an ndpDispatcher, |
| // channel.Endpoint and stack.Stack. |
| // |
| // stack.Stack will have a default route through the router (llAddr3) installed |
| // and a static link-address (linkAddr3) added to the link address cache for the |
| // router. |
| func stackAndNdpDispatcherWithDefaultRoute(t *testing.T, nicID tcpip.NICID) (*ndpDispatcher, *channel.Endpoint, *stack.Stack) { |
| t.Helper() |
| ndpDisp := &ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| e.LinkEPCapabilities |= stack.CapabilityResolutionRequired |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| NDPDisp: ndpDisp, |
| })}, |
| TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol}, |
| }) |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| s.SetRouteTable([]tcpip.Route{{ |
| Destination: header.IPv6EmptySubnet, |
| Gateway: llAddr3, |
| NIC: nicID, |
| }}) |
| |
| if err := s.AddStaticNeighbor(nicID, ipv6.ProtocolNumber, llAddr3, linkAddr3); err != nil { |
| t.Fatalf("s.AddStaticNeighbor(%d, %d, %s, %s): %s", nicID, ipv6.ProtocolNumber, llAddr3, linkAddr3, err) |
| } |
| return ndpDisp, e, s |
| } |
| |
| // addrForNewConnectionTo returns the local address used when creating a new |
| // connection to addr. |
| func addrForNewConnectionTo(t *testing.T, s *stack.Stack, addr tcpip.FullAddress) tcpip.Address { |
| t.Helper() |
| |
| wq := waiter.Queue{} |
| we, ch := waiter.NewChannelEntry(nil) |
| wq.EventRegister(&we, waiter.EventIn) |
| defer wq.EventUnregister(&we) |
| defer close(ch) |
| ep, err := s.NewEndpoint(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq) |
| if err != nil { |
| t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err) |
| } |
| defer ep.Close() |
| ep.SocketOptions().SetV6Only(true) |
| if err := ep.Connect(addr); err != nil { |
| t.Fatalf("ep.Connect(%+v): %s", addr, err) |
| } |
| got, err := ep.GetLocalAddress() |
| if err != nil { |
| t.Fatalf("ep.GetLocalAddress(): %s", err) |
| } |
| return got.Addr |
| } |
| |
| // addrForNewConnection returns the local address used when creating a new |
| // connection. |
| func addrForNewConnection(t *testing.T, s *stack.Stack) tcpip.Address { |
| t.Helper() |
| |
| return addrForNewConnectionTo(t, s, dstAddr) |
| } |
| |
| // addrForNewConnectionWithAddr returns the local address used when creating a |
| // new connection with a specific local address. |
| func addrForNewConnectionWithAddr(t *testing.T, s *stack.Stack, addr tcpip.FullAddress) tcpip.Address { |
| t.Helper() |
| |
| wq := waiter.Queue{} |
| we, ch := waiter.NewChannelEntry(nil) |
| wq.EventRegister(&we, waiter.EventIn) |
| defer wq.EventUnregister(&we) |
| defer close(ch) |
| ep, err := s.NewEndpoint(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq) |
| if err != nil { |
| t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err) |
| } |
| defer ep.Close() |
| ep.SocketOptions().SetV6Only(true) |
| if err := ep.Bind(addr); err != nil { |
| t.Fatalf("ep.Bind(%+v): %s", addr, err) |
| } |
| if err := ep.Connect(dstAddr); err != nil { |
| t.Fatalf("ep.Connect(%+v): %s", dstAddr, err) |
| } |
| got, err := ep.GetLocalAddress() |
| if err != nil { |
| t.Fatalf("ep.GetLocalAddress(): %s", err) |
| } |
| return got.Addr |
| } |
| |
| // TestAutoGenAddrDeprecateFromPI tests deprecating a SLAAC address when |
| // receiving a PI with 0 preferred lifetime. |
| func TestAutoGenAddrDeprecateFromPI(t *testing.T) { |
| 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 err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil { |
| t.Fatal(err) |
| } |
| |
| if got := addrForNewConnection(t, s); got != addr.Address { |
| t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address) |
| } |
| } |
| |
| // Receive PI for prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100)) |
| expectAutoGenAddrEvent(addr1, newAddr) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should have %s in the list of addresses", addr1) |
| } |
| expectPrimaryAddr(addr1) |
| |
| // Deprecate addr for prefix1 immedaitely. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 0)) |
| expectAutoGenAddrEvent(addr1, deprecatedAddr) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should have %s in the list of addresses", addr1) |
| } |
| // addr should still be the primary endpoint as there are no other addresses. |
| expectPrimaryAddr(addr1) |
| |
| // Refresh lifetimes of addr generated from prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 100)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly got an auto-generated event") |
| default: |
| } |
| expectPrimaryAddr(addr1) |
| |
| // Receive PI for prefix2. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100)) |
| expectAutoGenAddrEvent(addr2, newAddr) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| expectPrimaryAddr(addr2) |
| |
| // Deprecate addr for prefix2 immedaitely. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0)) |
| expectAutoGenAddrEvent(addr2, deprecatedAddr) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| // addr1 should be the primary endpoint now since addr2 is deprecated but |
| // addr1 is not. |
| expectPrimaryAddr(addr1) |
| // addr2 is deprecated but if explicitly requested, it should be used. |
| fullAddr2 := tcpip.FullAddress{Addr: addr2.Address, NIC: nicID} |
| if got := addrForNewConnectionWithAddr(t, s, fullAddr2); got != addr2.Address { |
| t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr2, got, addr2.Address) |
| } |
| |
| // Another PI w/ 0 preferred lifetime should not result in a deprecation |
| // event. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly got an auto-generated event") |
| default: |
| } |
| expectPrimaryAddr(addr1) |
| if got := addrForNewConnectionWithAddr(t, s, fullAddr2); got != addr2.Address { |
| t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr2, got, addr2.Address) |
| } |
| |
| // Refresh lifetimes of addr generated from prefix2. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly got an auto-generated event") |
| default: |
| } |
| expectPrimaryAddr(addr2) |
| } |
| |
| // TestAutoGenAddrJobDeprecation tests that an address is properly deprecated |
| // when its preferred lifetime expires. |
| func TestAutoGenAddrJobDeprecation(t *testing.T) { |
| const nicID = 1 |
| const newMinVL = 2 |
| newMinVLDuration := newMinVL * time.Second |
| |
| saved := ipv6.MinPrefixInformationValidLifetimeForUpdate |
| defer func() { |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = saved |
| }() |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = newMinVLDuration |
| |
| 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") |
| } |
| } |
| |
| expectAutoGenAddrEventAfter := func(addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType, timeout time.Duration) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(timeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| } |
| |
| expectPrimaryAddr := func(addr tcpip.AddressWithPrefix) { |
| t.Helper() |
| |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil { |
| t.Fatal(err) |
| } |
| |
| if got := addrForNewConnection(t, s); got != addr.Address { |
| t.Errorf("got addrForNewConnection = %s, want = %s", got, addr.Address) |
| } |
| } |
| |
| // Receive PI for prefix2. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 100)) |
| expectAutoGenAddrEvent(addr2, newAddr) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| expectPrimaryAddr(addr2) |
| |
| // Receive a PI for prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, 100, 90)) |
| expectAutoGenAddrEvent(addr1, newAddr) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should have %s in the list of addresses", addr1) |
| } |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| expectPrimaryAddr(addr1) |
| |
| // Refresh lifetime for addr of prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, newMinVL-1)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly got an auto-generated event") |
| default: |
| } |
| expectPrimaryAddr(addr1) |
| |
| // Wait for addr of prefix1 to be deprecated. |
| expectAutoGenAddrEventAfter(addr1, deprecatedAddr, newMinVLDuration-time.Second+defaultAsyncPositiveEventTimeout) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should not have %s in the list of addresses", addr1) |
| } |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| // addr2 should be the primary endpoint now since addr1 is deprecated but |
| // addr2 is not. |
| expectPrimaryAddr(addr2) |
| // addr1 is deprecated but if explicitly requested, it should be used. |
| fullAddr1 := tcpip.FullAddress{Addr: addr1.Address, NIC: nicID} |
| if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address { |
| t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address) |
| } |
| |
| // Refresh valid lifetime for addr of prefix1, w/ 0 preferred lifetime to make |
| // sure we do not get a deprecation event again. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, 0)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly got an auto-generated event") |
| default: |
| } |
| expectPrimaryAddr(addr2) |
| if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address { |
| t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address) |
| } |
| |
| // Refresh lifetimes for addr of prefix1. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, newMinVL, newMinVL-1)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly got an auto-generated event") |
| default: |
| } |
| // addr1 is the primary endpoint again since it is non-deprecated now. |
| expectPrimaryAddr(addr1) |
| |
| // Wait for addr of prefix1 to be deprecated. |
| expectAutoGenAddrEventAfter(addr1, deprecatedAddr, newMinVLDuration-time.Second+defaultAsyncPositiveEventTimeout) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should not have %s in the list of addresses", addr1) |
| } |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| // addr2 should be the primary endpoint now since it is not deprecated. |
| expectPrimaryAddr(addr2) |
| if got := addrForNewConnectionWithAddr(t, s, fullAddr1); got != addr1.Address { |
| t.Errorf("got addrForNewConnectionWithAddr(_, _, %+v) = %s, want = %s", fullAddr1, got, addr1.Address) |
| } |
| |
| // Wait for addr of prefix1 to be invalidated. |
| expectAutoGenAddrEventAfter(addr1, invalidatedAddr, time.Second+defaultAsyncPositiveEventTimeout) |
| if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should not have %s in the list of addresses", addr1) |
| } |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| expectPrimaryAddr(addr2) |
| |
| // Refresh both lifetimes for addr of prefix2 to the same value. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, newMinVL, newMinVL)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly got an auto-generated event") |
| default: |
| } |
| |
| // Wait for a deprecation then invalidation events, or just an invalidation |
| // event. We need to cover both cases but cannot deterministically hit both |
| // cases because the deprecation and invalidation handlers could be handled in |
| // either deprecation then invalidation, or invalidation then deprecation |
| // (which should be cancelled by the invalidation handler). |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr2, deprecatedAddr); diff == "" { |
| // If we get a deprecation event first, we should get an invalidation |
| // event almost immediately after. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr2, invalidatedAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| } else if diff := checkAutoGenAddrEvent(e, addr2, invalidatedAddr); diff == "" { |
| // If we get an invalidation event first, we should not get a deprecation |
| // event after. |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly got an auto-generated event") |
| case <-time.After(defaultAsyncNegativeEventTimeout): |
| } |
| } else { |
| t.Fatalf("got unexpected auto-generated event") |
| } |
| case <-time.After(newMinVLDuration + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should not have %s in the list of addresses", addr1) |
| } |
| if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should not have %s in the list of addresses", addr2) |
| } |
| // Should not have any primary endpoints. |
| if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil { |
| t.Fatal(err) |
| } |
| wq := waiter.Queue{} |
| we, ch := waiter.NewChannelEntry(nil) |
| wq.EventRegister(&we, waiter.EventIn) |
| defer wq.EventUnregister(&we) |
| defer close(ch) |
| ep, err := s.NewEndpoint(header.UDPProtocolNumber, header.IPv6ProtocolNumber, &wq) |
| if err != nil { |
| t.Fatalf("s.NewEndpoint(%d, %d, _): %s", header.UDPProtocolNumber, header.IPv6ProtocolNumber, err) |
| } |
| defer ep.Close() |
| ep.SocketOptions().SetV6Only(true) |
| |
| { |
| err := ep.Connect(dstAddr) |
| if _, ok := err.(*tcpip.ErrNoRoute); !ok { |
| t.Errorf("got ep.Connect(%+v) = %s, want = %s", dstAddr, err, &tcpip.ErrNoRoute{}) |
| } |
| } |
| } |
| |
| // Tests transitioning a SLAAC address's valid lifetime between finite and |
| // infinite values. |
| func TestAutoGenAddrFiniteToInfiniteToFiniteVL(t *testing.T) { |
| const infiniteVLSeconds = 2 |
| const minVLSeconds = 1 |
| savedIL := header.NDPInfiniteLifetime |
| savedMinVL := ipv6.MinPrefixInformationValidLifetimeForUpdate |
| defer func() { |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = savedMinVL |
| header.NDPInfiniteLifetime = savedIL |
| }() |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = minVLSeconds * time.Second |
| header.NDPInfiniteLifetime = infiniteVLSeconds * time.Second |
| |
| prefix, _, addr := prefixSubnetAddr(0, linkAddr1) |
| |
| tests := []struct { |
| name string |
| infiniteVL uint32 |
| }{ |
| { |
| name: "EqualToInfiniteVL", |
| infiniteVL: infiniteVLSeconds, |
| }, |
| // Our implementation supports changing header.NDPInfiniteLifetime for tests |
| // such that a packet can be received where the lifetime field has a value |
| // greater than header.NDPInfiniteLifetime. Because of this, we test to make |
| // sure that receiving a value greater than header.NDPInfiniteLifetime is |
| // handled the same as when receiving a value equal to |
| // header.NDPInfiniteLifetime. |
| { |
| name: "MoreThanInfiniteVL", |
| infiniteVL: infiniteVLSeconds + 1, |
| }, |
| } |
| |
| // This Run will not return until the parallel tests finish. |
| // |
| // We need this because we need to do some teardown work after the |
| // parallel tests complete. |
| // |
| // See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for |
| // more details. |
| t.Run("group", func(t *testing.T) { |
| for _, test := range tests { |
| test := test |
| |
| t.Run(test.name, func(t *testing.T) { |
| t.Parallel() |
| |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Receive an RA with finite prefix. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0)) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| |
| default: |
| t.Fatal("expected addr auto gen event") |
| } |
| |
| // Receive an new RA with prefix with infinite VL. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.infiniteVL, 0)) |
| |
| // Receive a new RA with prefix with finite VL. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, 0)) |
| |
| 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(minVLSeconds*time.Second + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timeout waiting for addr auto gen event") |
| } |
| }) |
| } |
| }) |
| } |
| |
| // TestAutoGenAddrValidLifetimeUpdates tests that the valid lifetime of an |
| // auto-generated address only gets updated when required to, as specified in |
| // RFC 4862 section 5.5.3.e. |
| func TestAutoGenAddrValidLifetimeUpdates(t *testing.T) { |
| const infiniteVL = 4294967295 |
| const newMinVL = 4 |
| saved := ipv6.MinPrefixInformationValidLifetimeForUpdate |
| defer func() { |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = saved |
| }() |
| ipv6.MinPrefixInformationValidLifetimeForUpdate = newMinVL * time.Second |
| |
| prefix, _, addr := prefixSubnetAddr(0, linkAddr1) |
| |
| tests := []struct { |
| name string |
| ovl uint32 |
| nvl uint32 |
| evl uint32 |
| }{ |
| // Should update the VL to the minimum VL for updating if the |
| // new VL is less than newMinVL but was originally greater than |
| // it. |
| { |
| "LargeVLToVLLessThanMinVLForUpdate", |
| 9999, |
| 1, |
| newMinVL, |
| }, |
| { |
| "LargeVLTo0", |
| 9999, |
| 0, |
| newMinVL, |
| }, |
| { |
| "InfiniteVLToVLLessThanMinVLForUpdate", |
| infiniteVL, |
| 1, |
| newMinVL, |
| }, |
| { |
| "InfiniteVLTo0", |
| infiniteVL, |
| 0, |
| newMinVL, |
| }, |
| |
| // Should not update VL if original VL was less than newMinVL |
| // and the new VL is also less than newMinVL. |
| { |
| "ShouldNotUpdateWhenBothOldAndNewAreLessThanMinVLForUpdate", |
| newMinVL - 1, |
| newMinVL - 3, |
| newMinVL - 1, |
| }, |
| |
| // Should take the new VL if the new VL is greater than the |
| // remaining time or is greater than newMinVL. |
| { |
| "MorethanMinVLToLesserButStillMoreThanMinVLForUpdate", |
| newMinVL + 5, |
| newMinVL + 3, |
| newMinVL + 3, |
| }, |
| { |
| "SmallVLToGreaterVLButStillLessThanMinVLForUpdate", |
| newMinVL - 3, |
| newMinVL - 1, |
| newMinVL - 1, |
| }, |
| { |
| "SmallVLToGreaterVLThatIsMoreThaMinVLForUpdate", |
| newMinVL - 3, |
| newMinVL + 1, |
| newMinVL + 1, |
| }, |
| } |
| |
| // This Run will not return until the parallel tests finish. |
| // |
| // We need this because we need to do some teardown work after the |
| // parallel tests complete. |
| // |
| // See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for |
| // more details. |
| t.Run("group", func(t *testing.T) { |
| for _, test := range tests { |
| test := test |
| |
| t.Run(test.name, func(t *testing.T) { |
| t.Parallel() |
| |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 10), |
| } |
| e := channel.New(10, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Receive an RA with prefix with initial VL, |
| // test.ovl. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.ovl, 0)) |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected addr auto gen event") |
| } |
| |
| // Receive an new RA with prefix with new VL, |
| // test.nvl. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, test.nvl, 0)) |
| |
| // |
| // Validate that the VL for the address got set |
| // to test.evl. |
| // |
| |
| // The address should not be invalidated until the effective valid |
| // lifetime has passed. |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly received an auto gen addr event") |
| case <-time.After(time.Duration(test.evl)*time.Second - defaultAsyncNegativeEventTimeout): |
| } |
| |
| // Wait for the invalidation event. |
| 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(defaultAsyncPositiveEventTimeout): |
| t.Fatal("timeout waiting for addr auto gen event") |
| } |
| }) |
| } |
| }) |
| } |
| |
| // TestAutoGenAddrRemoval tests that when auto-generated addresses are removed |
| // by the user, its resources will be cleaned up and an invalidation event will |
| // be sent to the integrator. |
| func TestAutoGenAddrRemoval(t *testing.T) { |
| prefix, _, addr := prefixSubnetAddr(0, linkAddr1) |
| |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", 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 a PI to auto-generate an address. |
| const lifetimeSeconds = 1 |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, 0)) |
| expectAutoGenAddrEvent(addr, newAddr) |
| |
| // Removing the address should result in an invalidation event |
| // immediately. |
| if err := s.RemoveAddress(1, addr.Address); err != nil { |
| t.Fatalf("RemoveAddress(_, %s) = %s", addr.Address, err) |
| } |
| expectAutoGenAddrEvent(addr, invalidatedAddr) |
| |
| // Wait for the original valid lifetime to make sure the original job got |
| // cancelled/cleaned up. |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly received an auto gen addr event") |
| case <-time.After(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout): |
| } |
| } |
| |
| // TestAutoGenAddrAfterRemoval tests adding a SLAAC address that was previously |
| // assigned to the NIC but is in the permanentExpired state. |
| func TestAutoGenAddrAfterRemoval(t *testing.T) { |
| 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 err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, addr); err != nil { |
| t.Fatal(err) |
| } |
| |
| 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", 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) { |
| prefix, _, addr := prefixSubnetAddr(0, linkAddr1) |
| |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| // Add the address as a static address before SLAAC tries to add it. |
| if err := s.AddProtocolAddress(1, tcpip.ProtocolAddress{Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr}); err != nil { |
| t.Fatalf("AddAddress(_, %d, %s) = %s", header.IPv6ProtocolNumber, addr.Address, err) |
| } |
| if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) { |
| t.Fatalf("Should have %s in the list of addresses", addr1) |
| } |
| |
| // Receive a PI where the generated address will be the same as the one |
| // that we already have assigned statically. |
| const lifetimeSeconds = 1 |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, 0)) |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly received an auto gen addr event for an address we already have statically") |
| default: |
| } |
| if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) { |
| t.Fatalf("Should have %s in the list of addresses", addr1) |
| } |
| |
| // Should not get an invalidation event after the PI's invalidation |
| // time. |
| select { |
| case <-ndpDisp.autoGenAddrC: |
| t.Fatal("unexpectedly received an auto gen addr event") |
| case <-time.After(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout): |
| } |
| if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) { |
| t.Fatalf("Should have %s in the list of addresses", addr1) |
| } |
| } |
| |
| // TestAutoGenAddrWithOpaqueIID tests that SLAAC generated addresses will use |
| // opaque interface identifiers when configured to do so. |
| func TestAutoGenAddrWithOpaqueIID(t *testing.T) { |
| const nicID = 1 |
| const nicName = "nic1" |
| 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) |
| } |
| |
| prefix1, subnet1, _ := prefixSubnetAddr(0, linkAddr1) |
| prefix2, subnet2, _ := prefixSubnetAddr(1, linkAddr1) |
| // addr1 and addr2 are the addresses that are expected to be generated when |
| // stack.Stack is configured to generate opaque interface identifiers as |
| // defined by RFC 7217. |
| addrBytes := []byte(subnet1.ID()) |
| addr1 := tcpip.AddressWithPrefix{ |
| Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet1, nicName, 0, secretKey)), |
| PrefixLen: 64, |
| } |
| addrBytes = []byte(subnet2.ID()) |
| addr2 := tcpip.AddressWithPrefix{ |
| Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet2, nicName, 0, secretKey)), |
| PrefixLen: 64, |
| } |
| |
| ndpDisp := ndpDispatcher{ |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| OpaqueIIDOpts: ipv6.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 prefix1 in a PI. |
| const validLifetimeSecondPrefix1 = 1 |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix1, true, true, validLifetimeSecondPrefix1, 0)) |
| expectAutoGenAddrEvent(addr1, newAddr) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should have %s in the list of addresses", addr1) |
| } |
| |
| // Receive an RA with prefix2 in a PI with a large valid lifetime. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 100, 0)) |
| expectAutoGenAddrEvent(addr2, newAddr) |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should have %s in the list of addresses", addr1) |
| } |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| |
| // Wait for addr of prefix1 to be invalidated. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(validLifetimeSecondPrefix1*time.Second + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| if containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr1) { |
| t.Fatalf("should not have %s in the list of addresses", addr1) |
| } |
| if !containsV6Addr(s.NICInfo()[nicID].ProtocolAddresses, addr2) { |
| t.Fatalf("should have %s in the list of addresses", addr2) |
| } |
| } |
| |
| func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) { |
| const nicID = 1 |
| const nicName = "nic" |
| const dadTransmits = 1 |
| const retransmitTimer = time.Second |
| const maxMaxRetries = 3 |
| const lifetimeSeconds = 10 |
| |
| // Needed for the temporary address sub test. |
| savedMaxDesync := ipv6.MaxDesyncFactor |
| defer func() { |
| ipv6.MaxDesyncFactor = savedMaxDesync |
| }() |
| ipv6.MaxDesyncFactor = time.Nanosecond |
| |
| 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) |
| |
| addrForSubnet := func(subnet tcpip.Subnet, dadCounter uint8) tcpip.AddressWithPrefix { |
| addrBytes := []byte(subnet.ID()) |
| return tcpip.AddressWithPrefix{ |
| Address: tcpip.Address(header.AppendOpaqueInterfaceIdentifier(addrBytes[:header.IIDOffsetInIPv6Address], subnet, nicName, dadCounter, secretKey)), |
| PrefixLen: 64, |
| } |
| } |
| |
| expectAutoGenAddrEvent := func(t *testing.T, ndpDisp *ndpDispatcher, 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") |
| } |
| } |
| |
| expectAutoGenAddrEventAsync := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| if diff := checkAutoGenAddrEvent(e, addr, eventType); diff != "" { |
| t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for addr auto gen event") |
| } |
| } |
| |
| expectDADEvent := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr, res); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected DAD event") |
| } |
| } |
| |
| expectDADEventAsync := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) { |
| t.Helper() |
| |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr, res); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(dadTransmits*retransmitTimer + defaultAsyncPositiveEventTimeout): |
| t.Fatal("timed out waiting for DAD event") |
| } |
| } |
| |
| stableAddrForTempAddrTest := addrForSubnet(subnet, 0) |
| |
| addrTypes := []struct { |
| name string |
| ndpConfigs ipv6.NDPConfigurations |
| autoGenLinkLocal bool |
| prepareFn func(t *testing.T, ndpDisp *ndpDispatcher, e *channel.Endpoint, tempIIDHistory []byte) []tcpip.AddressWithPrefix |
| addrGenFn func(dadCounter uint8, tempIIDHistory []byte) tcpip.AddressWithPrefix |
| }{ |
| { |
| name: "Global address", |
| ndpConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| prepareFn: func(_ *testing.T, _ *ndpDispatcher, e *channel.Endpoint, _ []byte) []tcpip.AddressWithPrefix { |
| // Receive an RA with prefix1 in a PI. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, lifetimeSeconds, lifetimeSeconds)) |
| return nil |
| |
| }, |
| addrGenFn: func(dadCounter uint8, _ []byte) tcpip.AddressWithPrefix { |
| return addrForSubnet(subnet, dadCounter) |
| }, |
| }, |
| { |
| name: "LinkLocal address", |
| ndpConfigs: ipv6.NDPConfigurations{}, |
| autoGenLinkLocal: true, |
| prepareFn: func(*testing.T, *ndpDispatcher, *channel.Endpoint, []byte) []tcpip.AddressWithPrefix { |
| return nil |
| }, |
| addrGenFn: func(dadCounter uint8, _ []byte) tcpip.AddressWithPrefix { |
| return addrForSubnet(header.IPv6LinkLocalPrefix.Subnet(), dadCounter) |
| }, |
| }, |
| { |
| name: "Temporary address", |
| ndpConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| AutoGenTempGlobalAddresses: true, |
| }, |
| prepareFn: func(t *testing.T, ndpDisp *ndpDispatcher, e *channel.Endpoint, tempIIDHistory []byte) []tcpip.AddressWithPrefix { |
| header.InitialTempIID(tempIIDHistory, nil, nicID) |
| |
| // Generate a stable SLAAC address so temporary addresses will be |
| // generated. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, 100, 100)) |
| expectAutoGenAddrEvent(t, ndpDisp, stableAddrForTempAddrTest, newAddr) |
| expectDADEventAsync(t, ndpDisp, stableAddrForTempAddrTest.Address, &stack.DADSucceeded{}) |
| |
| // The stable address will be assigned throughout the test. |
| return []tcpip.AddressWithPrefix{stableAddrForTempAddrTest} |
| }, |
| addrGenFn: func(_ uint8, tempIIDHistory []byte) tcpip.AddressWithPrefix { |
| return header.GenerateTempIPv6SLAACAddr(tempIIDHistory, stableAddrForTempAddrTest.Address) |
| }, |
| }, |
| } |
| |
| for _, addrType := range addrTypes { |
| // This Run will not return until the parallel tests finish. |
| // |
| // We need this because we need to do some teardown work after the parallel |
| // tests complete and limit the number of parallel tests running at the same |
| // time to reduce flakes. |
| // |
| // See https://godoc.org/testing#hdr-Subtests_and_Sub_benchmarks for |
| // more details. |
| t.Run(addrType.name, func(t *testing.T) { |
| for maxRetries := uint8(0); maxRetries <= maxMaxRetries; maxRetries++ { |
| for numFailures := uint8(0); numFailures <= maxRetries+1; numFailures++ { |
| maxRetries := maxRetries |
| numFailures := numFailures |
| addrType := addrType |
| |
| t.Run(fmt.Sprintf("%d max retries and %d failures", 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) |
| ndpConfigs := addrType.ndpConfigs |
| ndpConfigs.AutoGenAddressConflictRetries = maxRetries |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| AutoGenLinkLocal: addrType.autoGenLinkLocal, |
| DADConfigs: stack.DADConfigurations{ |
| DupAddrDetectTransmits: dadTransmits, |
| RetransmitTimer: retransmitTimer, |
| }, |
| NDPConfigs: ndpConfigs, |
| NDPDisp: &ndpDisp, |
| OpaqueIIDOpts: ipv6.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) |
| } |
| |
| var tempIIDHistory [header.IIDSize]byte |
| stableAddrs := addrType.prepareFn(t, &ndpDisp, e, tempIIDHistory[:]) |
| |
| // Simulate DAD conflicts so the address is regenerated. |
| for i := uint8(0); i < numFailures; i++ { |
| addr := addrType.addrGenFn(i, tempIIDHistory[:]) |
| expectAutoGenAddrEventAsync(t, &ndpDisp, addr, newAddr) |
| |
| // Should not have any new addresses assigned to the NIC. |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, stableAddrs, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // Simulate a DAD conflict. |
| rxNDPSolicit(e, addr.Address) |
| expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr) |
| expectDADEvent(t, &ndpDisp, addr.Address, &stack.DADDupAddrDetected{}) |
| |
| // 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) |
| } |
| expectDADEvent(t, &ndpDisp, addr.Address, &stack.DADAborted{}) |
| } |
| |
| // Should not have any new addresses assigned to the NIC. |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, stableAddrs, nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| |
| // If we had less failures than generation attempts, we should have |
| // an address after DAD resolves. |
| if maxRetries+1 > numFailures { |
| addr := addrType.addrGenFn(numFailures, tempIIDHistory[:]) |
| expectAutoGenAddrEventAsync(t, &ndpDisp, addr, newAddr) |
| expectDADEventAsync(t, &ndpDisp, addr.Address, &stack.DADSucceeded{}) |
| if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, append(stableAddrs, addr), nil); mismatch != "" { |
| t.Fatal(mismatch) |
| } |
| } |
| |
| // Should not attempt address generation again. |
| select { |
| case e := <-ndpDisp.autoGenAddrC: |
| t.Fatalf("unexpectedly got an auto-generated address event = %+v", e) |
| case <-time.After(defaultAsyncNegativeEventTimeout): |
| } |
| }) |
| } |
| } |
| }) |
| } |
| } |
| |
| // 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 ipv6.NDPConfigurations |
| autoGenLinkLocal bool |
| subnet tcpip.Subnet |
| triggerSLAACFn func(e *channel.Endpoint) |
| }{ |
| { |
| name: "Global address", |
| ndpConfigs: ipv6.NDPConfigurations{ |
| 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: ipv6.NDPConfigurations{ |
| 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.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| AutoGenLinkLocal: addrType.autoGenLinkLocal, |
| NDPConfigs: addrType.ndpConfigs, |
| NDPDisp: &ndpDisp, |
| DADConfigs: stack.DADConfigurations{ |
| DupAddrDetectTransmits: dadTransmits, |
| RetransmitTimer: retransmitTimer, |
| }, |
| })}, |
| }) |
| 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. |
| rxNDPSolicit(e, addr.Address) |
| expectAutoGenAddrEvent(addr, invalidatedAddr) |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); 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(defaultAsyncNegativeEventTimeout): |
| } |
| }) |
| } |
| } |
| |
| // 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.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| DADConfigs: stack.DADConfigurations{ |
| DupAddrDetectTransmits: dadTransmits, |
| RetransmitTimer: retransmitTimer, |
| }, |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| AutoGenGlobalAddresses: true, |
| AutoGenAddressConflictRetries: maxRetries, |
| }, |
| NDPDisp: &ndpDisp, |
| OpaqueIIDOpts: ipv6.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) |
| rxNDPSolicit(e, addr.Address) |
| expectAutoGenAddrEvent(addr, invalidatedAddr) |
| select { |
| case e := <-ndpDisp.dadC: |
| if diff := checkDADEvent(e, nicID, addr.Address, &stack.DADDupAddrDetected{}); 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, &stack.DADSucceeded{}); diff != "" { |
| t.Errorf("DAD event mismatch (-want +got):\n%s", diff) |
| } |
| case <-time.After(dadTransmits*retransmitTimer + defaultAsyncPositiveEventTimeout): |
| 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(defaultAsyncPositiveEventTimeout): |
| 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 + defaultAsyncPositiveEventTimeout): |
| 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. |
| func TestNDPRecursiveDNSServerDispatch(t *testing.T) { |
| tests := []struct { |
| name string |
| opt header.NDPRecursiveDNSServer |
| expected *ndpRDNSS |
| }{ |
| { |
| "Unspecified", |
| header.NDPRecursiveDNSServer([]byte{ |
| 0, 0, |
| 0, 0, 0, 2, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| }), |
| nil, |
| }, |
| { |
| "Multicast", |
| header.NDPRecursiveDNSServer([]byte{ |
| 0, 0, |
| 0, 0, 0, 2, |
| 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, |
| }), |
| nil, |
| }, |
| { |
| "OptionTooSmall", |
| header.NDPRecursiveDNSServer([]byte{ |
| 0, 0, |
| 0, 0, 0, 2, |
| 1, 2, 3, 4, 5, 6, 7, 8, |
| }), |
| nil, |
| }, |
| { |
| "0Addresses", |
| header.NDPRecursiveDNSServer([]byte{ |
| 0, 0, |
| 0, 0, 0, 2, |
| }), |
| nil, |
| }, |
| { |
| "Valid1Address", |
| header.NDPRecursiveDNSServer([]byte{ |
| 0, 0, |
| 0, 0, 0, 2, |
| 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1, |
| }), |
| &ndpRDNSS{ |
| []tcpip.Address{ |
| "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01", |
| }, |
| 2 * time.Second, |
| }, |
| }, |
| { |
| "Valid2Addresses", |
| header.NDPRecursiveDNSServer([]byte{ |
| 0, 0, |
| 0, 0, 0, 1, |
| 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1, |
| 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 2, |
| }), |
| &ndpRDNSS{ |
| []tcpip.Address{ |
| "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01", |
| "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x02", |
| }, |
| time.Second, |
| }, |
| }, |
| { |
| "Valid3Addresses", |
| header.NDPRecursiveDNSServer([]byte{ |
| 0, 0, |
| 0, 0, 0, 0, |
| 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 1, |
| 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 2, |
| 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 3, |
| }), |
| &ndpRDNSS{ |
| []tcpip.Address{ |
| "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x01", |
| "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x02", |
| "\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x00\x00\x00\x00\x03", |
| }, |
| 0, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| // We do not expect more than a single RDNSS |
| // event at any time for this test. |
| rdnssC: make(chan ndpRDNSSEvent, 1), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| if err := s.CreateNIC(1, e); err != nil { |
| t.Fatalf("CreateNIC(1) = %s", err) |
| } |
| |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, header.NDPOptionsSerializer{test.opt})) |
| |
| if test.expected != nil { |
| select { |
| case e := <-ndpDisp.rdnssC: |
| if e.nicID != 1 { |
| t.Errorf("got rdnss nicID = %d, want = 1", e.nicID) |
| } |
| if diff := cmp.Diff(e.rdnss.addrs, test.expected.addrs); diff != "" { |
| t.Errorf("rdnss addrs mismatch (-want +got):\n%s", diff) |
| } |
| if e.rdnss.lifetime != test.expected.lifetime { |
| t.Errorf("got rdnss lifetime = %s, want = %s", e.rdnss.lifetime, test.expected.lifetime) |
| } |
| default: |
| t.Fatal("expected an RDNSS option event") |
| } |
| } |
| |
| // Should have no more RDNSS options. |
| select { |
| case e := <-ndpDisp.rdnssC: |
| t.Fatalf("unexpectedly got a new RDNSS option event: %+v", e) |
| default: |
| } |
| }) |
| } |
| } |
| |
| // TestNDPDNSSearchListDispatch tests that the integrator is informed when an |
| // NDP DNS Search List option is received with at least one domain name in the |
| // search list. |
| func TestNDPDNSSearchListDispatch(t *testing.T) { |
| const nicID = 1 |
| |
| ndpDisp := ndpDispatcher{ |
| dnsslC: make(chan ndpDNSSLEvent, 3), |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| optSer := header.NDPOptionsSerializer{ |
| header.NDPDNSSearchList([]byte{ |
| 0, 0, |
| 0, 0, 0, 0, |
| 2, 'h', 'i', |
| 0, |
| }), |
| header.NDPDNSSearchList([]byte{ |
| 0, 0, |
| 0, 0, 0, 1, |
| 1, 'i', |
| 0, |
| 2, 'a', 'm', |
| 2, 'm', 'e', |
| 0, |
| }), |
| header.NDPDNSSearchList([]byte{ |
| 0, 0, |
| 0, 0, 1, 0, |
| 3, 'x', 'y', 'z', |
| 0, |
| 5, 'h', 'e', 'l', 'l', 'o', |
| 5, 'w', 'o', 'r', 'l', 'd', |
| 0, |
| 4, 't', 'h', 'i', 's', |
| 2, 'i', 's', |
| 1, 'a', |
| 4, 't', 'e', 's', 't', |
| 0, |
| }), |
| } |
| expected := []struct { |
| domainNames []string |
| lifetime time.Duration |
| }{ |
| { |
| domainNames: []string{ |
| "hi", |
| }, |
| lifetime: 0, |
| }, |
| { |
| domainNames: []string{ |
| "i", |
| "am.me", |
| }, |
| lifetime: time.Second, |
| }, |
| { |
| domainNames: []string{ |
| "xyz", |
| "hello.world", |
| "this.is.a.test", |
| }, |
| lifetime: 256 * time.Second, |
| }, |
| } |
| |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithOpts(llAddr1, 0, optSer)) |
| |
| for i, expected := range expected { |
| select { |
| case dnssl := <-ndpDisp.dnsslC: |
| if dnssl.nicID != nicID { |
| t.Errorf("got %d-th dnssl nicID = %d, want = %d", i, dnssl.nicID, nicID) |
| } |
| if diff := cmp.Diff(dnssl.domainNames, expected.domainNames); diff != "" { |
| t.Errorf("%d-th dnssl domain names mismatch (-want +got):\n%s", i, diff) |
| } |
| if dnssl.lifetime != expected.lifetime { |
| t.Errorf("got %d-th dnssl lifetime = %s, want = %s", i, dnssl.lifetime, expected.lifetime) |
| } |
| default: |
| t.Fatal("expected a DNSSL event") |
| } |
| } |
| |
| // Should have no more DNSSL options. |
| select { |
| case <-ndpDisp.dnsslC: |
| t.Fatal("unexpectedly got a DNSSL event") |
| default: |
| } |
| } |
| |
| // TestCleanupNDPState tests that all discovered routers and prefixes, and |
| // auto-generated addresses are invalidated when a NIC becomes a router. |
| func TestCleanupNDPState(t *testing.T) { |
| const ( |
| lifetimeSeconds = 5 |
| maxRouterAndPrefixEvents = 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) |
| llAddrWithPrefix1 := tcpip.AddressWithPrefix{ |
| Address: llAddr1, |
| PrefixLen: 64, |
| } |
| llAddrWithPrefix2 := tcpip.AddressWithPrefix{ |
| Address: llAddr2, |
| PrefixLen: 64, |
| } |
| |
| tests := []struct { |
| name string |
| cleanupFn func(t *testing.T, s *stack.Stack) |
| keepAutoGenLinkLocal bool |
| maxAutoGenAddrEvents int |
| skipFinalAddrCheck bool |
| }{ |
| // A NIC should still keep its auto-generated link-local address when |
| // becoming a router. |
| { |
| name: "Enable forwarding", |
| cleanupFn: func(t *testing.T, s *stack.Stack) { |
| t.Helper() |
| s.SetForwarding(ipv6.ProtocolNumber, true) |
| }, |
| keepAutoGenLinkLocal: true, |
| maxAutoGenAddrEvents: 4, |
| }, |
| |
| // A NIC should cleanup all NDP state when it is disabled. |
| { |
| name: "Disable NIC", |
| cleanupFn: func(t *testing.T, s *stack.Stack) { |
| t.Helper() |
| |
| if err := s.DisableNIC(nicID1); err != nil { |
| t.Fatalf("s.DisableNIC(%d): %s", nicID1, err) |
| } |
| if err := s.DisableNIC(nicID2); err != nil { |
| t.Fatalf("s.DisableNIC(%d): %s", nicID2, err) |
| } |
| }, |
| keepAutoGenLinkLocal: false, |
| maxAutoGenAddrEvents: 6, |
| }, |
| |
| // A NIC should cleanup all NDP state when it is removed. |
| { |
| name: "Remove NIC", |
| cleanupFn: func(t *testing.T, s *stack.Stack) { |
| t.Helper() |
| |
| if err := s.RemoveNIC(nicID1); err != nil { |
| t.Fatalf("s.RemoveNIC(%d): %s", nicID1, err) |
| } |
| if err := s.RemoveNIC(nicID2); err != nil { |
| t.Fatalf("s.RemoveNIC(%d): %s", nicID2, err) |
| } |
| }, |
| keepAutoGenLinkLocal: false, |
| maxAutoGenAddrEvents: 6, |
| // The NICs are removed so we can't check their addresses after calling |
| // stopFn. |
| skipFinalAddrCheck: true, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| ndpDisp := ndpDispatcher{ |
| routerC: make(chan ndpRouterEvent, maxRouterAndPrefixEvents), |
| rememberRouter: true, |
| prefixC: make(chan ndpPrefixEvent, maxRouterAndPrefixEvents), |
| rememberPrefix: true, |
| autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAutoGenAddrEvents), |
| } |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| AutoGenLinkLocal: true, |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| DiscoverDefaultRouters: true, |
| DiscoverOnLinkPrefixes: true, |
| AutoGenGlobalAddresses: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| 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{} |
| } |
| |
| e1 := channel.New(0, 1280, linkAddr1) |
| if err := s.CreateNIC(nicID1, e1); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID1, err) |
| } |
| // 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. |
| expectAutoGenAddrEvent() |
| |
| e2 := channel.New(0, 1280, linkAddr2) |
| if err := s.CreateNIC(nicID2, e2); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err) |
| } |
| expectAutoGenAddrEvent() |
| |
| // Receive RAs on NIC(1) and NIC(2) from default routers (llAddr3 and |
| // llAddr4) w/ PI (for prefix1 in RA from llAddr3 and prefix2 in RA from |
| // llAddr4) to discover multiple routers and prefixes, and auto-gen |
| // multiple addresses. |
| |
| e1.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr3, lifetimeSeconds, prefix1, true, true, lifetimeSeconds, lifetimeSeconds)) |
| if ok, _ := expectRouterEvent(); !ok { |
| t.Errorf("expected router event for %s on NIC(%d)", llAddr3, 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(llAddr4, lifetimeSeconds, prefix2, true, true, lifetimeSeconds, lifetimeSeconds)) |
| if ok, _ := expectRouterEvent(); !ok { |
| t.Errorf("expected router event for %s on NIC(%d)", llAddr4, 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(llAddr3, lifetimeSeconds, prefix1, true, true, lifetimeSeconds, lifetimeSeconds)) |
| if ok, _ := expectRouterEvent(); !ok { |
| t.Errorf("expected router event for %s on NIC(%d)", llAddr3, 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(llAddr4, lifetimeSeconds, prefix2, true, true, lifetimeSeconds, lifetimeSeconds)) |
| if ok, _ := expectRouterEvent(); !ok { |
| t.Errorf("expected router event for %s on NIC(%d)", llAddr4, 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 !containsV6Addr(nic1Addrs, llAddrWithPrefix1) { |
| t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs) |
| } |
| if !containsV6Addr(nic1Addrs, e1Addr1) { |
| t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e1Addr1, nicID1, nic1Addrs) |
| } |
| if !containsV6Addr(nic1Addrs, e1Addr2) { |
| t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e1Addr2, nicID1, nic1Addrs) |
| } |
| if !containsV6Addr(nic2Addrs, llAddrWithPrefix2) { |
| t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs) |
| } |
| if !containsV6Addr(nic2Addrs, e2Addr1) { |
| t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", e2Addr1, nicID2, nic2Addrs) |
| } |
| if !containsV6Addr(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() |
| } |
| |
| test.cleanupFn(t, s) |
| |
| // Collect invalidation events after having NDP state cleaned up. |
| gotRouterEvents := make(map[ndpRouterEvent]int) |
| for i := 0; i < maxRouterAndPrefixEvents; i++ { |
| ok, e := expectRouterEvent() |
| if !ok { |
| t.Errorf("expected %d router events after becoming a router; got = %d", maxRouterAndPrefixEvents, i) |
| break |
| } |
| gotRouterEvents[e]++ |
| } |
| gotPrefixEvents := make(map[ndpPrefixEvent]int) |
| for i := 0; i < maxRouterAndPrefixEvents; i++ { |
| ok, e := expectPrefixEvent() |
| if !ok { |
| t.Errorf("expected %d prefix events after becoming a router; got = %d", maxRouterAndPrefixEvents, i) |
| break |
| } |
| gotPrefixEvents[e]++ |
| } |
| gotAutoGenAddrEvents := make(map[ndpAutoGenAddrEvent]int) |
| for i := 0; i < test.maxAutoGenAddrEvents; i++ { |
| ok, e := expectAutoGenAddrEvent() |
| if !ok { |
| t.Errorf("expected %d auto-generated address events after becoming a router; got = %d", test.maxAutoGenAddrEvents, 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: llAddr3, discovered: false}: 1, |
| {nicID: nicID1, addr: llAddr4, discovered: false}: 1, |
| {nicID: nicID2, addr: llAddr3, discovered: false}: 1, |
| {nicID: nicID2, addr: llAddr4, 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 !test.keepAutoGenLinkLocal { |
| expectedAutoGenAddrEvents[ndpAutoGenAddrEvent{nicID: nicID1, addr: llAddrWithPrefix1, eventType: invalidatedAddr}] = 1 |
| expectedAutoGenAddrEvents[ndpAutoGenAddrEvent{nicID: nicID2, addr: llAddrWithPrefix2, eventType: invalidatedAddr}] = 1 |
| } |
| |
| if diff := cmp.Diff(expectedAutoGenAddrEvents, gotAutoGenAddrEvents); diff != "" { |
| t.Errorf("auto-generated address events mismatch (-want +got):\n%s", diff) |
| } |
| |
| if !test.skipFinalAddrCheck { |
| // Make sure the auto-generated addresses got removed. |
| nicinfo = s.NICInfo() |
| nic1Addrs = nicinfo[nicID1].ProtocolAddresses |
| nic2Addrs = nicinfo[nicID2].ProtocolAddresses |
| if containsV6Addr(nic1Addrs, llAddrWithPrefix1) != test.keepAutoGenLinkLocal { |
| if test.keepAutoGenLinkLocal { |
| t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs) |
| } else { |
| t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", llAddrWithPrefix1, nicID1, nic1Addrs) |
| } |
| } |
| if containsV6Addr(nic1Addrs, e1Addr1) { |
| t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e1Addr1, nicID1, nic1Addrs) |
| } |
| if containsV6Addr(nic1Addrs, e1Addr2) { |
| t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e1Addr2, nicID1, nic1Addrs) |
| } |
| if containsV6Addr(nic2Addrs, llAddrWithPrefix2) != test.keepAutoGenLinkLocal { |
| if test.keepAutoGenLinkLocal { |
| t.Errorf("missing %s from the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs) |
| } else { |
| t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", llAddrWithPrefix2, nicID2, nic2Addrs) |
| } |
| } |
| if containsV6Addr(nic2Addrs, e2Addr1) { |
| t.Errorf("still have %s in the list of addresses for NIC(%d): %+v", e2Addr1, nicID2, nic2Addrs) |
| } |
| if containsV6Addr(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 the NDP state was cleaned up). |
| time.Sleep(lifetimeSeconds*time.Second + defaultAsyncNegativeEventTimeout) |
| 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: |
| } |
| }) |
| } |
| } |
| |
| // TestDHCPv6ConfigurationFromNDPDA tests that the NDPDispatcher is properly |
| // informed when new information about what configurations are available via |
| // DHCPv6 is learned. |
| func TestDHCPv6ConfigurationFromNDPDA(t *testing.T) { |
| const nicID = 1 |
| |
| ndpDisp := ndpDispatcher{ |
| dhcpv6ConfigurationC: make(chan ndpDHCPv6Event, 1), |
| rememberRouter: true, |
| } |
| e := channel.New(0, 1280, linkAddr1) |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| HandleRAs: true, |
| }, |
| NDPDisp: &ndpDisp, |
| })}, |
| }) |
| |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| expectDHCPv6Event := func(configuration ipv6.DHCPv6ConfigurationFromNDPRA) { |
| t.Helper() |
| select { |
| case e := <-ndpDisp.dhcpv6ConfigurationC: |
| if diff := cmp.Diff(ndpDHCPv6Event{nicID: nicID, configuration: configuration}, e, cmp.AllowUnexported(e)); diff != "" { |
| t.Errorf("dhcpv6 event mismatch (-want +got):\n%s", diff) |
| } |
| default: |
| t.Fatal("expected DHCPv6 configuration event") |
| } |
| } |
| |
| expectNoDHCPv6Event := func() { |
| t.Helper() |
| select { |
| case <-ndpDisp.dhcpv6ConfigurationC: |
| t.Fatal("unexpected DHCPv6 configuration event") |
| default: |
| } |
| } |
| |
| // Even if the first RA reports no DHCPv6 configurations are available, the |
| // dispatcher should get an event. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false)) |
| expectDHCPv6Event(ipv6.DHCPv6NoConfiguration) |
| // Receiving the same update again should not result in an event to the |
| // dispatcher. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false)) |
| expectNoDHCPv6Event() |
| |
| // Receive an RA that updates the DHCPv6 configuration to Other |
| // Configurations. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true)) |
| expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true)) |
| expectNoDHCPv6Event() |
| |
| // Receive an RA that updates the DHCPv6 configuration to Managed Address. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false)) |
| expectDHCPv6Event(ipv6.DHCPv6ManagedAddress) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false)) |
| expectNoDHCPv6Event() |
| |
| // Receive an RA that updates the DHCPv6 configuration to none. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false)) |
| expectDHCPv6Event(ipv6.DHCPv6NoConfiguration) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, false)) |
| expectNoDHCPv6Event() |
| |
| // Receive an RA that updates the DHCPv6 configuration to Managed Address. |
| // |
| // Note, when the M flag is set, the O flag is redundant. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true)) |
| expectDHCPv6Event(ipv6.DHCPv6ManagedAddress) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true)) |
| expectNoDHCPv6Event() |
| // Even though the DHCPv6 flags are different, the effective configuration is |
| // the same so we should not receive a new event. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, false)) |
| expectNoDHCPv6Event() |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, true, true)) |
| expectNoDHCPv6Event() |
| |
| // Receive an RA that updates the DHCPv6 configuration to Other |
| // Configurations. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true)) |
| expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true)) |
| expectNoDHCPv6Event() |
| |
| // Cycling the NIC should cause the last DHCPv6 configuration to be cleared. |
| if err := s.DisableNIC(nicID); err != nil { |
| t.Fatalf("s.DisableNIC(%d): %s", nicID, err) |
| } |
| if err := s.EnableNIC(nicID); err != nil { |
| t.Fatalf("s.EnableNIC(%d): %s", nicID, err) |
| } |
| |
| // Receive an RA that updates the DHCPv6 configuration to Other |
| // Configurations. |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true)) |
| expectDHCPv6Event(ipv6.DHCPv6OtherConfigurations) |
| e.InjectInbound(header.IPv6ProtocolNumber, raBufWithDHCPv6(llAddr2, false, true)) |
| expectNoDHCPv6Event() |
| } |
| |
| // TestRouterSolicitation tests the initial Router Solicitations that are sent |
| // when a NIC newly becomes enabled. |
| func TestRouterSolicitation(t *testing.T) { |
| const nicID = 1 |
| |
| tests := []struct { |
| name string |
| linkHeaderLen uint16 |
| linkAddr tcpip.LinkAddress |
| nicAddr tcpip.Address |
| expectedSrcAddr tcpip.Address |
| expectedNDPOpts []header.NDPOption |
| maxRtrSolicit uint8 |
| rtrSolicitInt time.Duration |
| effectiveRtrSolicitInt time.Duration |
| maxRtrSolicitDelay time.Duration |
| effectiveMaxRtrSolicitDelay time.Duration |
| }{ |
| { |
| name: "Single RS with 2s delay and interval", |
| expectedSrcAddr: header.IPv6Any, |
| maxRtrSolicit: 1, |
| rtrSolicitInt: 2 * time.Second, |
| effectiveRtrSolicitInt: 2 * time.Second, |
| maxRtrSolicitDelay: 2 * time.Second, |
| effectiveMaxRtrSolicitDelay: 2 * time.Second, |
| }, |
| { |
| name: "Single RS with 4s delay and interval", |
| expectedSrcAddr: header.IPv6Any, |
| maxRtrSolicit: 1, |
| rtrSolicitInt: 4 * time.Second, |
| effectiveRtrSolicitInt: 4 * time.Second, |
| maxRtrSolicitDelay: 4 * time.Second, |
| effectiveMaxRtrSolicitDelay: 4 * time.Second, |
| }, |
| { |
| name: "Two RS with delay", |
| linkHeaderLen: 1, |
| nicAddr: llAddr1, |
| expectedSrcAddr: llAddr1, |
| maxRtrSolicit: 2, |
| rtrSolicitInt: 2 * time.Second, |
| effectiveRtrSolicitInt: 2 * time.Second, |
| maxRtrSolicitDelay: 500 * time.Millisecond, |
| effectiveMaxRtrSolicitDelay: 500 * time.Millisecond, |
| }, |
| { |
| name: "Single RS without delay", |
| linkHeaderLen: 2, |
| linkAddr: linkAddr1, |
| nicAddr: llAddr1, |
| expectedSrcAddr: llAddr1, |
| expectedNDPOpts: []header.NDPOption{ |
| header.NDPSourceLinkLayerAddressOption(linkAddr1), |
| }, |
| maxRtrSolicit: 1, |
| rtrSolicitInt: 2 * time.Second, |
| effectiveRtrSolicitInt: 2 * time.Second, |
| maxRtrSolicitDelay: 0, |
| effectiveMaxRtrSolicitDelay: 0, |
| }, |
| { |
| name: "Two RS without delay and invalid zero interval", |
| linkHeaderLen: 3, |
| linkAddr: linkAddr1, |
| expectedSrcAddr: header.IPv6Any, |
| maxRtrSolicit: 2, |
| rtrSolicitInt: 0, |
| effectiveRtrSolicitInt: 4 * time.Second, |
| maxRtrSolicitDelay: 0, |
| effectiveMaxRtrSolicitDelay: 0, |
| }, |
| { |
| name: "Three RS without delay", |
| linkAddr: linkAddr1, |
| expectedSrcAddr: header.IPv6Any, |
| maxRtrSolicit: 3, |
| rtrSolicitInt: 500 * time.Millisecond, |
| effectiveRtrSolicitInt: 500 * time.Millisecond, |
| maxRtrSolicitDelay: 0, |
| effectiveMaxRtrSolicitDelay: 0, |
| }, |
| { |
| name: "Two RS with invalid negative delay", |
| linkAddr: linkAddr1, |
| expectedSrcAddr: header.IPv6Any, |
| maxRtrSolicit: 2, |
| rtrSolicitInt: time.Second, |
| effectiveRtrSolicitInt: time.Second, |
| maxRtrSolicitDelay: -3 * time.Second, |
| effectiveMaxRtrSolicitDelay: time.Second, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| clock := faketime.NewManualClock() |
| e := channelLinkWithHeaderLength{ |
| Endpoint: channel.New(int(test.maxRtrSolicit), 1280, test.linkAddr), |
| headerLength: test.linkHeaderLen, |
| } |
| e.Endpoint.LinkEPCapabilities |= stack.CapabilityResolutionRequired |
| waitForPkt := func(timeout time.Duration) { |
| t.Helper() |
| |
| clock.Advance(timeout) |
| p, ok := e.Read() |
| if !ok { |
| t.Fatal("expected router solicitation packet") |
| } |
| |
| if p.Proto != header.IPv6ProtocolNumber { |
| t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber) |
| } |
| |
| // Make sure the right remote link address is used. |
| if want := header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersMulticastAddress); p.Route.RemoteLinkAddress != want { |
| t.Errorf("got remote link address = %s, want = %s", p.Route.RemoteLinkAddress, want) |
| } |
| |
| checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()), |
| checker.SrcAddr(test.expectedSrcAddr), |
| checker.DstAddr(header.IPv6AllRoutersMulticastAddress), |
| checker.TTL(header.NDPHopLimit), |
| checker.NDPRS(checker.NDPRSOptions(test.expectedNDPOpts)), |
| ) |
| |
| if l, want := p.Pkt.AvailableHeaderBytes(), int(test.linkHeaderLen); l != want { |
| t.Errorf("got p.Pkt.AvailableHeaderBytes() = %d; want = %d", l, want) |
| } |
| } |
| waitForNothing := func(timeout time.Duration) { |
| t.Helper() |
| |
| clock.Advance(timeout) |
| if p, ok := e.Read(); ok { |
| t.Fatalf("unexpectedly got a packet = %#v", p) |
| } |
| } |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| MaxRtrSolicitations: test.maxRtrSolicit, |
| RtrSolicitationInterval: test.rtrSolicitInt, |
| MaxRtrSolicitationDelay: test.maxRtrSolicitDelay, |
| }, |
| })}, |
| Clock: clock, |
| }) |
| if err := s.CreateNIC(nicID, &e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| if addr := test.nicAddr; addr != "" { |
| if err := s.AddAddress(nicID, header.IPv6ProtocolNumber, addr); err != nil { |
| t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr, err) |
| } |
| } |
| |
| // Make sure each RS is sent at the right time. |
| remaining := test.maxRtrSolicit |
| if remaining > 0 { |
| waitForPkt(test.effectiveMaxRtrSolicitDelay) |
| remaining-- |
| } |
| |
| for ; remaining > 0; remaining-- { |
| if test.effectiveRtrSolicitInt > defaultAsyncPositiveEventTimeout { |
| waitForNothing(test.effectiveRtrSolicitInt - time.Nanosecond) |
| waitForPkt(time.Nanosecond) |
| } else { |
| waitForPkt(test.effectiveRtrSolicitInt) |
| } |
| } |
| |
| // Make sure no more RS. |
| if test.effectiveRtrSolicitInt > test.effectiveMaxRtrSolicitDelay { |
| waitForNothing(test.effectiveRtrSolicitInt) |
| } else { |
| waitForNothing(test.effectiveMaxRtrSolicitDelay) |
| } |
| |
| if got, want := s.Stats().ICMP.V6.PacketsSent.RouterSolicit.Value(), uint64(test.maxRtrSolicit); got != want { |
| t.Fatalf("got sent RouterSolicit = %d, want = %d", got, want) |
| } |
| }) |
| } |
| } |
| |
| func TestStopStartSolicitingRouters(t *testing.T) { |
| const nicID = 1 |
| const delay = 0 |
| const interval = 500 * time.Millisecond |
| const maxRtrSolicitations = 3 |
| |
| tests := []struct { |
| name string |
| startFn func(t *testing.T, s *stack.Stack) |
| // first is used to tell stopFn that it is being called for the first time |
| // after router solicitations were last enabled. |
| stopFn func(t *testing.T, s *stack.Stack, first bool) |
| }{ |
| // Tests that when forwarding is enabled or disabled, router solicitations |
| // are stopped or started, respectively. |
| { |
| name: "Enable and disable forwarding", |
| startFn: func(t *testing.T, s *stack.Stack) { |
| t.Helper() |
| s.SetForwarding(ipv6.ProtocolNumber, false) |
| }, |
| stopFn: func(t *testing.T, s *stack.Stack, _ bool) { |
| t.Helper() |
| s.SetForwarding(ipv6.ProtocolNumber, true) |
| }, |
| }, |
| |
| // Tests that when a NIC is enabled or disabled, router solicitations |
| // are started or stopped, respectively. |
| { |
| name: "Enable and disable NIC", |
| startFn: func(t *testing.T, s *stack.Stack) { |
| t.Helper() |
| |
| if err := s.EnableNIC(nicID); err != nil { |
| t.Fatalf("s.EnableNIC(%d): %s", nicID, err) |
| } |
| }, |
| stopFn: func(t *testing.T, s *stack.Stack, _ bool) { |
| t.Helper() |
| |
| if err := s.DisableNIC(nicID); err != nil { |
| t.Fatalf("s.DisableNIC(%d): %s", nicID, err) |
| } |
| }, |
| }, |
| |
| // Tests that when a NIC is removed, router solicitations are stopped. We |
| // cannot start router solications on a removed NIC. |
| { |
| name: "Remove NIC", |
| stopFn: func(t *testing.T, s *stack.Stack, first bool) { |
| t.Helper() |
| |
| // Only try to remove the NIC the first time stopFn is called since it's |
| // impossible to remove an already removed NIC. |
| if !first { |
| return |
| } |
| |
| if err := s.RemoveNIC(nicID); err != nil { |
| t.Fatalf("s.RemoveNIC(%d): %s", nicID, err) |
| } |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| e := channel.New(maxRtrSolicitations, 1280, linkAddr1) |
| waitForPkt := func(timeout time.Duration) { |
| t.Helper() |
| |
| ctx, cancel := context.WithTimeout(context.Background(), timeout) |
| defer cancel() |
| p, ok := e.ReadContext(ctx) |
| if !ok { |
| t.Fatal("timed out waiting for packet") |
| } |
| |
| if p.Proto != header.IPv6ProtocolNumber { |
| t.Fatalf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber) |
| } |
| checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()), |
| checker.SrcAddr(header.IPv6Any), |
| checker.DstAddr(header.IPv6AllRoutersMulticastAddress), |
| checker.TTL(header.NDPHopLimit), |
| checker.NDPRS()) |
| } |
| s := stack.New(stack.Options{ |
| NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ |
| NDPConfigs: ipv6.NDPConfigurations{ |
| MaxRtrSolicitations: maxRtrSolicitations, |
| RtrSolicitationInterval: interval, |
| MaxRtrSolicitationDelay: delay, |
| }, |
| })}, |
| }) |
| if err := s.CreateNIC(nicID, e); err != nil { |
| t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) |
| } |
| |
| // Stop soliciting routers. |
| test.stopFn(t, s, true /* first */) |
| ctx, cancel := context.WithTimeout(context.Background(), delay+defaultAsyncNegativeEventTimeout) |
| defer cancel() |
| if _, ok := e.ReadContext(ctx); ok { |
| // A single RS may have been sent before solicitations were stopped. |
| ctx, cancel := context.WithTimeout(context.Background(), interval+defaultAsyncNegativeEventTimeout) |
| defer cancel() |
| if _, ok = e.ReadContext(ctx); ok { |
| t.Fatal("should not have sent more than one RS message") |
| } |
| } |
| |
| // Stopping router solicitations after it has already been stopped should |
| // do nothing. |
| test.stopFn(t, s, false /* first */) |
| ctx, cancel = context.WithTimeout(context.Background(), delay+defaultAsyncNegativeEventTimeout) |
| defer cancel() |
| if _, ok := e.ReadContext(ctx); ok { |
| t.Fatal("unexpectedly got a packet after router solicitation has been stopepd") |
| } |
| |
| // If test.startFn is nil, there is no way to restart router solications. |
| if test.startFn == nil { |
| return |
| } |
| |
| // Start soliciting routers. |
| test.startFn(t, s) |
| waitForPkt(delay + defaultAsyncPositiveEventTimeout) |
| waitForPkt(interval + defaultAsyncPositiveEventTimeout) |
| waitForPkt(interval + defaultAsyncPositiveEventTimeout) |
| ctx, cancel = context.WithTimeout(context.Background(), interval+defaultAsyncNegativeEventTimeout) |
| defer cancel() |
| if _, ok := e.ReadContext(ctx); ok { |
| t.Fatal("unexpectedly got an extra packet after sending out the expected RSs") |
| } |
| |
| // Starting router solicitations after it has already completed should do |
| // nothing. |
| test.startFn(t, s) |
| ctx, cancel = context.WithTimeout(context.Background(), delay+defaultAsyncNegativeEventTimeout) |
| defer cancel() |
| if _, ok := e.ReadContext(ctx); ok { |
| t.Fatal("unexpectedly got a packet after finishing router solicitations") |
| } |
| }) |
| } |
| } |