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

     1  // +build linux
     2  
     3  package cni
     4  
     5  import (
     6  	"encoding/json"
     7  	"io/ioutil"
     8  	"net"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/containernetworking/cni/libcni"
    17  	"github.com/containernetworking/cni/pkg/version"
    18  	"github.com/containers/podman/v3/libpod/network/types"
    19  	"github.com/containers/podman/v3/libpod/network/util"
    20  	pkgutil "github.com/containers/podman/v3/pkg/util"
    21  	"github.com/pkg/errors"
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  func createNetworkFromCNIConfigList(conf *libcni.NetworkConfigList, confPath string) (*types.Network, error) {
    26  	network := types.Network{
    27  		Name:        conf.Name,
    28  		ID:          getNetworkIDFromName(conf.Name),
    29  		Labels:      map[string]string{},
    30  		Options:     map[string]string{},
    31  		IPAMOptions: map[string]string{},
    32  	}
    33  
    34  	cniJSON := make(map[string]interface{})
    35  	err := json.Unmarshal(conf.Bytes, &cniJSON)
    36  	if err != nil {
    37  		return nil, errors.Wrapf(err, "failed to unmarshal network config %s", conf.Name)
    38  	}
    39  	if args, ok := cniJSON["args"]; ok {
    40  		if key, ok := args.(map[string]interface{}); ok {
    41  			// read network labels and options from the conf file
    42  			network.Labels = getNetworkArgsFromConfList(key, podmanLabelKey)
    43  			network.Options = getNetworkArgsFromConfList(key, podmanOptionsKey)
    44  		}
    45  	}
    46  
    47  	f, err := os.Stat(confPath)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	stat := f.Sys().(*syscall.Stat_t)
    52  	network.Created = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
    53  
    54  	firstPlugin := conf.Plugins[0]
    55  	network.Driver = firstPlugin.Network.Type
    56  
    57  	switch firstPlugin.Network.Type {
    58  	case types.BridgeNetworkDriver:
    59  		var bridge hostLocalBridge
    60  		err := json.Unmarshal(firstPlugin.Bytes, &bridge)
    61  		if err != nil {
    62  			return nil, errors.Wrapf(err, "failed to unmarshal the bridge plugin config in %s", confPath)
    63  		}
    64  		network.NetworkInterface = bridge.BrName
    65  
    66  		// if isGateway is false we have an internal network
    67  		if !bridge.IsGW {
    68  			network.Internal = true
    69  		}
    70  
    71  		// set network options
    72  		if bridge.MTU != 0 {
    73  			network.Options["mtu"] = strconv.Itoa(bridge.MTU)
    74  		}
    75  		if bridge.Vlan != 0 {
    76  			network.Options["vlan"] = strconv.Itoa(bridge.Vlan)
    77  		}
    78  
    79  		err = convertIPAMConfToNetwork(&network, bridge.IPAM, confPath)
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  
    84  	case types.MacVLANNetworkDriver:
    85  		var macvlan macVLANConfig
    86  		err := json.Unmarshal(firstPlugin.Bytes, &macvlan)
    87  		if err != nil {
    88  			return nil, errors.Wrapf(err, "failed to unmarshal the macvlan plugin config in %s", confPath)
    89  		}
    90  		network.NetworkInterface = macvlan.Master
    91  
    92  		// set network options
    93  		if macvlan.MTU != 0 {
    94  			network.Options["mtu"] = strconv.Itoa(macvlan.MTU)
    95  		}
    96  
    97  		err = convertIPAMConfToNetwork(&network, macvlan.IPAM, confPath)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  
   102  	default:
   103  		// A warning would be good but users would get this warning everytime so keep this at info level.
   104  		logrus.Infof("unsupported CNI config type %s in %s, this network can still be used but inspect or list cannot show all information",
   105  			firstPlugin.Network.Type, confPath)
   106  	}
   107  
   108  	// check if the dnsname plugin is configured
   109  	network.DNSEnabled = findPluginByName(conf.Plugins, "dnsname")
   110  
   111  	return &network, nil
   112  }
   113  
   114  func findPluginByName(plugins []*libcni.NetworkConfig, name string) bool {
   115  	for _, plugin := range plugins {
   116  		if plugin.Network.Type == name {
   117  			return true
   118  		}
   119  	}
   120  	return false
   121  }
   122  
   123  // convertIPAMConfToNetwork converts A cni IPAMConfig to libpod network subnets.
   124  // It returns an array of subnets and an extra bool if dhcp is configured.
   125  func convertIPAMConfToNetwork(network *types.Network, ipam ipamConfig, confPath string) error {
   126  	if ipam.PluginType == types.DHCPIPAMDriver {
   127  		network.IPAMOptions["driver"] = types.DHCPIPAMDriver
   128  		return nil
   129  	}
   130  
   131  	if ipam.PluginType != types.HostLocalIPAMDriver {
   132  		return errors.Errorf("unsupported ipam plugin %s in %s", ipam.PluginType, confPath)
   133  	}
   134  
   135  	network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
   136  	for _, r := range ipam.Ranges {
   137  		for _, ipam := range r {
   138  			s := types.Subnet{}
   139  
   140  			// Do not use types.ParseCIDR() because we want the ip to be
   141  			// the network address and not a random ip in the sub.
   142  			_, sub, err := net.ParseCIDR(ipam.Subnet)
   143  			if err != nil {
   144  				return err
   145  			}
   146  			s.Subnet = types.IPNet{IPNet: *sub}
   147  
   148  			// gateway
   149  			var gateway net.IP
   150  			if ipam.Gateway != "" {
   151  				gateway = net.ParseIP(ipam.Gateway)
   152  				if gateway == nil {
   153  					return errors.Errorf("failed to parse gateway ip %s", ipam.Gateway)
   154  				}
   155  				// convert to 4 byte if ipv4
   156  				ipv4 := gateway.To4()
   157  				if ipv4 != nil {
   158  					gateway = ipv4
   159  				}
   160  			} else if !network.Internal {
   161  				// only add a gateway address if the network is not internal
   162  				gateway, err = util.FirstIPInSubnet(sub)
   163  				if err != nil {
   164  					return errors.Errorf("failed to get first ip in subnet %s", sub.String())
   165  				}
   166  			}
   167  			s.Gateway = gateway
   168  
   169  			var rangeStart net.IP
   170  			var rangeEnd net.IP
   171  			if ipam.RangeStart != "" {
   172  				rangeStart = net.ParseIP(ipam.RangeStart)
   173  				if rangeStart == nil {
   174  					return errors.Errorf("failed to parse range start ip %s", ipam.RangeStart)
   175  				}
   176  			}
   177  			if ipam.RangeEnd != "" {
   178  				rangeEnd = net.ParseIP(ipam.RangeEnd)
   179  				if rangeEnd == nil {
   180  					return errors.Errorf("failed to parse range end ip %s", ipam.RangeEnd)
   181  				}
   182  			}
   183  			if rangeStart != nil || rangeEnd != nil {
   184  				s.LeaseRange = &types.LeaseRange{}
   185  				s.LeaseRange.StartIP = rangeStart
   186  				s.LeaseRange.EndIP = rangeEnd
   187  			}
   188  			network.Subnets = append(network.Subnets, s)
   189  		}
   190  	}
   191  	return nil
   192  }
   193  
   194  // getNetworkArgsFromConfList returns the map of args in a conflist, argType should be labels or options
   195  func getNetworkArgsFromConfList(args map[string]interface{}, argType string) map[string]string {
   196  	if args, ok := args[argType]; ok {
   197  		if labels, ok := args.(map[string]interface{}); ok {
   198  			result := make(map[string]string, len(labels))
   199  			for k, v := range labels {
   200  				if v, ok := v.(string); ok {
   201  					result[k] = v
   202  				}
   203  			}
   204  			return result
   205  		}
   206  	}
   207  	return nil
   208  }
   209  
   210  // createCNIConfigListFromNetwork will create a cni config file from the given network.
   211  // It returns the cni config and the path to the file where the config was written.
   212  // Set writeToDisk to false to only add this network into memory.
   213  func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writeToDisk bool) (*libcni.NetworkConfigList, string, error) {
   214  	var (
   215  		routes     []ipamRoute
   216  		ipamRanges [][]ipamLocalHostRangeConf
   217  		ipamConf   ipamConfig
   218  		err        error
   219  	)
   220  	if len(network.Subnets) > 0 {
   221  		for _, subnet := range network.Subnets {
   222  			route, err := newIPAMDefaultRoute(util.IsIPv6(subnet.Subnet.IP))
   223  			if err != nil {
   224  				return nil, "", err
   225  			}
   226  			routes = append(routes, route)
   227  			ipam := newIPAMLocalHostRange(subnet.Subnet, subnet.LeaseRange, subnet.Gateway)
   228  			ipamRanges = append(ipamRanges, []ipamLocalHostRangeConf{*ipam})
   229  		}
   230  		ipamConf = newIPAMHostLocalConf(routes, ipamRanges)
   231  	} else {
   232  		ipamConf = ipamConfig{PluginType: "dhcp"}
   233  	}
   234  
   235  	vlan := 0
   236  	mtu := 0
   237  	for k, v := range network.Options {
   238  		switch k {
   239  		case "mtu":
   240  			mtu, err = parseMTU(v)
   241  			if err != nil {
   242  				return nil, "", err
   243  			}
   244  
   245  		case "vlan":
   246  			vlan, err = parseVlan(v)
   247  			if err != nil {
   248  				return nil, "", err
   249  			}
   250  
   251  		default:
   252  			return nil, "", errors.Errorf("unsupported network option %s", k)
   253  		}
   254  	}
   255  
   256  	isGateway := true
   257  	ipMasq := true
   258  	if network.Internal {
   259  		isGateway = false
   260  		ipMasq = false
   261  	}
   262  	// create CNI plugin configuration
   263  	ncList := newNcList(network.Name, version.Current(), network.Labels, network.Options)
   264  	var plugins []interface{}
   265  
   266  	switch network.Driver {
   267  	case types.BridgeNetworkDriver:
   268  		bridge := newHostLocalBridge(network.NetworkInterface, isGateway, ipMasq, mtu, vlan, ipamConf)
   269  		plugins = append(plugins, bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin())
   270  		// if we find the dnsname plugin we add configuration for it
   271  		if hasDNSNamePlugin(n.cniPluginDirs) && network.DNSEnabled {
   272  			// Note: in the future we might like to allow for dynamic domain names
   273  			plugins = append(plugins, newDNSNamePlugin(defaultPodmanDomainName))
   274  		}
   275  		// Add the podman-machine CNI plugin if we are in a machine
   276  		if n.isMachine {
   277  			plugins = append(plugins, newPodmanMachinePlugin())
   278  		}
   279  
   280  	case types.MacVLANNetworkDriver:
   281  		plugins = append(plugins, newMacVLANPlugin(network.NetworkInterface, mtu, ipamConf))
   282  
   283  	default:
   284  		return nil, "", errors.Errorf("driver %q is not supported by cni", network.Driver)
   285  	}
   286  	ncList["plugins"] = plugins
   287  	b, err := json.MarshalIndent(ncList, "", "   ")
   288  	if err != nil {
   289  		return nil, "", err
   290  	}
   291  	cniPathName := ""
   292  	if writeToDisk {
   293  		cniPathName = filepath.Join(n.cniConfigDir, network.Name+".conflist")
   294  		err = ioutil.WriteFile(cniPathName, b, 0644)
   295  		if err != nil {
   296  			return nil, "", err
   297  		}
   298  		f, err := os.Stat(cniPathName)
   299  		if err != nil {
   300  			return nil, "", err
   301  		}
   302  		stat := f.Sys().(*syscall.Stat_t)
   303  		network.Created = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
   304  	} else {
   305  		network.Created = time.Now()
   306  	}
   307  	config, err := libcni.ConfListFromBytes(b)
   308  	if err != nil {
   309  		return nil, "", err
   310  	}
   311  	return config, cniPathName, nil
   312  }
   313  
   314  // parseMTU parses the mtu option
   315  func parseMTU(mtu string) (int, error) {
   316  	if mtu == "" {
   317  		return 0, nil // default
   318  	}
   319  	m, err := strconv.Atoi(mtu)
   320  	if err != nil {
   321  		return 0, err
   322  	}
   323  	if m < 0 {
   324  		return 0, errors.Errorf("mtu %d is less than zero", m)
   325  	}
   326  	return m, nil
   327  }
   328  
   329  // parseVlan parses the vlan option
   330  func parseVlan(vlan string) (int, error) {
   331  	if vlan == "" {
   332  		return 0, nil // default
   333  	}
   334  	v, err := strconv.Atoi(vlan)
   335  	if err != nil {
   336  		return 0, err
   337  	}
   338  	if v < 0 || v > 4094 {
   339  		return 0, errors.Errorf("vlan ID %d must be between 0 and 4094", v)
   340  	}
   341  	return v, nil
   342  }
   343  
   344  func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry, error) {
   345  	cniPorts := make([]cniPortMapEntry, 0, len(ports))
   346  	for _, port := range ports {
   347  		if port.Protocol == "" {
   348  			return nil, errors.New("port protocol should not be empty")
   349  		}
   350  		protocols := strings.Split(port.Protocol, ",")
   351  
   352  		for _, protocol := range protocols {
   353  			if !pkgutil.StringInSlice(protocol, []string{"tcp", "udp", "sctp"}) {
   354  				return nil, errors.Errorf("unknown port protocol %s", protocol)
   355  			}
   356  			cniPort := cniPortMapEntry{
   357  				HostPort:      int(port.HostPort),
   358  				ContainerPort: int(port.ContainerPort),
   359  				HostIP:        port.HostIP,
   360  				Protocol:      protocol,
   361  			}
   362  			cniPorts = append(cniPorts, cniPort)
   363  			for i := 1; i < int(port.Range); i++ {
   364  				cniPort := cniPortMapEntry{
   365  					HostPort:      int(port.HostPort) + i,
   366  					ContainerPort: int(port.ContainerPort) + i,
   367  					HostIP:        port.HostIP,
   368  					Protocol:      protocol,
   369  				}
   370  				cniPorts = append(cniPorts, cniPort)
   371  			}
   372  		}
   373  	}
   374  	return cniPorts, nil
   375  }