Skip to content
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
22 changes: 13 additions & 9 deletions ecs-agent/netlib/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ func getSingleNetNSAWSVPCTestData(testTaskID string) (*ecsacs.Task, tasknetworkc
NetworkMode: types.NetworkModeAwsvpc,
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{
{
Name: netNSName,
Path: netNSPath,
Index: 0,
Name: netNSName,
Path: netNSPath,
Index: 0,
NetworkMode: types.NetworkModeAwsvpc,
NetworkInterfaces: []*networkinterface.NetworkInterface{
&netIfs[0],
},
Expand Down Expand Up @@ -156,19 +157,21 @@ func getMultiNetNSMultiIfaceAWSVPCTestData(testTaskID string) (*ecsacs.Task, tas
NetworkMode: types.NetworkModeAwsvpc,
NetworkNamespaces: []*tasknetworkconfig.NetworkNamespace{
{
Name: primaryNetNSName,
Path: primaryNetNSPath,
Index: 0,
Name: primaryNetNSName,
Path: primaryNetNSPath,
Index: 0,
NetworkMode: types.NetworkModeAwsvpc,
NetworkInterfaces: []*networkinterface.NetworkInterface{
&netIfs[0],
},
KnownState: status.NetworkNone,
DesiredState: status.NetworkReadyPull,
},
{
Name: secondaryNetNSName,
Path: secondaryNetNSPath,
Index: 1,
Name: secondaryNetNSName,
Path: secondaryNetNSPath,
Index: 1,
NetworkMode: types.NetworkModeAwsvpc,
NetworkInterfaces: []*networkinterface.NetworkInterface{
&netIfs[1],
},
Expand Down Expand Up @@ -323,6 +326,7 @@ func getV2NTestData(testTaskID string) (*ecsacs.Task, tasknetworkconfig.TaskNetw
Name: netNSName,
Path: netNSPath,
Index: 0,
NetworkMode: types.NetworkModeAwsvpc,
NetworkInterfaces: netIfs,
KnownState: status.NetworkNone,
DesiredState: status.NetworkReadyPull,
Expand Down
12 changes: 12 additions & 0 deletions ecs-agent/netlib/model/tasknetworkconfig/network_namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/networkinterface"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/serviceconnect"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/status"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
)

// NetworkNamespace is model representing each network namespace.
Expand All @@ -30,6 +31,10 @@ type NetworkNamespace struct {
Path string
Index int

// NetworkMode represents the network mode for this namespace.
// Supported values: awsvpc (default), host(managed-instances only), daemon-bridge (managed-instances only).
NetworkMode types.NetworkMode

// NetworkInterfaces represents ENIs or any kind of network interface associated the particular netns.
NetworkInterfaces []*networkinterface.NetworkInterface

Expand Down Expand Up @@ -58,6 +63,7 @@ func NewNetworkNamespace(
NetworkInterfaces: networkInterfaces,
KnownState: status.NetworkNone,
DesiredState: status.NetworkReadyPull,
NetworkMode: types.NetworkModeAwsvpc,
}

// Sort interfaces as per their index values in ascending order.
Expand Down Expand Up @@ -104,3 +110,9 @@ func (ns *NetworkNamespace) GetInterfaceByIndex(idx int64) *networkinterface.Net

return nil
}

// WithNetworkMode sets the NetworkMode field
func (ns *NetworkNamespace) WithNetworkMode(mode types.NetworkMode) *NetworkNamespace {
ns.NetworkMode = mode
return ns
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package tasknetworkconfig
import (
"testing"

"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -47,6 +48,7 @@ func TestNewNetworkNamespace(t *testing.T) {
assert.Equal(t, primaryNetNSName, netns.Name)
assert.Equal(t, primaryNetNSPath, netns.Path)
assert.Equal(t, 0, netns.Index)
assert.Equal(t, types.NetworkModeAwsvpc, netns.NetworkMode)
assert.Empty(t, netns.AppMeshConfig)
assert.Equal(t, *netIFs[0], *netns.NetworkInterfaces[0])
assert.Equal(t, *netIFs[1], *netns.NetworkInterfaces[1])
Expand Down Expand Up @@ -78,3 +80,41 @@ func TestNetworkNamespace_IsPrimary(t *testing.T) {
require.Equal(t, tc.isPrimary, tc.netNS.IsPrimary())
}
}

func TestNetworkNamespace_WithNetworkMode(t *testing.T) {
testCases := []struct {
name string
networkMode types.NetworkMode
}{
{
name: "awsvpc mode",
networkMode: types.NetworkModeAwsvpc,
},
{
name: "host mode",
networkMode: types.NetworkModeHost,
},
{
name: "bridge mode",
networkMode: types.NetworkModeBridge,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
netns := &NetworkNamespace{
Name: "test-netns",
Path: "/test/path",
Index: 0,
NetworkMode: types.NetworkModeAwsvpc, // default
}

result := netns.WithNetworkMode(tc.networkMode)

// Verify the method returns the same instance
assert.Same(t, netns, result)
// Verify the NetworkMode was updated
assert.Equal(t, tc.networkMode, netns.NetworkMode)
})
}
}
4 changes: 4 additions & 0 deletions ecs-agent/netlib/network_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func (nb *networkBuilder) Start(
err = nb.startAWSVPC(ctx, taskID, netNS)
case types.NetworkModeHost:
err = nb.platformAPI.HandleHostMode()
case "daemon-bridge":
err = nb.platformAPI.ConfigureDaemonNetNS(netNS)
default:
err = errors.New("invalid network mode: " + string(mode))
}
Expand Down Expand Up @@ -132,6 +134,8 @@ func (nb *networkBuilder) Stop(ctx context.Context, mode types.NetworkMode, task
err = nb.stopAWSVPC(ctx, netNS)
case types.NetworkModeHost:
err = nb.platformAPI.HandleHostMode()
case "daemon-bridge":
err = nb.platformAPI.StopDaemonNetNS(ctx, netNS)
default:
err = errors.New("invalid network mode: " + string(mode))
}
Expand Down
64 changes: 64 additions & 0 deletions ecs-agent/netlib/network_builder_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@ func TestNetworkBuilder_BuildTaskNetworkConfiguration(t *testing.T) {

func TestNetworkBuilder_Start(t *testing.T) {
t.Run("awsvpc", testNetworkBuilder_StartAWSVPC)
t.Run("daemon-bridge", testNetworkBuilder_StartDaemonBridge)
}

// TestNetworkBuilder_Stop verifies stop workflow for AWSVPC mode.
func TestNetworkBuilder_Stop(t *testing.T) {
t.Run("awsvpc", testNetworkBuilder_StopAWSVPC)
t.Run("daemon-bridge", testNetworkBuilder_StopDaemonBridge)
}

// getTestFunc returns a test function that verifies the capability of the networkBuilder
Expand Down Expand Up @@ -380,3 +382,65 @@ func getExpectedCalls_StopAWSVPC(
platformAPI.EXPECT().DeleteDNSConfig(netNS.Name).Return(nil).Times(1),
platformAPI.EXPECT().DeleteNetNS(netNS.Path).Return(nil).Times(1))
}

// testNetworkBuilder_StartDaemonBridge verifies that the expected platform API calls
// are made by the network builder for daemon-bridge network mode.
func testNetworkBuilder_StartDaemonBridge(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ctx := context.TODO()
platformAPI := mock_platform.NewMockAPI(ctrl)
metricsFactory := mock_metrics.NewMockEntryFactory(ctrl)
mockEntry := mock_metrics.NewMockEntry(ctrl)
netBuilder := &networkBuilder{
platformAPI: platformAPI,
metricsFactory: metricsFactory,
}

netNS := &tasknetworkconfig.NetworkNamespace{
Name: "daemon-test",
Path: "/var/run/netns/daemon-test",
}

gomock.InOrder(
metricsFactory.EXPECT().New(metrics.BuildNetworkNamespaceMetricName).Return(mockEntry).Times(1),
mockEntry.EXPECT().WithFields(gomock.Any()).Return(mockEntry).Times(1),
platformAPI.EXPECT().ConfigureDaemonNetNS(netNS).Return(nil).Times(1),
mockEntry.EXPECT().Done(nil).Times(1),
)

err := netBuilder.Start(ctx, "daemon-bridge", taskID, netNS)
require.NoError(t, err)
}

// testNetworkBuilder_StopDaemonBridge verifies that the expected platform API calls
// are made by the network builder for stopping daemon-bridge network mode.
func testNetworkBuilder_StopDaemonBridge(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ctx := context.TODO()
platformAPI := mock_platform.NewMockAPI(ctrl)
metricsFactory := mock_metrics.NewMockEntryFactory(ctrl)
mockEntry := mock_metrics.NewMockEntry(ctrl)
netBuilder := &networkBuilder{
platformAPI: platformAPI,
metricsFactory: metricsFactory,
}

netNS := &tasknetworkconfig.NetworkNamespace{
Name: "daemon-test",
Path: "/var/run/netns/daemon-test",
}

gomock.InOrder(
metricsFactory.EXPECT().New(metrics.DeleteNetworkNamespaceMetricName).Return(mockEntry).Times(1),
mockEntry.EXPECT().WithFields(gomock.Any()).Return(mockEntry).Times(1),
platformAPI.EXPECT().StopDaemonNetNS(ctx, netNS).Return(nil).Times(1),
mockEntry.EXPECT().Done(nil).Times(1),
)

err := netBuilder.Stop(ctx, "daemon-bridge", taskID, netNS)
require.NoError(t, err)
}
8 changes: 8 additions & 0 deletions ecs-agent/netlib/platform/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ type API interface {
primaryIf *networkinterface.NetworkInterface,
scConfig *serviceconnect.ServiceConnectConfig,
) error

// ConfigureDaemonNetNS configures a network namespace for workloads running as daemons.
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error

// StopDaemonNetNS stops and cleans up a daemon network namespace.
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
StopDaemonNetNS(ctx context.Context, netNS *tasknetworkconfig.NetworkNamespace) error
}

// Config contains platform-specific data.
Expand Down
4 changes: 4 additions & 0 deletions ecs-agent/netlib/platform/cniconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const (
ECSSubNet = "169.254.172.0/22"
AgentEndpoint = "169.254.170.2/32"

// Daemon-bridge networking constants
DaemonBridgeGatewayIP = "169.254.172.1"
DefaultRouteDestination = "0.0.0.0/0"

CNIPluginLogFileEnv = "ECS_CNI_LOG_FILE"
VPCCNIPluginLogFileEnv = "VPC_CNI_LOG_FILE"
IPAMDataPathEnv = "IPAM_DB_PATH"
Expand Down
48 changes: 48 additions & 0 deletions ecs-agent/netlib/platform/cniconf_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,54 @@ func createBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
return bridgeConfig
}

// createDaemonBridgePluginConfig constructs the configuration object for bridge plugin in daemon-bridge mode
// It includes routes for ECS agent endpoint and default route for external traffic (including DNS resolution)
func createDaemonBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
cniConfig := ecscni.CNIConfig{
NetNSPath: netNSPath,
CNISpecVersion: cniSpecVersion,
CNIPluginName: BridgePluginName,
}

_, routeIPNet, _ := net.ParseCIDR(AgentEndpoint)
route := &types.Route{
Dst: *routeIPNet,
}

// Add routes for daemon-bridge mode
var routes []*types.Route
routes = append(routes, route) // ECS agent endpoint route

// Add default route for external traffic, which goes through the bridge gateway to reach host's trunk ENI
_, defaultNet, _ := net.ParseCIDR(DefaultRouteDestination)
bridgeGW := net.ParseIP(DaemonBridgeGatewayIP)
defaultRoute := &types.Route{
Dst: *defaultNet,
GW: bridgeGW,
}
routes = append(routes, defaultRoute)

ipamConfig := &ecscni.IPAMConfig{
CNIConfig: ecscni.CNIConfig{
NetNSPath: netNSPath,
CNISpecVersion: cniSpecVersion,
CNIPluginName: IPAMPluginName,
},
IPV4Subnet: ECSSubNet,
IPV4Routes: routes,
ID: netNSPath,
}

// Invoke the bridge plugin and ipam plugin
bridgeConfig := &ecscni.BridgeConfig{
CNIConfig: cniConfig,
Name: BridgeInterfaceName,
IPAM: *ipamConfig,
}

return bridgeConfig
}

func createAppMeshPluginConfig(
netNSPath string,
cfg *appmesh.AppMesh,
Expand Down
44 changes: 44 additions & 0 deletions ecs-agent/netlib/platform/cniconf_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,47 @@ func getTestV2NInterface() *networkinterface.NetworkInterface {
DomainNameSearchList: []string{searchDomainName},
}
}

func TestCreateDaemonBridgePluginConfig(t *testing.T) {
cniConfig := ecscni.CNIConfig{
NetNSPath: netNSPath,
CNISpecVersion: cniSpecVersion,
CNIPluginName: BridgePluginName,
}

_, agentRouteIPNet, _ := net.ParseCIDR(AgentEndpoint)
agentRoute := &types.Route{
Dst: *agentRouteIPNet,
}

_, defaultNet, _ := net.ParseCIDR(DefaultRouteDestination)
bridgeGW := net.ParseIP(DaemonBridgeGatewayIP)
defaultRoute := &types.Route{
Dst: *defaultNet,
GW: bridgeGW,
}

ipamConfig := &ecscni.IPAMConfig{
CNIConfig: ecscni.CNIConfig{
NetNSPath: netNSPath,
CNISpecVersion: cniSpecVersion,
CNIPluginName: IPAMPluginName,
},
IPV4Subnet: ECSSubNet,
IPV4Routes: []*types.Route{agentRoute, defaultRoute},
ID: netNSPath,
}

bridgeConfig := &ecscni.BridgeConfig{
CNIConfig: cniConfig,
Name: BridgeInterfaceName,
IPAM: *ipamConfig,
}

expected, err := json.Marshal(bridgeConfig)
require.NoError(t, err)
actual, err := json.Marshal(createDaemonBridgePluginConfig(netNSPath))
require.NoError(t, err)

require.Equal(t, expected, actual)
}
13 changes: 13 additions & 0 deletions ecs-agent/netlib/platform/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"time"

"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/ecscni"
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/tasknetworkconfig"

"github.com/containernetworking/cni/pkg/types"
)
Expand Down Expand Up @@ -92,3 +93,15 @@ func (c *common) interfacesMACToName() (map[string]string, error) {
func (c *common) HandleHostMode() error {
return errors.New("invalid platform for host mode")
}

// ConfigureDaemonNetNS configures a network namespace for workloads running as daemons.
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
func (c *common) ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error {
return errors.New("daemon network namespaces are not supported in this platform")
}

// StopDaemonNetNS stops and cleans up a daemon network namespace.
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
func (c *common) StopDaemonNetNS(ctx context.Context, netNS *tasknetworkconfig.NetworkNamespace) error {
return errors.New("daemon network namespaces are not supported in this platform")
}
Loading
Loading