Files
podman/libpod/network/create.go
Brent Baude d21c1aafe2 Detect if in podman machine virtual vm
When in podman machine virtual machines, podman needs to be able to
detect as such.  One implementation for this is when creating networks,
the podman-machine cni plugin needs to be added to the configuration.

This PR also includes the latest containers-common.

[NO TESTS NEEDED]

Signed-off-by: Brent Baude <bbaude@redhat.com>
2021-04-30 13:11:05 -05:00

311 lines
9.0 KiB
Go

package network
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/containernetworking/cni/pkg/version"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Create the CNI network
func Create(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (*entities.NetworkCreateReport, error) {
var fileName string
if err := isSupportedDriver(options.Driver); err != nil {
return nil, err
}
// Acquire a lock for CNI
l, err := acquireCNILock(runtimeConfig)
if err != nil {
return nil, err
}
defer l.releaseCNILock()
if len(options.MacVLAN) > 0 || options.Driver == MacVLANNetworkDriver {
fileName, err = createMacVLAN(name, options, runtimeConfig)
} else {
fileName, err = createBridge(name, options, runtimeConfig)
}
if err != nil {
return nil, err
}
return &entities.NetworkCreateReport{Filename: fileName}, nil
}
// validateBridgeOptions validate the bridge networking options
func validateBridgeOptions(options entities.NetworkCreateOptions) error {
subnet := &options.Subnet
ipRange := &options.Range
gateway := options.Gateway
// if IPv6 is set an IPv6 subnet MUST be specified
if options.IPv6 && ((subnet.IP == nil) || (subnet.IP != nil && !IsIPv6(subnet.IP))) {
return errors.Errorf("ipv6 option requires an IPv6 --subnet to be provided")
}
// range and gateway depend on subnet
if subnet.IP == nil && (ipRange.IP != nil || gateway != nil) {
return errors.Errorf("every ip-range or gateway must have a corresponding subnet")
}
// if a range is given, we need to ensure it is "in" the network range.
if ipRange.IP != nil {
firstIP, err := FirstIPInSubnet(ipRange)
if err != nil {
return errors.Wrapf(err, "failed to get first IP address from ip-range")
}
lastIP, err := LastIPInSubnet(ipRange)
if err != nil {
return errors.Wrapf(err, "failed to get last IP address from ip-range")
}
if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) {
return errors.Errorf("the ip range %s does not fall within the subnet range %s", ipRange.String(), subnet.String())
}
}
// if network is provided and if gateway is provided, make sure it is "in" network
if gateway != nil && !subnet.Contains(gateway) {
return errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String())
}
return nil
}
// parseMTU parses the mtu option
func parseMTU(mtu string) (int, error) {
if mtu == "" {
return 0, nil // default
}
m, err := strconv.Atoi(mtu)
if err != nil {
return 0, err
}
if m < 0 {
return 0, errors.Errorf("the value %d for mtu is less than zero", m)
}
return m, nil
}
// parseVlan parses the vlan option
func parseVlan(vlan string) (int, error) {
if vlan == "" {
return 0, nil // default
}
return strconv.Atoi(vlan)
}
// createBridge creates a CNI network
func createBridge(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) {
var (
ipamRanges [][]IPAMLocalHostRangeConf
err error
routes []IPAMRoute
)
isGateway := true
ipMasq := true
// validate options
if err := validateBridgeOptions(options); err != nil {
return "", err
}
// For compatibility with the docker implementation:
// 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
// if IPv6 is not specified the subnet may be specified and can be either IPv4 or IPv6 (podman, unlike docker, allows IPv6 only networks)
// If not subnet is specified an IPv4 subnet will be allocated
subnet := &options.Subnet
ipRange := &options.Range
gateway := options.Gateway
if subnet.IP != nil {
// if network is provided, does it conflict with existing CNI or live networks
err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
if err != nil {
return "", err
}
// obtain CNI subnet default route
defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP))
if err != nil {
return "", err
}
routes = append(routes, defaultRoute)
// obtain CNI range
ipamRange, err := NewIPAMLocalHostRange(subnet, ipRange, gateway)
if err != nil {
return "", err
}
ipamRanges = append(ipamRanges, ipamRange)
}
// if no network is provided or IPv6 flag used, figure out the IPv4 network
if options.IPv6 || len(routes) == 0 {
subnetV4, err := GetFreeNetwork(runtimeConfig)
if err != nil {
return "", err
}
// obtain IPv4 default route
defaultRoute, err := NewIPAMDefaultRoute(false)
if err != nil {
return "", err
}
routes = append(routes, defaultRoute)
// the CNI bridge plugin does not need to set
// the range or gateway options explicitly
ipamRange, err := NewIPAMLocalHostRange(subnetV4, nil, nil)
if err != nil {
return "", err
}
ipamRanges = append(ipamRanges, ipamRange)
}
// create CNI config
ipamConfig, err := NewIPAMHostLocalConf(routes, ipamRanges)
if err != nil {
return "", err
}
if options.Internal {
isGateway = false
ipMasq = false
}
var mtu int
var vlan int
for k, v := range options.Options {
var err error
switch k {
case "mtu":
mtu, err = parseMTU(v)
if err != nil {
return "", err
}
case "vlan":
vlan, err = parseVlan(v)
if err != nil {
return "", err
}
default:
return "", errors.Errorf("unsupported option %s", k)
}
}
// obtain host bridge name
bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig)
if err != nil {
return "", err
}
if len(name) > 0 {
netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig)
if err != nil {
return "", err
}
if util.StringInSlice(name, netNames) {
return "", errors.Errorf("the network name %s is already used", name)
}
} else {
// If no name is given, we give the name of the bridge device
name = bridgeDeviceName
}
// create CNI plugin configuration
ncList := NewNcList(name, version.Current(), options.Labels)
var plugins []CNIPlugins
// TODO need to iron out the role of isDefaultGW and IPMasq
bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, mtu, vlan, ipamConfig)
plugins = append(plugins, bridge)
plugins = append(plugins, NewPortMapPlugin())
plugins = append(plugins, NewFirewallPlugin())
plugins = append(plugins, NewTuningPlugin())
// if we find the dnsname plugin we add configuration for it
if HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !options.DisableDNS {
if options.Internal {
logrus.Warnf("dnsname and --internal networks are incompatible. dnsname plugin not configured for network %s", name)
} else {
// Note: in the future we might like to allow for dynamic domain names
plugins = append(plugins, NewDNSNamePlugin(DefaultPodmanDomainName))
}
}
// Add the podman-machine CNI plugin if we are in a machine
if runtimeConfig.MachineEnabled() { // check if we are in a machine vm
plugins = append(plugins, NewPodmanMachinePlugin())
}
ncList["plugins"] = plugins
b, err := json.MarshalIndent(ncList, "", " ")
if err != nil {
return "", err
}
if err := os.MkdirAll(GetCNIConfDir(runtimeConfig), 0755); err != nil {
return "", err
}
cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
err = ioutil.WriteFile(cniPathName, b, 0644)
return cniPathName, err
}
func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) {
var (
mtu int
plugins []CNIPlugins
)
liveNetNames, err := GetLiveNetworkNames()
if err != nil {
return "", err
}
// The parent can be defined with --macvlan or as an option (-o parent:device)
parentNetworkDevice := options.MacVLAN
if len(parentNetworkDevice) < 1 {
if parent, ok := options.Options["parent"]; ok {
parentNetworkDevice = parent
}
}
// Make sure the host-device exists if provided
if len(parentNetworkDevice) > 0 && !util.StringInSlice(parentNetworkDevice, liveNetNames) {
return "", errors.Errorf("failed to find network interface %q", parentNetworkDevice)
}
if len(name) > 0 {
netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig)
if err != nil {
return "", err
}
if util.StringInSlice(name, netNames) {
return "", errors.Errorf("the network name %s is already used", name)
}
} else {
name, err = GetFreeDeviceName(runtimeConfig)
if err != nil {
return "", err
}
}
ncList := NewNcList(name, version.Current(), options.Labels)
if val, ok := options.Options["mtu"]; ok {
intVal, err := strconv.Atoi(val)
if err != nil {
return "", err
}
if intVal > 0 {
mtu = intVal
}
}
macvlan, err := NewMacVLANPlugin(parentNetworkDevice, options.Gateway, &options.Range, &options.Subnet, mtu)
if err != nil {
return "", err
}
plugins = append(plugins, macvlan)
ncList["plugins"] = plugins
b, err := json.MarshalIndent(ncList, "", " ")
if err != nil {
return "", err
}
cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
err = ioutil.WriteFile(cniPathName, b, 0644)
return cniPathName, err
}