summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Holzinger <pholzing@redhat.com>2021-10-13 21:52:55 +0200
committerPaul Holzinger <pholzing@redhat.com>2021-11-11 15:54:02 +0100
commit12c62b92ff2f63cb34dcb9c0555b96983e6aad94 (patch)
tree01e489ea273813b8ae0bd7302c9f91bda5c2f23a
parent8fd31c674b02b800267b2a759e2406902fdb2723 (diff)
downloadpodman-12c62b92ff2f63cb34dcb9c0555b96983e6aad94.tar.gz
podman-12c62b92ff2f63cb34dcb9c0555b96983e6aad94.tar.bz2
podman-12c62b92ff2f63cb34dcb9c0555b96983e6aad94.zip
Make networking code reusable
To prevent code duplication when creating new network backends move reusable code into a separate internal package. This allows all network backends to use the same code as long as they implement the new NetUtil interface. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
-rw-r--r--libpod/network/cni/config.go152
-rw-r--r--libpod/network/cni/network.go116
-rw-r--r--libpod/network/cni/run.go39
-rw-r--r--libpod/network/cni/run_test.go2
-rw-r--r--libpod/network/internal/util/bridge.go67
-rw-r--r--libpod/network/internal/util/create.go48
-rw-r--r--libpod/network/internal/util/interface.go19
-rw-r--r--libpod/network/internal/util/interfaces.go (renamed from libpod/network/util/interfaces.go)4
-rw-r--r--libpod/network/internal/util/ip.go70
-rw-r--r--libpod/network/internal/util/ip_test.go63
-rw-r--r--libpod/network/internal/util/util.go123
-rw-r--r--libpod/network/internal/util/validate.go97
-rw-r--r--libpod/network/util/ip.go69
-rw-r--r--libpod/network/util/ip_test.go52
14 files changed, 518 insertions, 403 deletions
diff --git a/libpod/network/cni/config.go b/libpod/network/cni/config.go
index 3df155637..a63574f25 100644
--- a/libpod/network/cni/config.go
+++ b/libpod/network/cni/config.go
@@ -7,6 +7,7 @@ import (
"os"
"github.com/containers/podman/v3/libpod/define"
+ internalutil "github.com/containers/podman/v3/libpod/network/internal/util"
"github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/podman/v3/libpod/network/util"
pkgutil "github.com/containers/podman/v3/pkg/util"
@@ -41,38 +42,9 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
newNetwork.Driver = types.DefaultNetworkDriver
}
- // FIXME: Should we use a different type for network create without the ID field?
- // the caller is not allowed to set a specific ID
- if newNetwork.ID != "" {
- return nil, errors.Wrap(define.ErrInvalidArg, "ID can not be set for network create")
- }
-
- if newNetwork.Labels == nil {
- newNetwork.Labels = map[string]string{}
- }
- if newNetwork.Options == nil {
- newNetwork.Options = map[string]string{}
- }
- if newNetwork.IPAMOptions == nil {
- newNetwork.IPAMOptions = map[string]string{}
- }
-
- var name string
- var err error
- // validate the name when given
- if newNetwork.Name != "" {
- if !define.NameRegex.MatchString(newNetwork.Name) {
- return nil, errors.Wrapf(define.RegexError, "network name %s invalid", newNetwork.Name)
- }
- if _, ok := n.networks[newNetwork.Name]; ok {
- return nil, errors.Wrapf(define.ErrNetworkExists, "network name %s already used", newNetwork.Name)
- }
- } else {
- name, err = n.getFreeDeviceName()
- if err != nil {
- return nil, err
- }
- newNetwork.Name = name
+ err := internalutil.CommonNetworkCreate(n, &newNetwork)
+ if err != nil {
+ return nil, err
}
// Only get the used networks for validation if we do not create the default network.
@@ -84,7 +56,7 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
// fail because it thinks the network is used on the host.
var usedNetworks []*net.IPNet
if !defaultNet {
- usedNetworks, err = n.getUsedSubnets()
+ usedNetworks, err = internalutil.GetUsedSubnets(n)
if err != nil {
return nil, err
}
@@ -92,11 +64,7 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
switch newNetwork.Driver {
case types.BridgeNetworkDriver:
- // if the name was created with getFreeDeviceName set the interface to it as well
- if name != "" && newNetwork.NetworkInterface == "" {
- newNetwork.NetworkInterface = name
- }
- err = n.createBridge(&newNetwork, usedNetworks)
+ err = internalutil.CreateBridge(n, &newNetwork, usedNetworks)
if err != nil {
return nil, err
}
@@ -110,7 +78,7 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
}
for i := range newNetwork.Subnets {
- err := validateSubnet(&newNetwork.Subnets[i], !newNetwork.Internal, usedNetworks)
+ err := internalutil.ValidateSubnet(&newNetwork.Subnets[i], !newNetwork.Internal, usedNetworks)
if err != nil {
return nil, err
}
@@ -223,7 +191,7 @@ func createIPMACVLAN(network *types.Network) error {
return errors.New("internal is not supported with macvlan")
}
if network.NetworkInterface != "" {
- interfaceNames, err := util.GetLiveNetworkNames()
+ interfaceNames, err := internalutil.GetLiveNetworkNames()
if err != nil {
return err
}
@@ -238,107 +206,3 @@ func createIPMACVLAN(network *types.Network) error {
}
return nil
}
-
-func (n *cniNetwork) createBridge(network *types.Network, usedNetworks []*net.IPNet) error {
- if network.NetworkInterface != "" {
- bridges := n.getBridgeInterfaceNames()
- if pkgutil.StringInSlice(network.NetworkInterface, bridges) {
- return errors.Errorf("bridge name %s already in use", network.NetworkInterface)
- }
- if !define.NameRegex.MatchString(network.NetworkInterface) {
- return errors.Wrapf(define.RegexError, "bridge name %s invalid", network.NetworkInterface)
- }
- } else {
- var err error
- network.NetworkInterface, err = n.getFreeDeviceName()
- if err != nil {
- return err
- }
- }
-
- if len(network.Subnets) == 0 {
- freeSubnet, err := n.getFreeIPv4NetworkSubnet(usedNetworks)
- if err != nil {
- return err
- }
- network.Subnets = append(network.Subnets, *freeSubnet)
- }
- // ipv6 enabled means dual stack, check if we already have
- // a ipv4 or ipv6 subnet and add one if not.
- if network.IPv6Enabled {
- ipv4 := false
- ipv6 := false
- for _, subnet := range network.Subnets {
- if util.IsIPv6(subnet.Subnet.IP) {
- ipv6 = true
- }
- if util.IsIPv4(subnet.Subnet.IP) {
- ipv4 = true
- }
- }
- if !ipv4 {
- freeSubnet, err := n.getFreeIPv4NetworkSubnet(usedNetworks)
- if err != nil {
- return err
- }
- network.Subnets = append(network.Subnets, *freeSubnet)
- }
- if !ipv6 {
- freeSubnet, err := n.getFreeIPv6NetworkSubnet(usedNetworks)
- if err != nil {
- return err
- }
- network.Subnets = append(network.Subnets, *freeSubnet)
- }
- }
- network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
- return nil
-}
-
-// validateSubnet will validate a given Subnet. It checks if the
-// given gateway and lease range are part of this subnet. If the
-// gateway is empty and addGateway is true it will get the first
-// available ip in the subnet assigned.
-func validateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet) error {
- if s == nil {
- return errors.New("subnet is nil")
- }
- if s.Subnet.IP == nil {
- return errors.New("subnet ip is nil")
- }
-
- // Reparse to ensure subnet is valid.
- // Do not use types.ParseCIDR() because we want the ip to be
- // the network address and not a random ip in the subnet.
- _, net, err := net.ParseCIDR(s.Subnet.String())
- if err != nil {
- return errors.Wrap(err, "subnet invalid")
- }
-
- // check that the new subnet does not conflict with existing ones
- if util.NetworkIntersectsWithNetworks(net, usedNetworks) {
- return errors.Errorf("subnet %s is already used on the host or by another config", net.String())
- }
-
- s.Subnet = types.IPNet{IPNet: *net}
- if s.Gateway != nil {
- if !s.Subnet.Contains(s.Gateway) {
- return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
- }
- } else if addGateway {
- ip, err := util.FirstIPInSubnet(net)
- if err != nil {
- return err
- }
- s.Gateway = ip
- }
- if s.LeaseRange != nil {
- if s.LeaseRange.StartIP != nil && !s.Subnet.Contains(s.LeaseRange.StartIP) {
- return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
- }
- if s.LeaseRange.EndIP != nil && !s.Subnet.Contains(s.LeaseRange.EndIP) {
- return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
- }
- }
- return nil
-}
diff --git a/libpod/network/cni/network.go b/libpod/network/cni/network.go
index a37a84373..3e9cdaa47 100644
--- a/libpod/network/cni/network.go
+++ b/libpod/network/cni/network.go
@@ -6,8 +6,6 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
- "fmt"
- "net"
"os"
"strings"
"time"
@@ -15,8 +13,6 @@ import (
"github.com/containernetworking/cni/libcni"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/network/types"
- "github.com/containers/podman/v3/libpod/network/util"
- pkgutil "github.com/containers/podman/v3/pkg/util"
"github.com/containers/storage/pkg/lockfile"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -242,111 +238,29 @@ func getNetworkIDFromName(name string) string {
return hex.EncodeToString(hash[:])
}
-// getFreeIPv6NetworkSubnet returns a unused ipv4 subnet
-func (n *cniNetwork) getFreeIPv4NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) {
- // the default podman network is 10.88.0.0/16
- // start locking for free /24 networks
- network := &net.IPNet{
- IP: net.IP{10, 89, 0, 0},
- Mask: net.IPMask{255, 255, 255, 0},
- }
-
- // TODO: make sure to not use public subnets
- for {
- if intersectsConfig := util.NetworkIntersectsWithNetworks(network, usedNetworks); !intersectsConfig {
- logrus.Debugf("found free ipv4 network subnet %s", network.String())
- return &types.Subnet{
- Subnet: types.IPNet{IPNet: *network},
- }, nil
- }
- var err error
- network, err = util.NextSubnet(network)
- if err != nil {
- return nil, err
- }
- }
-}
+// Implement the NetUtil interface for easy code sharing with other network interfaces.
-// getFreeIPv6NetworkSubnet returns a unused ipv6 subnet
-func (n *cniNetwork) getFreeIPv6NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) {
- // FIXME: Is 10000 fine as limit? We should prevent an endless loop.
- for i := 0; i < 10000; i++ {
- // RFC4193: Choose the ipv6 subnet random and NOT sequentially.
- network, err := util.GetRandomIPv6Subnet()
- if err != nil {
- return nil, err
- }
- if intersectsConfig := util.NetworkIntersectsWithNetworks(&network, usedNetworks); !intersectsConfig {
- logrus.Debugf("found free ipv6 network subnet %s", network.String())
- return &types.Subnet{
- Subnet: types.IPNet{IPNet: network},
- }, nil
- }
- }
- return nil, errors.New("failed to get random ipv6 subnet")
-}
-
-// getUsedSubnets returns a list of all used subnets by network
-// configs and interfaces on the host.
-func (n *cniNetwork) getUsedSubnets() ([]*net.IPNet, error) {
- // first, load all used subnets from network configs
- subnets := make([]*net.IPNet, 0, len(n.networks))
+// ForEach call the given function for each network
+func (n *cniNetwork) ForEach(run func(types.Network)) {
for _, val := range n.networks {
- for i := range val.libpodNet.Subnets {
- subnets = append(subnets, &val.libpodNet.Subnets[i].Subnet.IPNet)
- }
+ run(*val.libpodNet)
}
- // second, load networks from the current system
- liveSubnets, err := util.GetLiveNetworkSubnets()
- if err != nil {
- return nil, err
- }
- return append(subnets, liveSubnets...), nil
}
-// getFreeDeviceName returns a free device name which can
-// be used for new configs as name and bridge interface name
-func (n *cniNetwork) getFreeDeviceName() (string, error) {
- bridgeNames := n.getBridgeInterfaceNames()
- netNames := n.getUsedNetworkNames()
- liveInterfaces, err := util.GetLiveNetworkNames()
- if err != nil {
- return "", nil
- }
- names := make([]string, 0, len(bridgeNames)+len(netNames)+len(liveInterfaces))
- names = append(names, bridgeNames...)
- names = append(names, netNames...)
- names = append(names, liveInterfaces...)
- // FIXME: Is a limit fine?
- // Start by 1, 0 is reserved for the default network
- for i := 1; i < 1000000; i++ {
- deviceName := fmt.Sprintf("%s%d", cniDeviceName, i)
- if !pkgutil.StringInSlice(deviceName, names) {
- logrus.Debugf("found free device name %s", deviceName)
- return deviceName, nil
- }
- }
- return "", errors.New("could not find free device name, to many iterations")
+// Len return the number of networks
+func (n *cniNetwork) Len() int {
+ return len(n.networks)
}
-// getUsedNetworkNames returns all network names already used
-// by network configs
-func (n *cniNetwork) getUsedNetworkNames() []string {
- names := make([]string, 0, len(n.networks))
- for _, val := range n.networks {
- names = append(names, val.libpodNet.Name)
- }
- return names
+// DefaultInterfaceName return the default cni bridge name, must be suffixed with a number.
+func (n *cniNetwork) DefaultInterfaceName() string {
+ return cniDeviceName
}
-// getUsedNetworkNames returns all bridge device names already used
-// by network configs
-func (n *cniNetwork) getBridgeInterfaceNames() []string {
- names := make([]string, 0, len(n.networks))
- for _, val := range n.networks {
- if val.libpodNet.Driver == types.BridgeNetworkDriver {
- names = append(names, val.libpodNet.NetworkInterface)
- }
+func (n *cniNetwork) Network(nameOrID string) (*types.Network, error) {
+ network, err := n.getNetwork(nameOrID)
+ if err != nil {
+ return nil, err
}
- return names
+ return network.libpodNet, err
}
diff --git a/libpod/network/cni/run.go b/libpod/network/cni/run.go
index 7795dfeeb..667ed3ab1 100644
--- a/libpod/network/cni/run.go
+++ b/libpod/network/cni/run.go
@@ -13,6 +13,7 @@ import (
types040 "github.com/containernetworking/cni/pkg/types/040"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/network/internal/util"
"github.com/containers/podman/v3/libpod/network/types"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
@@ -30,24 +31,9 @@ func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (ma
return nil, err
}
- if namespacePath == "" {
- return nil, errors.New("namespacePath is empty")
- }
- if options.ContainerID == "" {
- return nil, errors.New("ContainerID is empty")
- }
- if len(options.Networks) == 0 {
- return nil, errors.New("must specify at least one network")
- }
- for name, netOpts := range options.Networks {
- network := n.networks[name]
- if network == nil {
- return nil, errors.Wrapf(define.ErrNoSuchNetwork, "network %s", name)
- }
- err := validatePerNetworkOpts(network, netOpts)
- if err != nil {
- return nil, err
- }
+ err = util.ValidateSetupOptions(n, namespacePath, options)
+ if err != nil {
+ return nil, err
}
// set the loopback adapter up in the container netns
@@ -172,23 +158,6 @@ func CNIResultToStatus(res cnitypes.Result) (types.StatusBlock, error) {
return result, nil
}
-// validatePerNetworkOpts checks that all given static ips are in a subnet on this network
-func validatePerNetworkOpts(network *network, netOpts types.PerNetworkOptions) error {
- if netOpts.InterfaceName == "" {
- return errors.Errorf("interface name on network %s is empty", network.libpodNet.Name)
- }
-outer:
- for _, ip := range netOpts.StaticIPs {
- for _, s := range network.libpodNet.Subnets {
- if s.Subnet.Contains(ip) {
- continue outer
- }
- }
- return errors.Errorf("requested static ip %s not in any subnet on network %s", ip.String(), network.libpodNet.Name)
- }
- return nil
-}
-
func getRuntimeConfig(netns, conName, conID, networkName string, ports []cniPortMapEntry, opts types.PerNetworkOptions) *libcni.RuntimeConf {
rt := &libcni.RuntimeConf{
ContainerID: conID,
diff --git a/libpod/network/cni/run_test.go b/libpod/network/cni/run_test.go
index 3169cd0eb..6c54f82ef 100644
--- a/libpod/network/cni/run_test.go
+++ b/libpod/network/cni/run_test.go
@@ -1232,7 +1232,7 @@ var _ = Describe("run CNI", func() {
}
_, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
Expect(err).To(HaveOccurred())
- Expect(err.Error()).To(ContainSubstring("network somenet: network not found"))
+ Expect(err.Error()).To(ContainSubstring("unable to find network with name or ID somenet: network not found"))
})
})
diff --git a/libpod/network/internal/util/bridge.go b/libpod/network/internal/util/bridge.go
new file mode 100644
index 000000000..c054c7d4e
--- /dev/null
+++ b/libpod/network/internal/util/bridge.go
@@ -0,0 +1,67 @@
+package util
+
+import (
+ "net"
+
+ "github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/containers/podman/v3/libpod/network/util"
+ pkgutil "github.com/containers/podman/v3/pkg/util"
+ "github.com/pkg/errors"
+)
+
+func CreateBridge(n NetUtil, network *types.Network, usedNetworks []*net.IPNet) error {
+ if network.NetworkInterface != "" {
+ bridges := GetBridgeInterfaceNames(n)
+ if pkgutil.StringInSlice(network.NetworkInterface, bridges) {
+ return errors.Errorf("bridge name %s already in use", network.NetworkInterface)
+ }
+ if !define.NameRegex.MatchString(network.NetworkInterface) {
+ return errors.Wrapf(define.RegexError, "bridge name %s invalid", network.NetworkInterface)
+ }
+ } else {
+ var err error
+ network.NetworkInterface, err = GetFreeDeviceName(n)
+ if err != nil {
+ return err
+ }
+ }
+
+ if len(network.Subnets) == 0 {
+ freeSubnet, err := GetFreeIPv4NetworkSubnet(usedNetworks)
+ if err != nil {
+ return err
+ }
+ network.Subnets = append(network.Subnets, *freeSubnet)
+ }
+ // ipv6 enabled means dual stack, check if we already have
+ // a ipv4 or ipv6 subnet and add one if not.
+ if network.IPv6Enabled {
+ ipv4 := false
+ ipv6 := false
+ for _, subnet := range network.Subnets {
+ if util.IsIPv6(subnet.Subnet.IP) {
+ ipv6 = true
+ }
+ if util.IsIPv4(subnet.Subnet.IP) {
+ ipv4 = true
+ }
+ }
+ if !ipv4 {
+ freeSubnet, err := GetFreeIPv4NetworkSubnet(usedNetworks)
+ if err != nil {
+ return err
+ }
+ network.Subnets = append(network.Subnets, *freeSubnet)
+ }
+ if !ipv6 {
+ freeSubnet, err := GetFreeIPv6NetworkSubnet(usedNetworks)
+ if err != nil {
+ return err
+ }
+ network.Subnets = append(network.Subnets, *freeSubnet)
+ }
+ }
+ network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
+ return nil
+}
diff --git a/libpod/network/internal/util/create.go b/libpod/network/internal/util/create.go
new file mode 100644
index 000000000..ca716f913
--- /dev/null
+++ b/libpod/network/internal/util/create.go
@@ -0,0 +1,48 @@
+package util
+
+import (
+ "github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/pkg/errors"
+)
+
+func CommonNetworkCreate(n NetUtil, network *types.Network) error {
+ // FIXME: Should we use a different type for network create without the ID field?
+ // the caller is not allowed to set a specific ID
+ if network.ID != "" {
+ return errors.Wrap(define.ErrInvalidArg, "ID can not be set for network create")
+ }
+
+ if network.Labels == nil {
+ network.Labels = map[string]string{}
+ }
+ if network.Options == nil {
+ network.Options = map[string]string{}
+ }
+ if network.IPAMOptions == nil {
+ network.IPAMOptions = map[string]string{}
+ }
+
+ var name string
+ var err error
+ // validate the name when given
+ if network.Name != "" {
+ if !define.NameRegex.MatchString(network.Name) {
+ return errors.Wrapf(define.RegexError, "network name %s invalid", network.Name)
+ }
+ if _, err := n.Network(network.Name); err == nil {
+ return errors.Wrapf(define.ErrNetworkExists, "network name %s already used", network.Name)
+ }
+ } else {
+ name, err = GetFreeDeviceName(n)
+ if err != nil {
+ return err
+ }
+ network.Name = name
+ // also use the name as interface name when we create a bridge network
+ if network.Driver == types.BridgeNetworkDriver && network.NetworkInterface == "" {
+ network.NetworkInterface = name
+ }
+ }
+ return nil
+}
diff --git a/libpod/network/internal/util/interface.go b/libpod/network/internal/util/interface.go
new file mode 100644
index 000000000..4b01a09b8
--- /dev/null
+++ b/libpod/network/internal/util/interface.go
@@ -0,0 +1,19 @@
+package util
+
+import "github.com/containers/podman/v3/libpod/network/types"
+
+// This is a helper package to allow code sharing between the different
+// network interfaces.
+
+// NetUtil is a helper interface which all network interfaces should implement to allow easy code sharing
+type NetUtil interface {
+ // ForEach eaxecutes the given function for each network
+ ForEach(func(types.Network))
+ // Len returns the number of networks
+ Len() int
+ // DefaultInterfaceName return the default interface name, this will be suffixed by a number
+ DefaultInterfaceName() string
+ // Network returns the network with the given name or ID.
+ // It returns an error if the network is not found
+ Network(nameOrID string) (*types.Network, error)
+}
diff --git a/libpod/network/util/interfaces.go b/libpod/network/internal/util/interfaces.go
index dc2bd601d..20819f756 100644
--- a/libpod/network/util/interfaces.go
+++ b/libpod/network/internal/util/interfaces.go
@@ -2,9 +2,9 @@ package util
import "net"
-// GetLiveNetworkSubnets returns a slice of subnets representing what the system
+// getLiveNetworkSubnets returns a slice of subnets representing what the system
// has defined as network interfaces
-func GetLiveNetworkSubnets() ([]*net.IPNet, error) {
+func getLiveNetworkSubnets() ([]*net.IPNet, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, err
diff --git a/libpod/network/internal/util/ip.go b/libpod/network/internal/util/ip.go
new file mode 100644
index 000000000..7fe35d3d4
--- /dev/null
+++ b/libpod/network/internal/util/ip.go
@@ -0,0 +1,70 @@
+package util
+
+import (
+ "crypto/rand"
+ "net"
+
+ "github.com/pkg/errors"
+)
+
+func incByte(subnet *net.IPNet, idx int, shift uint) error {
+ if idx < 0 {
+ return errors.New("no more subnets left")
+ }
+ if subnet.IP[idx] == 255 {
+ subnet.IP[idx] = 0
+ return incByte(subnet, idx-1, 0)
+ }
+ subnet.IP[idx] += 1 << shift
+ return nil
+}
+
+// NextSubnet returns subnet incremented by 1
+func NextSubnet(subnet *net.IPNet) (*net.IPNet, error) {
+ newSubnet := &net.IPNet{
+ IP: subnet.IP,
+ Mask: subnet.Mask,
+ }
+ ones, bits := newSubnet.Mask.Size()
+ if ones == 0 {
+ return nil, errors.Errorf("%s has only one subnet", subnet.String())
+ }
+ zeroes := uint(bits - ones)
+ shift := zeroes % 8
+ idx := ones/8 - 1
+ if idx < 0 {
+ idx = 0
+ }
+ if err := incByte(newSubnet, idx, shift); err != nil {
+ return nil, err
+ }
+ return newSubnet, nil
+}
+
+func NetworkIntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) bool {
+ for _, nw := range networklist {
+ if networkIntersect(n, nw) {
+ return true
+ }
+ }
+ return false
+}
+
+func networkIntersect(n1, n2 *net.IPNet) bool {
+ return n2.Contains(n1.IP) || n1.Contains(n2.IP)
+}
+
+// getRandomIPv6Subnet returns a random internal ipv6 subnet as described in RFC3879.
+func getRandomIPv6Subnet() (net.IPNet, error) {
+ ip := make(net.IP, 8, net.IPv6len)
+ // read 8 random bytes
+ _, err := rand.Read(ip)
+ if err != nil {
+ return net.IPNet{}, nil
+ }
+ // first byte must be FD as per RFC3879
+ ip[0] = 0xfd
+ // add 8 zero bytes
+ ip = append(ip, make([]byte, 8)...)
+ return net.IPNet{IP: ip, Mask: net.CIDRMask(64, 128)}, nil
+}
diff --git a/libpod/network/internal/util/ip_test.go b/libpod/network/internal/util/ip_test.go
new file mode 100644
index 000000000..eaed769d7
--- /dev/null
+++ b/libpod/network/internal/util/ip_test.go
@@ -0,0 +1,63 @@
+package util
+
+import (
+ "fmt"
+ "net"
+ "reflect"
+ "testing"
+)
+
+func parseCIDR(n string) *net.IPNet {
+ _, parsedNet, _ := net.ParseCIDR(n)
+ return parsedNet
+}
+
+func TestNextSubnet(t *testing.T) {
+ type args struct {
+ subnet *net.IPNet
+ }
+ tests := []struct {
+ name string
+ args args
+ want *net.IPNet
+ wantErr bool
+ }{
+ {"class b", args{subnet: parseCIDR("192.168.0.0/16")}, parseCIDR("192.169.0.0/16"), false},
+ {"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false},
+ }
+ for _, tt := range tests {
+ test := tt
+ t.Run(test.name, func(t *testing.T) {
+ got, err := NextSubnet(test.args.subnet)
+ if (err != nil) != test.wantErr {
+ t.Errorf("NextSubnet() error = %v, wantErr %v", err, test.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("NextSubnet() got = %v, want %v", got, test.want)
+ }
+ })
+ }
+}
+
+func TestGetRandomIPv6Subnet(t *testing.T) {
+ for i := 0; i < 1000; i++ {
+ t.Run(fmt.Sprintf("GetRandomIPv6Subnet %d", i), func(t *testing.T) {
+ sub, err := getRandomIPv6Subnet()
+ if err != nil {
+ t.Errorf("GetRandomIPv6Subnet() error should be nil: %v", err)
+ return
+ }
+ if sub.IP.To4() != nil {
+ t.Errorf("ip %s is not an ipv6 address", sub.IP)
+ }
+ if sub.IP[0] != 0xfd {
+ t.Errorf("ipv6 %s does not start with fd", sub.IP)
+ }
+ ones, bytes := sub.Mask.Size()
+ if ones != 64 || bytes != 128 {
+ t.Errorf("wrong network mask %v, it should be /64", sub.Mask)
+ }
+ })
+ }
+}
diff --git a/libpod/network/internal/util/util.go b/libpod/network/internal/util/util.go
new file mode 100644
index 000000000..bf9d70aba
--- /dev/null
+++ b/libpod/network/internal/util/util.go
@@ -0,0 +1,123 @@
+package util
+
+import (
+ "errors"
+ "fmt"
+ "net"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/containers/podman/v3/pkg/util"
+ "github.com/sirupsen/logrus"
+)
+
+// GetBridgeInterfaceNames returns all bridge interface names
+// already used by network configs
+func GetBridgeInterfaceNames(n NetUtil) []string {
+ names := make([]string, 0, n.Len())
+ n.ForEach(func(net types.Network) {
+ if net.Driver == types.BridgeNetworkDriver {
+ names = append(names, net.NetworkInterface)
+ }
+ })
+ return names
+}
+
+// GetUsedNetworkNames returns all network names already used
+// by network configs
+func GetUsedNetworkNames(n NetUtil) []string {
+ names := make([]string, 0, n.Len())
+ n.ForEach(func(net types.Network) {
+ if net.Driver == types.BridgeNetworkDriver {
+ names = append(names, net.NetworkInterface)
+ }
+ })
+ return names
+}
+
+// GetFreeDeviceName returns a free device name which can
+// be used for new configs as name and bridge interface name.
+// The base name is suffixed by a number
+func GetFreeDeviceName(n NetUtil) (string, error) {
+ bridgeNames := GetBridgeInterfaceNames(n)
+ netNames := GetUsedNetworkNames(n)
+ liveInterfaces, err := GetLiveNetworkNames()
+ if err != nil {
+ return "", nil
+ }
+ names := make([]string, 0, len(bridgeNames)+len(netNames)+len(liveInterfaces))
+ names = append(names, bridgeNames...)
+ names = append(names, netNames...)
+ names = append(names, liveInterfaces...)
+ // FIXME: Is a limit fine?
+ // Start by 1, 0 is reserved for the default network
+ for i := 1; i < 1000000; i++ {
+ deviceName := fmt.Sprintf("%s%d", n.DefaultInterfaceName(), i)
+ if !util.StringInSlice(deviceName, names) {
+ logrus.Debugf("found free device name %s", deviceName)
+ return deviceName, nil
+ }
+ }
+ return "", errors.New("could not find free device name, to many iterations")
+}
+
+// GetUsedSubnets returns a list of all used subnets by network
+// configs and interfaces on the host.
+func GetUsedSubnets(n NetUtil) ([]*net.IPNet, error) {
+ // first, load all used subnets from network configs
+ subnets := make([]*net.IPNet, 0, n.Len())
+ n.ForEach(func(n types.Network) {
+ for i := range n.Subnets {
+ subnets = append(subnets, &n.Subnets[i].Subnet.IPNet)
+ }
+ })
+ // second, load networks from the current system
+ liveSubnets, err := getLiveNetworkSubnets()
+ if err != nil {
+ return nil, err
+ }
+ return append(subnets, liveSubnets...), nil
+}
+
+// GetFreeIPv6NetworkSubnet returns a unused ipv4 subnet
+func GetFreeIPv4NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) {
+ // the default podman network is 10.88.0.0/16
+ // start locking for free /24 networks
+ network := &net.IPNet{
+ IP: net.IP{10, 89, 0, 0},
+ Mask: net.IPMask{255, 255, 255, 0},
+ }
+
+ // TODO: make sure to not use public subnets
+ for {
+ if intersectsConfig := NetworkIntersectsWithNetworks(network, usedNetworks); !intersectsConfig {
+ logrus.Debugf("found free ipv4 network subnet %s", network.String())
+ return &types.Subnet{
+ Subnet: types.IPNet{IPNet: *network},
+ }, nil
+ }
+ var err error
+ network, err = NextSubnet(network)
+ if err != nil {
+ return nil, err
+ }
+ }
+}
+
+// GetFreeIPv6NetworkSubnet returns a unused ipv6 subnet
+func GetFreeIPv6NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) {
+ // FIXME: Is 10000 fine as limit? We should prevent an endless loop.
+ for i := 0; i < 10000; i++ {
+ // RFC4193: Choose the ipv6 subnet random and NOT sequentially.
+ network, err := getRandomIPv6Subnet()
+ if err != nil {
+ return nil, err
+ }
+ if intersectsConfig := NetworkIntersectsWithNetworks(&network, usedNetworks); !intersectsConfig {
+ logrus.Debugf("found free ipv6 network subnet %s", network.String())
+ return &types.Subnet{
+ Subnet: types.IPNet{IPNet: network},
+ }, nil
+ }
+ }
+ return nil, errors.New("failed to get random ipv6 subnet")
+}
diff --git a/libpod/network/internal/util/validate.go b/libpod/network/internal/util/validate.go
new file mode 100644
index 000000000..03a985043
--- /dev/null
+++ b/libpod/network/internal/util/validate.go
@@ -0,0 +1,97 @@
+package util
+
+import (
+ "net"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/containers/podman/v3/libpod/network/util"
+ "github.com/pkg/errors"
+)
+
+// ValidateSubnet will validate a given Subnet. It checks if the
+// given gateway and lease range are part of this subnet. If the
+// gateway is empty and addGateway is true it will get the first
+// available ip in the subnet assigned.
+func ValidateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet) error {
+ if s == nil {
+ return errors.New("subnet is nil")
+ }
+ if s.Subnet.IP == nil {
+ return errors.New("subnet ip is nil")
+ }
+
+ // Reparse to ensure subnet is valid.
+ // Do not use types.ParseCIDR() because we want the ip to be
+ // the network address and not a random ip in the subnet.
+ _, net, err := net.ParseCIDR(s.Subnet.String())
+ if err != nil {
+ return errors.Wrap(err, "subnet invalid")
+ }
+
+ // check that the new subnet does not conflict with existing ones
+ if NetworkIntersectsWithNetworks(net, usedNetworks) {
+ return errors.Errorf("subnet %s is already used on the host or by another config", net.String())
+ }
+
+ s.Subnet = types.IPNet{IPNet: *net}
+ if s.Gateway != nil {
+ if !s.Subnet.Contains(s.Gateway) {
+ return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
+ }
+ } else if addGateway {
+ ip, err := util.FirstIPInSubnet(net)
+ if err != nil {
+ return err
+ }
+ s.Gateway = ip
+ }
+ if s.LeaseRange != nil {
+ if s.LeaseRange.StartIP != nil && !s.Subnet.Contains(s.LeaseRange.StartIP) {
+ return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
+ }
+ if s.LeaseRange.EndIP != nil && !s.Subnet.Contains(s.LeaseRange.EndIP) {
+ return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
+ }
+ }
+ return nil
+}
+
+func ValidateSetupOptions(n NetUtil, namespacePath string, options types.SetupOptions) error {
+ if namespacePath == "" {
+ return errors.New("namespacePath is empty")
+ }
+ if options.ContainerID == "" {
+ return errors.New("ContainerID is empty")
+ }
+ if len(options.Networks) == 0 {
+ return errors.New("must specify at least one network")
+ }
+ for name, netOpts := range options.Networks {
+ network, err := n.Network(name)
+ if err != nil {
+ return err
+ }
+ err = validatePerNetworkOpts(network, netOpts)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// validatePerNetworkOpts checks that all given static ips are in a subnet on this network
+func validatePerNetworkOpts(network *types.Network, netOpts types.PerNetworkOptions) error {
+ if netOpts.InterfaceName == "" {
+ return errors.Errorf("interface name on network %s is empty", network.Name)
+ }
+outer:
+ for _, ip := range netOpts.StaticIPs {
+ for _, s := range network.Subnets {
+ if s.Subnet.Contains(ip) {
+ continue outer
+ }
+ }
+ return errors.Errorf("requested static ip %s not in any subnet on network %s", ip.String(), network.Name)
+ }
+ return nil
+}
diff --git a/libpod/network/util/ip.go b/libpod/network/util/ip.go
index b2ba92735..e75107a1c 100644
--- a/libpod/network/util/ip.go
+++ b/libpod/network/util/ip.go
@@ -1,11 +1,6 @@
package util
-import (
- "crypto/rand"
- "net"
-
- "github.com/pkg/errors"
-)
+import "net"
// IsIPv6 returns true if netIP is IPv6.
func IsIPv6(netIP net.IP) bool {
@@ -17,40 +12,6 @@ func IsIPv4(netIP net.IP) bool {
return netIP != nil && netIP.To4() != nil
}
-func incByte(subnet *net.IPNet, idx int, shift uint) error {
- if idx < 0 {
- return errors.New("no more subnets left")
- }
- if subnet.IP[idx] == 255 {
- subnet.IP[idx] = 0
- return incByte(subnet, idx-1, 0)
- }
- subnet.IP[idx] += 1 << shift
- return nil
-}
-
-// NextSubnet returns subnet incremented by 1
-func NextSubnet(subnet *net.IPNet) (*net.IPNet, error) {
- newSubnet := &net.IPNet{
- IP: subnet.IP,
- Mask: subnet.Mask,
- }
- ones, bits := newSubnet.Mask.Size()
- if ones == 0 {
- return nil, errors.Errorf("%s has only one subnet", subnet.String())
- }
- zeroes := uint(bits - ones)
- shift := zeroes % 8
- idx := ones/8 - 1
- if idx < 0 {
- idx = 0
- }
- if err := incByte(newSubnet, idx, shift); err != nil {
- return nil, err
- }
- return newSubnet, nil
-}
-
// LastIPInSubnet gets the last IP in a subnet
func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
// re-parse to ensure clean network address
@@ -83,31 +44,3 @@ func FirstIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
cidr.IP[len(cidr.IP)-1]++
return cidr.IP, nil
}
-
-func NetworkIntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) bool {
- for _, nw := range networklist {
- if networkIntersect(n, nw) {
- return true
- }
- }
- return false
-}
-
-func networkIntersect(n1, n2 *net.IPNet) bool {
- return n2.Contains(n1.IP) || n1.Contains(n2.IP)
-}
-
-// GetRandomIPv6Subnet returns a random internal ipv6 subnet as described in RFC3879.
-func GetRandomIPv6Subnet() (net.IPNet, error) {
- ip := make(net.IP, 8, net.IPv6len)
- // read 8 random bytes
- _, err := rand.Read(ip)
- if err != nil {
- return net.IPNet{}, nil
- }
- // first byte must be FD as per RFC3879
- ip[0] = 0xfd
- // add 8 zero bytes
- ip = append(ip, make([]byte, 8)...)
- return net.IPNet{IP: ip, Mask: net.CIDRMask(64, 128)}, nil
-}
diff --git a/libpod/network/util/ip_test.go b/libpod/network/util/ip_test.go
index c26ad140a..63ac555f0 100644
--- a/libpod/network/util/ip_test.go
+++ b/libpod/network/util/ip_test.go
@@ -1,9 +1,7 @@
package util
import (
- "fmt"
"net"
- "reflect"
"testing"
)
@@ -12,34 +10,6 @@ func parseCIDR(n string) *net.IPNet {
return parsedNet
}
-func TestNextSubnet(t *testing.T) {
- type args struct {
- subnet *net.IPNet
- }
- tests := []struct {
- name string
- args args
- want *net.IPNet
- wantErr bool
- }{
- {"class b", args{subnet: parseCIDR("192.168.0.0/16")}, parseCIDR("192.169.0.0/16"), false},
- {"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false},
- }
- for _, tt := range tests {
- test := tt
- t.Run(test.name, func(t *testing.T) {
- got, err := NextSubnet(test.args.subnet)
- if (err != nil) != test.wantErr {
- t.Errorf("NextSubnet() error = %v, wantErr %v", err, test.wantErr)
- return
- }
- if !reflect.DeepEqual(got, test.want) {
- t.Errorf("NextSubnet() got = %v, want %v", got, test.want)
- }
- })
- }
-}
-
func TestFirstIPInSubnet(t *testing.T) {
tests := []struct {
name string
@@ -101,25 +71,3 @@ func TestLastIPInSubnet(t *testing.T) {
})
}
}
-
-func TestGetRandomIPv6Subnet(t *testing.T) {
- for i := 0; i < 1000; i++ {
- t.Run(fmt.Sprintf("GetRandomIPv6Subnet %d", i), func(t *testing.T) {
- sub, err := GetRandomIPv6Subnet()
- if err != nil {
- t.Errorf("GetRandomIPv6Subnet() error should be nil: %v", err)
- return
- }
- if sub.IP.To4() != nil {
- t.Errorf("ip %s is not an ipv6 address", sub.IP)
- }
- if sub.IP[0] != 0xfd {
- t.Errorf("ipv6 %s does not start with fd", sub.IP)
- }
- ones, bytes := sub.Mask.Size()
- if ones != 64 || bytes != 128 {
- t.Errorf("wrong network mask %v, it should be /64", sub.Mask)
- }
- })
- }
-}