func (ndp *ndpState) rememberOnLinkPrefix(prefix tcpip.Subnet, l time.Duration) {
	if ndp.nic.stack.ndpDisp == nil {
		return
	}
	remember, routeTable := ndp.nic.stack.ndpDisp.OnOnLinkPrefixDiscovered(ndp.nic.ID(), prefix)
	if !remember {
		return
	}
	var doNotInvalidate bool
	var timer *time.Timer
	if l < header.NDPPrefixInformationInfiniteLifetime {
		timer = ndp.prefixInvalidationCallback(prefix, l, &doNotInvalidate)
	}
	ndp.onLinkPrefixes[prefix] = onLinkPrefixState{
		invalidationTimer: timer,
		doNotInvalidate:   &doNotInvalidate,
	}
	ndp.nic.stack.routeTable = routeTable
}
rememberOnLinkPrefix remembers a newly discovered on-link prefix with IPv6
address with prefix prefix with lifetime l.

The prefix identified by prefix MUST NOT already be known.

The NIC that ndp belongs to and its associated stack MUST be locked.
package stack
import (
	"fmt"
	"log"
	"time"
	"github.com/google/netstack/tcpip"
	"github.com/google/netstack/tcpip/buffer"
	"github.com/google/netstack/tcpip/header"
)
const (
	defaultDupAddrDetectTransmits = 1
	defaultRetransmitTimer = time.Second
	defaultHandleRAs = true
	defaultDiscoverDefaultRouters = true
	defaultDiscoverOnLinkPrefixes = true
	minimumRetransmitTimer = time.Millisecond
	MaxDiscoveredDefaultRouters = 10
	MaxDiscoveredOnLinkPrefixes = 10
)
type NDPDispatcher interface {
	OnDuplicateAddressDetectionStatus(nicID tcpip.NICID, addr tcpip.Address, resolved bool, err *tcpip.Error)
	OnDefaultRouterDiscovered(nicID tcpip.NICID, addr tcpip.Address) (bool, []tcpip.Route)
	OnDefaultRouterInvalidated(nicID tcpip.NICID, addr tcpip.Address) []tcpip.Route
	OnOnLinkPrefixDiscovered(nicID tcpip.NICID, prefix tcpip.Subnet) (bool, []tcpip.Route)
	OnOnLinkPrefixInvalidated(nicID tcpip.NICID, prefix tcpip.Subnet) []tcpip.Route
}
type NDPConfigurations struct {
	DupAddrDetectTransmits uint8
	RetransmitTimer time.Duration
	HandleRAs bool
	DiscoverDefaultRouters bool
	DiscoverOnLinkPrefixes bool
}
func DefaultNDPConfigurations() NDPConfigurations {
	return NDPConfigurations{
		DupAddrDetectTransmits: defaultDupAddrDetectTransmits,
		RetransmitTimer:        defaultRetransmitTimer,
		HandleRAs:              defaultHandleRAs,
		DiscoverDefaultRouters: defaultDiscoverDefaultRouters,
		DiscoverOnLinkPrefixes: defaultDiscoverOnLinkPrefixes,
	}
}
func (c *NDPConfigurations) validate() {
	if c.RetransmitTimer < minimumRetransmitTimer {
		c.RetransmitTimer = defaultRetransmitTimer
	}
}
type ndpState struct {
	nic *NIC
	configs NDPConfigurations
	dad map[tcpip.Address]dadState
	defaultRouters map[tcpip.Address]defaultRouterState
	onLinkPrefixes map[tcpip.Subnet]onLinkPrefixState
}
type dadState struct {
	timer *time.Timer
	done *bool
}
type defaultRouterState struct {
	invalidationTimer *time.Timer
	doNotInvalidate *bool
}
type onLinkPrefixState struct {
	invalidationTimer *time.Timer
	doNotInvalidate *bool
}
func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, ref *referencedNetworkEndpoint) *tcpip.Error {
	if !header.IsV6UnicastAddress(addr) {
		return tcpip.ErrAddressFamilyNotSupported
	}
	if _, ok := ndp.dad[addr]; ok {
		panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.nic.ID()))
	}
	remaining := ndp.configs.DupAddrDetectTransmits
	{
		done, err := ndp.doDuplicateAddressDetection(addr, remaining, ref)
		if err != nil {
			return err
		}
		if done {
			return nil
		}
	}
	remaining--
	var done bool
	var timer *time.Timer
	timer = time.AfterFunc(ndp.configs.RetransmitTimer, func() {
		var d bool
		var err *tcpip.Error
		doDadIteration := func() bool {
			ndp.nic.mu.Lock()
			defer ndp.nic.mu.Unlock()
			if done {
				return false
			}
			ref, ok := ndp.nic.endpoints[NetworkEndpointID{addr}]
			if !ok {
				panic(fmt.Sprintf("ndpdad: unrecognized addr %s for NIC(%d)", addr, ndp.nic.ID()))
			}
			d, err = ndp.doDuplicateAddressDetection(addr, remaining, ref)
			if err != nil || d {
				delete(ndp.dad, addr)
				if err != nil {
					log.Printf("ndpdad: Error occured during DAD iteration for addr (%s) on NIC(%d); err = %s", addr, ndp.nic.ID(), err)
				}
				return true
			}
			remaining--
			timer.Reset(ndp.nic.stack.ndpConfigs.RetransmitTimer)
			return false
		}
		if doDadIteration() && ndp.nic.stack.ndpDisp != nil {
			ndp.nic.stack.ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, d, err)
		}
	})
	ndp.dad[addr] = dadState{
		timer: timer,
		done:  &done,
	}
	return nil
}
func (ndp *ndpState) doDuplicateAddressDetection(addr tcpip.Address, remaining uint8, ref *referencedNetworkEndpoint) (bool, *tcpip.Error) {
	if ref.getKind() != permanentTentative {
		panic(fmt.Sprintf("ndpdad: addr %s is not tentative on NIC(%d)", addr, ndp.nic.ID()))
	}
	if remaining == 0 {
		ref.setKind(permanent)
		return true, nil
	}
	snmc := header.SolicitedNodeAddr(addr)
	snmcRef, ok := ndp.nic.endpoints[NetworkEndpointID{snmc}]
	if !ok {
		panic(fmt.Sprintf("ndpdad: NIC(%d) is not in the solicited-node multicast group (%s) but it has addr %s", ndp.nic.ID(), snmc, addr))
	}
	r := makeRoute(header.IPv6ProtocolNumber, header.IPv6Any, snmc, ndp.nic.linkEP.LinkAddress(), snmcRef, false, false)
	hdr := buffer.NewPrependable(int(r.MaxHeaderLength()) + header.ICMPv6NeighborSolicitMinimumSize)
	pkt := header.ICMPv6(hdr.Prepend(header.ICMPv6NeighborSolicitMinimumSize))
	pkt.SetType(header.ICMPv6NeighborSolicit)
	ns := header.NDPNeighborSolicit(pkt.NDPPayload())
	ns.SetTargetAddress(addr)
	pkt.SetChecksum(header.ICMPv6Checksum(pkt, r.LocalAddress, r.RemoteAddress, buffer.VectorisedView{}))
	sent := r.Stats().ICMP.V6PacketsSent
	if err := r.WritePacket(nil, NetworkHeaderParams{Protocol: header.ICMPv6ProtocolNumber, TTL: header.NDPHopLimit, TOS: DefaultTOS}, tcpip.PacketBuffer{
		Header: hdr,
	}); err != nil {
		sent.Dropped.Increment()
		return false, err
	}
	sent.NeighborSolicit.Increment()
	return false, nil
}
func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address) {
	dad, ok := ndp.dad[addr]
	if !ok {
		return
	}
	if dad.timer != nil {
		dad.timer.Stop()
		dad.timer = nil
		*dad.done = true
		dad.done = nil
	}
	delete(ndp.dad, addr)
	if ndp.nic.stack.ndpDisp != nil {
		go ndp.nic.stack.ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, false, nil)
	}
}
func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
	if !ndp.configs.HandleRAs || ndp.nic.stack.forwarding {
		return
	}
	if ndp.configs.DiscoverDefaultRouters {
		rtr, ok := ndp.defaultRouters[ip]
		rl := ra.RouterLifetime()
		switch {
		case !ok && rl != 0:
			if len(ndp.defaultRouters) < MaxDiscoveredDefaultRouters {
				ndp.rememberDefaultRouter(ip, rl)
			}
		case ok && rl != 0:
			timer := rtr.invalidationTimer
			if timer == nil {
				panic("ndphandlera: RA invalidation timer should not be nil")
			}
			if !timer.Stop() {
				*rtr.doNotInvalidate = true
			}
			timer.Reset(rl)
		case ok && rl == 0:
			ndp.invalidateDefaultRouter(ip)
		}
	}
	it, _ := ra.Options().Iter(false)
	for opt, done, _ := it.Next(); !done; opt, done, _ = it.Next() {
		switch opt.Type() {
		case header.NDPPrefixInformationType:
			if !ndp.configs.DiscoverOnLinkPrefixes {
				continue
			}
			pi := opt.(header.NDPPrefixInformation)
			prefix := pi.Subnet()
			if header.IsV6LinkLocalAddress(prefix.ID()) {
				continue
			}
			if prefix.Prefix() == 0 {
				continue
			}
			if !pi.OnLinkFlag() {
				continue
			}
			prefixState, ok := ndp.onLinkPrefixes[prefix]
			vl := pi.ValidLifetime()
			switch {
			case !ok && vl == 0:
				continue
			case !ok && vl != 0:
				if len(ndp.onLinkPrefixes) < MaxDiscoveredOnLinkPrefixes {
					ndp.rememberOnLinkPrefix(prefix, vl)
				}
				continue
			case ok && vl == 0:
				ndp.invalidateOnLinkPrefix(prefix)
				continue
			}
			timer := prefixState.invalidationTimer
			if timer == nil && vl >= header.NDPPrefixInformationInfiniteLifetime {
				continue
			}
			if timer != nil && !timer.Stop() {
				*prefixState.doNotInvalidate = true
			}
			if vl >= header.NDPPrefixInformationInfiniteLifetime {
				prefixState.invalidationTimer = nil
				ndp.onLinkPrefixes[prefix] = prefixState
				continue
			}
			if timer != nil {
				timer.Reset(vl)
				continue
			}
			prefixState.invalidationTimer = ndp.prefixInvalidationCallback(prefix, vl, prefixState.doNotInvalidate)
			ndp.onLinkPrefixes[prefix] = prefixState
		}
	}
}
func (ndp *ndpState) invalidateDefaultRouter(ip tcpip.Address) {
	rtr, ok := ndp.defaultRouters[ip]
	if !ok {
		return
	}
	rtr.invalidationTimer.Stop()
	rtr.invalidationTimer = nil
	*rtr.doNotInvalidate = true
	rtr.doNotInvalidate = nil
	delete(ndp.defaultRouters, ip)
	if ndp.nic.stack.ndpDisp != nil {
		ndp.nic.stack.routeTable = ndp.nic.stack.ndpDisp.OnDefaultRouterInvalidated(ndp.nic.ID(), ip)
	}
}
func (ndp *ndpState) rememberDefaultRouter(ip tcpip.Address, rl time.Duration) {
	if ndp.nic.stack.ndpDisp == nil {
		return
	}
	remember, routeTable := ndp.nic.stack.ndpDisp.OnDefaultRouterDiscovered(ndp.nic.ID(), ip)
	if !remember {
		return
	}
	var doNotInvalidate bool
	ndp.defaultRouters[ip] = defaultRouterState{
		invalidationTimer: time.AfterFunc(rl, func() {
			ndp.nic.stack.mu.Lock()
			defer ndp.nic.stack.mu.Unlock()
			ndp.nic.mu.Lock()
			defer ndp.nic.mu.Unlock()
			if doNotInvalidate {
				doNotInvalidate = false
				return
			}
			ndp.invalidateDefaultRouter(ip)
		}),
		doNotInvalidate: &doNotInvalidate,
	}
	ndp.nic.stack.routeTable = routeTable
}