Skip to content

Commit 79f4dac

Browse files
author
Jose Villalta
committed
adds daemon-bridge netns
1 parent 8ada03e commit 79f4dac

File tree

6 files changed

+212
-4
lines changed

6 files changed

+212
-4
lines changed

ecs-agent/netlib/platform/api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ type API interface {
7878
primaryIf *networkinterface.NetworkInterface,
7979
scConfig *serviceconnect.ServiceConnectConfig,
8080
) error
81+
82+
// ConfigureDaemonNetNS configures a network namespace for workloads running as daemons.
83+
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
84+
ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error
8185
}
8286

8387
// Config contains platform-specific data.

ecs-agent/netlib/platform/cniconf_linux.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,40 @@ func createBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
121121
return bridgeConfig
122122
}
123123

124+
// createBridgePluginConfig constructs the configuration object for bridge plugin
125+
func createDaemonBridgePluginConfig(netNSPath string) ecscni.PluginConfig {
126+
cniConfig := ecscni.CNIConfig{
127+
NetNSPath: netNSPath,
128+
CNISpecVersion: cniSpecVersion,
129+
CNIPluginName: BridgePluginName,
130+
}
131+
132+
_, routeIPNet, _ := net.ParseCIDR(AgentEndpoint)
133+
route := &types.Route{
134+
Dst: *routeIPNet,
135+
}
136+
137+
ipamConfig := &ecscni.IPAMConfig{
138+
CNIConfig: ecscni.CNIConfig{
139+
NetNSPath: netNSPath,
140+
CNISpecVersion: cniSpecVersion,
141+
CNIPluginName: IPAMPluginName,
142+
},
143+
IPV4Subnet: ECSSubNet,
144+
IPV4Routes: []*types.Route{route},
145+
ID: netNSPath,
146+
}
147+
148+
// Invoke the bridge plugin and ipam plugin
149+
bridgeConfig := &ecscni.BridgeConfig{
150+
CNIConfig: cniConfig,
151+
Name: "emi-bridge", // TODO create a const and make this a parameter.
152+
IPAM: *ipamConfig,
153+
}
154+
155+
return bridgeConfig
156+
}
157+
124158
func createAppMeshPluginConfig(
125159
netNSPath string,
126160
cfg *appmesh.AppMesh,

ecs-agent/netlib/platform/common.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"time"
2020

2121
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/ecscni"
22+
"github.com/aws/amazon-ecs-agent/ecs-agent/netlib/model/tasknetworkconfig"
2223

2324
"github.com/containernetworking/cni/pkg/types"
2425
)
@@ -92,3 +93,9 @@ func (c *common) interfacesMACToName() (map[string]string, error) {
9293
func (c *common) HandleHostMode() error {
9394
return errors.New("invalid platform for host mode")
9495
}
96+
97+
// ConfigureDaemonNetNS configures a network namespace for workloads running as daemons.
98+
// This is an internal networking mode available in EMI (ECS Managed Instances) only.
99+
func (c *common) ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error {
100+
return errors.New("daemon network namespaces are not supported in this platform")
101+
}

ecs-agent/netlib/platform/managed_linux.go

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,15 @@ func (m *managedLinux) BuildTaskNetworkConfiguration(
5656
return nil, errors.Wrap(err, "failed to translate network configuration")
5757
}
5858
case types.NetworkModeHost:
59-
netNSs, err = m.buildDefaultNetworkNamespace(taskID)
59+
netNSs, err = m.buildDefaultNetworkNamespaceConfig(taskID)
6060
if err != nil {
6161
return nil, errors.Wrap(err, "failed to create network namespace with host eni")
6262
}
63+
case "daemon-bridge":
64+
netNSs, err = m.buildHostDaemonNamespaceConfig(taskID)
65+
if err != nil {
66+
return nil, errors.Wrap(err, "failed to create daemon host namespace")
67+
}
6368
default:
6469
return nil, errors.New("invalid network mode: " + string(mode))
6570
}
@@ -205,7 +210,7 @@ func (m *managedLinux) ConfigureServiceConnect(
205210
}
206211

207212
// buildDefaultNetworkNamespace return default network namespace of host ENI for host mode.
208-
func (m *managedLinux) buildDefaultNetworkNamespace(taskID string) ([]*tasknetworkconfig.NetworkNamespace, error) {
213+
func (m *managedLinux) buildDefaultNetworkNamespaceConfig(taskID string) ([]*tasknetworkconfig.NetworkNamespace, error) {
209214
macAddress, err1 := m.client.GetMetadata(MacResource)
210215
ec2ID, err2 := m.client.GetMetadata(InstanceIDResource)
211216
macToNames, err3 := m.common.interfacesMACToName()
@@ -306,3 +311,147 @@ func (m *managedLinux) buildDefaultNetworkNamespace(taskID string) ([]*tasknetwo
306311
func (m *managedLinux) HandleHostMode() error {
307312
return nil
308313
}
314+
315+
func (m *managedLinux) buildHostDaemonNamespaceConfig(taskID string) ([]*tasknetworkconfig.NetworkNamespace, error) {
316+
macAddress, err1 := m.client.GetMetadata(MacResource)
317+
ec2ID, err2 := m.client.GetMetadata(InstanceIDResource)
318+
macToNames, err3 := m.common.interfacesMACToName()
319+
if err := goErr.Join(err1, err2, err3); err != nil {
320+
logger.Error("Error fetching fields for default ENI", logger.Fields{
321+
loggerfield.Error: err,
322+
})
323+
return nil, err
324+
}
325+
326+
hostENI := &ecsacs.ElasticNetworkInterface{
327+
AttachmentArn: aws.String("arn"),
328+
Ec2Id: aws.String(ec2ID),
329+
MacAddress: aws.String(macAddress),
330+
DomainNameServers: []*string{},
331+
DomainName: []*string{},
332+
PrivateDnsName: aws.String(DefaultArg),
333+
InterfaceAssociationProtocol: aws.String(DefaultArg),
334+
Index: aws.Int64(64),
335+
}
336+
337+
ipComp, err := net.DetermineIPCompatibility(m.netlink, macAddress)
338+
if err != nil {
339+
logger.Error("Failed to determine IP compatibility of host ENI", logger.Fields{
340+
loggerfield.Error: err,
341+
})
342+
return nil, err
343+
}
344+
345+
if !ipComp.IsIPv4Compatible() && !ipComp.IsIPv6Compatible() {
346+
return nil, errors.New("Failed to build the default network namespace because the host ENI is neither " +
347+
"IPv4 enabled nor IPv6 enabled")
348+
}
349+
350+
if ipComp.IsIPv6Compatible() {
351+
privateIpv6, err1 := m.client.GetMetadata(PrivateIPv6Address)
352+
ipv6SubNet, err2 := m.client.GetMetadata(fmt.Sprintf(IPv6SubNetCidrBlock, macAddress))
353+
if err := goErr.Join(err1, err2); err != nil {
354+
logger.Error("Error fetching IPv6 fields for default ENI", logger.Fields{
355+
loggerfield.Error: err,
356+
})
357+
return nil, err
358+
}
359+
360+
hostENI.Ipv6Addresses = []*ecsacs.IPv6AddressAssignment{
361+
{
362+
Primary: aws.Bool(true),
363+
Address: aws.String(privateIpv6),
364+
},
365+
}
366+
hostENI.SubnetGatewayIpv6Address = aws.String(ipv6SubNet)
367+
}
368+
369+
if ipComp.IsIPv4Compatible() {
370+
privateIpv4, err1 := m.client.GetMetadata(PrivateIPv4Address)
371+
ipv4SubNet, err2 := m.client.GetMetadata(fmt.Sprintf(IPv4SubNetCidrBlock, macAddress))
372+
if err := goErr.Join(err1, err2); err != nil {
373+
logger.Error("Error fetching IPv4 fields for default ENI", logger.Fields{
374+
loggerfield.Error: err,
375+
})
376+
return nil, err
377+
}
378+
379+
hostENI.Ipv4Addresses = []*ecsacs.IPv4AddressAssignment{
380+
{
381+
Primary: aws.Bool(true),
382+
PrivateAddress: aws.String(privateIpv4),
383+
},
384+
}
385+
hostENI.SubnetGatewayIpv4Address = aws.String(ipv4SubNet)
386+
}
387+
388+
netNSName := "host-daemon"
389+
netNSPath := m.common.GetNetNSPath(netNSName)
390+
netInt, err := networkinterface.New(hostENI, DefaultArg, nil, macToNames)
391+
if err != nil {
392+
logger.Error("Failed to create the network interface", logger.Fields{
393+
loggerfield.Error: err,
394+
})
395+
return nil, err
396+
}
397+
398+
netInt.Default = true
399+
netInt.DesiredStatus = status.NetworkReady
400+
netInt.KnownStatus = status.NetworkReady
401+
daemonNamespace, err := tasknetworkconfig.NewNetworkNamespace(netNSName, netNSPath, 0, nil, netInt)
402+
if err != nil {
403+
logger.Error("Error building default network namespace for host mode", logger.Fields{
404+
loggerfield.Error: err,
405+
})
406+
return nil, err
407+
}
408+
daemonNamespace.KnownState = status.NetworkReady
409+
daemonNamespace.DesiredState = status.NetworkReady
410+
return []*tasknetworkconfig.NetworkNamespace{daemonNamespace}, nil
411+
}
412+
413+
func (m *managedLinux) configureDaemonNetNS(ctx context.Context, taskID string, netNS *tasknetworkconfig.NetworkNamespace) error {
414+
var err error
415+
if netNS.DesiredState == status.NetworkDeleted {
416+
return errors.New("invalid transition state encountered: " + netNS.DesiredState.String())
417+
}
418+
419+
// Create the network namespace and setup DNS configuration within the netns.
420+
// This has to happen before any CNI plugin is executed.
421+
if netNS.KnownState == status.NetworkNone &&
422+
netNS.DesiredState == status.NetworkReadyPull {
423+
424+
logger.Debug("Creating netns: " + netNS.Path)
425+
// Create network namespace on the host.
426+
err = m.CreateNetNS(netNS.Path)
427+
if err != nil {
428+
return err
429+
}
430+
431+
logger.Debug("Creating DNS config files")
432+
433+
// Create necessary DNS config files for the netns.
434+
err = m.CreateDNSConfig(taskID, netNS)
435+
if err != nil {
436+
return err
437+
}
438+
}
439+
440+
// Create MI-Bridge
441+
var cniNetConf []ecscni.PluginConfig
442+
cniNetConf = append(cniNetConf, createDaemonBridgePluginConfig(netNS.Path))
443+
add := true
444+
445+
_, err = m.common.executeCNIPlugin(ctx, add, cniNetConf...)
446+
if err != nil {
447+
err = errors.Wrap(err, "failed to setup deamon network namespace bridge")
448+
}
449+
450+
return err
451+
}
452+
453+
// ConfigureDaemonNetNS will create a network namespace using the host ENI and host dns configuration.
454+
// It will contain a loopback interface and a bridge to the internal ECS subnet.
455+
func (m *managedLinux) ConfigureDaemonNetNS(netNS *tasknetworkconfig.NetworkNamespace) error {
456+
return m.configureDaemonNetNS(context.Background(), netNS.Path, netNS)
457+
}

ecs-agent/netlib/platform/managed_linux_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func testManagedLinuxBranchENIConfiguration(t *testing.T) {
131131
require.NoError(t, err)
132132
}
133133

134-
func TestBuildDefaultNetworkNamespace(t *testing.T) {
134+
func TestBuildDefaultNetworkNamespaceConfig(t *testing.T) {
135135
tests := []struct {
136136
name string
137137
taskID string
@@ -278,7 +278,7 @@ func TestBuildDefaultNetworkNamespace(t *testing.T) {
278278
common: *commonPlatform,
279279
}
280280

281-
namespaces, err := ml.buildDefaultNetworkNamespace(tt.taskID)
281+
namespaces, err := ml.buildDefaultNetworkNamespaceConfig(tt.taskID)
282282

283283
if tt.expectedError != nil {
284284
assert.Error(t, err)

ecs-agent/netlib/platform/mocks/platform_mocks.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)