diff --git a/.changelog/11728.txt b/.changelog/11728.txt new file mode 100644 index 000000000000..f8026bba8061 --- /dev/null +++ b/.changelog/11728.txt @@ -0,0 +1,3 @@ +```release-note:bug +client: Fixed host network reserved port fingerprinting +``` diff --git a/client/fingerprint/network.go b/client/fingerprint/network.go index 1c77a7657b39..043c62e0eff1 100644 --- a/client/fingerprint/network.go +++ b/client/fingerprint/network.go @@ -168,10 +168,16 @@ func (f *NetworkFingerprint) createNodeNetworkResources(ifaces []net.Interface, } else { family = structs.NodeNetworkAF_IPv6 } + + alias := deriveAddressAlias(iface, ip, conf) newAddr := structs.NodeNetworkAddress{ Address: ip.String(), Family: family, - Alias: deriveAddressAlias(iface, ip, conf), + Alias: alias, + } + + if hostNetwork, ok := conf.HostNetworks[alias]; ok { + newAddr.ReservedPorts = hostNetwork.ReservedPorts } if newAddr.Alias != "" { diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index 5b55b87a6904..a4692a949e39 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -4,11 +4,13 @@ import ( "fmt" "net" "os" + "sort" "testing" "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/helper/testlog" "github.com/hashicorp/nomad/nomad/structs" + "github.com/stretchr/testify/require" ) // Set skipOnlineTestEnvVar to a non-empty value to skip network tests. Useful @@ -437,3 +439,93 @@ func TestNetworkFingerPrint_LinkLocal_Disallowed(t *testing.T) { t.Fatalf("should not apply attributes") } } + +func TestNetworkFingerPrint_HostNetworkReservedPorts(t *testing.T) { + testCases := []struct { + name string + hostNetworks map[string]*structs.ClientHostNetworkConfig + expected []string + }{ + { + name: "no host networks", + hostNetworks: map[string]*structs.ClientHostNetworkConfig{}, + expected: []string{""}, + }, + { + name: "no reserved ports", + hostNetworks: map[string]*structs.ClientHostNetworkConfig{ + "alias1": { + Name: "alias1", + Interface: "eth3", + CIDR: "169.254.155.20/32", + }, + "alias2": { + Name: "alias2", + Interface: "eth1", + CIDR: "100.64.0.10/10", + }, + "alias3": { + Name: "alias3", + Interface: "eth0", + CIDR: "100.64.0.11/10", + }, + }, + expected: []string{"", "", ""}, + }, + { + name: "reserved ports in some aliases", + hostNetworks: map[string]*structs.ClientHostNetworkConfig{ + "alias1": { + Name: "alias1", + Interface: "eth3", + CIDR: "169.254.155.20/32", + ReservedPorts: "22", + }, + "alias2": { + Name: "alias2", + Interface: "eth1", + CIDR: "100.64.0.10/10", + ReservedPorts: "80,3000-4000", + }, + "alias3": { + Name: "alias3", + Interface: "eth0", + CIDR: "100.64.0.11/10", + }, + }, + expected: []string{"22", "80,3000-4000", ""}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + f := &NetworkFingerprint{ + logger: testlog.HCLogger(t), + interfaceDetector: &NetworkInterfaceDetectorMultipleInterfaces{}, + } + node := &structs.Node{ + Attributes: make(map[string]string), + } + cfg := &config.Config{ + NetworkInterface: "eth3", + HostNetworks: tc.hostNetworks, + } + + request := &FingerprintRequest{Config: cfg, Node: node} + var response FingerprintResponse + err := f.Fingerprint(request, &response) + require.NoError(t, err) + + got := []string{} + for _, network := range response.NodeResources.NodeNetworks { + for _, address := range network.Addresses { + got = append(got, address.ReservedPorts) + } + } + + sort.Strings(tc.expected) + sort.Strings(got) + require.Equal(t, tc.expected, got, "host networks should match reserved ports") + }) + } +}