mirror of
https://github.com/containers/podman.git
synced 2025-06-24 11:28:24 +08:00
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>
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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"))
|
||||
})
|
||||
})
|
||||
|
||||
|
67
libpod/network/internal/util/bridge.go
Normal file
67
libpod/network/internal/util/bridge.go
Normal file
@ -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
|
||||
}
|
48
libpod/network/internal/util/create.go
Normal file
48
libpod/network/internal/util/create.go
Normal file
@ -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
|
||||
}
|
19
libpod/network/internal/util/interface.go
Normal file
19
libpod/network/internal/util/interface.go
Normal file
@ -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)
|
||||
}
|
@ -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
|
70
libpod/network/internal/util/ip.go
Normal file
70
libpod/network/internal/util/ip.go
Normal file
@ -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
|
||||
}
|
63
libpod/network/internal/util/ip_test.go
Normal file
63
libpod/network/internal/util/ip_test.go
Normal file
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
123
libpod/network/internal/util/util.go
Normal file
123
libpod/network/internal/util/util.go
Normal file
@ -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")
|
||||
}
|
97
libpod/network/internal/util/validate.go
Normal file
97
libpod/network/internal/util/validate.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user