Skip to content

Commit

Permalink
Implement network namespacing
Browse files Browse the repository at this point in the history
Signed-off-by: apostasie <spam_blackhole@farcloser.world>
  • Loading branch information
apostasie committed Jun 19, 2024
1 parent c08b171 commit e910f8a
Show file tree
Hide file tree
Showing 17 changed files with 136 additions and 40 deletions.
2 changes: 1 addition & 1 deletion cmd/nerdctl/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func shellCompleteNetworkNames(cmd *cobra.Command, exclude []string) ([]string,
excludeMap[ex] = struct{}{}
}

e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(globalOptions.Namespace))
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
Expand Down
21 changes: 21 additions & 0 deletions cmd/nerdctl/network_inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,24 @@ func TestNetworkInspect(t *testing.T) {
}
assert.DeepEqual(base.T, expectedIPAM, got.IPAM)
}

func TestNetworkInspectWithNamespace(t *testing.T) {
if testutil.GetTarget() == testutil.Docker {
t.Skip("test skipped for docker")
}

t.Parallel()

base := testutil.NewBase(t)
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
networkName := testutil.Identifier(t)

tearDown := func() {
base.Cmd("network", "rm", networkName).Run()
}
tearDown()
t.Cleanup(tearDown)

base.Cmd("network", "create", networkName).AssertOK()
baseOther.Cmd("network", "inspect", networkName).AssertFail()
}
21 changes: 21 additions & 0 deletions cmd/nerdctl/network_list_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,24 @@ func TestNetworkLsFilter(t *testing.T) {
return nil
})
}

func TestNetworkLsWithNamespace(t *testing.T) {
if testutil.GetTarget() == testutil.Docker {
t.Skip("test skipped for docker")
}

t.Parallel()

base := testutil.NewBase(t)
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
networkName := testutil.Identifier(t)

tearDown := func() {
base.Cmd("network", "rm", networkName).Run()
}
tearDown()
t.Cleanup(tearDown)

base.Cmd("network", "create", networkName).AssertOK()
baseOther.Cmd("network", "ls").AssertOutNotContains(networkName)
}
21 changes: 21 additions & 0 deletions cmd/nerdctl/network_prune_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,24 @@ func TestNetworkPrune(t *testing.T) {
base.Cmd("stop", tID).AssertOK()
base.Cmd("network", "prune", "-f").AssertOutContains(testNetwork)
}

func TestNetworkPruneWithNamespace(t *testing.T) {
if testutil.GetTarget() == testutil.Docker {
t.Skip("test skipped for docker")
}

t.Parallel()

base := testutil.NewBase(t)
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
networkName := testutil.Identifier(t)

tearDown := func() {
base.Cmd("network", "rm", networkName).Run()
}
tearDown()
t.Cleanup(tearDown)

base.Cmd("network", "create", networkName).AssertOK()
baseOther.Cmd("network", "prune", "-f").AssertOutNotContains(networkName)
}
50 changes: 26 additions & 24 deletions cmd/nerdctl/network_remove_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,6 @@ import (
"gotest.tools/v3/assert"
)

func TestNetworkRemoveInOtherNamespace(t *testing.T) {
if rootlessutil.IsRootless() {
t.Skip("test skipped for remove rootless network")
}
if testutil.GetTarget() == testutil.Docker {
t.Skip("test skipped for docker")
}
// --namespace=nerdctl-test
base := testutil.NewBase(t)
// --namespace=nerdctl-other
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
networkName := testutil.Identifier(t)

base.Cmd("network", "create", networkName).AssertOK()
defer base.Cmd("network", "rm", networkName).AssertOK()

tID := testutil.Identifier(t)
base.Cmd("run", "-d", "--net", networkName, "--name", tID, testutil.AlpineImage, "sleep", "infinity").AssertOK()
defer base.Cmd("rm", "-f", tID).Run()

// delete network in namespace nerdctl-other
baseOther.Cmd("network", "rm", networkName).AssertFail()
}

func TestNetworkRemove(t *testing.T) {
if rootlessutil.IsRootless() {
t.Skip("test skipped for remove rootless network")
Expand Down Expand Up @@ -136,3 +112,29 @@ func TestNetworkRemoveByShortId(t *testing.T) {
_, err = netlink.LinkByName("br-" + networkID[:12])
assert.Error(t, err, "Link not found")
}

func TestNetworkRemoveWithNamespace(t *testing.T) {
if rootlessutil.IsRootless() {
t.Skip("test skipped for remove rootless network")
}
if testutil.GetTarget() == testutil.Docker {
t.Skip("test skipped for docker")
}

t.Parallel()
// --namespace=nerdctl-test
base := testutil.NewBase(t)
// --namespace=nerdctl-other
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
networkName := testutil.Identifier(t)

tearDown := func() {
base.Cmd("network", "rm", networkName).Run()
}
tearDown()
t.Cleanup(tearDown)

base.Cmd("network", "create", networkName).AssertOK()
// delete network in namespace nerdctl-other should fail
baseOther.Cmd("network", "rm", networkName).AssertFail()
}
13 changes: 11 additions & 2 deletions docs/cni.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,16 @@ For example:
## Custom networks

You can also customize your CNI network by providing configuration files.
For example you have one configuration file(`/etc/cni/net.d/10-mynet.conf`)

When rootful, the expected root location is `/etc/cni/net.d`.
For rootless, the expected root location is `~/.config/cni/net.d/`

Configuration files (like `10-mynet.conf`) can be placed either in the root location,
or under a subfolder.
If in the root location, this network will be available to all nerdctl namespaces.
If placed in a subfolder, it will be available only to the identically named namespace.

For example, you have one configuration file(`/etc/cni/net.d/10-mynet.conf`)
for `bridge` network:

```json
Expand All @@ -138,7 +147,7 @@ for `bridge` network:
```

This will configure a new CNI network with the name `mynet`, and you can use
this network to create a container:
this network to create a container in any namespace:

```console
# nerdctl run -it --net mynet --rm alpine ip addr show
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (

// New returns a new *composer.Composer.
func New(client *containerd.Client, globalOptions types.GlobalCommandOptions, options composer.Options, stdout, stderr io.Writer) (*composer.Composer, error) {
cniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
cniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(globalOptions.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/container/kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func cleanupNetwork(ctx context.Context, container containerd.Container, globalO
case nettype.Host, nettype.None, nettype.Container:
// NOP
case nettype.CNI:
e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithNamespace(globalOpts.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func Create(options types.NetworkCreateOptions, stdout io.Writer) error {
options.Subnets = []string{""}
}

e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

func Inspect(ctx context.Context, options types.NetworkInspectOptions) error {
globalOptions := options.GOptions
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))

if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func List(ctx context.Context, options types.NetworkListOptions) error {
}
}

e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
)

func Prune(ctx context.Context, client *containerd.Client, options types.NetworkPruneOptions) error {
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
)

func Remove(ctx context.Context, client *containerd.Client, options types.NetworkRemoveOptions) error {
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/containerutil/container_network_manager_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type cniNetworkManagerPlatform struct {

// Verifies that the internal network settings are correct.
func (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/containerutil/container_network_manager_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type cniNetworkManagerPlatform struct {

// Verifies that the internal network settings are correct.
func (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return err
}
Expand Down Expand Up @@ -67,7 +67,7 @@ func (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {
}

func (m *cniNetworkManager) getCNI() (gocni.CNI, error) {
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
if err != nil {
return nil, fmt.Errorf("failed to instantiate CNI env: %s", err)
}
Expand Down
26 changes: 24 additions & 2 deletions pkg/netutil/netutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
type CNIEnv struct {
Path string
NetconfPath string
Namespace string
}

type CNIEnvOpt func(e *CNIEnv) error
Expand Down Expand Up @@ -132,6 +133,16 @@ func WithDefaultNetwork() CNIEnvOpt {
}
}

func WithNamespace(namespace string) CNIEnvOpt {
return func(e *CNIEnv) error {
if err := os.MkdirAll(filepath.Join(e.NetconfPath, namespace), 0755); err != nil {
return err
}
e.Namespace = namespace
return nil
}
}

func NewCNIEnv(cniPath, cniConfPath string, opts ...CNIEnvOpt) (*CNIEnv, error) {
e := CNIEnv{
Path: cniPath,
Expand Down Expand Up @@ -193,7 +204,10 @@ func (e *CNIEnv) FilterNetworks(filterf func(*NetworkConfig) bool) ([]*NetworkCo
}

func (e *CNIEnv) getConfigPathForNetworkName(netName string) string {
return filepath.Join(e.NetconfPath, "nerdctl-"+netName+".conflist")
if netName == DefaultNetworkName || e.Namespace == "" {
return filepath.Join(e.NetconfPath, "nerdctl-"+netName+".conflist")
}
return filepath.Join(e.NetconfPath, e.Namespace, "nerdctl-"+netName+".conflist")
}

func (e *CNIEnv) usedSubnets() ([]*net.IPNet, error) {
Expand Down Expand Up @@ -404,10 +418,18 @@ func (e *CNIEnv) writeNetworkConfig(net *NetworkConfig) error {
// networkConfigList loads config from dir if dir exists.
func (e *CNIEnv) networkConfigList() ([]*NetworkConfig, error) {
l := []*NetworkConfig{}
fileNames, err := libcni.ConfFiles(e.NetconfPath, []string{".conf", ".conflist", ".json"})
common, err := libcni.ConfFiles(e.NetconfPath, []string{".conf", ".conflist", ".json"})
if err != nil {
return nil, err
}
namespaced := []string{}
if e.Namespace != "" {
namespaced, err = libcni.ConfFiles(filepath.Join(e.NetconfPath, e.Namespace), []string{".conf", ".conflist", ".json"})
if err != nil {
return nil, err
}
}
fileNames := append(common, namespaced...)
sort.Strings(fileNames)
for _, fileName := range fileNames {
var lcl *libcni.NetworkConfigList
Expand Down
2 changes: 1 addition & 1 deletion pkg/ocihook/ocihook.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath strin
case nettype.Host, nettype.None, nettype.Container:
// NOP
case nettype.CNI:
e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithDefaultNetwork())
e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithNamespace(namespace), netutil.WithDefaultNetwork())
if err != nil {
return nil, err
}
Expand Down

0 comments on commit e910f8a

Please sign in to comment.