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
This commit is contained in:
Ghanan Gowripalan 2021-02-08 19:03:54 -08:00 committed by gVisor bot
parent cfa4633c3d
commit 39251f31cb
18 changed files with 1185 additions and 286 deletions

View File

@ -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",
],
)

View File

@ -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)
}
// NewProtocol returns an ARP network protocol.
func NewProtocol(s *stack.Stack) stack.NetworkProtocol {
return &protocol{
stack: s,
// 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 NewProtocolWithOptions(Options{})(s)
}

View File

@ -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)
}
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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:
}
}

View File

@ -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",
],

View File

@ -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.

View File

@ -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.

View File

@ -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,
},

View File

@ -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,19 +594,37 @@ 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 {
if r.Resolved {
addressEndpoint.SetKind(stack.Permanent)
}
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, r.Resolved, r.Err)
}
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.onAddressAssignedLocked(addr)
}
})
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)
// Consider DAD to have resolved even if no DAD messages were actually
@ -657,108 +634,6 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE
}
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()
}
}
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)
if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil {
ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, dadDone, err)
}
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 */)
}
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
}

View File

@ -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)
}
}

View File

@ -424,7 +424,7 @@ func TestDADResolve(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
NDPDisp: &ndpDisp,
NDPConfigs: ipv6.NDPConfigurations{
DADConfigs: stack.DADConfigurations{
RetransmitTimer: test.retransTimer,
DupAddrDetectTransmits: test.dupAddrDetectTransmits,
},
@ -642,14 +642,14 @@ func TestDADFail(t *testing.T) {
ndpDisp := ndpDispatcher{
dadC: make(chan ndpDADEvent, 1),
}
ndpConfigs := ipv6.DefaultNDPConfigurations()
ndpConfigs.RetransmitTimer = time.Second * 2
dadConfigs := stack.DefaultDADConfigurations()
dadConfigs.RetransmitTimer = time.Second * 2
e := channel.New(0, 1280, linkAddr1)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
NDPDisp: &ndpDisp,
NDPConfigs: ndpConfigs,
DADConfigs: dadConfigs,
})},
})
if err := s.CreateNIC(nicID, e); err != nil {
@ -677,7 +677,7 @@ func TestDADFail(t *testing.T) {
// Wait for DAD to fail and make sure the address did
// not get resolved.
select {
case <-time.After(time.Duration(ndpConfigs.DupAddrDetectTransmits)*ndpConfigs.RetransmitTimer + time.Second):
case <-time.After(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer + time.Second):
// If we don't get a failure event after the
// expected resolution time + extra 1s buffer,
// something is wrong.
@ -748,7 +748,7 @@ func TestDADStop(t *testing.T) {
dadC: make(chan ndpDADEvent, 1),
}
ndpConfigs := ipv6.NDPConfigurations{
dadConfigs := stack.DADConfigurations{
RetransmitTimer: time.Second,
DupAddrDetectTransmits: 2,
}
@ -757,7 +757,7 @@ func TestDADStop(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
NDPDisp: &ndpDisp,
NDPConfigs: ndpConfigs,
DADConfigs: dadConfigs,
})},
})
if err := s.CreateNIC(nicID, e); err != nil {
@ -777,12 +777,12 @@ func TestDADStop(t *testing.T) {
// Wait for DAD to fail (since the address was removed during DAD).
select {
case <-time.After(time.Duration(ndpConfigs.DupAddrDetectTransmits)*ndpConfigs.RetransmitTimer + time.Second):
case <-time.After(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer + time.Second):
// If we don't get a failure event after the expected resolution
// time + extra 1s buffer, something is wrong.
t.Fatal("timed out waiting for DAD failure")
case e := <-ndpDisp.dadC:
if diff := checkDADEvent(e, nicID, addr1, false, nil); diff != "" {
if diff := checkDADEvent(e, nicID, addr1, false, &tcpip.ErrAborted{}); diff != "" {
t.Errorf("dad event mismatch (-want +got):\n%s", diff)
}
}
@ -865,16 +865,15 @@ func TestSetNDPConfigurations(t *testing.T) {
t.Fatalf("CreateNIC(%d, _) = %s", nicID2, err)
}
// Update the NDP configurations on NIC(1) to use DAD.
configs := ipv6.NDPConfigurations{
DupAddrDetectTransmits: test.dupAddrDetectTransmits,
RetransmitTimer: test.retransmitTimer,
}
// Update the configurations on NIC(1) to use DAD.
if ipv6Ep, err := s.GetNetworkEndpoint(nicID1, header.IPv6ProtocolNumber); err != nil {
t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID1, header.IPv6ProtocolNumber, err)
} else {
ndpEP := ipv6Ep.(ipv6.NDPEndpoint)
ndpEP.SetNDPConfigurations(configs)
dad := ipv6Ep.(stack.DuplicateAddressDetector)
dad.SetDADConfigurations(stack.DADConfigurations{
DupAddrDetectTransmits: test.dupAddrDetectTransmits,
RetransmitTimer: test.retransmitTimer,
})
}
// Created after updating NIC(1)'s NDP configurations
@ -1903,9 +1902,11 @@ func TestAutoGenTempAddr(t *testing.T) {
e := channel.New(0, 1280, linkAddr1)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: test.dupAddrTransmits,
RetransmitTimer: test.retransmitTimer,
},
NDPConfigs: ipv6.NDPConfigurations{
DupAddrDetectTransmits: test.dupAddrTransmits,
RetransmitTimer: test.retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenTempGlobalAddresses: true,
@ -2202,9 +2203,11 @@ func TestNoAutoGenTempAddrWithoutStableAddr(t *testing.T) {
e := channel.New(0, 1280, linkAddr1)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
},
NDPConfigs: ipv6.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenTempGlobalAddresses: true,
@ -2635,16 +2638,15 @@ func TestMixedSLAACAddrConflictRegen(t *testing.T) {
autoGenAddrC: make(chan ndpAutoGenAddrEvent, 2),
}
e := channel.New(0, 1280, linkAddr1)
ndpConfigs := ipv6.NDPConfigurations{
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenTempGlobalAddresses: test.tempAddrs,
AutoGenAddressConflictRetries: 1,
}
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
NDPConfigs: ndpConfigs,
NDPDisp: &ndpDisp,
NDPConfigs: ipv6.NDPConfigurations{
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenTempGlobalAddresses: test.tempAddrs,
AutoGenAddressConflictRetries: 1,
},
NDPDisp: &ndpDisp,
OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
NICNameFromID: test.nicNameFromID,
},
@ -2718,13 +2720,14 @@ func TestMixedSLAACAddrConflictRegen(t *testing.T) {
// Enable DAD.
ndpDisp.dadC = make(chan ndpDADEvent, 2)
ndpConfigs.DupAddrDetectTransmits = dupAddrTransmits
ndpConfigs.RetransmitTimer = retransmitTimer
if ipv6Ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber); err != nil {
t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
} else {
ndpEP := ipv6Ep.(ipv6.NDPEndpoint)
ndpEP.SetNDPConfigurations(ndpConfigs)
ndpEP := ipv6Ep.(stack.DuplicateAddressDetector)
ndpEP.SetDADConfigurations(stack.DADConfigurations{
DupAddrDetectTransmits: dupAddrTransmits,
RetransmitTimer: retransmitTimer,
})
}
// Do SLAAC for prefix.
@ -3830,12 +3833,12 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
}
}
expectDADEvent := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.Address, resolved bool) {
expectDADEvent := func(t *testing.T, ndpDisp *ndpDispatcher, addr tcpip.Address, resolved bool, err tcpip.Error) {
t.Helper()
select {
case e := <-ndpDisp.dadC:
if diff := checkDADEvent(e, nicID, addr, resolved, nil); diff != "" {
if diff := checkDADEvent(e, nicID, addr, resolved, err); diff != "" {
t.Errorf("dad event mismatch (-want +got):\n%s", diff)
}
default:
@ -3868,8 +3871,6 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
{
name: "Global address",
ndpConfigs: ipv6.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
},
@ -3884,11 +3885,8 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
},
},
{
name: "LinkLocal address",
ndpConfigs: ipv6.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
},
name: "LinkLocal address",
ndpConfigs: ipv6.NDPConfigurations{},
autoGenLinkLocal: true,
prepareFn: func(*testing.T, *ndpDispatcher, *channel.Endpoint, []byte) []tcpip.AddressWithPrefix {
return nil
@ -3900,8 +3898,6 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
{
name: "Temporary address",
ndpConfigs: ipv6.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenTempGlobalAddresses: true,
@ -3953,8 +3949,12 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
AutoGenLinkLocal: addrType.autoGenLinkLocal,
NDPConfigs: ndpConfigs,
NDPDisp: &ndpDisp,
DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
},
NDPConfigs: ndpConfigs,
NDPDisp: &ndpDisp,
OpaqueIIDOpts: ipv6.OpaqueInterfaceIdentifierOptions{
NICNameFromID: func(_ tcpip.NICID, nicName string) string {
return nicName
@ -3984,7 +3984,7 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
// Simulate a DAD conflict.
rxNDPSolicit(e, addr.Address)
expectAutoGenAddrEvent(t, &ndpDisp, addr, invalidatedAddr)
expectDADEvent(t, &ndpDisp, addr.Address, false)
expectDADEvent(t, &ndpDisp, addr.Address, false, nil)
// Attempting to add the address manually should not fail if the
// address's state was cleaned up when DAD failed.
@ -3994,7 +3994,7 @@ func TestAutoGenAddrInResponseToDADConflicts(t *testing.T) {
if err := s.RemoveAddress(nicID, addr.Address); err != nil {
t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, addr.Address, err)
}
expectDADEvent(t, &ndpDisp, addr.Address, false)
expectDADEvent(t, &ndpDisp, addr.Address, false, &tcpip.ErrAborted{})
}
// Should not have any new addresses assigned to the NIC.
@ -4048,8 +4048,6 @@ func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) {
{
name: "Global address",
ndpConfigs: ipv6.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenAddressConflictRetries: maxRetries,
@ -4064,8 +4062,6 @@ func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) {
{
name: "LinkLocal address",
ndpConfigs: ipv6.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
AutoGenAddressConflictRetries: maxRetries,
},
autoGenLinkLocal: true,
@ -4090,6 +4086,10 @@ func TestAutoGenAddrWithEUI64IIDNoDADRetries(t *testing.T) {
AutoGenLinkLocal: addrType.autoGenLinkLocal,
NDPConfigs: addrType.ndpConfigs,
NDPDisp: &ndpDisp,
DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
},
})},
})
if err := s.CreateNIC(nicID, e); err != nil {
@ -4171,9 +4171,11 @@ func TestAutoGenAddrContinuesLifetimesAfterRetry(t *testing.T) {
e := channel.New(0, 1280, linkAddr1)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
},
NDPConfigs: ipv6.NDPConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
HandleRAs: true,
AutoGenGlobalAddresses: true,
AutoGenAddressConflictRetries: maxRetries,

View File

@ -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
}

View File

@ -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

View File

@ -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.

View File

@ -2573,16 +2573,16 @@ func TestNICAutoGenAddrDoesDAD(t *testing.T) {
ndpDisp := ndpDispatcher{
dadC: make(chan ndpDADEvent),
}
ndpConfigs := ipv6.DefaultNDPConfigurations()
dadConfigs := stack.DefaultDADConfigurations()
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
NDPConfigs: ndpConfigs,
AutoGenLinkLocal: true,
NDPDisp: &ndpDisp,
DADConfigs: dadConfigs,
})},
}
e := channel.New(int(ndpConfigs.DupAddrDetectTransmits), 1280, linkAddr1)
e := channel.New(int(dadConfigs.DupAddrDetectTransmits), 1280, linkAddr1)
s := stack.New(opts)
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
@ -2598,7 +2598,7 @@ func TestNICAutoGenAddrDoesDAD(t *testing.T) {
// Wait for DAD to resolve.
select {
case <-time.After(time.Duration(ndpConfigs.DupAddrDetectTransmits)*ndpConfigs.RetransmitTimer + time.Second):
case <-time.After(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer + time.Second):
// We should get a resolution event after 1s (default time to
// resolve as per default NDP configurations). Waiting for that
// resolution time + an extra 1s without a resolution event
@ -3235,7 +3235,7 @@ func TestDoDADWhenNICEnabled(t *testing.T) {
}
opts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
NDPConfigs: ipv6.NDPConfigurations{
DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: dadTransmits,
RetransmitTimer: retransmitTimer,
},

View File

@ -1199,3 +1199,148 @@ func TestTCPConfirmNeighborReachability(t *testing.T) {
})
}
}
func TestDAD(t *testing.T) {
const (
host1NICID = 1
host2NICID = 4
)
dadConfigs := stack.DADConfigurations{
DupAddrDetectTransmits: 1,
RetransmitTimer: time.Second,
}
tests := []struct {
name string
netProto tcpip.NetworkProtocolNumber
dadNetProto tcpip.NetworkProtocolNumber
remoteAddr tcpip.Address
expectedResolved bool
}{
{
name: "IPv4 own address",
netProto: ipv4.ProtocolNumber,
dadNetProto: arp.ProtocolNumber,
remoteAddr: ipv4Addr1.AddressWithPrefix.Address,
expectedResolved: true,
},
{
name: "IPv6 own address",
netProto: ipv6.ProtocolNumber,
dadNetProto: ipv6.ProtocolNumber,
remoteAddr: ipv6Addr1.AddressWithPrefix.Address,
expectedResolved: true,
},
{
name: "IPv4 duplicate address",
netProto: ipv4.ProtocolNumber,
dadNetProto: arp.ProtocolNumber,
remoteAddr: ipv4Addr2.AddressWithPrefix.Address,
expectedResolved: false,
},
{
name: "IPv6 duplicate address",
netProto: ipv6.ProtocolNumber,
dadNetProto: ipv6.ProtocolNumber,
remoteAddr: ipv6Addr2.AddressWithPrefix.Address,
expectedResolved: false,
},
{
name: "IPv4 no duplicate address",
netProto: ipv4.ProtocolNumber,
dadNetProto: arp.ProtocolNumber,
remoteAddr: ipv4Addr3.AddressWithPrefix.Address,
expectedResolved: true,
},
{
name: "IPv6 no duplicate address",
netProto: ipv6.ProtocolNumber,
dadNetProto: ipv6.ProtocolNumber,
remoteAddr: ipv6Addr3.AddressWithPrefix.Address,
expectedResolved: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
clock := faketime.NewManualClock()
stackOpts := stack.Options{
Clock: clock,
NetworkProtocols: []stack.NetworkProtocolFactory{
arp.NewProtocol,
ipv4.NewProtocol,
ipv6.NewProtocol,
},
}
host1Stack, _ := setupStack(t, stackOpts, host1NICID, host2NICID)
// DAD should be disabled by default.
if res, err := host1Stack.CheckDuplicateAddress(host1NICID, test.netProto, test.remoteAddr, func(r stack.DADResult) {
t.Errorf("unexpectedly called DAD completion handler when DAD was supposed to be disabled")
}); err != nil {
t.Fatalf("host1Stack.CheckDuplicateAddress(%d, %d, %s, _): %s", host1NICID, test.netProto, test.remoteAddr, err)
} else if res != stack.DADDisabled {
t.Errorf("got host1Stack.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", host1NICID, test.netProto, test.remoteAddr, res, stack.DADDisabled)
}
// Enable DAD then attempt to check if an address is duplicated.
netEP, err := host1Stack.GetNetworkEndpoint(host1NICID, test.dadNetProto)
if err != nil {
t.Fatalf("host1Stack.GetNetworkEndpoint(%d, %d): %s", host1NICID, test.dadNetProto, err)
}
dad, ok := netEP.(stack.DuplicateAddressDetector)
if !ok {
t.Fatalf("expected %T to implement stack.DuplicateAddressDetector", netEP)
}
dad.SetDADConfigurations(dadConfigs)
ch := make(chan stack.DADResult, 3)
if res, err := host1Stack.CheckDuplicateAddress(host1NICID, test.netProto, test.remoteAddr, func(r stack.DADResult) {
ch <- r
}); err != nil {
t.Fatalf("host1Stack.CheckDuplicateAddress(%d, %d, %s, _): %s", host1NICID, test.netProto, test.remoteAddr, err)
} else if res != stack.DADStarting {
t.Errorf("got host1Stack.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", host1NICID, test.netProto, test.remoteAddr, res, stack.DADStarting)
}
expectResults := 1
if test.expectedResolved {
const delta = time.Nanosecond
clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits)*dadConfigs.RetransmitTimer - delta)
select {
case r := <-ch:
t.Fatalf("unexpectedly got DAD result before the DAD timeout; r = %#v", r)
default:
}
// If we expect the resolve to succeed try requesting DAD again on the
// same address. The handler for the new request should be called once
// the original DAD request completes.
expectResults = 2
if res, err := host1Stack.CheckDuplicateAddress(host1NICID, test.netProto, test.remoteAddr, func(r stack.DADResult) {
ch <- r
}); err != nil {
t.Fatalf("host1Stack.CheckDuplicateAddress(%d, %d, %s, _): %s", host1NICID, test.netProto, test.remoteAddr, err)
} else if res != stack.DADAlreadyRunning {
t.Errorf("got host1Stack.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", host1NICID, test.netProto, test.remoteAddr, res, stack.DADAlreadyRunning)
}
clock.Advance(delta)
}
for i := 0; i < expectResults; i++ {
if diff := cmp.Diff(stack.DADResult{Resolved: test.expectedResolved}, <-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:
}
})
}
}