Detect looped-back NDP DAD messages

...as per RFC 7527.

If a looped-back DAD message is received, do not fail DAD since our own
DAD message does not indicate that a neighbor has the address assigned.

Test: ndp_test.TestDADResolveLoopback
PiperOrigin-RevId: 363224288
This commit is contained in:
Ghanan Gowripalan
2021-03-16 11:09:26 -07:00
committed by gVisor bot
parent ebd7c1b889
commit 68065d1ceb
14 changed files with 882 additions and 479 deletions
+7
View File
@@ -1287,6 +1287,13 @@ func ndpOptions(t *testing.T, optsBuf header.NDPOptions, opts []header.NDPOption
} else if got, want := gotOpt.EthernetAddress(), wantOpt.EthernetAddress(); got != want {
t.Errorf("got EthernetAddress() = %s at index %d, want = %s", got, i, want)
}
case header.NDPNonceOption:
gotOpt, ok := opt.(header.NDPNonceOption)
if !ok {
t.Errorf("got type = %T at index = %d; want = %T", opt, i, wantOpt)
} else if diff := cmp.Diff(wantOpt.Nonce(), gotOpt.Nonce()); diff != "" {
t.Errorf("nonce mismatch (-want +got):\n%s", diff)
}
default:
t.Fatalf("checker not implemented for expected NDP option: %T", wantOpt)
}
+39 -1
View File
@@ -42,13 +42,17 @@ const (
// option, as per RFC 4861 section 4.6.2.
NDPPrefixInformationType NDPOptionIdentifier = 3
// NDPNonceOptionType is the type of the Nonce option, as per
// RFC 3971 section 5.3.2.
NDPNonceOptionType NDPOptionIdentifier = 14
// NDPRecursiveDNSServerOptionType is the type of the Recursive DNS
// Server option, as per RFC 8106 section 5.1.
NDPRecursiveDNSServerOptionType NDPOptionIdentifier = 25
// NDPDNSSearchListOptionType is the type of the DNS Search List option,
// as per RFC 8106 section 5.2.
NDPDNSSearchListOptionType = 31
NDPDNSSearchListOptionType NDPOptionIdentifier = 31
)
const (
@@ -231,6 +235,9 @@ func (i *NDPOptionIterator) Next() (NDPOption, bool, error) {
case NDPTargetLinkLayerAddressOptionType:
return NDPTargetLinkLayerAddressOption(body), false, nil
case NDPNonceOptionType:
return NDPNonceOption(body), false, nil
case NDPPrefixInformationType:
// Make sure the length of a Prefix Information option
// body is ndpPrefixInformationLength, as per RFC 4861
@@ -416,6 +423,37 @@ func (b NDPOptionsSerializer) Length() int {
return l
}
// NDPNonceOption is the NDP Nonce Option as defined by RFC 3971 section 5.3.2.
//
// It is the first X bytes following the NDP option's Type and Length field
// where X is the value in Length multiplied by lengthByteUnits - 2 bytes.
type NDPNonceOption []byte
// Type implements NDPOption.
func (o NDPNonceOption) Type() NDPOptionIdentifier {
return NDPNonceOptionType
}
// Length implements NDPOption.
func (o NDPNonceOption) Length() int {
return len(o)
}
// serializeInto implements NDPOption.
func (o NDPNonceOption) serializeInto(b []byte) int {
return copy(b, o)
}
// String implements fmt.Stringer.
func (o NDPNonceOption) String() string {
return fmt.Sprintf("%T(%x)", o, []byte(o))
}
// Nonce returns the nonce value this option holds.
func (o NDPNonceOption) Nonce() []byte {
return []byte(o)
}
// NDPSourceLinkLayerAddressOption is the NDP Source Link Layer Option
// as defined by RFC 4861 section 4.6.1.
//
File diff suppressed because it is too large Load Diff
+11 -3
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by "stringer -type NDPOptionIdentifier ."; DO NOT EDIT.
// Code generated by "stringer -type NDPOptionIdentifier"; DO NOT EDIT.
package header
@@ -25,12 +25,16 @@ func _() {
_ = x[NDPSourceLinkLayerAddressOptionType-1]
_ = x[NDPTargetLinkLayerAddressOptionType-2]
_ = x[NDPPrefixInformationType-3]
_ = x[NDPNonceOptionType-14]
_ = x[NDPRecursiveDNSServerOptionType-25]
_ = x[NDPDNSSearchListOptionType-31]
}
const (
_NDPOptionIdentifier_name_0 = "NDPSourceLinkLayerAddressOptionTypeNDPTargetLinkLayerAddressOptionTypeNDPPrefixInformationType"
_NDPOptionIdentifier_name_1 = "NDPRecursiveDNSServerOptionType"
_NDPOptionIdentifier_name_1 = "NDPNonceOptionType"
_NDPOptionIdentifier_name_2 = "NDPRecursiveDNSServerOptionType"
_NDPOptionIdentifier_name_3 = "NDPDNSSearchListOptionType"
)
var (
@@ -42,8 +46,12 @@ func (i NDPOptionIdentifier) String() string {
case 1 <= i && i <= 3:
i -= 1
return _NDPOptionIdentifier_name_0[_NDPOptionIdentifier_index_0[i]:_NDPOptionIdentifier_index_0[i+1]]
case i == 25:
case i == 14:
return _NDPOptionIdentifier_name_1
case i == 25:
return _NDPOptionIdentifier_name_2
case i == 31:
return _NDPOptionIdentifier_name_3
default:
return "NDPOptionIdentifier(" + strconv.FormatInt(int64(i), 10) + ")"
}
+9 -4
View File
@@ -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()
@@ -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.
@@ -15,6 +15,7 @@
package ip_test
import (
"bytes"
"testing"
"time"
@@ -32,8 +33,8 @@ type mockDADProtocol struct {
mu struct {
sync.Mutex
dad ip.DAD
sendCount map[tcpip.Address]int
dad ip.DAD
sentNonces map[tcpip.Address][][]byte
}
}
@@ -48,26 +49,30 @@ func (m *mockDADProtocol) init(t *testing.T, c stack.DADConfigurations, opts ip.
}
func (m *mockDADProtocol) initLocked() {
m.mu.sendCount = make(map[tcpip.Address]int)
m.mu.sentNonces = make(map[tcpip.Address][][]byte)
}
func (m *mockDADProtocol) SendDADMessage(addr tcpip.Address) tcpip.Error {
func (m *mockDADProtocol) SendDADMessage(addr tcpip.Address, nonce []byte) tcpip.Error {
m.mu.Lock()
defer m.mu.Unlock()
m.mu.sendCount[addr]++
m.mu.sentNonces[addr] = append(m.mu.sentNonces[addr], nonce)
return nil
}
func (m *mockDADProtocol) check(addrs []tcpip.Address) string {
sentNonces := make(map[tcpip.Address][][]byte)
for _, a := range addrs {
sentNonces[a] = append(sentNonces[a], nil)
}
return m.checkWithNonce(sentNonces)
}
func (m *mockDADProtocol) checkWithNonce(expectedSentNonces map[tcpip.Address][][]byte) 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)
diff := cmp.Diff(expectedSentNonces, m.mu.sentNonces)
m.initLocked()
return diff
}
@@ -84,6 +89,12 @@ func (m *mockDADProtocol) stop(addr tcpip.Address, reason stack.DADResult) {
m.mu.dad.StopLocked(addr, reason)
}
func (m *mockDADProtocol) extendIfNonceEqual(addr tcpip.Address, nonce []byte) ip.ExtendIfNonceEqualLockedDisposition {
m.mu.Lock()
defer m.mu.Unlock()
return m.mu.dad.ExtendIfNonceEqualLocked(addr, nonce)
}
func (m *mockDADProtocol) setConfigs(c stack.DADConfigurations) {
m.mu.Lock()
defer m.mu.Unlock()
@@ -277,3 +288,94 @@ func TestDADStop(t *testing.T) {
default:
}
}
func TestNonce(t *testing.T) {
const (
nonceSize = 2
extendRequestAttempts = 2
dupAddrDetectTransmits = 2
extendTransmits = 5
)
var secureRNGBytes [nonceSize * (dupAddrDetectTransmits + extendTransmits)]byte
for i := range secureRNGBytes {
secureRNGBytes[i] = byte(i)
}
tests := []struct {
name string
mockedReceivedNonce []byte
expectedResults [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition
expectedTransmits int
}{
{
name: "not matching",
mockedReceivedNonce: []byte{0, 0},
expectedResults: [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition{ip.NonceNotEqual, ip.NonceNotEqual},
expectedTransmits: dupAddrDetectTransmits,
},
{
name: "matching nonce",
mockedReceivedNonce: secureRNGBytes[:nonceSize],
expectedResults: [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition{ip.Extended, ip.AlreadyExtended},
expectedTransmits: dupAddrDetectTransmits + extendTransmits,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var dad mockDADProtocol
clock := faketime.NewManualClock()
dadConfigs := stack.DADConfigurations{
DupAddrDetectTransmits: dupAddrDetectTransmits,
RetransmitTimer: time.Second,
}
var secureRNG bytes.Reader
secureRNG.Reset(secureRNGBytes[:])
dad.init(t, dadConfigs, ip.DADOptions{
Clock: clock,
SecureRNG: &secureRNG,
NonceSize: nonceSize,
ExtendDADTransmits: extendTransmits,
})
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)
}
clock.Advance(0)
for i, want := range test.expectedResults {
if got := dad.extendIfNonceEqual(addr1, test.mockedReceivedNonce); got != want {
t.Errorf("(i=%d) got dad.extendIfNonceEqual(%s, _) = %d, want = %d", i, addr1, got, want)
}
}
for i := 0; i < test.expectedTransmits; i++ {
if diff := dad.checkWithNonce(map[tcpip.Address][][]byte{
addr1: {
secureRNGBytes[nonceSize*i:][:nonceSize],
},
}); diff != "" {
t.Errorf("(i=%d) dad check mismatch (-want +got):\n%s", i, diff)
}
clock.Advance(dadConfigs.RetransmitTimer)
}
if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADSucceeded{}}, <-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:
}
})
}
}
+38 -17
View File
@@ -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:
+65 -24
View File
@@ -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()
+17 -1
View File
@@ -15,6 +15,7 @@
package ipv6_test
import (
"bytes"
"testing"
"time"
@@ -119,11 +120,26 @@ func TestSendQueuedMLDReports(t *testing.T) {
},
}
nonce := [...]byte{
1, 2, 3, 4, 5, 6,
}
const maxNSMessages = 2
secureRNGBytes := make([]byte, len(nonce)*maxNSMessages)
for b := secureRNGBytes[:]; len(b) > 0; b = b[len(nonce):] {
if n := copy(b, nonce[:]); n != len(nonce) {
t.Fatalf("got copy(...) = %d, want = %d", n, len(nonce))
}
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
dadResolutionTime := test.retransmitTimer * time.Duration(test.dadTransmits)
clock := faketime.NewManualClock()
var secureRNG bytes.Reader
secureRNG.Reset(secureRNGBytes[:])
s := stack.New(stack.Options{
SecureRNG: &secureRNG,
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: test.dadTransmits,
@@ -154,7 +170,7 @@ func TestSendQueuedMLDReports(t *testing.T) {
checker.TTL(header.NDPHopLimit),
checker.NDPNS(
checker.NDPNSTargetAddress(addr),
checker.NDPNSOptions(nil),
checker.NDPNSOptions([]header.NDPOption{header.NDPNonceOption(nonce[:])}),
))
}
}
+6 -8
View File
@@ -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 {
+43 -29
View File
@@ -15,6 +15,7 @@
package ipv6
import (
"bytes"
"context"
"strings"
"testing"
@@ -1264,8 +1265,21 @@ func TestCheckDuplicateAddress(t *testing.T) {
DupAddrDetectTransmits: 1,
RetransmitTimer: time.Second,
}
nonces := [...][]byte{
{1, 2, 3, 4, 5, 6},
{7, 8, 9, 10, 11, 12},
}
var secureRNGBytes []byte
for _, n := range nonces {
secureRNGBytes = append(secureRNGBytes, n...)
}
var secureRNG bytes.Reader
secureRNG.Reset(secureRNGBytes[:])
s := stack.New(stack.Options{
Clock: clock,
SecureRNG: &secureRNG,
Clock: clock,
NetworkProtocols: []stack.NetworkProtocolFactory{NewProtocolWithOptions(Options{
DADConfigs: dadConfigs,
})},
@@ -1278,10 +1292,36 @@ func TestCheckDuplicateAddress(t *testing.T) {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
dadPacketsSent := 1
dadPacketsSent := 0
snmc := header.SolicitedNodeAddr(lladdr0)
remoteLinkAddr := header.EthernetAddressFromMulticastIPv6Address(snmc)
checkDADMsg := func() {
p, ok := e.ReadContext(context.Background())
if !ok {
t.Fatalf("expected %d-th DAD message", dadPacketsSent)
}
if p.Proto != header.IPv6ProtocolNumber {
t.Errorf("(i=%d) got p.Proto = %d, want = %d", dadPacketsSent, p.Proto, header.IPv6ProtocolNumber)
}
if p.Route.RemoteLinkAddress != remoteLinkAddr {
t.Errorf("(i=%d) got p.Route.RemoteLinkAddress = %s, want = %s", dadPacketsSent, 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([]header.NDPOption{header.NDPNonceOption(nonces[dadPacketsSent])}),
))
}
if err := s.AddAddress(nicID, ProtocolNumber, lladdr0); err != nil {
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, lladdr0, err)
}
checkDADMsg()
// Start DAD for the address we just added.
//
@@ -1297,6 +1337,7 @@ func TestCheckDuplicateAddress(t *testing.T) {
} else if res != stack.DADStarting {
t.Fatalf("got s.CheckDuplicateAddress(%d, %d, %s, _) = %d, want = %d", nicID, ProtocolNumber, lladdr0, res, stack.DADStarting)
}
checkDADMsg()
// Remove the address and make sure our DAD request was not stopped.
if err := s.RemoveAddress(nicID, lladdr0); err != nil {
@@ -1328,33 +1369,6 @@ func TestCheckDuplicateAddress(t *testing.T) {
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)
+78 -1
View File
@@ -15,6 +15,7 @@
package stack_test
import (
"bytes"
"context"
"encoding/binary"
"fmt"
@@ -29,6 +30,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/faketime"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
@@ -358,6 +360,66 @@ func TestDADDisabled(t *testing.T) {
}
}
func TestDADResolveLoopback(t *testing.T) {
const nicID = 1
ndpDisp := ndpDispatcher{
dadC: make(chan ndpDADEvent, 1),
}
dadConfigs := stack.DADConfigurations{
RetransmitTimer: time.Second,
DupAddrDetectTransmits: 1,
}
clock := faketime.NewManualClock()
s := stack.New(stack.Options{
Clock: clock,
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
NDPDisp: &ndpDisp,
DADConfigs: dadConfigs,
})},
})
if err := s.CreateNIC(nicID, loopback.New()); err != nil {
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
}
addrWithPrefix := tcpip.AddressWithPrefix{
Address: addr1,
PrefixLen: defaultPrefixLen,
}
if err := s.AddAddressWithPrefix(nicID, header.IPv6ProtocolNumber, addrWithPrefix); err != nil {
t.Fatalf("AddAddressWithPrefix(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addrWithPrefix, err)
}
// Address should not be considered bound to the NIC yet (DAD ongoing).
if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
t.Fatal(err)
}
// DAD should not resolve after the normal resolution time since our DAD
// message was looped back - we should extend our DAD process.
dadResolutionTime := time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer
clock.Advance(dadResolutionTime)
if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
t.Error(err)
}
// Make sure the address does not resolve before the extended resolution time
// has passed.
const delta = time.Nanosecond
// DAD will send extra NS probes if an NS message is looped back.
const extraTransmits = 3
clock.Advance(dadResolutionTime*extraTransmits - delta)
if err := checkGetMainNICAddress(s, nicID, header.IPv6ProtocolNumber, tcpip.AddressWithPrefix{}); err != nil {
t.Error(err)
}
// DAD should now resolve.
clock.Advance(delta)
if diff := checkDADEvent(<-ndpDisp.dadC, nicID, addr1, &stack.DADSucceeded{}); diff != "" {
t.Errorf("DAD event mismatch (-want +got):\n%s", diff)
}
}
// TestDADResolve tests that an address successfully resolves after performing
// DAD for various values of DupAddrDetectTransmits and RetransmitTimer.
// Included in the subtests is a test to make sure that an invalid
@@ -404,6 +466,16 @@ func TestDADResolve(t *testing.T) {
},
}
nonces := [][]byte{
{1, 2, 3, 4, 5, 6},
{7, 8, 9, 10, 11, 12},
}
var secureRNGBytes []byte
for _, n := range nonces {
secureRNGBytes = append(secureRNGBytes, n...)
}
for _, test := range tests {
test := test
@@ -419,7 +491,12 @@ func TestDADResolve(t *testing.T) {
headerLength: test.linkHeaderLen,
}
e.Endpoint.LinkEPCapabilities |= stack.CapabilityResolutionRequired
var secureRNG bytes.Reader
secureRNG.Reset(secureRNGBytes)
s := stack.New(stack.Options{
SecureRNG: &secureRNG,
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
NDPDisp: &ndpDisp,
DADConfigs: stack.DADConfigurations{
@@ -553,7 +630,7 @@ func TestDADResolve(t *testing.T) {
checker.TTL(header.NDPHopLimit),
checker.NDPNS(
checker.NDPNSTargetAddress(addr1),
checker.NDPNSOptions(nil),
checker.NDPNSOptions([]header.NDPOption{header.NDPNonceOption(nonces[i])}),
))
if l, want := p.Pkt.AvailableHeaderBytes(), int(test.linkHeaderLen); l != want {
+18
View File
@@ -23,6 +23,7 @@ import (
"bytes"
"encoding/binary"
"fmt"
"io"
mathrand "math/rand"
"sync/atomic"
"time"
@@ -445,6 +446,9 @@ type Stack struct {
// used when a random number is required.
randomGenerator *mathrand.Rand
// secureRNG is a cryptographically secure random number generator.
secureRNG io.Reader
// sendBufferSize holds the min/default/max send buffer sizes for
// endpoints other than TCP.
sendBufferSize tcpip.SendBufferSizeOption
@@ -528,6 +532,9 @@ type Options struct {
// IPTables are the initial iptables rules. If nil, iptables will allow
// all traffic.
IPTables *IPTables
// SecureRNG is a cryptographically secure random number generator.
SecureRNG io.Reader
}
// TransportEndpointInfo holds useful information about a transport endpoint
@@ -636,6 +643,10 @@ func New(opts Options) *Stack {
opts.NUDConfigs.resetInvalidFields()
if opts.SecureRNG == nil {
opts.SecureRNG = rand.Reader
}
s := &Stack{
transportProtocols: make(map[tcpip.TransportProtocolNumber]*transportProtocolState),
networkProtocols: make(map[tcpip.NetworkProtocolNumber]NetworkProtocol),
@@ -652,6 +663,7 @@ func New(opts Options) *Stack {
uniqueIDGenerator: opts.UniqueID,
nudDisp: opts.NUDDisp,
randomGenerator: mathrand.New(randSrc),
secureRNG: opts.SecureRNG,
sendBufferSize: tcpip.SendBufferSizeOption{
Min: MinBufferSize,
Default: DefaultBufferSize,
@@ -2048,6 +2060,12 @@ func (s *Stack) Rand() *mathrand.Rand {
return s.randomGenerator
}
// SecureRNG returns the stack's cryptographically secure random number
// generator.
func (s *Stack) SecureRNG() io.Reader {
return s.secureRNG
}
func generateRandUint32() uint32 {
b := make([]byte, 4)
if _, err := rand.Read(b); err != nil {