diff options
Diffstat (limited to 'libpod/network/cni/network.go')
-rw-r--r-- | libpod/network/cni/network.go | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/libpod/network/cni/network.go b/libpod/network/cni/network.go new file mode 100644 index 000000000..fde08a0c6 --- /dev/null +++ b/libpod/network/cni/network.go @@ -0,0 +1,340 @@ +// +build linux + +package cni + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "net" + "os" + "strings" + + "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" +) + +type cniNetwork struct { + // cniConfigDir is directory where the cni config files are stored. + cniConfigDir string + // cniPluginDirs is a list of directories where cni should look for the plugins. + cniPluginDirs []string + + cniConf *libcni.CNIConfig + + // defaultNetwork is the name for the default network. + defaultNetwork string + // defaultSubnet is the default subnet for the default network. + defaultSubnet types.IPNet + + // isMachine describes whenever podman runs in a podman machine environment. + isMachine bool + + // lock is a internal lock for critical operations + lock lockfile.Locker + + // networks is a map with loaded networks, the key is the network name + networks map[string]*network +} + +type network struct { + // filename is the full path to the cni config file on disk + filename string + libpodNet *types.Network + cniNet *libcni.NetworkConfigList +} + +type InitConfig struct { + // CNIConfigDir is directory where the cni config files are stored. + CNIConfigDir string + // CNIPluginDirs is a list of directories where cni should look for the plugins. + CNIPluginDirs []string + + // DefaultNetwork is the name for the default network. + DefaultNetwork string + // DefaultSubnet is the default subnet for the default network. + DefaultSubnet string + + // IsMachine describes whenever podman runs in a podman machine environment. + IsMachine bool + + // LockFile is the path to lock file. + LockFile string +} + +// NewCNINetworkInterface creates the ContainerNetwork interface for the CNI backend. +// Note: The networks are not loaded from disk until a method is called. +func NewCNINetworkInterface(conf InitConfig) (types.ContainerNetwork, error) { + // TODO: consider using a shared memory lock + lock, err := lockfile.GetLockfile(conf.LockFile) + if err != nil { + return nil, err + } + + defaultNetworkName := conf.DefaultNetwork + if defaultNetworkName == "" { + defaultNetworkName = types.DefaultNetworkName + } + + defaultSubnet := conf.DefaultSubnet + if defaultSubnet == "" { + defaultSubnet = types.DefaultSubnet + } + defaultNet, err := types.ParseCIDR(defaultSubnet) + if err != nil { + return nil, errors.Wrap(err, "failed to parse default subnet") + } + + cni := libcni.NewCNIConfig(conf.CNIPluginDirs, &cniExec{}) + n := &cniNetwork{ + cniConfigDir: conf.CNIConfigDir, + cniPluginDirs: conf.CNIPluginDirs, + cniConf: cni, + defaultNetwork: defaultNetworkName, + defaultSubnet: defaultNet, + isMachine: conf.IsMachine, + lock: lock, + } + + return n, nil +} + +func (n *cniNetwork) loadNetworks() error { + // skip loading networks if they are already loaded + if n.networks != nil { + return nil + } + // FIXME: do we have to support other file types as well, e.g. .conf? + files, err := libcni.ConfFiles(n.cniConfigDir, []string{".conflist"}) + if err != nil { + return err + } + networks := make(map[string]*network, len(files)) + for _, file := range files { + conf, err := libcni.ConfListFromFile(file) + if err != nil { + // do not log ENOENT errors + if !os.IsNotExist(err) { + logrus.Warnf("Error loading CNI config file %s: %v", file, err) + } + continue + } + + if !define.NameRegex.MatchString(conf.Name) { + logrus.Warnf("CNI config list %s has invalid name, skipping: %v", file, define.RegexError) + continue + } + + if _, err := n.cniConf.ValidateNetworkList(context.Background(), conf); err != nil { + logrus.Warnf("Error validating CNI config file %s: %v", file, err) + continue + } + + if val, ok := networks[conf.Name]; ok { + logrus.Warnf("CNI config list %s has the same network name as %s, skipping", file, val.filename) + continue + } + + net, err := createNetworkFromCNIConfigList(conf, file) + if err != nil { + logrus.Errorf("CNI config list %s could not be converted to a libpod config, skipping: %v", file, err) + continue + } + logrus.Tracef("Successfully loaded network %s: %v", net.Name, net) + networkInfo := network{ + filename: file, + cniNet: conf, + libpodNet: net, + } + networks[net.Name] = &networkInfo + } + + // create the default network in memory if it did not exists on disk + if networks[n.defaultNetwork] == nil { + networkInfo, err := n.createDefaultNetwork() + if err != nil { + return errors.Wrapf(err, "failed to create default network %s", n.defaultNetwork) + } + networks[n.defaultNetwork] = networkInfo + } + + logrus.Debugf("Successfully loaded %d networks", len(networks)) + n.networks = networks + return nil +} + +func (n *cniNetwork) createDefaultNetwork() (*network, error) { + net := types.Network{ + Name: n.defaultNetwork, + NetworkInterface: "cni-podman0", + Driver: types.BridgeNetworkDriver, + Subnets: []types.Subnet{ + {Subnet: n.defaultSubnet}, + }, + } + return n.networkCreate(net, false) +} + +// getNetwork will lookup a network by name or ID. It returns an +// error when no network was found or when more than one network +// with the given (partial) ID exists. +// getNetwork will read from the networks map, therefore the caller +// must ensure that n.lock is locked before using it. +func (n *cniNetwork) getNetwork(nameOrID string) (*network, error) { + // fast path check the map key, this will only work for names + if val, ok := n.networks[nameOrID]; ok { + return val, nil + } + // If there was no match we might got a full or partial ID. + var net *network + for _, val := range n.networks { + // This should not happen because we already looked up the map by name but check anyway. + if val.libpodNet.Name == nameOrID { + return val, nil + } + + if strings.HasPrefix(val.libpodNet.ID, nameOrID) { + if net != nil { + return nil, errors.Errorf("more than one result for network ID %s", nameOrID) + } + net = val + } + } + if net != nil { + return net, nil + } + return nil, errors.Wrapf(define.ErrNoSuchNetwork, "unable to find network with name or ID %s", nameOrID) +} + +// getNetworkIDFromName creates a network ID from the name. It is just the +// sha256 hash so it is not safe but it should be safe enough for our use case. +func getNetworkIDFromName(name string) string { + hash := sha256.Sum256([]byte(name)) + return hex.EncodeToString(hash[:]) +} + +// getFreeIPv6NetworkSubnet returns a unused ipv4 subnet +func (n *cniNetwork) getFreeIPv4NetworkSubnet() (*types.Subnet, error) { + networks, err := n.getUsedSubnets() + if err != nil { + return nil, err + } + + // 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 := util.NetworkIntersectsWithNetworks(network, networks); !intersectsConfig { + logrus.Debugf("found free ipv4 network subnet %s", network.String()) + return &types.Subnet{ + Subnet: types.IPNet{IPNet: *network}, + }, nil + } + network, err = util.NextSubnet(network) + if err != nil { + return nil, err + } + } +} + +// getFreeIPv6NetworkSubnet returns a unused ipv6 subnet +func (n *cniNetwork) getFreeIPv6NetworkSubnet() (*types.Subnet, error) { + networks, err := n.getUsedSubnets() + if err != nil { + return nil, err + } + + // 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, networks); !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 _, subnet := range val.libpodNet.Subnets { + // nolint:exportloopref + subnets = append(subnets, &subnet.Subnet.IPNet) + } + } + // second, load networks from the current system + liveSubnets, err := util.GetLiveNetworkSubnets() + 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 +} |