diff --git a/docs/command-reference.md b/docs/command-reference.md index 486872071fb..b87db78ddbd 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -174,9 +174,10 @@ Isolation flags: Network flags: -- :whale: `--net, --network=(bridge|host|none|container:|)`: Connect a container to a network. +- :whale: `--net, --network=(bridge|host|none|container:|ns:|)`: Connect a container to a network. - Default: "bridge" - - 'container:': reuse another container's network stack, container has to be precreated. + - `container:`: reuse another container's network stack, container has to be precreated. + - :nerd_face: `ns:`: run inside an existing network namespace - :nerd_face: Unlike Docker, this flag can be specified multiple times (`--net foo --net bar`) - :whale: `-p, --publish`: Publish a container's port(s) to the host - :whale: `--dns`: Set custom DNS servers diff --git a/pkg/cmd/container/kill.go b/pkg/cmd/container/kill.go index 4e53b4f9e3b..b077589473d 100644 --- a/pkg/cmd/container/kill.go +++ b/pkg/cmd/container/kill.go @@ -151,7 +151,7 @@ func cleanupNetwork(ctx context.Context, container containerd.Container, globalO } switch netType { - case nettype.Host, nettype.None, nettype.Container: + case nettype.Host, nettype.None, nettype.Container, nettype.Namespace: // NOP case nettype.CNI: e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithNamespace(globalOpts.Namespace), netutil.WithDefaultNetwork()) diff --git a/pkg/composer/serviceparser/serviceparser.go b/pkg/composer/serviceparser/serviceparser.go index e59f7cc4368..635b93594b4 100644 --- a/pkg/composer/serviceparser/serviceparser.go +++ b/pkg/composer/serviceparser/serviceparser.go @@ -385,7 +385,7 @@ func getNetworks(project *types.Project, svc types.ServiceConfig) ([]networkName return nil, errors.New("net and network_mode must not be set together") } if strings.Contains(svc.NetworkMode, ":") { - if !strings.HasPrefix(svc.NetworkMode, "container:") { + if !strings.HasPrefix(svc.NetworkMode, "container:") && !strings.HasPrefix(svc.NetworkMode, "ns:") { return nil, fmt.Errorf("unsupported network_mode: %q", svc.NetworkMode) } } diff --git a/pkg/containerutil/container_network_manager.go b/pkg/containerutil/container_network_manager.go index 7f7b97b13e4..fe3c33332f7 100644 --- a/pkg/containerutil/container_network_manager.go +++ b/pkg/containerutil/container_network_manager.go @@ -132,6 +132,10 @@ func NewNetworkingOptionsManager(globalOptions types.GlobalCommandOptions, netOp manager = &containerNetworkManager{globalOptions, netOpts, client} case nettype.CNI: manager = &cniNetworkManager{globalOptions, netOpts, client, cniNetworkManagerPlatform{}} + case nettype.Namespace: + // We'll handle Namespace networking identically to Host-mode networking, but + // put the container in the specified network namespace instead of the root. + manager = &hostNetworkManager{globalOptions, netOpts, client} default: return nil, fmt.Errorf("unexpected container networking type: %q", netType) } @@ -491,6 +495,23 @@ func copyFileContent(src string, dst string) error { return nil } +func getHostNetworkingNamespace(netModeArg string) (oci.SpecOpts, error) { + if !strings.Contains(netModeArg, ":") { + // Use the host root namespace by default + return oci.WithHostNamespace(specs.NetworkNamespace), nil + } + + netItems := strings.Split(netModeArg, ":") + if len(netItems) < 2 { + return nil, fmt.Errorf("namespace networking argument format must be 'ns:', got: %q", netModeArg) + } + netnsPath := netItems[1] + return oci.WithLinuxNamespace(specs.LinuxNamespace{ + Type: specs.NetworkNamespace, + Path: netnsPath, + }), nil +} + // ContainerNetworkingOpts Returns a slice of `oci.SpecOpts` and `containerd.NewContainerOpts` which represent // the network specs which need to be applied to the container with the given ID. func (m *hostNetworkManager) ContainerNetworkingOpts(_ context.Context, containerID string) ([]oci.SpecOpts, []containerd.NewContainerOpts, error) { @@ -525,8 +546,13 @@ func (m *hostNetworkManager) ContainerNetworkingOpts(_ context.Context, containe return nil, nil, err } + netModeArg := m.netOpts.NetworkSlice[0] + netNamespace, err := getHostNetworkingNamespace(netModeArg) + if err != nil { + return nil, nil, err + } specs := []oci.SpecOpts{ - oci.WithHostNamespace(specs.NetworkNamespace), + netNamespace, withDedupMounts("/etc/hosts", withCustomHosts(etcHostsPath)), withDedupMounts("/etc/resolv.conf", withCustomResolvConf(resolvConfPath)), } diff --git a/pkg/netutil/nettype/nettype.go b/pkg/netutil/nettype/nettype.go index 721167037c7..d319254260d 100644 --- a/pkg/netutil/nettype/nettype.go +++ b/pkg/netutil/nettype/nettype.go @@ -29,6 +29,7 @@ const ( Host CNI Container + Namespace ) var netTypeToName = map[interface{}]string{ @@ -37,6 +38,7 @@ var netTypeToName = map[interface{}]string{ Host: "host", CNI: "cni", Container: "container", + Namespace: "ns", } func Detect(names []string) (Type, error) { @@ -54,6 +56,8 @@ func Detect(names []string) (Type, error) { tmp = Host case "container": tmp = Container + case "ns": + tmp = Namespace default: tmp = CNI } diff --git a/pkg/ocihook/ocihook.go b/pkg/ocihook/ocihook.go index a98c84d1d37..b1b1068c1bb 100644 --- a/pkg/ocihook/ocihook.go +++ b/pkg/ocihook/ocihook.go @@ -170,7 +170,7 @@ func newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath strin } switch netType { - case nettype.Host, nettype.None, nettype.Container: + case nettype.Host, nettype.None, nettype.Container, nettype.Namespace: // NOP case nettype.CNI: e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithNamespace(namespace), netutil.WithDefaultNetwork())