535 lines
18 KiB
Go
535 lines
18 KiB
Go
// 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
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
)
|
|
|
|
const (
|
|
// defaultDupAddrDetectTransmits is the default number of NDP Neighbor
|
|
// Solicitation messages to send when doing Duplicate Address Detection
|
|
// for a tentative address.
|
|
//
|
|
// Default = 1 (from RFC 4862 section 5.1)
|
|
defaultDupAddrDetectTransmits = 1
|
|
|
|
// defaultRetransmitTimer is the default amount of time to wait between
|
|
// sending NDP Neighbor solicitation messages.
|
|
//
|
|
// Default = 1s (from RFC 4861 section 10).
|
|
defaultRetransmitTimer = time.Second
|
|
|
|
// defaultHandleRAs is the default configuration for whether or not to
|
|
// handle incoming Router Advertisements as a host.
|
|
//
|
|
// Default = true.
|
|
defaultHandleRAs = true
|
|
|
|
// defaultDiscoverDefaultRouters is the default configuration for
|
|
// whether or not to discover default routers from incoming Router
|
|
// Advertisements as a host.
|
|
//
|
|
// Default = true.
|
|
defaultDiscoverDefaultRouters = true
|
|
|
|
// minimumRetransmitTimer is the minimum amount of time to wait between
|
|
// sending NDP Neighbor solicitation messages. Note, RFC 4861 does
|
|
// not impose a minimum Retransmit Timer, but we do here to make sure
|
|
// the messages are not sent all at once. We also come to this value
|
|
// because in the RetransmitTimer field of a Router Advertisement, a
|
|
// value of 0 means unspecified, so the smallest valid value is 1.
|
|
// Note, the unit of the RetransmitTimer field in the Router
|
|
// Advertisement is milliseconds.
|
|
//
|
|
// Min = 1ms.
|
|
minimumRetransmitTimer = time.Millisecond
|
|
|
|
// MaxDiscoveredDefaultRouters is the maximum number of discovered
|
|
// default routers. The stack should stop discovering new routers after
|
|
// discovering MaxDiscoveredDefaultRouters routers.
|
|
//
|
|
// This value MUST be at minimum 2 as per RFC 4861 section 6.3.4, and
|
|
// SHOULD be more.
|
|
//
|
|
// Max = 10.
|
|
MaxDiscoveredDefaultRouters = 10
|
|
)
|
|
|
|
// NDPDispatcher is the interface integrators of netstack must implement to
|
|
// receive and handle NDP related events.
|
|
type NDPDispatcher interface {
|
|
// OnDuplicateAddressDetectionStatus will be called when the DAD process
|
|
// for an address (addr) on a NIC (with ID nicID) completes. resolved
|
|
// will be set to true if DAD completed successfully (no duplicate addr
|
|
// detected); false otherwise (addr was detected to be a duplicate on
|
|
// the link the NIC is a part of, or it was stopped for some other
|
|
// reason, such as the address being removed). If an error occured
|
|
// during DAD, err will be set and resolved must be ignored.
|
|
//
|
|
// This function is permitted to block indefinitely without interfering
|
|
// with the stack's operation.
|
|
OnDuplicateAddressDetectionStatus(nicID tcpip.NICID, addr tcpip.Address, resolved bool, err *tcpip.Error)
|
|
|
|
// OnDefaultRouterDiscovered will be called when a new default router is
|
|
// discovered. Implementations must return true along with a new valid
|
|
// route table if the newly discovered router should be remembered. If
|
|
// an implementation returns false, the second return value will be
|
|
// ignored.
|
|
//
|
|
// This function is not permitted to block indefinitely. This function
|
|
// is also not permitted to call into the stack.
|
|
OnDefaultRouterDiscovered(nicID tcpip.NICID, addr tcpip.Address) (bool, []tcpip.Route)
|
|
|
|
// OnDefaultRouterInvalidated will be called when a discovered default
|
|
// router is invalidated. Implementers must return a new valid route
|
|
// table.
|
|
//
|
|
// This function is not permitted to block indefinitely. This function
|
|
// is also not permitted to call into the stack.
|
|
OnDefaultRouterInvalidated(nicID tcpip.NICID, addr tcpip.Address) []tcpip.Route
|
|
}
|
|
|
|
// NDPConfigurations is the NDP configurations for the netstack.
|
|
type NDPConfigurations struct {
|
|
// The number of Neighbor Solicitation messages to send when doing
|
|
// Duplicate Address Detection for a tentative address.
|
|
//
|
|
// Note, a value of zero effectively disables DAD.
|
|
DupAddrDetectTransmits uint8
|
|
|
|
// The amount of time to wait between sending Neighbor solicitation
|
|
// messages.
|
|
//
|
|
// Must be greater than 0.5s.
|
|
RetransmitTimer time.Duration
|
|
|
|
// HandleRAs determines whether or not Router Advertisements will be
|
|
// processed.
|
|
HandleRAs bool
|
|
|
|
// DiscoverDefaultRouters determines whether or not default routers will
|
|
// be discovered from Router Advertisements. This configuration is
|
|
// ignored if HandleRAs is false.
|
|
DiscoverDefaultRouters bool
|
|
}
|
|
|
|
// DefaultNDPConfigurations returns an NDPConfigurations populated with
|
|
// default values.
|
|
func DefaultNDPConfigurations() NDPConfigurations {
|
|
return NDPConfigurations{
|
|
DupAddrDetectTransmits: defaultDupAddrDetectTransmits,
|
|
RetransmitTimer: defaultRetransmitTimer,
|
|
HandleRAs: defaultHandleRAs,
|
|
DiscoverDefaultRouters: defaultDiscoverDefaultRouters,
|
|
}
|
|
}
|
|
|
|
// validate modifies an NDPConfigurations with valid values. If invalid values
|
|
// are present in c, the corresponding default values will be used instead.
|
|
//
|
|
// If RetransmitTimer is less than minimumRetransmitTimer, then a value of
|
|
// defaultRetransmitTimer will be used.
|
|
func (c *NDPConfigurations) validate() {
|
|
if c.RetransmitTimer < minimumRetransmitTimer {
|
|
c.RetransmitTimer = defaultRetransmitTimer
|
|
}
|
|
}
|
|
|
|
// ndpState is the per-interface NDP state.
|
|
type ndpState struct {
|
|
// The NIC this ndpState is for.
|
|
nic *NIC
|
|
|
|
// configs is the per-interface NDP configurations.
|
|
configs NDPConfigurations
|
|
|
|
// The DAD state to send the next NS message, or resolve the address.
|
|
dad map[tcpip.Address]dadState
|
|
|
|
// The default routers discovered through Router Advertisements.
|
|
defaultRouters map[tcpip.Address]defaultRouterState
|
|
}
|
|
|
|
// dadState holds the Duplicate Address Detection timer and channel to signal
|
|
// to the DAD goroutine that DAD should stop.
|
|
type dadState struct {
|
|
// The DAD timer to send the next NS message, or resolve the address.
|
|
timer *time.Timer
|
|
|
|
// Used to let the DAD timer know that it has been stopped.
|
|
//
|
|
// Must only be read from or written to while protected by the lock of
|
|
// the NIC this dadState is associated with.
|
|
done *bool
|
|
}
|
|
|
|
// defaultRouterState holds data associated with a default router discovered by
|
|
// a Router Advertisement.
|
|
type defaultRouterState struct {
|
|
invalidationTimer *time.Timer
|
|
|
|
// Used to signal the timer not to invalidate the default router (R) in
|
|
// a race condition (T1 is a goroutine that handles an RA from R and T2
|
|
// is the goroutine that handles R's invalidation timer firing):
|
|
// T1: Receive a new RA from R
|
|
// T1: Obtain the NIC's lock before processing the RA
|
|
// T2: R's invalidation timer fires, and gets blocked on obtaining the
|
|
// NIC's lock
|
|
// T1: Refreshes/extends R's lifetime & releases NIC's lock
|
|
// T2: Obtains NIC's lock & invalidates R immediately
|
|
//
|
|
// To resolve this, T1 will check to see if the timer already fired, and
|
|
// signal the timer using this channel to not invalidate R, so that once
|
|
// T2 obtains the lock, it will see that there is an event on this
|
|
// channel and do nothing further.
|
|
doNotInvalidateC chan struct{}
|
|
}
|
|
|
|
// startDuplicateAddressDetection performs Duplicate Address Detection.
|
|
//
|
|
// This function must only be called by IPv6 addresses that are currently
|
|
// tentative.
|
|
//
|
|
// The NIC that ndp belongs to MUST be locked.
|
|
func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, ref *referencedNetworkEndpoint) *tcpip.Error {
|
|
// addr must be a valid unicast IPv6 address.
|
|
if !header.IsV6UnicastAddress(addr) {
|
|
return tcpip.ErrAddressFamilyNotSupported
|
|
}
|
|
|
|
// Should not attempt to perform DAD on an address that is currently in
|
|
// the DAD process.
|
|
if _, ok := ndp.dad[addr]; ok {
|
|
// Should never happen because we should only ever call this
|
|
// function for newly created addresses. If we attemped to
|
|
// "add" an address that already existed, we would returned an
|
|
// error since we attempted to add a duplicate address, or its
|
|
// reference count would have been increased without doing the
|
|
// work that would have been done for an address that was brand
|
|
// new. See NIC.addPermanentAddressLocked.
|
|
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 does a single iteration of the DAD loop.
|
|
//
|
|
// Returns true if the integrator needs to be informed of DAD
|
|
// completing.
|
|
doDadIteration := func() bool {
|
|
ndp.nic.mu.Lock()
|
|
defer ndp.nic.mu.Unlock()
|
|
|
|
if done {
|
|
// If we reach this point, it means that the DAD
|
|
// timer fired after another goroutine already
|
|
// obtained the NIC lock and stopped DAD before
|
|
// this function obtained the NIC lock. Simply
|
|
// return here and do nothing further.
|
|
return false
|
|
}
|
|
|
|
ref, ok := ndp.nic.endpoints[NetworkEndpointID{addr}]
|
|
if !ok {
|
|
// This should never happen.
|
|
// We should have an endpoint for addr since we
|
|
// are still performing DAD on it. If the
|
|
// endpoint does not exist, but we are doing DAD
|
|
// on it, then we started DAD at some point, but
|
|
// forgot to stop it when the endpoint was
|
|
// deleted.
|
|
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)
|
|
}
|
|
|
|
// Let the integrator know DAD has completed.
|
|
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
|
|
}
|
|
|
|
// doDuplicateAddressDetection is called on every iteration of the timer, and
|
|
// when DAD starts.
|
|
//
|
|
// It handles resolving the address (if there are no more NS to send), or
|
|
// sending the next NS if there are more NS to send.
|
|
//
|
|
// This function must only be called by IPv6 addresses that are currently
|
|
// tentative.
|
|
//
|
|
// The NIC that ndp belongs to (n) MUST be locked.
|
|
//
|
|
// Returns true if DAD has resolved; false if DAD is still ongoing.
|
|
func (ndp *ndpState) doDuplicateAddressDetection(addr tcpip.Address, remaining uint8, ref *referencedNetworkEndpoint) (bool, *tcpip.Error) {
|
|
if ref.getKind() != permanentTentative {
|
|
// The endpoint should still be marked as tentative
|
|
// since we are still performing DAD on it.
|
|
panic(fmt.Sprintf("ndpdad: addr %s is not tentative on NIC(%d)", addr, ndp.nic.ID()))
|
|
}
|
|
|
|
if remaining == 0 {
|
|
// DAD has resolved.
|
|
ref.setKind(permanent)
|
|
return true, nil
|
|
}
|
|
|
|
// Send a new NS.
|
|
snmc := header.SolicitedNodeAddr(addr)
|
|
snmcRef, ok := ndp.nic.endpoints[NetworkEndpointID{snmc}]
|
|
if !ok {
|
|
// This should never happen as if we have the
|
|
// address, we should have the solicited-node
|
|
// address.
|
|
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))
|
|
}
|
|
|
|
// Use the unspecified address as the source address when performing
|
|
// DAD.
|
|
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, hdr, buffer.VectorisedView{}, NetworkHeaderParams{Protocol: header.ICMPv6ProtocolNumber, TTL: header.NDPHopLimit, TOS: DefaultTOS}); err != nil {
|
|
sent.Dropped.Increment()
|
|
return false, err
|
|
}
|
|
sent.NeighborSolicit.Increment()
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// stopDuplicateAddressDetection ends a running Duplicate Address Detection
|
|
// process. Note, this may leave the DAD process for a tentative address in
|
|
// such a state forever, unless some other external event resolves the DAD
|
|
// process (receiving an NA from the true owner of addr, or an NS for addr
|
|
// (implying another node is attempting to use addr)). It is up to the caller
|
|
// of this function to handle such a scenario. Normally, addr will be removed
|
|
// from n right after this function returns or the address successfully
|
|
// resolved.
|
|
//
|
|
// The NIC that ndp belongs to MUST be locked.
|
|
func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address) {
|
|
dad, ok := ndp.dad[addr]
|
|
if !ok {
|
|
// Not currently performing DAD on addr, just return.
|
|
return
|
|
}
|
|
|
|
if dad.timer != nil {
|
|
dad.timer.Stop()
|
|
dad.timer = nil
|
|
|
|
*dad.done = true
|
|
dad.done = nil
|
|
}
|
|
|
|
delete(ndp.dad, addr)
|
|
|
|
// Let the integrator know DAD did not resolve.
|
|
if ndp.nic.stack.ndpDisp != nil {
|
|
go ndp.nic.stack.ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, false, nil)
|
|
}
|
|
}
|
|
|
|
// handleRA handles a Router Advertisement message that arrived on the NIC
|
|
// this ndp is for. Does nothing if the NIC is configured to not handle RAs.
|
|
//
|
|
// The NIC that ndp belongs to and its associated stack MUST be locked.
|
|
func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
|
|
// Is the NIC configured to handle RAs at all?
|
|
//
|
|
// Currently, the stack does not determine router interface status on a
|
|
// per-interface basis; it is a stack-wide configuration, so we check
|
|
// stack's forwarding flag to determine if the NIC is a routing
|
|
// interface.
|
|
if !ndp.configs.HandleRAs || ndp.nic.stack.forwarding {
|
|
return
|
|
}
|
|
|
|
// Is the NIC configured to discover default routers?
|
|
if ndp.configs.DiscoverDefaultRouters {
|
|
rtr, ok := ndp.defaultRouters[ip]
|
|
rl := ra.RouterLifetime()
|
|
switch {
|
|
case !ok && rl != 0:
|
|
// This is a new default router we are discovering.
|
|
//
|
|
// Only remember it if we currently know about less than
|
|
// MaxDiscoveredDefaultRouters routers.
|
|
if len(ndp.defaultRouters) < MaxDiscoveredDefaultRouters {
|
|
ndp.rememberDefaultRouter(ip, rl)
|
|
}
|
|
|
|
case ok && rl != 0:
|
|
// This is an already discovered default router. Update
|
|
// the invalidation timer.
|
|
timer := rtr.invalidationTimer
|
|
|
|
// We should ALWAYS have an invalidation timer for a
|
|
// discovered router.
|
|
if timer == nil {
|
|
panic("ndphandlera: RA invalidation timer should not be nil")
|
|
}
|
|
|
|
if !timer.Stop() {
|
|
// If we reach this point, then we know the
|
|
// timer fired after we already took the NIC
|
|
// lock. Signal the timer so that once it
|
|
// obtains the lock, it doesn't actually
|
|
// invalidate the router as we just got a new
|
|
// RA that refreshes its lifetime to a non-zero
|
|
// value. See
|
|
// defaultRouterState.doNotInvalidateC for more
|
|
// details.
|
|
rtr.doNotInvalidateC <- struct{}{}
|
|
}
|
|
|
|
timer.Reset(rl)
|
|
|
|
case ok && rl == 0:
|
|
// We know about the router but it is no longer to be
|
|
// used as a default router so invalidate it.
|
|
ndp.invalidateDefaultRouter(ip)
|
|
}
|
|
}
|
|
|
|
// TODO(b/140948104): Do Prefix Discovery.
|
|
// TODO(b/141556115): Do Parameter Discovery.
|
|
}
|
|
|
|
// invalidateDefaultRouter invalidates a discovered default router.
|
|
//
|
|
// The NIC that ndp belongs to and its associated stack MUST be locked.
|
|
func (ndp *ndpState) invalidateDefaultRouter(ip tcpip.Address) {
|
|
rtr, ok := ndp.defaultRouters[ip]
|
|
|
|
// Is the router still discovered?
|
|
if !ok {
|
|
// ...Nope, do nothing further.
|
|
return
|
|
}
|
|
|
|
rtr.invalidationTimer.Stop()
|
|
rtr.invalidationTimer = nil
|
|
close(rtr.doNotInvalidateC)
|
|
rtr.doNotInvalidateC = nil
|
|
|
|
delete(ndp.defaultRouters, ip)
|
|
|
|
// Let the integrator know a discovered default router is invalidated.
|
|
if ndp.nic.stack.ndpDisp != nil {
|
|
ndp.nic.stack.routeTable = ndp.nic.stack.ndpDisp.OnDefaultRouterInvalidated(ndp.nic.ID(), ip)
|
|
}
|
|
}
|
|
|
|
// rememberDefaultRouter remembers a newly discovered default router with IPv6
|
|
// link-local address ip with lifetime rl.
|
|
//
|
|
// The router identified by ip MUST NOT already be known by the NIC.
|
|
//
|
|
// The NIC that ndp belongs to and its associated stack MUST be locked.
|
|
func (ndp *ndpState) rememberDefaultRouter(ip tcpip.Address, rl time.Duration) {
|
|
if ndp.nic.stack.ndpDisp == nil {
|
|
return
|
|
}
|
|
|
|
// Inform the integrator when we discovered a default router.
|
|
remember, routeTable := ndp.nic.stack.ndpDisp.OnDefaultRouterDiscovered(ndp.nic.ID(), ip)
|
|
if !remember {
|
|
// Informed by the integrator to not remember the router, do
|
|
// nothing further.
|
|
return
|
|
}
|
|
|
|
// Used to signal the timer not to invalidate the default router (R) in
|
|
// a race condition. See defaultRouterState.doNotInvalidateC for more
|
|
// details.
|
|
doNotInvalidateC := make(chan struct{}, 1)
|
|
|
|
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()
|
|
|
|
select {
|
|
case <-doNotInvalidateC:
|
|
return
|
|
default:
|
|
}
|
|
|
|
ndp.invalidateDefaultRouter(ip)
|
|
}),
|
|
doNotInvalidateC: doNotInvalidateC,
|
|
}
|
|
|
|
ndp.nic.stack.routeTable = routeTable
|
|
}
|