github.com/AbhinandanKurakure/podman/v3@v3.4.10/libpod/network/cni/network.go (about)

     1  // +build linux
     2  
     3  package cni
     4  
     5  import (
     6  	"context"
     7  	"crypto/sha256"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"net"
    11  	"os"
    12  	"strings"
    13  
    14  	"github.com/containernetworking/cni/libcni"
    15  	"github.com/containers/podman/v3/libpod/define"
    16  	"github.com/containers/podman/v3/libpod/network/types"
    17  	"github.com/containers/podman/v3/libpod/network/util"
    18  	pkgutil "github.com/containers/podman/v3/pkg/util"
    19  	"github.com/containers/storage/pkg/lockfile"
    20  	"github.com/pkg/errors"
    21  	"github.com/sirupsen/logrus"
    22  )
    23  
    24  type cniNetwork struct {
    25  	// cniConfigDir is directory where the cni config files are stored.
    26  	cniConfigDir string
    27  	// cniPluginDirs is a list of directories where cni should look for the plugins.
    28  	cniPluginDirs []string
    29  
    30  	cniConf *libcni.CNIConfig
    31  
    32  	// defaultNetwork is the name for the default network.
    33  	defaultNetwork string
    34  	// defaultSubnet is the default subnet for the default network.
    35  	defaultSubnet types.IPNet
    36  
    37  	// isMachine describes whenever podman runs in a podman machine environment.
    38  	isMachine bool
    39  
    40  	// lock is a internal lock for critical operations
    41  	lock lockfile.Locker
    42  
    43  	// networks is a map with loaded networks, the key is the network name
    44  	networks map[string]*network
    45  }
    46  
    47  type network struct {
    48  	// filename is the full path to the cni config file on disk
    49  	filename  string
    50  	libpodNet *types.Network
    51  	cniNet    *libcni.NetworkConfigList
    52  }
    53  
    54  type InitConfig struct {
    55  	// CNIConfigDir is directory where the cni config files are stored.
    56  	CNIConfigDir string
    57  	// CNIPluginDirs is a list of directories where cni should look for the plugins.
    58  	CNIPluginDirs []string
    59  
    60  	// DefaultNetwork is the name for the default network.
    61  	DefaultNetwork string
    62  	// DefaultSubnet is the default subnet for the default network.
    63  	DefaultSubnet string
    64  
    65  	// IsMachine describes whenever podman runs in a podman machine environment.
    66  	IsMachine bool
    67  
    68  	// LockFile is the path to lock file.
    69  	LockFile string
    70  }
    71  
    72  // NewCNINetworkInterface creates the ContainerNetwork interface for the CNI backend.
    73  // Note: The networks are not loaded from disk until a method is called.
    74  func NewCNINetworkInterface(conf InitConfig) (types.ContainerNetwork, error) {
    75  	// TODO: consider using a shared memory lock
    76  	lock, err := lockfile.GetLockfile(conf.LockFile)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	defaultNetworkName := conf.DefaultNetwork
    82  	if defaultNetworkName == "" {
    83  		defaultNetworkName = types.DefaultNetworkName
    84  	}
    85  
    86  	defaultSubnet := conf.DefaultSubnet
    87  	if defaultSubnet == "" {
    88  		defaultSubnet = types.DefaultSubnet
    89  	}
    90  	defaultNet, err := types.ParseCIDR(defaultSubnet)
    91  	if err != nil {
    92  		return nil, errors.Wrap(err, "failed to parse default subnet")
    93  	}
    94  
    95  	cni := libcni.NewCNIConfig(conf.CNIPluginDirs, &cniExec{})
    96  	n := &cniNetwork{
    97  		cniConfigDir:   conf.CNIConfigDir,
    98  		cniPluginDirs:  conf.CNIPluginDirs,
    99  		cniConf:        cni,
   100  		defaultNetwork: defaultNetworkName,
   101  		defaultSubnet:  defaultNet,
   102  		isMachine:      conf.IsMachine,
   103  		lock:           lock,
   104  	}
   105  
   106  	return n, nil
   107  }
   108  
   109  func (n *cniNetwork) loadNetworks() error {
   110  	// skip loading networks if they are already loaded
   111  	if n.networks != nil {
   112  		return nil
   113  	}
   114  	// FIXME: do we have to support other file types as well, e.g. .conf?
   115  	files, err := libcni.ConfFiles(n.cniConfigDir, []string{".conflist"})
   116  	if err != nil {
   117  		return err
   118  	}
   119  	networks := make(map[string]*network, len(files))
   120  	for _, file := range files {
   121  		conf, err := libcni.ConfListFromFile(file)
   122  		if err != nil {
   123  			// do not log ENOENT errors
   124  			if !os.IsNotExist(err) {
   125  				logrus.Warnf("Error loading CNI config file %s: %v", file, err)
   126  			}
   127  			continue
   128  		}
   129  
   130  		if !define.NameRegex.MatchString(conf.Name) {
   131  			logrus.Warnf("CNI config list %s has invalid name, skipping: %v", file, define.RegexError)
   132  			continue
   133  		}
   134  
   135  		if _, err := n.cniConf.ValidateNetworkList(context.Background(), conf); err != nil {
   136  			logrus.Warnf("Error validating CNI config file %s: %v", file, err)
   137  			continue
   138  		}
   139  
   140  		if val, ok := networks[conf.Name]; ok {
   141  			logrus.Warnf("CNI config list %s has the same network name as %s, skipping", file, val.filename)
   142  			continue
   143  		}
   144  
   145  		net, err := createNetworkFromCNIConfigList(conf, file)
   146  		if err != nil {
   147  			logrus.Errorf("CNI config list %s could not be converted to a libpod config, skipping: %v", file, err)
   148  			continue
   149  		}
   150  		logrus.Tracef("Successfully loaded network %s: %v", net.Name, net)
   151  		networkInfo := network{
   152  			filename:  file,
   153  			cniNet:    conf,
   154  			libpodNet: net,
   155  		}
   156  		networks[net.Name] = &networkInfo
   157  	}
   158  
   159  	// create the default network in memory if it did not exists on disk
   160  	if networks[n.defaultNetwork] == nil {
   161  		networkInfo, err := n.createDefaultNetwork()
   162  		if err != nil {
   163  			return errors.Wrapf(err, "failed to create default network %s", n.defaultNetwork)
   164  		}
   165  		networks[n.defaultNetwork] = networkInfo
   166  	}
   167  
   168  	logrus.Debugf("Successfully loaded %d networks", len(networks))
   169  	n.networks = networks
   170  	return nil
   171  }
   172  
   173  func (n *cniNetwork) createDefaultNetwork() (*network, error) {
   174  	net := types.Network{
   175  		Name:             n.defaultNetwork,
   176  		NetworkInterface: "cni-podman0",
   177  		Driver:           types.BridgeNetworkDriver,
   178  		Subnets: []types.Subnet{
   179  			{Subnet: n.defaultSubnet},
   180  		},
   181  	}
   182  	return n.networkCreate(net, false)
   183  }
   184  
   185  // getNetwork will lookup a network by name or ID. It returns an
   186  // error when no network was found or when more than one network
   187  // with the given (partial) ID exists.
   188  // getNetwork will read from the networks map, therefore the caller
   189  // must ensure that n.lock is locked before using it.
   190  func (n *cniNetwork) getNetwork(nameOrID string) (*network, error) {
   191  	// fast path check the map key, this will only work for names
   192  	if val, ok := n.networks[nameOrID]; ok {
   193  		return val, nil
   194  	}
   195  	// If there was no match we might got a full or partial ID.
   196  	var net *network
   197  	for _, val := range n.networks {
   198  		// This should not happen because we already looked up the map by name but check anyway.
   199  		if val.libpodNet.Name == nameOrID {
   200  			return val, nil
   201  		}
   202  
   203  		if strings.HasPrefix(val.libpodNet.ID, nameOrID) {
   204  			if net != nil {
   205  				return nil, errors.Errorf("more than one result for network ID %s", nameOrID)
   206  			}
   207  			net = val
   208  		}
   209  	}
   210  	if net != nil {
   211  		return net, nil
   212  	}
   213  	return nil, errors.Wrapf(define.ErrNoSuchNetwork, "unable to find network with name or ID %s", nameOrID)
   214  }
   215  
   216  // getNetworkIDFromName creates a network ID from the name. It is just the
   217  // sha256 hash so it is not safe but it should be safe enough for our use case.
   218  func getNetworkIDFromName(name string) string {
   219  	hash := sha256.Sum256([]byte(name))
   220  	return hex.EncodeToString(hash[:])
   221  }
   222  
   223  // getFreeIPv6NetworkSubnet returns a unused ipv4 subnet
   224  func (n *cniNetwork) getFreeIPv4NetworkSubnet() (*types.Subnet, error) {
   225  	networks, err := n.getUsedSubnets()
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	// the default podman network is 10.88.0.0/16
   231  	// start locking for free /24 networks
   232  	network := &net.IPNet{
   233  		IP:   net.IP{10, 89, 0, 0},
   234  		Mask: net.IPMask{255, 255, 255, 0},
   235  	}
   236  
   237  	// TODO: make sure to not use public subnets
   238  	for {
   239  		if intersectsConfig := util.NetworkIntersectsWithNetworks(network, networks); !intersectsConfig {
   240  			logrus.Debugf("found free ipv4 network subnet %s", network.String())
   241  			return &types.Subnet{
   242  				Subnet: types.IPNet{IPNet: *network},
   243  			}, nil
   244  		}
   245  		network, err = util.NextSubnet(network)
   246  		if err != nil {
   247  			return nil, err
   248  		}
   249  	}
   250  }
   251  
   252  // getFreeIPv6NetworkSubnet returns a unused ipv6 subnet
   253  func (n *cniNetwork) getFreeIPv6NetworkSubnet() (*types.Subnet, error) {
   254  	networks, err := n.getUsedSubnets()
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	// FIXME: Is 10000 fine as limit? We should prevent an endless loop.
   260  	for i := 0; i < 10000; i++ {
   261  		// RFC4193: Choose the ipv6 subnet random and NOT sequentially.
   262  		network, err := util.GetRandomIPv6Subnet()
   263  		if err != nil {
   264  			return nil, err
   265  		}
   266  		if intersectsConfig := util.NetworkIntersectsWithNetworks(&network, networks); !intersectsConfig {
   267  			logrus.Debugf("found free ipv6 network subnet %s", network.String())
   268  			return &types.Subnet{
   269  				Subnet: types.IPNet{IPNet: network},
   270  			}, nil
   271  		}
   272  	}
   273  	return nil, errors.New("failed to get random ipv6 subnet")
   274  }
   275  
   276  // getUsedSubnets returns a list of all used subnets by network
   277  // configs and interfaces on the host.
   278  func (n *cniNetwork) getUsedSubnets() ([]*net.IPNet, error) {
   279  	// first, load all used subnets from network configs
   280  	subnets := make([]*net.IPNet, 0, len(n.networks))
   281  	for _, val := range n.networks {
   282  		for _, subnet := range val.libpodNet.Subnets {
   283  			// nolint:exportloopref
   284  			subnets = append(subnets, &subnet.Subnet.IPNet)
   285  		}
   286  	}
   287  	// second, load networks from the current system
   288  	liveSubnets, err := util.GetLiveNetworkSubnets()
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	return append(subnets, liveSubnets...), nil
   293  }
   294  
   295  // getFreeDeviceName returns a free device name which can
   296  // be used for new configs as name and bridge interface name
   297  func (n *cniNetwork) getFreeDeviceName() (string, error) {
   298  	bridgeNames := n.getBridgeInterfaceNames()
   299  	netNames := n.getUsedNetworkNames()
   300  	liveInterfaces, err := util.GetLiveNetworkNames()
   301  	if err != nil {
   302  		return "", nil
   303  	}
   304  	names := make([]string, 0, len(bridgeNames)+len(netNames)+len(liveInterfaces))
   305  	names = append(names, bridgeNames...)
   306  	names = append(names, netNames...)
   307  	names = append(names, liveInterfaces...)
   308  	// FIXME: Is a limit fine?
   309  	// Start by 1, 0 is reserved for the default network
   310  	for i := 1; i < 1000000; i++ {
   311  		deviceName := fmt.Sprintf("%s%d", cniDeviceName, i)
   312  		if !pkgutil.StringInSlice(deviceName, names) {
   313  			logrus.Debugf("found free device name %s", deviceName)
   314  			return deviceName, nil
   315  		}
   316  	}
   317  	return "", errors.New("could not find free device name, to many iterations")
   318  }
   319  
   320  // getUsedNetworkNames returns all network names already used
   321  // by network configs
   322  func (n *cniNetwork) getUsedNetworkNames() []string {
   323  	names := make([]string, 0, len(n.networks))
   324  	for _, val := range n.networks {
   325  		names = append(names, val.libpodNet.Name)
   326  	}
   327  	return names
   328  }
   329  
   330  // getUsedNetworkNames returns all bridge device names already used
   331  // by network configs
   332  func (n *cniNetwork) getBridgeInterfaceNames() []string {
   333  	names := make([]string, 0, len(n.networks))
   334  	for _, val := range n.networks {
   335  		if val.libpodNet.Driver == types.BridgeNetworkDriver {
   336  			names = append(names, val.libpodNet.NetworkInterface)
   337  		}
   338  	}
   339  	return names
   340  }