From 6961d912061c81b7a444e7c00163ae240e318c42 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Thu, 27 Jan 2022 15:13:55 +0100 Subject: network create: allow multiple subnets podman network create --subnet, --gateway and --ip-range can now be specified multiple times to join the network to more than one subnet. This is very useful if you want to use a dual stack network and assign a fixed ipv4 and ipv6 subnet. The order of the options is important here, the first --gateway/--ip-range will be assigned to the first subnet and so on. Signed-off-by: Paul Holzinger --- cmd/podman/networks/create.go | 64 ++++++++++++++------ docs/source/markdown/podman-network-create.1.md | 18 ++++-- pkg/domain/entities/network.go | 6 +- test/e2e/network_create_test.go | 78 +++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 25 deletions(-) diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go index 9f6470858..3dd393c46 100644 --- a/cmd/podman/networks/create.go +++ b/cmd/podman/networks/create.go @@ -47,13 +47,13 @@ func networkCreateFlags(cmd *cobra.Command) { _ = cmd.RegisterFlagCompletionFunc(optFlagName, completion.AutocompleteNone) gatewayFlagName := "gateway" - flags.IPVar(&networkCreateOptions.Gateway, gatewayFlagName, nil, "IPv4 or IPv6 gateway for the subnet") + flags.IPSliceVar(&networkCreateOptions.Gateways, gatewayFlagName, nil, "IPv4 or IPv6 gateway for the subnet") _ = cmd.RegisterFlagCompletionFunc(gatewayFlagName, completion.AutocompleteNone) flags.BoolVar(&networkCreateOptions.Internal, "internal", false, "restrict external access from this network") ipRangeFlagName := "ip-range" - flags.IPNetVar(&networkCreateOptions.Range, ipRangeFlagName, net.IPNet{}, "allocate container IP from range") + flags.StringArrayVar(&networkCreateOptions.Ranges, ipRangeFlagName, nil, "allocate container IP from range") _ = cmd.RegisterFlagCompletionFunc(ipRangeFlagName, completion.AutocompleteNone) // TODO consider removing this for 4.0 @@ -72,7 +72,7 @@ func networkCreateFlags(cmd *cobra.Command) { flags.BoolVar(&networkCreateOptions.IPv6, "ipv6", false, "enable IPv6 networking") subnetFlagName := "subnet" - flags.IPNetVar(&networkCreateOptions.Subnet, subnetFlagName, net.IPNet{}, "subnet in CIDR format") + flags.StringArrayVar(&networkCreateOptions.Subnets, subnetFlagName, nil, "subnets in CIDR format") _ = cmd.RegisterFlagCompletionFunc(subnetFlagName, completion.AutocompleteNone) flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin") @@ -125,27 +125,35 @@ func networkCreate(cmd *cobra.Command, args []string) error { } } - if networkCreateOptions.Subnet.IP != nil { - s := types.Subnet{ - Subnet: types.IPNet{IPNet: networkCreateOptions.Subnet}, - Gateway: networkCreateOptions.Gateway, + if len(networkCreateOptions.Subnets) > 0 { + if len(networkCreateOptions.Gateways) > len(networkCreateOptions.Subnets) { + return errors.New("cannot set more gateways than subnets") } - if networkCreateOptions.Range.IP != nil { - startIP, err := util.FirstIPInSubnet(&networkCreateOptions.Range) + if len(networkCreateOptions.Ranges) > len(networkCreateOptions.Subnets) { + return errors.New("cannot set more ranges than subnets") + } + + for i := range networkCreateOptions.Subnets { + subnet, err := types.ParseCIDR(networkCreateOptions.Subnets[i]) if err != nil { - return errors.Wrap(err, "failed to get first ip in range") + return err } - lastIP, err := util.LastIPInSubnet(&networkCreateOptions.Range) - if err != nil { - return errors.Wrap(err, "failed to get last ip in range") + s := types.Subnet{ + Subnet: subnet, + } + if len(networkCreateOptions.Ranges) > i { + leaseRange, err := parseRange(networkCreateOptions.Ranges[i]) + if err != nil { + return err + } + s.LeaseRange = leaseRange } - s.LeaseRange = &types.LeaseRange{ - StartIP: startIP, - EndIP: lastIP, + if len(networkCreateOptions.Gateways) > i { + s.Gateway = networkCreateOptions.Gateways[i] } + network.Subnets = append(network.Subnets, s) } - network.Subnets = append(network.Subnets, s) - } else if networkCreateOptions.Range.IP != nil || networkCreateOptions.Gateway != nil { + } else if len(networkCreateOptions.Ranges) > 0 || len(networkCreateOptions.Gateways) > 0 { return errors.New("cannot set gateway or range without subnet") } @@ -156,3 +164,23 @@ func networkCreate(cmd *cobra.Command, args []string) error { fmt.Println(response.Name) return nil } + +func parseRange(iprange string) (*types.LeaseRange, error) { + _, subnet, err := net.ParseCIDR(iprange) + if err != nil { + return nil, err + } + + startIP, err := util.FirstIPInSubnet(subnet) + if err != nil { + return nil, errors.Wrap(err, "failed to get first ip in range") + } + lastIP, err := util.LastIPInSubnet(subnet) + if err != nil { + return nil, errors.Wrap(err, "failed to get last ip in range") + } + return &types.LeaseRange{ + StartIP: startIP, + EndIP: lastIP, + }, nil +} diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md index 8a9b2f3cf..5be0c2595 100644 --- a/docs/source/markdown/podman-network-create.1.md +++ b/docs/source/markdown/podman-network-create.1.md @@ -46,7 +46,8 @@ The `macvlan` and `ipvlan` driver support the following options: #### **--gateway** Define a gateway for the subnet. If you want to provide a gateway address, you must also provide a -*subnet* option. +*subnet* option. Can be specified multiple times. +The argument order of the **--subnet**, **--gateway** and **--ip-range** options must match. #### **--internal** @@ -56,7 +57,8 @@ automatically disabled. #### **--ip-range** Allocate container IP from a range. The range must be a complete subnet and in CIDR notation. The *ip-range* option -must be used with a *subnet* option. +must be used with a *subnet* option. Can be specified multiple times. +The argument order of the **--subnet**, **--gateway** and **--ip-range** options must match. #### **--label** @@ -64,11 +66,13 @@ Set metadata for a network (e.g., --label mykey=value). #### **--subnet** -The subnet in CIDR notation. +The subnet in CIDR notation. Can be specified multiple times to allocate more than one subnet for this network. +The argument order of the **--subnet**, **--gateway** and **--ip-range** options must match. +This is useful to set a static ipv4 and ipv6 subnet. #### **--ipv6** -Enable IPv6 (Dual Stack) networking. +Enable IPv6 (Dual Stack) networking. If not subnets are given it will allocate a ipv4 and ipv6 subnet. ## EXAMPLE @@ -102,6 +106,12 @@ $ podman network create --subnet 192.168.55.0/24 --ip-range 192.168.55.128/25 cni-podman5 ``` +Create a network with a static ipv4 and ipv6 subnet and set a gateway. +``` +$ podman network create --subnet 192.168.55.0/24 --gateway 192.168.55.3 --subnet fd52:2a5a:747e:3acd::/64 --gateway fd52:2a5a:747e:3acd::10 +podman4 +``` + Create a Macvlan based network using the host interface eth0. Macvlan networks can only be used as root. ``` # podman network create -d macvlan -o parent=eth0 newnet diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go index 79edc3227..a057640b3 100644 --- a/pkg/domain/entities/network.go +++ b/pkg/domain/entities/network.go @@ -43,12 +43,12 @@ type NetworkRmReport struct { type NetworkCreateOptions struct { DisableDNS bool Driver string - Gateway net.IP + Gateways []net.IP Internal bool Labels map[string]string MacVLAN string - Range net.IPNet - Subnet net.IPNet + Ranges []string + Subnets []string IPv6 bool // Mapping of driver options and values. Options map[string]string diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index ade78a308..4a8a24ad7 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -356,4 +356,82 @@ var _ = Describe("Podman network create", func() { } }) + It("podman network create with multiple subnets", func() { + name := "subnets-" + stringid.GenerateNonCryptoID() + subnet1 := "10.10.0.0/24" + subnet2 := "10.10.1.0/24" + nc := podmanTest.Podman([]string{"network", "create", "--subnet", subnet1, "--subnet", subnet2, name}) + nc.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(name) + Expect(nc).To(Exit(0)) + Expect(nc.OutputToString()).To(Equal(name)) + + inspect := podmanTest.Podman([]string{"network", "inspect", name}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).To(Exit(0)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"subnet": "` + subnet1)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"subnet": "` + subnet2)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"ipv6_enabled": false`)) + }) + + It("podman network create with multiple subnets dual stack", func() { + name := "subnets-" + stringid.GenerateNonCryptoID() + subnet1 := "10.10.2.0/24" + subnet2 := "fd52:2a5a:747e:3acd::/64" + nc := podmanTest.Podman([]string{"network", "create", "--subnet", subnet1, "--subnet", subnet2, name}) + nc.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(name) + Expect(nc).To(Exit(0)) + Expect(nc.OutputToString()).To(Equal(name)) + + inspect := podmanTest.Podman([]string{"network", "inspect", name}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).To(Exit(0)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"subnet": "` + subnet1)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"subnet": "` + subnet2)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"ipv6_enabled": true`)) + }) + + It("podman network create with multiple subnets dual stack with gateway and range", func() { + name := "subnets-" + stringid.GenerateNonCryptoID() + subnet1 := "10.10.3.0/24" + gw1 := "10.10.3.10" + range1 := "10.10.3.0/26" + subnet2 := "fd52:2a5a:747e:3acd::/64" + gw2 := "fd52:2a5a:747e:3acd::10" + nc := podmanTest.Podman([]string{"network", "create", "--subnet", subnet1, "--gateway", gw1, "--ip-range", range1, "--subnet", subnet2, "--gateway", gw2, name}) + nc.WaitWithDefaultTimeout() + defer podmanTest.removeCNINetwork(name) + Expect(nc).To(Exit(0)) + Expect(nc.OutputToString()).To(Equal(name)) + + inspect := podmanTest.Podman([]string{"network", "inspect", name}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).To(Exit(0)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"subnet": "` + subnet1)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"gateway": "` + gw1)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"start_ip": "10.10.3.1",`)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"end_ip": "10.10.3.63"`)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"subnet": "` + subnet2)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"gateway": "` + gw2)) + Expect(inspect.OutputToString()).To(ContainSubstring(`"ipv6_enabled": true`)) + }) + + It("podman network create invalid options with multiple subnets", func() { + name := "subnets-" + stringid.GenerateNonCryptoID() + subnet1 := "10.10.3.0/24" + gw1 := "10.10.3.10" + gw2 := "fd52:2a5a:747e:3acd::10" + nc := podmanTest.Podman([]string{"network", "create", "--subnet", subnet1, "--gateway", gw1, "--gateway", gw2, name}) + nc.WaitWithDefaultTimeout() + Expect(nc).To(Exit(125)) + Expect(nc.ErrorToString()).To(Equal("Error: cannot set more gateways than subnets")) + + range1 := "10.10.3.0/26" + range2 := "10.10.3.0/28" + nc = podmanTest.Podman([]string{"network", "create", "--subnet", subnet1, "--ip-range", range1, "--ip-range", range2, name}) + nc.WaitWithDefaultTimeout() + Expect(nc).To(Exit(125)) + Expect(nc.ErrorToString()).To(Equal("Error: cannot set more ranges than subnets")) + }) }) -- cgit v1.2.3-54-g00ecf