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

     1  // Copyright 2022 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 multicast
    16  
    17  import (
    18  	"os"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/google/go-cmp/cmp/cmpopts"
    24  	"gvisor.dev/gvisor/pkg/buffer"
    25  	"gvisor.dev/gvisor/pkg/refs"
    26  	"gvisor.dev/gvisor/pkg/tcpip"
    27  	"gvisor.dev/gvisor/pkg/tcpip/faketime"
    28  	"gvisor.dev/gvisor/pkg/tcpip/stack"
    29  	"gvisor.dev/gvisor/pkg/tcpip/testutil"
    30  )
    31  
    32  const (
    33  	defaultMinTTL             = 10
    34  	defaultMTU                = 1500
    35  	inputNICID    tcpip.NICID = 1
    36  	outgoingNICID tcpip.NICID = 2
    37  	defaultNICID  tcpip.NICID = 3
    38  )
    39  
    40  var (
    41  	defaultAddress            = testutil.MustParse4("192.168.1.1")
    42  	defaultRouteKey           = stack.UnicastSourceAndMulticastDestination{Source: defaultAddress, Destination: defaultAddress}
    43  	defaultOutgoingInterfaces = []stack.MulticastRouteOutgoingInterface{{ID: outgoingNICID, MinTTL: defaultMinTTL}}
    44  	defaultRoute              = stack.MulticastRoute{inputNICID, defaultOutgoingInterfaces}
    45  )
    46  
    47  func newPacketBuffer(body string) *stack.PacketBuffer {
    48  	return stack.NewPacketBuffer(stack.PacketBufferOptions{
    49  		Payload: buffer.MakeWithData([]byte(body)),
    50  	})
    51  }
    52  
    53  type configOption func(*Config)
    54  
    55  func withMaxPendingQueueSize(size uint8) configOption {
    56  	return func(c *Config) {
    57  		c.MaxPendingQueueSize = size
    58  	}
    59  }
    60  
    61  func withClock(clock tcpip.Clock) configOption {
    62  	return func(c *Config) {
    63  		c.Clock = clock
    64  	}
    65  }
    66  
    67  func defaultConfig(opts ...configOption) Config {
    68  	c := &Config{
    69  		MaxPendingQueueSize: DefaultMaxPendingQueueSize,
    70  		Clock:               faketime.NewManualClock(),
    71  	}
    72  
    73  	for _, opt := range opts {
    74  		opt(c)
    75  	}
    76  
    77  	return *c
    78  }
    79  
    80  func installedRouteComparer(a *InstalledRoute, b *InstalledRoute) bool {
    81  	if !cmp.Equal(a.OutgoingInterfaces, b.OutgoingInterfaces) {
    82  		return false
    83  	}
    84  
    85  	if a.ExpectedInputInterface != b.ExpectedInputInterface {
    86  		return false
    87  	}
    88  
    89  	return a.LastUsedTimestamp() == b.LastUsedTimestamp()
    90  }
    91  
    92  func TestInit(t *testing.T) {
    93  	tests := []struct {
    94  		name        string
    95  		config      Config
    96  		invokeTwice bool
    97  		wantErr     error
    98  	}{
    99  		{
   100  			name:        "MissingClock",
   101  			config:      defaultConfig(withClock(nil)),
   102  			invokeTwice: false,
   103  			wantErr:     ErrMissingClock,
   104  		},
   105  		{
   106  			name:        "AlreadyInitialized",
   107  			config:      defaultConfig(),
   108  			invokeTwice: true,
   109  			wantErr:     ErrAlreadyInitialized,
   110  		},
   111  		{
   112  			name:        "ValidConfig",
   113  			config:      defaultConfig(),
   114  			invokeTwice: false,
   115  			wantErr:     nil,
   116  		},
   117  	}
   118  
   119  	for _, tc := range tests {
   120  		t.Run(tc.name, func(t *testing.T) {
   121  			table := RouteTable{}
   122  			defer table.Close()
   123  			err := table.Init(tc.config)
   124  
   125  			if tc.invokeTwice {
   126  				err = table.Init(tc.config)
   127  			}
   128  
   129  			if !cmp.Equal(err, tc.wantErr, cmpopts.EquateErrors()) {
   130  				t.Errorf("table.Init(%#v) = %s, want %s", tc.config, err, tc.wantErr)
   131  			}
   132  		})
   133  	}
   134  }
   135  
   136  func TestNewInstalledRoute(t *testing.T) {
   137  	table := RouteTable{}
   138  	defer table.Close()
   139  	clock := faketime.NewManualClock()
   140  	clock.Advance(5 * time.Second)
   141  
   142  	config := defaultConfig(withClock(clock))
   143  	if err := table.Init(config); err != nil {
   144  		t.Fatalf("table.Init(%#v): %s", config, err)
   145  	}
   146  
   147  	route := table.NewInstalledRoute(defaultRoute)
   148  
   149  	expectedRoute := &InstalledRoute{
   150  		MulticastRoute:    defaultRoute,
   151  		lastUsedTimestamp: clock.NowMonotonic(),
   152  	}
   153  
   154  	if diff := cmp.Diff(expectedRoute, route, cmp.Comparer(installedRouteComparer)); diff != "" {
   155  		t.Errorf("Installed route mismatch (-want +got):\n%s", diff)
   156  	}
   157  }
   158  
   159  func TestGetRouteResultStates(t *testing.T) {
   160  	table := RouteTable{}
   161  	defer table.Close()
   162  	config := defaultConfig(withMaxPendingQueueSize(2))
   163  	if err := table.Init(config); err != nil {
   164  		t.Fatalf("table.Init(%#v): %s", config, err)
   165  	}
   166  
   167  	pkt := newPacketBuffer("hello")
   168  	defer pkt.DecRef()
   169  	// Queue two pending packets for the same route. The GetRouteResultState
   170  	// should transition from NoRouteFoundAndPendingInserted to
   171  	// PacketQueuedInPendingRoute.
   172  	for _, wantPendingRouteState := range []GetRouteResultState{NoRouteFoundAndPendingInserted, PacketQueuedInPendingRoute} {
   173  		routeResult, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
   174  
   175  		if !hasBufferSpace {
   176  			t.Errorf("table.GetRouteOrInsertPending(%#v, %#v) = (_, false), want = (_, true)", defaultRouteKey, pkt)
   177  		}
   178  
   179  		expectedResult := GetRouteResult{GetRouteResultState: wantPendingRouteState}
   180  		if diff := cmp.Diff(expectedResult, routeResult); diff != "" {
   181  			t.Errorf("table.GetRouteOrInsertPending(%#v, %#v) GetRouteResult mismatch (-want +got):\n%s", defaultRouteKey, pkt, diff)
   182  		}
   183  	}
   184  
   185  	// Queuing a third packet should yield an error since the pending queue is
   186  	// already at max capacity.
   187  	if _, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt); hasBufferSpace {
   188  		t.Errorf("table.GetRouteOrInsertPending(%#v, %#v) = (_, true), want = (_, false)", defaultRouteKey, pkt)
   189  	}
   190  }
   191  
   192  func TestPendingRouteExpiration(t *testing.T) {
   193  	pkt := newPacketBuffer("foo")
   194  	defer pkt.DecRef()
   195  
   196  	testCases := []struct {
   197  		name                string
   198  		advanceBeforeInsert time.Duration
   199  		advanceAfterInsert  time.Duration
   200  		wantPendingRoute    bool
   201  	}{
   202  		{
   203  			name:                "not expired",
   204  			advanceBeforeInsert: DefaultCleanupInterval / 2,
   205  			// The time is advanced far enough to run the cleanup routine, but not
   206  			// far enough to expire the route.
   207  			advanceAfterInsert: DefaultCleanupInterval,
   208  			wantPendingRoute:   true,
   209  		},
   210  		{
   211  			name: "expired",
   212  			// The cleanup routine will be run twice. The second invocation will
   213  			// remove the expired route.
   214  			advanceBeforeInsert: DefaultCleanupInterval / 2,
   215  			advanceAfterInsert:  DefaultCleanupInterval * 2,
   216  			wantPendingRoute:    false,
   217  		},
   218  	}
   219  
   220  	for _, test := range testCases {
   221  		t.Run(test.name, func(t *testing.T) {
   222  			clock := faketime.NewManualClock()
   223  
   224  			table := RouteTable{}
   225  			defer table.Close()
   226  			config := defaultConfig(withClock(clock))
   227  
   228  			if err := table.Init(config); err != nil {
   229  				t.Fatalf("table.Init(%#v): %s", config, err)
   230  			}
   231  
   232  			clock.Advance(test.advanceBeforeInsert)
   233  
   234  			if _, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt); !hasBufferSpace {
   235  				t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", defaultRouteKey, pkt)
   236  			}
   237  
   238  			clock.Advance(test.advanceAfterInsert)
   239  
   240  			table.pendingMu.RLock()
   241  			_, ok := table.pendingRoutes[defaultRouteKey]
   242  
   243  			if table.isCleanupRoutineRunning != test.wantPendingRoute {
   244  				t.Errorf("table.isCleanupRoutineRunning = %t, want = %t", table.isCleanupRoutineRunning, test.wantPendingRoute)
   245  			}
   246  			table.pendingMu.RUnlock()
   247  
   248  			if test.wantPendingRoute != ok {
   249  				t.Errorf("table.pendingRoutes[%#v] = (_, %t), want = (_, %t)", defaultRouteKey, ok, test.wantPendingRoute)
   250  			}
   251  		})
   252  	}
   253  }
   254  
   255  func TestAddInstalledRouteWithPending(t *testing.T) {
   256  	pkt := newPacketBuffer("foo")
   257  	defer pkt.DecRef()
   258  
   259  	cmpOpts := []cmp.Option{
   260  		cmp.Transformer("AsSlices", func(pkt *stack.PacketBuffer) [][]byte {
   261  			return pkt.AsSlices()
   262  		}),
   263  		cmp.Comparer(func(a [][]byte, b [][]byte) bool {
   264  			return cmp.Equal(a, b)
   265  		}),
   266  	}
   267  
   268  	testCases := []struct {
   269  		name    string
   270  		advance time.Duration
   271  		want    []*stack.PacketBuffer
   272  	}{
   273  		{
   274  			name:    "not expired",
   275  			advance: DefaultPendingRouteExpiration,
   276  			want:    []*stack.PacketBuffer{pkt},
   277  		},
   278  		{
   279  			name:    "expired",
   280  			advance: DefaultPendingRouteExpiration + 1,
   281  			want:    nil,
   282  		},
   283  	}
   284  
   285  	for _, test := range testCases {
   286  		t.Run(test.name, func(t *testing.T) {
   287  			clock := faketime.NewManualClock()
   288  
   289  			table := RouteTable{}
   290  			defer table.Close()
   291  			config := defaultConfig(withClock(clock))
   292  
   293  			if err := table.Init(config); err != nil {
   294  				t.Fatalf("table.Init(%#v): %s", config, err)
   295  			}
   296  
   297  			if _, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt); !hasBufferSpace {
   298  				t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", defaultRouteKey, pkt)
   299  			}
   300  
   301  			// Disable the cleanup routine.
   302  			table.cleanupPendingRoutesTimer.Stop()
   303  
   304  			clock.Advance(test.advance)
   305  
   306  			route := table.NewInstalledRoute(defaultRoute)
   307  			pendingPackets := table.AddInstalledRoute(defaultRouteKey, route)
   308  
   309  			if diff := cmp.Diff(test.want, pendingPackets, cmpOpts...); diff != "" {
   310  				t.Errorf("table.AddInstalledRoute(%#v, %#v) mismatch (-want +got):\n%s", defaultRouteKey, route, diff)
   311  			}
   312  
   313  			for _, pendingPkt := range pendingPackets {
   314  				pendingPkt.DecRef()
   315  			}
   316  
   317  			// Verify that the pending route is actually deleted.
   318  			table.pendingMu.RLock()
   319  			if pendingRoute, ok := table.pendingRoutes[defaultRouteKey]; ok {
   320  				t.Errorf("table.pendingRoutes[%#v] = (%#v, true), want (_, false)", defaultRouteKey, pendingRoute)
   321  			}
   322  			table.pendingMu.RUnlock()
   323  		})
   324  	}
   325  }
   326  
   327  func TestAddInstalledRouteWithNoPending(t *testing.T) {
   328  	table := RouteTable{}
   329  	defer table.Close()
   330  	config := defaultConfig()
   331  	if err := table.Init(config); err != nil {
   332  		t.Fatalf("table.Init(%#v): %s", config, err)
   333  	}
   334  
   335  	firstRoute := table.NewInstalledRoute(defaultRoute)
   336  
   337  	secondMulticastRoute := stack.MulticastRoute{defaultNICID, defaultOutgoingInterfaces}
   338  	secondRoute := table.NewInstalledRoute(secondMulticastRoute)
   339  
   340  	pkt := newPacketBuffer("hello")
   341  	defer pkt.DecRef()
   342  	for _, route := range [...]*InstalledRoute{firstRoute, secondRoute} {
   343  		if pendingPackets := table.AddInstalledRoute(defaultRouteKey, route); pendingPackets != nil {
   344  			t.Errorf("table.AddInstalledRoute(%#v, %#v) = %#v, want = false", defaultRouteKey, route, pendingPackets)
   345  		}
   346  
   347  		// AddInstalledRoute is invoked for the same routeKey two times. Verify
   348  		// that the fetched InstalledRoute reflects the most recent invocation of
   349  		// AddInstalledRoute.
   350  		routeResult, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
   351  
   352  		if !hasBufferSpace {
   353  			t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", defaultRouteKey, pkt)
   354  		}
   355  
   356  		if routeResult.GetRouteResultState != InstalledRouteFound {
   357  			t.Errorf("routeResult.GetRouteResultState = %s, want = InstalledRouteFound", routeResult.GetRouteResultState)
   358  		}
   359  
   360  		if diff := cmp.Diff(route, routeResult.InstalledRoute, cmp.Comparer(installedRouteComparer)); diff != "" {
   361  			t.Errorf("route.InstalledRoute mismatch (-want +got):\n%s", diff)
   362  		}
   363  	}
   364  }
   365  
   366  func TestRemoveInstalledRoute(t *testing.T) {
   367  	table := RouteTable{}
   368  	defer table.Close()
   369  	config := defaultConfig()
   370  	if err := table.Init(config); err != nil {
   371  		t.Fatalf("table.Init(%#v): %s", config, err)
   372  	}
   373  
   374  	route := table.NewInstalledRoute(defaultRoute)
   375  
   376  	table.AddInstalledRoute(defaultRouteKey, route)
   377  
   378  	if removed := table.RemoveInstalledRoute(defaultRouteKey); !removed {
   379  		t.Errorf("table.RemoveInstalledRoute(%#v) = false, want = true", defaultRouteKey)
   380  	}
   381  
   382  	pkt := newPacketBuffer("hello")
   383  	defer pkt.DecRef()
   384  
   385  	result, hasBufferSpace := table.GetRouteOrInsertPending(defaultRouteKey, pkt)
   386  
   387  	if !hasBufferSpace {
   388  		t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", defaultRouteKey, pkt)
   389  	}
   390  
   391  	if result.InstalledRoute != nil {
   392  		t.Errorf("result.InstalledRoute = %v, want = nil", result.InstalledRoute)
   393  	}
   394  }
   395  
   396  func TestRemoveInstalledRouteWithNoMatchingRoute(t *testing.T) {
   397  	table := RouteTable{}
   398  	defer table.Close()
   399  	config := defaultConfig()
   400  	if err := table.Init(config); err != nil {
   401  		t.Fatalf("table.Init(%#v): %s", config, err)
   402  	}
   403  
   404  	if removed := table.RemoveInstalledRoute(defaultRouteKey); removed {
   405  		t.Errorf("table.RemoveInstalledRoute(%#v) = true, want = false", defaultRouteKey)
   406  	}
   407  }
   408  
   409  func TestRemoveAllInstalledRoutes(t *testing.T) {
   410  	otherAddress := testutil.MustParse4("192.168.2.1")
   411  
   412  	table := RouteTable{}
   413  	defer table.Close()
   414  	config := defaultConfig()
   415  	if err := table.Init(config); err != nil {
   416  		t.Fatalf("table.Init(%#v): %s", config, err)
   417  	}
   418  
   419  	routes := map[stack.UnicastSourceAndMulticastDestination]stack.MulticastRoute{
   420  		defaultRouteKey: defaultRoute,
   421  		stack.UnicastSourceAndMulticastDestination{otherAddress, otherAddress}: defaultRoute,
   422  	}
   423  
   424  	for key, route := range routes {
   425  		installedRoute := table.NewInstalledRoute(route)
   426  		table.AddInstalledRoute(key, installedRoute)
   427  	}
   428  
   429  	table.RemoveAllInstalledRoutes()
   430  
   431  	for key := range routes {
   432  		pkt := newPacketBuffer("hello")
   433  		defer pkt.DecRef()
   434  
   435  		result, hasBufferSpace := table.GetRouteOrInsertPending(key, pkt)
   436  
   437  		if !hasBufferSpace {
   438  			t.Fatalf("table.GetRouteOrInsertPending(%#v, %#v): false", key, pkt)
   439  		}
   440  
   441  		if result.InstalledRoute != nil {
   442  			t.Errorf("result.InstalledRoute = %v, want = nil", result.InstalledRoute)
   443  		}
   444  	}
   445  }
   446  
   447  func TestGetLastUsedTimestampWithNoMatchingRoute(t *testing.T) {
   448  	table := RouteTable{}
   449  	defer table.Close()
   450  	config := defaultConfig()
   451  	if err := table.Init(config); err != nil {
   452  		t.Fatalf("table.Init(%#v): %s", config, err)
   453  	}
   454  
   455  	if _, found := table.GetLastUsedTimestamp(defaultRouteKey); found {
   456  		t.Errorf("table.GetLastUsedTimetsamp(%#v) = (_, true), want = (_, false)", defaultRouteKey)
   457  	}
   458  }
   459  
   460  func TestSetLastUsedTimestamp(t *testing.T) {
   461  	clock := faketime.NewManualClock()
   462  	clock.Advance(10 * time.Second)
   463  
   464  	currentTime := clock.NowMonotonic()
   465  	validLastUsedTime := currentTime.Add(10 * time.Second)
   466  
   467  	tests := []struct {
   468  		name             string
   469  		lastUsedTime     tcpip.MonotonicTime
   470  		wantLastUsedTime tcpip.MonotonicTime
   471  	}{
   472  		{
   473  			name:             "valid timestamp",
   474  			lastUsedTime:     validLastUsedTime,
   475  			wantLastUsedTime: validLastUsedTime,
   476  		},
   477  		{
   478  			name:             "timestamp before",
   479  			lastUsedTime:     currentTime.Add(-5 * time.Second),
   480  			wantLastUsedTime: currentTime,
   481  		},
   482  	}
   483  
   484  	for _, test := range tests {
   485  		t.Run(test.name, func(t *testing.T) {
   486  			table := RouteTable{}
   487  			defer table.Close()
   488  			config := defaultConfig(withClock(clock))
   489  			if err := table.Init(config); err != nil {
   490  				t.Fatalf("table.Init(%#v): %s", config, err)
   491  			}
   492  
   493  			route := table.NewInstalledRoute(defaultRoute)
   494  
   495  			table.AddInstalledRoute(defaultRouteKey, route)
   496  
   497  			route.SetLastUsedTimestamp(test.lastUsedTime)
   498  
   499  			// Verify that the updated timestamp is actually reflected in the RouteTable.
   500  			timestamp, found := table.GetLastUsedTimestamp(defaultRouteKey)
   501  
   502  			if !found {
   503  				t.Fatalf("table.GetLastUsedTimestamp(%#v) = (_, false_), want = (_, true)", defaultRouteKey)
   504  			}
   505  
   506  			if timestamp != test.wantLastUsedTime {
   507  				t.Errorf("table.GetLastUsedTimestamp(%#v) = (%s, _), want = (%s, _)", defaultRouteKey, timestamp, test.wantLastUsedTime)
   508  			}
   509  		})
   510  	}
   511  }
   512  
   513  func TestMain(m *testing.M) {
   514  	refs.SetLeakMode(refs.LeaksPanic)
   515  	code := m.Run()
   516  	refs.DoLeakCheck()
   517  	os.Exit(code)
   518  }