Skip to content

Commit 0346cc8

Browse files
Check imds version to confirm if imds changes are available
1 parent 463514b commit 0346cc8

File tree

6 files changed

+654
-402
lines changed

6 files changed

+654
-402
lines changed

cns/fakes/imdsclientfake.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package fakes
88

99
import (
1010
"context"
11+
"net"
1112

1213
"github.com/Azure/azure-container-networking/cns/imds"
1314
"github.com/Azure/azure-container-networking/cns/wireserver"
@@ -59,19 +60,38 @@ func (m *MockIMDSClient) GetVMUniqueID(ctx context.Context) (string, error) {
5960
}
6061

6162
func (m *MockIMDSClient) GetNetworkInterfaces(ctx context.Context) ([]imds.NetworkInterface, error) {
62-
if ctx.Value(SimulateError) != nil {
63-
return nil, imds.ErrUnexpectedStatusCode
64-
}
63+
if ctx.Value(SimulateError) != nil {
64+
return nil, imds.ErrUnexpectedStatusCode
65+
}
6566

66-
// Return some mock network interfaces for testing
67-
return []imds.NetworkInterface{
68-
{
69-
InterfaceCompartmentID: "nc1",
70-
InterfaceCompartmentVersion: "1",
71-
},
72-
{
73-
InterfaceCompartmentID: "nc2",
74-
InterfaceCompartmentVersion: "2",
75-
},
76-
}, nil
67+
// Parse MAC addresses for testing
68+
macAddr1, _ := net.ParseMAC("00:15:5d:01:02:01")
69+
macAddr2, _ := net.ParseMAC("00:15:5d:01:02:02")
70+
71+
// Return some mock network interfaces for testing
72+
return []imds.NetworkInterface{
73+
{
74+
InterfaceCompartmentID: "nc1",
75+
MacAddress: imds.HardwareAddr(macAddr1),
76+
},
77+
{
78+
InterfaceCompartmentID: "nc2",
79+
MacAddress: imds.HardwareAddr(macAddr2),
80+
},
81+
}, nil
7782
}
83+
84+
func (m *MockIMDSClient) GetIMDSVersions(ctx context.Context) (*imds.APIVersionsResponse, error) {
85+
if ctx.Value(SimulateError) != nil {
86+
return nil, imds.ErrUnexpectedStatusCode
87+
}
88+
89+
// Return supported API versions including the expected one
90+
return &imds.APIVersionsResponse{
91+
APIVersions: []string{
92+
"2017-03-01",
93+
"2021-01-01",
94+
"2025-07-24",
95+
},
96+
}, nil
97+
}

cns/imds/client.go

Lines changed: 86 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package imds
66
import (
77
"context"
88
"encoding/json"
9+
"net"
910
"net/http"
1011
"net/url"
1112

@@ -44,15 +45,17 @@ func RetryAttempts(attempts uint) ClientOption {
4445
}
4546

4647
const (
47-
vmUniqueIDProperty = "vmId"
48-
imdsComputePath = "/metadata/instance/compute"
49-
imdsNetworkPath = "/metadata/instance/network"
50-
imdsAPIVersion = "api-version=2025-07-24"
51-
imdsFormatJSON = "format=json"
52-
metadataHeaderKey = "Metadata"
53-
metadataHeaderValue = "true"
54-
defaultRetryAttempts = 3
55-
defaultIMDSEndpoint = "http://169.254.169.254"
48+
vmUniqueIDProperty = "vmId"
49+
imdsComputePath = "/metadata/instance/compute"
50+
imdsNetworkPath = "/metadata/instance/network"
51+
imdsVersionsPath = "/metadata/versions"
52+
imdsDefaultAPIVersion = "api-version=2021-01-01"
53+
imdsNCDetailsVersion = "api-version=2025-07-24"
54+
imdsFormatJSON = "format=json"
55+
metadataHeaderKey = "Metadata"
56+
metadataHeaderValue = "true"
57+
defaultRetryAttempts = 3
58+
defaultIMDSEndpoint = "http://169.254.169.254"
5659
)
5760

5861
var (
@@ -80,7 +83,7 @@ func NewClient(opts ...ClientOption) *Client {
8083
func (c *Client) GetVMUniqueID(ctx context.Context) (string, error) {
8184
var vmUniqueID string
8285
err := retry.Do(func() error {
83-
computeDoc, err := c.getInstanceMetadata(ctx, imdsComputePath)
86+
computeDoc, err := c.getInstanceMetadata(ctx, imdsComputePath, imdsDefaultAPIVersion)
8487
if err != nil {
8588
return errors.Wrap(err, "error getting IMDS compute metadata")
8689
}
@@ -106,7 +109,7 @@ func (c *Client) GetVMUniqueID(ctx context.Context) (string, error) {
106109
func (c *Client) GetNetworkInterfaces(ctx context.Context) ([]NetworkInterface, error) {
107110
var networkData NetworkInterfaces
108111
err := retry.Do(func() error {
109-
networkInterfaces, err := c.getInstanceMetadata(ctx, imdsNetworkPath)
112+
networkInterfaces, err := c.getInstanceMetadata(ctx, imdsNetworkPath, imdsNCDetailsVersion)
110113
if err != nil {
111114
return errors.Wrap(err, "error getting IMDS network metadata")
112115
}
@@ -130,7 +133,7 @@ func (c *Client) GetNetworkInterfaces(ctx context.Context) ([]NetworkInterface,
130133
return networkData.Interface, nil
131134
}
132135

133-
func (c *Client) getInstanceMetadata(ctx context.Context, imdsMetadataPath string) (map[string]any, error) {
136+
func (c *Client) getInstanceMetadata(ctx context.Context, imdsMetadataPath string, imdsAPIVersion string) (map[string]any, error) {
134137
imdsRequestURL, err := url.JoinPath(c.config.endpoint, imdsMetadataPath)
135138
if err != nil {
136139
return nil, errors.Wrap(err, "unable to build path to IMDS metadata for path"+imdsMetadataPath)
@@ -162,14 +165,83 @@ func (c *Client) getInstanceMetadata(ctx context.Context, imdsMetadataPath strin
162165
return m, nil
163166
}
164167

168+
func (c *Client) GetIMDSVersions(ctx context.Context) (*APIVersionsResponse, error) {
169+
var versionsResp APIVersionsResponse
170+
err := retry.Do(func() error {
171+
// Build the URL for the versions endpoint
172+
imdsRequestURL, err := url.JoinPath(c.config.endpoint, imdsVersionsPath)
173+
if err != nil {
174+
return errors.Wrap(err, "unable to build path to IMDS versions endpoint")
175+
}
176+
177+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imdsRequestURL, http.NoBody)
178+
if err != nil {
179+
return errors.Wrap(err, "error building IMDS versions http request")
180+
}
181+
182+
req.Header.Add(metadataHeaderKey, metadataHeaderValue)
183+
resp, err := c.cli.Do(req)
184+
if err != nil {
185+
return errors.Wrap(err, "error querying IMDS versions API")
186+
}
187+
defer resp.Body.Close()
188+
189+
if resp.StatusCode != http.StatusOK {
190+
return errors.Wrapf(ErrUnexpectedStatusCode, "unexpected status code %d", resp.StatusCode)
191+
}
192+
193+
if err := json.NewDecoder(resp.Body).Decode(&versionsResp); err != nil {
194+
return errors.Wrap(err, "error decoding IMDS versions response as json")
195+
}
196+
197+
return nil
198+
}, retry.Context(ctx), retry.Attempts(c.config.retryAttempts), retry.DelayType(retry.BackOffDelay))
199+
200+
if err != nil {
201+
return nil, errors.Wrap(err, "exhausted retries querying IMDS versions")
202+
}
203+
204+
return &versionsResp, nil
205+
}
206+
207+
// Required for marshaling/unmarshaling of mac address
208+
type HardwareAddr net.HardwareAddr
209+
210+
func (h HardwareAddr) MarshalJSON() ([]byte, error) {
211+
return json.Marshal(net.HardwareAddr(h).String())
212+
}
213+
214+
func (h *HardwareAddr) UnmarshalJSON(data []byte) error {
215+
var s string
216+
if err := json.Unmarshal(data, &s); err != nil {
217+
return err
218+
}
219+
mac, err := net.ParseMAC(s)
220+
if err != nil {
221+
return err
222+
}
223+
*h = HardwareAddr(mac)
224+
return nil
225+
}
226+
227+
func (h HardwareAddr) String() string {
228+
return net.HardwareAddr(h).String()
229+
}
230+
165231
// NetworkInterface represents a network interface from IMDS
166232
type NetworkInterface struct {
167233
// IMDS returns compartment fields - these are mapped to NC ID and NC version
168-
InterfaceCompartmentID string `json:"interfaceCompartmentID,omitempty"`
169-
InterfaceCompartmentVersion string `json:"interfaceCompartmentVersion,omitempty"`
234+
MacAddress HardwareAddr `json:"macAddress"`
235+
InterfaceCompartmentID string `json:"interfaceCompartmentID,omitempty"`
170236
}
171237

172238
// NetworkInterfaces represents the network interfaces from IMDS
173239
type NetworkInterfaces struct {
174240
Interface []NetworkInterface `json:"interface"`
175241
}
242+
243+
244+
// APIVersionsResponse represents versions form IMDS
245+
type APIVersionsResponse struct {
246+
APIVersions []string `json:"apiVersions"`
247+
}

0 commit comments

Comments
 (0)