summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/network
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
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')
-rw-r--r--pkg/tcpip/network/arp/BUILD2
-rw-r--r--pkg/tcpip/network/arp/arp.go77
-rw-r--r--pkg/tcpip/network/arp/arp_test.go50
-rw-r--r--pkg/tcpip/network/internal/ip/BUILD28
-rw-r--r--pkg/tcpip/network/internal/ip/duplicate_address_detection.go172
-rw-r--r--pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go279
-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
12 files changed, 852 insertions, 217 deletions
diff --git a/pkg/tcpip/network/arp/BUILD b/pkg/tcpip/network/arp/BUILD
index 933845269..29c8cdffd 100644
--- a/pkg/tcpip/network/arp/BUILD
+++ b/pkg/tcpip/network/arp/BUILD
@@ -10,10 +10,12 @@ go_library(
],
visibility = ["//visibility:public"],
deps = [
+ "//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//pkg/tcpip/header/parse",
+ "//pkg/tcpip/network/internal/ip",
"//pkg/tcpip/stack",
],
)
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/arp/arp_test.go b/pkg/tcpip/network/arp/arp_test.go
index c8b9ff9fc..018d6a578 100644
--- a/pkg/tcpip/network/arp/arp_test.go
+++ b/pkg/tcpip/network/arp/arp_test.go
@@ -659,3 +659,53 @@ func TestLinkAddressRequest(t *testing.T) {
})
}
}
+
+func TestDADARPRequestPacket(t *testing.T) {
+ s := stack.New(stack.Options{
+ NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocolWithOptions(arp.Options{
+ DADConfigs: stack.DADConfigurations{
+ DupAddrDetectTransmits: 1,
+ RetransmitTimer: time.Second,
+ },
+ }), ipv4.NewProtocol},
+ })
+ e := channel.New(1, defaultMTU, stackLinkAddr)
+ if err := s.CreateNIC(nicID, e); err != nil {
+ t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err)
+ }
+
+ if res, err := s.CheckDuplicateAddress(nicID, header.IPv4ProtocolNumber, remoteAddr, func(stack.DADResult) {}); err != nil {
+ t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, header.IPv4ProtocolNumber, remoteAddr, err)
+ } else if res != stack.DADStarting {
+ t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, header.IPv4ProtocolNumber, remoteAddr, res, stack.DADStarting)
+ }
+
+ pkt, ok := e.ReadContext(context.Background())
+ if !ok {
+ t.Fatal("expected to send an ARP request")
+ }
+
+ if pkt.Route.RemoteLinkAddress != header.EthernetBroadcastAddress {
+ t.Errorf("got pkt.Route.RemoteLinkAddress = %s, want = %s", pkt.Route.RemoteLinkAddress, header.EthernetBroadcastAddress)
+ }
+
+ req := header.ARP(stack.PayloadSince(pkt.Pkt.NetworkHeader()))
+ if !req.IsValid() {
+ t.Errorf("got req.IsValid() = false, want = true")
+ }
+ if got := req.Op(); got != header.ARPRequest {
+ t.Errorf("got req.Op() = %d, want = %d", got, header.ARPRequest)
+ }
+ if got := tcpip.LinkAddress(req.HardwareAddressSender()); got != stackLinkAddr {
+ t.Errorf("got req.HardwareAddressSender() = %s, want = %s", got, stackLinkAddr)
+ }
+ if got := tcpip.Address(req.ProtocolAddressSender()); got != header.IPv4Any {
+ t.Errorf("got req.ProtocolAddressSender() = %s, want = %s", got, header.IPv4Any)
+ }
+ if got, want := tcpip.LinkAddress(req.HardwareAddressTarget()), tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"); got != want {
+ t.Errorf("got req.HardwareAddressTarget() = %s, want = %s", got, want)
+ }
+ if got := tcpip.Address(req.ProtocolAddressTarget()); got != remoteAddr {
+ t.Errorf("got req.ProtocolAddressTarget() = %s, want = %s", got, remoteAddr)
+ }
+}
diff --git a/pkg/tcpip/network/internal/ip/BUILD b/pkg/tcpip/network/internal/ip/BUILD
new file mode 100644
index 000000000..0f55a9770
--- /dev/null
+++ b/pkg/tcpip/network/internal/ip/BUILD
@@ -0,0 +1,28 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "ip",
+ srcs = ["duplicate_address_detection.go"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//pkg/sync",
+ "//pkg/tcpip",
+ "//pkg/tcpip/stack",
+ ],
+)
+
+go_test(
+ name = "ip_x_test",
+ size = "small",
+ srcs = ["duplicate_address_detection_test.go"],
+ deps = [
+ ":ip",
+ "//pkg/sync",
+ "//pkg/tcpip",
+ "//pkg/tcpip/faketime",
+ "//pkg/tcpip/stack",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ ],
+)
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/duplicate_address_detection_test.go b/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go
new file mode 100644
index 000000000..18c357b56
--- /dev/null
+++ b/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go
@@ -0,0 +1,279 @@
+// 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_test
+
+import (
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "gvisor.dev/gvisor/pkg/sync"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/faketime"
+ "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip"
+ "gvisor.dev/gvisor/pkg/tcpip/stack"
+)
+
+type mockDADProtocol struct {
+ t *testing.T
+
+ mu struct {
+ sync.Mutex
+
+ dad ip.DAD
+ sendCount map[tcpip.Address]int
+ }
+}
+
+func (m *mockDADProtocol) init(t *testing.T, c stack.DADConfigurations, opts ip.DADOptions) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ m.t = t
+ opts.Protocol = m
+ m.mu.dad.Init(&m.mu, c, opts)
+ m.initLocked()
+}
+
+func (m *mockDADProtocol) initLocked() {
+ m.mu.sendCount = make(map[tcpip.Address]int)
+}
+
+func (m *mockDADProtocol) SendDADMessage(addr tcpip.Address) tcpip.Error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ m.mu.sendCount[addr]++
+ return nil
+}
+
+func (m *mockDADProtocol) check(addrs []tcpip.Address) string {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ sendCount := make(map[tcpip.Address]int)
+ for _, a := range addrs {
+ sendCount[a]++
+ }
+
+ diff := cmp.Diff(sendCount, m.mu.sendCount)
+ m.initLocked()
+ return diff
+}
+
+func (m *mockDADProtocol) checkDuplicateAddress(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return m.mu.dad.CheckDuplicateAddressLocked(addr, h)
+}
+
+func (m *mockDADProtocol) stop(addr tcpip.Address, aborted bool) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ m.mu.dad.StopLocked(addr, aborted)
+}
+
+func (m *mockDADProtocol) setConfigs(c stack.DADConfigurations) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ m.mu.dad.SetConfigsLocked(c)
+}
+
+const (
+ addr1 = tcpip.Address("\x01")
+ addr2 = tcpip.Address("\x02")
+ addr3 = tcpip.Address("\x03")
+ addr4 = tcpip.Address("\x04")
+)
+
+type dadResult struct {
+ Addr tcpip.Address
+ R stack.DADResult
+}
+
+func handler(ch chan<- dadResult, a tcpip.Address) func(stack.DADResult) {
+ return func(r stack.DADResult) {
+ ch <- dadResult{Addr: a, R: r}
+ }
+}
+
+func TestDADCheckDuplicateAddress(t *testing.T) {
+ var dad mockDADProtocol
+ clock := faketime.NewManualClock()
+ dad.init(t, stack.DADConfigurations{}, ip.DADOptions{
+ Clock: clock,
+ })
+
+ ch := make(chan dadResult, 2)
+
+ // DAD should initially be disabled.
+ if res := dad.checkDuplicateAddress(addr1, handler(nil, "")); res != stack.DADDisabled {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADDisabled)
+ }
+ // Wait for any initially fired timers to complete.
+ clock.Advance(0)
+ if diff := dad.check(nil); diff != "" {
+ t.Errorf("dad check mismatch (-want +got):\n%s", diff)
+ }
+
+ // Enable and request DAD.
+ dadConfigs1 := stack.DADConfigurations{
+ DupAddrDetectTransmits: 1,
+ RetransmitTimer: time.Second,
+ }
+ dad.setConfigs(dadConfigs1)
+ if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
+ }
+ clock.Advance(0)
+ if diff := dad.check([]tcpip.Address{addr1}); diff != "" {
+ t.Errorf("dad check mismatch (-want +got):\n%s", diff)
+ }
+ // The second request for DAD on the same address should use the original
+ // request since it has not completed yet.
+ if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADAlreadyRunning {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADAlreadyRunning)
+ }
+ clock.Advance(0)
+ if diff := dad.check(nil); diff != "" {
+ t.Errorf("dad check mismatch (-want +got):\n%s", diff)
+ }
+
+ dadConfigs2 := stack.DADConfigurations{
+ DupAddrDetectTransmits: 2,
+ RetransmitTimer: time.Second,
+ }
+ dad.setConfigs(dadConfigs2)
+ // A new address should start a new DAD process.
+ if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
+ }
+ clock.Advance(0)
+ if diff := dad.check([]tcpip.Address{addr2}); diff != "" {
+ t.Errorf("dad check mismatch (-want +got):\n%s", diff)
+ }
+
+ // Make sure DAD for addr1 only resolves after the expected timeout.
+ const delta = time.Nanosecond
+ dadConfig1Duration := time.Duration(dadConfigs1.DupAddrDetectTransmits) * dadConfigs1.RetransmitTimer
+ clock.Advance(dadConfig1Duration - delta)
+ select {
+ case r := <-ch:
+ t.Fatalf("unexpectedly got a DAD result before the expected timeout of %s; r = %#v", dadConfig1Duration, r)
+ default:
+ }
+ clock.Advance(delta)
+ for i := 0; i < 2; i++ {
+ if diff := cmp.Diff(dadResult{Addr: addr1, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" {
+ t.Errorf("(i=%d) dad result mismatch (-want +got):\n%s", i, diff)
+ }
+ }
+
+ // Make sure DAD for addr2 only resolves after the expected timeout.
+ dadConfig2Duration := time.Duration(dadConfigs2.DupAddrDetectTransmits) * dadConfigs2.RetransmitTimer
+ clock.Advance(dadConfig2Duration - dadConfig1Duration - delta)
+ select {
+ case r := <-ch:
+ t.Fatalf("unexpectedly got a DAD result before the expected timeout of %s; r = %#v", dadConfig2Duration, r)
+ default:
+ }
+ clock.Advance(delta)
+ if diff := cmp.Diff(dadResult{Addr: addr2, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" {
+ t.Errorf("dad result mismatch (-want +got):\n%s", diff)
+ }
+
+ // Should be able to restart DAD for addr2 after it resolved.
+ if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
+ }
+ clock.Advance(0)
+ if diff := dad.check([]tcpip.Address{addr2, addr2}); diff != "" {
+ t.Errorf("dad check mismatch (-want +got):\n%s", diff)
+ }
+ clock.Advance(dadConfig2Duration)
+ if diff := cmp.Diff(dadResult{Addr: addr2, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" {
+ t.Errorf("dad result mismatch (-want +got):\n%s", diff)
+ }
+
+ // Should not have anymore results.
+ select {
+ case r := <-ch:
+ t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r)
+ default:
+ }
+}
+
+func TestDADStop(t *testing.T) {
+ var dad mockDADProtocol
+ clock := faketime.NewManualClock()
+ dadConfigs := stack.DADConfigurations{
+ DupAddrDetectTransmits: 1,
+ RetransmitTimer: time.Second,
+ }
+ dad.init(t, dadConfigs, ip.DADOptions{
+ Clock: clock,
+ })
+
+ ch := make(chan dadResult, 1)
+
+ if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
+ }
+ if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
+ }
+ if res := dad.checkDuplicateAddress(addr3, handler(ch, addr3)); res != stack.DADStarting {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
+ }
+ clock.Advance(0)
+ if diff := dad.check([]tcpip.Address{addr1, addr2, addr3}); diff != "" {
+ t.Errorf("dad check mismatch (-want +got):\n%s", diff)
+ }
+
+ dad.stop(addr1, true /* aborted */)
+ if diff := cmp.Diff(dadResult{Addr: addr1, R: stack.DADResult{Resolved: false, Err: &tcpip.ErrAborted{}}}, <-ch); diff != "" {
+ t.Errorf("dad result mismatch (-want +got):\n%s", diff)
+ }
+
+ dad.stop(addr2, false /* aborted */)
+ if diff := cmp.Diff(dadResult{Addr: addr2, R: stack.DADResult{Resolved: false, Err: nil}}, <-ch); diff != "" {
+ t.Errorf("dad result mismatch (-want +got):\n%s", diff)
+ }
+
+ dadResolutionDuration := time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer
+ clock.Advance(dadResolutionDuration)
+ if diff := cmp.Diff(dadResult{Addr: addr3, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" {
+ t.Errorf("dad result mismatch (-want +got):\n%s", diff)
+ }
+
+ // Should be able to restart DAD for an address we stopped DAD on.
+ if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
+ t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
+ }
+ clock.Advance(0)
+ if diff := dad.check([]tcpip.Address{addr1}); diff != "" {
+ t.Errorf("dad check mismatch (-want +got):\n%s", diff)
+ }
+ clock.Advance(dadResolutionDuration)
+ if diff := cmp.Diff(dadResult{Addr: addr1, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" {
+ t.Errorf("dad result mismatch (-want +got):\n%s", diff)
+ }
+
+ // Should not have anymore updates.
+ select {
+ case r := <-ch:
+ t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r)
+ default:
+ }
+}
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)
+ }
+}