summaryrefslogtreecommitdiff
path: root/libpod/network/internal
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 /libpod/network/internal
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>
Diffstat (limited to 'libpod/network/internal')
-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.go34
-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
8 files changed, 521 insertions, 0 deletions
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/internal/util/interfaces.go b/libpod/network/internal/util/interfaces.go
new file mode 100644
index 000000000..20819f756
--- /dev/null
+++ b/libpod/network/internal/util/interfaces.go
@@ -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
+}
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
+}