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  }