From 39251f31cb92d6c2b053416d04e195e290b106f2 Mon Sep 17 00:00:00 2001 From: Ghanan Gowripalan Date: Mon, 8 Feb 2021 19:03:54 -0800 Subject: 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 --- pkg/tcpip/network/arp/BUILD | 2 + pkg/tcpip/network/arp/arp.go | 77 +++++- pkg/tcpip/network/arp/arp_test.go | 50 ++++ pkg/tcpip/network/internal/ip/BUILD | 28 +++ .../internal/ip/duplicate_address_detection.go | 172 +++++++++++++ .../ip/duplicate_address_detection_test.go | 279 +++++++++++++++++++++ pkg/tcpip/network/ipv6/BUILD | 1 + pkg/tcpip/network/ipv6/icmp.go | 35 +-- pkg/tcpip/network/ipv6/ipv6.go | 62 ++++- pkg/tcpip/network/ipv6/mld_test.go | 2 +- pkg/tcpip/network/ipv6/ndp.go | 253 ++++++------------- pkg/tcpip/network/ipv6/ndp_test.go | 108 ++++++++ pkg/tcpip/stack/ndp_test.go | 108 ++++---- pkg/tcpip/stack/nic.go | 33 ++- pkg/tcpip/stack/registration.go | 93 ++++++- pkg/tcpip/stack/stack.go | 11 + pkg/tcpip/stack/stack_test.go | 10 +- .../tests/integration/link_resolution_test.go | 145 +++++++++++ 18 files changed, 1184 insertions(+), 285 deletions(-) create mode 100644 pkg/tcpip/network/internal/ip/BUILD create mode 100644 pkg/tcpip/network/internal/ip/duplicate_address_detection.go create mode 100644 pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go diff --git a/pkg/tcpip/network/arp/BUILD b/pkg/tcpip/network/arp/BUILD index 933845269..29c8cdffd 100644 --- a/pkg/tcpip/network/arp/BUILD +++ b/pkg/tcpip/network/arp/BUILD @@ -10,10 +10,12 @@ go_library( ], visibility = ["//visibility:public"], deps = [ + "//pkg/sync", "//pkg/tcpip", "//pkg/tcpip/buffer", "//pkg/tcpip/header", "//pkg/tcpip/header/parse", + "//pkg/tcpip/network/internal/ip", "//pkg/tcpip/stack", ], ) diff --git a/pkg/tcpip/network/arp/arp.go b/pkg/tcpip/network/arp/arp.go index 5d7803537..3fcdea119 100644 --- a/pkg/tcpip/network/arp/arp.go +++ b/pkg/tcpip/network/arp/arp.go @@ -22,10 +22,12 @@ import ( "reflect" "sync/atomic" + "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/header/parse" + "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip" "gvisor.dev/gvisor/pkg/tcpip/stack" ) @@ -34,6 +36,7 @@ const ( ProtocolNumber = header.ARPProtocolNumber ) +var _ stack.DuplicateAddressDetector = (*endpoint)(nil) var _ stack.LinkAddressResolver = (*endpoint)(nil) // ARP endpoints need to implement stack.NetworkEndpoint because the stack @@ -52,6 +55,35 @@ type endpoint struct { nic stack.NetworkInterface stats sharedStats + + mu struct { + sync.Mutex + + dad ip.DAD + } +} + +// CheckDuplicateAddress implements stack.DuplicateAddressDetector. +func (e *endpoint) CheckDuplicateAddress(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition { + e.mu.Lock() + defer e.mu.Unlock() + return e.mu.dad.CheckDuplicateAddressLocked(addr, h) +} + +// SetDADConfigurations implements stack.DuplicateAddressDetector. +func (e *endpoint) SetDADConfigurations(c stack.DADConfigurations) { + e.mu.Lock() + defer e.mu.Unlock() + e.mu.dad.SetConfigsLocked(c) +} + +// DuplicateAddressProtocol implements stack.DuplicateAddressDetector. +func (*endpoint) DuplicateAddressProtocol() tcpip.NetworkProtocolNumber { + return header.IPv4ProtocolNumber +} + +func (e *endpoint) SendDADMessage(addr tcpip.Address) tcpip.Error { + return e.sendARPRequest(header.IPv4Any, addr, header.EthernetBroadcastAddress) } func (e *endpoint) Enable() tcpip.Error { @@ -199,6 +231,10 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) { addr := tcpip.Address(h.ProtocolAddressSender()) linkAddr := tcpip.LinkAddress(h.HardwareAddressSender()) + e.mu.Lock() + e.mu.dad.StopLocked(addr, false /* aborted */) + e.mu.Unlock() + // The solicited, override, and isRouter flags are not available for ARP; // they are only available for IPv6 Neighbor Advertisements. switch err := e.nic.HandleNeighborConfirmation(header.IPv4ProtocolNumber, addr, linkAddr, stack.ReachabilityConfirmationFlags{ @@ -227,9 +263,9 @@ func (e *endpoint) Stats() stack.NetworkEndpointStats { var _ stack.NetworkProtocol = (*protocol)(nil) -// protocol implements stack.NetworkProtocol and stack.LinkAddressResolver. type protocol struct { - stack *stack.Stack + stack *stack.Stack + options Options } func (p *protocol) Number() tcpip.NetworkProtocolNumber { return ProtocolNumber } @@ -246,6 +282,14 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.Tran nic: nic, } + e.mu.Lock() + e.mu.dad.Init(&e.mu, p.options.DADConfigs, ip.DADOptions{ + Clock: p.stack.Clock(), + Protocol: e, + NICID: nic.ID(), + }) + e.mu.Unlock() + tcpip.InitStatCounters(reflect.ValueOf(&e.stats.localStats).Elem()) stackStats := p.stack.Stats() @@ -286,8 +330,12 @@ func (e *endpoint) LinkAddressRequest(targetAddr, localAddr tcpip.Address, remot return &tcpip.ErrBadLocalAddress{} } + return e.sendARPRequest(localAddr, targetAddr, remoteLinkAddr) +} + +func (e *endpoint) sendARPRequest(localAddr, targetAddr tcpip.Address, remoteLinkAddr tcpip.LinkAddress) tcpip.Error { pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: int(e.nic.MaxHeaderLength()) + header.ARPSize, + ReserveHeaderBytes: int(e.MaxHeaderLength()), }) h := header.ARP(pkt.NetworkHeader().Push(header.ARPSize)) pkt.NetworkProtocolNumber = ProtocolNumber @@ -302,6 +350,8 @@ func (e *endpoint) LinkAddressRequest(targetAddr, localAddr tcpip.Address, remot if n := copy(h.ProtocolAddressTarget(), targetAddr); n != header.IPv4AddressSize { panic(fmt.Sprintf("copied %d bytes, expected %d bytes", n, header.IPv4AddressSize)) } + + stats := e.stats.arp if err := e.nic.WritePacketToRemote(remoteLinkAddr, nil /* gso */, ProtocolNumber, pkt); err != nil { stats.outgoingRequestsDropped.Increment() return err @@ -342,9 +392,24 @@ func (*protocol) Parse(pkt *stack.PacketBuffer) (proto tcpip.TransportProtocolNu return 0, false, parse.ARP(pkt) } +// Options holds options to configure a protocol. +type Options struct { + // DADConfigs is the default DAD configurations used by ARP endpoints. + DADConfigs stack.DADConfigurations +} + +// NewProtocolWithOptions returns an ARP network protocol factory that +// will return an ARP network protocol with the provided options. +func NewProtocolWithOptions(opts Options) stack.NetworkProtocolFactory { + return func(s *stack.Stack) stack.NetworkProtocol { + return &protocol{ + stack: s, + options: opts, + } + } +} + // NewProtocol returns an ARP network protocol. func NewProtocol(s *stack.Stack) stack.NetworkProtocol { - return &protocol{ - stack: s, - } + return NewProtocolWithOptions(Options{})(s) } diff --git a/pkg/tcpip/network/arp/arp_test.go b/pkg/tcpip/network/arp/arp_test.go index c8b9ff9fc..018d6a578 100644 --- a/pkg/tcpip/network/arp/arp_test.go +++ b/pkg/tcpip/network/arp/arp_test.go @@ -659,3 +659,53 @@ func TestLinkAddressRequest(t *testing.T) { }) } } + +func TestDADARPRequestPacket(t *testing.T) { + s := stack.New(stack.Options{ + NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocolWithOptions(arp.Options{ + DADConfigs: stack.DADConfigurations{ + DupAddrDetectTransmits: 1, + RetransmitTimer: time.Second, + }, + }), ipv4.NewProtocol}, + }) + e := channel.New(1, defaultMTU, stackLinkAddr) + if err := s.CreateNIC(nicID, e); err != nil { + t.Fatalf("s.CreateNIC(%d, _): %s", nicID, err) + } + + if res, err := s.CheckDuplicateAddress(nicID, header.IPv4ProtocolNumber, remoteAddr, func(stack.DADResult) {}); err != nil { + t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, header.IPv4ProtocolNumber, remoteAddr, err) + } else if res != stack.DADStarting { + t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, header.IPv4ProtocolNumber, remoteAddr, res, stack.DADStarting) + } + + pkt, ok := e.ReadContext(context.Background()) + if !ok { + t.Fatal("expected to send an ARP request") + } + + if pkt.Route.RemoteLinkAddress != header.EthernetBroadcastAddress { + t.Errorf("got pkt.Route.RemoteLinkAddress = %s, want = %s", pkt.Route.RemoteLinkAddress, header.EthernetBroadcastAddress) + } + + req := header.ARP(stack.PayloadSince(pkt.Pkt.NetworkHeader())) + if !req.IsValid() { + t.Errorf("got req.IsValid() = false, want = true") + } + if got := req.Op(); got != header.ARPRequest { + t.Errorf("got req.Op() = %d, want = %d", got, header.ARPRequest) + } + if got := tcpip.LinkAddress(req.HardwareAddressSender()); got != stackLinkAddr { + t.Errorf("got req.HardwareAddressSender() = %s, want = %s", got, stackLinkAddr) + } + if got := tcpip.Address(req.ProtocolAddressSender()); got != header.IPv4Any { + t.Errorf("got req.ProtocolAddressSender() = %s, want = %s", got, header.IPv4Any) + } + if got, want := tcpip.LinkAddress(req.HardwareAddressTarget()), tcpip.LinkAddress("\x00\x00\x00\x00\x00\x00"); got != want { + t.Errorf("got req.HardwareAddressTarget() = %s, want = %s", got, want) + } + if got := tcpip.Address(req.ProtocolAddressTarget()); got != remoteAddr { + t.Errorf("got req.ProtocolAddressTarget() = %s, want = %s", got, remoteAddr) + } +} diff --git a/pkg/tcpip/network/internal/ip/BUILD b/pkg/tcpip/network/internal/ip/BUILD new file mode 100644 index 000000000..0f55a9770 --- /dev/null +++ b/pkg/tcpip/network/internal/ip/BUILD @@ -0,0 +1,28 @@ +load("//tools:defs.bzl", "go_library", "go_test") + +package(licenses = ["notice"]) + +go_library( + name = "ip", + srcs = ["duplicate_address_detection.go"], + visibility = ["//visibility:public"], + deps = [ + "//pkg/sync", + "//pkg/tcpip", + "//pkg/tcpip/stack", + ], +) + +go_test( + name = "ip_x_test", + size = "small", + srcs = ["duplicate_address_detection_test.go"], + deps = [ + ":ip", + "//pkg/sync", + "//pkg/tcpip", + "//pkg/tcpip/faketime", + "//pkg/tcpip/stack", + "@com_github_google_go_cmp//cmp:go_default_library", + ], +) diff --git a/pkg/tcpip/network/internal/ip/duplicate_address_detection.go b/pkg/tcpip/network/internal/ip/duplicate_address_detection.go new file mode 100644 index 000000000..6f89a6a16 --- /dev/null +++ b/pkg/tcpip/network/internal/ip/duplicate_address_detection.go @@ -0,0 +1,172 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package ip holds IPv4/IPv6 common utilities. +package ip + +import ( + "fmt" + + "gvisor.dev/gvisor/pkg/sync" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +type dadState struct { + done *bool + timer tcpip.Timer + + completionHandlers []stack.DADCompletionHandler +} + +// DADProtocol is a protocol whose core state machine can be represented by DAD. +type DADProtocol interface { + // SendDADMessage attempts to send a DAD probe message. + SendDADMessage(tcpip.Address) tcpip.Error +} + +// DADOptions holds options for DAD. +type DADOptions struct { + Clock tcpip.Clock + Protocol DADProtocol + NICID tcpip.NICID +} + +// DAD performs duplicate address detection for addresses. +type DAD struct { + opts DADOptions + configs stack.DADConfigurations + + protocolMU sync.Locker + addresses map[tcpip.Address]dadState +} + +// Init initializes the DAD state. +// +// Must only be called once for the lifetime of d; Init will panic if it is +// called twice. +// +// The lock will only be taken when timers fire. +func (d *DAD) Init(protocolMU sync.Locker, configs stack.DADConfigurations, opts DADOptions) { + if d.addresses != nil { + panic("attempted to initialize DAD state twice") + } + + *d = DAD{ + opts: opts, + configs: configs, + protocolMU: protocolMU, + addresses: make(map[tcpip.Address]dadState), + } +} + +// CheckDuplicateAddressLocked performs DAD for an address, calling the +// completion handler once DAD resolves. +// +// If DAD is already performing for the provided address, h will be called when +// the currently running process completes. +// +// Precondition: d.protocolMU must be locked. +func (d *DAD) CheckDuplicateAddressLocked(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition { + if d.configs.DupAddrDetectTransmits == 0 { + return stack.DADDisabled + } + + ret := stack.DADAlreadyRunning + s, ok := d.addresses[addr] + if !ok { + ret = stack.DADStarting + + remaining := d.configs.DupAddrDetectTransmits + + // Protected by d.protocolMU. + done := false + + s = dadState{ + done: &done, + timer: d.opts.Clock.AfterFunc(0, func() { + var err tcpip.Error + dadDone := remaining == 0 + if !dadDone { + err = d.opts.Protocol.SendDADMessage(addr) + } + + d.protocolMU.Lock() + defer d.protocolMU.Unlock() + + if done { + return + } + + s, ok := d.addresses[addr] + if !ok { + panic(fmt.Sprintf("dad: timer fired but missing state for %s on NIC(%d)", addr, d.opts.NICID)) + } + + if !dadDone && err == nil { + remaining-- + s.timer.Reset(d.configs.RetransmitTimer) + return + } + + // At this point we know that either DAD has resolved or we hit an error + // sending the last DAD message. Either way, clear the DAD state. + done = false + s.timer.Stop() + delete(d.addresses, addr) + + r := stack.DADResult{Resolved: dadDone, Err: err} + for _, h := range s.completionHandlers { + h(r) + } + }), + } + } + + s.completionHandlers = append(s.completionHandlers, h) + d.addresses[addr] = s + return ret +} + +// StopLocked stops a currently running DAD process. +// +// Precondition: d.protocolMU must be locked. +func (d *DAD) StopLocked(addr tcpip.Address, aborted bool) { + s, ok := d.addresses[addr] + if !ok { + return + } + + *s.done = true + s.timer.Stop() + delete(d.addresses, addr) + + var err tcpip.Error + if aborted { + err = &tcpip.ErrAborted{} + } + + r := stack.DADResult{Resolved: false, Err: err} + for _, h := range s.completionHandlers { + h(r) + } +} + +// SetConfigsLocked sets the DAD configurations. +// +// Precondition: d.protocolMU must be locked. +func (d *DAD) SetConfigsLocked(c stack.DADConfigurations) { + c.Validate() + d.configs = c +} diff --git a/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go b/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go new file mode 100644 index 000000000..18c357b56 --- /dev/null +++ b/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go @@ -0,0 +1,279 @@ +// Copyright 2021 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ip_test + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "gvisor.dev/gvisor/pkg/sync" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/faketime" + "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +type mockDADProtocol struct { + t *testing.T + + mu struct { + sync.Mutex + + dad ip.DAD + sendCount map[tcpip.Address]int + } +} + +func (m *mockDADProtocol) init(t *testing.T, c stack.DADConfigurations, opts ip.DADOptions) { + m.mu.Lock() + defer m.mu.Unlock() + + m.t = t + opts.Protocol = m + m.mu.dad.Init(&m.mu, c, opts) + m.initLocked() +} + +func (m *mockDADProtocol) initLocked() { + m.mu.sendCount = make(map[tcpip.Address]int) +} + +func (m *mockDADProtocol) SendDADMessage(addr tcpip.Address) tcpip.Error { + m.mu.Lock() + defer m.mu.Unlock() + m.mu.sendCount[addr]++ + return nil +} + +func (m *mockDADProtocol) check(addrs []tcpip.Address) string { + m.mu.Lock() + defer m.mu.Unlock() + + sendCount := make(map[tcpip.Address]int) + for _, a := range addrs { + sendCount[a]++ + } + + diff := cmp.Diff(sendCount, m.mu.sendCount) + m.initLocked() + return diff +} + +func (m *mockDADProtocol) checkDuplicateAddress(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition { + m.mu.Lock() + defer m.mu.Unlock() + return m.mu.dad.CheckDuplicateAddressLocked(addr, h) +} + +func (m *mockDADProtocol) stop(addr tcpip.Address, aborted bool) { + m.mu.Lock() + defer m.mu.Unlock() + m.mu.dad.StopLocked(addr, aborted) +} + +func (m *mockDADProtocol) setConfigs(c stack.DADConfigurations) { + m.mu.Lock() + defer m.mu.Unlock() + m.mu.dad.SetConfigsLocked(c) +} + +const ( + addr1 = tcpip.Address("\x01") + addr2 = tcpip.Address("\x02") + addr3 = tcpip.Address("\x03") + addr4 = tcpip.Address("\x04") +) + +type dadResult struct { + Addr tcpip.Address + R stack.DADResult +} + +func handler(ch chan<- dadResult, a tcpip.Address) func(stack.DADResult) { + return func(r stack.DADResult) { + ch <- dadResult{Addr: a, R: r} + } +} + +func TestDADCheckDuplicateAddress(t *testing.T) { + var dad mockDADProtocol + clock := faketime.NewManualClock() + dad.init(t, stack.DADConfigurations{}, ip.DADOptions{ + Clock: clock, + }) + + ch := make(chan dadResult, 2) + + // DAD should initially be disabled. + if res := dad.checkDuplicateAddress(addr1, handler(nil, "")); res != stack.DADDisabled { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADDisabled) + } + // Wait for any initially fired timers to complete. + clock.Advance(0) + if diff := dad.check(nil); diff != "" { + t.Errorf("dad check mismatch (-want +got):\n%s", diff) + } + + // Enable and request DAD. + dadConfigs1 := stack.DADConfigurations{ + DupAddrDetectTransmits: 1, + RetransmitTimer: time.Second, + } + dad.setConfigs(dadConfigs1) + if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting) + } + clock.Advance(0) + if diff := dad.check([]tcpip.Address{addr1}); diff != "" { + t.Errorf("dad check mismatch (-want +got):\n%s", diff) + } + // The second request for DAD on the same address should use the original + // request since it has not completed yet. + if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADAlreadyRunning { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADAlreadyRunning) + } + clock.Advance(0) + if diff := dad.check(nil); diff != "" { + t.Errorf("dad check mismatch (-want +got):\n%s", diff) + } + + dadConfigs2 := stack.DADConfigurations{ + DupAddrDetectTransmits: 2, + RetransmitTimer: time.Second, + } + dad.setConfigs(dadConfigs2) + // A new address should start a new DAD process. + if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting) + } + clock.Advance(0) + if diff := dad.check([]tcpip.Address{addr2}); diff != "" { + t.Errorf("dad check mismatch (-want +got):\n%s", diff) + } + + // Make sure DAD for addr1 only resolves after the expected timeout. + const delta = time.Nanosecond + dadConfig1Duration := time.Duration(dadConfigs1.DupAddrDetectTransmits) * dadConfigs1.RetransmitTimer + clock.Advance(dadConfig1Duration - delta) + select { + case r := <-ch: + t.Fatalf("unexpectedly got a DAD result before the expected timeout of %s; r = %#v", dadConfig1Duration, r) + default: + } + clock.Advance(delta) + for i := 0; i < 2; i++ { + if diff := cmp.Diff(dadResult{Addr: addr1, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" { + t.Errorf("(i=%d) dad result mismatch (-want +got):\n%s", i, diff) + } + } + + // Make sure DAD for addr2 only resolves after the expected timeout. + dadConfig2Duration := time.Duration(dadConfigs2.DupAddrDetectTransmits) * dadConfigs2.RetransmitTimer + clock.Advance(dadConfig2Duration - dadConfig1Duration - delta) + select { + case r := <-ch: + t.Fatalf("unexpectedly got a DAD result before the expected timeout of %s; r = %#v", dadConfig2Duration, r) + default: + } + clock.Advance(delta) + if diff := cmp.Diff(dadResult{Addr: addr2, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" { + t.Errorf("dad result mismatch (-want +got):\n%s", diff) + } + + // Should be able to restart DAD for addr2 after it resolved. + if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting) + } + clock.Advance(0) + if diff := dad.check([]tcpip.Address{addr2, addr2}); diff != "" { + t.Errorf("dad check mismatch (-want +got):\n%s", diff) + } + clock.Advance(dadConfig2Duration) + if diff := cmp.Diff(dadResult{Addr: addr2, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" { + t.Errorf("dad result mismatch (-want +got):\n%s", diff) + } + + // Should not have anymore results. + select { + case r := <-ch: + t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r) + default: + } +} + +func TestDADStop(t *testing.T) { + var dad mockDADProtocol + clock := faketime.NewManualClock() + dadConfigs := stack.DADConfigurations{ + DupAddrDetectTransmits: 1, + RetransmitTimer: time.Second, + } + dad.init(t, dadConfigs, ip.DADOptions{ + Clock: clock, + }) + + ch := make(chan dadResult, 1) + + if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting) + } + if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting) + } + if res := dad.checkDuplicateAddress(addr3, handler(ch, addr3)); res != stack.DADStarting { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting) + } + clock.Advance(0) + if diff := dad.check([]tcpip.Address{addr1, addr2, addr3}); diff != "" { + t.Errorf("dad check mismatch (-want +got):\n%s", diff) + } + + dad.stop(addr1, true /* aborted */) + if diff := cmp.Diff(dadResult{Addr: addr1, R: stack.DADResult{Resolved: false, Err: &tcpip.ErrAborted{}}}, <-ch); diff != "" { + t.Errorf("dad result mismatch (-want +got):\n%s", diff) + } + + dad.stop(addr2, false /* aborted */) + if diff := cmp.Diff(dadResult{Addr: addr2, R: stack.DADResult{Resolved: false, Err: nil}}, <-ch); diff != "" { + t.Errorf("dad result mismatch (-want +got):\n%s", diff) + } + + dadResolutionDuration := time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer + clock.Advance(dadResolutionDuration) + if diff := cmp.Diff(dadResult{Addr: addr3, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" { + t.Errorf("dad result mismatch (-want +got):\n%s", diff) + } + + // Should be able to restart DAD for an address we stopped DAD on. + if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting { + t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting) + } + clock.Advance(0) + if diff := dad.check([]tcpip.Address{addr1}); diff != "" { + t.Errorf("dad check mismatch (-want +got):\n%s", diff) + } + clock.Advance(dadResolutionDuration) + if diff := cmp.Diff(dadResult{Addr: addr1, R: stack.DADResult{Resolved: true, Err: nil}}, <-ch); diff != "" { + t.Errorf("dad result mismatch (-want +got):\n%s", diff) + } + + // Should not have anymore updates. + select { + case r := <-ch: + t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r) + default: + } +} diff --git a/pkg/tcpip/network/ipv6/BUILD b/pkg/tcpip/network/ipv6/BUILD index 0c5f8d683..d75b0b8de 100644 --- a/pkg/tcpip/network/ipv6/BUILD +++ b/pkg/tcpip/network/ipv6/BUILD @@ -21,6 +21,7 @@ go_library( "//pkg/tcpip/header/parse", "//pkg/tcpip/network/fragmentation", "//pkg/tcpip/network/hash", + "//pkg/tcpip/network/internal/ip", "//pkg/tcpip/network/ip", "//pkg/tcpip/stack", ], diff --git a/pkg/tcpip/network/ipv6/icmp.go b/pkg/tcpip/network/ipv6/icmp.go index edf4ef4e5..2690644d6 100644 --- a/pkg/tcpip/network/ipv6/icmp.go +++ b/pkg/tcpip/network/ipv6/icmp.go @@ -537,6 +537,11 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool) { // NDP datagrams are very small and ToView() will not incur allocations. na := header.NDPNeighborAdvert(payload.ToView()) targetAddr := na.TargetAddress() + + e.dad.mu.Lock() + e.dad.mu.dad.StopLocked(targetAddr, false /* aborted */) + e.dad.mu.Unlock() + if e.hasTentativeAddr(targetAddr) { // We just got an NA from a node that owns an address we are performing // DAD on, implying the address is not unique. In this case we let the @@ -866,37 +871,9 @@ func (e *endpoint) LinkAddressRequest(targetAddr, localAddr tcpip.Address, remot return &tcpip.ErrBadLocalAddress{} } - optsSerializer := header.NDPOptionsSerializer{ + return e.sendNDPNS(localAddr, remoteAddr, targetAddr, remoteLinkAddr, header.NDPOptionsSerializer{ header.NDPSourceLinkLayerAddressOption(e.nic.LinkAddress()), - } - neighborSolicitSize := header.ICMPv6NeighborSolicitMinimumSize + optsSerializer.Length() - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: int(e.nic.MaxHeaderLength()) + header.IPv6FixedHeaderSize + neighborSolicitSize, }) - pkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber - packet := header.ICMPv6(pkt.TransportHeader().Push(neighborSolicitSize)) - packet.SetType(header.ICMPv6NeighborSolicit) - ns := header.NDPNeighborSolicit(packet.MessageBody()) - ns.SetTargetAddress(targetAddr) - ns.Options().Serialize(optsSerializer) - packet.SetChecksum(header.ICMPv6Checksum(packet, localAddr, remoteAddr, buffer.VectorisedView{})) - - if err := addIPHeader(localAddr, remoteAddr, pkt, stack.NetworkHeaderParams{ - Protocol: header.ICMPv6ProtocolNumber, - TTL: header.NDPHopLimit, - }, header.IPv6ExtHdrSerializer{}); err != nil { - panic(fmt.Sprintf("failed to add IP header: %s", err)) - } - - stat := e.stats.icmp.packetsSent - - if err := e.nic.WritePacketToRemote(remoteLinkAddr, nil /* gso */, ProtocolNumber, pkt); err != nil { - stat.dropped.Increment() - return err - } - - stat.neighborSolicit.Increment() - return nil } // ResolveStaticAddress implements stack.LinkAddressResolver. diff --git a/pkg/tcpip/network/ipv6/ipv6.go b/pkg/tcpip/network/ipv6/ipv6.go index 5856c9d3c..56bbf1cc3 100644 --- a/pkg/tcpip/network/ipv6/ipv6.go +++ b/pkg/tcpip/network/ipv6/ipv6.go @@ -32,6 +32,7 @@ import ( "gvisor.dev/gvisor/pkg/tcpip/header/parse" "gvisor.dev/gvisor/pkg/tcpip/network/fragmentation" "gvisor.dev/gvisor/pkg/tcpip/network/hash" + "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip" "gvisor.dev/gvisor/pkg/tcpip/stack" ) @@ -164,6 +165,7 @@ func getLabel(addr tcpip.Address) uint8 { panic(fmt.Sprintf("should have a label for address = %s", addr)) } +var _ stack.DuplicateAddressDetector = (*endpoint)(nil) var _ stack.LinkAddressResolver = (*endpoint)(nil) var _ stack.LinkResolvableNetworkEndpoint = (*endpoint)(nil) var _ stack.GroupAddressableEndpoint = (*endpoint)(nil) @@ -192,6 +194,23 @@ type endpoint struct { ndp ndpState mld mldState } + + // dad is used to check if an arbitrary address is already assigned to some + // neighbor. + // + // Note: this is different from mu.ndp.dad which is used to perform DAD for + // addresses that are assigned to the interface. Removing an address aborts + // DAD; if we had used the same state, handlers for a removed address would + // not be called with the actual DAD result. + // + // LOCK ORDERING: mu > dad.mu. + dad struct { + mu struct { + sync.Mutex + + dad ip.DAD + } + } } // NICNameFromID is a function that returns a stable name for the specified NIC, @@ -226,6 +245,29 @@ type OpaqueInterfaceIdentifierOptions struct { SecretKey []byte } +// CheckDuplicateAddress implements stack.DuplicateAddressDetector. +func (e *endpoint) CheckDuplicateAddress(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition { + e.dad.mu.Lock() + defer e.dad.mu.Unlock() + return e.dad.mu.dad.CheckDuplicateAddressLocked(addr, h) +} + +// SetDADConfigurations implements stack.DuplicateAddressDetector. +func (e *endpoint) SetDADConfigurations(c stack.DADConfigurations) { + e.mu.Lock() + defer e.mu.Unlock() + e.dad.mu.Lock() + defer e.dad.mu.Unlock() + + e.mu.ndp.dad.SetConfigsLocked(c) + e.dad.mu.dad.SetConfigsLocked(c) +} + +// DuplicateAddressProtocol implements stack.DuplicateAddressDetector. +func (*endpoint) DuplicateAddressProtocol() tcpip.NetworkProtocolNumber { + return ProtocolNumber +} + // HandleLinkResolutionFailure implements stack.LinkResolvableNetworkEndpoint. func (e *endpoint) HandleLinkResolutionFailure(pkt *stack.PacketBuffer) { // handleControl expects the entire offending packet to be in the packet @@ -321,7 +363,7 @@ func (e *endpoint) dupTentativeAddrDetected(addr tcpip.Address) tcpip.Error { // If the address is a SLAAC address, do not invalidate its SLAAC prefix as an // attempt will be made to generate a new address for it. - if err := e.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */); err != nil { + if err := e.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */, true /* dadFailure */); err != nil { return err } @@ -525,7 +567,7 @@ func (e *endpoint) stopDADForPermanentAddressesLocked() { addr := addressEndpoint.AddressWithPrefix().Address if header.IsV6UnicastAddress(addr) { - e.mu.ndp.stopDuplicateAddressDetection(addr) + e.mu.ndp.stopDuplicateAddressDetection(addr, false /* failed */) } return true @@ -1390,18 +1432,18 @@ func (e *endpoint) RemovePermanentAddress(addr tcpip.Address) tcpip.Error { return &tcpip.ErrBadLocalAddress{} } - return e.removePermanentEndpointLocked(addressEndpoint, true) + return e.removePermanentEndpointLocked(addressEndpoint, true /* allowSLAACInvalidation */, false /* dadFailure */) } // removePermanentEndpointLocked is like removePermanentAddressLocked except // it works with a stack.AddressEndpoint. // // Precondition: e.mu must be write locked. -func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEndpoint, allowSLAACInvalidation bool) tcpip.Error { +func (e *endpoint) removePermanentEndpointLocked(addressEndpoint stack.AddressEndpoint, allowSLAACInvalidation, dadFailure bool) tcpip.Error { addr := addressEndpoint.AddressWithPrefix() unicast := header.IsV6UnicastAddress(addr.Address) if unicast { - e.mu.ndp.stopDuplicateAddressDetection(addr.Address) + e.mu.ndp.stopDuplicateAddressDetection(addr.Address, dadFailure) // If we are removing an address generated via SLAAC, cleanup // its SLAAC resources and notify the integrator. @@ -1747,6 +1789,13 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.Tran e.mu.addressableEndpointState.Init(e) e.mu.ndp.init(e) e.mu.mld.init(e) + e.dad.mu.Lock() + e.dad.mu.dad.Init(&e.dad.mu, p.options.DADConfigs, ip.DADOptions{ + Clock: p.stack.Clock(), + Protocol: &e.mu.ndp, + NICID: nic.ID(), + }) + e.dad.mu.Unlock() e.mu.Unlock() stackStats := p.stack.Stats() @@ -1949,6 +1998,9 @@ type Options struct { // MLD holds options for MLD. MLD MLDOptions + + // DADConfigs holds the default DAD configurations used by IPv6 endpoints. + DADConfigs stack.DADConfigurations } // NewProtocolWithOptions returns an IPv6 network protocol. diff --git a/pkg/tcpip/network/ipv6/mld_test.go b/pkg/tcpip/network/ipv6/mld_test.go index f6ffa7133..fe39555e0 100644 --- a/pkg/tcpip/network/ipv6/mld_test.go +++ b/pkg/tcpip/network/ipv6/mld_test.go @@ -126,7 +126,7 @@ func TestSendQueuedMLDReports(t *testing.T) { clock := faketime.NewManualClock() s := stack.New(stack.Options{ NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{ - NDPConfigs: ipv6.NDPConfigurations{ + DADConfigs: stack.DADConfigurations{ DupAddrDetectTransmits: test.dadTransmits, RetransmitTimer: test.retransmitTimer, }, diff --git a/pkg/tcpip/network/ipv6/ndp.go b/pkg/tcpip/network/ipv6/ndp.go index 411a6c862..a55330b7e 100644 --- a/pkg/tcpip/network/ipv6/ndp.go +++ b/pkg/tcpip/network/ipv6/ndp.go @@ -23,34 +23,11 @@ import ( "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/network/internal/ip" "gvisor.dev/gvisor/pkg/tcpip/stack" ) const ( - // defaultRetransmitTimer is the default amount of time to wait between - // sending reachability probes. - // - // Default taken from RETRANS_TIMER of RFC 4861 section 10. - defaultRetransmitTimer = time.Second - - // minimumRetransmitTimer is the minimum amount of time to wait between - // sending reachability probes. - // - // Note, RFC 4861 does not impose a minimum Retransmit Timer, but we do here - // to make sure the messages are not sent all at once. We also come to this - // value because in the RetransmitTimer field of a Router Advertisement, a - // value of 0 means unspecified, so the smallest valid value is 1. Note, the - // unit of the RetransmitTimer field in the Router Advertisement is - // milliseconds. - minimumRetransmitTimer = time.Millisecond - - // defaultDupAddrDetectTransmits is the default number of NDP Neighbor - // Solicitation messages to send when doing Duplicate Address Detection - // for a tentative address. - // - // Default = 1 (from RFC 4862 section 5.1) - defaultDupAddrDetectTransmits = 1 - // defaultMaxRtrSolicitations is the default number of Router // Solicitation messages to send when an IPv6 endpoint becomes enabled. // @@ -330,18 +307,6 @@ type NDPDispatcher interface { // NDPConfigurations is the NDP configurations for the netstack. type NDPConfigurations struct { - // The number of Neighbor Solicitation messages to send when doing - // Duplicate Address Detection for a tentative address. - // - // Note, a value of zero effectively disables DAD. - DupAddrDetectTransmits uint8 - - // The amount of time to wait between sending Neighbor solicitation - // messages. - // - // Must be greater than or equal to 1ms. - RetransmitTimer time.Duration - // The number of Router Solicitation messages to send when the IPv6 endpoint // becomes enabled. MaxRtrSolicitations uint8 @@ -413,8 +378,6 @@ type NDPConfigurations struct { // default values. func DefaultNDPConfigurations() NDPConfigurations { return NDPConfigurations{ - DupAddrDetectTransmits: defaultDupAddrDetectTransmits, - RetransmitTimer: defaultRetransmitTimer, MaxRtrSolicitations: defaultMaxRtrSolicitations, RtrSolicitationInterval: defaultRtrSolicitationInterval, MaxRtrSolicitationDelay: defaultMaxRtrSolicitationDelay, @@ -432,10 +395,6 @@ func DefaultNDPConfigurations() NDPConfigurations { // validate modifies an NDPConfigurations with valid values. If invalid values // are present in c, the corresponding default values are used instead. func (c *NDPConfigurations) validate() { - if c.RetransmitTimer < minimumRetransmitTimer { - c.RetransmitTimer = defaultRetransmitTimer - } - if c.RtrSolicitationInterval < minimumRtrSolicitationInterval { c.RtrSolicitationInterval = defaultRtrSolicitationInterval } @@ -476,7 +435,7 @@ type ndpState struct { configs NDPConfigurations // The DAD timers to send the next NS message, or resolve the address. - dad map[tcpip.Address]timer + dad ip.DAD // The default routers discovered through Router Advertisements. defaultRouters map[tcpip.Address]defaultRouterState @@ -635,130 +594,46 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE panic(fmt.Sprintf("ndpdad: addr %s is not tentative on NIC(%d)", addr, ndp.ep.nic.ID())) } - // Should not attempt to perform DAD on an address that is currently in the - // DAD process. - if _, ok := ndp.dad[addr]; ok { - // Should never happen because we should only ever call this function for - // newly created addresses. If we attemped to "add" an address that already - // existed, we would get an error since we attempted to add a duplicate - // address, or its reference count would have been increased without doing - // the work that would have been done for an address that was brand new. - // See endpoint.addAddressLocked. - panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.ep.nic.ID())) - } + ret := ndp.dad.CheckDuplicateAddressLocked(addr, func(r stack.DADResult) { + if addressEndpoint.GetKind() != stack.PermanentTentative { + // The endpoint should still be marked as tentative since we are still + // performing DAD on it. + panic(fmt.Sprintf("ndpdad: addr %s is no longer tentative on NIC(%d)", addr, ndp.ep.nic.ID())) + } - if ndp.configs.DupAddrDetectTransmits == 0 { - addressEndpoint.SetKind(stack.Permanent) + if r.Resolved { + addressEndpoint.SetKind(stack.Permanent) + } - // Consider DAD to have resolved even if no DAD messages were actually - // transmitted. if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, true, nil) + ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, r.Resolved, r.Err) } - ndp.ep.onAddressAssignedLocked(addr) - return nil - } - - var remaining remainingCounter - remaining.init(ndp.configs.DupAddrDetectTransmits) - // We initially start a timer to fire immediately because some of the DAD work - // cannot be done while holding the IPv6 endpoint's lock. This is effectively - // the same as starting a goroutine but we use a timer that fires immediately - // so we can reset it for the next DAD iteration. - - // Protected by ndp.ep.mu. - done := false - - ndp.dad[addr] = timer{ - done: &done, - timer: ndp.ep.protocol.stack.Clock().AfterFunc(0, func() { - // Okay to hold this lock while writing packets since we use a different - // lock per DAD timer so there will not be any lock contention. - remaining.mu.Lock() - defer remaining.mu.Unlock() - - var err tcpip.Error - dadDone := remaining.mu.remaining == 0 - if !dadDone { - snmc := header.SolicitedNodeAddr(addr) - - icmp := header.ICMPv6(buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize)) - icmp.SetType(header.ICMPv6NeighborSolicit) - ns := header.NDPNeighborSolicit(icmp.MessageBody()) - ns.SetTargetAddress(addr) - icmp.SetChecksum(header.ICMPv6Checksum(icmp, header.IPv6Any, snmc, buffer.VectorisedView{})) - - pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ - ReserveHeaderBytes: int(ndp.ep.MaxHeaderLength()), - Data: buffer.View(icmp).ToVectorisedView(), - }) - - sent := ndp.ep.stats.icmp.packetsSent - if err := addIPHeader(header.IPv6Any, snmc, pkt, stack.NetworkHeaderParams{ - Protocol: header.ICMPv6ProtocolNumber, - TTL: header.NDPHopLimit, - }, nil /* extensionHeaders */); err != nil { - panic(fmt.Sprintf("failed to add IP header: %s", err)) - } - - err = ndp.ep.nic.WritePacketToRemote(header.EthernetAddressFromMulticastIPv6Address(snmc), nil /* gso */, ProtocolNumber, pkt) - if err != nil { - sent.dropped.Increment() - } else { - sent.neighborSolicit.Increment() - } + if r.Resolved { + if addressEndpoint.ConfigType() == stack.AddressConfigSlaac { + // Reset the generation attempts counter as we are starting the + // generation of a new address for the SLAAC prefix. + ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */) } - ndp.ep.mu.Lock() - defer ndp.ep.mu.Unlock() - - if done { - // DAD was stopped. - return - } - - timer, ok := ndp.dad[addr] - if !ok { - panic(fmt.Sprintf("ndpdad: DAD timer fired but missing state for %s on NIC(%d)", addr, ndp.ep.nic.ID())) - } - - if addressEndpoint.GetKind() != stack.PermanentTentative { - // The endpoint should still be marked as tentative since we are still - // performing DAD on it. - panic(fmt.Sprintf("ndpdad: addr %s is no longer tentative on NIC(%d)", addr, ndp.ep.nic.ID())) - } - - if dadDone { - // DAD has resolved. - addressEndpoint.SetKind(stack.Permanent) - } else if err == nil { - // DAD is not done and we had no errors when sending the last NDP NS, - // schedule the next DAD timer. - remaining.mu.remaining-- - timer.timer.Reset(ndp.configs.RetransmitTimer) - return - } - - // At this point we know that either DAD is done or we hit an error - // sending the last NDP NS. Either way, clean up addr's DAD state and let - // the integrator know DAD has completed. - delete(ndp.dad, addr) + ndp.ep.onAddressAssignedLocked(addr) + } + }) - if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, dadDone, err) - } + switch ret { + case stack.DADStarting: + case stack.DADAlreadyRunning: + panic(fmt.Sprintf("ndpdad: already performing DAD for addr %s on NIC(%d)", addr, ndp.ep.nic.ID())) + case stack.DADDisabled: + addressEndpoint.SetKind(stack.Permanent) - if dadDone { - if addressEndpoint.ConfigType() == stack.AddressConfigSlaac { - // Reset the generation attempts counter as we are starting the - // generation of a new address for the SLAAC prefix. - ndp.regenerateTempSLAACAddr(addressEndpoint.AddressWithPrefix().Subnet(), true /* resetGenAttempts */) - } + // Consider DAD to have resolved even if no DAD messages were actually + // transmitted. + if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { + ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, true, nil) + } - ndp.ep.onAddressAssignedLocked(addr) - } - }), + ndp.ep.onAddressAssignedLocked(addr) } return nil @@ -772,21 +647,8 @@ func (ndp *ndpState) startDuplicateAddressDetection(addr tcpip.Address, addressE // of this function to handle such a scenario. // // The IPv6 endpoint that ndp belongs to MUST be locked. -func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address) { - timer, ok := ndp.dad[addr] - if !ok { - // Not currently performing DAD on addr, just return. - return - } - - timer.timer.Stop() - *timer.done = true - delete(ndp.dad, addr) - - // Let the integrator know DAD did not resolve. - if ndpDisp := ndp.ep.protocol.options.NDPDisp; ndpDisp != nil { - ndpDisp.OnDuplicateAddressDetectionStatus(ndp.ep.nic.ID(), addr, false, nil) - } +func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address, failed bool) { + ndp.dad.StopLocked(addr, !failed) } // handleRA handles a Router Advertisement message that arrived on the NIC @@ -1651,7 +1513,7 @@ func (ndp *ndpState) invalidateSLAACPrefix(prefix tcpip.Subnet, state slaacPrefi if addressEndpoint := state.stableAddr.addressEndpoint; addressEndpoint != nil { // Since we are already invalidating the prefix, do not invalidate the // prefix when removing the address. - if err := ndp.ep.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */); err != nil { + if err := ndp.ep.removePermanentEndpointLocked(addressEndpoint, false /* allowSLAACInvalidation */, false /* dadFailure */); err != nil { panic(fmt.Sprintf("ndp: error removing stable SLAAC address %s: %s", addressEndpoint.AddressWithPrefix(), err)) } } @@ -1710,7 +1572,7 @@ func (ndp *ndpState) cleanupSLAACPrefixResources(prefix tcpip.Subnet, state slaa func (ndp *ndpState) invalidateTempSLAACAddr(tempAddrs map[tcpip.Address]tempSLAACAddrState, tempAddr tcpip.Address, tempAddrState tempSLAACAddrState) { // Since we are already invalidating the address, do not invalidate the // address when removing the address. - if err := ndp.ep.removePermanentEndpointLocked(tempAddrState.addressEndpoint, false /* allowSLAACInvalidation */); err != nil { + if err := ndp.ep.removePermanentEndpointLocked(tempAddrState.addressEndpoint, false /* allowSLAACInvalidation */, false /* dadFailure */); err != nil { panic(fmt.Sprintf("error removing temporary SLAAC address %s: %s", tempAddrState.addressEndpoint.AddressWithPrefix(), err)) } @@ -1942,13 +1804,17 @@ func (ndp *ndpState) stopSolicitingRouters() { } func (ndp *ndpState) init(ep *endpoint) { - if ndp.dad != nil { + if ndp.defaultRouters != nil { panic("attempted to initialize NDP state twice") } ndp.ep = ep ndp.configs = ep.protocol.options.NDPConfigs - ndp.dad = make(map[tcpip.Address]timer) + ndp.dad.Init(&ndp.ep.mu, ep.protocol.options.DADConfigs, ip.DADOptions{ + Clock: ep.protocol.stack.Clock(), + Protocol: ndp, + NICID: ep.nic.ID(), + }) ndp.defaultRouters = make(map[tcpip.Address]defaultRouterState) ndp.onLinkPrefixes = make(map[tcpip.Subnet]onLinkPrefixState) ndp.slaacPrefixes = make(map[tcpip.Subnet]slaacPrefixState) @@ -1958,3 +1824,38 @@ func (ndp *ndpState) init(ep *endpoint) { ndp.temporaryAddressDesyncFactor = time.Duration(rand.Int63n(int64(MaxDesyncFactor))) } } + +func (ndp *ndpState) SendDADMessage(addr tcpip.Address) tcpip.Error { + snmc := header.SolicitedNodeAddr(addr) + return ndp.ep.sendNDPNS(header.IPv6Any, snmc, addr, header.EthernetAddressFromMulticastIPv6Address(snmc), nil /* opts */) +} + +func (e *endpoint) sendNDPNS(srcAddr, dstAddr, targetAddr tcpip.Address, remoteLinkAddr tcpip.LinkAddress, opts header.NDPOptionsSerializer) tcpip.Error { + icmp := header.ICMPv6(buffer.NewView(header.ICMPv6NeighborSolicitMinimumSize + opts.Length())) + icmp.SetType(header.ICMPv6NeighborSolicit) + ns := header.NDPNeighborSolicit(icmp.MessageBody()) + ns.SetTargetAddress(targetAddr) + ns.Options().Serialize(opts) + icmp.SetChecksum(header.ICMPv6Checksum(icmp, srcAddr, dstAddr, buffer.VectorisedView{})) + + pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ + ReserveHeaderBytes: int(e.MaxHeaderLength()), + Data: buffer.View(icmp).ToVectorisedView(), + }) + + if err := addIPHeader(srcAddr, dstAddr, pkt, stack.NetworkHeaderParams{ + Protocol: header.ICMPv6ProtocolNumber, + TTL: header.NDPHopLimit, + }, nil /* extensionHeaders */); err != nil { + panic(fmt.Sprintf("failed to add IP header: %s", err)) + } + + sent := e.stats.icmp.packetsSent + err := e.nic.WritePacketToRemote(remoteLinkAddr, nil /* gso */, ProtocolNumber, pkt) + if err != nil { + sent.dropped.Increment() + } else { + sent.neighborSolicit.Increment() + } + return err +} diff --git a/pkg/tcpip/network/ipv6/ndp_test.go b/pkg/tcpip/network/ipv6/ndp_test.go index 5ff247653..ce20af0e3 100644 --- a/pkg/tcpip/network/ipv6/ndp_test.go +++ b/pkg/tcpip/network/ipv6/ndp_test.go @@ -24,6 +24,7 @@ import ( "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/checker" + "gvisor.dev/gvisor/pkg/tcpip/faketime" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/link/channel" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -1222,3 +1223,110 @@ func TestRouterAdvertValidation(t *testing.T) { }) } } + +// TestCheckDuplicateAddress checks that calls to CheckDuplicateAddress and DAD +// performed when adding new addresses do not interfere with each other. +func TestCheckDuplicateAddress(t *testing.T) { + const nicID = 1 + + clock := faketime.NewManualClock() + dadConfigs := stack.DADConfigurations{ + DupAddrDetectTransmits: 1, + RetransmitTimer: time.Second, + } + s := stack.New(stack.Options{ + Clock: clock, + NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocolWithOptions(Options{ + DADConfigs: dadConfigs, + })}, + }) + // This test is expected to send at max 2 DAD messages. We allow an extra + // packet to be stored to catch unexpected packets. + e := channel.New(3, header.IPv6MinimumMTU, linkAddr0) + e.LinkEPCapabilities |= stack.CapabilityResolutionRequired + if err := s.CreateNIC(nicID, e); err != nil { + t.Fatalf("CreateNIC(%d, _) = %s", nicID, err) + } + + dadPacketsSent := 1 + if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil { + t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err) + } + + // Start DAD for the address we just added. + // + // Even though the stack will perform DAD before the added address transitions + // from tentative to assigned, this DAD request should be independent of that. + ch := make(chan stack.DADResult, 3) + dadRequestsMade := 1 + dadPacketsSent++ + if res, err := s.CheckDuplicateAddress(nicID, ProtocolNumber, lladdr0, func(r stack.DADResult) { + ch <- r + }); err != nil { + t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, ProtocolNumber, lladdr0, err) + } else if res != stack.DADStarting { + t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADStarting) + } + + // Remove the address and make sure our DAD request was not stopped. + if err := s.RemoveAddress(nicID, lladdr0); err != nil { + t.Fatalf("RemoveAddress(%d, %s): %s", nicID, lladdr0, err) + } + // Should not restart DAD since we already requested DAD above - the handler + // should be called when the original request compeletes so we should not send + // an extra DAD message here. + dadRequestsMade++ + if res, err := s.CheckDuplicateAddress(nicID, ProtocolNumber, lladdr0, func(r stack.DADResult) { + ch <- r + }); err != nil { + t.Fatalf("s.CheckDuplicateAddress(%d, %d, %s, _): %s", nicID, ProtocolNumber, lladdr0, err) + } else if res != stack.DADAlreadyRunning { + t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADAlreadyRunning) + } + + // Wait for DAD to resolve. + clock.Advance(time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer) + for i := 0; i < dadRequestsMade; i++ { + if diff := cmp.Diff(stack.DADResult{Resolved: true}, <-ch); diff != "" { + t.Errorf("(i=%d) DAD result mismatch (-want +got):\n%s", i, diff) + } + } + // Should have no more results. + select { + case r := <-ch: + t.Errorf("unexpectedly got an extra DAD result; r = %#v", r) + default: + } + + snmc := header.SolicitedNodeAddr(lladdr0) + remoteLinkAddr := header.EthernetAddressFromMulticastIPv6Address(snmc) + + for i := 0; i < dadPacketsSent; i++ { + p, ok := e.Read() + if !ok { + t.Fatalf("expected %d-th DAD message", i) + } + + if p.Proto != header.IPv6ProtocolNumber { + t.Errorf("(i=%d) got p.Proto = %d, want = %d", i, p.Proto, header.IPv6ProtocolNumber) + } + + if p.Route.RemoteLinkAddress != remoteLinkAddr { + t.Errorf("(i=%d) got p.Route.RemoteLinkAddress = %s, want = %s", i, p.Route.RemoteLinkAddress, remoteLinkAddr) + } + + checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()), + checker.SrcAddr(header.IPv6Any), + checker.DstAddr(snmc), + checker.TTL(header.NDPHopLimit), + checker.NDPNS( + checker.NDPNSTargetAddress(lladdr0), + checker.NDPNSOptions(nil), + )) + } + + // Should have no more packets. + if p, ok := e.Read(); ok { + t.Errorf("got unexpected packet = %#v", p) + } +} diff --git a/pkg/tcpip/stack/ndp_test.go b/pkg/tcpip/stack/ndp_test.go index 4fa4101b0..3b6ba9509 100644 --- a/pkg/tcpip/stack/ndp_test.go +++ b/pkg/tcpip/stack/ndp_test.go @@ -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, diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index 00cfba35a..f66db16a7 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -55,8 +55,9 @@ type nic struct { // The network endpoints themselves may be modified by calling the interface's // methods, but the map reference and entries must be constant. - networkEndpoints map[tcpip.NetworkProtocolNumber]NetworkEndpoint - linkAddrResolvers map[tcpip.NetworkProtocolNumber]*linkResolver + networkEndpoints map[tcpip.NetworkProtocolNumber]NetworkEndpoint + linkAddrResolvers map[tcpip.NetworkProtocolNumber]*linkResolver + duplicateAddressDetectors map[tcpip.NetworkProtocolNumber]DuplicateAddressDetector // enabled is set to 1 when the NIC is enabled and 0 when it is disabled. // @@ -145,13 +146,14 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC nic := &nic{ LinkEndpoint: ep, - stack: stack, - id: id, - name: name, - context: ctx, - stats: makeNICStats(), - networkEndpoints: make(map[tcpip.NetworkProtocolNumber]NetworkEndpoint), - linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]*linkResolver), + stack: stack, + id: id, + name: name, + context: ctx, + stats: makeNICStats(), + networkEndpoints: make(map[tcpip.NetworkProtocolNumber]NetworkEndpoint), + linkAddrResolvers: make(map[tcpip.NetworkProtocolNumber]*linkResolver), + duplicateAddressDetectors: make(map[tcpip.NetworkProtocolNumber]DuplicateAddressDetector), } nic.linkResQueue.init(nic) nic.mu.packetEPs = make(map[tcpip.NetworkProtocolNumber]*packetEndpointList) @@ -176,6 +178,10 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, ctx NICC nic.linkAddrResolvers[r.LinkAddressProtocol()] = l } } + + if d, ok := netEP.(DuplicateAddressDetector); ok { + nic.duplicateAddressDetectors[d.DuplicateAddressProtocol()] = d + } } nic.LinkEndpoint.Attach(nic) @@ -991,3 +997,12 @@ func (n *nic) CheckLocalAddress(protocol tcpip.NetworkProtocolNumber, addr tcpip return false } + +func (n *nic) checkDuplicateAddress(protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, h DADCompletionHandler) (DADCheckAddressDisposition, tcpip.Error) { + d, ok := n.duplicateAddressDetectors[protocol] + if !ok { + return 0, &tcpip.ErrNotSupported{} + } + + return d.CheckDuplicateAddress(addr, h), nil +} diff --git a/pkg/tcpip/stack/registration.go b/pkg/tcpip/stack/registration.go index 2bc1c4270..43e9e4beb 100644 --- a/pkg/tcpip/stack/registration.go +++ b/pkg/tcpip/stack/registration.go @@ -16,6 +16,7 @@ package stack import ( "fmt" + "time" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/buffer" @@ -851,7 +852,97 @@ type InjectableLinkEndpoint interface { InjectOutbound(dest tcpip.Address, packet []byte) tcpip.Error } -// A LinkAddressResolver handles link address resolution for a network protocol. +// DADResult is the result of a duplicate address detection process. +type DADResult struct { + // Resolved is true when DAD completed without detecting a duplicate address + // on the link. + // + // Ignored when Err is non-nil. + Resolved bool + + // Err is an error encountered while performing DAD. + Err tcpip.Error +} + +// DADCompletionHandler is a handler for DAD completion. +type DADCompletionHandler func(DADResult) + +// DADCheckAddressDisposition enumerates the possible return values from +// DAD.CheckDuplicateAddress. +type DADCheckAddressDisposition int + +const ( + _ DADCheckAddressDisposition = iota + + // DADDisabled indicates that DAD is disabled. + DADDisabled + + // DADStarting indicates that DAD is starting for an address. + DADStarting + + // DADAlreadyRunning indicates that DAD was already started for an address. + DADAlreadyRunning +) + +const ( + // defaultDupAddrDetectTransmits is the default number of NDP Neighbor + // Solicitation messages to send when doing Duplicate Address Detection + // for a tentative address. + // + // Default = 1 (from RFC 4862 section 5.1) + defaultDupAddrDetectTransmits = 1 +) + +// DADConfigurations holds configurations for duplicate address detection. +type DADConfigurations struct { + // The number of Neighbor Solicitation messages to send when doing + // Duplicate Address Detection for a tentative address. + // + // Note, a value of zero effectively disables DAD. + DupAddrDetectTransmits uint8 + + // The amount of time to wait between sending Neighbor Solicitation + // messages. + // + // Must be greater than or equal to 1ms. + RetransmitTimer time.Duration +} + +// DefaultDADConfigurations returns the default DAD configurations. +func DefaultDADConfigurations() DADConfigurations { + return DADConfigurations{ + DupAddrDetectTransmits: defaultDupAddrDetectTransmits, + RetransmitTimer: defaultRetransmitTimer, + } +} + +// Validate modifies the configuration with valid values. If invalid values are +// present in the configurations, the corresponding default values are used +// instead. +func (c *DADConfigurations) Validate() { + if c.RetransmitTimer < minimumRetransmitTimer { + c.RetransmitTimer = defaultRetransmitTimer + } +} + +// DuplicateAddressDetector handles checking if an address is already assigned +// to some neighboring node on the link. +type DuplicateAddressDetector interface { + // CheckDuplicateAddress checks if an address is assigned to a neighbor. + // + // If DAD is already being performed for the address, the handler will be + // called with the result of the original DAD request. + CheckDuplicateAddress(tcpip.Address, DADCompletionHandler) DADCheckAddressDisposition + + // SetDADConfiguations sets the configurations for DAD. + SetDADConfigurations(c DADConfigurations) + + // DuplicateAddressProtocol returns the network protocol the receiver can + // perform duplicate address detection for. + DuplicateAddressProtocol() tcpip.NetworkProtocolNumber +} + +// LinkAddressResolver handles link address resolution for a network protocol. type LinkAddressResolver interface { // LinkAddressRequest sends a request for the link address of the target // address. The request is broadcasted on the local network if a remote link diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 46c1817ea..674c9a1ff 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -1466,6 +1466,17 @@ func (s *Stack) CheckNetworkProtocol(protocol tcpip.NetworkProtocolNumber) bool return ok } +// CheckDuplicateAddress performs duplicate address detection for the address on +// the specified interface. +func (s *Stack) CheckDuplicateAddress(nicID tcpip.NICID, protocol tcpip.NetworkProtocolNumber, addr tcpip.Address, h DADCompletionHandler) (DADCheckAddressDisposition, tcpip.Error) { + nic, ok := s.nics[nicID] + if !ok { + return 0, &tcpip.ErrUnknownNICID{} + } + + return nic.checkDuplicateAddress(protocol, addr, h) +} + // CheckLocalAddress determines if the given local address exists, and if it // does, returns the id of the NIC it's bound to. Returns 0 if the address // does not exist. diff --git a/pkg/tcpip/stack/stack_test.go b/pkg/tcpip/stack/stack_test.go index 62066b3aa..92a0cb401 100644 --- a/pkg/tcpip/stack/stack_test.go +++ b/pkg/tcpip/stack/stack_test.go @@ -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, }, diff --git a/pkg/tcpip/tests/integration/link_resolution_test.go b/pkg/tcpip/tests/integration/link_resolution_test.go index 553ec950f..824f81a42 100644 --- a/pkg/tcpip/tests/integration/link_resolution_test.go +++ b/pkg/tcpip/tests/integration/link_resolution_test.go @@ -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: + } + }) + } +} -- cgit v1.2.3