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 = &eth0
   131  	case "eth1":
   132  		intf = &eth1
   133  	case "eth2":
   134  		intf = &eth2
   135  	case "eth3":
   136  		intf = &eth3
   137  	case "eth4":
   138  		intf = &eth4
   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  }