Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/nerdctl/network/network_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func createCommand() *cobra.Command {
cmd.RegisterFlagCompletionFunc("ipam-driver", completion.IPAMDrivers)
cmd.Flags().StringArray("ipam-opt", nil, "Set IPAM driver specific options")
cmd.Flags().StringArray("subnet", nil, `Subnet in CIDR format that represents a network segment, e.g. "10.5.0.0/16"`)
cmd.Flags().String("gateway", "", `Gateway for the master subnet`)
cmd.Flags().StringArray("gateway", nil, "IPv4 or IPv6 Gateway for the master subnet")
cmd.Flags().String("ip-range", "", `Allocate container ip from a sub-range`)
cmd.Flags().StringArray("label", nil, "Set metadata for a network")
cmd.Flags().Bool("ipv6", false, "Enable IPv6 networking")
Expand Down Expand Up @@ -84,7 +84,7 @@ func createAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
gatewayStr, err := cmd.Flags().GetString("gateway")
gateways, err := cmd.Flags().GetStringArray("gateway")
if err != nil {
return err
}
Expand Down Expand Up @@ -114,7 +114,7 @@ func createAction(cmd *cobra.Command, args []string) error {
IPAMDriver: ipamDriver,
IPAMOptions: strutil.ConvertKVStringsToMap(ipamOpts),
Subnets: subnets,
Gateway: gatewayStr,
Gateway: gateways,
IPRange: ipRangeStr,
Labels: labels,
IPv6: ipv6,
Expand Down
35 changes: 35 additions & 0 deletions cmd/nerdctl/network/network_create_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,41 @@ func TestNetworkCreate(t *testing.T) {
}
},
},
{
Description: "dual-stack with explicit gateways",
Require: nerdtest.OnlyIPv6,
Setup: func(data test.Data, helpers test.Helpers) {
// Before the fix the IPv6 gateway was checked against the IPv4
// subnet and creation failed.
helpers.Ensure("network", "create", data.Identifier(),
"--ipv6",
"--subnet", "10.5.0.0/16",
"--subnet", "2001:db8:5::/64",
"--gateway", "10.5.0.1",
"--gateway", "2001:db8:5::1",
)
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("network", "rm", data.Identifier())
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("network", "inspect", data.Identifier())
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: expect.ExitCodeSuccess,
Output: func(stdout string, t tig.T) {
netw := nerdtest.InspectNetwork(helpers, data.Identifier())
gateways := map[string]string{}
for _, c := range netw.IPAM.Config {
gateways[c.Subnet] = c.Gateway
}
assert.Equal(t, gateways["10.5.0.0/16"], "10.5.0.1")
assert.Equal(t, gateways["2001:db8:5::/64"], "2001:db8:5::1")
},
}
},
},
{
Description: "internal enabled",
Setup: func(data test.Data, helpers test.Helpers) {
Expand Down
2 changes: 1 addition & 1 deletion docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1265,7 +1265,7 @@ Flags:
- :nerd_face: `--ipam-driver=dhcp`: DHCP IPAM driver for unix, requires root
- :whale: `--ipam-opt`: Set IPAM driver specific options
- :whale: `--subnet`: Subnet in CIDR format that represents a network segment, e.g. "10.5.0.0/16"
- :whale: `--gateway`: Gateway for the master subnet
- :whale: `--gateway`: IPv4 or IPv6 Gateway for the master subnet
- :whale: `--ip-range`: Allocate container ip from a sub-range
- :whale: `--label`: Set metadata on a network
- :whale: `--ipv6`: Enable IPv6. Should be used with a valid subnet.
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/types/network_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type NetworkCreateOptions struct {
IPAMDriver string
IPAMOptions map[string]string
Subnets []string
Gateway string
Gateway []string
IPRange string
Labels []string
IPv6 bool
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/network/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (

func Create(options types.NetworkCreateOptions, stdout io.Writer) error {
if len(options.Subnets) == 0 {
if options.Gateway != "" || options.IPRange != "" {
if len(options.Gateway) > 0 || options.IPRange != "" {
return fmt.Errorf("cannot set gateway or ip-range without subnet, specify --subnet manually")
}
options.Subnets = []string{""}
Expand Down
6 changes: 3 additions & 3 deletions pkg/netutil/netutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,20 +438,20 @@ func (e *CNIEnv) createDefaultNetworkConfig(bridgeIP string) error {
}

bridgeCIDR := DefaultCIDR
bridgeGatewayIP := ""
var bridgeGateways []string
if bridgeIP != "" {
bIP, bCIDR, err := net.ParseCIDR(bridgeIP)
if err != nil {
return fmt.Errorf("invalid bridge ip %s: %w", bridgeIP, err)
}
bridgeGatewayIP = bIP.String()
bridgeGateways = []string{bIP.String()}
bridgeCIDR = bCIDR.String()
}
opts := types.NetworkCreateOptions{
Name: DefaultNetworkName,
Driver: DefaultNetworkName,
Subnets: []string{bridgeCIDR},
Gateway: bridgeGatewayIP,
Gateway: bridgeGateways,
IPAMDriver: "default",
Labels: []string{fmt.Sprintf("%s=true", labels.NerdctlDefaultNetwork)},
}
Expand Down
37 changes: 33 additions & 4 deletions pkg/netutil/netutil_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]
return plugins, nil
}

func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRangeStr string, opts map[string]string, ipv6 bool, internal bool) (map[string]interface{}, error) {
func (e *CNIEnv) generateIPAM(driver string, subnets []string, gateways []string, ipRangeStr string, opts map[string]string, ipv6 bool, internal bool) (map[string]interface{}, error) {
var ipamConfig interface{}
switch driver {
case "default", "host-local":
Expand All @@ -225,13 +225,14 @@ func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRan
{Dst: "0.0.0.0/0"},
}
}
ranges, findIPv4, err := e.parseIPAMRanges(subnets, gatewayStr, ipRangeStr, ipv6)
ranges, findIPv4, err := e.parseIPAMRanges(subnets, gateways, ipRangeStr, ipv6)
if err != nil {
return nil, err
}
ipamConf.Ranges = append(ipamConf.Ranges, ranges...)
if !findIPv4 {
ranges, _, _ = e.parseIPAMRanges([]string{""}, gatewayStr, ipRangeStr, ipv6)
// The default IPv4 range uses a computed gateway; pass none.
ranges, _, _ = e.parseIPAMRanges([]string{""}, nil, ipRangeStr, ipv6)
ipamConf.Ranges = append(ipamConf.Ranges, ranges...)
}
ipamConfig = ipamConf
Expand Down Expand Up @@ -288,9 +289,21 @@ func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRan
return ipam, nil
}

func (e *CNIEnv) parseIPAMRanges(subnets []string, gateway, ipRange string, ipv6 bool) ([][]IPAMRange, bool, error) {
func (e *CNIEnv) parseIPAMRanges(subnets []string, gateways []string, ipRange string, ipv6 bool) ([][]IPAMRange, bool, error) {
// Parse the gateways once up front; matching them to subnets below is then
// just a containment check, with no parse error mixed into the loop.
parsedGateways := make([]net.IP, len(gateways))
for i, g := range gateways {
gw := net.ParseIP(g)
if gw == nil {
return nil, false, fmt.Errorf("failed to parse gateway %q", g)
}
parsedGateways[i] = gw
}

findIPv4 := false
ranges := make([][]IPAMRange, 0, len(subnets))
used := make([]bool, len(gateways))
for i := range subnets {
subnet, err := e.parseSubnet(subnets[i])
if err != nil {
Expand All @@ -303,12 +316,28 @@ func (e *CNIEnv) parseIPAMRanges(subnets []string, gateway, ipRange string, ipv6
if !findIPv4 && subnet.IP.To4() != nil {
findIPv4 = true
}
// Pair the subnet with the gateway it contains, so dual-stack matches
// the v4 gateway to the v4 subnet and v6 to v6.
gateway := ""
for j, gw := range parsedGateways {
if !used[j] && subnet.Contains(gw) {
gateway, used[j] = gateways[j], true
break
}
}
ipamRange, err := parseIPAMRange(subnet, gateway, ipRange)
if err != nil {
return nil, findIPv4, err
}
ranges = append(ranges, []IPAMRange{*ipamRange})
}
// Only known after every subnet is seen: a gateway that matched none is a
// user error, same as Docker.
for j, ok := range used {
if !ok {
return nil, findIPv4, fmt.Errorf("no matching subnet for gateway %q", gateways[j])
}
}
return ranges, findIPv4, nil
}

Expand Down
8 changes: 7 additions & 1 deletion pkg/netutil/netutil_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,19 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string]
return plugins, nil
}

func (e *CNIEnv) generateIPAM(driver string, subnets []string, gatewayStr, ipRangeStr string, opts map[string]string, ipv6 bool, internal bool) (map[string]interface{}, error) {
func (e *CNIEnv) generateIPAM(driver string, subnets []string, gateways []string, ipRangeStr string, opts map[string]string, ipv6 bool, internal bool) (map[string]interface{}, error) {
switch driver {
case "default":
default:
return nil, fmt.Errorf("unsupported ipam driver %q", driver)
}

// Windows is single-subnet, so use at most one gateway.
gatewayStr := ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this used somewhere ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, just below at parseIPAMRange(subnet, gatewayStr, ipRangeStr). Windows is single-subnet so it takes at most the first gateway.

if len(gateways) > 0 {
gatewayStr = gateways[0]
}

ipamConfig := newWindowsIPAMConfig()
subnet, err := e.parseSubnet(subnets[0])
if err != nil {
Expand Down
Loading