github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/devices_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 //go:build linux 5 6 package linux 7 8 import ( 9 "context" 10 "fmt" 11 "net" 12 "sort" 13 "testing" 14 15 "github.com/cilium/hive/cell" 16 "github.com/cilium/hive/hivetest" 17 "github.com/stretchr/testify/require" 18 "github.com/vishvananda/netlink" 19 "golang.org/x/sys/unix" 20 21 "github.com/cilium/cilium/pkg/hive" 22 "github.com/cilium/cilium/pkg/logging" 23 "github.com/cilium/cilium/pkg/node" 24 "github.com/cilium/cilium/pkg/option" 25 "github.com/cilium/cilium/pkg/testutils" 26 "github.com/cilium/cilium/pkg/testutils/netns" 27 ) 28 29 type DevicesSuite struct { 30 prevConfigDirectRoutingDevice string 31 prevConfigIPv6MCastDevice string 32 prevConfigEnableIPv4 bool 33 prevConfigEnableIPv6 bool 34 prevConfigEnableHostLegacyRouting bool 35 prevConfigEnableNodePort bool 36 prevConfigNodePortAcceleration string 37 prevConfigRoutingMode string 38 prevConfigEnableIPv6NDP bool 39 } 40 41 func setUpDevicesSuite(tb testing.TB) *DevicesSuite { 42 testutils.PrivilegedTest(tb) 43 44 var err error 45 46 s := &DevicesSuite{} 47 s.prevConfigDirectRoutingDevice = option.Config.DirectRoutingDevice 48 s.prevConfigEnableIPv4 = option.Config.EnableIPv4 49 s.prevConfigEnableIPv6 = option.Config.EnableIPv6 50 s.prevConfigEnableNodePort = option.Config.EnableNodePort 51 s.prevConfigNodePortAcceleration = option.Config.NodePortAcceleration 52 s.prevConfigRoutingMode = option.Config.RoutingMode 53 s.prevConfigEnableIPv6NDP = option.Config.EnableIPv6NDP 54 s.prevConfigIPv6MCastDevice = option.Config.IPv6MCastDevice 55 require.Nil(tb, err) 56 57 tb.Cleanup(func() { 58 option.Config.DirectRoutingDevice = s.prevConfigDirectRoutingDevice 59 option.Config.EnableIPv4 = s.prevConfigEnableIPv4 60 option.Config.EnableIPv6 = s.prevConfigEnableIPv6 61 option.Config.EnableNodePort = s.prevConfigEnableNodePort 62 option.Config.EnableHostLegacyRouting = s.prevConfigEnableHostLegacyRouting 63 option.Config.NodePortAcceleration = s.prevConfigNodePortAcceleration 64 option.Config.RoutingMode = s.prevConfigRoutingMode 65 option.Config.EnableIPv6NDP = s.prevConfigEnableIPv6NDP 66 option.Config.IPv6MCastDevice = s.prevConfigIPv6MCastDevice 67 }) 68 69 return s 70 } 71 72 func nodeSetIP(ip net.IP) { 73 node.UpdateLocalNodeInTest(func(n *node.LocalNode) { 74 n.SetNodeInternalIP(ip) 75 }) 76 } 77 78 func TestDetect(t *testing.T) { 79 s := setUpDevicesSuite(t) 80 s.withFixture(t, func() { 81 option.Config.DirectRoutingDevice = "" 82 option.Config.EnableNodePort = true 83 option.Config.NodePortAcceleration = option.NodePortAccelerationDisabled 84 option.Config.EnableHostLegacyRouting = true 85 option.Config.EnableNodePort = false 86 87 // 1. No devices, nothing to detect. 88 dm, err := newDeviceManagerForTests(t) 89 require.Nil(t, err) 90 91 devices, err := dm.Detect(false) 92 require.Nil(t, err) 93 require.EqualValues(t, []string{}, devices) 94 dm.Stop(t) 95 96 // 2. Nodeport, detection is performed: 97 option.Config.EnableNodePort = true 98 require.Nil(t, createDummy("dummy0", "192.168.0.1/24", false)) 99 nodeSetIP(net.ParseIP("192.168.0.1")) 100 101 dm, err = newDeviceManagerForTests(t) 102 require.Nil(t, err) 103 devices, err = dm.Detect(true) 104 require.Nil(t, err) 105 require.EqualValues(t, []string{"dummy0"}, devices) 106 require.Equal(t, "dummy0", option.Config.DirectRoutingDevice) 107 option.Config.DirectRoutingDevice = "" 108 dm.Stop(t) 109 110 // Manually specified devices, no detection is performed 111 option.Config.EnableNodePort = true 112 nodeSetIP(net.ParseIP("192.168.0.1")) 113 require.Nil(t, createDummy("dummy1", "192.168.1.1/24", false)) 114 115 dm, err = newDeviceManagerForTests(t, "dummy0") 116 require.Nil(t, err) 117 devices, err = dm.Detect(true) 118 require.Nil(t, err) 119 require.EqualValues(t, []string{"dummy0"}, devices) 120 require.Equal(t, "dummy0", option.Config.DirectRoutingDevice) 121 option.Config.DirectRoutingDevice = "" 122 123 // Direct routing mode, should find all devices and set direct 124 // routing device to the one with k8s node ip. 125 require.Nil(t, createDummy("dummy2", "192.168.2.1/24", false)) 126 require.Nil(t, createDummy("dummy3", "192.168.3.1/24", false)) 127 require.Nil(t, delRoutes("dummy3")) // Delete routes so it won't be detected 128 nodeSetIP(net.ParseIP("192.168.1.1")) 129 option.Config.EnableIPv4 = true 130 option.Config.EnableIPv6 = false 131 option.Config.RoutingMode = option.RoutingModeNative 132 dm, err = newDeviceManagerForTests(t) 133 require.Nil(t, err) 134 devices, err = dm.Detect(true) 135 require.Nil(t, err) 136 require.EqualValues(t, []string{"dummy0", "dummy1", "dummy2"}, devices) 137 require.Equal(t, "dummy1", option.Config.DirectRoutingDevice) 138 option.Config.DirectRoutingDevice = "" 139 dm.Stop(t) 140 141 // Tunnel routing mode with XDP, should find all devices and set direct 142 // routing device to the one with k8s node ip. 143 nodeSetIP(net.ParseIP("192.168.1.1")) 144 option.Config.EnableIPv4 = true 145 option.Config.EnableIPv6 = false 146 option.Config.RoutingMode = option.RoutingModeTunnel 147 option.Config.EnableNodePort = true 148 option.Config.NodePortAcceleration = option.NodePortAccelerationNative 149 150 dm, err = newDeviceManagerForTests(t) 151 require.Nil(t, err) 152 devices, err = dm.Detect(true) 153 require.Nil(t, err) 154 require.EqualValues(t, []string{"dummy0", "dummy1", "dummy2"}, devices) 155 require.Equal(t, "dummy1", option.Config.DirectRoutingDevice) 156 157 option.Config.DirectRoutingDevice = "" 158 option.Config.NodePortAcceleration = option.NodePortAccelerationDisabled 159 option.Config.RoutingMode = option.RoutingModeNative 160 dm.Stop(t) 161 162 // Use IPv6 node IP and enable IPv6NDP and check that multicast device is detected. 163 option.Config.EnableIPv6 = true 164 option.Config.EnableIPv6NDP = true 165 require.Nil(t, createDummy("dummy_v6", "2001:db8::face/64", true)) 166 nodeSetIP(nil) 167 nodeSetIP(net.ParseIP("2001:db8::face")) 168 dm, err = newDeviceManagerForTests(t) 169 require.Nil(t, err) 170 devices, err = dm.Detect(true) 171 require.Nil(t, err) 172 require.EqualValues(t, []string{"dummy0", "dummy1", "dummy2", "dummy_v6"}, devices) 173 require.Equal(t, "dummy_v6", option.Config.DirectRoutingDevice) 174 require.EqualValues(t, "dummy_v6", option.Config.IPv6MCastDevice) 175 option.Config.DirectRoutingDevice = "" 176 dm.Stop(t) 177 178 // Only consider veth devices if they have a default route. 179 require.Nil(t, createVeth("veth0", "192.168.4.1/24", false)) 180 dm, err = newDeviceManagerForTests(t) 181 require.Nil(t, err) 182 devices, err = dm.Detect(true) 183 require.Nil(t, err) 184 require.EqualValues(t, []string{"dummy0", "dummy1", "dummy2", "dummy_v6"}, devices) 185 dm.Stop(t) 186 187 require.Nil(t, addRoute(addRouteParams{iface: "veth0", gw: "192.168.4.254", table: unix.RT_TABLE_MAIN})) 188 dm, err = newDeviceManagerForTests(t) 189 require.Nil(t, err) 190 devices, err = dm.Detect(true) 191 require.Nil(t, err) 192 require.EqualValues(t, []string{"dummy0", "dummy1", "dummy2", "dummy_v6", "veth0"}, devices) 193 dm.Stop(t) 194 195 // Detect devices that only have routes in non-main tables 196 require.Nil(t, addRoute(addRouteParams{iface: "dummy3", dst: "192.168.3.1/24", scope: unix.RT_SCOPE_LINK, table: 11})) 197 dm, err = newDeviceManagerForTests(t) 198 require.Nil(t, err) 199 devices, err = dm.Detect(true) 200 require.Nil(t, err) 201 require.EqualValues(t, []string{"dummy0", "dummy1", "dummy2", "dummy3", "dummy_v6", "veth0"}, devices) 202 dm.Stop(t) 203 204 // Skip bridge devices, and devices added to the bridge 205 require.Nil(t, createBridge("br0", "192.168.5.1/24", false)) 206 dm, err = newDeviceManagerForTests(t) 207 require.Nil(t, err) 208 devices, err = dm.Detect(true) 209 require.Nil(t, err) 210 require.EqualValues(t, []string{"dummy0", "dummy1", "dummy2", "dummy3", "dummy_v6", "veth0"}, devices) 211 dm.Stop(t) 212 213 require.Nil(t, setMaster("dummy3", "br0")) 214 dm, err = newDeviceManagerForTests(t) 215 require.Nil(t, err) 216 devices, err = dm.Detect(true) 217 require.Nil(t, err) 218 require.EqualValues(t, []string{"dummy0", "dummy1", "dummy2", "dummy_v6", "veth0"}, devices) 219 dm.Stop(t) 220 221 // Don't skip bond devices, but do skip bond slaves. 222 require.Nil(t, createBond("bond0", "192.168.6.1/24", false)) 223 require.Nil(t, setBondMaster("dummy2", "bond0")) 224 dm, err = newDeviceManagerForTests(t) 225 require.Nil(t, err) 226 devices, err = dm.Detect(true) 227 require.Nil(t, err) 228 sort.Strings(devices) 229 require.EqualValues(t, []string{"bond0", "dummy0", "dummy1", "dummy_v6", "veth0"}, devices) 230 dm.Stop(t) 231 }) 232 } 233 234 func TestExpandDirectRoutingDevice(t *testing.T) { 235 s := setUpDevicesSuite(t) 236 s.withFixture(t, func() { 237 option.Config.EnableNodePort = true 238 option.Config.RoutingMode = option.RoutingModeNative 239 240 require.Nil(t, createDummy("dummy0", "192.168.0.1/24", false)) 241 require.Nil(t, createDummy("dummy1", "192.168.1.2/24", false)) 242 require.Nil(t, createDummy("unmatched", "192.168.4.5/24", false)) 243 nodeSetIP(net.ParseIP("192.168.0.1")) 244 245 // 1. Check expansion works and non-matching prefixes are ignored 246 option.Config.DirectRoutingDevice = "dummy+" 247 dm, err := newDeviceManagerForTests(t) 248 require.Nil(t, err) 249 _, err = dm.Detect(true) 250 require.Nil(t, err) 251 require.Equal(t, "dummy0", option.Config.DirectRoutingDevice) 252 dm.Stop(t) 253 254 // 2. Check that expansion fails if directRoutingDevice is specified but yields empty expansion 255 option.Config.DirectRoutingDevice = "none+" 256 dm, err = newDeviceManagerForTests(t) 257 require.Nil(t, err) 258 _, err = dm.Detect(true) 259 require.Error(t, err) 260 require.Equal(t, "", option.Config.DirectRoutingDevice) 261 dm.Stop(t) 262 }) 263 } 264 265 func (s *DevicesSuite) withFixture(t *testing.T, test func()) { 266 logging.SetLogLevelToDebug() 267 268 ns := netns.NewNetNS(t) 269 270 ns.Do(func() error { 271 node.WithTestLocalNodeStore(test) 272 return nil 273 }) 274 } 275 276 func createLink(linkTemplate netlink.Link, iface, ipAddr string, flagMulticast bool) error { 277 var flags net.Flags 278 if flagMulticast { 279 flags = net.FlagMulticast 280 } 281 *linkTemplate.Attrs() = netlink.LinkAttrs{ 282 Name: iface, 283 Flags: flags, 284 } 285 286 if err := netlink.LinkAdd(linkTemplate); err != nil { 287 return err 288 } 289 290 if ipAddr != "" { 291 if err := addAddr(iface, ipAddr); err != nil { 292 return err 293 } 294 } 295 296 link, err := netlink.LinkByName(iface) 297 if err != nil { 298 return err 299 } 300 301 if err := netlink.LinkSetUp(link); err != nil { 302 return err 303 } 304 305 return nil 306 } 307 308 func deleteLink(name string) error { 309 link, err := netlink.LinkByName(name) 310 if err != nil { 311 return err 312 } 313 return netlink.LinkDel(link) 314 } 315 316 func createDummy(iface, ipAddr string, flagMulticast bool) error { 317 return createLink(&netlink.Dummy{}, iface, ipAddr, flagMulticast) 318 } 319 320 func createVeth(iface, ipAddr string, flagMulticast bool) error { 321 return createLink(&netlink.Veth{PeerName: iface + "_"}, iface, ipAddr, flagMulticast) 322 } 323 324 func createBridge(iface, ipAddr string, flagMulticast bool) error { 325 return createLink(&netlink.Bridge{}, iface, ipAddr, flagMulticast) 326 } 327 328 func createBond(iface, ipAddr string, flagMulticast bool) error { 329 bond := netlink.NewLinkBond(netlink.LinkAttrs{}) 330 bond.Mode = netlink.BOND_MODE_BALANCE_RR 331 return createLink(bond, iface, ipAddr, flagMulticast) 332 } 333 334 func setLinkUp(iface string) error { 335 link, err := netlink.LinkByName(iface) 336 if err != nil { 337 return err 338 } 339 return netlink.LinkSetUp(link) 340 } 341 342 func setMaster(iface string, master string) error { 343 masterLink, err := netlink.LinkByName(master) 344 if err != nil { 345 return err 346 } 347 link, err := netlink.LinkByName(iface) 348 if err != nil { 349 return err 350 } 351 return netlink.LinkSetMaster(link, masterLink) 352 } 353 354 func setBondMaster(iface string, master string) error { 355 masterLink, err := netlink.LinkByName(master) 356 if err != nil { 357 return err 358 } 359 link, err := netlink.LinkByName(iface) 360 if err != nil { 361 return err 362 } 363 netlink.LinkSetDown(link) 364 defer netlink.LinkSetUp(link) 365 return netlink.LinkSetBondSlave(link, masterLink.(*netlink.Bond)) 366 } 367 func addAddr(iface string, cidr string) error { 368 return addAddrScoped(iface, cidr, netlink.SCOPE_SITE, 0) 369 } 370 371 func addAddrScoped(iface string, cidr string, scope netlink.Scope, flags int) error { 372 ip, ipnet, err := net.ParseCIDR(cidr) 373 if err != nil { 374 return fmt.Errorf("ParseCIDR: %w", err) 375 } 376 ipnet.IP = ip 377 link, err := netlink.LinkByName(iface) 378 if err != nil { 379 return fmt.Errorf("LinkByName: %w", err) 380 } 381 382 if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: ipnet, Scope: int(scope), Flags: flags}); err != nil { 383 return fmt.Errorf("AddrAdd: %w", err) 384 } 385 return nil 386 } 387 388 type addRouteParams struct { 389 iface string 390 gw string 391 src string 392 dst string 393 table int 394 scope netlink.Scope 395 } 396 397 func addRoute(p addRouteParams) error { 398 link, err := netlink.LinkByName(p.iface) 399 if err != nil { 400 return err 401 } 402 403 var dst *net.IPNet 404 if p.dst != "" { 405 _, dst, err = net.ParseCIDR(p.dst) 406 if err != nil { 407 return err 408 } 409 } 410 411 var src net.IP 412 if p.src != "" { 413 src = net.ParseIP(p.src) 414 } 415 416 if p.table == 0 { 417 p.table = unix.RT_TABLE_MAIN 418 } 419 420 route := &netlink.Route{ 421 LinkIndex: link.Attrs().Index, 422 Dst: dst, 423 Src: src, 424 Gw: net.ParseIP(p.gw), 425 Table: p.table, 426 Scope: p.scope, 427 } 428 if err := netlink.RouteAdd(route); err != nil { 429 return err 430 } 431 432 return nil 433 } 434 435 func delRoute(p addRouteParams) error { 436 link, err := netlink.LinkByName(p.iface) 437 if err != nil { 438 return err 439 } 440 441 var dst *net.IPNet 442 if p.dst != "" { 443 _, dst, err = net.ParseCIDR(p.dst) 444 if err != nil { 445 return err 446 } 447 } 448 449 var src net.IP 450 if p.src != "" { 451 src = net.ParseIP(p.src) 452 } 453 454 if p.table == 0 { 455 p.table = unix.RT_TABLE_MAIN 456 } 457 458 route := &netlink.Route{ 459 LinkIndex: link.Attrs().Index, 460 Dst: dst, 461 Src: src, 462 Gw: net.ParseIP(p.gw), 463 Table: p.table, 464 Scope: p.scope, 465 } 466 if err := netlink.RouteDel(route); err != nil { 467 return err 468 } 469 470 return nil 471 } 472 473 func delRoutes(iface string) error { 474 link, err := netlink.LinkByName(iface) 475 if err != nil { 476 return err 477 } 478 479 filter := netlink.Route{ 480 Table: unix.RT_TABLE_UNSPEC, 481 LinkIndex: link.Attrs().Index, 482 } 483 mask := netlink.RT_FILTER_TABLE | netlink.RT_FILTER_OIF 484 485 routes, err := netlink.RouteListFiltered(netlink.FAMILY_ALL, &filter, mask) 486 if err != nil { 487 return err 488 } 489 490 for _, r := range routes { 491 if err := netlink.RouteDel(&r); err != nil { 492 return err 493 } 494 } 495 496 return nil 497 } 498 499 func newDeviceManagerForTests(t testing.TB, devs ...string) (dm *DeviceManager, err error) { 500 h := hive.New( 501 DevicesControllerCell, 502 cell.Provide(func() (*netlinkFuncs, error) { return makeNetlinkFuncs() }), 503 cell.Invoke(func(dm_ *DeviceManager) { 504 dm = dm_ 505 })) 506 hive.AddConfigOverride(h, func(c *DevicesConfig) { 507 c.Devices = devs 508 }) 509 err = h.Start(hivetest.Logger(t), context.TODO()) 510 dm.hive = h 511 return 512 } 513 514 func (dm *DeviceManager) Stop(t testing.TB) { 515 if dm.hive != nil { 516 dm.hive.Stop(hivetest.Logger(t), context.TODO()) 517 } 518 }