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:
parent
cfa4633c3d
commit
39251f31cb
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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:
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
],
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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:
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue