Skip to content

Commit bea516f

Browse files
committed
feat: add support for com.docker.network.bridge.enable_icc network option
Signed-off-by: Swagat Bora <[email protected]>
1 parent 401800a commit bea516f

File tree

6 files changed

+160
-8
lines changed

6 files changed

+160
-8
lines changed

cmd/nerdctl/network/network_create_linux_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"gotest.tools/v3/assert"
2626

2727
"github.com/containerd/nerdctl/mod/tigron/expect"
28+
"github.com/containerd/nerdctl/mod/tigron/require"
2829
"github.com/containerd/nerdctl/mod/tigron/test"
2930

3031
"github.com/containerd/nerdctl/v2/pkg/testutil"
@@ -110,3 +111,97 @@ func TestNetworkCreate(t *testing.T) {
110111

111112
testCase.Run(t)
112113
}
114+
115+
func TestNetworkCreateICC(t *testing.T) {
116+
testCase := nerdtest.Setup()
117+
118+
testCase.Require = require.All(
119+
require.Linux,
120+
nerdtest.Rootful,
121+
)
122+
123+
testCase.SubTests = []*test.Case{
124+
{
125+
Description: "with enable_icc=false",
126+
Require: nerdtest.CNIFirewallVersionGE("1.7.1"),
127+
NoParallel: true,
128+
Setup: func(data test.Data, helpers test.Helpers) {
129+
// Create a network with ICC disabled
130+
helpers.Ensure("network", "create", data.Identifier(), "--driver", "bridge",
131+
"--opt", "com.docker.network.bridge.enable_icc=false")
132+
133+
// Run a container in that network
134+
data.Labels().Set("container1", helpers.Capture("run", "-d", "--net", data.Identifier(),
135+
"--name", data.Identifier("c1"), testutil.CommonImage, "sleep", "infinity"))
136+
137+
// Wait for container to be running
138+
nerdtest.EnsureContainerStarted(helpers, data.Identifier("c1"))
139+
},
140+
Cleanup: func(data test.Data, helpers test.Helpers) {
141+
helpers.Anyhow("container", "rm", "-f", data.Identifier("c1"))
142+
helpers.Anyhow("network", "rm", data.Identifier())
143+
},
144+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
145+
// Try to ping the other container in the same network
146+
// This should fail when ICC is disabled
147+
return helpers.Command("run", "--rm", "--net", data.Identifier(),
148+
testutil.CommonImage, "ping", "-c", "1", "-W", "1", data.Identifier("c1"))
149+
},
150+
Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), // Expect ping to fail with exit code 1
151+
},
152+
{
153+
Description: "with enable_icc=true",
154+
Require: nerdtest.CNIFirewallVersionGE("1.7.1"),
155+
NoParallel: true,
156+
Setup: func(data test.Data, helpers test.Helpers) {
157+
// Create a network with ICC enabled (default)
158+
helpers.Ensure("network", "create", data.Identifier(), "--driver", "bridge",
159+
"--opt", "com.docker.network.bridge.enable_icc=true")
160+
161+
// Run a container in that network
162+
data.Labels().Set("container1", helpers.Capture("run", "-d", "--net", data.Identifier(),
163+
"--name", data.Identifier("c1"), testutil.CommonImage, "sleep", "infinity"))
164+
// Wait for container to be running
165+
nerdtest.EnsureContainerStarted(helpers, data.Identifier("c1"))
166+
},
167+
Cleanup: func(data test.Data, helpers test.Helpers) {
168+
helpers.Anyhow("container", "rm", "-f", data.Identifier("c1"))
169+
helpers.Anyhow("network", "rm", data.Identifier())
170+
},
171+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
172+
// Try to ping the other container in the same network
173+
// This should succeed when ICC is enabled
174+
return helpers.Command("run", "--rm", "--net", data.Identifier(),
175+
testutil.CommonImage, "ping", "-c", "1", "-W", "1", data.Identifier("c1"))
176+
},
177+
Expected: test.Expects(0, nil, nil), // Expect ping to succeed with exit code 0
178+
},
179+
{
180+
Description: "with no enable_icc option set",
181+
NoParallel: true,
182+
Setup: func(data test.Data, helpers test.Helpers) {
183+
// Create a network with ICC enabled (default)
184+
helpers.Ensure("network", "create", data.Identifier(), "--driver", "bridge")
185+
186+
// Run a container in that network
187+
data.Labels().Set("container1", helpers.Capture("run", "-d", "--net", data.Identifier(),
188+
"--name", data.Identifier("c1"), testutil.CommonImage, "sleep", "infinity"))
189+
// Wait for container to be running
190+
nerdtest.EnsureContainerStarted(helpers, data.Identifier("c1"))
191+
},
192+
Cleanup: func(data test.Data, helpers test.Helpers) {
193+
helpers.Anyhow("container", "rm", "-f", data.Identifier("c1"))
194+
helpers.Anyhow("network", "rm", data.Identifier())
195+
},
196+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
197+
// Try to ping the other container in the same network
198+
// This should succeed when no ICC is set
199+
return helpers.Command("run", "--rm", "--net", data.Identifier(),
200+
testutil.CommonImage, "ping", "-c", "1", "-W", "1", data.Identifier("c1"))
201+
},
202+
Expected: test.Expects(0, nil, nil), // Expect ping to succeed with exit code 0
203+
},
204+
}
205+
206+
testCase.Run(t)
207+
}

docs/command-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,8 @@ Flags:
10511051
- :whale: `-o, --opt`: Set driver specific options
10521052
- :whale: `--opt=com.docker.network.driver.mtu=<MTU>`: Set the containers network MTU
10531053
- :nerd_face: `--opt=mtu=<MTU>`: Alias of `--opt=com.docker.network.driver.mtu=<MTU>`
1054+
- :whale: `--opt=com.docker.network.bridge.enable_icc=<true/false>`: Enable or Disable inter-container connectivity
1055+
- :nerd_face: `--opt=icc=<true/false>`: Alias of `--opt=com.docker.network.bridge.enable_icc`
10541056
- :whale: `--opt=macvlan_mode=(bridge)>`: Set macvlan network mode (default: bridge)
10551057
- :whale: `--opt=ipvlan_mode=(l2|l3)`: Set IPvlan network mode (default: l2)
10561058
- :nerd_face: `--opt=mode=(bridge|l2|l3)`: Alias of `--opt=macvlan_mode=(bridge)` and `--opt=ipvlan_mode=(l2|l3)`

pkg/netutil/cni_plugin_unix.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,18 @@ type firewallConfig struct {
9595

9696
// IngressPolicy is supported since firewall plugin v1.1.0.
9797
// "same-bridge" mode replaces the deprecated "isolation" plugin.
98+
// "isolated" mode has been added since firewall plugin v1.7.1
9899
IngressPolicy string `json:"ingressPolicy,omitempty"`
99100
}
100101

101-
func newFirewallPlugin() *firewallConfig {
102+
func newFirewallPlugin(ingressPolicy string) *firewallConfig {
103+
if ingressPolicy != "same-bridge" && ingressPolicy != "isolated" {
104+
ingressPolicy = "same-bridge" // Default to "same-bridge" if invalid value provided
105+
}
106+
102107
c := &firewallConfig{
103108
PluginType: "firewall",
104-
IngressPolicy: "same-bridge",
109+
IngressPolicy: ingressPolicy,
105110
}
106111
if rootlessutil.IsRootless() {
107112
// https://github.com/containerd/nerdctl/issues/2818

pkg/netutil/netutil_unix.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]
9999
case "bridge":
100100
mtu := 0
101101
iPMasq := true
102+
icc := true
102103
for opt, v := range opts {
103104
switch opt {
104105
case "mtu", "com.docker.network.driver.mtu":
@@ -111,6 +112,11 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]
111112
if err != nil {
112113
return nil, err
113114
}
115+
case "icc", "com.docker.network.bridge.enable_icc":
116+
icc, err = strconv.ParseBool(v)
117+
if err != nil {
118+
return nil, err
119+
}
114120
default:
115121
return nil, fmt.Errorf("unsupported %q network option %q", driver, opt)
116122
}
@@ -129,10 +135,25 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]
129135
if ipv6 {
130136
bridge.Capabilities["ips"] = true
131137
}
132-
plugins = []CNIPlugin{bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin()}
138+
139+
// Determine the appropriate firewall ingress policy based on icc setting
140+
ingressPolicy := "same-bridge" // Default policy
141+
firewallPath := filepath.Join(e.Path, "firewall")
142+
if !icc {
143+
// Check if firewall plugin supports the "isolated" policy (v1.7.1+)
144+
ok, err := FirewallPluginGEQVersion(firewallPath, "v1.7.1")
145+
if err != nil {
146+
log.L.WithError(err).Warnf("Failed to detect whether %q is newer than v1.7.1", firewallPath)
147+
} else if ok {
148+
ingressPolicy = "isolated"
149+
} else {
150+
log.L.Warnf("To use 'isolated' ingress policy, CNI plugin \"firewall\" (>= 1.7.1) needs to be installed in CNI_PATH (%q), see https://www.cni.dev/plugins/current/meta/firewall/", e.Path)
151+
}
152+
}
153+
154+
plugins = []CNIPlugin{bridge, newPortMapPlugin(), newFirewallPlugin(ingressPolicy), newTuningPlugin()}
133155
if name != DefaultNetworkName {
134-
firewallPath := filepath.Join(e.Path, "firewall")
135-
ok, err := firewallPluginGEQ110(firewallPath)
156+
ok, err := FirewallPluginGEQVersion(firewallPath, "v1.1.0")
136157
if err != nil {
137158
log.L.WithError(err).Warnf("Failed to detect whether %q is newer than v1.1.0", firewallPath)
138159
}
@@ -281,7 +302,8 @@ func (e *CNIEnv) parseIPAMRanges(subnets []string, gateway, ipRange string, ipv6
281302
return ranges, findIPv4, nil
282303
}
283304

284-
func firewallPluginGEQ110(firewallPath string) (bool, error) {
305+
// FirewallPluginGEQVersion checks if the firewall plugin is greater than or equal to the specified version
306+
func FirewallPluginGEQVersion(firewallPath string, versionStr string) (bool, error) {
285307
// TODO: guess true by default in 2023
286308
guessed := false
287309

@@ -310,8 +332,8 @@ func firewallPluginGEQ110(firewallPath string) (bool, error) {
310332
if err != nil {
311333
return guessed, fmt.Errorf("failed to guess the version of %q: %w", firewallPath, err)
312334
}
313-
ver110 := semver.MustParse("v1.1.0")
314-
return ver.GreaterThan(ver110) || ver.Equal(ver110), nil
335+
targetVer := semver.MustParse(versionStr)
336+
return ver.GreaterThan(targetVer) || ver.Equal(targetVer), nil
315337
}
316338

317339
// guessFirewallPluginVersion guess the version of the CNI firewall plugin (not the version of the implemented CNI spec).

pkg/netutil/netutil_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package netutil
1818

1919
import (
2020
"encoding/json"
21+
"errors"
2122
"fmt"
2223
"net"
2324

@@ -95,3 +96,7 @@ func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRan
9596
}
9697
return ipam, nil
9798
}
99+
100+
func FirewallPluginGEQVersion(firewallPath string, versionStr string) (bool, error) {
101+
return false, errors.New("unsupported in windows")
102+
}

pkg/testutil/nerdtest/requirements.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"os"
2424
"os/exec"
25+
"path/filepath"
2526
"strings"
2627

2728
"github.com/Masterminds/semver/v3"
@@ -33,8 +34,10 @@ import (
3334

3435
"github.com/containerd/nerdctl/v2/pkg/buildkitutil"
3536
"github.com/containerd/nerdctl/v2/pkg/clientutil"
37+
ncdefaults "github.com/containerd/nerdctl/v2/pkg/defaults"
3638
"github.com/containerd/nerdctl/v2/pkg/infoutil"
3739
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
40+
"github.com/containerd/nerdctl/v2/pkg/netutil"
3841
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
3942
"github.com/containerd/nerdctl/v2/pkg/testutil"
4043
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/platform"
@@ -439,3 +442,23 @@ func ContainerdVersion(v string) *test.Requirement {
439442
},
440443
}
441444
}
445+
446+
// CNIFirewallVersionGE checks if the CNI firewall plugin version is greater than or equal to the specified version
447+
func CNIFirewallVersionGE(requiredVersion string) *test.Requirement {
448+
return &test.Requirement{
449+
Check: func(data test.Data, helpers test.Helpers) (bool, string) {
450+
cniPath := ncdefaults.CNIPath()
451+
firewallPath := filepath.Join(cniPath, "firewall")
452+
ok, err := netutil.FirewallPluginGEQVersion(firewallPath, requiredVersion)
453+
if err != nil {
454+
return false, fmt.Sprintf("Failed to check CNI firewall version: %v", err)
455+
}
456+
457+
if !ok {
458+
return false, fmt.Sprintf("CNI firewall plugin version is less than required version %s", requiredVersion)
459+
}
460+
461+
return true, fmt.Sprintf("CNI firewall plugin version is greater than or equal to required version %s", requiredVersion)
462+
},
463+
}
464+
}

0 commit comments

Comments
 (0)