// 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" "encoding/binary" "fmt" "math/rand" "testing" "time" "github.com/google/go-cmp/cmp" cryptorand "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/testutil" "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" "gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/waiter" ) var ( addr1 = testutil.MustParse6("a00::1") addr2 = testutil.MustParse6("a00::2") addr3 = testutil.MustParse6("a00::3") ) const ( 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 ) 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 ndpOffLinkRouteEvent struct { nicID tcpip.NICID subnet tcpip.Subnet router tcpip.Address prf header.NDPRoutePreference // true if route was updated, false if invalidated. updated 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 } func (e ndpAutoGenAddrEvent) String() string { return fmt.Sprintf("%T{nicID=%d addr=%s eventType=%d}", e, e.nicID, e.addr, e.eventType) } 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 offLinkRouteC chan ndpOffLinkRouteEvent prefixC chan ndpPrefixEvent 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.OnOffLinkRouteUpdated. func (n *ndpDispatcher) OnOffLinkRouteUpdated(nicID tcpip.NICID, subnet tcpip.Subnet, router tcpip.Address, prf header.NDPRoutePreference) { if c := n.offLinkRouteC; c != nil { c <- ndpOffLinkRouteEvent{ nicID, subnet, router, prf, true, } } } // Implements ipv6.NDPDispatcher.OnOffLinkRouteInvalidated. func (n *ndpDispatcher) OnOffLinkRouteInvalidated(nicID tcpip.NICID, subnet tcpip.Subnet, router tcpip.Address) { if c := n.offLinkRouteC; c != nil { var prf header.NDPRoutePreference c <- ndpOffLinkRouteEvent{ nicID, subnet, router, prf, false, } } } // Implements ipv6.NDPDispatcher.OnOnLinkPrefixDiscovered. func (n *ndpDispatcher) OnOnLinkPrefixDiscovered(nicID tcpip.NICID, prefix tcpip.Subnet) { if c := n.prefixC; c != nil { c <- ndpPrefixEvent{ nicID, prefix, true, } } } // 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) { if c := n.autoGenAddrC; c != nil { c <- ndpAutoGenAddrEvent{ nicID, addr, newAddr, } } } 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, } protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix, } if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, 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) } protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: tcpip.AddressWithPrefix{ Address: addr1, PrefixLen: defaultPrefixLen, }, } if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, 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 { t.Run(test.name, func(t *testing.T) { ndpDisp := ndpDispatcher{ dadC: make(chan ndpDADEvent, 1), } 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) clock := faketime.NewManualClock() s := stack.New(stack.Options{ Clock: clock, RandSource: rand.NewSource(time.Now().UnixNano()), 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, } protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix, } if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID, protocolAddr, err) } // Make sure the address does not resolve before the resolution time has // passed. const delta = time.Nanosecond clock.Advance(test.expectedRetransmitTimer*time.Duration(test.dupAddrDetectTransmits) - delta) 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. clock.Advance(delta) 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.Fatalf("expected DAD event for %s on NIC(%d)", addr1, nicID) } 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, ok := e.Read() if !ok { t.Fatal("packet didn't arrive") } // 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) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPDisp: &ndpDisp, DADConfigs: dadConfigs, })}, Clock: clock, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr1.WithPrefix(), } if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, 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. clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer) select { 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) } default: // 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") } 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.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, 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) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPDisp: &ndpDisp, DADConfigs: dadConfigs, })}, Clock: clock, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _): %s", nicID, err) } protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr1.WithPrefix(), } if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, 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). clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer) select { case e := <-ndpDisp.dadC: if diff := checkDADEvent(e, nicID, addr1, &stack.DADAborted{}); diff != "" { t.Errorf("DAD event mismatch (-want +got):\n%s", diff) } default: // 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") } 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) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPDisp: &ndpDisp, })}, Clock: clock, }) 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} protocolAddr1 := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix1, } if err := s.AddProtocolAddress(nicID1, protocolAddr1, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID1, protocolAddr1, err) } addrWithPrefix2 := tcpip.AddressWithPrefix{Address: addr2, PrefixLen: defaultPrefixLen} protocolAddr2 := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix2, } if err := s.AddProtocolAddress(nicID2, protocolAddr2, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID2, protocolAddr2, err) } expectDADEvent(nicID2, addr2) addrWithPrefix3 := tcpip.AddressWithPrefix{Address: addr3, PrefixLen: defaultPrefixLen} protocolAddr3 := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addrWithPrefix3, } if err := s.AddProtocolAddress(nicID3, protocolAddr3, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}) = %s", nicID3, protocolAddr3, 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 before resolution to make sure the address didn't // resolve on NIC(1) yet. const delta = 1 clock.Advance(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. clock.Advance(delta) select { case e := <-ndpDisp.dadC: if diff := checkDADEvent(e, nicID1, addr1, &stack.DADSucceeded{}); diff != "" { t.Errorf("DAD event mismatch (-want +got):\n%s", diff) } default: t.Fatal("timed out waiting for DAD resolution") } if err := checkGetMainNICAddress(s, nicID1, header.IPv6ProtocolNumber, addrWithPrefix1); err != nil { t.Fatal(err) } }) } } // raBuf returns a valid NDP Router Advertisement with options, router // preference and DHCPv6 configurations specified. func raBuf(ip tcpip.Address, rl uint16, managedAddress, otherConfigurations bool, prf header.NDPRoutePreference, optSer header.NDPOptionsSerializer) *stack.PacketBuffer { const flagsByte = 1 const routerLifetimeOffset = 2 icmpSize := header.ICMPv6HeaderSize + header.NDPRAMinimumSize + 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[routerLifetimeOffset:], rl) // Populate the Managed Address flag field. if managedAddress { // The Managed Addresses flag field is the 7th bit of the flags byte. raPayload[flagsByte] |= 1 << 7 } // Populate the Other Configurations flag field. if otherConfigurations { // The Other Configurations flag field is the 6th bit of the flags byte. raPayload[flagsByte] |= 1 << 6 } // The Prf field is held in the flags byte. raPayload[flagsByte] |= byte(prf) << 3 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 raBuf(ip, rl, false /* managedAddress */, false /* otherConfigurations */, 0 /* prf */, 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, otherConfigurations bool) *stack.PacketBuffer { return raBuf(ip, 0, managedAddresses, otherConfigurations, 0 /* prf */, 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 raBufSimple(ip tcpip.Address, rl uint16) *stack.PacketBuffer { return raBufWithOpts(ip, rl, header.NDPOptionsSerializer{}) } // raBufWithPrf returns a valid NDP Router Advertisement with a preference. // // Note, raBufWithPrf does not populate any of the RA fields other than the // Router Lifetime and Default Router Preference fields. func raBufWithPrf(ip tcpip.Address, rl uint16, prf header.NDPRoutePreference) *stack.PacketBuffer { return raBuf(ip, rl, false /* managedAddress */, false /* otherConfigurations */, prf, 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[:]), }) } // raBufWithRIO returns a valid NDP Router Advertisement with a single Route // Information option. // // All fields in the RA will be zero except the RIO option. func raBufWithRIO(t *testing.T, ip tcpip.Address, prefix tcpip.AddressWithPrefix, lifetimeSeconds uint32, prf header.NDPRoutePreference) *stack.PacketBuffer { // buf will hold the route information option after the Type and Length // fields. // // 2.3. Route Information Option // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Type | Length | Prefix Length |Resvd|Prf|Resvd| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Route Lifetime | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Prefix (Variable Length) | // . . // . . // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ var buf [22]byte buf[0] = uint8(prefix.PrefixLen) buf[1] = byte(prf) << 3 binary.BigEndian.PutUint32(buf[2:], lifetimeSeconds) if n := copy(buf[6:], prefix.Address); n != len(prefix.Address) { t.Fatalf("got copy(...) = %d, want = %d", n, len(prefix.Address)) } return raBufWithOpts(ip, 0 /* router lifetime */, header.NDPOptionsSerializer{ header.NDPRouteInformation(buf[:]), }) } func TestDynamicConfigurationsDisabled(t *testing.T) { const ( nicID = 1 maxRtrSolicitDelay = time.Second ) prefix := tcpip.AddressWithPrefix{ Address: testutil.MustParse6("102:304:506:708::"), PrefixLen: 64, } tests := []struct { name string config func(bool) ipv6.NDPConfigurations ra *stack.PacketBuffer }{ { name: "No Router Discovery", config: func(enable bool) ipv6.NDPConfigurations { return ipv6.NDPConfigurations{DiscoverDefaultRouters: enable} }, ra: raBufSimple(llAddr2, 1000), }, { name: "No Prefix Discovery", config: func(enable bool) ipv6.NDPConfigurations { return ipv6.NDPConfigurations{DiscoverOnLinkPrefixes: enable} }, ra: raBufWithPI(llAddr2, 0, prefix, true, false, 10, 0), }, { name: "No Autogenerate Addresses", config: func(enable bool) ipv6.NDPConfigurations { return ipv6.NDPConfigurations{AutoGenGlobalAddresses: enable} }, ra: raBufWithPI(llAddr2, 0, prefix, false, true, 10, 0), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { // Being configured to discover routers/prefixes or auto-generate // addresses means RAs must be handled, and router/prefix discovery or // SLAAC must be enabled. // // This tests all possible combinations of the configurations where // router/prefix discovery or SLAAC are disabled. for i := 0; i < 7; i++ { handle := ipv6.HandlingRAsDisabled if i&1 != 0 { handle = ipv6.HandlingRAsEnabledWhenForwardingDisabled } enable := i&2 != 0 forwarding := i&4 == 0 t.Run(fmt.Sprintf("HandleRAs(%s), Forwarding(%t), Enabled(%t)", handle, forwarding, enable), func(t *testing.T) { ndpDisp := ndpDispatcher{ offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1), prefixC: make(chan ndpPrefixEvent, 1), autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), } ndpConfigs := test.config(enable) ndpConfigs.HandleRAs = handle ndpConfigs.MaxRtrSolicitations = 1 ndpConfigs.RtrSolicitationInterval = maxRtrSolicitDelay ndpConfigs.MaxRtrSolicitationDelay = maxRtrSolicitDelay clock := faketime.NewManualClock() s := stack.New(stack.Options{ Clock: clock, NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ndpConfigs, NDPDisp: &ndpDisp, })}, }) if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil { t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err) } e := channel.New(1, 1280, linkAddr1) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _): %s", nicID, err) } handleRAsDisabled := handle == ipv6.HandlingRAsDisabled || forwarding ep, err := s.GetNetworkEndpoint(nicID, ipv6.ProtocolNumber) if err != nil { t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, ipv6.ProtocolNumber, err) } stats := ep.Stats() v6Stats, ok := stats.(*ipv6.Stats) if !ok { t.Fatalf("got v6Stats = %T, expected = %T", stats, v6Stats) } // Make sure that when handling RAs are enabled, we solicit routers. clock.Advance(maxRtrSolicitDelay) if got, want := v6Stats.ICMP.PacketsSent.RouterSolicit.Value(), boolToUint64(!handleRAsDisabled); got != want { t.Errorf("got v6Stats.ICMP.PacketsSent.RouterSolicit.Value() = %d, want = %d", got, want) } if handleRAsDisabled { if p, ok := e.Read(); ok { t.Errorf("unexpectedly got a packet = %#v", p) } } else if p, ok := e.Read(); !ok { t.Error("expected router solicitation packet") } else if p.Proto != header.IPv6ProtocolNumber { t.Errorf("got Proto = %d, want = %d", p.Proto, header.IPv6ProtocolNumber) } else { if want := header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllRoutersLinkLocalMulticastAddress); 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(header.IPv6Any), checker.DstAddr(header.IPv6AllRoutersLinkLocalMulticastAddress), checker.TTL(header.NDPHopLimit), checker.NDPRS(checker.NDPRSOptions(nil)), ) } // Make sure we do not discover any routers or prefixes, or perform // SLAAC on reception of an RA. e.InjectInbound(header.IPv6ProtocolNumber, test.ra.Clone()) // Make sure that the unhandled RA stat is only incremented when // handling RAs is disabled. if got, want := v6Stats.UnhandledRouterAdvertisements.Value(), boolToUint64(handleRAsDisabled); got != want { t.Errorf("got v6Stats.UnhandledRouterAdvertisements.Value() = %d, want = %d", got, want) } select { case e := <-ndpDisp.offLinkRouteC: t.Errorf("unexpectedly updated an off-link route when configured not to: %#v", e) default: } select { case e := <-ndpDisp.prefixC: t.Errorf("unexpectedly discovered a prefix when configured not to: %#v", e) default: } select { case e := <-ndpDisp.autoGenAddrC: t.Errorf("unexpectedly auto-generated an address when configured not to: %#v", e) default: } }) } }) } } func boolToUint64(v bool) uint64 { if v { return 1 } return 0 } func checkOffLinkRouteEvent(e ndpOffLinkRouteEvent, nicID tcpip.NICID, subnet tcpip.Subnet, router tcpip.Address, prf header.NDPRoutePreference, updated bool) string { return cmp.Diff(ndpOffLinkRouteEvent{nicID: nicID, subnet: subnet, router: router, prf: prf, updated: updated}, e, cmp.AllowUnexported(e)) } func testWithRAs(t *testing.T, f func(*testing.T, ipv6.HandleRAsConfiguration, bool)) { tests := [...]struct { name string handleRAs ipv6.HandleRAsConfiguration forwarding bool }{ { name: "Handle RAs when forwarding disabled", handleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, forwarding: false, }, { name: "Always Handle RAs with forwarding disabled", handleRAs: ipv6.HandlingRAsAlwaysEnabled, forwarding: false, }, { name: "Always Handle RAs with forwarding enabled", handleRAs: ipv6.HandlingRAsAlwaysEnabled, forwarding: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { f(t, test.handleRAs, test.forwarding) }) } } func TestOffLinkRouteDiscovery(t *testing.T) { const nicID = 1 moreSpecificPrefix := tcpip.AddressWithPrefix{Address: testutil.MustParse6("a00::"), PrefixLen: 16} tests := []struct { name string discoverDefaultRouters bool discoverMoreSpecificRoutes bool dest tcpip.Subnet ra func(*testing.T, tcpip.Address, uint16, header.NDPRoutePreference) *stack.PacketBuffer }{ { name: "Default router discovery", discoverDefaultRouters: true, discoverMoreSpecificRoutes: false, dest: header.IPv6EmptySubnet, ra: func(_ *testing.T, router tcpip.Address, lifetimeSeconds uint16, prf header.NDPRoutePreference) *stack.PacketBuffer { return raBufWithPrf(router, lifetimeSeconds, prf) }, }, { name: "More-specific route discovery", discoverDefaultRouters: false, discoverMoreSpecificRoutes: true, dest: moreSpecificPrefix.Subnet(), ra: func(t *testing.T, router tcpip.Address, lifetimeSeconds uint16, prf header.NDPRoutePreference) *stack.PacketBuffer { return raBufWithRIO(t, router, moreSpecificPrefix, uint32(lifetimeSeconds), prf) }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { testWithRAs(t, func(t *testing.T, handleRAs ipv6.HandleRAsConfiguration, forwarding bool) { ndpDisp := ndpDispatcher{ offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: handleRAs, DiscoverDefaultRouters: test.discoverDefaultRouters, DiscoverMoreSpecificRoutes: test.discoverMoreSpecificRoutes, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) expectOffLinkRouteEvent := func(addr tcpip.Address, prf header.NDPRoutePreference, updated bool) { t.Helper() select { case e := <-ndpDisp.offLinkRouteC: if diff := checkOffLinkRouteEvent(e, nicID, test.dest, addr, prf, updated); diff != "" { t.Errorf("off-link route event mismatch (-want +got):\n%s", diff) } default: t.Fatal("expected router discovery event") } } expectAsyncOffLinkRouteInvalidationEvent := func(addr tcpip.Address, timeout time.Duration) { t.Helper() clock.Advance(timeout) select { case e := <-ndpDisp.offLinkRouteC: var prf header.NDPRoutePreference if diff := checkOffLinkRouteEvent(e, nicID, test.dest, addr, prf, false); diff != "" { t.Errorf("off-link route event mismatch (-want +got):\n%s", diff) } default: t.Fatal("timed out waiting for router discovery event") } } if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil { t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err) } if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _): %s", nicID, err) } // Rx an RA from lladdr2 with zero lifetime. It should not be // remembered. e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 0, header.MediumRoutePreference)) select { case <-ndpDisp.offLinkRouteC: t.Fatal("unexpectedly updated an off-link route with 0 lifetime") default: } // Discover an off-link route through llAddr2. e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 1000, header.ReservedRoutePreference)) if test.discoverMoreSpecificRoutes { // The reserved value is considered invalid with more-specific route // discovery so we inject the same packet but with the default // (medium) preference value. select { case <-ndpDisp.offLinkRouteC: t.Fatal("unexpectedly updated an off-link route with a reserved preference value") default: } e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 1000, header.MediumRoutePreference)) } expectOffLinkRouteEvent(llAddr2, header.MediumRoutePreference, true) // Rx an RA from another router (lladdr3) with non-zero lifetime and // non-default preference value. const l3LifetimeSeconds = 6 e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr3, l3LifetimeSeconds, header.HighRoutePreference)) expectOffLinkRouteEvent(llAddr3, header.HighRoutePreference, true) // Rx an RA from lladdr2 with lesser lifetime and default (medium) // preference value. const l2LifetimeSeconds = 2 e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, l2LifetimeSeconds, header.MediumRoutePreference)) select { case <-ndpDisp.offLinkRouteC: t.Fatal("should not receive a off-link route event when updating lifetimes for known routers") default: } // Rx an RA from lladdr2 with a different preference. e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, l2LifetimeSeconds, header.LowRoutePreference)) expectOffLinkRouteEvent(llAddr2, header.LowRoutePreference, true) // 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. expectAsyncOffLinkRouteInvalidationEvent(llAddr2, l2LifetimeSeconds*time.Second) // Rx an RA from lladdr2 with huge lifetime. e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 1000, header.MediumRoutePreference)) expectOffLinkRouteEvent(llAddr2, header.MediumRoutePreference, true) // Rx an RA from lladdr2 with zero lifetime. It should be invalidated. e.InjectInbound(header.IPv6ProtocolNumber, test.ra(t, llAddr2, 0, header.MediumRoutePreference)) expectOffLinkRouteEvent(llAddr2, header.MediumRoutePreference, 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. expectAsyncOffLinkRouteInvalidationEvent(llAddr3, l3LifetimeSeconds*time.Second) }) }) } } // TestRouterDiscoveryMaxRouters tests that only // ipv6.MaxDiscoveredOffLinkRoutes discovered routers are remembered. func TestRouterDiscoveryMaxRouters(t *testing.T) { const nicID = 1 ndpDisp := ndpDispatcher{ offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1), } e := channel.New(0, 1280, linkAddr1) s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, DiscoverDefaultRouters: true, }, NDPDisp: &ndpDisp, })}, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _): %s", nicID, err) } // Receive an RA from 2 more than the max number of discovered routers. for i := 1; i <= ipv6.MaxDiscoveredOffLinkRoutes+2; i++ { linkAddr := []byte{2, 2, 3, 4, 5, 0} linkAddr[5] = byte(i) llAddr := header.LinkLocalAddr(tcpip.LinkAddress(linkAddr)) e.InjectInbound(header.IPv6ProtocolNumber, raBufSimple(llAddr, 5)) if i <= ipv6.MaxDiscoveredOffLinkRoutes { select { case e := <-ndpDisp.offLinkRouteC: if diff := checkOffLinkRouteEvent(e, nicID, header.IPv6EmptySubnet, llAddr, header.MediumRoutePreference, true); diff != "" { t.Errorf("off-link route event mismatch (-want +got):\n%s", diff) } default: t.Fatal("expected router discovery event") } } else { select { case <-ndpDisp.offLinkRouteC: t.Fatal("should not have discovered a new router after we already discovered the max number of routers") 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)) } func TestPrefixDiscovery(t *testing.T) { prefix1, subnet1, _ := prefixSubnetAddr(0, "") prefix2, subnet2, _ := prefixSubnetAddr(1, "") prefix3, subnet3, _ := prefixSubnetAddr(2, "") testWithRAs(t, func(t *testing.T, handleRAs ipv6.HandleRAsConfiguration, forwarding bool) { ndpDisp := ndpDispatcher{ prefixC: make(chan ndpPrefixEvent, 1), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: handleRAs, DiscoverOnLinkPrefixes: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) 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") } } if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil { t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err) } // 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. clock.Advance(time.Duration(lifetime) * time.Second) select { case e := <-ndpDisp.prefixC: if diff := checkPrefixEvent(e, subnet2, false); diff != "" { t.Errorf("prefix event mismatch (-want +got):\n%s", diff) } default: 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) { prefix := tcpip.AddressWithPrefix{ Address: testutil.MustParse6("102:304:506:708::"), PrefixLen: 64, } subnet := prefix.Subnet() ndpDisp := ndpDispatcher{ prefixC: make(chan ndpPrefixEvent, 1), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, DiscoverOnLinkPrefixes: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) 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, infiniteLifetimeSeconds, 0)) expectPrefixEvent(subnet, true) clock.Advance(header.NDPInfiniteLifetime) select { case <-ndpDisp.prefixC: t.Fatal("unexpectedly invalidated a prefix with infinite lifetime") default: } // Receive an RA with finite lifetime. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, false, infiniteLifetimeSeconds-1, 0)) clock.Advance(header.NDPInfiniteLifetime - time.Second) select { case e := <-ndpDisp.prefixC: if diff := checkPrefixEvent(e, subnet, false); diff != "" { t.Errorf("prefix event mismatch (-want +got):\n%s", diff) } default: 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, infiniteLifetimeSeconds-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, infiniteLifetimeSeconds, 0)) clock.Advance(header.NDPInfiniteLifetime) select { case <-ndpDisp.prefixC: t.Fatal("unexpectedly invalidated a prefix with infinite lifetime") default: } // 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), } e := channel.New(0, 1280, linkAddr1) s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, 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) } // 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)) } const minVLSeconds = uint32(ipv6.MinPrefixInformationValidLifetimeForUpdate / time.Second) const infiniteLifetimeSeconds = uint32(header.NDPInfiniteLifetime / time.Second) // TestAutoGenAddr tests that an address is properly generated and invalidated // when configured to do so. func TestAutoGenAddr(t *testing.T) { prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1) prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1) testWithRAs(t, func(t *testing.T, handleRAs ipv6.HandleRAsConfiguration, forwarding bool) { ndpDisp := ndpDispatcher{ autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: handleRAs, AutoGenGlobalAddresses: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil { t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err) } 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 with a valid lifetime that exceeds // the minimum. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, minVLSeconds+1, 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, 100, 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. clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate) select { case e := <-ndpDisp.autoGenAddrC: if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" { t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) } default: 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 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, }, } for i, test := range tests { t.Run(test.name, func(t *testing.T) { 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) clock := faketime.NewManualClock() 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: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, AutoGenTempGlobalAddresses: true, MaxTempAddrValidLifetime: 2 * ipv6.MinPrefixInformationValidLifetimeForUpdate, MaxTempAddrPreferredLifetime: 2 * ipv6.MinPrefixInformationValidLifetimeForUpdate, }, NDPDisp: &ndpDisp, TempIIDSeed: seed, })}, Clock: clock, }) 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() clock.RunImmediatelyScheduledJobs() 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("timed out waiting for addr auto gen event") } } expectDADEventAsync := func(addr tcpip.Address) { t.Helper() clock.Advance(time.Duration(test.dupAddrTransmits) * test.retransmitTimer) 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.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 with a valid lifetime that exceeds // the minimum and won't be reached in this test. tempAddr2 := newTempAddr(addr2.Address) e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix2, true, true, 2*minVLSeconds, 2*minVLSeconds)) 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, minVLSeconds, 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. clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate) 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) } default: t.Fatal("timed out waiting for addr auto gen event") } default: 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 tests := []struct { name string dupAddrTransmits uint8 retransmitTimer time.Duration }{ { name: "DAD disabled", }, { name: "DAD enabled", dupAddrTransmits: 1, retransmitTimer: time.Second, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ndpDisp := ndpDispatcher{ dadC: make(chan ndpDADEvent, 1), autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ AutoGenTempGlobalAddresses: true, }, NDPDisp: &ndpDisp, AutoGenLinkLocal: true, })}, Clock: clock, }) 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") } clock.Advance(time.Duration(test.dupAddrTransmits) * test.retransmitTimer) select { case e := <-ndpDisp.dadC: if diff := checkDADEvent(e, nicID, llAddr1, &stack.DADSucceeded{}); diff != "" { t.Errorf("DAD event mismatch (-want +got):\n%s", diff) } default: 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) default: } }) } } // 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 ) 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) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ DADConfigs: stack.DADConfigurations{ DupAddrDetectTransmits: dadTransmits, RetransmitTimer: retransmitTimer, }, NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, AutoGenTempGlobalAddresses: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) 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. clock.Advance(dadTransmits * retransmitTimer) 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) } default: 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) } default: 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 regenAdv = 2 * time.Second numTempAddrs = 3 maxTempAddrValidLifetime = numTempAddrs * ipv6.MinPrefixInformationValidLifetimeForUpdate ) prefix, _, addr := prefixSubnetAddr(0, linkAddr1) var tempIIDHistory [header.IIDSize]byte header.InitialTempIID(tempIIDHistory[:], nil, nicID) var tempAddrs [numTempAddrs]tcpip.AddressWithPrefix for i := 0; i < len(tempAddrs); i++ { tempAddrs[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) } ndpDisp := ndpDispatcher{ autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), } e := channel.New(0, 1280, linkAddr1) ndpConfigs := ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, AutoGenTempGlobalAddresses: true, RegenAdvanceDuration: regenAdv, MaxTempAddrValidLifetime: maxTempAddrValidLifetime, MaxTempAddrPreferredLifetime: ipv6.MinPrefixInformationValidLifetimeForUpdate, } clock := faketime.NewManualClock() randSource := savingRandSource{ s: rand.NewSource(time.Now().UnixNano()), } s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ndpConfigs, NDPDisp: &ndpDisp, })}, Clock: clock, RandSource: &randSource, }) 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() clock.Advance(timeout) 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("timed out waiting for addr auto gen event") } } tempDesyncFactor := time.Duration(randSource.lastInt63) % ipv6.MaxDesyncFactor effectiveMaxTempAddrPL := ipv6.MinPrefixInformationValidLifetimeForUpdate - tempDesyncFactor // The time since the last regeneration before a new temporary address is // generated. tempAddrRegenenerationTime := effectiveMaxTempAddrPL - regenAdv // 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, minVLSeconds, minVLSeconds)) expectAutoGenAddrEvent(addr, newAddr) expectAutoGenAddrEvent(tempAddrs[0], newAddr) if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0]}, nil); mismatch != "" { t.Fatal(mismatch) } // Wait for regeneration expectAutoGenAddrEventAsync(tempAddrs[1], newAddr, tempAddrRegenenerationTime) e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, minVLSeconds)) if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0], tempAddrs[1]}, nil); mismatch != "" { t.Fatal(mismatch) } expectAutoGenAddrEventAsync(tempAddrs[0], deprecatedAddr, regenAdv) // Wait for regeneration expectAutoGenAddrEventAsync(tempAddrs[2], newAddr, tempAddrRegenenerationTime-regenAdv) expectAutoGenAddrEventAsync(tempAddrs[1], deprecatedAddr, regenAdv) // 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) } // Refresh lifetimes and wait for the last temporary address to be deprecated. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, minVLSeconds, minVLSeconds)) expectAutoGenAddrEventAsync(tempAddrs[2], deprecatedAddr, effectiveMaxTempAddrPL-regenAdv) // Refresh lifetimes such that the prefix is valid and preferred forever. // // This should not affect the lifetimes of temporary addresses because they // are capped by the maximum valid and preferred lifetimes for temporary // addresses. e.InjectInbound(header.IPv6ProtocolNumber, raBufWithPI(llAddr2, 0, prefix, true, true, infiniteLifetimeSeconds, infiniteLifetimeSeconds)) // Wait for all the temporary addresses to get invalidated. invalidateAfter := maxTempAddrValidLifetime - clock.NowMonotonic().Sub(tcpip.MonotonicTime{}) for _, addr := range tempAddrs { expectAutoGenAddrEventAsync(addr, invalidatedAddr, invalidateAfter) invalidateAfter = tempAddrRegenenerationTime } if mismatch := addressCheck(s.NICInfo()[nicID].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 regenAdv = 2 * time.Second numTempAddrs = 3 maxTempAddrPreferredLifetime = ipv6.MinPrefixInformationValidLifetimeForUpdate maxTempAddrPreferredLifetimeSeconds = uint32(maxTempAddrPreferredLifetime / time.Second) ) prefix, _, addr := prefixSubnetAddr(0, linkAddr1) var tempIIDHistory [header.IIDSize]byte header.InitialTempIID(tempIIDHistory[:], nil, nicID) var tempAddrs [numTempAddrs]tcpip.AddressWithPrefix for i := 0; i < len(tempAddrs); i++ { tempAddrs[i] = header.GenerateTempIPv6SLAACAddr(tempIIDHistory[:], addr.Address) } ndpDisp := ndpDispatcher{ autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), } e := channel.New(0, 1280, linkAddr1) ndpConfigs := ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, AutoGenTempGlobalAddresses: true, RegenAdvanceDuration: regenAdv, MaxTempAddrPreferredLifetime: maxTempAddrPreferredLifetime, MaxTempAddrValidLifetime: maxTempAddrPreferredLifetime * 2, } clock := faketime.NewManualClock() initialTime := clock.NowMonotonic() randSource := savingRandSource{ s: rand.NewSource(time.Now().UnixNano()), } s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ndpConfigs, NDPDisp: &ndpDisp, })}, Clock: clock, RandSource: &randSource, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } tempDesyncFactor := time.Duration(randSource.lastInt63) % ipv6.MaxDesyncFactor 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() clock.Advance(timeout) 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("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, maxTempAddrPreferredLifetimeSeconds, maxTempAddrPreferredLifetimeSeconds)) expectAutoGenAddrEvent(addr, newAddr) expectAutoGenAddrEvent(tempAddrs[0], newAddr) if mismatch := addressCheck(s.NICInfo()[nicID].ProtocolAddresses, []tcpip.AddressWithPrefix{addr, tempAddrs[0]}, 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, maxTempAddrPreferredLifetimeSeconds, 0)) expectAutoGenAddrEvent(addr, deprecatedAddr) expectAutoGenAddrEvent(tempAddrs[0], deprecatedAddr) select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpected auto gen addr event = %#v", e) default: } effectiveMaxTempAddrPL := maxTempAddrPreferredLifetime - tempDesyncFactor // The time since the last regeneration before a new temporary address is // generated. tempAddrRegenenerationTime := effectiveMaxTempAddrPL - regenAdv // Advance the clock by the regeneration time but don't expect a new temporary // address as the prefix is deprecated. clock.Advance(tempAddrRegenenerationTime) select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpected auto gen addr event = %#v", e) default: } // 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, maxTempAddrPreferredLifetimeSeconds, maxTempAddrPreferredLifetimeSeconds)) expectAutoGenAddrEvent(tempAddrs[1], newAddr) // Wait for the first temporary address to be deprecated. expectAutoGenAddrEventAsync(tempAddrs[0], deprecatedAddr, regenAdv) select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpected auto gen addr event = %s", e) default: } // 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. const largeLifetimeSeconds = minVLSeconds * 2 const largeLifetime = time.Duration(largeLifetimeSeconds) * time.Second ndpConfigs.MaxTempAddrValidLifetime = 2 * largeLifetime ndpConfigs.MaxTempAddrPreferredLifetime = largeLifetime 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, largeLifetimeSeconds, largeLifetimeSeconds)) timeSinceInitialTime := clock.NowMonotonic().Sub(initialTime) clock.Advance(largeLifetime - timeSinceInitialTime) expectAutoGenAddrEvent(tempAddrs[0], deprecatedAddr) // to offset the advement of time to test the first temporary address's // deprecation after the second was generated advLess := regenAdv expectAutoGenAddrEventAsync(tempAddrs[2], newAddr, timeSinceInitialTime-advLess-(tempDesyncFactor+regenAdv)) expectAutoGenAddrEventAsync(tempAddrs[1], deprecatedAddr, regenAdv) select { case e := <-ndpDisp.autoGenAddrC: t.Fatalf("unexpected auto gen addr event = %+v", e) default: } } // 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: ipv6.HandlingRAsEnabledWhenForwardingDisabled, 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 } protocolAddr := tcpip.ProtocolAddress{ Protocol: ipv6.ProtocolNumber, AddressWithPrefix: test.addrs[j].Address.WithPrefix(), } if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, 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, *faketime.ManualClock) { t.Helper() ndpDisp := &ndpDispatcher{ autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), } e := channel.New(0, 1280, linkAddr1) e.LinkEPCapabilities |= stack.CapabilityResolutionRequired clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, }, NDPDisp: ndpDisp, })}, TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol}, Clock: clock, }) 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, clock } // 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.ReadableEvents) 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.ReadableEvents) 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 prefix1, _, addr1 := prefixSubnetAddr(0, linkAddr1) prefix2, _, addr2 := prefixSubnetAddr(1, linkAddr1) ndpDisp, e, s, clock := 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() clock.Advance(timeout) 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("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, infiniteLifetimeSeconds, infiniteLifetimeSeconds)) 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, minVLSeconds, minVLSeconds-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, ipv6.MinPrefixInformationValidLifetimeForUpdate-time.Second) 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, minVLSeconds, 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, minVLSeconds, minVLSeconds-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, ipv6.MinPrefixInformationValidLifetimeForUpdate-time.Second) 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) 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, minVLSeconds, minVLSeconds)) 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). // // Since we're about to cause both events to fire, we need the dispatcher // channel to be able to hold both. if got, want := len(ndpDisp.autoGenAddrC), 0; got != want { t.Fatalf("got len(ndpDisp.autoGenAddrC) = %d, want %d", got, want) } if got, want := cap(ndpDisp.autoGenAddrC), 1; got != want { t.Fatalf("got cap(ndpDisp.autoGenAddrC) = %d, want %d", got, want) } ndpDisp.autoGenAddrC = make(chan ndpAutoGenAddrEvent, 2) clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate) 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) } default: 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") default: } } else { t.Fatalf("got unexpected auto-generated event") } default: 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.ReadableEvents) 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 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, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ndpDisp := ndpDispatcher{ autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) 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)) clock.Advance(ipv6.MinPrefixInformationValidLifetimeForUpdate) select { case e := <-ndpDisp.autoGenAddrC: if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" { t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) } default: 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 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 minVLSeconds but was originally greater than // it. { "LargeVLToVLLessThanMinVLForUpdate", 9999, 1, minVLSeconds, }, { "LargeVLTo0", 9999, 0, minVLSeconds, }, { "InfiniteVLToVLLessThanMinVLForUpdate", infiniteVL, 1, minVLSeconds, }, { "InfiniteVLTo0", infiniteVL, 0, minVLSeconds, }, // Should not update VL if original VL was less than minVLSeconds // and the new VL is also less than minVLSeconds. { "ShouldNotUpdateWhenBothOldAndNewAreLessThanMinVLForUpdate", minVLSeconds - 1, minVLSeconds - 3, minVLSeconds - 1, }, // Should take the new VL if the new VL is greater than the // remaining time or is greater than minVLSeconds. { "MorethanMinVLToLesserButStillMoreThanMinVLForUpdate", minVLSeconds + 5, minVLSeconds + 3, minVLSeconds + 3, }, { "SmallVLToGreaterVLButStillLessThanMinVLForUpdate", minVLSeconds - 3, minVLSeconds - 1, minVLSeconds - 1, }, { "SmallVLToGreaterVLThatIsMoreThaMinVLForUpdate", minVLSeconds - 3, minVLSeconds + 1, minVLSeconds + 1, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { ndpDisp := ndpDispatcher{ autoGenAddrC: make(chan ndpAutoGenAddrEvent, 10), } e := channel.New(10, 1280, linkAddr1) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) 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. const delta = 1 clock.Advance(time.Duration(test.evl)*time.Second - delta) select { case <-ndpDisp.autoGenAddrC: t.Fatal("unexpectedly received an auto gen addr event") default: } // Wait for the invalidation event. clock.Advance(delta) select { case e := <-ndpDisp.autoGenAddrC: if diff := checkAutoGenAddrEvent(e, addr, invalidatedAddr); diff != "" { t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) } default: 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) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) 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. clock.Advance(lifetimeSeconds * time.Second) select { case <-ndpDisp.autoGenAddrC: t.Fatal("unexpectedly received an auto gen addr event") default: } } // 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, } properties := stack.AddressProperties{PEB: stack.FirstPrimaryEndpoint} if err := s.AddProtocolAddress(nicID, protoAddr2, properties); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, %+v) = %s", nicID, protoAddr2, properties, 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) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) 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. protocolAddr := tcpip.ProtocolAddress{Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr} if err := s.AddProtocolAddress(1, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(1, %+v, {}) = %s", protocolAddr, 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. clock.Advance(lifetimeSeconds * time.Second) select { case <-ndpDisp.autoGenAddrC: t.Fatal("unexpectedly received an auto gen addr event") default: } if !containsV6Addr(s.NICInfo()[1].ProtocolAddresses, addr) { t.Fatalf("Should have %s in the list of addresses", addr1) } } func makeSecretKey(t *testing.T) []byte { secretKey := make([]byte, header.OpaqueIIDSecretKeyMinBytes) n, err := cryptorand.Read(secretKey) if err != nil { t.Fatalf("cryptorand.Read(_): %s", err) } if l := len(secretKey); n != l { t.Fatalf("got cryptorand.Read(_) = (%d, nil), want = (%d, nil)", n, l) } return secretKey } // 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" secretKey := makeSecretKey(t) 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) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, }, NDPDisp: &ndpDisp, OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{ NICNameFromID: func(_ tcpip.NICID, nicName string) string { return nicName }, SecretKey: secretKey, }, })}, Clock: clock, }) 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. clock.Advance(validLifetimeSecondPrefix1 * time.Second) select { case e := <-ndpDisp.autoGenAddrC: if diff := checkAutoGenAddrEvent(e, addr1, invalidatedAddr); diff != "" { t.Errorf("auto-gen addr event mismatch (-want +got):\n%s", diff) } default: 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 secretKey := makeSecretKey(t) 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, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, addr tcpip.AddressWithPrefix, eventType ndpAutoGenAddrEventType) { t.Helper() clock.RunImmediatelyScheduledJobs() 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("timed out waiting for addr auto gen event") } } expectDADEvent := func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) { t.Helper() clock.RunImmediatelyScheduledJobs() 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, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, addr tcpip.Address, res stack.DADResult) { t.Helper() clock.Advance(dadTransmits * retransmitTimer) 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("timed out waiting for DAD event") } } stableAddrForTempAddrTest := addrForSubnet(subnet, 0) addrTypes := []struct { name string ndpConfigs ipv6.NDPConfigurations autoGenLinkLocal bool prepareFn func(t *testing.T, clock *faketime.ManualClock, ndpDisp *ndpDispatcher, e *channel.Endpoint, tempIIDHistory []byte) []tcpip.AddressWithPrefix addrGenFn func(dadCounter uint8, tempIIDHistory []byte) tcpip.AddressWithPrefix }{ { name: "Global address", ndpConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, }, prepareFn: func(_ *testing.T, _ *faketime.ManualClock, _ *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, *faketime.ManualClock, *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: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, AutoGenTempGlobalAddresses: true, }, prepareFn: func(t *testing.T, clock *faketime.ManualClock, 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, clock, 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 { 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) { ndpDisp := ndpDispatcher{ dadC: make(chan ndpDADEvent, 1), autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), } e := channel.New(0, 1280, linkAddr1) ndpConfigs := addrType.ndpConfigs ndpConfigs.AutoGenAddressConflictRetries = maxRetries clock := faketime.NewManualClock() 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, }, })}, Clock: clock, }) 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, clock, &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, clock, &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, clock, &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. protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr, } if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err) } if err := s.RemoveAddress(nicID, addr.Address); err != nil { t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr.Address, err) } expectDADEvent(t, clock, &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, clock, &ndpDisp, addr, newAddr) expectDADEventAsync(t, clock, &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) default: } }) } } }) } } // 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: ipv6.HandlingRAsEnabledWhenForwardingDisabled, 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) { ndpDisp := ndpDispatcher{ dadC: make(chan ndpDADEvent, 1), autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() 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, }, })}, Clock: clock, }) 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) default: } }) } } // 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 secretKey := makeSecretKey(t) prefix, subnet, _ := prefixSubnetAddr(0, linkAddr1) ndpDisp := ndpDispatcher{ dadC: make(chan ndpDADEvent, 1), autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2), } e := channel.New(0, 1280, linkAddr1) clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ DADConfigs: stack.DADConfigurations{ DupAddrDetectTransmits: dadTransmits, RetransmitTimer: retransmitTimer, }, NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, AutoGenGlobalAddresses: true, AutoGenAddressConflictRetries: maxRetries, }, NDPDisp: &ndpDisp, OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{ NICNameFromID: func(_ tcpip.NICID, nicName string) string { return nicName }, SecretKey: secretKey, }, })}, Clock: clock, }) 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. clock.Advance(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) clock.Advance(dadTransmits * retransmitTimer) 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) } default: 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. clock.Advance(lifetimeSeconds*time.Second - failureTimer - dadTransmits*retransmitTimer) 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) } default: 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) } } default: 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: ipv6.HandlingRAsEnabledWhenForwardingDisabled, }, 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: ipv6.HandlingRAsEnabledWhenForwardingDisabled, }, 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: } } func TestNoCleanupNDPStateWhenForwardingEnabled(t *testing.T) { const ( lifetimeSeconds = 999 nicID = 1 ) ndpDisp := ndpDispatcher{ offLinkRouteC: make(chan ndpOffLinkRouteEvent, 1), prefixC: make(chan ndpPrefixEvent, 1), autoGenAddrC: make(chan ndpAutoGenAddrEvent, 1), } s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ AutoGenLinkLocal: true, NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, DiscoverDefaultRouters: true, DiscoverOnLinkPrefixes: true, AutoGenGlobalAddresses: true, }, NDPDisp: &ndpDisp, })}, }) e1 := channel.New(0, header.IPv6MinimumMTU, linkAddr1) if err := s.CreateNIC(nicID, e1); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } llAddr := tcpip.AddressWithPrefix{Address: llAddr1, PrefixLen: header.IPv6LinkLocalPrefix.PrefixLen} select { case e := <-ndpDisp.autoGenAddrC: if diff := checkAutoGenAddrEvent(e, llAddr, newAddr); diff != "" { t.Errorf("auto-gen addr mismatch (-want +got):\n%s", diff) } default: t.Errorf("expected auto-gen addr event for %s on NIC(%d)", llAddr, nicID) } prefix, subnet, addr := prefixSubnetAddr(0, linkAddr1) e1.InjectInbound( header.IPv6ProtocolNumber, raBufWithPI( llAddr3, lifetimeSeconds, prefix, true, /* onLink */ true, /* auto */ lifetimeSeconds, lifetimeSeconds, ), ) select { case e := <-ndpDisp.offLinkRouteC: if diff := checkOffLinkRouteEvent(e, nicID, header.IPv6EmptySubnet, llAddr3, header.MediumRoutePreference, true /* discovered */); diff != "" { t.Errorf("off-link route event mismatch (-want +got):\n%s", diff) } default: t.Errorf("expected off-link route event for %s on NIC(%d)", llAddr3, nicID) } select { case e := <-ndpDisp.prefixC: if diff := checkPrefixEvent(e, subnet, true /* discovered */); diff != "" { t.Errorf("off-link route event mismatch (-want +got):\n%s", diff) } default: t.Errorf("expected prefix event for %s on NIC(%d)", prefix, nicID) } select { case e := <-ndpDisp.autoGenAddrC: if diff := checkAutoGenAddrEvent(e, addr, newAddr); diff != "" { t.Errorf("auto-gen addr mismatch (-want +got):\n%s", diff) } default: t.Errorf("expected auto-gen addr event for %s on NIC(%d)", addr, nicID) } // Enabling or disabling forwarding should not invalidate discovered prefixes // or routers, or auto-generated address. for _, forwarding := range [...]bool{true, false} { t.Run(fmt.Sprintf("Transition forwarding to %t", forwarding), func(t *testing.T) { if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, forwarding); err != nil { t.Fatalf("SetForwardingDefaultAndAllNICs(%d, %t): %s", ipv6.ProtocolNumber, forwarding, err) } select { case e := <-ndpDisp.offLinkRouteC: t.Errorf("unexpected off-link route event = %#v", e) default: } select { case e := <-ndpDisp.prefixC: t.Errorf("unexpected prefix event = %#v", e) default: } select { case e := <-ndpDisp.autoGenAddrC: t.Errorf("unexpected auto-gen addr event = %#v", e) default: } }) } } 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 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{ offLinkRouteC: make(chan ndpOffLinkRouteEvent, maxRouterAndPrefixEvents), prefixC: make(chan ndpPrefixEvent, maxRouterAndPrefixEvents), autoGenAddrC: make(chan ndpAutoGenAddrEvent, test.maxAutoGenAddrEvents), } clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ AutoGenLinkLocal: true, NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, DiscoverDefaultRouters: true, DiscoverOnLinkPrefixes: true, AutoGenGlobalAddresses: true, }, NDPDisp: &ndpDisp, })}, Clock: clock, }) expectOffLinkRouteEvent := func() (bool, ndpOffLinkRouteEvent) { select { case e := <-ndpDisp.offLinkRouteC: return true, e default: } return false, ndpOffLinkRouteEvent{} } 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, _ := expectOffLinkRouteEvent(); !ok { t.Errorf("expected off-link route 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, _ := expectOffLinkRouteEvent(); !ok { t.Errorf("expected off-link route 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, _ := expectOffLinkRouteEvent(); !ok { t.Errorf("expected off-link route 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, _ := expectOffLinkRouteEvent(); !ok { t.Errorf("expected off-link route 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. gotOffLinkRouteEvents := make(map[ndpOffLinkRouteEvent]int) for i := 0; i < maxRouterAndPrefixEvents; i++ { ok, e := expectOffLinkRouteEvent() if !ok { t.Errorf("expected %d off-link route events after becoming a router; got = %d", maxRouterAndPrefixEvents, i) break } gotOffLinkRouteEvents[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() } expectedOffLinkRouteEvents := map[ndpOffLinkRouteEvent]int{ {nicID: nicID1, subnet: header.IPv6EmptySubnet, router: llAddr3, updated: false}: 1, {nicID: nicID1, subnet: header.IPv6EmptySubnet, router: llAddr4, updated: false}: 1, {nicID: nicID2, subnet: header.IPv6EmptySubnet, router: llAddr3, updated: false}: 1, {nicID: nicID2, subnet: header.IPv6EmptySubnet, router: llAddr4, updated: false}: 1, } if diff := cmp.Diff(expectedOffLinkRouteEvents, gotOffLinkRouteEvents); diff != "" { t.Errorf("off-link route 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). clock.Advance(lifetimeSeconds * time.Second) select { case <-ndpDisp.offLinkRouteC: t.Error("unexpected off-link route 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), } e := channel.New(0, 1280, linkAddr1) s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, }, 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() } var _ rand.Source = (*savingRandSource)(nil) type savingRandSource struct { s rand.Source lastInt63 int64 } func (d *savingRandSource) Int63() int64 { i := d.s.Int63() d.lastInt63 = i return i } func (d *savingRandSource) Seed(seed int64) { d.s.Seed(seed) } // 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, }, } subTests := []struct { name string handleRAs ipv6.HandleRAsConfiguration afterFirstRS func(*testing.T, *stack.Stack) }{ { name: "Handle RAs when forwarding disabled", handleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, afterFirstRS: func(*testing.T, *stack.Stack) {}, }, // Enabling forwarding when RAs are always configured to be handled // should not stop router solicitations. { name: "Handle RAs always", handleRAs: ipv6.HandlingRAsAlwaysEnabled, afterFirstRS: func(t *testing.T, s *stack.Stack) { if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil { t.Fatalf("SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err) } }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { for _, subTest := range subTests { t.Run(subTest.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.IPv6AllRoutersLinkLocalMulticastAddress); 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.IPv6AllRoutersLinkLocalMulticastAddress), 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) } } randSource := savingRandSource{ s: rand.NewSource(time.Now().UnixNano()), } s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: subTest.handleRAs, MaxRtrSolicitations: test.maxRtrSolicit, RtrSolicitationInterval: test.rtrSolicitInt, MaxRtrSolicitationDelay: test.maxRtrSolicitDelay, }, })}, Clock: clock, RandSource: &randSource, }) opts := stack.NICOptions{Disabled: true} if err := s.CreateNICWithOptions(nicID, &e, opts); err != nil { t.Fatalf("CreateNICWithOptions(%d, _, %#v) = %s", nicID, opts, err) } if addr := test.nicAddr; addr != "" { protocolAddr := tcpip.ProtocolAddress{ Protocol: header.IPv6ProtocolNumber, AddressWithPrefix: addr.WithPrefix(), } if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil { t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err) } } if err := s.EnableNIC(nicID); err != nil { t.Fatalf("EnableNIC(%d): %s", nicID, err) } // Make sure each RS is sent at the right time. remaining := test.maxRtrSolicit if remaining != 0 { maxRtrSolicitDelay := test.maxRtrSolicitDelay if maxRtrSolicitDelay < 0 { maxRtrSolicitDelay = ipv6.DefaultNDPConfigurations().MaxRtrSolicitationDelay } var actualRtrSolicitDelay time.Duration if maxRtrSolicitDelay != 0 { actualRtrSolicitDelay = time.Duration(randSource.lastInt63) % maxRtrSolicitDelay } waitForPkt(actualRtrSolicitDelay) remaining-- } subTest.afterFirstRS(t, s) for ; remaining != 0; remaining-- { if test.effectiveRtrSolicitInt != 0 { waitForNothing(test.effectiveRtrSolicitInt - time.Nanosecond) waitForPkt(time.Nanosecond) } else { waitForPkt(0) } } // 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() if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, false); err != nil { t.Fatalf("SetForwardingDefaultAndAllNICs(%d, false): %s", ipv6.ProtocolNumber, err) } }, stopFn: func(t *testing.T, s *stack.Stack, _ bool) { t.Helper() if err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil { t.Fatalf("SetForwardingDefaultAndAllNICs(%d, true): %s", ipv6.ProtocolNumber, err) } }, }, // 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(clock *faketime.ManualClock, timeout time.Duration) { t.Helper() clock.Advance(timeout) p, ok := e.Read() 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.IPv6AllRoutersLinkLocalMulticastAddress), checker.TTL(header.NDPHopLimit), checker.NDPRS()) } clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ NDPConfigs: ipv6.NDPConfigurations{ HandleRAs: ipv6.HandlingRAsEnabledWhenForwardingDisabled, MaxRtrSolicitations: maxRtrSolicitations, RtrSolicitationInterval: interval, MaxRtrSolicitationDelay: delay, }, })}, Clock: clock, }) if err := s.CreateNIC(nicID, e); err != nil { t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) } // Stop soliciting routers. test.stopFn(t, s, true /* first */) clock.Advance(delay) if _, ok := e.Read(); ok { // A single RS may have been sent before solicitations were stopped. clock.Advance(interval) if _, ok = e.Read(); 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 */) clock.Advance(delay) if _, ok := e.Read(); 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(clock, delay) waitForPkt(clock, interval) waitForPkt(clock, interval) clock.Advance(interval) if _, ok := e.Read(); 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) clock.Advance(interval) if _, ok := e.Read(); ok { t.Fatal("unexpectedly got a packet after finishing router solicitations") } }) } }