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 }