blob: c66985644e6c0a13a218a701e355bbf7ded35ed5 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// +build !build_with_native_toolchain
package netstack
import (
"context"
"errors"
"fmt"
"sync"
"time"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/routes"
syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
networking_metrics "networking_metrics_golib"
"fidl/fuchsia/cobalt"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
const (
// staticRouteAvoidingLifeCycleHooks is the dynamic flag when adding a
// new route in response to an NDP discovery event.
//
// routes are added as a 'static' route because the integrator (Netstack)
// removes all dynamic routes on DHCPv4 related changes. Routes must be
// 'static' to escape Netstack's DHCP-learned routes' lifecycle management
// hooks that 'dynamic' routes will be affected by.
//
// TODO(fxbug.dev/43503): Instead of adding routes as static, support a type
// of dynamic route specifically for NDP.
staticRouteAvoidingLifeCycleHooks = false
ndpSyslogTagName = "ndp"
)
// ndpEvent is a marker interface used to improve type safety in ndpDispatcher.
type ndpEvent interface {
isNDPEvent()
}
// ndpRouterAndDADEventCommon holds the common fields for NDP default router
// discovery and invalidation, and Duplicate Address Detection events.
type ndpRouterAndDADEventCommon struct {
nicID tcpip.NICID
addr tcpip.Address
}
// isNDPEvent implements ndpEvent.isNDPEvent.
func (*ndpRouterAndDADEventCommon) isNDPEvent() {}
type ndpDuplicateAddressDetectionEvent struct {
ndpRouterAndDADEventCommon
result stack.DADResult
}
type ndpDiscoveredRouterEvent struct {
ndpRouterAndDADEventCommon
}
type ndpInvalidatedRouterEvent struct {
ndpRouterAndDADEventCommon
}
// ndpPrefixEventCommon holds the common fields for all events related to NDP
// on-link prefix discovery and invalidation.
type ndpPrefixEventCommon struct {
nicID tcpip.NICID
prefix tcpip.Subnet
}
// isNDPEvent implements ndpEvent.isNDPEvent.
func (*ndpPrefixEventCommon) isNDPEvent() {}
type ndpDiscoveredPrefixEvent struct {
ndpPrefixEventCommon
}
type ndpInvalidatedPrefixEvent struct {
ndpPrefixEventCommon
}
// ndpAutoGenAddrEventCommon holds the common fields for all events related to
// auto-generated address events.
type ndpAutoGenAddrEventCommon struct {
nicID tcpip.NICID
addrWithPrefix tcpip.AddressWithPrefix
}
// isNDPEvent implements ndpEvent.isNDPEvent.
func (*ndpAutoGenAddrEventCommon) isNDPEvent() {}
type ndpGeneratedAutoGenAddrEvent struct {
ndpAutoGenAddrEventCommon
}
type ndpInvalidatedAutoGenAddrEvent struct {
ndpAutoGenAddrEventCommon
}
// ndpRecursiveDNSServerEvent holds the fields for an NDP Recursive DNS Server
// list event.
type ndpRecursiveDNSServerEvent struct {
nicID tcpip.NICID
addrs []tcpip.Address
lifetime time.Duration
}
// isNDPEvent implements ndpEvent.isNDPEvent.
func (*ndpRecursiveDNSServerEvent) isNDPEvent() {}
var _ ipv6.NDPDispatcher = (*ndpDispatcher)(nil)
// ndpDispatcher is a type that implements ipv6.NDPDispatcher to handle the
// discovery and invaldiation of default routers, on-link prefixes and
// auto-generated addresses; and reception of recursive DNS server lists for
// IPv6.
//
// ndpDispatcher employs a worker goroutine (see ndpDispatcher.start), that
// will handle the events. The event handlers themselves will add the events to
// a queue to be handled by the goroutine. This is done so that ordering can be
// guaranteed between events that share some relationship (e.g. a router's
// invalidation must happen after its discovery). Without this enforcement, we
// could (in theory) complete handling an invalidation event before its
// respective discovery event (even though we will receive the discovery event
// before the invalidation event (this is enforced by the Stack)).
type ndpDispatcher struct {
// ns MUST be assigned before calling ndpDispatcher.start and it must
// never be modified after being assigned.
ns *Netstack
// Used to ensure that only a finite number of goroutines may be
// permitted to run at a time.
sem chan struct{}
// notifyCh is used to signal the worker goroutine that a new event is
// available.
notifyCh chan struct{}
// testNotifyCh is used to signal tests when events is empty.
//
// testNotifyCh should only be set by tests.
testNotifyCh chan struct{}
// dhcpv6Obs tracks unique DHCPv6 configurations since the last Cobalt pull.
dhcpv6Obs dhcpV6Observation
// dynamicAddressSourceObs tracks the most recent dynamic address
// configuration options available for an interface.
dynamicAddressSourceObs availableDynamicIPv6AddressConfigObservation
mu struct {
sync.Mutex
// events holds a queue of events that need to be handled by the
// worker goroutine. We use a slice instead of a channel so that
// we can guarantee that event handlers do not block trying to
// write to events if it is full.
// TODO(ghanan): use the ilist pkg from gvisor/pkg/ilist
events []ndpEvent
}
}
// OnDuplicateAddressDetectionResult implements ipv6.NDPDispatcher.
func (n *ndpDispatcher) OnDuplicateAddressDetectionResult(nicID tcpip.NICID, addr tcpip.Address, result stack.DADResult) {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnDuplicateAddressDetectionStatus(%d, %s, %#v)", nicID, addr, result)
n.addEvent(&ndpDuplicateAddressDetectionEvent{
ndpRouterAndDADEventCommon: ndpRouterAndDADEventCommon{
nicID: nicID,
addr: addr,
},
result: result,
})
}
// OnDefaultRouterDiscovered implements ipv6.NDPDispatcher.
//
// Adds the event to the event queue and returns true so Stack remembers the
// discovered default router.
func (n *ndpDispatcher) OnDefaultRouterDiscovered(nicID tcpip.NICID, addr tcpip.Address) bool {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnDefaultRouterDiscovered(%d, %s)", nicID, addr)
n.addEvent(&ndpDiscoveredRouterEvent{ndpRouterAndDADEventCommon: ndpRouterAndDADEventCommon{nicID: nicID, addr: addr}})
return true
}
// OnDefaultRouterInvalidated implements ipv6.NDPDispatcher.
func (n *ndpDispatcher) OnDefaultRouterInvalidated(nicID tcpip.NICID, addr tcpip.Address) {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnDefaultRouterInvalidated(%d, %s)", nicID, addr)
n.addEvent(&ndpInvalidatedRouterEvent{ndpRouterAndDADEventCommon: ndpRouterAndDADEventCommon{nicID: nicID, addr: addr}})
}
// OnOnLinkPrefixDiscovered implements ipv6.NDPDispatcher.
//
// Adds the event to the event queue and returns true so Stack remembers the
// discovered on-link prefix.
func (n *ndpDispatcher) OnOnLinkPrefixDiscovered(nicID tcpip.NICID, prefix tcpip.Subnet) bool {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnOnLinkPrefixDiscovered(%d, %s)", nicID, prefix)
n.addEvent(&ndpDiscoveredPrefixEvent{ndpPrefixEventCommon: ndpPrefixEventCommon{nicID: nicID, prefix: prefix}})
return true
}
// OnOnLinkPrefixInvalidated implements ipv6.NDPDispatcher.
func (n *ndpDispatcher) OnOnLinkPrefixInvalidated(nicID tcpip.NICID, prefix tcpip.Subnet) {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnOnLinkPrefixInvalidated(%d, %s)", nicID, prefix)
n.addEvent(&ndpInvalidatedPrefixEvent{ndpPrefixEventCommon: ndpPrefixEventCommon{nicID: nicID, prefix: prefix}})
}
// OnAutoGenAddress implements ipv6.NDPDispatcher.
//
// Adds the event to the event queue and returns true so Stack adds the
// auto-generated address.
func (n *ndpDispatcher) OnAutoGenAddress(nicID tcpip.NICID, addrWithPrefix tcpip.AddressWithPrefix) bool {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnAutoGenAddress(%d, %s)", nicID, addrWithPrefix)
n.addEvent(&ndpGeneratedAutoGenAddrEvent{ndpAutoGenAddrEventCommon: ndpAutoGenAddrEventCommon{nicID: nicID, addrWithPrefix: addrWithPrefix}})
// Metrics only care about dynamic global address configuration options so
// only increase the counter if we generated a global SLAAC address.
if !header.IsV6LinkLocalUnicastAddress(addrWithPrefix.Address) {
n.dynamicAddressSourceObs.incGlobalSLAAC(nicID)
}
return true
}
// OnAutoGenAddressDeprecated implements ipv6.NDPDispatcher.
func (*ndpDispatcher) OnAutoGenAddressDeprecated(tcpip.NICID, tcpip.AddressWithPrefix) {
// No need to do anything with this as deprecated addresses are still usable.
// stack.Stack will handle not returning deprecated addresses if more
// preferred addresses exist.
}
// OnAutoGenAddressInvalidated implements ipv6.NDPDispatcher.
func (n *ndpDispatcher) OnAutoGenAddressInvalidated(nicID tcpip.NICID, addrWithPrefix tcpip.AddressWithPrefix) {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnAutoGenAddressInvalidated(%d, %s)", nicID, addrWithPrefix)
n.addEvent(&ndpInvalidatedAutoGenAddrEvent{ndpAutoGenAddrEventCommon: ndpAutoGenAddrEventCommon{nicID: nicID, addrWithPrefix: addrWithPrefix}})
// Metrics only care about dynamic global address configuration options so
// only decrease the counter if we invalidated a global SLAAC address.
if !header.IsV6LinkLocalUnicastAddress(addrWithPrefix.Address) {
n.dynamicAddressSourceObs.decGlobalSLAAC(nicID)
}
}
// OnRecursiveDNSServerOption implements ipv6.NDPDispatcher.
func (n *ndpDispatcher) OnRecursiveDNSServerOption(nicID tcpip.NICID, addrs []tcpip.Address, lifetime time.Duration) {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnRecursiveDNSServerOption(%d, %s, %s)", nicID, addrs, lifetime)
n.addEvent(&ndpRecursiveDNSServerEvent{nicID: nicID, addrs: addrs, lifetime: lifetime})
}
// OnDNSSearchListOption implements ipv6.NDPDispatcher.
func (n *ndpDispatcher) OnDNSSearchListOption(nicID tcpip.NICID, domainNames []string, lifetime time.Duration) {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnDNSSearchListOption(%d, %s, %s)", nicID, domainNames, lifetime)
}
var _ NICRemovedHandler = (*availableDynamicIPv6AddressConfigObservation)(nil)
var _ cobaltEventProducer = (*availableDynamicIPv6AddressConfigObservation)(nil)
// Using a var so that it can be overriden for tests.
//
// TODO(fxbug.dev/57075): Use a fake clock in tests so we can make these constants.
var (
// availableDynamicIPv6AddressConfigDelayInitialEvents is the initial delay
// before registering with the cobalt client that events are ready.
//
// The delay should be large enough to let the network configurations
// stabilize.
availableDynamicIPv6AddressConfigDelayInitialEvents = 10 * time.Minute
// availableDynamicIPv6AddressConfigDelayBetweenEvents is the delay between
// registering with the cobalt client that events are ready after the initial
// registration.
availableDynamicIPv6AddressConfigDelayBetweenEvents = time.Hour
)
type byNICAvailableDynamicIPv6AddressConfig map[tcpip.NICID]struct {
globalSLAAC uint32
lastDHCPv6 ipv6.DHCPv6ConfigurationFromNDPRA
}
type availableDynamicIPv6AddressConfigObservation struct {
hasEvents func()
mu struct {
sync.Mutex
obs byNICAvailableDynamicIPv6AddressConfig
}
}
func (o *availableDynamicIPv6AddressConfigObservation) initWithoutTimer(hasEvents func()) {
o.hasEvents = hasEvents
o.mu.Lock()
o.mu.obs = make(byNICAvailableDynamicIPv6AddressConfig)
o.mu.Unlock()
}
// init sets the events registration callback and starts the timer to register
// events.
func (o *availableDynamicIPv6AddressConfigObservation) init(hasEvents func()) {
o.initWithoutTimer(hasEvents)
var mu sync.Mutex
var t *time.Timer
mu.Lock()
defer mu.Unlock()
t = time.AfterFunc(availableDynamicIPv6AddressConfigDelayInitialEvents, func() {
o.hasEvents()
mu.Lock()
defer mu.Unlock()
t.Reset(availableDynamicIPv6AddressConfigDelayBetweenEvents)
})
}
func (o *availableDynamicIPv6AddressConfigObservation) RemovedNIC(nicID tcpip.NICID) {
o.mu.Lock()
defer o.mu.Unlock()
delete(o.mu.obs, nicID)
}
func (o *availableDynamicIPv6AddressConfigObservation) incGlobalSLAAC(nicID tcpip.NICID) {
o.mu.Lock()
defer o.mu.Unlock()
nic := o.mu.obs[nicID]
nic.globalSLAAC++
o.mu.obs[nicID] = nic
}
func (o *availableDynamicIPv6AddressConfigObservation) decGlobalSLAAC(nicID tcpip.NICID) {
o.mu.Lock()
defer o.mu.Unlock()
nic := o.mu.obs[nicID]
if nic.globalSLAAC == 0 {
panic(fmt.Sprintf("cannot have a negative globalSLAAC count for nicID = %d", nicID))
}
nic.globalSLAAC--
o.mu.obs[nicID] = nic
}
func (o *availableDynamicIPv6AddressConfigObservation) setLastDHCPv6(nicID tcpip.NICID, v ipv6.DHCPv6ConfigurationFromNDPRA) {
o.mu.Lock()
defer o.mu.Unlock()
nic := o.mu.obs[nicID]
nic.lastDHCPv6 = v
o.mu.obs[nicID] = nic
}
func (o *availableDynamicIPv6AddressConfigObservation) events() []cobalt.CobaltEvent {
o.mu.Lock()
defer o.mu.Unlock()
events := make([]cobalt.CobaltEvent, 0, len(o.mu.obs))
for nicID, nic := range o.mu.obs {
var v uint8
if nic.globalSLAAC != 0 {
v |= 1
}
if nic.lastDHCPv6 == ipv6.DHCPv6ManagedAddress {
v |= 2
}
var code networking_metrics.NetworkingMetricDimensionDynamicIpv6AddressSource
switch v {
case 0:
code = networking_metrics.NoGlobalSlaacOrDhcpv6ManagedAddress
case 1:
code = networking_metrics.GlobalSlaacOnly
case 2:
code = networking_metrics.Dhcpv6ManagedAddressOnly
case 3:
code = networking_metrics.GlobalSlaacAndDhcpv6ManagedAddress
default:
panic(fmt.Sprintf("unrecognized v = %d", v))
}
events = append(events, cobalt.CobaltEvent{
MetricId: networking_metrics.AvailableDynamicIpv6AddressConfigMetricId,
EventCodes: networking_metrics.AvailableDynamicIpv6AddressConfigEventCodes{
DynamicIpv6AddressSource: code,
InterfaceId: networking_metrics.AvailableDynamicIpv6AddressConfigMetricDimensionInterfaceId(nicID),
}.ToArray(),
Payload: cobalt.EventPayloadWithEventCount(cobalt.CountEvent{
Count: 1,
}),
})
}
return events
}
var _ cobaltEventProducer = (*dhcpV6Observation)(nil)
type dhcpV6Observation struct {
mu struct {
sync.Mutex
seen map[ipv6.DHCPv6ConfigurationFromNDPRA]int
hasEvents func()
}
}
func (o *dhcpV6Observation) init(hasEvents func()) {
o.mu.Lock()
defer o.mu.Unlock()
o.mu.hasEvents = hasEvents
}
func (o *dhcpV6Observation) events() []cobalt.CobaltEvent {
o.mu.Lock()
defer o.mu.Unlock()
var res []cobalt.CobaltEvent
for c, count := range o.mu.seen {
var code networking_metrics.NetworkingMetricDimensionConfigurationFromNdpra
switch c {
case ipv6.DHCPv6NoConfiguration:
code = networking_metrics.NoConfiguration
case ipv6.DHCPv6ManagedAddress:
code = networking_metrics.ManagedAddress
case ipv6.DHCPv6OtherConfigurations:
code = networking_metrics.OtherConfigurations
default:
_ = syslog.Warnf("ndp: unknown ipv6.DHCPv6ConfigurationFromNDPRA: %s", c)
}
for i := 0; i < count; i++ {
res = append(res, cobalt.CobaltEvent{
MetricId: networking_metrics.DhcpV6ConfigurationMetricId,
EventCodes: []uint32{uint32(code)},
Payload: cobalt.EventPayloadWithEvent(cobalt.Event{}),
})
}
}
o.mu.seen = nil
return res
}
// OnDHCPv6Configuration implements ipv6.NDPDispatcher.
func (n *ndpDispatcher) OnDHCPv6Configuration(nicID tcpip.NICID, configuration ipv6.DHCPv6ConfigurationFromNDPRA) {
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "OnDHCPv6Configuration(%d, %s)", nicID, configuration)
n.dhcpv6Obs.mu.Lock()
if n.dhcpv6Obs.mu.seen == nil {
n.dhcpv6Obs.mu.seen = make(map[ipv6.DHCPv6ConfigurationFromNDPRA]int)
}
n.dhcpv6Obs.mu.seen[configuration] += 1
hasEvents := n.dhcpv6Obs.mu.hasEvents
n.dhcpv6Obs.mu.Unlock()
if hasEvents == nil {
panic("ndp dispatcher: dhcpV6Observation: hasEvents callback unspecified (ensure init has been called)")
}
hasEvents()
n.dynamicAddressSourceObs.setLastDHCPv6(nicID, configuration)
}
// addEvent adds an event to be handled by the ndpDispatcher goroutine.
func (n *ndpDispatcher) addEvent(e ndpEvent) {
n.mu.Lock()
n.mu.events = append(n.mu.events, e)
n.mu.Unlock()
select {
case n.notifyCh <- struct{}{}:
default:
// If we are unable to send to notifyCh, then we know that the
// worker goroutine has already been signalled to wake up and
// handle pending events.
}
}
// start starts the ndpDispatcher goroutine which will handle the NDP events.
//
// The worker goroutine will be stopped if ctx is cancelled.
//
// Panics if n does not have an associated Netstack.
func (n *ndpDispatcher) start(ctx context.Context) {
_ = syslog.InfoTf(ndpSyslogTagName, "starting worker goroutine...")
if n.ns == nil {
panic(fmt.Sprintf("ndp: ndpDispatcher (%p) does not have an associated Netstack", n))
}
go func() {
n.sem <- struct{}{}
defer func() { <-n.sem }()
done := ctx.Done()
_ = syslog.InfoTf(ndpSyslogTagName, "started worker goroutine")
for {
var event ndpEvent
for {
// Has ctx been cancelled?
if err := ctx.Err(); err != nil {
_ = syslog.InfoTf(ndpSyslogTagName, "stopping worker goroutine; ctx.Err(): %s", err)
return
}
// Get the next event from the queue, but do not remove the event from
// the queue yet. The event will be removed from the queue once it has
// been handled. This is to avoid a race condition in tests where
// waiting for the queue to empty can block indefinitely if the queue is
// already empty.
//
// This is safe because the worker goroutine will be the only goroutine
// handling events and popping from the queue. Other goroutines will
// only push to the queue.
n.mu.Lock()
if len(n.mu.events) > 0 {
event = n.mu.events[0]
}
n.mu.Unlock()
if event != nil {
break
}
// No NDP events to handle. Wait for an NDP or ctx cancellation event to
// handle.
select {
case <-done:
_ = syslog.InfoTf(ndpSyslogTagName, "stopping worker goroutine; ctx.Err(): %s", ctx.Err())
return
case <-n.notifyCh:
continue
}
}
// Handle the event.
switch event := event.(type) {
case *ndpDuplicateAddressDetectionEvent:
switch result := event.result.(type) {
case *stack.DADSucceeded:
_ = syslog.InfoTf(ndpSyslogTagName, "DAD resolved for %s on nicID (%d), sending interface changed event...", event.addr, event.nicID)
case *stack.DADError:
logFn := syslog.ErrorTf
if _, ok := result.Err.(*tcpip.ErrClosedForSend); ok {
logFn = syslog.WarnTf
}
_ = logFn(ndpSyslogTagName, "DAD for %s on nicID (%d) encountered error = %s, sending interface changed event...", event.addr, event.nicID, result.Err)
case *stack.DADAborted:
_ = syslog.WarnTf(ndpSyslogTagName, "DAD for %s on nicID (%d) aborted, sending interface changed event...", event.addr, event.nicID)
case *stack.DADDupAddrDetected:
_ = syslog.WarnTf(ndpSyslogTagName, "DAD found %s holding %s on nicID (%d), sending interface changed event...", result.HolderLinkAddress, event.addr, event.nicID)
default:
panic(fmt.Sprintf("unhandled DAD result variant %#v", result))
}
n.ns.onInterfacesChanged()
n.ns.onPropertiesChange(event.nicID)
case *ndpDiscoveredRouterEvent:
nicID, addr := event.nicID, event.addr
rt := defaultV6Route(nicID, addr)
_ = syslog.InfoTf(ndpSyslogTagName, "discovered a default router (%s) on nicID (%d), adding a default route to it: [%s]", addr, nicID, rt)
// rt is added as a 'static' route because Netstack will remove dynamic
// routes on DHCPv4 changes. See
// staticRouteAvoidingLifeCycleHooks for more details.
if err := n.ns.AddRoute(rt, metricNotSet, staticRouteAvoidingLifeCycleHooks); err != nil {
_ = syslog.ErrorTf(ndpSyslogTagName, "failed to add the default route [%s] for the discovered router (%s) on nicID (%d): %s", rt, addr, nicID, err)
}
case *ndpInvalidatedRouterEvent:
nicID, addr := event.nicID, event.addr
rt := defaultV6Route(nicID, addr)
_ = syslog.InfoTf(ndpSyslogTagName, "invalidating a default router (%s) from nicID (%d), removing the default route to it: [%s]", addr, nicID, rt)
// If the route does not exist, we do not consider that an error as it
// may have been removed by the user.
if err := n.ns.DelRoute(rt); err != nil && !errors.Is(err, routes.ErrNoSuchRoute) {
_ = syslog.ErrorTf(ndpSyslogTagName, "failed to remove the default route [%s] for the invalidated router (%s) on nicID (%d): %s", rt, addr, nicID, err)
}
case *ndpDiscoveredPrefixEvent:
nicID, prefix := event.nicID, event.prefix
rt := onLinkV6Route(nicID, prefix)
_ = syslog.InfoTf(ndpSyslogTagName, "discovered an on-link prefix (%s) on nicID (%d), adding an on-link route to it: [%s]", prefix, nicID, rt)
// rt is added as a 'static' route because Netstack will remove dynamic
// routes on DHCPv4 changes. See
// staticRouteAvoidingLifeCycleHooks for more details.
if err := n.ns.AddRoute(rt, metricNotSet, staticRouteAvoidingLifeCycleHooks); err != nil {
_ = syslog.ErrorTf(ndpSyslogTagName, "failed to add the on-link route [%s] for the discovered on-link prefix (%s) on nicID (%d): %s", rt, prefix, nicID, err)
}
case *ndpInvalidatedPrefixEvent:
nicID, prefix := event.nicID, event.prefix
rt := onLinkV6Route(nicID, prefix)
_ = syslog.InfoTf(ndpSyslogTagName, "invalidating an on-link prefix (%s) from nicID (%d), removing the on-link route to it: [%s]", prefix, nicID, rt)
// If the route does not exist, we do not consider that an error as it
// may have been removed by the user.
if err := n.ns.DelRoute(rt); err != nil && !errors.Is(err, routes.ErrNoSuchRoute) {
_ = syslog.ErrorTf(ndpSyslogTagName, "failed to remove the on-link route [%s] for the invalidated on-link prefix (%s) on nicID (%d): %s", rt, prefix, nicID, err)
}
case *ndpGeneratedAutoGenAddrEvent:
nicID, addrWithPrefix := event.nicID, event.addrWithPrefix
_ = syslog.InfoTf(ndpSyslogTagName, "added an auto-generated address (%s) on nicID (%d)", addrWithPrefix, nicID)
case *ndpInvalidatedAutoGenAddrEvent:
nicID, addrWithPrefix := event.nicID, event.addrWithPrefix
_ = syslog.InfoTf(ndpSyslogTagName, "invalidated an auto-generated address (%s) on nicID (%d), sending interface changed event...", addrWithPrefix, nicID)
n.ns.onInterfacesChanged()
n.ns.onPropertiesChange(event.nicID)
case *ndpRecursiveDNSServerEvent:
nicID, addrs, lifetime := event.nicID, event.addrs, event.lifetime
_ = syslog.VLogTf(syslog.DebugVerbosity, ndpSyslogTagName, "updating expiring DNS servers (%s) on nicID (%d) with lifetime (%s)...", addrs, nicID, lifetime)
servers := make([]tcpip.FullAddress, 0, len(addrs))
for _, a := range addrs {
// The default DNS port will be used since the Port field is
// unspecified here.
servers = append(servers, tcpip.FullAddress{Addr: a, NIC: nicID})
}
// lifetime should never be greater than header.NDPInfiniteLifetime.
if lifetime > header.NDPInfiniteLifetime {
panic(fmt.Sprintf("ndp: got recursive DNS server event with lifetime (%s) greater than infinite lifetime (%s) on nicID (%d) with addrs (%s)", lifetime, header.NDPInfiniteLifetime, nicID, addrs))
}
if lifetime == header.NDPInfiniteLifetime {
// A lifetime value less than 0 implies infinite lifetime to the DNS
// client.
lifetime = -1
}
n.ns.dnsConfig.UpdateNdpServers(servers, lifetime)
default:
panic(fmt.Sprintf("unrecognized event type: %T", event))
}
// Remove the event we just handled from the queue. If the queue is empty
// after popping, then we know that all events in the queue (before taking
// the lock) have been handled.
n.mu.Lock()
n.mu.events[0] = nil
n.mu.events = n.mu.events[1:]
eventsLeft := len(n.mu.events)
n.mu.Unlock()
// Signal tests that are waiting for the event queue to be empty. We
// signal after handling the last event so that when the test wakes up,
// the test can safely assume that all events in the queue (up to this
// notification) have been handled.
if eventsLeft == 0 {
select {
case n.testNotifyCh <- struct{}{}:
default:
}
}
}
}()
}
// newNDPDispatcher returns a new ndpDispatcher that allows 1 worker goroutine
// to be employed.
func newNDPDispatcher() *ndpDispatcher {
return &ndpDispatcher{
// This is set to 1 to guarantee ordering between events that
// share some relationship. See ndpDispatcher for more details.
sem: make(chan struct{}, 1),
notifyCh: make(chan struct{}, 1),
}
}