github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/fingerprint/network_test.go (about) 1 package fingerprint 2 3 import ( 4 "fmt" 5 "net" 6 "os" 7 "sort" 8 "testing" 9 10 "github.com/hashicorp/nomad/ci" 11 "github.com/hashicorp/nomad/client/config" 12 "github.com/hashicorp/nomad/helper/testlog" 13 "github.com/hashicorp/nomad/nomad/structs" 14 "github.com/stretchr/testify/require" 15 ) 16 17 // Set skipOnlineTestEnvVar to a non-empty value to skip network tests. Useful 18 // when working offline (e.g. an airplane). 19 const skipOnlineTestsEnvVar = "TEST_NOMAD_SKIP_ONLINE_NET" 20 21 var ( 22 lo = net.Interface{ 23 Index: 2, 24 MTU: 65536, 25 Name: "lo", 26 HardwareAddr: []byte{23, 43, 54, 54}, 27 Flags: net.FlagUp | net.FlagLoopback, 28 } 29 30 eth0 = net.Interface{ 31 Index: 3, 32 MTU: 1500, 33 Name: "eth0", 34 HardwareAddr: []byte{23, 44, 54, 67}, 35 Flags: net.FlagUp | net.FlagMulticast | net.FlagBroadcast, 36 } 37 38 eth1 = net.Interface{ 39 Index: 4, 40 MTU: 1500, 41 Name: "eth1", 42 HardwareAddr: []byte{23, 44, 54, 69}, 43 Flags: net.FlagMulticast | net.FlagBroadcast, 44 } 45 46 eth2 = net.Interface{ 47 Index: 4, 48 MTU: 1500, 49 Name: "eth2", 50 HardwareAddr: []byte{23, 44, 54, 70}, 51 Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, 52 } 53 54 // One link local address 55 eth3 = net.Interface{ 56 Index: 4, 57 MTU: 1500, 58 Name: "eth3", 59 HardwareAddr: []byte{23, 44, 54, 71}, 60 Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, 61 } 62 63 // One link local address and one globally routable address 64 eth4 = net.Interface{ 65 Index: 4, 66 MTU: 1500, 67 Name: "eth4", 68 HardwareAddr: []byte{23, 44, 54, 72}, 69 Flags: net.FlagUp | net.FlagBroadcast | net.FlagMulticast, 70 } 71 ) 72 73 // A fake network detector which returns no devices 74 type NetworkInterfaceDetectorNoDevices struct { 75 } 76 77 func (f *NetworkInterfaceDetectorNoDevices) Interfaces() ([]net.Interface, error) { 78 return make([]net.Interface, 0), nil 79 } 80 81 func (f *NetworkInterfaceDetectorNoDevices) InterfaceByName(name string) (*net.Interface, error) { 82 return nil, fmt.Errorf("Device with name %s doesn't exist", name) 83 } 84 85 func (f *NetworkInterfaceDetectorNoDevices) Addrs(intf *net.Interface) ([]net.Addr, error) { 86 return nil, fmt.Errorf("No interfaces found for device %v", intf.Name) 87 } 88 89 // A fake network detector which returns only loopback 90 type NetworkInterfaceDetectorOnlyLo struct { 91 } 92 93 func (n *NetworkInterfaceDetectorOnlyLo) Interfaces() ([]net.Interface, error) { 94 return []net.Interface{lo}, nil 95 } 96 97 func (n *NetworkInterfaceDetectorOnlyLo) InterfaceByName(name string) (*net.Interface, error) { 98 if name == "lo" { 99 return &lo, nil 100 } 101 102 return nil, fmt.Errorf("No device with name %v found", name) 103 } 104 105 func (n *NetworkInterfaceDetectorOnlyLo) Addrs(intf *net.Interface) ([]net.Addr, error) { 106 if intf.Name == "lo" { 107 _, ipnet1, _ := net.ParseCIDR("127.0.0.1/8") 108 _, ipnet2, _ := net.ParseCIDR("2001:DB8::/48") 109 return []net.Addr{ipnet1, ipnet2}, nil 110 } 111 112 return nil, fmt.Errorf("Can't find addresses for device: %v", intf.Name) 113 } 114 115 // A fake network detector which simulates the presence of multiple interfaces 116 type NetworkInterfaceDetectorMultipleInterfaces struct { 117 } 118 119 func (n *NetworkInterfaceDetectorMultipleInterfaces) Interfaces() ([]net.Interface, error) { 120 // Return link local first to test we don't prefer it 121 return []net.Interface{lo, eth0, eth1, eth2, eth3, eth4}, nil 122 } 123 124 func (n *NetworkInterfaceDetectorMultipleInterfaces) InterfaceByName(name string) (*net.Interface, error) { 125 var intf *net.Interface 126 switch name { 127 case "lo": 128 intf = &lo 129 case "eth0": 130 intf = ð0 131 case "eth1": 132 intf = ð1 133 case "eth2": 134 intf = ð2 135 case "eth3": 136 intf = ð3 137 case "eth4": 138 intf = ð4 139 } 140 if intf != nil { 141 return intf, nil 142 } 143 144 return nil, fmt.Errorf("No device with name %v found", name) 145 } 146 147 func (n *NetworkInterfaceDetectorMultipleInterfaces) Addrs(intf *net.Interface) ([]net.Addr, error) { 148 if intf.Name == "lo" { 149 _, ipnet1, _ := net.ParseCIDR("127.0.0.1/8") 150 _, ipnet2, _ := net.ParseCIDR("2001:DB8::/48") 151 return []net.Addr{ipnet1, ipnet2}, nil 152 } 153 154 if intf.Name == "eth0" { 155 _, ipnet1, _ := net.ParseCIDR("100.64.0.11/10") 156 _, ipnet2, _ := net.ParseCIDR("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64") 157 ipAddr, _ := net.ResolveIPAddr("ip6", "fe80::140c:9579:8037:f565") 158 return []net.Addr{ipnet1, ipnet2, ipAddr}, nil 159 } 160 161 if intf.Name == "eth1" { 162 _, ipnet1, _ := net.ParseCIDR("100.64.0.10/10") 163 _, ipnet2, _ := net.ParseCIDR("2003:DB8::/48") 164 return []net.Addr{ipnet1, ipnet2}, nil 165 } 166 167 if intf.Name == "eth2" { 168 return []net.Addr{}, nil 169 } 170 171 if intf.Name == "eth3" { 172 _, ipnet1, _ := net.ParseCIDR("169.254.155.20/32") 173 return []net.Addr{ipnet1}, nil 174 } 175 176 if intf.Name == "eth4" { 177 _, ipnet1, _ := net.ParseCIDR("169.254.155.20/32") 178 _, ipnet2, _ := net.ParseCIDR("100.64.0.10/10") 179 return []net.Addr{ipnet1, ipnet2}, nil 180 } 181 182 return nil, fmt.Errorf("Can't find addresses for device: %v", intf.Name) 183 } 184 185 func TestNetworkFingerprint_basic(t *testing.T) { 186 ci.Parallel(t) 187 188 if v := os.Getenv(skipOnlineTestsEnvVar); v != "" { 189 t.Skipf("Environment variable %+q not empty, skipping test", skipOnlineTestsEnvVar) 190 } 191 192 f := &NetworkFingerprint{logger: testlog.HCLogger(t), interfaceDetector: &DefaultNetworkInterfaceDetector{}} 193 node := &structs.Node{ 194 Attributes: make(map[string]string), 195 } 196 cfg := &config.Config{NetworkSpeed: 101} 197 198 request := &FingerprintRequest{Config: cfg, Node: node} 199 var response FingerprintResponse 200 err := f.Fingerprint(request, &response) 201 if err != nil { 202 t.Fatalf("err: %v", err) 203 } 204 205 if !response.Detected { 206 t.Fatalf("expected response to be applicable") 207 } 208 209 attributes := response.Attributes 210 if len(attributes) == 0 { 211 t.Fatalf("should apply (HINT: working offline? Set env %q=y", skipOnlineTestsEnvVar) 212 } 213 214 assertNodeAttributeContains(t, attributes, "unique.network.ip-address") 215 216 ip := attributes["unique.network.ip-address"] 217 match := net.ParseIP(ip) 218 if match == nil { 219 t.Fatalf("Bad IP match: %s", ip) 220 } 221 222 if response.Resources == nil || len(response.Resources.Networks) == 0 { 223 t.Fatal("Expected to find Network Resources") 224 } 225 226 // Test at least the first Network Resource 227 net := response.Resources.Networks[0] 228 if net.IP == "" { 229 t.Fatal("Expected Network Resource to not be empty") 230 } 231 if net.CIDR == "" { 232 t.Fatal("Expected Network Resource to have a CIDR") 233 } 234 if net.Device == "" { 235 t.Fatal("Expected Network Resource to have a Device Name") 236 } 237 if net.MBits != 101 { 238 t.Fatalf("Expected Network Resource to have bandwidth %d; got %d", 101, net.MBits) 239 } 240 } 241 242 func TestNetworkFingerprint_default_device_absent(t *testing.T) { 243 ci.Parallel(t) 244 245 f := &NetworkFingerprint{logger: testlog.HCLogger(t), interfaceDetector: &NetworkInterfaceDetectorOnlyLo{}} 246 node := &structs.Node{ 247 Attributes: make(map[string]string), 248 } 249 cfg := &config.Config{NetworkSpeed: 100, NetworkInterface: "eth0"} 250 251 request := &FingerprintRequest{Config: cfg, Node: node} 252 var response FingerprintResponse 253 err := f.Fingerprint(request, &response) 254 if err == nil { 255 t.Fatalf("err: %v", err) 256 } 257 258 if response.Detected { 259 t.Fatalf("expected response to not be applicable") 260 } 261 262 if len(response.Attributes) != 0 { 263 t.Fatalf("attributes should be zero but instead are: %v", response.Attributes) 264 } 265 } 266 267 func TestNetworkFingerPrint_default_device(t *testing.T) { 268 ci.Parallel(t) 269 270 f := &NetworkFingerprint{logger: testlog.HCLogger(t), interfaceDetector: &NetworkInterfaceDetectorOnlyLo{}} 271 node := &structs.Node{ 272 Attributes: make(map[string]string), 273 } 274 cfg := &config.Config{NetworkSpeed: 100, NetworkInterface: "lo"} 275 276 request := &FingerprintRequest{Config: cfg, Node: node} 277 var response FingerprintResponse 278 err := f.Fingerprint(request, &response) 279 if err != nil { 280 t.Fatalf("err: %v", err) 281 } 282 283 if !response.Detected { 284 t.Fatalf("expected response to be applicable") 285 } 286 287 attributes := response.Attributes 288 if len(attributes) == 0 { 289 t.Fatalf("should apply") 290 } 291 292 assertNodeAttributeContains(t, attributes, "unique.network.ip-address") 293 294 ip := attributes["unique.network.ip-address"] 295 match := net.ParseIP(ip) 296 if match == nil { 297 t.Fatalf("Bad IP match: %s", ip) 298 } 299 300 if response.Resources == nil || len(response.Resources.Networks) == 0 { 301 t.Fatal("Expected to find Network Resources") 302 } 303 304 // Test at least the first Network Resource 305 net := response.Resources.Networks[0] 306 if net.IP == "" { 307 t.Fatal("Expected Network Resource to not be empty") 308 } 309 if net.CIDR == "" { 310 t.Fatal("Expected Network Resource to have a CIDR") 311 } 312 if net.Device == "" { 313 t.Fatal("Expected Network Resource to have a Device Name") 314 } 315 if net.MBits == 0 { 316 t.Fatal("Expected Network Resource to have a non-zero bandwidth") 317 } 318 } 319 320 func TestNetworkFingerPrint_LinkLocal_Allowed(t *testing.T) { 321 ci.Parallel(t) 322 323 f := &NetworkFingerprint{logger: testlog.HCLogger(t), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}} 324 node := &structs.Node{ 325 Attributes: make(map[string]string), 326 } 327 cfg := &config.Config{NetworkSpeed: 100, NetworkInterface: "eth3"} 328 329 request := &FingerprintRequest{Config: cfg, Node: node} 330 var response FingerprintResponse 331 err := f.Fingerprint(request, &response) 332 if err != nil { 333 t.Fatalf("err: %v", err) 334 } 335 336 if !response.Detected { 337 t.Fatalf("expected response to be applicable") 338 } 339 340 attributes := response.Attributes 341 assertNodeAttributeContains(t, attributes, "unique.network.ip-address") 342 343 ip := attributes["unique.network.ip-address"] 344 match := net.ParseIP(ip) 345 if match == nil { 346 t.Fatalf("Bad IP match: %s", ip) 347 } 348 349 if response.Resources == nil || len(response.Resources.Networks) == 0 { 350 t.Fatal("Expected to find Network Resources") 351 } 352 353 // Test at least the first Network Resource 354 net := response.Resources.Networks[0] 355 if net.IP == "" { 356 t.Fatal("Expected Network Resource to not be empty") 357 } 358 if net.CIDR == "" { 359 t.Fatal("Expected Network Resource to have a CIDR") 360 } 361 if net.Device == "" { 362 t.Fatal("Expected Network Resource to have a Device Name") 363 } 364 if net.MBits == 0 { 365 t.Fatal("Expected Network Resource to have a non-zero bandwidth") 366 } 367 } 368 369 func TestNetworkFingerPrint_LinkLocal_Allowed_MixedIntf(t *testing.T) { 370 ci.Parallel(t) 371 372 f := &NetworkFingerprint{logger: testlog.HCLogger(t), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}} 373 node := &structs.Node{ 374 Attributes: make(map[string]string), 375 } 376 cfg := &config.Config{NetworkSpeed: 100, NetworkInterface: "eth4"} 377 378 request := &FingerprintRequest{Config: cfg, Node: node} 379 var response FingerprintResponse 380 err := f.Fingerprint(request, &response) 381 if err != nil { 382 t.Fatalf("err: %v", err) 383 } 384 385 if !response.Detected { 386 t.Fatalf("expected response to be applicable") 387 } 388 389 attributes := response.Attributes 390 if len(attributes) == 0 { 391 t.Fatalf("should apply attributes") 392 } 393 394 assertNodeAttributeContains(t, attributes, "unique.network.ip-address") 395 396 ip := attributes["unique.network.ip-address"] 397 match := net.ParseIP(ip) 398 if match == nil { 399 t.Fatalf("Bad IP match: %s", ip) 400 } 401 402 if response.Resources == nil || len(response.Resources.Networks) == 0 { 403 t.Fatal("Expected to find Network Resources") 404 } 405 406 // Test at least the first Network Resource 407 net := response.Resources.Networks[0] 408 if net.IP == "" { 409 t.Fatal("Expected Network Resource to not be empty") 410 } 411 if net.IP == "169.254.155.20" { 412 t.Fatalf("expected non-link local address; got %v", net.IP) 413 } 414 if net.CIDR == "" { 415 t.Fatal("Expected Network Resource to have a CIDR") 416 } 417 if net.Device == "" { 418 t.Fatal("Expected Network Resource to have a Device Name") 419 } 420 if net.MBits == 0 { 421 t.Fatal("Expected Network Resource to have a non-zero bandwidth") 422 } 423 } 424 425 func TestNetworkFingerPrint_LinkLocal_Disallowed(t *testing.T) { 426 ci.Parallel(t) 427 428 f := &NetworkFingerprint{logger: testlog.HCLogger(t), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}} 429 node := &structs.Node{ 430 Attributes: make(map[string]string), 431 } 432 cfg := &config.Config{ 433 NetworkSpeed: 100, 434 NetworkInterface: "eth3", 435 Options: map[string]string{ 436 networkDisallowLinkLocalOption: "true", 437 }, 438 } 439 440 request := &FingerprintRequest{Config: cfg, Node: node} 441 var response FingerprintResponse 442 err := f.Fingerprint(request, &response) 443 if err != nil { 444 t.Fatalf("err: %v", err) 445 } 446 447 if !response.Detected { 448 t.Fatalf("expected response to be applicable") 449 } 450 451 if len(response.Attributes) != 0 { 452 t.Fatalf("should not apply attributes") 453 } 454 } 455 456 func TestNetworkFingerPrint_MultipleAliases(t *testing.T) { 457 ci.Parallel(t) 458 459 f := &NetworkFingerprint{logger: testlog.HCLogger(t), interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}} 460 node := &structs.Node{ 461 Attributes: make(map[string]string), 462 } 463 cfg := &config.Config{ 464 NetworkSpeed: 100, 465 NetworkInterface: "eth3", 466 HostNetworks: map[string]*structs.ClientHostNetworkConfig{ 467 "alias1": { 468 Name: "alias1", 469 Interface: "eth3", 470 CIDR: "169.254.155.20/32", 471 }, 472 "alias2": { 473 Name: "alias2", 474 Interface: "eth3", 475 CIDR: "169.254.155.20/32", 476 }, 477 "alias3": { 478 Name: "alias3", 479 Interface: "eth0", 480 CIDR: "100.64.0.11/10", 481 }, 482 }, 483 } 484 485 request := &FingerprintRequest{Config: cfg, Node: node} 486 var response FingerprintResponse 487 err := f.Fingerprint(request, &response) 488 require.NoError(t, err) 489 490 aliases := []string{} 491 for _, network := range response.NodeResources.NodeNetworks { 492 for _, address := range network.Addresses { 493 aliases = append(aliases, address.Alias) 494 } 495 } 496 expected := []string{} 497 for alias := range cfg.HostNetworks { 498 expected = append(expected, alias) 499 } 500 sort.Strings(expected) 501 sort.Strings(aliases) 502 require.Equal(t, expected, aliases, "host networks should match aliases") 503 } 504 505 func TestNetworkFingerPrint_HostNetworkReservedPorts(t *testing.T) { 506 ci.Parallel(t) 507 508 testCases := []struct { 509 name string 510 hostNetworks map[string]*structs.ClientHostNetworkConfig 511 expected []string 512 }{ 513 { 514 name: "no host networks", 515 hostNetworks: map[string]*structs.ClientHostNetworkConfig{}, 516 expected: []string{""}, 517 }, 518 { 519 name: "no reserved ports", 520 hostNetworks: map[string]*structs.ClientHostNetworkConfig{ 521 "alias1": { 522 Name: "alias1", 523 Interface: "eth3", 524 CIDR: "169.254.155.20/32", 525 }, 526 "alias2": { 527 Name: "alias2", 528 Interface: "eth3", 529 CIDR: "169.254.155.20/32", 530 }, 531 "alias3": { 532 Name: "alias3", 533 Interface: "eth0", 534 CIDR: "100.64.0.11/10", 535 }, 536 }, 537 expected: []string{"", "", ""}, 538 }, 539 { 540 name: "reserved ports in some aliases", 541 hostNetworks: map[string]*structs.ClientHostNetworkConfig{ 542 "alias1": { 543 Name: "alias1", 544 Interface: "eth3", 545 CIDR: "169.254.155.20/32", 546 ReservedPorts: "22", 547 }, 548 "alias2": { 549 Name: "alias2", 550 Interface: "eth3", 551 CIDR: "169.254.155.20/32", 552 ReservedPorts: "80,3000-4000", 553 }, 554 "alias3": { 555 Name: "alias3", 556 Interface: "eth0", 557 CIDR: "100.64.0.11/10", 558 }, 559 }, 560 expected: []string{"22", "80,3000-4000", ""}, 561 }, 562 } 563 564 for _, tc := range testCases { 565 t.Run(tc.name, func(t *testing.T) { 566 f := &NetworkFingerprint{ 567 logger: testlog.HCLogger(t), 568 interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}, 569 } 570 node := &structs.Node{ 571 Attributes: make(map[string]string), 572 } 573 cfg := &config.Config{ 574 NetworkInterface: "eth3", 575 HostNetworks: tc.hostNetworks, 576 } 577 578 request := &FingerprintRequest{Config: cfg, Node: node} 579 var response FingerprintResponse 580 err := f.Fingerprint(request, &response) 581 require.NoError(t, err) 582 583 got := []string{} 584 for _, network := range response.NodeResources.NodeNetworks { 585 for _, address := range network.Addresses { 586 got = append(got, address.ReservedPorts) 587 } 588 } 589 590 sort.Strings(tc.expected) 591 sort.Strings(got) 592 require.Equal(t, tc.expected, got, "host networks should match reserved ports") 593 }) 594 } 595 }