mirror of
https://github.com/containers/podman.git
synced 2025-06-24 19:42:56 +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"
|
"os"
|
||||||
|
|
||||||
"github.com/containers/podman/v3/libpod/define"
|
"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/types"
|
||||||
"github.com/containers/podman/v3/libpod/network/util"
|
"github.com/containers/podman/v3/libpod/network/util"
|
||||||
pkgutil "github.com/containers/podman/v3/pkg/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
|
newNetwork.Driver = types.DefaultNetworkDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Should we use a different type for network create without the ID field?
|
err := internalutil.CommonNetworkCreate(n, &newNetwork)
|
||||||
// the caller is not allowed to set a specific ID
|
if err != nil {
|
||||||
if newNetwork.ID != "" {
|
return nil, err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only get the used networks for validation if we do not create the default network.
|
// 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.
|
// fail because it thinks the network is used on the host.
|
||||||
var usedNetworks []*net.IPNet
|
var usedNetworks []*net.IPNet
|
||||||
if !defaultNet {
|
if !defaultNet {
|
||||||
usedNetworks, err = n.getUsedSubnets()
|
usedNetworks, err = internalutil.GetUsedSubnets(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -92,11 +64,7 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
|
|||||||
|
|
||||||
switch newNetwork.Driver {
|
switch newNetwork.Driver {
|
||||||
case types.BridgeNetworkDriver:
|
case types.BridgeNetworkDriver:
|
||||||
// if the name was created with getFreeDeviceName set the interface to it as well
|
err = internalutil.CreateBridge(n, &newNetwork, usedNetworks)
|
||||||
if name != "" && newNetwork.NetworkInterface == "" {
|
|
||||||
newNetwork.NetworkInterface = name
|
|
||||||
}
|
|
||||||
err = n.createBridge(&newNetwork, usedNetworks)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -110,7 +78,7 @@ func (n *cniNetwork) networkCreate(newNetwork types.Network, defaultNet bool) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range newNetwork.Subnets {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -223,7 +191,7 @@ func createIPMACVLAN(network *types.Network) error {
|
|||||||
return errors.New("internal is not supported with macvlan")
|
return errors.New("internal is not supported with macvlan")
|
||||||
}
|
}
|
||||||
if network.NetworkInterface != "" {
|
if network.NetworkInterface != "" {
|
||||||
interfaceNames, err := util.GetLiveNetworkNames()
|
interfaceNames, err := internalutil.GetLiveNetworkNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -238,107 +206,3 @@ func createIPMACVLAN(network *types.Network) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -15,8 +13,6 @@ import (
|
|||||||
"github.com/containernetworking/cni/libcni"
|
"github.com/containernetworking/cni/libcni"
|
||||||
"github.com/containers/podman/v3/libpod/define"
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/libpod/network/types"
|
"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/containers/storage/pkg/lockfile"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -242,111 +238,29 @@ func getNetworkIDFromName(name string) string {
|
|||||||
return hex.EncodeToString(hash[:])
|
return hex.EncodeToString(hash[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFreeIPv6NetworkSubnet returns a unused ipv4 subnet
|
// Implement the NetUtil interface for easy code sharing with other network interfaces.
|
||||||
func (n *cniNetwork) getFreeIPv4NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) {
|
|
||||||
// the default podman network is 10.88.0.0/16
|
|
||||||
// start locking for free /24 networks
|
|
||||||
network := &net.IPNet{
|
|
||||||
IP: net.IP{10, 89, 0, 0},
|
|
||||||
Mask: net.IPMask{255, 255, 255, 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make sure to not use public subnets
|
// ForEach call the given function for each network
|
||||||
for {
|
func (n *cniNetwork) ForEach(run func(types.Network)) {
|
||||||
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))
|
|
||||||
for _, val := range n.networks {
|
for _, val := range n.networks {
|
||||||
for i := range val.libpodNet.Subnets {
|
run(*val.libpodNet)
|
||||||
subnets = append(subnets, &val.libpodNet.Subnets[i].Subnet.IPNet)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return append(subnets, liveSubnets...), nil
|
return network.libpodNet, err
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
types040 "github.com/containernetworking/cni/pkg/types/040"
|
types040 "github.com/containernetworking/cni/pkg/types/040"
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
"github.com/containers/podman/v3/libpod/define"
|
"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/containers/podman/v3/libpod/network/types"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -30,24 +31,9 @@ func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (ma
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if namespacePath == "" {
|
err = util.ValidateSetupOptions(n, namespacePath, options)
|
||||||
return nil, errors.New("namespacePath is empty")
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the loopback adapter up in the container netns
|
// set the loopback adapter up in the container netns
|
||||||
@ -172,23 +158,6 @@ func CNIResultToStatus(res cnitypes.Result) (types.StatusBlock, error) {
|
|||||||
return result, nil
|
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 {
|
func getRuntimeConfig(netns, conName, conID, networkName string, ports []cniPortMapEntry, opts types.PerNetworkOptions) *libcni.RuntimeConf {
|
||||||
rt := &libcni.RuntimeConf{
|
rt := &libcni.RuntimeConf{
|
||||||
ContainerID: conID,
|
ContainerID: conID,
|
||||||
|
@ -1232,7 +1232,7 @@ var _ = Describe("run CNI", func() {
|
|||||||
}
|
}
|
||||||
_, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
|
_, err := libpodNet.Setup(netNSContainer.Path(), setupOpts)
|
||||||
Expect(err).To(HaveOccurred())
|
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"
|
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
|
// has defined as network interfaces
|
||||||
func GetLiveNetworkSubnets() ([]*net.IPNet, error) {
|
func getLiveNetworkSubnets() ([]*net.IPNet, error) {
|
||||||
addrs, err := net.InterfaceAddrs()
|
addrs, err := net.InterfaceAddrs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
package util
|
||||||
|
|
||||||
import (
|
import "net"
|
||||||
"crypto/rand"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsIPv6 returns true if netIP is IPv6.
|
// IsIPv6 returns true if netIP is IPv6.
|
||||||
func IsIPv6(netIP net.IP) bool {
|
func IsIPv6(netIP net.IP) bool {
|
||||||
@ -17,40 +12,6 @@ func IsIPv4(netIP net.IP) bool {
|
|||||||
return netIP != nil && netIP.To4() != nil
|
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
|
// LastIPInSubnet gets the last IP in a subnet
|
||||||
func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
|
func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
|
||||||
// re-parse to ensure clean network address
|
// 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]++
|
cidr.IP[len(cidr.IP)-1]++
|
||||||
return cidr.IP, nil
|
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
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,34 +10,6 @@ func parseCIDR(n string) *net.IPNet {
|
|||||||
return parsedNet
|
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) {
|
func TestFirstIPInSubnet(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
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