From 15fc88770701e30fc8d1a85a6984df9e016bd6cd Mon Sep 17 00:00:00 2001 From: Keith Nguyen Date: Mon, 17 Nov 2025 16:47:15 +0000 Subject: [PATCH 1/4] feat: add support for windows MAC hex dump --- cns/imds/client.go | 25 ++++++++++++++++++++++++- cns/imds/client_test.go | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cns/imds/client.go b/cns/imds/client.go index ac06e6d8a3..662df9ba26 100644 --- a/cns/imds/client.go +++ b/cns/imds/client.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "net/url" + "strings" "github.com/avast/retry-go/v4" "github.com/pkg/errors" @@ -218,7 +219,8 @@ func (h *HardwareAddr) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &s); err != nil { return errors.Wrap(err, "failed to unmarshal JSON data") } - mac, err := net.ParseMAC(s) + + mac, err := parseMacAddress(s) if err != nil { return errors.Wrap(err, "failed to parse MAC address") } @@ -226,6 +228,27 @@ func (h *HardwareAddr) UnmarshalJSON(data []byte) error { return nil } +// parseMacAddress is a wrapper around net.ParseMAC to handle Windows MAC address. Windows MAC addresse is a pure hex +// dump without delimiter, so we need to add delimiters. This happens when CNS gets MAC address from IMDS. +func parseMacAddress(s string) (net.HardwareAddr, error) { + if !strings.ContainsAny(s, ":-.") && len(s)%2 == 0 { + var sb strings.Builder + for i := 0; i < len(s); i += 2 { + if i > 0 { + sb.WriteByte(':') + } + sb.WriteString(s[i : i+2]) + } + s = sb.String() + } + + mac, err := net.ParseMAC(s) + if err != nil { + return nil, errors.Wrap(err, "failed to parse MAC address") + } + return mac, nil +} + func (h *HardwareAddr) String() string { return net.HardwareAddr(*h).String() } diff --git a/cns/imds/client_test.go b/cns/imds/client_test.go index ac97ba5251..73868e74af 100644 --- a/cns/imds/client_test.go +++ b/cns/imds/client_test.go @@ -111,7 +111,7 @@ func TestGetNetworkInterfaces(t *testing.T) { }, { "interfaceCompartmentID": "", - "macAddress": "00:00:5e:00:53:02" + "macAddress": "00005e005302" } ] }`) From 4e52d5834af04550bc8b968621ce5a46a6263d05 Mon Sep 17 00:00:00 2001 From: Keith Nguyen Date: Mon, 17 Nov 2025 17:07:35 +0000 Subject: [PATCH 2/4] test: invalid MAC address length --- cns/imds/client.go | 25 +++++++++++++------------ cns/imds/client_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/cns/imds/client.go b/cns/imds/client.go index 662df9ba26..f72acaccc6 100644 --- a/cns/imds/client.go +++ b/cns/imds/client.go @@ -46,17 +46,18 @@ func RetryAttempts(attempts uint) ClientOption { } const ( - vmUniqueIDProperty = "vmId" - imdsComputePath = "/metadata/instance/compute" - imdsNetworkPath = "/metadata/instance/network" - imdsVersionsPath = "/metadata/versions" - imdsDefaultAPIVersion = "api-version=2021-01-01" - imdsNCDetailsVersion = "api-version=2025-07-24" - imdsFormatJSON = "format=json" - metadataHeaderKey = "Metadata" - metadataHeaderValue = "true" - defaultRetryAttempts = 3 - defaultIMDSEndpoint = "http://169.254.169.254" + vmUniqueIDProperty = "vmId" + imdsComputePath = "/metadata/instance/compute" + imdsNetworkPath = "/metadata/instance/network" + imdsVersionsPath = "/metadata/versions" + imdsDefaultAPIVersion = "api-version=2021-01-01" + imdsNCDetailsVersion = "api-version=2025-07-24" + imdsMACAddressStringLength = 12 // 6 bytes in hex equals 12 characters + imdsFormatJSON = "format=json" + metadataHeaderKey = "Metadata" + metadataHeaderValue = "true" + defaultRetryAttempts = 3 + defaultIMDSEndpoint = "http://169.254.169.254" ) var ( @@ -231,7 +232,7 @@ func (h *HardwareAddr) UnmarshalJSON(data []byte) error { // parseMacAddress is a wrapper around net.ParseMAC to handle Windows MAC address. Windows MAC addresse is a pure hex // dump without delimiter, so we need to add delimiters. This happens when CNS gets MAC address from IMDS. func parseMacAddress(s string) (net.HardwareAddr, error) { - if !strings.ContainsAny(s, ":-.") && len(s)%2 == 0 { + if !strings.ContainsAny(s, ":-.") && len(s) == imdsMACAddressStringLength { var sb strings.Builder for i := 0; i < len(s); i += 2 { if i > 0 { diff --git a/cns/imds/client_test.go b/cns/imds/client_test.go index 73868e74af..701b72b6d8 100644 --- a/cns/imds/client_test.go +++ b/cns/imds/client_test.go @@ -160,6 +160,39 @@ func TestGetNetworkInterfaces(t *testing.T) { assert.NotEqual(t, firstMAC.String(), secondMAC.String(), "MAC addresses should be different") } +func TestGetNetworkInterfacesInvalidMAC(t *testing.T) { + networkInterfaces := []byte(`{ + "interface": [ + { + "interfaceCompartmentID": "nc-12345-67890", + "macAddress": "00005e00530" + }, + ] + }`) + + mockIMDSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // request header "Metadata: true" must be present + metadataHeader := r.Header.Get("Metadata") + assert.Equal(t, "true", metadataHeader) + + // verify path is network metadata + assert.Contains(t, r.URL.Path, "/metadata/instance/network") + + w.WriteHeader(http.StatusOK) + _, writeErr := w.Write(networkInterfaces) + if writeErr != nil { + t.Errorf("error writing response: %v", writeErr) + return + } + })) + defer mockIMDSServer.Close() + + imdsClient := imds.NewClient(imds.Endpoint(mockIMDSServer.URL)) + interfaces, err := imdsClient.GetNetworkInterfaces(context.Background()) + require.Error(t, err, "expected error for invalid MAC address") + require.Nil(t, interfaces, "expected nil interfaces on error") +} + func TestGetNetworkInterfacesInvalidEndpoint(t *testing.T) { imdsClient := imds.NewClient(imds.Endpoint(string([]byte{0x7f})), imds.RetryAttempts(1)) _, err := imdsClient.GetNetworkInterfaces(context.Background()) From fb1b5bbefcdd671626d20809d748bc8cd3f8112d Mon Sep 17 00:00:00 2001 From: Keith Nguyen Date: Mon, 17 Nov 2025 17:13:23 +0000 Subject: [PATCH 3/4] fix: typo --- cns/imds/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cns/imds/client.go b/cns/imds/client.go index f72acaccc6..57ec60303d 100644 --- a/cns/imds/client.go +++ b/cns/imds/client.go @@ -229,7 +229,7 @@ func (h *HardwareAddr) UnmarshalJSON(data []byte) error { return nil } -// parseMacAddress is a wrapper around net.ParseMAC to handle Windows MAC address. Windows MAC addresse is a pure hex +// parseMacAddress is a wrapper around net.ParseMAC to handle Windows MAC address. Windows MAC address is a pure hex // dump without delimiter, so we need to add delimiters. This happens when CNS gets MAC address from IMDS. func parseMacAddress(s string) (net.HardwareAddr, error) { if !strings.ContainsAny(s, ":-.") && len(s) == imdsMACAddressStringLength { From 3088ddaa6f621f630696169d0e8dba66baddbe23 Mon Sep 17 00:00:00 2001 From: Keith Nguyen Date: Mon, 17 Nov 2025 17:15:19 +0000 Subject: [PATCH 4/4] fix: comment --- cns/imds/client_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cns/imds/client_test.go b/cns/imds/client_test.go index 701b72b6d8..263a5276f0 100644 --- a/cns/imds/client_test.go +++ b/cns/imds/client_test.go @@ -165,17 +165,15 @@ func TestGetNetworkInterfacesInvalidMAC(t *testing.T) { "interface": [ { "interfaceCompartmentID": "nc-12345-67890", - "macAddress": "00005e00530" + "macAddress": "00005e00530" // incorrect windows MAC address length }, ] }`) mockIMDSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // request header "Metadata: true" must be present metadataHeader := r.Header.Get("Metadata") assert.Equal(t, "true", metadataHeader) - // verify path is network metadata assert.Contains(t, r.URL.Path, "/metadata/instance/network") w.WriteHeader(http.StatusOK)