gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/network/internal/ip/duplicate_address_detection_test.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ip_test
    16  
    17  import (
    18  	"bytes"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"gvisor.dev/gvisor/pkg/sync"
    24  	"gvisor.dev/gvisor/pkg/tcpip"
    25  	"gvisor.dev/gvisor/pkg/tcpip/faketime"
    26  	"gvisor.dev/gvisor/pkg/tcpip/network/internal/ip"
    27  	"gvisor.dev/gvisor/pkg/tcpip/stack"
    28  )
    29  
    30  type mockDADProtocol struct {
    31  	t *testing.T
    32  
    33  	mu struct {
    34  		sync.Mutex
    35  
    36  		dad        ip.DAD
    37  		sentNonces map[tcpip.Address][][]byte
    38  	}
    39  }
    40  
    41  func (m *mockDADProtocol) init(t *testing.T, c stack.DADConfigurations, opts ip.DADOptions) {
    42  	m.mu.Lock()
    43  	defer m.mu.Unlock()
    44  
    45  	m.t = t
    46  	opts.Protocol = m
    47  	m.mu.dad.Init(&m.mu, c, opts)
    48  	m.initLocked()
    49  }
    50  
    51  func (m *mockDADProtocol) initLocked() {
    52  	m.mu.sentNonces = make(map[tcpip.Address][][]byte)
    53  }
    54  
    55  func (m *mockDADProtocol) SendDADMessage(addr tcpip.Address, nonce []byte) tcpip.Error {
    56  	m.mu.Lock()
    57  	defer m.mu.Unlock()
    58  	m.mu.sentNonces[addr] = append(m.mu.sentNonces[addr], nonce)
    59  	return nil
    60  }
    61  
    62  func (m *mockDADProtocol) check(addrs []tcpip.Address) string {
    63  	sentNonces := make(map[tcpip.Address][][]byte)
    64  	for _, a := range addrs {
    65  		sentNonces[a] = append(sentNonces[a], nil)
    66  	}
    67  
    68  	return m.checkWithNonce(sentNonces)
    69  }
    70  
    71  func (m *mockDADProtocol) checkWithNonce(expectedSentNonces map[tcpip.Address][][]byte) string {
    72  	m.mu.Lock()
    73  	defer m.mu.Unlock()
    74  
    75  	diff := cmp.Diff(expectedSentNonces, m.mu.sentNonces)
    76  	m.initLocked()
    77  	return diff
    78  }
    79  
    80  func (m *mockDADProtocol) checkDuplicateAddress(addr tcpip.Address, h stack.DADCompletionHandler) stack.DADCheckAddressDisposition {
    81  	m.mu.Lock()
    82  	defer m.mu.Unlock()
    83  	return m.mu.dad.CheckDuplicateAddressLocked(addr, h)
    84  }
    85  
    86  func (m *mockDADProtocol) stop(addr tcpip.Address, reason stack.DADResult) {
    87  	m.mu.Lock()
    88  	defer m.mu.Unlock()
    89  	m.mu.dad.StopLocked(addr, reason)
    90  }
    91  
    92  func (m *mockDADProtocol) extendIfNonceEqual(addr tcpip.Address, nonce []byte) ip.ExtendIfNonceEqualLockedDisposition {
    93  	m.mu.Lock()
    94  	defer m.mu.Unlock()
    95  	return m.mu.dad.ExtendIfNonceEqualLocked(addr, nonce)
    96  }
    97  
    98  func (m *mockDADProtocol) setConfigs(c stack.DADConfigurations) {
    99  	m.mu.Lock()
   100  	defer m.mu.Unlock()
   101  	m.mu.dad.SetConfigsLocked(c)
   102  }
   103  
   104  var (
   105  	addr1 = tcpip.AddrFromSlice([]byte("\x01\x00\x00\x00"))
   106  	addr2 = tcpip.AddrFromSlice([]byte("\x02\x00\x00\x00"))
   107  	addr3 = tcpip.AddrFromSlice([]byte("\x03\x00\x00\x00"))
   108  	addr4 = tcpip.AddrFromSlice([]byte("\x04\x00\x00\x00"))
   109  )
   110  
   111  type dadResult struct {
   112  	Addr tcpip.Address
   113  	R    stack.DADResult
   114  }
   115  
   116  func handler(ch chan<- dadResult, a tcpip.Address) func(stack.DADResult) {
   117  	return func(r stack.DADResult) {
   118  		ch <- dadResult{Addr: a, R: r}
   119  	}
   120  }
   121  
   122  func TestDADCheckDuplicateAddress(t *testing.T) {
   123  	var dad mockDADProtocol
   124  	clock := faketime.NewManualClock()
   125  	dad.init(t, stack.DADConfigurations{}, ip.DADOptions{
   126  		Clock: clock,
   127  	})
   128  
   129  	ch := make(chan dadResult, 2)
   130  
   131  	// DAD should initially be disabled.
   132  	if res := dad.checkDuplicateAddress(addr1, handler(nil, tcpip.Address{})); res != stack.DADDisabled {
   133  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADDisabled)
   134  	}
   135  	// Wait for any initially fired timers to complete.
   136  	clock.RunImmediatelyScheduledJobs()
   137  	if diff := dad.check(nil); diff != "" {
   138  		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
   139  	}
   140  
   141  	// Enable and request DAD.
   142  	dadConfigs1 := stack.DADConfigurations{
   143  		DupAddrDetectTransmits: 1,
   144  		RetransmitTimer:        time.Second,
   145  	}
   146  	dad.setConfigs(dadConfigs1)
   147  	if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
   148  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
   149  	}
   150  	clock.RunImmediatelyScheduledJobs()
   151  	if diff := dad.check([]tcpip.Address{addr1}); diff != "" {
   152  		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
   153  	}
   154  	// The second request for DAD on the same address should use the original
   155  	// request since it has not completed yet.
   156  	if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADAlreadyRunning {
   157  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADAlreadyRunning)
   158  	}
   159  	clock.RunImmediatelyScheduledJobs()
   160  	if diff := dad.check(nil); diff != "" {
   161  		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
   162  	}
   163  
   164  	dadConfigs2 := stack.DADConfigurations{
   165  		DupAddrDetectTransmits: 2,
   166  		RetransmitTimer:        time.Second,
   167  	}
   168  	dad.setConfigs(dadConfigs2)
   169  	// A new address should start a new DAD process.
   170  	if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
   171  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
   172  	}
   173  	clock.RunImmediatelyScheduledJobs()
   174  	if diff := dad.check([]tcpip.Address{addr2}); diff != "" {
   175  		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
   176  	}
   177  
   178  	// Make sure DAD for addr1 only resolves after the expected timeout.
   179  	const delta = time.Nanosecond
   180  	dadConfig1Duration := time.Duration(dadConfigs1.DupAddrDetectTransmits) * dadConfigs1.RetransmitTimer
   181  	clock.Advance(dadConfig1Duration - delta)
   182  	select {
   183  	case r := <-ch:
   184  		t.Fatalf("unexpectedly got a DAD result before the expected timeout of %s; r = %#v", dadConfig1Duration, r)
   185  	default:
   186  	}
   187  	clock.Advance(delta)
   188  	for i := 0; i < 2; i++ {
   189  		if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
   190  			t.Errorf("(i=%d) dad result mismatch (-want +got):\n%s", i, diff)
   191  		}
   192  	}
   193  
   194  	// Make sure DAD for addr2 only resolves after the expected timeout.
   195  	dadConfig2Duration := time.Duration(dadConfigs2.DupAddrDetectTransmits) * dadConfigs2.RetransmitTimer
   196  	clock.Advance(dadConfig2Duration - dadConfig1Duration - delta)
   197  	select {
   198  	case r := <-ch:
   199  		t.Fatalf("unexpectedly got a DAD result before the expected timeout of %s; r = %#v", dadConfig2Duration, r)
   200  	default:
   201  	}
   202  	clock.Advance(delta)
   203  	if diff := cmp.Diff(dadResult{Addr: addr2, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
   204  		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
   205  	}
   206  
   207  	// Should be able to restart DAD for addr2 after it resolved.
   208  	if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
   209  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
   210  	}
   211  	clock.RunImmediatelyScheduledJobs()
   212  	if diff := dad.check([]tcpip.Address{addr2, addr2}); diff != "" {
   213  		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
   214  	}
   215  	clock.Advance(dadConfig2Duration)
   216  	if diff := cmp.Diff(dadResult{Addr: addr2, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
   217  		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
   218  	}
   219  
   220  	// Should not have anymore results.
   221  	select {
   222  	case r := <-ch:
   223  		t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r)
   224  	default:
   225  	}
   226  }
   227  
   228  func TestDADStop(t *testing.T) {
   229  	var dad mockDADProtocol
   230  	clock := faketime.NewManualClock()
   231  	dadConfigs := stack.DADConfigurations{
   232  		DupAddrDetectTransmits: 1,
   233  		RetransmitTimer:        time.Second,
   234  	}
   235  	dad.init(t, dadConfigs, ip.DADOptions{
   236  		Clock: clock,
   237  	})
   238  
   239  	ch := make(chan dadResult, 1)
   240  
   241  	if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
   242  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
   243  	}
   244  	if res := dad.checkDuplicateAddress(addr2, handler(ch, addr2)); res != stack.DADStarting {
   245  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
   246  	}
   247  	if res := dad.checkDuplicateAddress(addr3, handler(ch, addr3)); res != stack.DADStarting {
   248  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr2, res, stack.DADStarting)
   249  	}
   250  	clock.RunImmediatelyScheduledJobs()
   251  	if diff := dad.check([]tcpip.Address{addr1, addr2, addr3}); diff != "" {
   252  		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
   253  	}
   254  
   255  	dad.stop(addr1, &stack.DADAborted{})
   256  	if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADAborted{}}, <-ch); diff != "" {
   257  		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
   258  	}
   259  
   260  	dad.stop(addr2, &stack.DADDupAddrDetected{})
   261  	if diff := cmp.Diff(dadResult{Addr: addr2, R: &stack.DADDupAddrDetected{}}, <-ch); diff != "" {
   262  		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
   263  	}
   264  
   265  	dadResolutionDuration := time.Duration(dadConfigs.DupAddrDetectTransmits) * dadConfigs.RetransmitTimer
   266  	clock.Advance(dadResolutionDuration)
   267  	if diff := cmp.Diff(dadResult{Addr: addr3, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
   268  		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
   269  	}
   270  
   271  	// Should be able to restart DAD for an address we stopped DAD on.
   272  	if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
   273  		t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
   274  	}
   275  	clock.RunImmediatelyScheduledJobs()
   276  	if diff := dad.check([]tcpip.Address{addr1}); diff != "" {
   277  		t.Errorf("dad check mismatch (-want +got):\n%s", diff)
   278  	}
   279  	clock.Advance(dadResolutionDuration)
   280  	if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
   281  		t.Errorf("dad result mismatch (-want +got):\n%s", diff)
   282  	}
   283  
   284  	// Should not have anymore updates.
   285  	select {
   286  	case r := <-ch:
   287  		t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r)
   288  	default:
   289  	}
   290  }
   291  
   292  func TestNonce(t *testing.T) {
   293  	const (
   294  		nonceSize = 2
   295  
   296  		extendRequestAttempts = 2
   297  
   298  		dupAddrDetectTransmits = 2
   299  		extendTransmits        = 5
   300  	)
   301  
   302  	var secureRNGBytes [nonceSize * (dupAddrDetectTransmits + extendTransmits)]byte
   303  	for i := range secureRNGBytes {
   304  		secureRNGBytes[i] = byte(i)
   305  	}
   306  
   307  	tests := []struct {
   308  		name                string
   309  		mockedReceivedNonce []byte
   310  		expectedResults     [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition
   311  		expectedTransmits   int
   312  	}{
   313  		{
   314  			name:                "not matching",
   315  			mockedReceivedNonce: []byte{0, 0},
   316  			expectedResults:     [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition{ip.NonceNotEqual, ip.NonceNotEqual},
   317  			expectedTransmits:   dupAddrDetectTransmits,
   318  		},
   319  		{
   320  			name:                "matching nonce",
   321  			mockedReceivedNonce: secureRNGBytes[:nonceSize],
   322  			expectedResults:     [extendRequestAttempts]ip.ExtendIfNonceEqualLockedDisposition{ip.Extended, ip.AlreadyExtended},
   323  			expectedTransmits:   dupAddrDetectTransmits + extendTransmits,
   324  		},
   325  	}
   326  
   327  	for _, test := range tests {
   328  		t.Run(test.name, func(t *testing.T) {
   329  			var dad mockDADProtocol
   330  			clock := faketime.NewManualClock()
   331  			dadConfigs := stack.DADConfigurations{
   332  				DupAddrDetectTransmits: dupAddrDetectTransmits,
   333  				RetransmitTimer:        time.Second,
   334  			}
   335  
   336  			var secureRNG bytes.Reader
   337  			secureRNG.Reset(secureRNGBytes[:])
   338  			dad.init(t, dadConfigs, ip.DADOptions{
   339  				Clock:              clock,
   340  				SecureRNG:          &secureRNG,
   341  				NonceSize:          nonceSize,
   342  				ExtendDADTransmits: extendTransmits,
   343  			})
   344  
   345  			ch := make(chan dadResult, 1)
   346  			if res := dad.checkDuplicateAddress(addr1, handler(ch, addr1)); res != stack.DADStarting {
   347  				t.Errorf("got dad.checkDuplicateAddress(%s, _) = %d, want = %d", addr1, res, stack.DADStarting)
   348  			}
   349  
   350  			clock.RunImmediatelyScheduledJobs()
   351  			for i, want := range test.expectedResults {
   352  				if got := dad.extendIfNonceEqual(addr1, test.mockedReceivedNonce); got != want {
   353  					t.Errorf("(i=%d) got dad.extendIfNonceEqual(%s, _) = %d, want = %d", i, addr1, got, want)
   354  				}
   355  			}
   356  
   357  			for i := 0; i < test.expectedTransmits; i++ {
   358  				if diff := dad.checkWithNonce(map[tcpip.Address][][]byte{
   359  					addr1: {
   360  						secureRNGBytes[nonceSize*i:][:nonceSize],
   361  					},
   362  				}); diff != "" {
   363  					t.Errorf("(i=%d) dad check mismatch (-want +got):\n%s", i, diff)
   364  				}
   365  
   366  				clock.Advance(dadConfigs.RetransmitTimer)
   367  			}
   368  
   369  			if diff := cmp.Diff(dadResult{Addr: addr1, R: &stack.DADSucceeded{}}, <-ch); diff != "" {
   370  				t.Errorf("dad result mismatch (-want +got):\n%s", diff)
   371  			}
   372  
   373  			// Should not have anymore updates.
   374  			select {
   375  			case r := <-ch:
   376  				t.Fatalf("unexpectedly got an extra DAD result; r = %#v", r)
   377  			default:
   378  			}
   379  		})
   380  	}
   381  }