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 }