Skip to content

Commit 21f0fcf

Browse files
author
Jose Villalta
committed
feat(netlib): Add static link-local IP assignment to daemon namespaces
Assign predictable link-local IP addresses to managed daemon network namespaces. Daemon namespaces now receive 169.254.172.2/22 for IPv4 and fd00:ec2::172:2/112 for IPv6, enabling reliable communication between the host and daemons running inside the namespace. Changes: - Add DaemonNamespaceIPv4 and DaemonNamespaceIPv6 constants - Update createDaemonBridgePluginConfig to set static IPs in IPAM config - Update unit tests for IPv4-only, IPv6-only, and dual-stack cases - Add e2e connectivity tests for IPv4 and IPv6
1 parent f5f623d commit 21f0fcf

File tree

4 files changed

+432
-23
lines changed

4 files changed

+432
-23
lines changed

ecs-agent/netlib/platform/cniconf.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ const (
2121
ECSSubNet = "169.254.172.0/22"
2222
AgentEndpoint = "169.254.170.2/32"
2323

24-
// Daemon-bridge networking constants
24+
// Daemon-bridge networking constants.
2525
DaemonBridgeGatewayIP = "169.254.172.1"
2626
DefaultRouteDestination = "0.0.0.0/0"
27+
// DaemonNamespaceIPv4 is the static IPv4 address assigned to daemon namespace interfaces.
28+
// The address includes the /22 prefix length to match the ECS subnet.
29+
DaemonNamespaceIPv4 = "169.254.172.2/22"
2730

2831
// IPv6 daemon-bridge networking constants
2932
// Using fd00:ec2::172:0/112 as a unique local address (ULA) subnet for ECS internal communication.
@@ -33,6 +36,9 @@ const (
3336
ECSSubNetIPv6 = "fd00:ec2::172:0/112"
3437
DaemonBridgeGatewayIPv6 = "fd00:ec2::172:1"
3538
DefaultRouteDestinationIPv6 = "::/0"
39+
// DaemonNamespaceIPv6 is the static IPv6 address assigned to daemon namespace interfaces.
40+
// The address includes the /112 prefix length to match the ECS IPv6 subnet.
41+
DaemonNamespaceIPv6 = "fd00:ec2::172:2/112"
3642

3743
CNIPluginLogFileEnv = "ECS_CNI_LOG_FILE"
3844
VPCCNIPluginLogFileEnv = "VPC_CNI_LOG_FILE"

ecs-agent/netlib/platform/cniconf_linux.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,17 @@ func createDaemonBridgePluginConfig(netNSPath string, ipComp ipcompatibility.IPC
179179
CNISpecVersion: cniSpecVersion,
180180
CNIPluginName: IPAMPluginName,
181181
},
182-
IPV4Subnet: ECSSubNet,
183-
IPV4Routes: ipv4Routes,
184-
IPV6Routes: ipv6Routes,
185-
ID: netNSPath,
182+
IPV4Subnet: ECSSubNet,
183+
IPV4Address: DaemonNamespaceIPv4,
184+
IPV4Routes: ipv4Routes,
185+
IPV6Routes: ipv6Routes,
186+
ID: netNSPath,
186187
}
187188

188-
// Add IPv6 subnet and gateway if IPv6 compatible
189+
// Add IPv6 subnet, gateway, and static address if IPv6 compatible.
189190
if ipComp.IsIPv6Compatible() {
190191
ipamConfig.IPV6Subnet = ECSSubNetIPv6
192+
ipamConfig.IPV6Address = DaemonNamespaceIPv6
191193
ipamConfig.IPV6Gateway = DaemonBridgeGatewayIPv6
192194
}
193195

ecs-agent/netlib/platform/cniconf_linux_test.go

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -359,15 +359,21 @@ func getTestV2NInterface() *networkinterface.NetworkInterface {
359359
}
360360
}
361361

362+
// TestCreateDaemonBridgePluginConfig verifies that createDaemonBridgePluginConfig produces
363+
// correct CNI configurations for different IP compatibility modes, including static IP
364+
// address assignment for daemon namespaces.
365+
// **Validates: Requirements 2.1, 2.2, 3.1, 3.2, 4.1**
362366
func TestCreateDaemonBridgePluginConfig(t *testing.T) {
363-
// Common CNI config for all test cases
367+
t.Parallel()
368+
369+
// Common CNI config for all test cases.
364370
cniConfig := ecscni.CNIConfig{
365371
NetNSPath: netNSPath,
366372
CNISpecVersion: cniSpecVersion,
367373
CNIPluginName: BridgePluginName,
368374
}
369375

370-
// Common routes
376+
// Common routes.
371377
_, agentRouteIPNet, _ := net.ParseCIDR(AgentEndpoint)
372378
agentRoute := &types.Route{
373379
Dst: *agentRouteIPNet,
@@ -387,15 +393,15 @@ func TestCreateDaemonBridgePluginConfig(t *testing.T) {
387393
GW: bridgeGWv6,
388394
}
389395

390-
for _, tc := range []struct {
396+
tests := []struct {
391397
name string
392398
ipComp ipcompatibility.IPCompatibility
393399
expectedConfig *ecscni.BridgeConfig
394400
expectError bool
395401
errorContains string
396402
}{
397403
{
398-
name: "IPv4-only configuration",
404+
name: "IPv4-only configuration includes static IPv4 address",
399405
ipComp: ipcompatibility.NewIPv4OnlyCompatibility(),
400406
expectedConfig: &ecscni.BridgeConfig{
401407
CNIConfig: cniConfig,
@@ -406,15 +412,16 @@ func TestCreateDaemonBridgePluginConfig(t *testing.T) {
406412
CNISpecVersion: cniSpecVersion,
407413
CNIPluginName: IPAMPluginName,
408414
},
409-
IPV4Subnet: ECSSubNet,
410-
IPV4Routes: []*types.Route{agentRoute, defaultRouteIPv4},
411-
ID: netNSPath,
415+
IPV4Subnet: ECSSubNet,
416+
IPV4Address: DaemonNamespaceIPv4,
417+
IPV4Routes: []*types.Route{agentRoute, defaultRouteIPv4},
418+
ID: netNSPath,
412419
},
413420
},
414421
expectError: false,
415422
},
416423
{
417-
name: "IPv6-only configuration",
424+
name: "IPv6-only configuration includes static IPv6 address",
418425
ipComp: ipcompatibility.NewIPv6OnlyCompatibility(),
419426
expectedConfig: &ecscni.BridgeConfig{
420427
CNIConfig: cniConfig,
@@ -426,8 +433,10 @@ func TestCreateDaemonBridgePluginConfig(t *testing.T) {
426433
CNIPluginName: IPAMPluginName,
427434
},
428435
IPV4Subnet: ECSSubNet,
429-
IPV4Routes: []*types.Route{agentRoute}, // ECS agent endpoint always included
436+
IPV4Address: DaemonNamespaceIPv4, // IPv4 address is always set for IPAM.
437+
IPV4Routes: []*types.Route{agentRoute}, // ECS agent endpoint always included.
430438
IPV6Subnet: ECSSubNetIPv6,
439+
IPV6Address: DaemonNamespaceIPv6,
431440
IPV6Gateway: DaemonBridgeGatewayIPv6,
432441
IPV6Routes: []*types.Route{defaultRouteIPv6},
433442
ID: netNSPath,
@@ -436,7 +445,7 @@ func TestCreateDaemonBridgePluginConfig(t *testing.T) {
436445
expectError: false,
437446
},
438447
{
439-
name: "Dual-stack configuration",
448+
name: "Dual-stack configuration includes both static IPv4 and IPv6 addresses",
440449
ipComp: ipcompatibility.NewDualStackCompatibility(),
441450
expectedConfig: &ecscni.BridgeConfig{
442451
CNIConfig: cniConfig,
@@ -448,8 +457,10 @@ func TestCreateDaemonBridgePluginConfig(t *testing.T) {
448457
CNIPluginName: IPAMPluginName,
449458
},
450459
IPV4Subnet: ECSSubNet,
460+
IPV4Address: DaemonNamespaceIPv4,
451461
IPV4Routes: []*types.Route{agentRoute, defaultRouteIPv4},
452462
IPV6Subnet: ECSSubNetIPv6,
463+
IPV6Address: DaemonNamespaceIPv6,
453464
IPV6Gateway: DaemonBridgeGatewayIPv6,
454465
IPV6Routes: []*types.Route{defaultRouteIPv6},
455466
ID: netNSPath,
@@ -463,19 +474,22 @@ func TestCreateDaemonBridgePluginConfig(t *testing.T) {
463474
expectError: true,
464475
errorContains: "host is neither IPv4 nor IPv6 compatible",
465476
},
466-
} {
467-
tc := tc
477+
}
468478

469-
t.Run(tc.name, func(t *testing.T) {
470-
actualConfig, err := createDaemonBridgePluginConfig(netNSPath, tc.ipComp)
479+
for _, tt := range tests {
480+
tt := tt // Capture range variable for parallel execution.
481+
t.Run(tt.name, func(t *testing.T) {
482+
t.Parallel()
483+
484+
actualConfig, err := createDaemonBridgePluginConfig(netNSPath, tt.ipComp)
471485

472-
if tc.expectError {
486+
if tt.expectError {
473487
require.Error(t, err)
474-
require.Contains(t, err.Error(), tc.errorContains)
488+
require.Contains(t, err.Error(), tt.errorContains)
475489
require.Nil(t, actualConfig)
476490
} else {
477491
require.NoError(t, err)
478-
expected, err := json.Marshal(tc.expectedConfig)
492+
expected, err := json.Marshal(tt.expectedConfig)
479493
require.NoError(t, err)
480494
actual, err := json.Marshal(actualConfig)
481495
require.NoError(t, err)

0 commit comments

Comments
 (0)