Skip to content

Commit fb297f4

Browse files
committed
Implement network namespacing
Signed-off-by: apostasie <[email protected]>
1 parent 4100ec5 commit fb297f4

18 files changed

+140
-40
lines changed

cmd/nerdctl/completion.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func shellCompleteNetworkNames(cmd *cobra.Command, exclude []string) ([]string,
109109
excludeMap[ex] = struct{}{}
110110
}
111111

112-
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath)
112+
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(globalOptions.Namespace))
113113
if err != nil {
114114
return nil, cobra.ShellCompDirectiveError
115115
}

cmd/nerdctl/network_inspect_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,24 @@ func TestNetworkInspect(t *testing.T) {
6666
}
6767
assert.DeepEqual(base.T, expectedIPAM, got.IPAM)
6868
}
69+
70+
func TestNetworkInspectWithNamespace(t *testing.T) {
71+
if testutil.GetTarget() == testutil.Docker {
72+
t.Skip("test skipped for docker")
73+
}
74+
75+
t.Parallel()
76+
77+
base := testutil.NewBase(t)
78+
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
79+
networkName := testutil.Identifier(t)
80+
81+
tearDown := func() {
82+
base.Cmd("network", "rm", networkName).Run()
83+
}
84+
tearDown()
85+
t.Cleanup(tearDown)
86+
87+
base.Cmd("network", "create", networkName).AssertOK()
88+
baseOther.Cmd("network", "inspect", networkName).AssertFail()
89+
}

cmd/nerdctl/network_list_linux_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,24 @@ func TestNetworkLsFilter(t *testing.T) {
7474
return nil
7575
})
7676
}
77+
78+
func TestNetworkLsWithNamespace(t *testing.T) {
79+
if testutil.GetTarget() == testutil.Docker {
80+
t.Skip("test skipped for docker")
81+
}
82+
83+
t.Parallel()
84+
85+
base := testutil.NewBase(t)
86+
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
87+
networkName := testutil.Identifier(t)
88+
89+
tearDown := func() {
90+
base.Cmd("network", "rm", networkName).Run()
91+
}
92+
tearDown()
93+
t.Cleanup(tearDown)
94+
95+
base.Cmd("network", "create", networkName).AssertOK()
96+
baseOther.Cmd("network", "ls").AssertOutNotContains(networkName)
97+
}

cmd/nerdctl/network_prune_linux_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,24 @@ func TestNetworkPrune(t *testing.T) {
3636
base.Cmd("stop", tID).AssertOK()
3737
base.Cmd("network", "prune", "-f").AssertOutContains(testNetwork)
3838
}
39+
40+
func TestNetworkPruneWithNamespace(t *testing.T) {
41+
if testutil.GetTarget() == testutil.Docker {
42+
t.Skip("test skipped for docker")
43+
}
44+
45+
t.Parallel()
46+
47+
base := testutil.NewBase(t)
48+
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
49+
networkName := testutil.Identifier(t)
50+
51+
tearDown := func() {
52+
base.Cmd("network", "rm", networkName).Run()
53+
}
54+
tearDown()
55+
t.Cleanup(tearDown)
56+
57+
base.Cmd("network", "create", networkName).AssertOK()
58+
baseOther.Cmd("network", "prune", "-f").AssertOutNotContains(networkName)
59+
}

cmd/nerdctl/network_remove_linux_test.go

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,6 @@ import (
2525
"gotest.tools/v3/assert"
2626
)
2727

28-
func TestNetworkRemoveInOtherNamespace(t *testing.T) {
29-
if rootlessutil.IsRootless() {
30-
t.Skip("test skipped for remove rootless network")
31-
}
32-
if testutil.GetTarget() == testutil.Docker {
33-
t.Skip("test skipped for docker")
34-
}
35-
// --namespace=nerdctl-test
36-
base := testutil.NewBase(t)
37-
// --namespace=nerdctl-other
38-
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
39-
networkName := testutil.Identifier(t)
40-
41-
base.Cmd("network", "create", networkName).AssertOK()
42-
defer base.Cmd("network", "rm", networkName).AssertOK()
43-
44-
tID := testutil.Identifier(t)
45-
base.Cmd("run", "-d", "--net", networkName, "--name", tID, testutil.AlpineImage, "sleep", "infinity").AssertOK()
46-
defer base.Cmd("rm", "-f", tID).Run()
47-
48-
// delete network in namespace nerdctl-other
49-
baseOther.Cmd("network", "rm", networkName).AssertFail()
50-
}
51-
5228
func TestNetworkRemove(t *testing.T) {
5329
if rootlessutil.IsRootless() {
5430
t.Skip("test skipped for remove rootless network")
@@ -136,3 +112,29 @@ func TestNetworkRemoveByShortId(t *testing.T) {
136112
_, err = netlink.LinkByName("br-" + networkID[:12])
137113
assert.Error(t, err, "Link not found")
138114
}
115+
116+
func TestNetworkRemoveWithNamespace(t *testing.T) {
117+
if rootlessutil.IsRootless() {
118+
t.Skip("test skipped for remove rootless network")
119+
}
120+
if testutil.GetTarget() == testutil.Docker {
121+
t.Skip("test skipped for docker")
122+
}
123+
124+
t.Parallel()
125+
// --namespace=nerdctl-test
126+
base := testutil.NewBase(t)
127+
// --namespace=nerdctl-other
128+
baseOther := testutil.NewBaseWithNamespace(t, "nerdctl-other")
129+
networkName := testutil.Identifier(t)
130+
131+
tearDown := func() {
132+
base.Cmd("network", "rm", networkName).Run()
133+
}
134+
tearDown()
135+
t.Cleanup(tearDown)
136+
137+
base.Cmd("network", "create", networkName).AssertOK()
138+
// delete network in namespace nerdctl-other should fail
139+
baseOther.Cmd("network", "rm", networkName).AssertFail()
140+
}

docs/cni.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,16 @@ For example:
116116
## Custom networks
117117

118118
You can also customize your CNI network by providing configuration files.
119-
For example you have one configuration file(`/etc/cni/net.d/10-mynet.conf`)
119+
120+
When rootful, the expected root location is `/etc/cni/net.d`.
121+
For rootless, the expected root location is `~/.config/cni/net.d/`
122+
123+
Configuration files (like `10-mynet.conf`) can be placed either in the root location,
124+
or under a subfolder.
125+
If in the root location, this network will be available to all nerdctl namespaces.
126+
If placed in a subfolder, it will be available only to the identically named namespace.
127+
128+
For example, you have one configuration file(`/etc/cni/net.d/10-mynet.conf`)
120129
for `bridge` network:
121130

122131
```json
@@ -138,7 +147,7 @@ for `bridge` network:
138147
```
139148

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

143152
```console
144153
# nerdctl run -it --net mynet --rm alpine ip addr show

docs/dir.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,9 @@ Data volume
6565

6666
Can be overridden with `nerdctl --cni-netconfpath=<NETCONFPATH>` flag and environment variable `$NETCONFPATH`.
6767

68+
At the top-level of <NETCONFPATH>, network (files) are shared accross all namespaces.
69+
Sub-folders inside <NETCONFPATH> are only available to the namespace bearing the same name,
70+
and its networks definitions are private.
71+
6872
Files:
6973
- `nerdctl-<NWNAME>.conflist`: CNI conf list created by nerdctl

pkg/cmd/compose/compose.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import (
4141

4242
// New returns a new *composer.Composer.
4343
func New(client *containerd.Client, globalOptions types.GlobalCommandOptions, options composer.Options, stdout, stderr io.Writer) (*composer.Composer, error) {
44-
cniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
44+
cniEnv, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(globalOptions.Namespace), netutil.WithDefaultNetwork())
4545
if err != nil {
4646
return nil, err
4747
}

pkg/cmd/container/kill.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func cleanupNetwork(ctx context.Context, container containerd.Container, globalO
152152
case nettype.Host, nettype.None, nettype.Container:
153153
// NOP
154154
case nettype.CNI:
155-
e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithDefaultNetwork())
155+
e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithNamespace(globalOpts.Namespace), netutil.WithDefaultNetwork())
156156
if err != nil {
157157
return err
158158
}

pkg/cmd/network/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func Create(options types.NetworkCreateOptions, stdout io.Writer) error {
3333
options.Subnets = []string{""}
3434
}
3535

36-
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
36+
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
3737
if err != nil {
3838
return err
3939
}

pkg/cmd/network/inspect.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232

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

3737
if err != nil {
3838
return err

pkg/cmd/network/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func List(ctx context.Context, options types.NetworkListOptions) error {
6565
}
6666
}
6767

68-
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath)
68+
e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
6969
if err != nil {
7070
return err
7171
}

pkg/cmd/network/prune.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
)
2929

3030
func Prune(ctx context.Context, client *containerd.Client, options types.NetworkPruneOptions) error {
31-
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
31+
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
3232
if err != nil {
3333
return err
3434
}

pkg/cmd/network/remove.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
)
2828

2929
func Remove(ctx context.Context, client *containerd.Client, options types.NetworkRemoveOptions) error {
30-
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath)
30+
e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace))
3131
if err != nil {
3232
return err
3333
}

pkg/containerutil/container_network_manager_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type cniNetworkManagerPlatform struct {
3939

4040
// Verifies that the internal network settings are correct.
4141
func (m *cniNetworkManager) VerifyNetworkOptions(_ context.Context) error {
42-
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
42+
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
4343
if err != nil {
4444
return err
4545
}

pkg/containerutil/container_network_manager_windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type cniNetworkManagerPlatform struct {
3636

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

6969
func (m *cniNetworkManager) getCNI() (gocni.CNI, error) {
70-
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithDefaultNetwork())
70+
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
7171
if err != nil {
7272
return nil, fmt.Errorf("failed to instantiate CNI env: %s", err)
7373
}

pkg/netutil/netutil.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646
type CNIEnv struct {
4747
Path string
4848
NetconfPath string
49+
Namespace string
4950
}
5051

5152
type CNIEnvOpt func(e *CNIEnv) error
@@ -132,6 +133,16 @@ func WithDefaultNetwork() CNIEnvOpt {
132133
}
133134
}
134135

136+
func WithNamespace(namespace string) CNIEnvOpt {
137+
return func(e *CNIEnv) error {
138+
if err := os.MkdirAll(filepath.Join(e.NetconfPath, namespace), 0755); err != nil {
139+
return err
140+
}
141+
e.Namespace = namespace
142+
return nil
143+
}
144+
}
145+
135146
func NewCNIEnv(cniPath, cniConfPath string, opts ...CNIEnvOpt) (*CNIEnv, error) {
136147
e := CNIEnv{
137148
Path: cniPath,
@@ -193,7 +204,10 @@ func (e *CNIEnv) FilterNetworks(filterf func(*NetworkConfig) bool) ([]*NetworkCo
193204
}
194205

195206
func (e *CNIEnv) getConfigPathForNetworkName(netName string) string {
196-
return filepath.Join(e.NetconfPath, "nerdctl-"+netName+".conflist")
207+
if netName == DefaultNetworkName || e.Namespace == "" {
208+
return filepath.Join(e.NetconfPath, "nerdctl-"+netName+".conflist")
209+
}
210+
return filepath.Join(e.NetconfPath, e.Namespace, "nerdctl-"+netName+".conflist")
197211
}
198212

199213
func (e *CNIEnv) usedSubnets() ([]*net.IPNet, error) {
@@ -404,10 +418,18 @@ func (e *CNIEnv) writeNetworkConfig(net *NetworkConfig) error {
404418
// networkConfigList loads config from dir if dir exists.
405419
func (e *CNIEnv) networkConfigList() ([]*NetworkConfig, error) {
406420
l := []*NetworkConfig{}
407-
fileNames, err := libcni.ConfFiles(e.NetconfPath, []string{".conf", ".conflist", ".json"})
421+
common, err := libcni.ConfFiles(e.NetconfPath, []string{".conf", ".conflist", ".json"})
408422
if err != nil {
409423
return nil, err
410424
}
425+
namespaced := []string{}
426+
if e.Namespace != "" {
427+
namespaced, err = libcni.ConfFiles(filepath.Join(e.NetconfPath, e.Namespace), []string{".conf", ".conflist", ".json"})
428+
if err != nil {
429+
return nil, err
430+
}
431+
}
432+
fileNames := append(common, namespaced...)
411433
sort.Strings(fileNames)
412434
for _, fileName := range fileNames {
413435
var lcl *libcni.NetworkConfigList

pkg/ocihook/ocihook.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath strin
152152
case nettype.Host, nettype.None, nettype.Container:
153153
// NOP
154154
case nettype.CNI:
155-
e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithDefaultNetwork())
155+
e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithNamespace(namespace), netutil.WithDefaultNetwork())
156156
if err != nil {
157157
return nil, err
158158
}

0 commit comments

Comments
 (0)