Skip to content

Commit f1f9766

Browse files
committed
feat: configure custom IP for default bridge network
Signed-off-by: Swagat Bora <[email protected]>
1 parent c71e8d9 commit f1f9766

15 files changed

+147
-20
lines changed

cmd/nerdctl/helpers/flagutil.go

+5
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error)
9999
if err != nil {
100100
return types.GlobalCommandOptions{}, err
101101
}
102+
bridgeIP, err := cmd.Flags().GetString("bridge-ip")
103+
if err != nil {
104+
return types.GlobalCommandOptions{}, err
105+
}
102106
return types.GlobalCommandOptions{
103107
Debug: debug,
104108
DebugFull: debugFull,
@@ -113,6 +117,7 @@ func ProcessRootCmdFlags(cmd *cobra.Command) (types.GlobalCommandOptions, error)
113117
HostsDir: hostsDir,
114118
Experimental: experimental,
115119
HostGatewayIP: hostGatewayIP,
120+
BridgeIP: bridgeIP,
116121
}, nil
117122
}
118123

cmd/nerdctl/internal/internal_oci_hook.go

+2
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ func internalOCIHookAction(cmd *cobra.Command, args []string) error {
5656
}
5757
cniPath := globalOptions.CNIPath
5858
cniNetconfpath := globalOptions.CNINetConfPath
59+
bridgeIP := globalOptions.BridgeIP
5960
return ocihook.Run(os.Stdin, os.Stderr, event,
6061
dataStore,
6162
cniPath,
6263
cniNetconfpath,
64+
bridgeIP,
6365
)
6466
}

cmd/nerdctl/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ func initRootCmdFlags(rootCmd *cobra.Command, tomlPath string) (*pflag.FlagSet,
183183
// Experimental enable experimental feature, see in https://github.com/containerd/nerdctl/blob/main/docs/experimental.md
184184
helpers.AddPersistentBoolFlag(rootCmd, "experimental", nil, nil, cfg.Experimental, "NERDCTL_EXPERIMENTAL", "Control experimental: https://github.com/containerd/nerdctl/blob/main/docs/experimental.md")
185185
helpers.AddPersistentStringFlag(rootCmd, "host-gateway-ip", nil, nil, nil, aliasToBeInherited, cfg.HostGatewayIP, "NERDCTL_HOST_GATEWAY_IP", "IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the host. It has no effect without setting --add-host")
186+
helpers.AddPersistentStringFlag(rootCmd, "bridge-ip", nil, nil, nil, aliasToBeInherited, cfg.BridgeIP, "NERDCTL_BRIDGE_IP", "IP address for the default nerdctl bridge network")
186187
return aliasToBeInherited, nil
187188
}
188189

docs/config.md

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ experimental = true
4545
| `hosts_dir` | `--hosts-dir` | | `certs.d` directory | Since 0.16.0 |
4646
| `experimental` | `--experimental` | `NERDCTL_EXPERIMENTAL` | Enable [experimental features](experimental.md) | Since 0.22.3 |
4747
| `host_gateway_ip` | `--host-gateway-ip` | `NERDCTL_HOST_GATEWAY_IP` | IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the host. It has no effect without setting --add-host | Since 1.3.0 |
48+
| `bridge_ip` | `--bridge-ip` | `NERDCTL_BRIDGE_IP` | IP address for the default nerdctl bridge network | Since 1.7.8 |
4849

4950
The properties are parsed in the following precedence:
5051
1. CLI flag

pkg/cmd/compose/compose.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func New(client *containerd.Client, globalOptions types.GlobalCommandOptions, op
6565
return nil, err
6666
}
6767

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

pkg/cmd/container/kill.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func cleanupNetwork(ctx context.Context, container containerd.Container, globalO
154154
case nettype.Host, nettype.None, nettype.Container, nettype.Namespace:
155155
// NOP
156156
case nettype.CNI:
157-
e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithNamespace(globalOpts.Namespace), netutil.WithDefaultNetwork())
157+
e, err := netutil.NewCNIEnv(globalOpts.CNIPath, globalOpts.CNINetConfPath, netutil.WithNamespace(globalOpts.Namespace), netutil.WithDefaultNetwork(globalOpts.BridgeIP))
158158
if err != nil {
159159
return err
160160
}

pkg/config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Config struct {
3939
HostsDir []string `toml:"hosts_dir"`
4040
Experimental bool `toml:"experimental"`
4141
HostGatewayIP string `toml:"host_gateway_ip"`
42+
BridgeIP string `toml:"bridge_ip, omitempty"`
4243
}
4344

4445
// New creates a default Config object statically,

pkg/containerutil/container_network_manager_linux.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type cniNetworkManagerPlatform struct {
4040

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

pkg/containerutil/container_network_manager_windows.go

+2-2
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.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
39+
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork(m.globalOptions.BridgeIP))
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.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork())
70+
e, err := netutil.NewCNIEnv(m.globalOptions.CNIPath, m.globalOptions.CNINetConfPath, netutil.WithNamespace(m.globalOptions.Namespace), netutil.WithDefaultNetwork(m.globalOptions.BridgeIP))
7171
if err != nil {
7272
return nil, fmt.Errorf("failed to instantiate CNI env: %s", err)
7373
}

pkg/netutil/netutil.go

+19-7
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,9 @@ func namespaceUsedNetworks(ctx context.Context, containers []containerd.Containe
180180
return used, nil
181181
}
182182

183-
func WithDefaultNetwork() CNIEnvOpt {
183+
func WithDefaultNetwork(bridgeIP string) CNIEnvOpt {
184184
return func(e *CNIEnv) error {
185-
return e.ensureDefaultNetworkConfig()
185+
return e.ensureDefaultNetworkConfig(bridgeIP)
186186
}
187187
}
188188

@@ -323,7 +323,6 @@ func (e *CNIEnv) CreateNetwork(opts types.NetworkCreateOptions) (*NetworkConfig,
323323
if _, ok := netMap[opts.Name]; ok {
324324
return errdefs.ErrAlreadyExists
325325
}
326-
327326
ipam, err := e.generateIPAM(opts.IPAMDriver, opts.Subnets, opts.Gateway, opts.IPRange, opts.IPAMOptions, opts.IPv6)
328327
if err != nil {
329328
return err
@@ -406,31 +405,44 @@ func (e *CNIEnv) GetDefaultNetworkConfig() (*NetworkConfig, error) {
406405
return nil, nil
407406
}
408407

409-
func (e *CNIEnv) ensureDefaultNetworkConfig() error {
408+
func (e *CNIEnv) ensureDefaultNetworkConfig(bridgeIP string) error {
410409
defaultNet, err := e.GetDefaultNetworkConfig()
411410
if err != nil {
412411
return fmt.Errorf("failed to check for default network: %s", err)
413412
}
414413
if defaultNet == nil {
415-
if err := e.createDefaultNetworkConfig(); err != nil {
414+
if err := e.createDefaultNetworkConfig(bridgeIP); err != nil {
416415
return fmt.Errorf("failed to create default network: %s", err)
417416
}
418417
}
419418
return nil
420419
}
421420

422-
func (e *CNIEnv) createDefaultNetworkConfig() error {
421+
func (e *CNIEnv) createDefaultNetworkConfig(bridgeIP string) error {
423422
filename := e.getConfigPathForNetworkName(DefaultNetworkName)
424423
if _, err := os.Stat(filename); err == nil {
425424
return fmt.Errorf("already found existing network config at %q, cannot create new network named %q", filename, DefaultNetworkName)
426425
}
426+
427+
bridgeCIDR := DefaultCIDR
428+
bridgeGatewayIP := DefaultGatewayIP
429+
if bridgeIP != "" {
430+
bIP, bCIDR, err := net.ParseCIDR(bridgeIP)
431+
if err != nil {
432+
return fmt.Errorf("invalid bridge ip %s: %s", bridgeIP, err)
433+
}
434+
bridgeGatewayIP = bIP.String()
435+
bridgeCIDR = bCIDR.String()
436+
}
427437
opts := types.NetworkCreateOptions{
428438
Name: DefaultNetworkName,
429439
Driver: DefaultNetworkName,
430-
Subnets: []string{DefaultCIDR},
440+
Subnets: []string{bridgeCIDR},
441+
Gateway: bridgeGatewayIP,
431442
IPAMDriver: "default",
432443
Labels: []string{fmt.Sprintf("%s=true", labels.NerdctlDefaultNetwork)},
433444
}
445+
434446
_, err := e.CreateNetwork(opts)
435447
if err != nil && !errdefs.IsAlreadyExists(err) {
436448
return err

pkg/netutil/netutil_linux_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ func TestDefaultNetworkCreation(t *testing.T) {
3030
}
3131

3232
testDefaultNetworkCreation(t)
33+
testDefaultNetworkCreationWithBridgeIP(t)
3334
}

pkg/netutil/netutil_test.go

+106-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package netutil
1818

1919
import (
2020
"bytes"
21+
"encoding/json"
2122
"fmt"
2223
"net"
2324
"os"
@@ -33,6 +34,8 @@ import (
3334
"github.com/containerd/nerdctl/v2/pkg/testutil"
3435
)
3536

37+
const testBridgeIP = "10.1.100.1/24" // nolint:unused
38+
3639
const preExistingNetworkConfigTemplate = `
3740
{
3841
"cniVersion": "0.2.0",
@@ -160,10 +163,77 @@ func testDefaultNetworkCreation(t *testing.T) {
160163
assert.Assert(t, defaultNetConf == nil)
161164

162165
// Attempt to create the default network.
163-
err = cniEnv.ensureDefaultNetworkConfig()
166+
err = cniEnv.ensureDefaultNetworkConfig("")
167+
assert.NilError(t, err)
168+
169+
// Ensure default network config is present now.
170+
defaultNetConf, err = cniEnv.GetDefaultNetworkConfig()
171+
assert.NilError(t, err)
172+
assert.Assert(t, defaultNetConf != nil)
173+
174+
// Check network config file present.
175+
stat, err := os.Stat(defaultNetConf.File)
176+
assert.NilError(t, err)
177+
firstConfigModTime := stat.ModTime()
178+
179+
// Check default network label present.
180+
assert.Assert(t, defaultNetConf.NerdctlLabels != nil)
181+
lstr, ok := (*defaultNetConf.NerdctlLabels)[labels.NerdctlDefaultNetwork]
182+
assert.Assert(t, ok)
183+
boolv, err := strconv.ParseBool(lstr)
184+
assert.NilError(t, err)
185+
assert.Assert(t, boolv)
186+
187+
// Ensure network isn't created twice or accidentally re-created.
188+
err = cniEnv.ensureDefaultNetworkConfig("")
189+
assert.NilError(t, err)
190+
191+
// Check for any other network config files.
192+
files := []os.FileInfo{}
193+
walkF := func(p string, info os.FileInfo, err error) error {
194+
files = append(files, info)
195+
return nil
196+
}
197+
err = filepath.Walk(cniConfTestDir, walkF)
198+
assert.NilError(t, err)
199+
assert.Assert(t, len(files) == 2) // files[0] is the entry for '.'
200+
assert.Assert(t, filepath.Join(cniConfTestDir, files[1].Name()) == defaultNetConf.File)
201+
assert.Assert(t, firstConfigModTime == files[1].ModTime())
202+
}
203+
204+
// Tests whether nerdctl properly creates the default network
205+
// with a custom bridge IP and subnet.
206+
// nolint:unused
207+
func testDefaultNetworkCreationWithBridgeIP(t *testing.T) {
208+
// To prevent subnet collisions when attempting to recreate the default network
209+
// in the isolated CNI config dir we'll be using, we must first delete
210+
// the network in the default CNI config dir.
211+
defaultCniEnv := CNIEnv{
212+
Path: ncdefaults.CNIPath(),
213+
NetconfPath: ncdefaults.CNINetConfPath(),
214+
}
215+
defaultNet, err := defaultCniEnv.GetDefaultNetworkConfig()
164216
assert.NilError(t, err)
217+
if defaultNet != nil {
218+
assert.NilError(t, defaultCniEnv.RemoveNetwork(defaultNet))
219+
}
165220

166-
// Ensure no default network config is present now.
221+
// We create a tempdir for the CNI conf path to ensure an empty env for this test.
222+
cniConfTestDir := t.TempDir()
223+
cniEnv := CNIEnv{
224+
Path: ncdefaults.CNIPath(),
225+
NetconfPath: cniConfTestDir,
226+
}
227+
// Ensure no default network config is not present.
228+
defaultNetConf, err := cniEnv.GetDefaultNetworkConfig()
229+
assert.NilError(t, err)
230+
assert.Assert(t, defaultNetConf == nil)
231+
232+
// Attempt to create the default network with a test bridgeIP
233+
err = cniEnv.ensureDefaultNetworkConfig(testBridgeIP)
234+
assert.NilError(t, err)
235+
236+
// Ensure default network config is present now.
167237
defaultNetConf, err = cniEnv.GetDefaultNetworkConfig()
168238
assert.NilError(t, err)
169239
assert.Assert(t, defaultNetConf != nil)
@@ -181,8 +251,40 @@ func testDefaultNetworkCreation(t *testing.T) {
181251
assert.NilError(t, err)
182252
assert.Assert(t, boolv)
183253

254+
// Check bridge IP is set.
255+
assert.Assert(t, defaultNetConf.Plugins != nil)
256+
assert.Assert(t, len(defaultNetConf.Plugins) > 0)
257+
bridgePlugin := defaultNetConf.Plugins[0]
258+
var bridgeConfig struct {
259+
Type string `json:"type"`
260+
Bridge string `json:"bridge"`
261+
IPAM struct {
262+
Ranges [][]struct {
263+
Gateway string `json:"gateway"`
264+
Subnet string `json:"subnet"`
265+
} `json:"ranges"`
266+
Routes []struct {
267+
Dst string `json:"dst"`
268+
} `json:"routes"`
269+
Type string `json:"type"`
270+
} `json:"ipam"`
271+
}
272+
273+
err = json.Unmarshal(bridgePlugin.Bytes, &bridgeConfig)
274+
if err != nil {
275+
t.Fatalf("Failed to parse bridge plugin config: %v", err)
276+
}
277+
278+
// Assert on bridge plugin configuration
279+
assert.Equal(t, "bridge", bridgeConfig.Type)
280+
// Assert on IPAM configuration
281+
assert.Equal(t, "10.1.100.1", bridgeConfig.IPAM.Ranges[0][0].Gateway)
282+
assert.Equal(t, "10.1.100.0/24", bridgeConfig.IPAM.Ranges[0][0].Subnet)
283+
assert.Equal(t, "0.0.0.0/0", bridgeConfig.IPAM.Routes[0].Dst)
284+
assert.Equal(t, "host-local", bridgeConfig.IPAM.Type)
285+
184286
// Ensure network isn't created twice or accidentally re-created.
185-
err = cniEnv.ensureDefaultNetworkConfig()
287+
err = cniEnv.ensureDefaultNetworkConfig(testBridgeIP)
186288
assert.NilError(t, err)
187289

188290
// Check for any other network config files.
@@ -249,7 +351,7 @@ func TestNetworkWithDefaultNameAlreadyExists(t *testing.T) {
249351
assert.Assert(t, defaultNetConf != nil)
250352
assert.Assert(t, defaultNetConf.File == testConfFile)
251353

252-
err = cniEnv.ensureDefaultNetworkConfig()
354+
err = cniEnv.ensureDefaultNetworkConfig("")
253355
assert.NilError(t, err)
254356

255357
netConfs, err = cniEnv.NetworkList()

pkg/netutil/netutil_unix.go

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
const (
4444
DefaultNetworkName = "bridge"
4545
DefaultCIDR = "10.4.0.0/24"
46+
DefaultGatewayIP = "10.4.0.1"
4647
DefaultIPAMDriver = "host-local"
4748

4849
// When creating non-default network without passing in `--subnet` option,

pkg/netutil/netutil_windows.go

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
const (
2828
DefaultNetworkName = "nat"
2929
DefaultCIDR = "10.4.0.0/24"
30+
DefaultGatewayIP = "10.4.0.1"
3031

3132
// When creating non-default network without passing in `--subnet` option,
3233
// nerdctl assigns subnet address for the creation starting from `StartingCIDR`

pkg/ocihook/ocihook.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const (
6161
NetworkNamespace = labels.Prefix + "network-namespace"
6262
)
6363

64-
func Run(stdin io.Reader, stderr io.Writer, event, dataStore, cniPath, cniNetconfPath string) error {
64+
func Run(stdin io.Reader, stderr io.Writer, event, dataStore, cniPath, cniNetconfPath, bridgeIP string) error {
6565
if stdin == nil || event == "" || dataStore == "" || cniPath == "" || cniNetconfPath == "" {
6666
return errors.New("got insufficient args")
6767
}
@@ -113,7 +113,7 @@ func Run(stdin io.Reader, stderr io.Writer, event, dataStore, cniPath, cniNetcon
113113
}
114114
defer lockutil.Unlock(lock)
115115

116-
opts, err := newHandlerOpts(&state, dataStore, cniPath, cniNetconfPath)
116+
opts, err := newHandlerOpts(&state, dataStore, cniPath, cniNetconfPath, bridgeIP)
117117
if err != nil {
118118
return err
119119
}
@@ -128,7 +128,7 @@ func Run(stdin io.Reader, stderr io.Writer, event, dataStore, cniPath, cniNetcon
128128
}
129129
}
130130

131-
func newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath string) (*handlerOpts, error) {
131+
func newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath, bridgeIP string) (*handlerOpts, error) {
132132
o := &handlerOpts{
133133
state: state,
134134
dataStore: dataStore,
@@ -173,7 +173,7 @@ func newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath strin
173173
case nettype.Host, nettype.None, nettype.Container, nettype.Namespace:
174174
// NOP
175175
case nettype.CNI:
176-
e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithNamespace(namespace), netutil.WithDefaultNetwork())
176+
e, err := netutil.NewCNIEnv(cniPath, cniNetconfPath, netutil.WithNamespace(namespace), netutil.WithDefaultNetwork(bridgeIP))
177177
if err != nil {
178178
return nil, err
179179
}

0 commit comments

Comments
 (0)