// +build linux package cni import ( "context" "crypto/sha256" "encoding/hex" "fmt" "net" "os" "strings" "time" "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 // modTime is the timestamp when the config dir was modified modTime time.Time // 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 } // Drivers will return the list of supported network drivers // for this interface. func (n *cniNetwork) Drivers() []string { return []string{types.BridgeNetworkDriver, types.MacVLANNetworkDriver, types.IPVLANNetworkDriver} } func (n *cniNetwork) loadNetworks() error { // check the mod time of the config dir f, err := os.Stat(n.cniConfigDir) if err != nil { return err } modTime := f.ModTime() // skip loading networks if they are already loaded and // if the config dir was not modified since the last call if n.networks != nil && modTime.Equal(n.modTime) { return nil } // make sure the remove all networks before we reload them n.networks = nil n.modTime = modTime // 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 !errors.Is(err, os.ErrNotExist) { 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.Debugf("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, true) } // 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(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 := 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 i := range val.libpodNet.Subnets { subnets = append(subnets, &val.libpodNet.Subnets[i].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 }