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

     1  // +build linux
     2  
     3  package cni
     4  
     5  import (
     6  	"context"
     7  	"net"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/containernetworking/cni/libcni"
    12  	cnitypes "github.com/containernetworking/cni/pkg/types"
    13  	"github.com/containernetworking/cni/pkg/types/current"
    14  	"github.com/containernetworking/plugins/pkg/ns"
    15  	"github.com/containers/podman/v3/libpod/define"
    16  	"github.com/containers/podman/v3/libpod/network/types"
    17  	"github.com/hashicorp/go-multierror"
    18  	"github.com/pkg/errors"
    19  	"github.com/sirupsen/logrus"
    20  	"github.com/vishvananda/netlink"
    21  )
    22  
    23  // Setup will setup the container network namespace. It returns
    24  // a map of StatusBlocks, the key is the network name.
    25  func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (map[string]types.StatusBlock, error) {
    26  	n.lock.Lock()
    27  	defer n.lock.Unlock()
    28  	err := n.loadNetworks()
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	if namespacePath == "" {
    34  		return nil, errors.New("namespacePath is empty")
    35  	}
    36  	if options.ContainerID == "" {
    37  		return nil, errors.New("ContainerID is empty")
    38  	}
    39  	if len(options.Networks) == 0 {
    40  		return nil, errors.New("must specify at least one network")
    41  	}
    42  	for name, netOpts := range options.Networks {
    43  		network := n.networks[name]
    44  		if network == nil {
    45  			return nil, errors.Wrapf(define.ErrNoSuchNetwork, "network %s", name)
    46  		}
    47  		err := validatePerNetworkOpts(network, netOpts)
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  	}
    52  
    53  	// set the loopback adapter up in the container netns
    54  	err = ns.WithNetNSPath(namespacePath, func(_ ns.NetNS) error {
    55  		link, err := netlink.LinkByName("lo")
    56  		if err == nil {
    57  			err = netlink.LinkSetUp(link)
    58  		}
    59  		return err
    60  	})
    61  	if err != nil {
    62  		return nil, errors.Wrapf(err, "failed to set the loopback adapter up")
    63  	}
    64  
    65  	var retErr error
    66  	teardownOpts := options
    67  	teardownOpts.Networks = map[string]types.PerNetworkOptions{}
    68  	// make sure to teardown the already connected networks on error
    69  	defer func() {
    70  		if retErr != nil {
    71  			if len(teardownOpts.Networks) > 0 {
    72  				err := n.teardown(namespacePath, types.TeardownOptions(teardownOpts))
    73  				if err != nil {
    74  					logrus.Warn(err)
    75  				}
    76  			}
    77  		}
    78  	}()
    79  
    80  	ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	results := make(map[string]types.StatusBlock, len(options.Networks))
    86  	for name, netOpts := range options.Networks {
    87  		network := n.networks[name]
    88  		rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, netOpts)
    89  
    90  		// If we have more than one static ip we need parse the ips via runtime config,
    91  		// make sure to add the ips capability to the first plugin otherwise it doesn't get the ips
    92  		if len(netOpts.StaticIPs) > 0 && !network.cniNet.Plugins[0].Network.Capabilities["ips"] {
    93  			caps := make(map[string]interface{})
    94  			caps["capabilities"] = map[string]bool{"ips": true}
    95  			network.cniNet.Plugins[0], retErr = libcni.InjectConf(network.cniNet.Plugins[0], caps)
    96  			if retErr != nil {
    97  				return nil, retErr
    98  			}
    99  		}
   100  
   101  		var res cnitypes.Result
   102  		res, retErr = n.cniConf.AddNetworkList(context.Background(), network.cniNet, rt)
   103  		// Add this network to teardown opts since it is now connected.
   104  		// Also add this if an errors was returned since we want to call teardown on this regardless.
   105  		teardownOpts.Networks[name] = netOpts
   106  		if retErr != nil {
   107  			return nil, retErr
   108  		}
   109  
   110  		var cnires *current.Result
   111  		cnires, retErr = current.GetResult(res)
   112  		if retErr != nil {
   113  			return nil, retErr
   114  		}
   115  		logrus.Debugf("cni result for container %s network %s: %v", options.ContainerID, name, cnires)
   116  		var status types.StatusBlock
   117  		status, retErr = cniResultToStatus(cnires)
   118  		if retErr != nil {
   119  			return nil, retErr
   120  		}
   121  		results[name] = status
   122  	}
   123  	return results, nil
   124  }
   125  
   126  // cniResultToStatus convert the cni result to status block
   127  func cniResultToStatus(cniResult *current.Result) (types.StatusBlock, error) {
   128  	result := types.StatusBlock{}
   129  	nameservers := make([]net.IP, 0, len(cniResult.DNS.Nameservers))
   130  	for _, nameserver := range cniResult.DNS.Nameservers {
   131  		ip := net.ParseIP(nameserver)
   132  		if ip == nil {
   133  			return result, errors.Errorf("failed to parse cni nameserver ip %s", nameserver)
   134  		}
   135  		nameservers = append(nameservers, ip)
   136  	}
   137  	result.DNSServerIPs = nameservers
   138  	result.DNSSearchDomains = cniResult.DNS.Search
   139  
   140  	interfaces := make(map[string]types.NetInterface)
   141  	for _, ip := range cniResult.IPs {
   142  		if ip.Interface == nil {
   143  			// we do no expect ips without an interface
   144  			continue
   145  		}
   146  		if len(cniResult.Interfaces) <= *ip.Interface {
   147  			return result, errors.Errorf("invalid cni result, interface index %d out of range", *ip.Interface)
   148  		}
   149  		cniInt := cniResult.Interfaces[*ip.Interface]
   150  		netInt, ok := interfaces[cniInt.Name]
   151  		if ok {
   152  			netInt.Networks = append(netInt.Networks, types.NetAddress{
   153  				Subnet:  types.IPNet{IPNet: ip.Address},
   154  				Gateway: ip.Gateway,
   155  			})
   156  			interfaces[cniInt.Name] = netInt
   157  		} else {
   158  			mac, err := net.ParseMAC(cniInt.Mac)
   159  			if err != nil {
   160  				return result, err
   161  			}
   162  			interfaces[cniInt.Name] = types.NetInterface{
   163  				MacAddress: mac,
   164  				Networks: []types.NetAddress{{
   165  					Subnet:  types.IPNet{IPNet: ip.Address},
   166  					Gateway: ip.Gateway,
   167  				}},
   168  			}
   169  		}
   170  	}
   171  	result.Interfaces = interfaces
   172  	return result, nil
   173  }
   174  
   175  // validatePerNetworkOpts checks that all given static ips are in a subnet on this network
   176  func validatePerNetworkOpts(network *network, netOpts types.PerNetworkOptions) error {
   177  	if netOpts.InterfaceName == "" {
   178  		return errors.Errorf("interface name on network %s is empty", network.libpodNet.Name)
   179  	}
   180  outer:
   181  	for _, ip := range netOpts.StaticIPs {
   182  		for _, s := range network.libpodNet.Subnets {
   183  			if s.Subnet.Contains(ip) {
   184  				continue outer
   185  			}
   186  		}
   187  		return errors.Errorf("requested static ip %s not in any subnet on network %s", ip.String(), network.libpodNet.Name)
   188  	}
   189  	if len(netOpts.Aliases) > 0 && !network.libpodNet.DNSEnabled {
   190  		return errors.New("cannot set aliases on a network without dns enabled")
   191  	}
   192  	return nil
   193  }
   194  
   195  func getRuntimeConfig(netns, conName, conID, networkName string, ports []cniPortMapEntry, opts types.PerNetworkOptions) *libcni.RuntimeConf {
   196  	rt := &libcni.RuntimeConf{
   197  		ContainerID: conID,
   198  		NetNS:       netns,
   199  		IfName:      opts.InterfaceName,
   200  		Args: [][2]string{
   201  			{"IgnoreUnknown", "1"},
   202  			// Do not set the K8S env vars, see https://github.com/containers/podman/issues/12083.
   203  			// Only K8S_POD_NAME is used by dnsname to get the container name.
   204  			{"K8S_POD_NAME", conName},
   205  		},
   206  		CapabilityArgs: map[string]interface{}{},
   207  	}
   208  
   209  	// Propagate environment CNI_ARGS
   210  	for _, kvpairs := range strings.Split(os.Getenv("CNI_ARGS"), ";") {
   211  		if keyval := strings.SplitN(kvpairs, "=", 2); len(keyval) == 2 {
   212  			rt.Args = append(rt.Args, [2]string{keyval[0], keyval[1]})
   213  		}
   214  	}
   215  
   216  	// Add mac address to cni args
   217  	if len(opts.StaticMAC) > 0 {
   218  		rt.Args = append(rt.Args, [2]string{"MAC", opts.StaticMAC.String()})
   219  	}
   220  
   221  	if len(opts.StaticIPs) == 1 {
   222  		// Add a single IP to the args field. CNI plugins < 1.0.0
   223  		// do not support multiple ips via capability args.
   224  		rt.Args = append(rt.Args, [2]string{"IP", opts.StaticIPs[0].String()})
   225  	} else if len(opts.StaticIPs) > 1 {
   226  		// Set the static ips in the capability args
   227  		// to support more than one static ip per network.
   228  		rt.CapabilityArgs["ips"] = opts.StaticIPs
   229  	}
   230  
   231  	// Set network aliases for the dnsname plugin.
   232  	if len(opts.Aliases) > 0 {
   233  		rt.CapabilityArgs["aliases"] = map[string][]string{
   234  			networkName: opts.Aliases,
   235  		}
   236  	}
   237  
   238  	// Set PortMappings in Capabilities
   239  	if len(ports) > 0 {
   240  		rt.CapabilityArgs["portMappings"] = ports
   241  	}
   242  
   243  	return rt
   244  }
   245  
   246  // Teardown will teardown the container network namespace.
   247  func (n *cniNetwork) Teardown(namespacePath string, options types.TeardownOptions) error {
   248  	n.lock.Lock()
   249  	defer n.lock.Unlock()
   250  	err := n.loadNetworks()
   251  	if err != nil {
   252  		return err
   253  	}
   254  	return n.teardown(namespacePath, options)
   255  }
   256  
   257  func (n *cniNetwork) teardown(namespacePath string, options types.TeardownOptions) error {
   258  	// Note: An empty namespacePath is allowed because some plugins
   259  	// still need teardown, for example ipam should remove used ip allocations.
   260  
   261  	ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings)
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	var multiErr *multierror.Error
   267  	for name, netOpts := range options.Networks {
   268  		rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, netOpts)
   269  
   270  		cniConfList, newRt, err := getCachedNetworkConfig(n.cniConf, name, rt)
   271  		if err == nil {
   272  			rt = newRt
   273  		} else {
   274  			logrus.Warnf("failed to load cached network config: %v, falling back to loading network %s from disk", err, name)
   275  			network := n.networks[name]
   276  			if network == nil {
   277  				multiErr = multierror.Append(multiErr, errors.Wrapf(define.ErrNoSuchNetwork, "network %s", name))
   278  				continue
   279  			}
   280  			cniConfList = network.cniNet
   281  		}
   282  
   283  		err = n.cniConf.DelNetworkList(context.Background(), cniConfList, rt)
   284  		if err != nil {
   285  			multiErr = multierror.Append(multiErr, err)
   286  		}
   287  	}
   288  	return multiErr.ErrorOrNil()
   289  }
   290  
   291  func getCachedNetworkConfig(cniConf *libcni.CNIConfig, name string, rt *libcni.RuntimeConf) (*libcni.NetworkConfigList, *libcni.RuntimeConf, error) {
   292  	cniConfList := &libcni.NetworkConfigList{
   293  		Name: name,
   294  	}
   295  	confBytes, rt, err := cniConf.GetNetworkListCachedConfig(cniConfList, rt)
   296  	if err != nil {
   297  		return nil, nil, err
   298  	} else if confBytes == nil {
   299  		return nil, nil, errors.Errorf("network %s not found in CNI cache", name)
   300  	}
   301  
   302  	cniConfList, err = libcni.ConfListFromBytes(confBytes)
   303  	if err != nil {
   304  		return nil, nil, err
   305  	}
   306  	return cniConfList, rt, nil
   307  }