use libnetwork from c/common

The libpod/network packages were moved to c/common so that buildah can
use it as well. To prevent duplication use it in podman as well and
remove it from here.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2021-12-17 14:46:15 +01:00
parent 2cdab5d539
commit 495884b319
121 changed files with 458 additions and 6459 deletions

View File

@@ -0,0 +1,68 @@
package util
import (
"net"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/libnetwork/util"
pkgutil "github.com/containers/common/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 !types.NameRegex.MatchString(network.NetworkInterface) {
return errors.Wrapf(types.RegexError, "bridge name %s invalid", network.NetworkInterface)
}
} else {
var err error
network.NetworkInterface, err = GetFreeDeviceName(n)
if err != nil {
return err
}
}
if network.IPAMOptions["driver"] != types.DHCPIPAMDriver {
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
}

View File

@@ -0,0 +1,41 @@
package util
import (
"github.com/containers/common/libnetwork/types"
"github.com/pkg/errors"
)
func CommonNetworkCreate(n NetUtil, network *types.Network) error {
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 !types.NameRegex.MatchString(network.Name) {
return errors.Wrapf(types.RegexError, "network name %s invalid", network.Name)
}
if _, err := n.Network(network.Name); err == nil {
return errors.Wrapf(types.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
}

View File

@@ -0,0 +1,19 @@
package util
import "github.com/containers/common/libnetwork/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)
}

View File

@@ -0,0 +1,34 @@
package util
import "net"
// getLiveNetworkSubnets returns a slice of subnets representing what the system
// has defined as network interfaces
func getLiveNetworkSubnets() ([]*net.IPNet, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
nets := make([]*net.IPNet, 0, len(addrs))
for _, address := range addrs {
_, n, err := net.ParseCIDR(address.String())
if err != nil {
return nil, err
}
nets = append(nets, n)
}
return nets, nil
}
// GetLiveNetworkNames returns a list of network interface names on the system
func GetLiveNetworkNames() ([]string, error) {
liveInterfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
interfaceNames := make([]string, 0, len(liveInterfaces))
for _, i := range liveInterfaces {
interfaceNames = append(interfaceNames, i.Name)
}
return interfaceNames, nil
}

View 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{}, err
}
// 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
}

View File

@@ -0,0 +1,37 @@
package util
import (
"strconv"
"github.com/pkg/errors"
)
// ParseMTU parses the mtu option
func ParseMTU(mtu string) (int, error) {
if mtu == "" {
return 0, nil // default
}
m, err := strconv.Atoi(mtu)
if err != nil {
return 0, err
}
if m < 0 {
return 0, errors.Errorf("mtu %d is less than zero", m)
}
return m, nil
}
// ParseVlan parses the vlan option
func ParseVlan(vlan string) (int, error) {
if vlan == "" {
return 0, nil // default
}
v, err := strconv.Atoi(vlan)
if err != nil {
return 0, err
}
if v < 0 || v > 4094 {
return 0, errors.Errorf("vlan ID %d must be between 0 and 4094", v)
}
return v, nil
}

View File

@@ -0,0 +1,123 @@
package util
import (
"errors"
"fmt"
"net"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/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
}
// GetFreeIPv4NetworkSubnet 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")
}

View File

@@ -0,0 +1,124 @@
package util
import (
"net"
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/libnetwork/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.
_, n, 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(n, usedNetworks) {
return errors.Errorf("subnet %s is already used on the host or by another config", n.String())
}
s.Subnet = types.IPNet{IPNet: *n}
if s.Gateway != nil {
if !s.Subnet.Contains(s.Gateway) {
return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
}
util.NormalizeIP(&s.Gateway)
} else if addGateway {
ip, err := util.FirstIPInSubnet(n)
if err != nil {
return err
}
s.Gateway = ip
}
if s.LeaseRange != nil {
if s.LeaseRange.StartIP != nil {
if !s.Subnet.Contains(s.LeaseRange.StartIP) {
return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
}
util.NormalizeIP(&s.LeaseRange.StartIP)
}
if s.LeaseRange.EndIP != nil {
if !s.Subnet.Contains(s.LeaseRange.EndIP) {
return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
}
util.NormalizeIP(&s.LeaseRange.EndIP)
}
}
return nil
}
// ValidateSubnets will validate the subnets for this network.
// It also sets the gateway if the gateway is empty and it sets
// IPv6Enabled to true if at least one subnet is ipv6.
func ValidateSubnets(network *types.Network, usedNetworks []*net.IPNet) error {
for i := range network.Subnets {
err := ValidateSubnet(&network.Subnets[i], !network.Internal, usedNetworks)
if err != nil {
return err
}
if util.IsIPv6(network.Subnets[i].Subnet.IP) {
network.IPv6Enabled = true
}
}
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 {
netOpts := netOpts
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)
}
if network.IPAMOptions["driver"] == types.HostLocalIPAMDriver {
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
}