Skip to content

Commit a518963

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

File tree

6 files changed

+163
-8
lines changed

6 files changed

+163
-8
lines changed

cmd/nerdctl/network/network_create_linux_test.go

Lines changed: 98 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
"github.com/containerd/nerdctl/mod/tigron/tig"
3031

@@ -111,3 +112,100 @@ func TestNetworkCreate(t *testing.T) {
111112

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

docs/command-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,8 @@ Flags:
10751075
- :whale: `-o, --opt`: Set driver specific options
10761076
- :whale: `--opt=com.docker.network.driver.mtu=<MTU>`: Set the containers network MTU
10771077
- :nerd_face: `--opt=mtu=<MTU>`: Alias of `--opt=com.docker.network.driver.mtu=<MTU>`
1078+
- :whale: `--opt=com.docker.network.bridge.enable_icc=<true/false>`: Enable or Disable inter-container connectivity
1079+
- :nerd_face: `--opt=icc=<true/false>`: Alias of `--opt=com.docker.network.bridge.enable_icc`
10781080
- :whale: `--opt=macvlan_mode=(bridge)>`: Set macvlan network mode (default: bridge)
10791081
- :whale: `--opt=ipvlan_mode=(l2|l3)`: Set IPvlan network mode (default: l2)
10801082
- :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
@@ -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)