github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/routing/routing_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package linuxrouting
     5  
     6  import (
     7  	"net"
     8  	"net/netip"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/vishvananda/netlink"
    13  
    14  	"github.com/cilium/cilium/pkg/datapath/linux/linux_defaults"
    15  	"github.com/cilium/cilium/pkg/datapath/linux/route"
    16  	ipamOption "github.com/cilium/cilium/pkg/ipam/option"
    17  	"github.com/cilium/cilium/pkg/mac"
    18  	"github.com/cilium/cilium/pkg/node"
    19  	"github.com/cilium/cilium/pkg/option"
    20  	"github.com/cilium/cilium/pkg/testutils"
    21  	"github.com/cilium/cilium/pkg/testutils/netns"
    22  )
    23  
    24  func setupLinuxRoutingSuite(tb testing.TB) {
    25  	testutils.PrivilegedTest(tb)
    26  }
    27  
    28  func TestConfigure(t *testing.T) {
    29  	setupLinuxRoutingSuite(t)
    30  
    31  	ns1 := netns.NewNetNS(t)
    32  	ns1.Do(func() error {
    33  		ip, ri := getFakes(t, true)
    34  		masterMAC := ri.MasterIfMAC
    35  		ifaceCleanup := createDummyDevice(t, masterMAC)
    36  		defer ifaceCleanup()
    37  
    38  		runConfigureThenDelete(t, ri, ip, 1500)
    39  		return nil
    40  	})
    41  
    42  	ns2 := netns.NewNetNS(t)
    43  	ns2.Do(func() error {
    44  		ip, ri := getFakes(t, false)
    45  		masterMAC := ri.MasterIfMAC
    46  		ifaceCleanup := createDummyDevice(t, masterMAC)
    47  		defer ifaceCleanup()
    48  
    49  		runConfigureThenDelete(t, ri, ip, 1500)
    50  		return nil
    51  	})
    52  }
    53  
    54  func TestConfigureRouteWithIncompatibleIP(t *testing.T) {
    55  	setupLinuxRoutingSuite(t)
    56  
    57  	_, ri := getFakes(t, true)
    58  	ipv6 := netip.MustParseAddr("fd00::2").AsSlice()
    59  	err := ri.Configure(ipv6, 1500, false, false)
    60  	require.Error(t, err)
    61  	require.ErrorContains(t, err, "IP not compatible")
    62  }
    63  
    64  func TestDeleteRouteWithIncompatibleIP(t *testing.T) {
    65  	setupLinuxRoutingSuite(t)
    66  
    67  	ipv6 := netip.MustParseAddr("fd00::2")
    68  	err := Delete(ipv6, false)
    69  	require.Error(t, err)
    70  	require.ErrorContains(t, err, "IP not compatible")
    71  }
    72  
    73  func TestDelete(t *testing.T) {
    74  	setupLinuxRoutingSuite(t)
    75  
    76  	fakeIP, fakeRoutingInfo := getFakes(t, true)
    77  	masterMAC := fakeRoutingInfo.MasterIfMAC
    78  
    79  	tests := []struct {
    80  		name    string
    81  		preRun  func() netip.Addr
    82  		wantErr bool
    83  	}{
    84  		{
    85  			name: "valid IP addr matching rules",
    86  			preRun: func() netip.Addr {
    87  				runConfigure(t, fakeRoutingInfo, fakeIP, 1500)
    88  				return fakeIP
    89  			},
    90  			wantErr: false,
    91  		},
    92  		{
    93  			name: "IP addr doesn't match rules",
    94  			preRun: func() netip.Addr {
    95  				ip := netip.MustParseAddr("192.168.2.233")
    96  
    97  				runConfigure(t, fakeRoutingInfo, fakeIP, 1500)
    98  				return ip
    99  			},
   100  			wantErr: true,
   101  		},
   102  		{
   103  			name: "IP addr matches more than number expected",
   104  			preRun: func() netip.Addr {
   105  				ip := netip.MustParseAddr("192.168.2.233")
   106  
   107  				runConfigure(t, fakeRoutingInfo, ip, 1500)
   108  
   109  				// Find interface ingress rules so that we can create a
   110  				// near-duplicate.
   111  				rules, err := route.ListRules(netlink.FAMILY_V4, &route.Rule{
   112  					Priority: linux_defaults.RulePriorityIngress,
   113  				})
   114  				require.Nil(t, err)
   115  				require.NotEqual(t, 0, len(rules))
   116  
   117  				// Insert almost duplicate rule; the reason for this is to
   118  				// trigger an error while trying to delete the ingress rule. We
   119  				// are setting the Src because ingress rules don't have
   120  				// one (only Dst), thus we set Src to create a near-duplicate.
   121  				r := rules[0]
   122  				r.Src = &net.IPNet{IP: fakeIP.AsSlice(), Mask: net.CIDRMask(32, 32)}
   123  				require.Nil(t, netlink.RuleAdd(&r))
   124  
   125  				return ip
   126  			},
   127  			wantErr: true,
   128  		},
   129  		{
   130  			name: "fails to delete rules due to masquerade misconfiguration",
   131  			preRun: func() netip.Addr {
   132  				runConfigure(t, fakeRoutingInfo, fakeIP, 1500)
   133  				// inconsistency with fakeRoutingInfo.Masquerade should lead to failure
   134  				option.Config.EnableIPv4Masquerade = false
   135  				return fakeIP
   136  			},
   137  			wantErr: true,
   138  		},
   139  	}
   140  	for _, tt := range tests {
   141  		t.Log("Test: " + tt.name)
   142  		ns := netns.NewNetNS(t)
   143  		ns.Do(func() error {
   144  			ifaceCleanup := createDummyDevice(t, masterMAC)
   145  			defer ifaceCleanup()
   146  
   147  			ip := tt.preRun()
   148  			err := Delete(ip, false)
   149  			require.Equal(t, tt.wantErr, (err != nil))
   150  			return nil
   151  		})
   152  	}
   153  }
   154  
   155  func runConfigureThenDelete(t *testing.T, ri RoutingInfo, ip netip.Addr, mtu int) {
   156  	// Create rules and routes
   157  	beforeCreationRules, beforeCreationRoutes := listRulesAndRoutes(t, netlink.FAMILY_V4)
   158  	runConfigure(t, ri, ip, mtu)
   159  	afterCreationRules, afterCreationRoutes := listRulesAndRoutes(t, netlink.FAMILY_V4)
   160  
   161  	require.NotEqual(t, 0, len(afterCreationRules))
   162  	require.NotEqual(t, 0, len(afterCreationRoutes))
   163  	require.NotEqual(t, len(afterCreationRules), len(beforeCreationRules))
   164  	require.NotEqual(t, len(afterCreationRoutes), len(beforeCreationRoutes))
   165  
   166  	// Delete rules and routes
   167  	beforeDeletionRules, beforeDeletionRoutes := listRulesAndRoutes(t, netlink.FAMILY_V4)
   168  	runDelete(t, ip)
   169  	afterDeletionRules, afterDeletionRoutes := listRulesAndRoutes(t, netlink.FAMILY_V4)
   170  
   171  	require.NotEqual(t, len(afterDeletionRules), len(beforeDeletionRules))
   172  	require.NotEqual(t, len(afterDeletionRoutes), len(beforeDeletionRoutes))
   173  	require.Equal(t, len(beforeCreationRules), len(afterDeletionRules))
   174  	require.Equal(t, len(beforeCreationRoutes), len(afterDeletionRoutes))
   175  }
   176  
   177  func runConfigure(t *testing.T, ri RoutingInfo, ip netip.Addr, mtu int) {
   178  	err := ri.Configure(ip.AsSlice(), mtu, false, false)
   179  	require.Nil(t, err)
   180  }
   181  
   182  func runDelete(t *testing.T, ip netip.Addr) {
   183  	err := Delete(ip, false)
   184  	require.Nil(t, err)
   185  }
   186  
   187  // listRulesAndRoutes returns all rules and routes configured on the machine
   188  // this test is running on. Note that this function is intended to be used
   189  // within a network namespace for isolation.
   190  func listRulesAndRoutes(t *testing.T, family int) ([]netlink.Rule, []netlink.Route) {
   191  	rules, err := route.ListRules(family, nil)
   192  	require.Nil(t, err)
   193  
   194  	// Rules are created under specific tables, so find the routes that are in
   195  	// those tables.
   196  	var routes []netlink.Route
   197  	for _, r := range rules {
   198  		rr, err := netlink.RouteListFiltered(family, &netlink.Route{
   199  			Table: r.Table,
   200  		}, netlink.RT_FILTER_TABLE)
   201  		require.Nil(t, err)
   202  
   203  		routes = append(routes, rr...)
   204  	}
   205  
   206  	return rules, routes
   207  }
   208  
   209  // createDummyDevice creates a new dummy device with a MAC of `macAddr` to be
   210  // used as a harness in this test. This function returns a function which can
   211  // be used to remove the device for cleanup purposes.
   212  func createDummyDevice(t *testing.T, macAddr mac.MAC) func() {
   213  	if linkExistsWithMAC(t, macAddr) {
   214  		t.FailNow()
   215  	}
   216  
   217  	dummy := &netlink.Dummy{
   218  		LinkAttrs: netlink.LinkAttrs{
   219  			// NOTE: This name must be less than 16 chars, source:
   220  			// https://elixir.bootlin.com/linux/v5.6/source/include/uapi/linux/if.h#L33
   221  			Name:         "linuxrout-test",
   222  			HardwareAddr: net.HardwareAddr(macAddr),
   223  		},
   224  	}
   225  	err := netlink.LinkAdd(dummy)
   226  	require.Nil(t, err)
   227  
   228  	found := linkExistsWithMAC(t, macAddr)
   229  	require.Equal(t, true, found)
   230  
   231  	return func() {
   232  		require.Nil(t, netlink.LinkDel(dummy))
   233  	}
   234  }
   235  
   236  // getFakes returns a fake IP simulating an Endpoint IP and RoutingInfo as test harnesses.
   237  // To create routing info with a list of CIDRs which the interface has access to, set withCIDR parameter to true
   238  func getFakes(t *testing.T, withCIDR bool) (netip.Addr, RoutingInfo) {
   239  	fakeGateway := netip.MustParseAddr("192.168.2.1")
   240  	fakeSubnet1CIDR := netip.MustParsePrefix("192.168.0.0/16")
   241  	fakeSubnet2CIDR := netip.MustParsePrefix("192.170.0.0/16")
   242  	fakeMAC, err := mac.ParseMAC("00:11:22:33:44:55")
   243  	require.Nil(t, err)
   244  	require.NotNil(t, fakeMAC)
   245  
   246  	var fakeRoutingInfo *RoutingInfo
   247  	if withCIDR {
   248  		fakeRoutingInfo, err = parse(
   249  			fakeGateway.String(),
   250  			[]string{fakeSubnet1CIDR.String(), fakeSubnet2CIDR.String()},
   251  			fakeMAC.String(),
   252  			"1",
   253  			ipamOption.IPAMENI,
   254  			true,
   255  		)
   256  	} else {
   257  		fakeRoutingInfo, err = parse(
   258  			fakeGateway.String(),
   259  			nil,
   260  			fakeMAC.String(),
   261  			"1",
   262  			ipamOption.IPAMAzure,
   263  			false,
   264  		)
   265  	}
   266  	require.Nil(t, err)
   267  	require.NotNil(t, fakeRoutingInfo)
   268  
   269  	node.SetRouterInfo(fakeRoutingInfo)
   270  	option.Config.IPAM = fakeRoutingInfo.IpamMode
   271  	option.Config.EnableIPv4Masquerade = fakeRoutingInfo.Masquerade
   272  
   273  	fakeIP := netip.MustParseAddr("192.168.2.123")
   274  	return fakeIP, *fakeRoutingInfo
   275  }
   276  
   277  func linkExistsWithMAC(t *testing.T, macAddr mac.MAC) bool {
   278  	links, err := netlink.LinkList()
   279  	require.Nil(t, err)
   280  
   281  	for _, link := range links {
   282  		if link.Attrs().HardwareAddr.String() == macAddr.String() {
   283  			return true
   284  		}
   285  	}
   286  
   287  	return false
   288  }