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 <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger 2022-01-27 15:13:55 +01:00
parent 5659b0734c
commit 6961d91206
No known key found for this signature in database
GPG key ID: EB145DD938A3CAF2
4 changed files with 145 additions and 29 deletions

View file

@ -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 err != nil {
return errors.Wrap(err, "failed to get first ip in range")
}
lastIP, err := util.LastIPInSubnet(&networkCreateOptions.Range)
if err != nil {
return errors.Wrap(err, "failed to get last ip in range")
}
s.LeaseRange = &types.LeaseRange{
StartIP: startIP,
EndIP: lastIP,
}
if len(networkCreateOptions.Ranges) > len(networkCreateOptions.Subnets) {
return errors.New("cannot set more ranges than subnets")
}
network.Subnets = append(network.Subnets, s)
} else if networkCreateOptions.Range.IP != nil || networkCreateOptions.Gateway != nil {
for i := range networkCreateOptions.Subnets {
subnet, err := types.ParseCIDR(networkCreateOptions.Subnets[i])
if err != nil {
return err
}
s := types.Subnet{
Subnet: subnet,
}
if len(networkCreateOptions.Ranges) > i {
leaseRange, err := parseRange(networkCreateOptions.Ranges[i])
if err != nil {
return err
}
s.LeaseRange = leaseRange
}
if len(networkCreateOptions.Gateways) > i {
s.Gateway = networkCreateOptions.Gateways[i]
}
network.Subnets = append(network.Subnets, s)
}
} 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
}

View file

@ -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

View file

@ -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

View file

@ -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"))
})
})