Skip to content

Commit f23b09f

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

File tree

6 files changed

+161
-7
lines changed

6 files changed

+161
-7
lines changed

cmd/nerdctl/network/network_create_linux_test.go

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

2828
"github.com/containerd/nerdctl/mod/tigron/expect"
29+
"github.com/containerd/nerdctl/mod/tigron/require"
2930
"github.com/containerd/nerdctl/mod/tigron/test"
3031
"github.com/containerd/nerdctl/mod/tigron/tig"
3132

@@ -159,3 +160,99 @@ func TestNetworkCreate(t *testing.T) {
159160

160161
testCase.Run(t)
161162
}
163+
164+
func TestNetworkCreateICC(t *testing.T) {
165+
testCase := nerdtest.Setup()
166+
167+
testCase.Require = require.All(
168+
require.Linux,
169+
)
170+
171+
testCase.SubTests = []*test.Case{
172+
{
173+
Description: "with enable_icc=false",
174+
Require: nerdtest.CNIFirewallVersion("1.7.1"),
175+
NoParallel: true,
176+
Setup: func(data test.Data, helpers test.Helpers) {
177+
// Create a network with ICC disabled
178+
helpers.Ensure("network", "create", data.Identifier(), "--driver", "bridge",
179+
"--opt", "com.docker.network.bridge.enable_icc=false")
180+
181+
// Run a container in that network
182+
data.Labels().Set("container1", helpers.Capture("run", "-d", "--net", data.Identifier(),
183+
"--name", data.Identifier("c1"), testutil.CommonImage, "sleep", "infinity"))
184+
185+
// Wait for container to be running
186+
nerdtest.EnsureContainerStarted(helpers, data.Identifier("c1"))
187+
},
188+
Cleanup: func(data test.Data, helpers test.Helpers) {
189+
helpers.Anyhow("container", "rm", "-f", data.Identifier("c1"))
190+
helpers.Anyhow("network", "rm", data.Identifier())
191+
},
192+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
193+
// DEBUG
194+
iptablesSave := "iptables-save | grep CNI-ISOLATION || true"
195+
helpers.Custom("sh", "-ec", iptablesSave).Run(&test.Expected{})
196+
// Try to ping the other container in the same network
197+
// This should fail when ICC is disabled
198+
return helpers.Command("run", "--rm", "--net", data.Identifier(),
199+
testutil.CommonImage, "ping", "-c", "1", "-W", "1", data.Identifier("c1"))
200+
},
201+
Expected: test.Expects(expect.ExitCodeGenericFail, nil, nil), // Expect ping to fail with exit code 1
202+
},
203+
{
204+
Description: "with enable_icc=true",
205+
Require: nerdtest.CNIFirewallVersion("1.7.1"),
206+
NoParallel: true,
207+
Setup: func(data test.Data, helpers test.Helpers) {
208+
// Create a network with ICC enabled (default)
209+
helpers.Ensure("network", "create", data.Identifier(), "--driver", "bridge",
210+
"--opt", "com.docker.network.bridge.enable_icc=true")
211+
212+
// Run a container in that network
213+
data.Labels().Set("container1", helpers.Capture("run", "-d", "--net", data.Identifier(),
214+
"--name", data.Identifier("c1"), testutil.CommonImage, "sleep", "infinity"))
215+
// Wait for container to be running
216+
nerdtest.EnsureContainerStarted(helpers, data.Identifier("c1"))
217+
},
218+
Cleanup: func(data test.Data, helpers test.Helpers) {
219+
helpers.Anyhow("container", "rm", "-f", data.Identifier("c1"))
220+
helpers.Anyhow("network", "rm", data.Identifier())
221+
},
222+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
223+
// Try to ping the other container in the same network
224+
// This should succeed when ICC is enabled
225+
return helpers.Command("run", "--rm", "--net", data.Identifier(),
226+
testutil.CommonImage, "ping", "-c", "1", "-W", "1", data.Identifier("c1"))
227+
},
228+
Expected: test.Expects(0, nil, nil), // Expect ping to succeed with exit code 0
229+
},
230+
{
231+
Description: "with no enable_icc option set",
232+
NoParallel: true,
233+
Setup: func(data test.Data, helpers test.Helpers) {
234+
// Create a network with ICC enabled (default)
235+
helpers.Ensure("network", "create", data.Identifier(), "--driver", "bridge")
236+
237+
// Run a container in that network
238+
data.Labels().Set("container1", helpers.Capture("run", "-d", "--net", data.Identifier(),
239+
"--name", data.Identifier("c1"), testutil.CommonImage, "sleep", "infinity"))
240+
// Wait for container to be running
241+
nerdtest.EnsureContainerStarted(helpers, data.Identifier("c1"))
242+
},
243+
Cleanup: func(data test.Data, helpers test.Helpers) {
244+
helpers.Anyhow("container", "rm", "-f", data.Identifier("c1"))
245+
helpers.Anyhow("network", "rm", data.Identifier())
246+
},
247+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
248+
// Try to ping the other container in the same network
249+
// This should succeed when no ICC is set
250+
return helpers.Command("run", "--rm", "--net", data.Identifier(),
251+
testutil.CommonImage, "ping", "-c", "1", "-W", "1", data.Identifier("c1"))
252+
},
253+
Expected: test.Expects(0, nil, nil), // Expect ping to succeed with exit code 0
254+
},
255+
}
256+
257+
testCase.Run(t)
258+
}

docs/command-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,8 @@ Flags:
11931193
- :whale: `-o, --opt`: Set driver specific options
11941194
- :whale: `--opt=com.docker.network.driver.mtu=<MTU>`: Set the containers network MTU
11951195
- :nerd_face: `--opt=mtu=<MTU>`: Alias of `--opt=com.docker.network.driver.mtu=<MTU>`
1196+
- :whale: `--opt=com.docker.network.bridge.enable_icc=<true/false>`: Enable or Disable inter-container connectivity
1197+
- :nerd_face: `--opt=icc=<true/false>`: Alias of `--opt=com.docker.network.bridge.enable_icc`
11961198
- :whale: `--opt=macvlan_mode=(bridge)>`: Set macvlan network mode (default: bridge)
11971199
- :whale: `--opt=ipvlan_mode=(l2|l3)`: Set IPvlan network mode (default: l2)
11981200
- :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: 27 additions & 5 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
}
@@ -133,14 +139,29 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]
133139
if ipv6 {
134140
bridge.Capabilities["ips"] = true
135141
}
142+
143+
// Determine the appropriate firewall ingress policy based on icc setting
144+
ingressPolicy := "same-bridge" // Default policy
145+
firewallPath := filepath.Join(e.Path, "firewall")
146+
if !icc {
147+
// Check if firewall plugin supports the "isolated" policy (v1.7.1+)
148+
ok, err := FirewallPluginGEQVersion(firewallPath, "v1.7.1")
149+
if err != nil {
150+
log.L.WithError(err).Warnf("Failed to detect whether %q is newer than v1.7.1", firewallPath)
151+
} else if ok {
152+
ingressPolicy = "isolated"
153+
} else {
154+
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)
155+
}
156+
}
157+
136158
if internal {
137159
plugins = []CNIPlugin{bridge, newFirewallPlugin(), newTuningPlugin()}
138160
} else {
139161
plugins = []CNIPlugin{bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin()}
140162
}
141163
if name != DefaultNetworkName {
142-
firewallPath := filepath.Join(e.Path, "firewall")
143-
ok, err := firewallPluginGEQ110(firewallPath)
164+
ok, err := FirewallPluginGEQVersion(firewallPath, "v1.1.0")
144165
if err != nil {
145166
log.L.WithError(err).Warnf("Failed to detect whether %q is newer than v1.1.0", firewallPath)
146167
}
@@ -291,7 +312,8 @@ func (e *CNIEnv) parseIPAMRanges(subnets []string, gateway, ipRange string, ipv6
291312
return ranges, findIPv4, nil
292313
}
293314

294-
func firewallPluginGEQ110(firewallPath string) (bool, error) {
315+
// FirewallPluginGEQVersion checks if the firewall plugin is greater than or equal to the specified version
316+
func FirewallPluginGEQVersion(firewallPath string, versionStr string) (bool, error) {
295317
// TODO: guess true by default in 2023
296318
guessed := false
297319

@@ -320,8 +342,8 @@ func firewallPluginGEQ110(firewallPath string) (bool, error) {
320342
if err != nil {
321343
return guessed, fmt.Errorf("failed to guess the version of %q: %w", firewallPath, err)
322344
}
323-
ver110 := semver.MustParse("v1.1.0")
324-
return ver.GreaterThan(ver110) || ver.Equal(ver110), nil
345+
targetVer := semver.MustParse(versionStr)
346+
return ver.GreaterThan(targetVer) || ver.Equal(targetVer), nil
325347
}
326348

327349
// 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
@@ -21,6 +21,7 @@ import (
2121
"encoding/json"
2222
"fmt"
2323
"os/exec"
24+
"path/filepath"
2425
"strings"
2526

2627
"github.com/Masterminds/semver/v3"
@@ -32,8 +33,10 @@ import (
3233

3334
"github.com/containerd/nerdctl/v2/pkg/buildkitutil"
3435
"github.com/containerd/nerdctl/v2/pkg/clientutil"
36+
ncdefaults "github.com/containerd/nerdctl/v2/pkg/defaults"
3537
"github.com/containerd/nerdctl/v2/pkg/infoutil"
3638
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
39+
"github.com/containerd/nerdctl/v2/pkg/netutil"
3740
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
3841
"github.com/containerd/nerdctl/v2/pkg/snapshotterutil"
3942
"github.com/containerd/nerdctl/v2/pkg/testutil"
@@ -447,3 +450,23 @@ func ContainerdVersion(v string) *test.Requirement {
447450
},
448451
}
449452
}
453+
454+
// CNIFirewallVersion checks if the CNI firewall plugin version is greater than or equal to the specified version
455+
func CNIFirewallVersion(requiredVersion string) *test.Requirement {
456+
return &test.Requirement{
457+
Check: func(data test.Data, helpers test.Helpers) (bool, string) {
458+
cniPath := ncdefaults.CNIPath()
459+
firewallPath := filepath.Join(cniPath, "firewall")
460+
ok, err := netutil.FirewallPluginGEQVersion(firewallPath, requiredVersion)
461+
if err != nil {
462+
return false, fmt.Sprintf("Failed to check CNI firewall version: %v", err)
463+
}
464+
465+
if !ok {
466+
return false, fmt.Sprintf("CNI firewall plugin version is less than required version %s", requiredVersion)
467+
}
468+
469+
return true, fmt.Sprintf("CNI firewall plugin version is greater than or equal to required version %s", requiredVersion)
470+
},
471+
}
472+
}

0 commit comments

Comments
 (0)