From 12c62b92ff2f63cb34dcb9c0555b96983e6aad94 Mon Sep 17 00:00:00 2001
From: Paul Holzinger <pholzing@redhat.com>
Date: Wed, 13 Oct 2021 21:52:55 +0200
Subject: [PATCH] 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>
---
 libpod/network/cni/config.go                  | 152 +-----------------
 libpod/network/cni/network.go                 | 124 +++-----------
 libpod/network/cni/run.go                     |  39 +----
 libpod/network/cni/run_test.go                |   2 +-
 libpod/network/internal/util/bridge.go        |  67 ++++++++
 libpod/network/internal/util/create.go        |  48 ++++++
 libpod/network/internal/util/interface.go     |  19 +++
 .../network/{ => internal}/util/interfaces.go |   4 +-
 libpod/network/internal/util/ip.go            |  70 ++++++++
 libpod/network/internal/util/ip_test.go       |  63 ++++++++
 libpod/network/internal/util/util.go          | 123 ++++++++++++++
 libpod/network/internal/util/validate.go      |  97 +++++++++++
 libpod/network/util/ip.go                     |  69 +-------
 libpod/network/util/ip_test.go                |  52 ------
 14 files changed, 522 insertions(+), 407 deletions(-)
 create mode 100644 libpod/network/internal/util/bridge.go
 create mode 100644 libpod/network/internal/util/create.go
 create mode 100644 libpod/network/internal/util/interface.go
 rename libpod/network/{ => internal}/util/interfaces.go (86%)
 create mode 100644 libpod/network/internal/util/ip.go
 create mode 100644 libpod/network/internal/util/ip_test.go
 create mode 100644 libpod/network/internal/util/util.go
 create mode 100644 libpod/network/internal/util/validate.go

diff --git a/libpod/network/cni/config.go b/libpod/network/cni/config.go
index 3df155637b..a63574f258 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 a37a84373c..3e9cdaa470 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},
-	}
+// Implement the NetUtil interface for easy code sharing with other network interfaces.
 
-	// 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
-		}
-	}
-}
-
-// 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()
+}
+
+// Len return the number of networks
+func (n *cniNetwork) Len() int {
+	return len(n.networks)
+}
+
+// DefaultInterfaceName return the default cni bridge name, must be suffixed with a number.
+func (n *cniNetwork) DefaultInterfaceName() string {
+	return cniDeviceName
+}
+
+func (n *cniNetwork) Network(nameOrID string) (*types.Network, error) {
+	network, err := n.getNetwork(nameOrID)
 	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")
-}
-
-// 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
-}
-
-// 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)
-		}
-	}
-	return names
+	return network.libpodNet, err
 }
diff --git a/libpod/network/cni/run.go b/libpod/network/cni/run.go
index 7795dfeebb..667ed3ab14 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 3169cd0eb2..6c54f82efd 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 0000000000..c054c7d4e5
--- /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 0000000000..ca716f913d
--- /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 0000000000..4b01a09b8b
--- /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
similarity index 86%
rename from libpod/network/util/interfaces.go
rename to libpod/network/internal/util/interfaces.go
index dc2bd601db..20819f7566 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 0000000000..7fe35d3d41
--- /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 0000000000..eaed769d77
--- /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 0000000000..bf9d70abaa
--- /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 0000000000..03a9850435
--- /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 b2ba927356..e75107a1ca 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 c26ad140a3..63ac555f01 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)
-			}
-		})
-	}
-}