Skip to content

Prefix on nicv6 support #3658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion azure-ipam/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (p *IPAMPlugin) CmdAdd(args *cniSkel.CmdArgs) error {
p.logger.Debug("Received CNS IP config response", zap.Any("response", resp))

// Get Pod IP and gateway IP from ip config response
podIPNet, err := ipconfig.ProcessIPConfigsResp(resp)
podIPNet, gatewayIP, err := ipconfig.ProcessIPConfigsResp(resp)
if err != nil {
p.logger.Error("Failed to interpret CNS IPConfigResponse", zap.Error(err), zap.Any("response", resp))
return cniTypes.NewError(ErrProcessIPConfigResponse, err.Error(), "failed to interpret CNS IPConfigResponse")
Expand All @@ -136,9 +136,33 @@ func (p *IPAMPlugin) CmdAdd(args *cniSkel.CmdArgs) error {
Mask: net.CIDRMask(ipNet.Bits(), 128), // nolint
}
}
ipConfig.Gateway = (*gatewayIP)[i]
cniResult.IPs[i] = ipConfig
}

cniResult.Interfaces = []*types100.Interface{}
seenInterfaces := map[string]bool{}

for _, podIPInfo := range resp.PodIPInfo {
// Skip if interface already seen
// This is to avoid duplicate interfaces in the result
// Deduplication is necessary because there is one podIPInfo entry for each IP family(IPv4 and IPv6), and both may point to the same interface or if multiple interfaces are assigned to the same pod
if podIPInfo.MacAddress == "" || seenInterfaces[podIPInfo.MacAddress] {
continue
}

infMac, err := net.ParseMAC(podIPInfo.MacAddress)
if err != nil {
p.logger.Error("Failed to parse interface MAC address", zap.Error(err), zap.String("macAddress", podIPInfo.MacAddress))
return cniTypes.NewError(cniTypes.ErrUnsupportedField, err.Error(), "failed to parse interface MAC address")
}

cniResult.Interfaces = append(cniResult.Interfaces, &types100.Interface{
Mac: infMac.String(),
})
seenInterfaces[podIPInfo.MacAddress] = true
}

Comment on lines +154 to +165
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add UT for this case with dualstack ips?

Copy link
Author

@NihaNallappagari NihaNallappagari Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added UT for dual stack scenarios

// Get versioned result
versionedCniResult, err := cniResult.GetAsVersion(nwCfg.CNIVersion)
if err != nil {
Expand Down
195 changes: 190 additions & 5 deletions azure-ipam/ipam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,41 @@ func (c *MockCNSClient) RequestIPAddress(ctx context.Context, ipconfig cns.IPCon
},
}
return result, nil
case "nilGateway":
result := &cns.IPConfigResponse{
PodIpInfo: cns.PodIpInfo{
PodIPConfig: cns.IPSubnet{
IPAddress: "10.0.1.10",
PrefixLength: 24,
},
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "10.0.1.0",
PrefixLength: 24,
},
DNSServers: nil,
GatewayIPAddress: "", // nil/empty gateway
},
HostPrimaryIPInfo: cns.HostIPInfo{
Gateway: "",
PrimaryIP: "10.0.0.1",
Subnet: "10.0.0.0/24",
},
},
Response: cns.Response{
ReturnCode: 0,
Message: "",
},
}
return result, nil
default:
result := &cns.IPConfigResponse{
PodIpInfo: cns.PodIpInfo{
PodIPConfig: cns.IPSubnet{
IPAddress: "10.0.1.10",
PrefixLength: 24,
},
MacAddress: "00:11:22:33:44:55",
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "10.0.1.0",
Expand Down Expand Up @@ -92,11 +120,61 @@ func (c *MockCNSClient) RequestIPs(ctx context.Context, ipconfig cns.IPConfigsRe
switch ipconfig.InfraContainerID {
case "failRequestCNSArgs":
return nil, errFoo
case "happyArgsSingle", "failProcessCNSRespSingleIP", "failRequestCNSArgsSingleIP":
case "happyArgsSingle", "failProcessCNSRespSingleIP", "failRequestCNSArgsSingleIP", "nilGateway":
e := &client.CNSClientError{}
e.Code = types.UnsupportedAPI
e.Err = errUnsupportedAPI
return nil, e
case "happyArgsDual":
result := &cns.IPConfigsResponse{
PodIPInfo: []cns.PodIpInfo{
{
PodIPConfig: cns.IPSubnet{
IPAddress: "10.0.1.10",
PrefixLength: 24,
},
MacAddress: "00:11:22:33:44:55",
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "10.0.1.0",
PrefixLength: 24,
},
DNSServers: nil,
GatewayIPAddress: "10.0.0.1",
},
HostPrimaryIPInfo: cns.HostIPInfo{
Gateway: "10.0.0.1",
PrimaryIP: "10.0.0.1",
Subnet: "10.0.0.0/24",
},
},
{
PodIPConfig: cns.IPSubnet{
IPAddress: "fd11:1234::1",
PrefixLength: 120,
},
MacAddress: "00:11:22:33:44:55", // Same MAC for dual-stack scenario
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "fd11:1234::",
PrefixLength: 120,
},
DNSServers: nil,
GatewayIPv6Address: "fe80::1234:5678:9abc",
},
HostPrimaryIPInfo: cns.HostIPInfo{
Gateway: "fe80::1234:5678:9abc",
PrimaryIP: "fe80::1234:5678:9abc",
Subnet: "fd11:1234::/120",
},
},
},
Response: cns.Response{
ReturnCode: 0,
Message: "",
},
}
return result, nil
case "failProcessCNSResp":
result := &cns.IPConfigsResponse{
PodIPInfo: []cns.PodIpInfo{
Expand Down Expand Up @@ -129,8 +207,8 @@ func (c *MockCNSClient) RequestIPs(ctx context.Context, ipconfig cns.IPConfigsRe
IPAddress: "fd11:1234::",
PrefixLength: 112,
},
DNSServers: nil,
GatewayIPAddress: "fe80::1234:5678:9abc",
DNSServers: nil,
GatewayIPv6Address: "fe80::1234:5678:9abc",
},
HostPrimaryIPInfo: cns.HostIPInfo{
Gateway: "fe80::1234:5678:9abc",
Expand All @@ -153,6 +231,7 @@ func (c *MockCNSClient) RequestIPs(ctx context.Context, ipconfig cns.IPConfigsRe
IPAddress: "10.0.1.10",
PrefixLength: 24,
},
MacAddress: "00:11:22:33:44:55",
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "10.0.1.0",
Expand All @@ -172,20 +251,61 @@ func (c *MockCNSClient) RequestIPs(ctx context.Context, ipconfig cns.IPConfigsRe
IPAddress: "fd11:1234::1",
PrefixLength: 120,
},
MacAddress: "00:11:22:33:44:55", // Same MAC for dual-stack scenario
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "fd11:1234::",
PrefixLength: 120,
},
DNSServers: nil,
GatewayIPAddress: "fe80::1234:5678:9abc",
DNSServers: nil,
GatewayIPv6Address: "fe80::1234:5678:9abc",
},
HostPrimaryIPInfo: cns.HostIPInfo{
Gateway: "fe80::1234:5678:9abc",
PrimaryIP: "fe80::1234:5678:9abc",
Subnet: "fd11:1234::/120",
},
},
{
PodIPConfig: cns.IPSubnet{
IPAddress: "192.168.1.10",
PrefixLength: 24,
},
MacAddress: "aa:bb:cc:dd:ee:ff",
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "192.168.1.0",
PrefixLength: 24,
},
DNSServers: nil,
GatewayIPAddress: "192.168.1.1",
},
HostPrimaryIPInfo: cns.HostIPInfo{
Gateway: "192.168.1.1",
PrimaryIP: "192.168.1.1",
Subnet: "192.168.1.0/24",
},
},
{
PodIPConfig: cns.IPSubnet{
IPAddress: "172.16.1.10",
PrefixLength: 24,
},
MacAddress: "",
NetworkContainerPrimaryIPConfig: cns.IPConfiguration{
IPSubnet: cns.IPSubnet{
IPAddress: "172.16.1.0",
PrefixLength: 24,
},
DNSServers: nil,
GatewayIPAddress: "172.16.1.1",
},
HostPrimaryIPInfo: cns.HostIPInfo{
Gateway: "172.16.1.1",
PrimaryIP: "172.16.1.1",
Subnet: "172.16.1.0/24",
},
},
},
Response: cns.Response{
ReturnCode: 0,
Expand Down Expand Up @@ -281,12 +401,18 @@ func TestCmdAdd(t *testing.T) {
args: buildArgs("happyArgsSingle", happyPodArgs, happyNetConfByteArr),
want: &types100.Result{
CNIVersion: "1.0.0",
Interfaces: []*types100.Interface{
{
Mac: "00:11:22:33:44:55",
},
},
IPs: []*types100.IPConfig{
{
Address: net.IPNet{
IP: net.IPv4(10, 0, 1, 10),
Mask: net.CIDRMask(24, 32),
},
Gateway: net.IPv4(10, 0, 0, 1),
},
},
DNS: cniTypes.DNS{},
Expand All @@ -298,24 +424,83 @@ func TestCmdAdd(t *testing.T) {
args: buildArgs("happyArgsDual", happyPodArgs, happyNetConfByteArr),
want: &types100.Result{
CNIVersion: "1.0.0",
Interfaces: []*types100.Interface{
{
Mac: "00:11:22:33:44:55", // Single interface for dual-stack
},
},
IPs: []*types100.IPConfig{
{
Address: net.IPNet{
IP: net.IPv4(10, 0, 1, 10),
Mask: net.CIDRMask(24, 32),
},
Gateway: net.IPv4(10, 0, 0, 1),
},
{
Address: net.IPNet{
IP: net.ParseIP("fd11:1234::1"),
Mask: net.CIDRMask(120, 128),
},
Gateway: net.ParseIP("fe80::1234:5678:9abc"),
},
},
DNS: cniTypes.DNS{},
},
wantErr: false,
},
{
name: "Test MAC address deduplication and multi-interface",
args: buildArgs("happyArgsMultiInterface", happyPodArgs, happyNetConfByteArr),
want: &types100.Result{
CNIVersion: "1.0.0",
Interfaces: []*types100.Interface{
{
Mac: "00:11:22:33:44:55", // First unique MAC (dual-stack)
},
{
Mac: "aa:bb:cc:dd:ee:ff", // Second unique MAC
},
},
IPs: []*types100.IPConfig{
{
Address: net.IPNet{
IP: net.IPv4(10, 0, 1, 10),
Mask: net.CIDRMask(24, 32),
},
Gateway: net.IPv4(10, 0, 0, 1),
},
{
Address: net.IPNet{
IP: net.ParseIP("fd11:1234::1"),
Mask: net.CIDRMask(120, 128),
},
Gateway: net.ParseIP("fe80::1234:5678:9abc"),
},
{
Address: net.IPNet{
IP: net.IPv4(192, 168, 1, 10),
Mask: net.CIDRMask(24, 32),
},
Gateway: net.IPv4(192, 168, 1, 1),
},
{
Address: net.IPNet{
IP: net.IPv4(172, 16, 1, 10),
Mask: net.CIDRMask(24, 32),
},
Gateway: net.IPv4(172, 16, 1, 1),
},
},
DNS: cniTypes.DNS{},
},
wantErr: false,
},
{
name: "CNI add with nil gateway IP",
args: buildArgs("nilGateway", happyPodArgs, happyNetConfByteArr),
wantErr: true,
},
{
name: "Fail request CNS ipconfig during CmdAdd",
args: buildArgs("failRequestCNSArgs", happyPodArgs, happyNetConfByteArr),
Expand Down
23 changes: 20 additions & 3 deletions azure-ipam/ipconfig/ipconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ipconfig
import (
"encoding/json"
"fmt"
"net"
"net/netip"

"github.com/Azure/azure-container-networking/cns"
Expand Down Expand Up @@ -63,23 +64,39 @@ func CreateIPConfigsReq(args *cniSkel.CmdArgs) (cns.IPConfigsRequest, error) {
return req, nil
}

func ProcessIPConfigsResp(resp *cns.IPConfigsResponse) (*[]netip.Prefix, error) {
func ProcessIPConfigsResp(resp *cns.IPConfigsResponse) (*[]netip.Prefix, *[]net.IP, error) {
podIPNets := make([]netip.Prefix, len(resp.PodIPInfo))
gatewaysIPs := make([]net.IP, len(resp.PodIPInfo))

for i := range resp.PodIPInfo {
var gatewayIP net.IP

podCIDR := fmt.Sprintf(
"%s/%d",
resp.PodIPInfo[i].PodIPConfig.IPAddress,
resp.PodIPInfo[i].NetworkContainerPrimaryIPConfig.IPSubnet.PrefixLength,
)
podIPNet, err := netip.ParsePrefix(podCIDR)
if err != nil {
return nil, errors.Wrapf(err, "cns returned invalid pod CIDR %q", podCIDR)
return nil, nil, errors.Wrapf(err, "cns returned invalid pod CIDR %q", podCIDR)
}
podIPNets[i] = podIPNet

var gatewayStr string
if podIPNet.Addr().Is4() {
gatewayStr = resp.PodIPInfo[i].NetworkContainerPrimaryIPConfig.GatewayIPAddress
} else if podIPNet.Addr().Is6() {
gatewayStr = resp.PodIPInfo[i].NetworkContainerPrimaryIPConfig.GatewayIPv6Address
}

gatewayIP = net.ParseIP(gatewayStr)
if gatewayIP == nil {
return nil, nil, errors.Errorf("failed to parse gateway IP %q for pod ip %s", gatewayStr, resp.PodIPInfo[i].PodIPConfig.IPAddress)
}
gatewaysIPs[i] = gatewayIP
}

return &podIPNets, nil
return &podIPNets, &gatewaysIPs, nil
}

type k8sPodEnvArgs struct {
Expand Down
Loading
Loading