github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/route/route_linux_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package route
     5  
     6  import (
     7  	"net"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/vishvananda/netlink"
    14  	"golang.org/x/sys/unix"
    15  
    16  	"github.com/cilium/cilium/pkg/testutils"
    17  	"github.com/cilium/cilium/pkg/testutils/netns"
    18  )
    19  
    20  func setup(tb testing.TB) {
    21  	testutils.PrivilegedTest(tb)
    22  }
    23  
    24  func testReplaceNexthopRoute(t *testing.T, link netlink.Link, routerNet *net.IPNet) {
    25  	route := Route{
    26  		Table: 10,
    27  	}
    28  	// delete route in case it exists from a previous failed run
    29  	deleteNexthopRoute(route, link, routerNet)
    30  
    31  	// defer cleanup in case of failure
    32  	defer deleteNexthopRoute(route, link, routerNet)
    33  
    34  	replaced, err := replaceNexthopRoute(route, link, routerNet)
    35  	require.Nil(t, err)
    36  	require.Equal(t, true, replaced)
    37  
    38  	// We expect routes to always be replaced
    39  	replaced, err = replaceNexthopRoute(route, link, routerNet)
    40  	require.Nil(t, err)
    41  	require.Equal(t, true, replaced)
    42  
    43  	err = deleteNexthopRoute(route, link, routerNet)
    44  	require.Nil(t, err)
    45  }
    46  
    47  func TestReplaceNexthopRoute(t *testing.T) {
    48  	setup(t)
    49  
    50  	link, err := netlink.LinkByName("lo")
    51  	require.Nil(t, err)
    52  
    53  	_, routerNet, err := net.ParseCIDR("1.2.3.4/32")
    54  	require.Nil(t, err)
    55  	testReplaceNexthopRoute(t, link, routerNet)
    56  
    57  	_, routerNet, err = net.ParseCIDR("f00d::a02:100:0:815b/128")
    58  	require.Nil(t, err)
    59  	testReplaceNexthopRoute(t, link, routerNet)
    60  }
    61  
    62  func testReplaceRoute(t *testing.T, prefixStr, nexthopStr string, lookupTest bool) {
    63  	_, prefix, err := net.ParseCIDR(prefixStr)
    64  	require.Nil(t, err)
    65  	require.NotNil(t, prefix)
    66  
    67  	nexthop := net.ParseIP(nexthopStr)
    68  	require.NotNil(t, nexthop)
    69  
    70  	rt := Route{
    71  		Device:  "lo",
    72  		Prefix:  *prefix,
    73  		Nexthop: &nexthop,
    74  	}
    75  
    76  	// delete route in case it exists from a previous failed run
    77  	Delete(rt)
    78  
    79  	// Defer deletion of route and nexthop route to cleanup in case of failure
    80  	defer Delete(rt)
    81  	defer Delete(Route{
    82  		Device: "lo",
    83  		Prefix: *rt.getNexthopAsIPNet(),
    84  		Scope:  netlink.SCOPE_LINK,
    85  	})
    86  
    87  	err = Upsert(rt)
    88  	require.Nil(t, err)
    89  
    90  	if lookupTest {
    91  		// Account for minimal kernel race condition where route is not
    92  		// yet available
    93  		require.Nil(t, testutils.WaitUntil(func() bool {
    94  			installedRoute, err := Lookup(rt)
    95  			require.Nil(t, err)
    96  			return installedRoute != nil
    97  		}, 5*time.Second))
    98  	}
    99  
   100  	err = Delete(rt)
   101  	require.Nil(t, err)
   102  }
   103  
   104  func TestReplaceRoute(t *testing.T) {
   105  	setup(t)
   106  
   107  	testReplaceRoute(t, "2.2.0.0/16", "1.2.3.4", true)
   108  	// lookup test broken for IPv6 as long as use lo as device
   109  	testReplaceRoute(t, "f00d::a02:200:0:0/96", "f00d::a02:100:0:815b", false)
   110  }
   111  
   112  func testReplaceRule(t *testing.T, mark int, from, to *net.IPNet, table int) {
   113  	rule := Rule{Mark: mark, From: from, To: to, Table: table}
   114  
   115  	// delete rule in case it exists from a previous failed run
   116  	DeleteRule(netlink.FAMILY_V4, rule)
   117  
   118  	rule.Priority = 1
   119  	err := ReplaceRule(rule)
   120  	require.Nil(t, err)
   121  
   122  	exists, err := lookupRule(rule, netlink.FAMILY_V4)
   123  	require.Nil(t, err)
   124  	require.Equal(t, true, exists)
   125  
   126  	rule.Mask++
   127  	exists, err = lookupRule(rule, netlink.FAMILY_V4)
   128  	require.Nil(t, err)
   129  	require.Equal(t, false, exists)
   130  	rule.Mask--
   131  
   132  	err = DeleteRule(netlink.FAMILY_V4, rule)
   133  	require.Nil(t, err)
   134  
   135  	exists, err = lookupRule(rule, netlink.FAMILY_V4)
   136  	require.Nil(t, err)
   137  	require.Equal(t, false, exists)
   138  }
   139  
   140  func testReplaceRuleIPv6(t *testing.T, mark int, from, to *net.IPNet, table int) {
   141  	rule := Rule{Mark: mark, From: from, To: to, Table: table}
   142  
   143  	// delete rule in case it exists from a previous failed run
   144  	DeleteRule(netlink.FAMILY_V6, rule)
   145  
   146  	rule.Priority = 1
   147  	err := ReplaceRuleIPv6(rule)
   148  	require.Nil(t, err)
   149  
   150  	exists, err := lookupRule(rule, netlink.FAMILY_V6)
   151  	require.Nil(t, err)
   152  	require.Equal(t, true, exists)
   153  
   154  	err = DeleteRule(netlink.FAMILY_V6, rule)
   155  	require.Nil(t, err)
   156  
   157  	exists, err = lookupRule(rule, netlink.FAMILY_V6)
   158  	require.Nil(t, err)
   159  	require.Equal(t, false, exists)
   160  }
   161  
   162  func TestReplaceRule(t *testing.T) {
   163  	setup(t)
   164  
   165  	_, cidr1, err := net.ParseCIDR("10.10.0.0/16")
   166  	require.Nil(t, err)
   167  	testReplaceRule(t, 0xf00, nil, nil, 123)
   168  	testReplaceRule(t, 0xf00, cidr1, nil, 124)
   169  	testReplaceRule(t, 0, nil, cidr1, 125)
   170  	testReplaceRule(t, 0, cidr1, cidr1, 126)
   171  }
   172  
   173  func TestReplaceRule6(t *testing.T) {
   174  	setup(t)
   175  
   176  	_, cidr1, err := net.ParseCIDR("beef::/48")
   177  	require.Nil(t, err)
   178  	testReplaceRuleIPv6(t, 0xf00, nil, nil, 123)
   179  	testReplaceRuleIPv6(t, 0xf00, cidr1, nil, 124)
   180  	testReplaceRuleIPv6(t, 0, nil, cidr1, 125)
   181  	testReplaceRuleIPv6(t, 0, cidr1, cidr1, 126)
   182  }
   183  
   184  func TestRule_String(t *testing.T) {
   185  	setup(t)
   186  
   187  	_, fakeIP, _ := net.ParseCIDR("10.10.10.10/32")
   188  	_, fakeIP2, _ := net.ParseCIDR("1.1.1.1/32")
   189  
   190  	tests := []struct {
   191  		name    string
   192  		rule    Rule
   193  		wantStr string
   194  	}{
   195  		{
   196  			name: "contains from and to IPs",
   197  			rule: Rule{
   198  				From: fakeIP,
   199  				To:   fakeIP2,
   200  			},
   201  			wantStr: "0: from 10.10.10.10/32 to 1.1.1.1/32 lookup 0 proto unspec",
   202  		},
   203  		{
   204  			name: "contains priority",
   205  			rule: Rule{
   206  				Priority: 1,
   207  			},
   208  			wantStr: "1: from all to all lookup 0 proto unspec",
   209  		},
   210  		{
   211  			name: "contains table",
   212  			rule: Rule{
   213  				Table: 1,
   214  			},
   215  			wantStr: "0: from all to all lookup 1 proto unspec",
   216  		},
   217  		{
   218  			name: "contains mark and mask",
   219  			rule: Rule{
   220  				Mark: 1,
   221  				Mask: 1,
   222  			},
   223  			wantStr: "0: from all to all lookup 0 mark 0x1 mask 0x1 proto unspec",
   224  		},
   225  		{
   226  			name: "main table",
   227  			rule: Rule{
   228  				Table: unix.RT_TABLE_MAIN,
   229  			},
   230  			wantStr: "0: from all to all lookup main proto unspec",
   231  		},
   232  	}
   233  	for _, tt := range tests {
   234  		if diff := cmp.Diff(tt.wantStr, tt.rule.String()); diff != "" {
   235  			t.Errorf("%s", diff)
   236  		}
   237  	}
   238  }
   239  
   240  func TestListRules(t *testing.T) {
   241  	testutils.PrivilegedTest(t)
   242  
   243  	testListRules4(t)
   244  	testListRules6(t)
   245  }
   246  
   247  func testListRules4(t *testing.T) {
   248  	_, fakeIP, _ := net.ParseCIDR("192.0.2.40/32")
   249  	_, fakeIP2, _ := net.ParseCIDR("192.0.2.60/32")
   250  
   251  	runListRules(t, netlink.FAMILY_V4, fakeIP, fakeIP2)
   252  }
   253  
   254  func testListRules6(t *testing.T) {
   255  	_, fakeIP, _ := net.ParseCIDR("fd44:7089:ff32:712b:4000::/64")
   256  	_, fakeIP2, _ := net.ParseCIDR("fd44:7089:ff32:712b:8000::/96")
   257  
   258  	runListRules(t, netlink.FAMILY_V6, fakeIP, fakeIP2)
   259  }
   260  
   261  func runListRules(t *testing.T, family int, fakeIP, fakeIP2 *net.IPNet) {
   262  	tests := []struct {
   263  		name       string
   264  		ruleFilter *Rule
   265  		preRun     func() *netlink.Rule // Creates sample rule harness
   266  		postRun    func(*netlink.Rule)  // Deletes sample rule harness
   267  		setupWant  func(*netlink.Rule) ([]netlink.Rule, bool)
   268  	}{
   269  		{
   270  			name:       "returns all rules",
   271  			ruleFilter: nil,
   272  			preRun:     func() *netlink.Rule { return nil },
   273  			postRun:    func(r *netlink.Rule) {},
   274  			setupWant: func(_ *netlink.Rule) ([]netlink.Rule, bool) {
   275  				defaultRules, _ := ListRules(family, nil)
   276  				return defaultRules, false
   277  			},
   278  		},
   279  		{
   280  			name:       "returns one rule filtered by From",
   281  			ruleFilter: &Rule{From: fakeIP},
   282  			preRun: func() *netlink.Rule {
   283  				r := netlink.NewRule()
   284  				r.Src = fakeIP
   285  				r.Family = family
   286  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   287  				r.Table = 1
   288  				addRule(t, r)
   289  				return r
   290  			},
   291  			postRun: func(r *netlink.Rule) { delRule(t, r) },
   292  			setupWant: func(r *netlink.Rule) ([]netlink.Rule, bool) {
   293  				return []netlink.Rule{*r}, false
   294  			},
   295  		},
   296  		{
   297  			name:       "returns one rule filtered by To",
   298  			ruleFilter: &Rule{To: fakeIP},
   299  			preRun: func() *netlink.Rule {
   300  				r := netlink.NewRule()
   301  				r.Dst = fakeIP
   302  				r.Family = family
   303  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   304  				r.Table = 1
   305  				addRule(t, r)
   306  				return r
   307  			},
   308  			postRun: func(r *netlink.Rule) { delRule(t, r) },
   309  			setupWant: func(r *netlink.Rule) ([]netlink.Rule, bool) {
   310  				return []netlink.Rule{*r}, false
   311  			},
   312  		},
   313  		{
   314  			name:       "returns two rules filtered by To",
   315  			ruleFilter: &Rule{To: fakeIP},
   316  			preRun: func() *netlink.Rule {
   317  				r := netlink.NewRule()
   318  				r.Dst = fakeIP
   319  				r.Family = family
   320  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   321  				r.Table = 1
   322  				addRule(t, r)
   323  
   324  				rc := *r // Create almost identical copy
   325  				rc.Src = fakeIP2
   326  				addRule(t, &rc)
   327  
   328  				return r
   329  			},
   330  			postRun: func(r *netlink.Rule) {
   331  				delRule(t, r)
   332  
   333  				rc := *r // Delete the almost identical copy
   334  				rc.Src = fakeIP2
   335  				delRule(t, &rc)
   336  			},
   337  			setupWant: func(r *netlink.Rule) ([]netlink.Rule, bool) {
   338  				rs := []netlink.Rule{}
   339  				rs = append(rs, *r)
   340  
   341  				rc := *r // Append the almost identical copy
   342  				rc.Src = fakeIP2
   343  				rs = append(rs, rc)
   344  
   345  				return rs, false
   346  			},
   347  		},
   348  		{
   349  			name:       "returns one rule filtered by From when two rules exist",
   350  			ruleFilter: &Rule{From: fakeIP2},
   351  			preRun: func() *netlink.Rule {
   352  				r := netlink.NewRule()
   353  				r.Dst = fakeIP
   354  				r.Family = family
   355  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   356  				r.Table = 1
   357  				addRule(t, r)
   358  
   359  				rc := *r // Create almost identical copy
   360  				rc.Src = fakeIP2
   361  				addRule(t, &rc)
   362  
   363  				return r
   364  			},
   365  			postRun: func(r *netlink.Rule) {
   366  				delRule(t, r)
   367  
   368  				rc := *r // Delete the almost identical copy
   369  				rc.Src = fakeIP2
   370  				delRule(t, &rc)
   371  			},
   372  			setupWant: func(r *netlink.Rule) ([]netlink.Rule, bool) {
   373  				rs := []netlink.Rule{}
   374  				// Do not append `r`
   375  
   376  				rc := *r // Append the almost identical copy
   377  				rc.Src = fakeIP2
   378  				rs = append(rs, rc)
   379  
   380  				return rs, false
   381  			},
   382  		},
   383  		{
   384  			name:       "returns rules with specific priority",
   385  			ruleFilter: &Rule{Priority: 5},
   386  			preRun: func() *netlink.Rule {
   387  				r := netlink.NewRule()
   388  				r.Src = fakeIP
   389  				r.Family = family
   390  				r.Priority = 5
   391  				r.Table = 1
   392  				addRule(t, r)
   393  
   394  				for i := 2; i < 5; i++ {
   395  					rc := *r // Create almost identical copy
   396  					rc.Table = i
   397  					addRule(t, &rc)
   398  				}
   399  
   400  				return r
   401  			},
   402  			postRun: func(r *netlink.Rule) {
   403  				delRule(t, r)
   404  
   405  				for i := 2; i < 5; i++ {
   406  					rc := *r // Delete the almost identical copy
   407  					rc.Table = i
   408  					delRule(t, &rc)
   409  				}
   410  			},
   411  			setupWant: func(r *netlink.Rule) ([]netlink.Rule, bool) {
   412  				rs := []netlink.Rule{}
   413  				rs = append(rs, *r)
   414  
   415  				for i := 2; i < 5; i++ {
   416  					rc := *r // Append the almost identical copy
   417  					rc.Table = i
   418  					rs = append(rs, rc)
   419  				}
   420  
   421  				return rs, false
   422  			},
   423  		},
   424  		{
   425  			name:       "returns rules filtered by Table",
   426  			ruleFilter: &Rule{Table: 199},
   427  			preRun: func() *netlink.Rule {
   428  				r := netlink.NewRule()
   429  				r.Src = fakeIP
   430  				r.Family = family
   431  				r.Priority = 1 // Must add priority otherwise it's auto-assigned
   432  				r.Table = 199
   433  				addRule(t, r)
   434  				return r
   435  			},
   436  			postRun: func(r *netlink.Rule) { delRule(t, r) },
   437  			setupWant: func(r *netlink.Rule) ([]netlink.Rule, bool) {
   438  				return []netlink.Rule{*r}, false
   439  			},
   440  		},
   441  		{
   442  			name:       "returns rules filtered by Mask",
   443  			ruleFilter: &Rule{Mask: 0x5},
   444  			preRun: func() *netlink.Rule {
   445  				r := netlink.NewRule()
   446  				r.Src = fakeIP
   447  				r.Family = family
   448  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   449  				r.Table = 1
   450  				r.Mask = 0x5
   451  				addRule(t, r)
   452  				return r
   453  			},
   454  			postRun: func(r *netlink.Rule) { delRule(t, r) },
   455  			setupWant: func(r *netlink.Rule) ([]netlink.Rule, bool) {
   456  				return []netlink.Rule{*r}, false
   457  			},
   458  		},
   459  		{
   460  			name:       "returns rules filtered by Mark",
   461  			ruleFilter: &Rule{Mark: 0xbb},
   462  			preRun: func() *netlink.Rule {
   463  				r := netlink.NewRule()
   464  				r.Src = fakeIP
   465  				r.Family = family
   466  				r.Priority = 1 // Must add priority, table, mask otherwise it's auto-assigned
   467  				r.Table = 1
   468  				r.Mask = 0xff
   469  				r.Mark = 0xbb
   470  				addRule(t, r)
   471  				return r
   472  			},
   473  			postRun: func(r *netlink.Rule) { delRule(t, r) },
   474  			setupWant: func(r *netlink.Rule) ([]netlink.Rule, bool) {
   475  				return []netlink.Rule{*r}, false
   476  			},
   477  		},
   478  	}
   479  
   480  	for _, tt := range tests {
   481  		t.Run(tt.name, func(t *testing.T) {
   482  			ns := netns.NewNetNS(t)
   483  			require.NoError(t, ns.Do(func() error {
   484  				rule := tt.preRun()
   485  				rules, err := ListRules(family, tt.ruleFilter)
   486  				tt.postRun(rule)
   487  
   488  				wantRules, wantErr := tt.setupWant(rule)
   489  
   490  				if diff := cmp.Diff(wantRules, rules); diff != "" {
   491  					t.Errorf("expected len: %d, got: %d\n%s\n", len(wantRules), len(rules), diff)
   492  				}
   493  				require.Equal(t, err != nil, wantErr)
   494  
   495  				return nil
   496  			}))
   497  		})
   498  	}
   499  }
   500  
   501  func addRule(tb testing.TB, r *netlink.Rule) {
   502  	if err := netlink.RuleAdd(r); err != nil {
   503  		tb.Logf("Unable to add rule: %v", err)
   504  	}
   505  }
   506  
   507  func delRule(tb testing.TB, r *netlink.Rule) {
   508  	if err := netlink.RuleDel(r); err != nil {
   509  		tb.Logf("Unable to delete rule: %v", err)
   510  	}
   511  }