github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/libpod/network/create.go (about)

     1  package network
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/containernetworking/cni/pkg/version"
    11  	"github.com/containers/common/pkg/config"
    12  	"github.com/containers/podman/v2/pkg/domain/entities"
    13  	"github.com/containers/podman/v2/pkg/rootless"
    14  	"github.com/containers/podman/v2/pkg/util"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // Create the CNI network
    19  func Create(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (*entities.NetworkCreateReport, error) {
    20  	var fileName string
    21  	if err := isSupportedDriver(options.Driver); err != nil {
    22  		return nil, err
    23  	}
    24  	// Acquire a lock for CNI
    25  	l, err := acquireCNILock(filepath.Join(runtimeConfig.Engine.TmpDir, LockFileName))
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  	defer l.releaseCNILock()
    30  	if len(options.MacVLAN) > 0 {
    31  		fileName, err = createMacVLAN(name, options, runtimeConfig)
    32  	} else {
    33  		fileName, err = createBridge(name, options, runtimeConfig)
    34  	}
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	return &entities.NetworkCreateReport{Filename: fileName}, nil
    39  }
    40  
    41  // validateBridgeOptions validate the bridge networking options
    42  func validateBridgeOptions(options entities.NetworkCreateOptions) error {
    43  	subnet := &options.Subnet
    44  	ipRange := &options.Range
    45  	gateway := options.Gateway
    46  	// if IPv6 is set an IPv6 subnet MUST be specified
    47  	if options.IPv6 && ((subnet.IP == nil) || (subnet.IP != nil && !IsIPv6(subnet.IP))) {
    48  		return errors.Errorf("ipv6 option requires an IPv6 --subnet to be provided")
    49  	}
    50  	// range and gateway depend on subnet
    51  	if subnet.IP == nil && (ipRange.IP != nil || gateway != nil) {
    52  		return errors.Errorf("every ip-range or gateway must have a corresponding subnet")
    53  	}
    54  
    55  	// if a range is given, we need to ensure it is "in" the network range.
    56  	if ipRange.IP != nil {
    57  		firstIP, err := FirstIPInSubnet(ipRange)
    58  		if err != nil {
    59  			return errors.Wrapf(err, "failed to get first IP address from ip-range")
    60  		}
    61  		lastIP, err := LastIPInSubnet(ipRange)
    62  		if err != nil {
    63  			return errors.Wrapf(err, "failed to get last IP address from ip-range")
    64  		}
    65  		if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
    66  			return errors.Errorf("the ip range %s does not fall within the subnet range %s", ipRange.String(), subnet.String())
    67  		}
    68  	}
    69  
    70  	// if network is provided and if gateway is provided, make sure it is "in" network
    71  	if gateway != nil && !subnet.Contains(gateway) {
    72  		return errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
    73  	}
    74  
    75  	return nil
    76  
    77  }
    78  
    79  // createBridge creates a CNI network
    80  func createBridge(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) {
    81  	var (
    82  		ipamRanges [][]IPAMLocalHostRangeConf
    83  		err        error
    84  		routes     []IPAMRoute
    85  	)
    86  	isGateway := true
    87  	ipMasq := true
    88  
    89  	// validate options
    90  	if err := validateBridgeOptions(options); err != nil {
    91  		return "", err
    92  	}
    93  
    94  	// For compatibility with the docker implementation:
    95  	// if IPv6 is enabled (it really means dual-stack) then an IPv6 subnet has to be provided, and one free network is allocated for IPv4
    96  	// if IPv6 is not specified the subnet may be specified and can be either IPv4 or IPv6 (podman, unlike docker, allows IPv6 only networks)
    97  	// If not subnet is specified an IPv4 subnet will be allocated
    98  	subnet := &options.Subnet
    99  	ipRange := &options.Range
   100  	gateway := options.Gateway
   101  	if subnet.IP != nil {
   102  		// if network is provided, does it conflict with existing CNI or live networks
   103  		err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
   104  		if err != nil {
   105  			return "", err
   106  		}
   107  		// obtain CNI subnet default route
   108  		defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP))
   109  		if err != nil {
   110  			return "", err
   111  		}
   112  		routes = append(routes, defaultRoute)
   113  		// obtain CNI range
   114  		ipamRange, err := NewIPAMLocalHostRange(subnet, ipRange, gateway)
   115  		if err != nil {
   116  			return "", err
   117  		}
   118  		ipamRanges = append(ipamRanges, ipamRange)
   119  	}
   120  	// if no network is provided or IPv6 flag used, figure out the IPv4 network
   121  	if options.IPv6 || len(routes) == 0 {
   122  		subnetV4, err := GetFreeNetwork(runtimeConfig)
   123  		if err != nil {
   124  			return "", err
   125  		}
   126  		// obtain IPv4 default route
   127  		defaultRoute, err := NewIPAMDefaultRoute(false)
   128  		if err != nil {
   129  			return "", err
   130  		}
   131  		routes = append(routes, defaultRoute)
   132  		// the CNI bridge plugin does not need to set
   133  		// the range or gateway options explicitly
   134  		ipamRange, err := NewIPAMLocalHostRange(subnetV4, nil, nil)
   135  		if err != nil {
   136  			return "", err
   137  		}
   138  		ipamRanges = append(ipamRanges, ipamRange)
   139  	}
   140  
   141  	// create CNI config
   142  	ipamConfig, err := NewIPAMHostLocalConf(routes, ipamRanges)
   143  	if err != nil {
   144  		return "", err
   145  	}
   146  
   147  	if options.Internal {
   148  		isGateway = false
   149  		ipMasq = false
   150  	}
   151  
   152  	// obtain host bridge name
   153  	bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig)
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  
   158  	if len(name) > 0 {
   159  		netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig)
   160  		if err != nil {
   161  			return "", err
   162  		}
   163  		if util.StringInSlice(name, netNames) {
   164  			return "", errors.Errorf("the network name %s is already used", name)
   165  		}
   166  	} else {
   167  		// If no name is given, we give the name of the bridge device
   168  		name = bridgeDeviceName
   169  	}
   170  
   171  	// create CNI plugin configuration
   172  	ncList := NewNcList(name, version.Current())
   173  	var plugins []CNIPlugins
   174  	// TODO need to iron out the role of isDefaultGW and IPMasq
   175  	bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig)
   176  	plugins = append(plugins, bridge)
   177  	plugins = append(plugins, NewPortMapPlugin())
   178  	plugins = append(plugins, NewFirewallPlugin())
   179  	plugins = append(plugins, NewTuningPlugin())
   180  	// if we find the dnsname plugin or are rootless, we add configuration for it
   181  	// the rootless-cni-infra container has the dnsname plugin always installed
   182  	if (HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) || rootless.IsRootless()) && !options.DisableDNS {
   183  		// Note: in the future we might like to allow for dynamic domain names
   184  		plugins = append(plugins, NewDNSNamePlugin(DefaultPodmanDomainName))
   185  	}
   186  	ncList["plugins"] = plugins
   187  	b, err := json.MarshalIndent(ncList, "", "   ")
   188  	if err != nil {
   189  		return "", err
   190  	}
   191  	if err := os.MkdirAll(GetCNIConfDir(runtimeConfig), 0755); err != nil {
   192  		return "", err
   193  	}
   194  	cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
   195  	err = ioutil.WriteFile(cniPathName, b, 0644)
   196  	return cniPathName, err
   197  }
   198  
   199  func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) {
   200  	var (
   201  		plugins []CNIPlugins
   202  	)
   203  	liveNetNames, err := GetLiveNetworkNames()
   204  	if err != nil {
   205  		return "", err
   206  	}
   207  
   208  	// Make sure the host-device exists
   209  	if !util.StringInSlice(options.MacVLAN, liveNetNames) {
   210  		return "", errors.Errorf("failed to find network interface %q", options.MacVLAN)
   211  	}
   212  	if len(name) > 0 {
   213  		netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig)
   214  		if err != nil {
   215  			return "", err
   216  		}
   217  		if util.StringInSlice(name, netNames) {
   218  			return "", errors.Errorf("the network name %s is already used", name)
   219  		}
   220  	} else {
   221  		name, err = GetFreeDeviceName(runtimeConfig)
   222  		if err != nil {
   223  			return "", err
   224  		}
   225  	}
   226  	ncList := NewNcList(name, version.Current())
   227  	macvlan := NewMacVLANPlugin(options.MacVLAN)
   228  	plugins = append(plugins, macvlan)
   229  	ncList["plugins"] = plugins
   230  	b, err := json.MarshalIndent(ncList, "", "   ")
   231  	if err != nil {
   232  		return "", err
   233  	}
   234  	cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
   235  	err = ioutil.WriteFile(cniPathName, b, 0644)
   236  	return cniPathName, err
   237  }