summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/network/ipv6
diff options
context:
space:
mode:
authorGhanan Gowripalan <ghanan@google.com>2021-02-08 19:03:54 -0800
committergVisor bot <gvisor-bot@google.com>2021-02-08 19:05:45 -0800
commit39251f31cb92d6c2b053416d04e195e290b106f2 (patch)
treebf3c80dc631655f48fc0b9686cfe2af2e6a4ab74 /pkg/tcpip/network/ipv6
parentcfa4633c3d206aa2f9abdaac60d053162244ee6d (diff)
Support performing DAD for any address
...as long as the network protocol supports duplicate address detection. This CL provides the facilities for a netstack integrator to perform DAD. DHCP recommends that clients effectively perform DAD before accepting an offer. As per RFC 2131 section 4.4.1 pg 38, The client SHOULD perform a check on the suggested address to ensure that the address is not already in use. For example, if the client is on a network that supports ARP, the client may issue an ARP request for the suggested request. The implementation of ARP-based IPv4 DAD effectively operates the same as IPv6's NDP DAD - using ARP requests and responses in place of NDP neighbour solicitations and advertisements, respectively. DAD performed by calls to (*Stack).CheckDuplicateAddress don't interfere with DAD performed when a new IPv6 address is added. This is so that integrator requests to check for duplicate addresses aren't unexpectedly aborted when addresses are removed. A network package internal package provides protocol agnostic DAD state management that specific protocols that provide DAD can use. Fixes #4550. Tests: - internal/ip_test.* - integration_test.TestDAD - arp_test.TestDADARPRequestPacket - ipv6.TestCheckDuplicateAddress PiperOrigin-RevId: 356405593
Diffstat (limited to 'pkg/tcpip/network/ipv6')
-rw-r--r--pkg/tcpip/network/ipv6/BUILD1
-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/mld_test.go2
-rw-r--r--pkg/tcpip/network/ipv6/ndp.go253
-rw-r--r--pkg/tcpip/network/ipv6/ndp_test.go108
6 files changed, 250 insertions, 211 deletions
diff --git a/pkg/tcpip/network/ipv6/BUILD b/pkg/tcpip/network/ipv6/BUILD
index 0c5f8d683..d75b0b8de 100644
--- a/pkg/tcpip/network/ipv6/BUILD
+++ b/pkg/tcpip/network/ipv6/BUILD
@@ -21,6 +21,7 @@ go_library(
"//pkg/tcpip/header/parse",
"//pkg/tcpip/network/fragmentation",
"//pkg/tcpip/network/hash",
+ "//pkg/tcpip/network/internal/ip",
"//pkg/tcpip/network/ip",
"//pkg/tcpip/stack",
],
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/mld_test.go b/pkg/tcpip/network/ipv6/mld_test.go
index f6ffa7133..fe39555e0 100644
--- a/pkg/tcpip/network/ipv6/mld_test.go
+++ b/pkg/tcpip/network/ipv6/mld_test.go
@@ -126,7 +126,7 @@ func TestSendQueuedMLDReports(t *testing.T) {
clock := faketime.NewManualClock()
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
- NDPConfigs: ipv6.NDPConfigurations{
+ DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: test.dadTransmits,
RetransmitTimer: test.retransmitTimer,
},
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/network/ipv6/ndp_test.go b/pkg/tcpip/network/ipv6/ndp_test.go
index 5ff247653..ce20af0e3 100644
--- a/pkg/tcpip/network/ipv6/ndp_test.go
+++ b/pkg/tcpip/network/ipv6/ndp_test.go
@@ -24,6 +24,7 @@ import (
"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/stack"
@@ -1222,3 +1223,110 @@ func TestRouterAdvertValidation(t *testing.T) {
})
}
}
+
+// TestCheckDuplicateAddress checks that calls to CheckDuplicateAddress and DAD
+// performed when adding new addresses do not interfere with each other.
+func TestCheckDuplicateAddress(t *testing.T) {
+ const nicID = 1
+
+ clock := faketime.NewManualClock()
+ dadConfigs := stack.DADConfigurations{
+ DupAddrDetectTransmits: 1,
+ RetransmitTimer: time.Second,
+ }
+ s := stack.New(stack.Options{
+ Clock: clock,
+ NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocolWithOptions(Options{
+ DADConfigs: dadConfigs,
+ })},
+ })
+ // This test is expected to send at max 2 DAD messages. We allow an extra
+ // packet to be stored to catch unexpected packets.
+ e := channel.New(3, header.IPv6MinimumMTU, linkAddr0)
+ e.LinkEPCapabilities |= stack.CapabilityResolutionRequired
+ if err := s.CreateNIC(nicID, e); err != nil {
+ t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
+ }
+
+ dadPacketsSent := 1
+ if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
+ t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
+ }
+
+ // Start DAD for the address we just added.
+ //
+ // Even though the stack will perform DAD before the added address transitions
+ // from tentative to assigned, this DAD request should be independent of that.
+ ch := make(chan stack.DADResult, 3)
+ dadRequestsMade := 1
+ dadPacketsSent++
+ if res, err := s.CheckDuplicateAddress(nicID, ProtocolNumber, lladdr0, func(r stack.DADResult) {
+ ch <- r
+ }); err != nil {
+ t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, ProtocolNumber, lladdr0, err)
+ } else if res != stack.DADStarting {
+ t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADStarting)
+ }
+
+ // Remove the address and make sure our DAD request was not stopped.
+ if err := s.RemoveAddress(nicID, lladdr0); err != nil {
+ t.Fatalf("RemoveAddress(%d, %s): %s", nicID, lladdr0, err)
+ }
+ // Should not restart DAD since we already requested DAD above - the handler
+ // should be called when the original request compeletes so we should not send
+ // an extra DAD message here.
+ dadRequestsMade++
+ if res, err := s.CheckDuplicateAddress(nicID, ProtocolNumber, lladdr0, func(r stack.DADResult) {
+ ch <- r
+ }); err != nil {
+ t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, ProtocolNumber, lladdr0, err)
+ } else if res != stack.DADAlreadyRunning {
+ t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADAlreadyRunning)
+ }
+
+ // Wait for DAD to resolve.
+ clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer)
+ for i := 0; i < dadRequestsMade; i++ {
+ if diff := cmp.Diff(stack.DADResult{Resolved: true}, <-ch); diff != "" {
+ t.Errorf("(i=%d) DAD result mismatch (-want +got):\n%s", i, diff)
+ }
+ }
+ // Should have no more results.
+ select {
+ case r := <-ch:
+ t.Errorf("unexpectedly got an extra DAD result; r = %#v", r)
+ default:
+ }
+
+ snmc := header.SolicitedNodeAddr(lladdr0)
+ remoteLinkAddr := header.EthernetAddressFromMulticastIPv6Address(snmc)
+
+ for i := 0; i < dadPacketsSent; i++ {
+ p, ok := e.Read()
+ if !ok {
+ t.Fatalf("expected %d-th DAD message", i)
+ }
+
+ if p.Proto != header.IPv6ProtocolNumber {
+ t.Errorf("(i=%d) got p.Proto = %d, want = %d", i, p.Proto, header.IPv6ProtocolNumber)
+ }
+
+ if p.Route.RemoteLinkAddress != remoteLinkAddr {
+ t.Errorf("(i=%d) got p.Route.RemoteLinkAddress = %s, want = %s", i, p.Route.RemoteLinkAddress, remoteLinkAddr)
+ }
+
+ checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
+ checker.SrcAddr(header.IPv6Any),
+ checker.DstAddr(snmc),
+ checker.TTL(header.NDPHopLimit),
+ checker.NDPNS(
+ checker.NDPNSTargetAddress(lladdr0),
+ checker.NDPNSOptions(nil),
+ ))
+ }
+
+ // Should have no more packets.
+ if p, ok := e.Read(); ok {
+ t.Errorf("got unexpected packet = %#v", p)
+ }
+}