summaryrefslogtreecommitdiffhomepage
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/tcpip/network/arp/arp.go77
-rw-r--r--pkg/tcpip/network/internal/ip/duplicate_address_detection.go172
-rw-r--r--pkg/tcpip/network/internal/ip/ip_state_autogen.go3
-rw-r--r--pkg/tcpip/network/ipv6/icmp.go35
-rw-r--r--pkg/tcpip/network/ipv6/ipv6.go62
-rw-r--r--pkg/tcpip/network/ipv6/ndp.go253
-rw-r--r--pkg/tcpip/stack/nic.go33
-rw-r--r--pkg/tcpip/stack/registration.go93
-rw-r--r--pkg/tcpip/stack/stack.go11
9 files changed, 513 insertions, 226 deletions
diff --git a/pkg/tcpip/network/arp/arp.go b/pkg/tcpip/network/arp/arp.go
index 5d7803537..3fcdea119 100644
--- a/pkg/tcpip/network/arp/arp.go
+++ b/pkg/tcpip/network/arp/arp.go
@@ -22,10 +22,12 @@ import (
"reflect"
"sync/atomic"
+ "gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/header/parse"
+ "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -34,6 +36,7 @@ const (
ProtocolNumber = header.ARPProtocolNumber
)
+var _ stack.DuplicateAddressDetector = (*endpoint)(nil)
var _ stack.LinkAddressResolver = (*endpoint)(nil)
// ARP endpoints need to implement stack.NetworkEndpoint because the stack
@@ -52,6 +55,35 @@ type endpoint struct {
nic stack.NetworkInterface
stats sharedStats
+
+ mu struct {
+ sync.Mutex
+
+ dad ip.DAD
+ }
+}
+
+// CheckDuplicateAddress implements stack.DuplicateAddressDetector.
+func (e *endpoint) CheckDuplicateAddress(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ return e.mu.dad.CheckDuplicateAddressLocked(addr, h)
+}
+
+// SetDADConfigurations implements stack.DuplicateAddressDetector.
+func (e *endpoint) SetDADConfigurations(c stack.DADConfigurations) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.mu.dad.SetConfigsLocked(c)
+}
+
+// DuplicateAddressProtocol implements stack.DuplicateAddressDetector.
+func (*endpoint) DuplicateAddressProtocol() tcpip.NetworkProtocolNumber {
+ return header.IPv4ProtocolNumber
+}
+
+func (e *endpoint) SendDADMessage(addr tcpip.Address) tcpip.Error {
+ return e.sendARPRequest(header.IPv4Any, addr, header.EthernetBroadcastAddress)
}
func (e *endpoint) Enable() tcpip.Error {
@@ -199,6 +231,10 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
addr := tcpip.Address(h.ProtocolAddressSender())
linkAddr := tcpip.LinkAddress(h.HardwareAddressSender())
+ e.mu.Lock()
+ e.mu.dad.StopLocked(addr, false /* aborted */)
+ e.mu.Unlock()
+
// The solicited, override, and isRouter flags are not available for ARP;
// they are only available for IPv6 Neighbor Advertisements.
switch err := e.nic.HandleNeighborConfirmation(header.IPv4ProtocolNumber, addr, linkAddr, stack.ReachabilityConfirmationFlags{
@@ -227,9 +263,9 @@ func (e *endpoint) Stats() stack.NetworkEndpointStats {
var _ stack.NetworkProtocol = (*protocol)(nil)
-// protocol implements stack.NetworkProtocol and stack.LinkAddressResolver.
type protocol struct {
- stack *stack.Stack
+ stack *stack.Stack
+ options Options
}
func (p *protocol) Number() tcpip.NetworkProtocolNumber { return ProtocolNumber }
@@ -246,6 +282,14 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.Tran
nic: nic,
}
+ e.mu.Lock()
+ e.mu.dad.Init(&e.mu, p.options.DADConfigs, ip.DADOptions{
+ Clock: p.stack.Clock(),
+ Protocol: e,
+ NICID: nic.ID(),
+ })
+ e.mu.Unlock()
+
tcpip.InitStatCounters(reflect.ValueOf(&e.stats.localStats).Elem())
stackStats := p.stack.Stats()
@@ -286,8 +330,12 @@ func (e *endpoint) LinkAddressRequest(targetAddr, localAddr tcpip.Address, remot
return &tcpip.ErrBadLocalAddress{}
}
+ return e.sendARPRequest(localAddr, targetAddr, remoteLinkAddr)
+}
+
+func (e *endpoint) sendARPRequest(localAddr, targetAddr tcpip.Address, remoteLinkAddr tcpip.LinkAddress) tcpip.Error {
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: int(e.nic.MaxHeaderLength()) + header.ARPSize,
+ ReserveHeaderBytes: int(e.MaxHeaderLength()),
})
h := header.ARP(pkt.NetworkHeader().Push(header.ARPSize))
pkt.NetworkProtocolNumber = ProtocolNumber
@@ -302,6 +350,8 @@ func (e *endpoint) LinkAddressRequest(targetAddr, localAddr tcpip.Address, remot
if n := copy(h.ProtocolAddressTarget(), targetAddr); n != header.IPv4AddressSize {
panic(fmt.Sprintf("copied %d bytes, expected %d bytes", n, header.IPv4AddressSize))
}
+
+ stats := e.stats.arp
if err := e.nic.WritePacketToRemote(remoteLinkAddr, nil /* gso */, ProtocolNumber, pkt); err != nil {
stats.outgoingRequestsDropped.Increment()
return err
@@ -342,9 +392,24 @@ func (*protocol) Parse(pkt *stack.PacketBuffer) (proto tcpip.TransportProtocolNu
return 0, false, parse.ARP(pkt)
}
+// Options holds options to configure a protocol.
+type Options struct {
+ // DADConfigs is the default DAD configurations used by ARP endpoints.
+ DADConfigs stack.DADConfigurations
+}
+
+// NewProtocolWithOptions returns an ARP network protocol factory that
+// will return an ARP network protocol with the provided options.
+func NewProtocolWithOptions(opts Options) stack.NetworkProtocolFactory {
+ return func(s *stack.Stack) stack.NetworkProtocol {
+ return &protocol{
+ stack: s,
+ options: opts,
+ }
+ }
+}
+
// NewProtocol returns an ARP network protocol.
func NewProtocol(s *stack.Stack) stack.NetworkProtocol {
- return &protocol{
- stack: s,
- }
+ return NewProtocolWithOptions(Options{})(s)
}
diff --git a/pkg/tcpip/network/internal/ip/duplicate_address_detection.go b/pkg/tcpip/network/internal/ip/duplicate_address_detection.go
new file mode 100644
index 000000000..6f89a6a16
--- /dev/null
+++ b/pkg/tcpip/network/internal/ip/duplicate_address_detection.go
@@ -0,0 +1,172 @@
+// Copyright 2021 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 ip holds IPv4/IPv6 common utilities.
+package ip
+
+import (
+ "fmt"
+
+ "gvisor.dev/gvisor/pkg/sync"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/stack"
+)
+
+type dadState struct {
+ done *bool
+ timer tcpip.Timer
+
+ completionHandlers []stack.DADCompletionHandler
+}
+
+// DADProtocol is a protocol whose core state machine can be represented by DAD.
+type DADProtocol interface {
+ // SendDADMessage attempts to send a DAD probe message.
+ SendDADMessage(tcpip.Address) tcpip.Error
+}
+
+// DADOptions holds options for DAD.
+type DADOptions struct {
+ Clock tcpip.Clock
+ Protocol DADProtocol
+ NICID tcpip.NICID
+}
+
+// DAD performs duplicate address detection for addresses.
+type DAD struct {
+ opts DADOptions
+ configs stack.DADConfigurations
+
+ protocolMU sync.Locker
+ addresses map[tcpip.Address]dadState
+}
+
+// Init initializes the DAD state.
+//
+// Must only be called once for the lifetime of d; Init will panic if it is
+// called twice.
+//
+// The lock will only be taken when timers fire.
+func (d *DAD) Init(protocolMU sync.Locker, configs stack.DADConfigurations, opts DADOptions) {
+ if d.addresses != nil {
+ panic("attempted to initialize DAD state twice")
+ }
+
+ *d = DAD{
+ opts: opts,
+ configs: configs,
+ protocolMU: protocolMU,
+ addresses: make(map[tcpip.Address]dadState),
+ }
+}
+
+// CheckDuplicateAddressLocked performs DAD for an address, calling the
+// completion handler once DAD resolves.
+//
+// If DAD is already performing for the provided address, h will be called when
+// the currently running process completes.
+//
+// Precondition: d.protocolMU must be locked.
+func (d *DAD) CheckDuplicateAddressLocked(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition {
+ if d.configs.DupAddrDetectTransmits == 0 {
+ return stack.DADDisabled
+ }
+
+ ret := stack.DADAlreadyRunning
+ s, ok := d.addresses[addr]
+ if !ok {
+ ret = stack.DADStarting
+
+ remaining := d.configs.DupAddrDetectTransmits
+
+ // Protected by d.protocolMU.
+ done := false
+
+ s = dadState{
+ done: &done,
+ timer: d.opts.Clock.AfterFunc(0, func() {
+ var err tcpip.Error
+ dadDone := remaining == 0
+ if !dadDone {
+ err = d.opts.Protocol.SendDADMessage(addr)
+ }
+
+ d.protocolMU.Lock()
+ defer d.protocolMU.Unlock()
+
+ if done {
+ return
+ }
+
+ s, ok := d.addresses[addr]
+ if !ok {
+ panic(fmt.Sprintf("dad: timer fired but missing state for %s on NIC(%d)", addr, d.opts.NICID))
+ }
+
+ if !dadDone && err == nil {
+ remaining--
+ s.timer.Reset(d.configs.RetransmitTimer)
+ return
+ }
+
+ // At this point we know that either DAD has resolved or we hit an error
+ // sending the last DAD message. Either way, clear the DAD state.
+ done = false
+ s.timer.Stop()
+ delete(d.addresses, addr)
+
+ r := stack.DADResult{Resolved: dadDone, Err: err}
+ for _, h := range s.completionHandlers {
+ h(r)
+ }
+ }),
+ }
+ }
+
+ s.completionHandlers = append(s.completionHandlers, h)
+ d.addresses[addr] = s
+ return ret
+}
+
+// StopLocked stops a currently running DAD process.
+//
+// Precondition: d.protocolMU must be locked.
+func (d *DAD) StopLocked(addr tcpip.Address, aborted bool) {
+ s, ok := d.addresses[addr]
+ if !ok {
+ return
+ }
+
+ *s.done = true
+ s.timer.Stop()
+ delete(d.addresses, addr)
+
+ var err tcpip.Error
+ if aborted {
+ err = &tcpip.ErrAborted{}
+ }
+
+ r := stack.DADResult{Resolved: false, Err: err}
+ for _, h := range s.completionHandlers {
+ h(r)
+ }
+}
+
+// SetConfigsLocked sets the DAD configurations.
+//
+// Precondition: d.protocolMU must be locked.
+func (d *DAD) SetConfigsLocked(c stack.DADConfigurations) {
+ c.Validate()
+ d.configs = c
+}
diff --git a/pkg/tcpip/network/internal/ip/ip_state_autogen.go b/pkg/tcpip/network/internal/ip/ip_state_autogen.go
new file mode 100644
index 000000000..aee77044e
--- /dev/null
+++ b/pkg/tcpip/network/internal/ip/ip_state_autogen.go
@@ -0,0 +1,3 @@
+// automatically generated by stateify.
+
+package ip
diff --git a/pkg/tcpip/network/ipv6/icmp.go b/pkg/tcpip/network/ipv6/icmp.go
index edf4ef4e5..2690644d6 100644
--- a/pkg/tcpip/network/ipv6/icmp.go
+++ b/pkg/tcpip/network/ipv6/icmp.go
@@ -537,6 +537,11 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool) {
// NDP datagrams are very small and ToView() will not incur allocations.
na := header.NDPNeighborAdvert(payload.ToView())
targetAddr := na.TargetAddress()
+
+ e.dad.mu.Lock()
+ e.dad.mu.dad.StopLocked(targetAddr, false /* aborted */)
+ e.dad.mu.Unlock()
+
if e.hasTentativeAddr(targetAddr) {
// We just got an NA from a node that owns an address we are performing
// DAD on, implying the address is not unique. In this case we let the
@@ -866,37 +871,9 @@ func (e *endpoint) LinkAddressRequest(targetAddr, localAddr tcpip.Address, remot
return &tcpip.ErrBadLocalAddress{}
}
- optsSerializer := header.NDPOptionsSerializer{
+ return e.sendNDPNS(localAddr, remoteAddr, targetAddr, remoteLinkAddr, header.NDPOptionsSerializer{
header.NDPSourceLinkLayerAddressOption(e.nic.LinkAddress()),
- }
- neighborSolicitSize := header.ICMPv6NeighborSolicitMinimumSize + optsSerializer.Length()
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: int(e.nic.MaxHeaderLength()) + header.IPv6FixedHeaderSize + neighborSolicitSize,
})
- pkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
- packet := header.ICMPv6(pkt.TransportHeader().Push(neighborSolicitSize))
- packet.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(packet.MessageBody())
- ns.SetTargetAddress(targetAddr)
- ns.Options().Serialize(optsSerializer)
- packet.SetChecksum(header.ICMPv6Checksum(packet, localAddr, remoteAddr, buffer.VectorisedView{}))
-
- if err := addIPHeader(localAddr, remoteAddr, pkt, stack.NetworkHeaderParams{
- Protocol: header.ICMPv6ProtocolNumber,
- TTL: header.NDPHopLimit,
- }, header.IPv6ExtHdrSerializer{}); err != nil {
- panic(fmt.Sprintf("failed to add IP header: %s", err))
- }
-
- stat := e.stats.icmp.packetsSent
-
- if err := e.nic.WritePacketToRemote(remoteLinkAddr, nil /* gso */, ProtocolNumber, pkt); err != nil {
- stat.dropped.Increment()
- return err
- }
-
- stat.neighborSolicit.Increment()
- return nil
}
// ResolveStaticAddress implements stack.LinkAddressResolver.
diff --git a/pkg/tcpip/network/ipv6/ipv6.go b/pkg/tcpip/network/ipv6/ipv6.go
index 5856c9d3c..56bbf1cc3 100644
--- a/pkg/tcpip/network/ipv6/ipv6.go
+++ b/pkg/tcpip/network/ipv6/ipv6.go
@@ -32,6 +32,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/header/parse"
"gvisor.dev/gvisor/pkg/tcpip/network/fragmentation"
"gvisor.dev/gvisor/pkg/tcpip/network/hash"
+ "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
@@ -164,6 +165,7 @@ func getLabel(addr tcpip.Address) uint8 {
panic(fmt.Sprintf("should have a label for address = %s", addr))
}
+var _ stack.DuplicateAddressDetector = (*endpoint)(nil)
var _ stack.LinkAddressResolver = (*endpoint)(nil)
var _ stack.LinkResolvableNetworkEndpoint = (*endpoint)(nil)
var _ stack.GroupAddressableEndpoint = (*endpoint)(nil)
@@ -192,6 +194,23 @@ type endpoint struct {
ndp ndpState
mld mldState
}
+
+ // dad is used to check if an arbitrary address is already assigned to some
+ // neighbor.
+ //
+ // Note: this is different from mu.ndp.dad which is used to perform DAD for
+ // addresses that are assigned to the interface. Removing an address aborts
+ // DAD; if we had used the same state, handlers for a removed address would
+ // not be called with the actual DAD result.
+ //
+ // LOCK ORDERING: mu > dad.mu.
+ dad struct {
+ mu struct {
+ sync.Mutex
+
+ dad ip.DAD
+ }
+ }
}
// NICNameFromID is a function that returns a stable name for the specified NIC,
@@ -226,6 +245,29 @@ type OpaqueInterfaceIdentifierOptions struct {
SecretKey []byte
}
+// CheckDuplicateAddress implements stack.DuplicateAddressDetector.
+func (e *endpoint) CheckDuplicateAddress(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition {
+ e.dad.mu.Lock()
+ defer e.dad.mu.Unlock()
+ return e.dad.mu.dad.CheckDuplicateAddressLocked(addr, h)
+}
+
+// SetDADConfigurations implements stack.DuplicateAddressDetector.
+func (e *endpoint) SetDADConfigurations(c stack.DADConfigurations) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ e.dad.mu.Lock()
+ defer e.dad.mu.Unlock()
+
+ e.mu.ndp.dad.SetConfigsLocked(c)
+ e.dad.mu.dad.SetConfigsLocked(c)
+}
+
+// DuplicateAddressProtocol implements stack.DuplicateAddressDetector.
+func (*endpoint) DuplicateAddressProtocol() tcpip.NetworkProtocolNumber {
+ return ProtocolNumber
+}
+
// HandleLinkResolutionFailure implements stack.LinkResolvableNetworkEndpoint.
func (e *endpoint) HandleLinkResolutionFailure(pkt *stack.PacketBuffer) {
// handleControl expects the entire offending packet to be in the packet
@@ -321,7 +363,7 @@ func (e *endpoint) dupTentativeAddrDetected(addr tcpip.Address) tcpip.Error {
// If the address is a SLAAC address, do not invalidate its SLAAC prefix as an
// attempt will be made to generate a new address for it.
- if err := e.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */); err != nil {
+ if err := e.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */, true /* dadFailure */); err != nil {
return err
}
@@ -525,7 +567,7 @@ func (e *endpoint) stopDADForPermanentAddressesLocked() {
addr := addressEndpoint.AddressWithPrefix().Address
if header.IsV6UnicastAddress(addr) {
- e.mu.ndp.stopDuplicateAddressDetection(addr)
+ e.mu.ndp.stopDuplicateAddressDetection(addr, false /* failed */)
}
return true
@@ -1390,18 +1432,18 @@ func (e *endpoint) RemovePermanentAddress(addr tcpip.Address) tcpip.Error {
return &tcpip.ErrBadLocalAddress{}
}
- return e.removePermanentEndpointLocked(addressEndpoint, true)
+ return e.removePermanentEndpointLocked(addressEndpoint, true /* allowSLAACInvalidation */, false /* dadFailure */)
}
// removePermanentEndpointLocked is like removePermanentAddressLocked except
// it works with a stack.AddressEndpoint.
//
// Precondition: e.mu must be write locked.
-func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEndpoint, allowSLAACInvalidation bool) tcpip.Error {
+func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEndpoint, allowSLAACInvalidation, dadFailure bool) tcpip.Error {
addr := addressEndpoint.AddressWithPrefix()
unicast := header.IsV6UnicastAddress(addr.Address)
if unicast {
- e.mu.ndp.stopDuplicateAddressDetection(addr.Address)
+ e.mu.ndp.stopDuplicateAddressDetection(addr.Address, dadFailure)
// If we are removing an address generated via SLAAC, cleanup
// its SLAAC resources and notify the integrator.
@@ -1747,6 +1789,13 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.Tran
e.mu.addressableEndpointState.Init(e)
e.mu.ndp.init(e)
e.mu.mld.init(e)
+ e.dad.mu.Lock()
+ e.dad.mu.dad.Init(&e.dad.mu, p.options.DADConfigs, ip.DADOptions{
+ Clock: p.stack.Clock(),
+ Protocol: &e.mu.ndp,
+ NICID: nic.ID(),
+ })
+ e.dad.mu.Unlock()
e.mu.Unlock()
stackStats := p.stack.Stats()
@@ -1949,6 +1998,9 @@ type Options struct {
// MLD holds options for MLD.
MLD MLDOptions
+
+ // DADConfigs holds the default DAD configurations used by IPv6 endpoints.
+ DADConfigs stack.DADConfigurations
}
// NewProtocolWithOptions returns an IPv6 network protocol.
diff --git a/pkg/tcpip/network/ipv6/ndp.go b/pkg/tcpip/network/ipv6/ndp.go
index 411a6c862..a55330b7e 100644
--- a/pkg/tcpip/network/ipv6/ndp.go
+++ b/pkg/tcpip/network/ipv6/ndp.go
@@ -23,34 +23,11 @@ import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
const (
- // defaultRetransmitTimer is the default amount of time to wait between
- // sending reachability probes.
- //
- // Default taken from RETRANS_TIMER of RFC 4861 section 10.
- defaultRetransmitTimer = time.Second
-
- // minimumRetransmitTimer is the minimum amount of time to wait between
- // sending reachability probes.
- //
- // 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.
- minimumRetransmitTimer = time.Millisecond
-
- // 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
-
// defaultMaxRtrSolicitations is the default number of Router
// Solicitation messages to send when an IPv6 endpoint becomes enabled.
//
@@ -330,18 +307,6 @@ type NDPDispatcher interface {
// 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 or equal to 1ms.
- RetransmitTimer time.Duration
-
// The number of Router Solicitation messages to send when the IPv6 endpoint
// becomes enabled.
MaxRtrSolicitations uint8
@@ -413,8 +378,6 @@ type NDPConfigurations struct {
// default values.
func DefaultNDPConfigurations() NDPConfigurations {
return NDPConfigurations{
- DupAddrDetectTransmits: defaultDupAddrDetectTransmits,
- RetransmitTimer: defaultRetransmitTimer,
MaxRtrSolicitations: defaultMaxRtrSolicitations,
RtrSolicitationInterval: defaultRtrSolicitationInterval,
MaxRtrSolicitationDelay: defaultMaxRtrSolicitationDelay,
@@ -432,10 +395,6 @@ func DefaultNDPConfigurations() NDPConfigurations {
// validate modifies an NDPConfigurations with valid values. If invalid values
// are present in c, the corresponding default values are used instead.
func (c *NDPConfigurations) validate() {
- if c.RetransmitTimer < minimumRetransmitTimer {
- c.RetransmitTimer = defaultRetransmitTimer
- }
-
if c.RtrSolicitationInterval < minimumRtrSolicitationInterval {
c.RtrSolicitationInterval = defaultRtrSolicitationInterval
}
@@ -476,7 +435,7 @@ type ndpState struct {
configs NDPConfigurations
// The DAD timers to send the next NS message, or resolve the address.
- dad map[tcpip.Address]timer
+ dad ip.DAD
// The default routers discovered through Router Advertisements.
defaultRouters map[tcpip.Address]defaultRouterState
@@ -635,130 +594,46 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE
panic(fmt.Sprintf("ndpdad: addr %s is not tentative on NIC(%d)", addr, ndp.ep.nic.ID()))
}
- // 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 get 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 endpoint.addAddressLocked.
- panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.ep.nic.ID()))
- }
+ ret := ndp.dad.CheckDuplicateAddressLocked(addr, func(r stack.DADResult) {
+ if addressEndpoint.GetKind() != stack.PermanentTentative {
+ // The endpoint should still be marked as tentative since we are still
+ // performing DAD on it.
+ panic(fmt.Sprintf("ndpdad: addr %s is no longer tentative on NIC(%d)", addr, ndp.ep.nic.ID()))
+ }
- if ndp.configs.DupAddrDetectTransmits == 0 {
- addressEndpoint.SetKind(stack.Permanent)
+ if r.Resolved {
+ addressEndpoint.SetKind(stack.Permanent)
+ }
- // Consider DAD to have resolved even if no DAD messages were actually
- // transmitted.
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
- ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, true, nil)
+ ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, r.Resolved, r.Err)
}
- ndp.ep.onAddressAssignedLocked(addr)
- return nil
- }
-
- var remaining remainingCounter
- remaining.init(ndp.configs.DupAddrDetectTransmits)
- // We initially start a timer to fire immediately because some of the DAD work
- // cannot be done while holding the IPv6 endpoint's lock. This is effectively
- // the same as starting a goroutine but we use a timer that fires immediately
- // so we can reset it for the next DAD iteration.
-
- // Protected by ndp.ep.mu.
- done := false
-
- ndp.dad[addr] = timer{
- done: &done,
- timer: ndp.ep.protocol.stack.Clock().AfterFunc(0, func() {
- // Okay to hold this lock while writing packets since we use a different
- // lock per DAD timer so there will not be any lock contention.
- remaining.mu.Lock()
- defer remaining.mu.Unlock()
-
- var err tcpip.Error
- dadDone := remaining.mu.remaining == 0
- if !dadDone {
- snmc := header.SolicitedNodeAddr(addr)
-
- icmp := header.ICMPv6(buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize))
- icmp.SetType(header.ICMPv6NeighborSolicit)
- ns := header.NDPNeighborSolicit(icmp.MessageBody())
- ns.SetTargetAddress(addr)
- icmp.SetChecksum(header.ICMPv6Checksum(icmp, header.IPv6Any, snmc, buffer.VectorisedView{}))
-
- pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
- ReserveHeaderBytes: int(ndp.ep.MaxHeaderLength()),
- Data: buffer.View(icmp).ToVectorisedView(),
- })
-
- sent := ndp.ep.stats.icmp.packetsSent
- if err := addIPHeader(header.IPv6Any, snmc, pkt, stack.NetworkHeaderParams{
- Protocol: header.ICMPv6ProtocolNumber,
- TTL: header.NDPHopLimit,
- }, nil /* extensionHeaders */); err != nil {
- panic(fmt.Sprintf("failed to add IP header: %s", err))
- }
-
- err = ndp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(snmc), nil /* gso */, ProtocolNumber, pkt)
- if err != nil {
- sent.dropped.Increment()
- } else {
- sent.neighborSolicit.Increment()
- }
+ if r.Resolved {
+ if addressEndpoint.ConfigType() == stack.AddressConfigSlaac {
+ // Reset the generation attempts counter as we are starting the
+ // generation of a new address for the SLAAC prefix.
+ ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */)
}
- ndp.ep.mu.Lock()
- defer ndp.ep.mu.Unlock()
-
- if done {
- // DAD was stopped.
- return
- }
-
- timer, ok := ndp.dad[addr]
- if !ok {
- panic(fmt.Sprintf("ndpdad: DAD timer fired but missing state for %s on NIC(%d)", addr, ndp.ep.nic.ID()))
- }
-
- if addressEndpoint.GetKind() != stack.PermanentTentative {
- // The endpoint should still be marked as tentative since we are still
- // performing DAD on it.
- panic(fmt.Sprintf("ndpdad: addr %s is no longer tentative on NIC(%d)", addr, ndp.ep.nic.ID()))
- }
-
- if dadDone {
- // DAD has resolved.
- addressEndpoint.SetKind(stack.Permanent)
- } else if err == nil {
- // DAD is not done and we had no errors when sending the last NDP NS,
- // schedule the next DAD timer.
- remaining.mu.remaining--
- timer.timer.Reset(ndp.configs.RetransmitTimer)
- return
- }
-
- // At this point we know that either DAD is done or we hit an error
- // sending the last NDP NS. Either way, clean up addr's DAD state and let
- // the integrator know DAD has completed.
- delete(ndp.dad, addr)
+ ndp.ep.onAddressAssignedLocked(addr)
+ }
+ })
- if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
- ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, dadDone, err)
- }
+ switch ret {
+ case stack.DADStarting:
+ case stack.DADAlreadyRunning:
+ panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.ep.nic.ID()))
+ case stack.DADDisabled:
+ addressEndpoint.SetKind(stack.Permanent)
- if dadDone {
- if addressEndpoint.ConfigType() == stack.AddressConfigSlaac {
- // Reset the generation attempts counter as we are starting the
- // generation of a new address for the SLAAC prefix.
- ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */)
- }
+ // Consider DAD to have resolved even if no DAD messages were actually
+ // transmitted.
+ if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
+ ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, true, nil)
+ }
- ndp.ep.onAddressAssignedLocked(addr)
- }
- }),
+ ndp.ep.onAddressAssignedLocked(addr)
}
return nil
@@ -772,21 +647,8 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE
// of this function to handle such a scenario.
//
// The IPv6 endpoint that ndp belongs to MUST be locked.
-func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address) {
- timer, ok := ndp.dad[addr]
- if !ok {
- // Not currently performing DAD on addr, just return.
- return
- }
-
- timer.timer.Stop()
- *timer.done = true
- delete(ndp.dad, addr)
-
- // Let the integrator know DAD did not resolve.
- if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
- ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, false, nil)
- }
+func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address, failed bool) {
+ ndp.dad.StopLocked(addr, !failed)
}
// handleRA handles a Router Advertisement message that arrived on the NIC
@@ -1651,7 +1513,7 @@ func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefi
if addressEndpoint := state.stableAddr.addressEndpoint; addressEndpoint != nil {
// Since we are already invalidating the prefix, do not invalidate the
// prefix when removing the address.
- if err := ndp.ep.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */); err != nil {
+ if err := ndp.ep.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */, false /* dadFailure */); err != nil {
panic(fmt.Sprintf("ndp: error removing stable SLAAC address %s: %s", addressEndpoint.AddressWithPrefix(), err))
}
}
@@ -1710,7 +1572,7 @@ func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaa
func (ndp *ndpState) invalidateTempSLAACAddr(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) {
// Since we are already invalidating the address, do not invalidate the
// address when removing the address.
- if err := ndp.ep.removePermanentEndpointLocked(tempAddrState.addressEndpoint, false /* allowSLAACInvalidation */); err != nil {
+ if err := ndp.ep.removePermanentEndpointLocked(tempAddrState.addressEndpoint, false /* allowSLAACInvalidation */, false /* dadFailure */); err != nil {
panic(fmt.Sprintf("error removing temporary SLAAC address %s: %s", tempAddrState.addressEndpoint.AddressWithPrefix(), err))
}
@@ -1942,13 +1804,17 @@ func (ndp *ndpState) stopSolicitingRouters() {
}
func (ndp *ndpState) init(ep *endpoint) {
- if ndp.dad != nil {
+ if ndp.defaultRouters != nil {
panic("attempted to initialize NDP state twice")
}
ndp.ep = ep
ndp.configs = ep.protocol.options.NDPConfigs
- ndp.dad = make(map[tcpip.Address]timer)
+ ndp.dad.Init(&ndp.ep.mu, ep.protocol.options.DADConfigs, ip.DADOptions{
+ Clock: ep.protocol.stack.Clock(),
+ Protocol: ndp,
+ NICID: ep.nic.ID(),
+ })
ndp.defaultRouters = make(map[tcpip.Address]defaultRouterState)
ndp.onLinkPrefixes = make(map[tcpip.Subnet]onLinkPrefixState)
ndp.slaacPrefixes = make(map[tcpip.Subnet]slaacPrefixState)
@@ -1958,3 +1824,38 @@ func (ndp *ndpState) init(ep *endpoint) {
ndp.temporaryAddressDesyncFactor = time.Duration(rand.Int63n(int64(MaxDesyncFactor)))
}
}
+
+func (ndp *ndpState) SendDADMessage(addr tcpip.Address) tcpip.Error {
+ snmc := header.SolicitedNodeAddr(addr)
+ return ndp.ep.sendNDPNS(header.IPv6Any, snmc, addr, header.EthernetAddressFromMulticastIPv6Address(snmc), nil /* opts */)
+}
+
+func (e *endpoint) sendNDPNS(srcAddr, dstAddr, targetAddr tcpip.Address, remoteLinkAddr tcpip.LinkAddress, opts header.NDPOptionsSerializer) tcpip.Error {
+ icmp := header.ICMPv6(buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize + opts.Length()))
+ icmp.SetType(header.ICMPv6NeighborSolicit)
+ ns := header.NDPNeighborSolicit(icmp.MessageBody())
+ ns.SetTargetAddress(targetAddr)
+ ns.Options().Serialize(opts)
+ icmp.SetChecksum(header.ICMPv6Checksum(icmp, srcAddr, dstAddr, buffer.VectorisedView{}))
+
+ pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
+ ReserveHeaderBytes: int(e.MaxHeaderLength()),
+ Data: buffer.View(icmp).ToVectorisedView(),
+ })
+
+ if err := addIPHeader(srcAddr, dstAddr, pkt, stack.NetworkHeaderParams{
+ Protocol: header.ICMPv6ProtocolNumber,
+ TTL: header.NDPHopLimit,
+ }, nil /* extensionHeaders */); err != nil {
+ panic(fmt.Sprintf("failed to add IP header: %s", err))
+ }
+
+ sent := e.stats.icmp.packetsSent
+ err := e.nic.WritePacketToRemote(remoteLinkAddr, nil /* gso */, ProtocolNumber, pkt)
+ if err != nil {
+ sent.dropped.Increment()
+ } else {
+ sent.neighborSolicit.Increment()
+ }
+ return err
+}
diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go
index 00cfba35a..f66db16a7 100644
--- a/pkg/tcpip/stack/nic.go
+++ b/pkg/tcpip/stack/nic.go
@@ -55,8 +55,9 @@ type nic struct {
// The network endpoints themselves may be modified by calling the interface's
// methods, but the map reference and entries must be constant.
- networkEndpoints map[tcpip.NetworkProtocolNumber]NetworkEndpoint
- linkAddrResolvers map[tcpip.NetworkProtocolNumber]*linkResolver
+ networkEndpoints map[tcpip.NetworkProtocolNumber]NetworkEndpoint
+ linkAddrResolvers map[tcpip.NetworkProtocolNumber]*linkResolver
+ duplicateAddressDetectors map[tcpip.NetworkProtocolNumber]DuplicateAddressDetector
// enabled is set to 1 when the NIC is enabled and 0 when it is disabled.
//
@@ -145,13 +146,14 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC
nic := &nic{
LinkEndpoint: ep,
- stack: stack,
- id: id,
- name: name,
- context: ctx,
- stats: makeNICStats(),
- networkEndpoints: make(map[tcpip.NetworkProtocolNumber]NetworkEndpoint),
- linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]*linkResolver),
+ stack: stack,
+ id: id,
+ name: name,
+ context: ctx,
+ stats: makeNICStats(),
+ networkEndpoints: make(map[tcpip.NetworkProtocolNumber]NetworkEndpoint),
+ linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]*linkResolver),
+ duplicateAddressDetectors: make(map[tcpip.NetworkProtocolNumber]DuplicateAddressDetector),
}
nic.linkResQueue.init(nic)
nic.mu.packetEPs = make(map[tcpip.NetworkProtocolNumber]*packetEndpointList)
@@ -176,6 +178,10 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC
nic.linkAddrResolvers[r.LinkAddressProtocol()] = l
}
}
+
+ if d, ok := netEP.(DuplicateAddressDetector); ok {
+ nic.duplicateAddressDetectors[d.DuplicateAddressProtocol()] = d
+ }
}
nic.LinkEndpoint.Attach(nic)
@@ -991,3 +997,12 @@ func (n *nic) CheckLocalAddress(protocol tcpip.NetworkProtocolNumber, addr tcpip
return false
}
+
+func (n *nic) checkDuplicateAddress(protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, h DADCompletionHandler) (DADCheckAddressDisposition, tcpip.Error) {
+ d, ok := n.duplicateAddressDetectors[protocol]
+ if !ok {
+ return 0, &tcpip.ErrNotSupported{}
+ }
+
+ return d.CheckDuplicateAddress(addr, h), nil
+}
diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go
index 2bc1c4270..43e9e4beb 100644
--- a/pkg/tcpip/stack/registration.go
+++ b/pkg/tcpip/stack/registration.go
@@ -16,6 +16,7 @@ package stack
import (
"fmt"
+ "time"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
@@ -851,7 +852,97 @@ type InjectableLinkEndpoint interface {
InjectOutbound(dest tcpip.Address, packet []byte) tcpip.Error
}
-// A LinkAddressResolver handles link address resolution for a network protocol.
+// DADResult is the result of a duplicate address detection process.
+type DADResult struct {
+ // Resolved is true when DAD completed without detecting a duplicate address
+ // on the link.
+ //
+ // Ignored when Err is non-nil.
+ Resolved bool
+
+ // Err is an error encountered while performing DAD.
+ Err tcpip.Error
+}
+
+// DADCompletionHandler is a handler for DAD completion.
+type DADCompletionHandler func(DADResult)
+
+// DADCheckAddressDisposition enumerates the possible return values from
+// DAD.CheckDuplicateAddress.
+type DADCheckAddressDisposition int
+
+const (
+ _ DADCheckAddressDisposition = iota
+
+ // DADDisabled indicates that DAD is disabled.
+ DADDisabled
+
+ // DADStarting indicates that DAD is starting for an address.
+ DADStarting
+
+ // DADAlreadyRunning indicates that DAD was already started for an address.
+ DADAlreadyRunning
+)
+
+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
+)
+
+// DADConfigurations holds configurations for duplicate address detection.
+type DADConfigurations 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 or equal to 1ms.
+ RetransmitTimer time.Duration
+}
+
+// DefaultDADConfigurations returns the default DAD configurations.
+func DefaultDADConfigurations() DADConfigurations {
+ return DADConfigurations{
+ DupAddrDetectTransmits: defaultDupAddrDetectTransmits,
+ RetransmitTimer: defaultRetransmitTimer,
+ }
+}
+
+// Validate modifies the configuration with valid values. If invalid values are
+// present in the configurations, the corresponding default values are used
+// instead.
+func (c *DADConfigurations) Validate() {
+ if c.RetransmitTimer < minimumRetransmitTimer {
+ c.RetransmitTimer = defaultRetransmitTimer
+ }
+}
+
+// DuplicateAddressDetector handles checking if an address is already assigned
+// to some neighboring node on the link.
+type DuplicateAddressDetector interface {
+ // CheckDuplicateAddress checks if an address is assigned to a neighbor.
+ //
+ // If DAD is already being performed for the address, the handler will be
+ // called with the result of the original DAD request.
+ CheckDuplicateAddress(tcpip.Address, DADCompletionHandler) DADCheckAddressDisposition
+
+ // SetDADConfiguations sets the configurations for DAD.
+ SetDADConfigurations(c DADConfigurations)
+
+ // DuplicateAddressProtocol returns the network protocol the receiver can
+ // perform duplicate address detection for.
+ DuplicateAddressProtocol() tcpip.NetworkProtocolNumber
+}
+
+// LinkAddressResolver handles link address resolution for a network protocol.
type LinkAddressResolver interface {
// LinkAddressRequest sends a request for the link address of the target
// address. The request is broadcasted on the local network if a remote link
diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go
index 46c1817ea..674c9a1ff 100644
--- a/pkg/tcpip/stack/stack.go
+++ b/pkg/tcpip/stack/stack.go
@@ -1466,6 +1466,17 @@ func (s *Stack) CheckNetworkProtocol(protocol tcpip.NetworkProtocolNumber) bool
return ok
}
+// CheckDuplicateAddress performs duplicate address detection for the address on
+// the specified interface.
+func (s *Stack) CheckDuplicateAddress(nicID tcpip.NICID, protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, h DADCompletionHandler) (DADCheckAddressDisposition, tcpip.Error) {
+ nic, ok := s.nics[nicID]
+ if !ok {
+ return 0, &tcpip.ErrUnknownNICID{}
+ }
+
+ return nic.checkDuplicateAddress(protocol, addr, h)
+}
+
// CheckLocalAddress determines if the given local address exists, and if it
// does, returns the id of the NIC it's bound to. Returns 0 if the address
// does not exist.