summaryrefslogtreecommitdiffhomepage
path: root/pkg/tcpip/network
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/tcpip/network')
-rw-r--r--pkg/tcpip/network/arp/arp.go13
-rw-r--r--pkg/tcpip/network/internal/ip/duplicate_address_detection.go139
-rw-r--r--pkg/tcpip/network/ipv6/icmp.go55
-rw-r--r--pkg/tcpip/network/ipv6/ipv6.go89
-rw-r--r--pkg/tcpip/network/ipv6/ndp.go14
5 files changed, 251 insertions, 59 deletions
diff --git a/pkg/tcpip/network/arp/arp.go b/pkg/tcpip/network/arp/arp.go
index 43a4b7cac..7ae38d684 100644
--- a/pkg/tcpip/network/arp/arp.go
+++ b/pkg/tcpip/network/arp/arp.go
@@ -38,6 +38,7 @@ const (
var _ stack.DuplicateAddressDetector = (*endpoint)(nil)
var _ stack.LinkAddressResolver = (*endpoint)(nil)
+var _ ip.DADProtocol = (*endpoint)(nil)
// ARP endpoints need to implement stack.NetworkEndpoint because the stack
// considers the layer above the link-layer a network layer; the only
@@ -82,7 +83,8 @@ func (*endpoint) DuplicateAddressProtocol() tcpip.NetworkProtocolNumber {
return header.IPv4ProtocolNumber
}
-func (e *endpoint) SendDADMessage(addr tcpip.Address) tcpip.Error {
+// SendDADMessage implements ip.DADProtocol.
+func (e *endpoint) SendDADMessage(addr tcpip.Address, _ []byte) tcpip.Error {
return e.sendARPRequest(header.IPv4Any, addr, header.EthernetBroadcastAddress)
}
@@ -284,9 +286,12 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.Tran
e.mu.Lock()
e.mu.dad.Init(&e.mu, p.options.DADConfigs, ip.DADOptions{
- Clock: p.stack.Clock(),
- Protocol: e,
- NICID: nic.ID(),
+ Clock: p.stack.Clock(),
+ SecureRNG: p.stack.SecureRNG(),
+ // ARP does not support sending nonce values.
+ NonceSize: 0,
+ Protocol: e,
+ NICID: nic.ID(),
})
e.mu.Unlock()
diff --git a/pkg/tcpip/network/internal/ip/duplicate_address_detection.go b/pkg/tcpip/network/internal/ip/duplicate_address_detection.go
index 0053646ee..eed49f5d2 100644
--- a/pkg/tcpip/network/internal/ip/duplicate_address_detection.go
+++ b/pkg/tcpip/network/internal/ip/duplicate_address_detection.go
@@ -16,14 +16,27 @@
package ip
import (
+ "bytes"
"fmt"
+ "io"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
+type extendRequest int
+
+const (
+ notRequested extendRequest = iota
+ requested
+ extended
+)
+
type dadState struct {
+ nonce []byte
+ extendRequest extendRequest
+
done *bool
timer tcpip.Timer
@@ -33,14 +46,17 @@ type dadState struct {
// 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
+ SendDADMessage(tcpip.Address, []byte) tcpip.Error
}
// DADOptions holds options for DAD.
type DADOptions struct {
- Clock tcpip.Clock
- Protocol DADProtocol
- NICID tcpip.NICID
+ Clock tcpip.Clock
+ SecureRNG io.Reader
+ NonceSize uint8
+ ExtendDADTransmits uint8
+ Protocol DADProtocol
+ NICID tcpip.NICID
}
// DAD performs duplicate address detection for addresses.
@@ -63,6 +79,10 @@ func (d *DAD) Init(protocolMU sync.Locker, configs stack.DADConfigurations, opts
panic("attempted to initialize DAD state twice")
}
+ if opts.NonceSize != 0 && opts.ExtendDADTransmits == 0 {
+ panic(fmt.Sprintf("given a non-zero value for NonceSize (%d) but zero for ExtendDADTransmits", opts.NonceSize))
+ }
+
*d = DAD{
opts: opts,
configs: configs,
@@ -96,10 +116,55 @@ func (d *DAD) CheckDuplicateAddressLocked(addr tcpip.Address, h stack.DADComplet
s = dadState{
done: &done,
timer: d.opts.Clock.AfterFunc(0, func() {
- var err tcpip.Error
dadDone := remaining == 0
+
+ nonce, earlyReturn := func() ([]byte, bool) {
+ d.protocolMU.Lock()
+ defer d.protocolMU.Unlock()
+
+ if done {
+ return nil, true
+ }
+
+ 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))
+ }
+
+ // As per RFC 7527 section 4
+ //
+ // If any probe is looped back within RetransTimer milliseconds
+ // after having sent DupAddrDetectTransmits NS(DAD) messages, the
+ // interface continues with another MAX_MULTICAST_SOLICIT number of
+ // NS(DAD) messages transmitted RetransTimer milliseconds apart.
+ if dadDone && s.extendRequest == requested {
+ dadDone = false
+ remaining = d.opts.ExtendDADTransmits
+ s.extendRequest = extended
+ }
+
+ if !dadDone && d.opts.NonceSize != 0 {
+ if s.nonce == nil {
+ s.nonce = make([]byte, d.opts.NonceSize)
+ }
+
+ if n, err := io.ReadFull(d.opts.SecureRNG, s.nonce); err != nil {
+ panic(fmt.Sprintf("SecureRNG.Read(...): %s", err))
+ } else if n != len(s.nonce) {
+ panic(fmt.Sprintf("expected to read %d bytes from secure RNG, only read %d bytes", len(s.nonce), n))
+ }
+ }
+
+ d.addresses[addr] = s
+ return s.nonce, false
+ }()
+ if earlyReturn {
+ return
+ }
+
+ var err tcpip.Error
if !dadDone {
- err = d.opts.Protocol.SendDADMessage(addr)
+ err = d.opts.Protocol.SendDADMessage(addr, nonce)
}
d.protocolMU.Lock()
@@ -142,6 +207,68 @@ func (d *DAD) CheckDuplicateAddressLocked(addr tcpip.Address, h stack.DADComplet
return ret
}
+// ExtendIfNonceEqualLockedDisposition enumerates the possible results from
+// ExtendIfNonceEqualLocked.
+type ExtendIfNonceEqualLockedDisposition int
+
+const (
+ // Extended indicates that the DAD process was extended.
+ Extended ExtendIfNonceEqualLockedDisposition = iota
+
+ // AlreadyExtended indicates that the DAD process was already extended.
+ AlreadyExtended
+
+ // NoDADStateFound indicates that DAD state was not found for the address.
+ NoDADStateFound
+
+ // NonceDisabled indicates that nonce values are not sent with DAD messages.
+ NonceDisabled
+
+ // NonceNotEqual indicates that the nonce value passed and the nonce in the
+ // last send DAD message are not equal.
+ NonceNotEqual
+)
+
+// ExtendIfNonceEqualLocked extends the DAD process if the provided nonce is the
+// same as the nonce sent in the last DAD message.
+//
+// Precondition: d.protocolMU must be locked.
+func (d *DAD) ExtendIfNonceEqualLocked(addr tcpip.Address, nonce []byte) ExtendIfNonceEqualLockedDisposition {
+ s, ok := d.addresses[addr]
+ if !ok {
+ return NoDADStateFound
+ }
+
+ if d.opts.NonceSize == 0 {
+ return NonceDisabled
+ }
+
+ if s.extendRequest != notRequested {
+ return AlreadyExtended
+ }
+
+ // As per RFC 7527 section 4
+ //
+ // If any probe is looped back within RetransTimer milliseconds after having
+ // sent DupAddrDetectTransmits NS(DAD) messages, the interface continues
+ // with another MAX_MULTICAST_SOLICIT number of NS(DAD) messages transmitted
+ // RetransTimer milliseconds apart.
+ //
+ // If a DAD message has already been sent and the nonce value we observed is
+ // the same as the nonce value we last sent, then we assume our probe was
+ // looped back and request an extension to the DAD process.
+ //
+ // Note, the first DAD message is sent asynchronously so we need to make sure
+ // that we sent a DAD message by checking if we have a nonce value set.
+ if s.nonce != nil && bytes.Equal(s.nonce, nonce) {
+ s.extendRequest = requested
+ d.addresses[addr] = s
+ return Extended
+ }
+
+ return NonceNotEqual
+}
+
// StopLocked stops a currently running DAD process.
//
// Precondition: d.protocolMU must be locked.
diff --git a/pkg/tcpip/network/ipv6/icmp.go b/pkg/tcpip/network/ipv6/icmp.go
index 8059e0690..2afa856dc 100644
--- a/pkg/tcpip/network/ipv6/icmp.go
+++ b/pkg/tcpip/network/ipv6/icmp.go
@@ -369,6 +369,18 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
return
}
+ var it header.NDPOptionIterator
+ {
+ var err error
+ it, err = ns.Options().Iter(false /* check */)
+ if err != nil {
+ // Options are not valid as per the wire format, silently drop the
+ // packet.
+ received.invalid.Increment()
+ return
+ }
+ }
+
if e.hasTentativeAddr(targetAddr) {
// If the target address is tentative and the source of the packet is a
// unicast (specified) address, then the source of the packet is
@@ -382,6 +394,22 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
// stack know so it can handle such a scenario and do nothing further with
// the NS.
if srcAddr == header.IPv6Any {
+ var nonce []byte
+ for {
+ opt, done, err := it.Next()
+ if err != nil {
+ received.invalid.Increment()
+ return
+ }
+ if done {
+ break
+ }
+ if n, ok := opt.(header.NDPNonceOption); ok {
+ nonce = n.Nonce()
+ break
+ }
+ }
+
// Since this is a DAD message we know the sender does not actually hold
// the target address so there is no "holder".
var holderLinkAddress tcpip.LinkAddress
@@ -397,7 +425,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
//
// TODO(gvisor.dev/issue/4046): Handle the scenario when a duplicate
// address is detected for an assigned address.
- switch err := e.dupTentativeAddrDetected(targetAddr, holderLinkAddress); err.(type) {
+ switch err := e.dupTentativeAddrDetected(targetAddr, holderLinkAddress, nonce); err.(type) {
case nil, *tcpip.ErrBadAddress, *tcpip.ErrInvalidEndpointState:
default:
panic(fmt.Sprintf("unexpected error handling duplicate tentative address: %s", err))
@@ -418,21 +446,10 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
return
}
- var sourceLinkAddr tcpip.LinkAddress
- {
- it, err := ns.Options().Iter(false /* check */)
- if err != nil {
- // Options are not valid as per the wire format, silently drop the
- // packet.
- received.invalid.Increment()
- return
- }
-
- sourceLinkAddr, ok = getSourceLinkAddr(it)
- if !ok {
- received.invalid.Increment()
- return
- }
+ sourceLinkAddr, ok := getSourceLinkAddr(it)
+ if !ok {
+ received.invalid.Increment()
+ return
}
// As per RFC 4861 section 4.3, the Source Link-Layer Address Option MUST
@@ -586,6 +603,10 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
e.dad.mu.Unlock()
if e.hasTentativeAddr(targetAddr) {
+ // We only send a nonce value in DAD messages to check for loopedback
+ // messages so we use the empty nonce value here.
+ var nonce []byte
+
// 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
// stack know so it can handle such a scenario and do nothing furthur with
@@ -602,7 +623,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
//
// TODO(gvisor.dev/issue/4046): Handle the scenario when a duplicate
// address is detected for an assigned address.
- switch err := e.dupTentativeAddrDetected(targetAddr, targetLinkAddr); err.(type) {
+ switch err := e.dupTentativeAddrDetected(targetAddr, targetLinkAddr, nonce); err.(type) {
case nil, *tcpip.ErrBadAddress, *tcpip.ErrInvalidEndpointState:
return
default:
diff --git a/pkg/tcpip/network/ipv6/ipv6.go b/pkg/tcpip/network/ipv6/ipv6.go
index 46b6cc41a..350493958 100644
--- a/pkg/tcpip/network/ipv6/ipv6.go
+++ b/pkg/tcpip/network/ipv6/ipv6.go
@@ -348,7 +348,7 @@ func (e *endpoint) hasTentativeAddr(addr tcpip.Address) bool {
// dupTentativeAddrDetected removes the tentative address if it exists. If the
// address was generated via SLAAC, an attempt is made to generate a new
// address.
-func (e *endpoint) dupTentativeAddrDetected(addr tcpip.Address, holderLinkAddr tcpip.LinkAddress) tcpip.Error {
+func (e *endpoint) dupTentativeAddrDetected(addr tcpip.Address, holderLinkAddr tcpip.LinkAddress, nonce []byte) tcpip.Error {
e.mu.Lock()
defer e.mu.Unlock()
@@ -361,27 +361,48 @@ func (e *endpoint) dupTentativeAddrDetected(addr tcpip.Address, holderLinkAddr t
return &tcpip.ErrInvalidEndpointState{}
}
- // 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 */, &stack.DADDupAddrDetected{HolderLinkAddress: holderLinkAddr}); err != nil {
- return err
- }
+ switch result := e.mu.ndp.dad.ExtendIfNonceEqualLocked(addr, nonce); result {
+ case ip.Extended:
+ // The nonce we got back was the same we sent so we know the message
+ // indicating a duplicate address was likely ours so do not consider
+ // the address duplicate here.
+ return nil
+ case ip.AlreadyExtended:
+ // See Extended.
+ //
+ // Our DAD message was looped back already.
+ return nil
+ case ip.NoDADStateFound:
+ panic(fmt.Sprintf("expected DAD state for tentative address %s", addr))
+ case ip.NonceDisabled:
+ // If nonce is disabled then we have no way to know if the packet was
+ // looped-back so we have to assume it indicates a duplicate address.
+ fallthrough
+ case ip.NonceNotEqual:
+ // 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 */, &stack.DADDupAddrDetected{HolderLinkAddress: holderLinkAddr}); err != nil {
+ return err
+ }
- prefix := addressEndpoint.Subnet()
+ prefix := addressEndpoint.Subnet()
- switch t := addressEndpoint.ConfigType(); t {
- case stack.AddressConfigStatic:
- case stack.AddressConfigSlaac:
- e.mu.ndp.regenerateSLAACAddr(prefix)
- case stack.AddressConfigSlaacTemp:
- // Do not reset the generation attempts counter for the prefix as the
- // temporary address is being regenerated in response to a DAD conflict.
- e.mu.ndp.regenerateTempSLAACAddr(prefix, false /* resetGenAttempts */)
+ switch t := addressEndpoint.ConfigType(); t {
+ case stack.AddressConfigStatic:
+ case stack.AddressConfigSlaac:
+ e.mu.ndp.regenerateSLAACAddr(prefix)
+ case stack.AddressConfigSlaacTemp:
+ // Do not reset the generation attempts counter for the prefix as the
+ // temporary address is being regenerated in response to a DAD conflict.
+ e.mu.ndp.regenerateTempSLAACAddr(prefix, false /* resetGenAttempts */)
+ default:
+ panic(fmt.Sprintf("unrecognized address config type = %d", t))
+ }
+
+ return nil
default:
- panic(fmt.Sprintf("unrecognized address config type = %d", t))
+ panic(fmt.Sprintf("unhandled result = %d", result))
}
-
- return nil
}
// transitionForwarding transitions the endpoint's forwarding status to
@@ -1797,16 +1818,36 @@ func (p *protocol) NewEndpoint(nic stack.NetworkInterface, dispatcher stack.Tran
dispatcher: dispatcher,
protocol: p,
}
+
+ // NDP options must be 8 octet aligned and the first 2 bytes are used for
+ // the type and length fields leaving 6 octets as the minimum size for a
+ // nonce option without padding.
+ const nonceSize = 6
+
+ // As per RFC 7527 section 4.1,
+ //
+ // If any probe is looped back within RetransTimer milliseconds after
+ // having sent DupAddrDetectTransmits NS(DAD) messages, the interface
+ // continues with another MAX_MULTICAST_SOLICIT number of NS(DAD)
+ // messages transmitted RetransTimer milliseconds apart.
+ //
+ // Value taken from RFC 4861 section 10.
+ const maxMulticastSolicit = 3
+ dadOptions := ip.DADOptions{
+ Clock: p.stack.Clock(),
+ SecureRNG: p.stack.SecureRNG(),
+ NonceSize: nonceSize,
+ ExtendDADTransmits: maxMulticastSolicit,
+ Protocol: &e.mu.ndp,
+ NICID: nic.ID(),
+ }
+
e.mu.Lock()
e.mu.addressableEndpointState.Init(e)
- e.mu.ndp.init(e)
+ e.mu.ndp.init(e, dadOptions)
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.dad.Init(&e.dad.mu, p.options.DADConfigs, dadOptions)
e.dad.mu.Unlock()
e.mu.Unlock()
diff --git a/pkg/tcpip/network/ipv6/ndp.go b/pkg/tcpip/network/ipv6/ndp.go
index d9b728878..536493f87 100644
--- a/pkg/tcpip/network/ipv6/ndp.go
+++ b/pkg/tcpip/network/ipv6/ndp.go
@@ -1789,18 +1789,14 @@ func (ndp *ndpState) stopSolicitingRouters() {
ndp.rtrSolicitTimer = timer{}
}
-func (ndp *ndpState) init(ep *endpoint) {
+func (ndp *ndpState) init(ep *endpoint, dadOptions ip.DADOptions) {
if ndp.defaultRouters != nil {
panic("attempted to initialize NDP state twice")
}
ndp.ep = ep
ndp.configs = ep.protocol.options.NDPConfigs
- ndp.dad.Init(&ndp.ep.mu, ep.protocol.options.DADConfigs, ip.DADOptions{
- Clock: ep.protocol.stack.Clock(),
- Protocol: ndp,
- NICID: ep.nic.ID(),
- })
+ ndp.dad.Init(&ndp.ep.mu, ep.protocol.options.DADConfigs, dadOptions)
ndp.defaultRouters = make(map[tcpip.Address]defaultRouterState)
ndp.onLinkPrefixes = make(map[tcpip.Subnet]onLinkPrefixState)
ndp.slaacPrefixes = make(map[tcpip.Subnet]slaacPrefixState)
@@ -1811,9 +1807,11 @@ func (ndp *ndpState) init(ep *endpoint) {
}
}
-func (ndp *ndpState) SendDADMessage(addr tcpip.Address) tcpip.Error {
+func (ndp *ndpState) SendDADMessage(addr tcpip.Address, nonce []byte) tcpip.Error {
snmc := header.SolicitedNodeAddr(addr)
- return ndp.ep.sendNDPNS(header.IPv6Any, snmc, addr, header.EthernetAddressFromMulticastIPv6Address(snmc), nil /* opts */)
+ return ndp.ep.sendNDPNS(header.IPv6Any, snmc, addr, header.EthernetAddressFromMulticastIPv6Address(snmc), header.NDPOptionsSerializer{
+ header.NDPNonceOption(nonce),
+ })
}
func (e *endpoint) sendNDPNS(srcAddr, dstAddr, targetAddr tcpip.Address, remoteLinkAddr tcpip.LinkAddress, opts header.NDPOptionsSerializer) tcpip.Error {