| // 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 ( |
| "bytes" |
| "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 |
| sentNonces map[tcpip.Address][][]byte |
| } |
| } |
| |
| 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.sentNonces = make(map[tcpip.Address][][]byte) |
| } |
| |
| func (m *mockDADProtocol) SendDADMessage(addr tcpip.Address, nonce []byte) tcpip.Error { |
| m.mu.Lock() |
| defer m.mu.Unlock() |
| 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() |
| |
| diff := cmp.Diff(expectedSentNonces, m.mu.sentNonces) |
| 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, reason stack.DADResult) { |
| m.mu.Lock() |
| defer m.mu.Unlock() |
| 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() |
| 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.DADSucceeded{}}, <-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.DADSucceeded{}}, <-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.DADSucceeded{}}, <-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, &stack.DADAborted{}) |
| if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADAborted{}}, <-ch); diff != "" { |
| t.Errorf("dad result mismatch (-want +got):\n%s", diff) |
| } |
| |
| dad.stop(addr2, &stack.DADDupAddrDetected{}) |
| if diff := cmp.Diff(dadResult{Addr: addr2, R: &stack.DADDupAddrDetected{}}, <-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.DADSucceeded{}}, <-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.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: |
| } |
| } |
| |
| 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: |
| } |
| }) |
| } |
| } |