diff options
author | Paul Holzinger <pholzing@redhat.com> | 2021-08-16 16:11:26 +0200 |
---|---|---|
committer | Paul Holzinger <pholzing@redhat.com> | 2021-09-15 20:00:20 +0200 |
commit | 85e8fbf7f33717ef6a0d6cf9e2143b52c874c2de (patch) | |
tree | 82b0c29102d2779c18ea8a6f10df5dc1139e3817 /libpod/network | |
parent | 218f132fdf4939d9e0374ef860d534f19e71df54 (diff) | |
download | podman-85e8fbf7f33717ef6a0d6cf9e2143b52c874c2de.tar.gz podman-85e8fbf7f33717ef6a0d6cf9e2143b52c874c2de.tar.bz2 podman-85e8fbf7f33717ef6a0d6cf9e2143b52c874c2de.zip |
Wire network interface into libpod
Make use of the new network interface in libpod.
This commit contains several breaking changes:
- podman network create only outputs the new network name and not file
path.
- podman network ls shows the network driver instead of the cni version
and plugins.
- podman network inspect outputs the new network struct and not the cni
conflist.
- The bindings and libpod api endpoints have been changed to use the new
network structure.
The container network status is stored in a new field in the state. The
status should be received with the new `c.getNetworkStatus`. This will
migrate the old status to the new format. Therefore old containers should
contine to work correctly in all cases even when network connect/
disconnect is used.
New features:
- podman network reload keeps the ip and mac for more than one network.
- podman container restore keeps the ip and mac for more than one
network.
- The network create compat endpoint can now use more than one ipam
config.
The man pages and the swagger doc are updated to reflect the latest
changes.
Signed-off-by: Paul Holzinger <pholzing@redhat.com>
Diffstat (limited to 'libpod/network')
-rw-r--r-- | libpod/network/cni/run.go | 7 | ||||
-rw-r--r-- | libpod/network/config.go | 159 | ||||
-rw-r--r-- | libpod/network/create.go | 310 | ||||
-rw-r--r-- | libpod/network/create_test.go | 130 | ||||
-rw-r--r-- | libpod/network/devices.go | 59 | ||||
-rw-r--r-- | libpod/network/files.go | 211 | ||||
-rw-r--r-- | libpod/network/ip.go | 19 | ||||
-rw-r--r-- | libpod/network/lock.go | 35 | ||||
-rw-r--r-- | libpod/network/netconflist.go | 312 | ||||
-rw-r--r-- | libpod/network/netconflist_test.go | 109 | ||||
-rw-r--r-- | libpod/network/network.go | 288 | ||||
-rw-r--r-- | libpod/network/network_test.go | 35 | ||||
-rw-r--r-- | libpod/network/subnet.go | 78 | ||||
-rw-r--r-- | libpod/network/subnet_test.go | 97 | ||||
-rw-r--r-- | libpod/network/types/network.go | 9 | ||||
-rw-r--r-- | libpod/network/util/filters.go | 40 |
16 files changed, 42 insertions, 1856 deletions
diff --git a/libpod/network/cni/run.go b/libpod/network/cni/run.go index 14634262c..b69953c4b 100644 --- a/libpod/network/cni/run.go +++ b/libpod/network/cni/run.go @@ -114,7 +114,7 @@ func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (ma } logrus.Debugf("cni result for container %s network %s: %v", options.ContainerID, name, cnires) var status types.StatusBlock - status, retErr = cniResultToStatus(cnires) + status, retErr = CNIResultToStatus(cnires) if retErr != nil { return nil, retErr } @@ -123,8 +123,9 @@ func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (ma return results, nil } -// cniResultToStatus convert the cni result to status block -func cniResultToStatus(cniResult *current.Result) (types.StatusBlock, error) { +// CNIResultToStatus convert the cni result to status block +// nolint:golint +func CNIResultToStatus(cniResult *current.Result) (types.StatusBlock, error) { result := types.StatusBlock{} nameservers := make([]net.IP, 0, len(cniResult.DNS.Nameservers)) for _, nameserver := range cniResult.DNS.Nameservers { diff --git a/libpod/network/config.go b/libpod/network/config.go deleted file mode 100644 index 9a3bc4763..000000000 --- a/libpod/network/config.go +++ /dev/null @@ -1,159 +0,0 @@ -package network - -import ( - "encoding/json" - "net" - - "github.com/containers/storage/pkg/lockfile" -) - -// TODO once the containers.conf file stuff is worked out, this should be modified -// to honor defines in the containers.conf as well as overrides? - -const ( - // CNIConfigDir is the path where CNI config files exist - CNIConfigDir = "/etc/cni/net.d" - // CNIDeviceName is the default network device name and in - // reality should have an int appended to it (cni-podman4) - CNIDeviceName = "cni-podman" - // DefaultPodmanDomainName is used for the dnsname plugin to define - // a localized domain name for a created network - DefaultPodmanDomainName = "dns.podman" - // LockFileName is used for obtaining a lock and is appended - // to libpod's tmpdir in practice - LockFileName = "cni.lock" -) - -// CNILock is for preventing name collision and -// unpredictable results when doing some CNI operations. -type CNILock struct { - lockfile.Locker -} - -// GetDefaultPodmanNetwork outputs the default network for podman -func GetDefaultPodmanNetwork() (*net.IPNet, error) { - _, n, err := net.ParseCIDR("10.88.1.0/24") - return n, err -} - -// CNIPlugins is a way of marshalling a CNI network configuration to disk -type CNIPlugins interface { - Bytes() ([]byte, error) -} - -// HostLocalBridge describes a configuration for a bridge plugin -// https://github.com/containernetworking/plugins/tree/master/plugins/main/bridge#network-configuration-reference -type HostLocalBridge struct { - PluginType string `json:"type"` - BrName string `json:"bridge,omitempty"` - IsGW bool `json:"isGateway"` - IsDefaultGW bool `json:"isDefaultGateway,omitempty"` - ForceAddress bool `json:"forceAddress,omitempty"` - IPMasq bool `json:"ipMasq,omitempty"` - MTU int `json:"mtu,omitempty"` - HairpinMode bool `json:"hairpinMode,omitempty"` - PromiscMode bool `json:"promiscMode,omitempty"` - Vlan int `json:"vlan,omitempty"` - IPAM IPAMConfig `json:"ipam"` -} - -// Bytes outputs []byte -func (h *HostLocalBridge) Bytes() ([]byte, error) { - return json.MarshalIndent(h, "", "\t") -} - -// IPAMConfig describes an IPAM configuration -// https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#network-configuration-reference -type IPAMConfig struct { - PluginType string `json:"type"` - Routes []IPAMRoute `json:"routes,omitempty"` - ResolveConf string `json:"resolveConf,omitempty"` - DataDir string `json:"dataDir,omitempty"` - Ranges [][]IPAMLocalHostRangeConf `json:"ranges,omitempty"` -} - -// IPAMLocalHostRangeConf describes the new style IPAM ranges -type IPAMLocalHostRangeConf struct { - Subnet string `json:"subnet"` - RangeStart string `json:"rangeStart,omitempty"` - RangeEnd string `json:"rangeEnd,omitempty"` - Gateway string `json:"gateway,omitempty"` -} - -// Bytes outputs the configuration as []byte -func (i IPAMConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(i, "", "\t") -} - -// IPAMRoute describes a route in an ipam config -type IPAMRoute struct { - Dest string `json:"dst"` -} - -// PortMapConfig describes the default portmapping config -type PortMapConfig struct { - PluginType string `json:"type"` - Capabilities map[string]bool `json:"capabilities"` -} - -// Bytes outputs the configuration as []byte -func (p PortMapConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(p, "", "\t") -} - -// MacVLANConfig describes the macvlan config -type MacVLANConfig struct { - PluginType string `json:"type"` - Master string `json:"master"` - IPAM IPAMConfig `json:"ipam"` - MTU int `json:"mtu,omitempty"` -} - -// Bytes outputs the configuration as []byte -func (p MacVLANConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(p, "", "\t") -} - -// FirewallConfig describes the firewall plugin -type FirewallConfig struct { - PluginType string `json:"type"` - Backend string `json:"backend"` -} - -// Bytes outputs the configuration as []byte -func (f FirewallConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(f, "", "\t") -} - -// TuningConfig describes the tuning plugin -type TuningConfig struct { - PluginType string `json:"type"` -} - -// Bytes outputs the configuration as []byte -func (f TuningConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(f, "", "\t") -} - -// DNSNameConfig describes the dns container name resolution plugin config -type DNSNameConfig struct { - PluginType string `json:"type"` - DomainName string `json:"domainName"` - Capabilities map[string]bool `json:"capabilities"` -} - -// PodmanMachineConfig enables port handling on the host OS -type PodmanMachineConfig struct { - PluginType string `json:"type"` - Capabilities map[string]bool `json:"capabilities"` -} - -// Bytes outputs the configuration as []byte -func (d DNSNameConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(d, "", "\t") -} - -// Bytes outputs the configuration as []byte -func (p PodmanMachineConfig) Bytes() ([]byte, error) { - return json.MarshalIndent(p, "", "\t") -} diff --git a/libpod/network/create.go b/libpod/network/create.go deleted file mode 100644 index aca8150b5..000000000 --- a/libpod/network/create.go +++ /dev/null @@ -1,310 +0,0 @@ -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 -} diff --git a/libpod/network/create_test.go b/libpod/network/create_test.go deleted file mode 100644 index c3824bd91..000000000 --- a/libpod/network/create_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package network - -import ( - "net" - "testing" - - "github.com/containers/podman/v3/pkg/domain/entities" -) - -func Test_validateBridgeOptions(t *testing.T) { - tests := []struct { - name string - subnet net.IPNet - ipRange net.IPNet - gateway net.IP - isIPv6 bool - wantErr bool - }{ - { - name: "IPv4 subnet only", - subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, - }, - { - name: "IPv4 subnet and range", - subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, - ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, - }, - { - name: "IPv4 subnet and gateway", - subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, - gateway: net.ParseIP("192.168.0.10"), - }, - { - name: "IPv4 subnet, range and gateway", - subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, - ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, - gateway: net.ParseIP("192.168.0.10"), - }, - { - name: "IPv6 subnet only", - subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - }, - { - name: "IPv6 subnet and range", - subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, - isIPv6: true, - }, - { - name: "IPv6 subnet and gateway", - subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - gateway: net.ParseIP("2001:DB8::2"), - isIPv6: true, - }, - { - name: "IPv6 subnet, range and gateway", - subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, - gateway: net.ParseIP("2001:DB8::2"), - isIPv6: true, - }, - { - name: "IPv6 subnet, range and gateway without IPv6 option (PODMAN SUPPORTS IT UNLIKE DOCKER)", - subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, - gateway: net.ParseIP("2001:DB8::2"), - isIPv6: false, - }, - { - name: "range provided but not subnet", - ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, - wantErr: true, - }, - { - name: "gateway provided but not subnet", - gateway: net.ParseIP("192.168.0.10"), - wantErr: true, - }, - { - name: "IPv4 subnet but IPv6 required", - subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, - ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, - gateway: net.ParseIP("192.168.0.10"), - isIPv6: true, - wantErr: true, - }, - { - name: "IPv6 required but IPv4 options used", - subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, - ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, - gateway: net.ParseIP("192.168.0.10"), - isIPv6: true, - wantErr: true, - }, - { - name: "IPv6 required but not subnet provided", - isIPv6: true, - wantErr: true, - }, - { - name: "range out of the subnet", - subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - ipRange: net.IPNet{IP: net.ParseIP("2001:1:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, - gateway: net.ParseIP("2001:DB8::2"), - isIPv6: true, - wantErr: true, - }, - { - name: "gateway out of the subnet", - subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - gateway: net.ParseIP("2001::2"), - isIPv6: true, - wantErr: true, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - options := entities.NetworkCreateOptions{ - Subnet: tt.subnet, - Range: tt.ipRange, - Gateway: tt.gateway, - IPv6: tt.isIPv6, - } - if err := validateBridgeOptions(options); (err != nil) != tt.wantErr { - t.Errorf("validateBridgeOptions() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/libpod/network/devices.go b/libpod/network/devices.go deleted file mode 100644 index fc9aff337..000000000 --- a/libpod/network/devices.go +++ /dev/null @@ -1,59 +0,0 @@ -package network - -import ( - "fmt" - - "github.com/containers/common/pkg/config" - "github.com/containers/podman/v3/pkg/util" - "github.com/sirupsen/logrus" - "github.com/vishvananda/netlink" -) - -// GetFreeDeviceName returns a device name that is unused; used when no network -// name is provided by user -func GetFreeDeviceName(config *config.Config) (string, error) { - var ( - deviceNum uint - deviceName string - ) - networkNames, err := GetNetworkNamesFromFileSystem(config) - if err != nil { - return "", err - } - liveNetworksNames, err := GetLiveNetworkNames() - if err != nil { - return "", err - } - bridgeNames, err := GetBridgeNamesFromFileSystem(config) - if err != nil { - return "", err - } - for { - deviceName = fmt.Sprintf("%s%d", CNIDeviceName, deviceNum) - logrus.Debugf("checking if device name %q exists in other cni networks", deviceName) - if util.StringInSlice(deviceName, networkNames) { - deviceNum++ - continue - } - logrus.Debugf("checking if device name %q exists in live networks", deviceName) - if util.StringInSlice(deviceName, liveNetworksNames) { - deviceNum++ - continue - } - logrus.Debugf("checking if device name %q already exists as a bridge name ", deviceName) - if !util.StringInSlice(deviceName, bridgeNames) { - break - } - deviceNum++ - } - return deviceName, nil -} - -// RemoveInterface removes an interface by the given name -func RemoveInterface(interfaceName string) error { - link, err := netlink.LinkByName(interfaceName) - if err != nil { - return err - } - return netlink.LinkDel(link) -} diff --git a/libpod/network/files.go b/libpod/network/files.go deleted file mode 100644 index d876113f9..000000000 --- a/libpod/network/files.go +++ /dev/null @@ -1,211 +0,0 @@ -package network - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "sort" - "strings" - - "github.com/containernetworking/cni/libcni" - "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" - "github.com/containers/common/pkg/config" - "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/pkg/network" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// ErrNoSuchNetworkInterface indicates that no network interface exists -var ErrNoSuchNetworkInterface = errors.New("unable to find interface name for network") - -// GetCNIConfDir get CNI configuration directory -func GetCNIConfDir(configArg *config.Config) string { - if len(configArg.Network.NetworkConfigDir) < 1 { - dc, err := config.DefaultConfig() - if err != nil { - // Fallback to hard-coded dir - return CNIConfigDir - } - return dc.Network.NetworkConfigDir - } - return configArg.Network.NetworkConfigDir -} - -// LoadCNIConfsFromDir loads all the CNI configurations from a dir -func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) { - files, err := libcni.ConfFiles(dir, []string{".conflist"}) - if err != nil { - return nil, err - } - sort.Strings(files) - - configs := make([]*libcni.NetworkConfigList, 0, len(files)) - for _, confFile := range files { - conf, err := libcni.ConfListFromFile(confFile) - if err != nil { - return nil, errors.Wrapf(err, "in %s", confFile) - } - configs = append(configs, conf) - } - return configs, nil -} - -// GetCNIConfigPathByNameOrID finds a CNI network by name and -// returns its configuration file path -func GetCNIConfigPathByNameOrID(config *config.Config, name string) (string, error) { - files, err := libcni.ConfFiles(GetCNIConfDir(config), []string{".conflist"}) - if err != nil { - return "", err - } - idMatch := 0 - file := "" - for _, confFile := range files { - conf, err := libcni.ConfListFromFile(confFile) - if err != nil { - return "", errors.Wrapf(err, "in %s", confFile) - } - if conf.Name == name { - return confFile, nil - } - if strings.HasPrefix(network.GetNetworkID(conf.Name), name) { - idMatch++ - file = confFile - } - } - if idMatch == 1 { - return file, nil - } - if idMatch > 1 { - return "", errors.Errorf("more than one result for network ID %s", name) - } - return "", errors.Wrap(define.ErrNoSuchNetwork, fmt.Sprintf("unable to find network configuration for %s", name)) -} - -// ReadRawCNIConfByNameOrID reads the raw CNI configuration for a CNI -// network by name -func ReadRawCNIConfByNameOrID(config *config.Config, name string) ([]byte, error) { - confFile, err := GetCNIConfigPathByNameOrID(config, name) - if err != nil { - return nil, err - } - b, err := ioutil.ReadFile(confFile) - return b, err -} - -// GetNetworkLabels returns a list of labels as a string -func GetNetworkLabels(list *libcni.NetworkConfigList) NcLabels { - cniJSON := make(map[string]interface{}) - err := json.Unmarshal(list.Bytes, &cniJSON) - if err != nil { - logrus.Errorf("failed to unmarshal network config %v %v", cniJSON["name"], err) - return nil - } - if args, ok := cniJSON["args"]; ok { - if key, ok := args.(map[string]interface{}); ok { - if labels, ok := key[PodmanLabelKey]; ok { - if labels, ok := labels.(map[string]interface{}); ok { - result := make(NcLabels, len(labels)) - for k, v := range labels { - if v, ok := v.(string); ok { - result[k] = v - } else { - logrus.Errorf("network config %v invalid label value type %T should be string", cniJSON["name"], labels) - } - } - return result - } - logrus.Errorf("network config %v invalid label type %T should be map[string]string", cniJSON["name"], labels) - } - } - } - return nil -} - -// GetNetworksFromFilesystem gets all the networks from the cni configuration -// files -func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) { - var cniNetworks []*allocator.Net - - networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config)) - if err != nil { - return nil, err - } - for _, n := range networks { - for _, cniplugin := range n.Plugins { - if cniplugin.Network.Type == "bridge" { - ipamConf := allocator.Net{} - if err := json.Unmarshal(cniplugin.Bytes, &ipamConf); err != nil { - return nil, err - } - cniNetworks = append(cniNetworks, &ipamConf) - break - } - } - } - return cniNetworks, nil -} - -// GetNetworkNamesFromFileSystem gets all the names from the cni network -// configuration files -func GetNetworkNamesFromFileSystem(config *config.Config) ([]string, error) { - networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config)) - if err != nil { - return nil, err - } - networkNames := []string{} - for _, n := range networks { - networkNames = append(networkNames, n.Name) - } - return networkNames, nil -} - -// GetInterfaceNameFromConfig returns the interface name for the bridge plugin -func GetInterfaceNameFromConfig(path string) (string, error) { - var name string - conf, err := libcni.ConfListFromFile(path) - if err != nil { - return "", err - } - for _, cniplugin := range conf.Plugins { - if cniplugin.Network.Type == "bridge" { - plugin := make(map[string]interface{}) - if err := json.Unmarshal(cniplugin.Bytes, &plugin); err != nil { - return "", err - } - name = plugin["bridge"].(string) - break - } - } - if len(name) == 0 { - return "", ErrNoSuchNetworkInterface - } - return name, nil -} - -// GetBridgeNamesFromFileSystem is a convenience function to get all the bridge -// names from the configured networks -func GetBridgeNamesFromFileSystem(config *config.Config) ([]string, error) { - networks, err := LoadCNIConfsFromDir(GetCNIConfDir(config)) - if err != nil { - return nil, err - } - - bridgeNames := []string{} - for _, n := range networks { - var name string - // iterate network conflists - for _, cniplugin := range n.Plugins { - // iterate plugins - if cniplugin.Network.Type == "bridge" { - plugin := make(map[string]interface{}) - if err := json.Unmarshal(cniplugin.Bytes, &plugin); err != nil { - continue - } - name = plugin["bridge"].(string) - } - } - bridgeNames = append(bridgeNames, name) - } - return bridgeNames, nil -} diff --git a/libpod/network/ip.go b/libpod/network/ip.go deleted file mode 100644 index ba93a0d05..000000000 --- a/libpod/network/ip.go +++ /dev/null @@ -1,19 +0,0 @@ -package network - -import ( - "net" - - "github.com/containernetworking/plugins/pkg/ip" -) - -// CalcGatewayIP takes a network and returns the first IP in it. -func CalcGatewayIP(ipn *net.IPNet) net.IP { - // taken from cni bridge plugin as it is not exported - nid := ipn.IP.Mask(ipn.Mask) - return ip.NextIP(nid) -} - -// IsIPv6 returns if netIP is IPv6. -func IsIPv6(netIP net.IP) bool { - return netIP != nil && netIP.To4() == nil -} diff --git a/libpod/network/lock.go b/libpod/network/lock.go deleted file mode 100644 index 037f41efa..000000000 --- a/libpod/network/lock.go +++ /dev/null @@ -1,35 +0,0 @@ -package network - -import ( - "os" - "path/filepath" - - "github.com/containers/common/pkg/config" - "github.com/containers/storage" -) - -// acquireCNILock gets a lock that should be used in create and -// delete cases to avoid unwanted collisions in network names. -// TODO this uses a file lock and should be converted to shared memory -// when we have a more general shared memory lock in libpod -func acquireCNILock(config *config.Config) (*CNILock, error) { - cniDir := GetCNIConfDir(config) - err := os.MkdirAll(cniDir, 0755) - if err != nil { - return nil, err - } - l, err := storage.GetLockfile(filepath.Join(cniDir, LockFileName)) - if err != nil { - return nil, err - } - l.Lock() - cnilock := CNILock{ - Locker: l, - } - return &cnilock, nil -} - -// ReleaseCNILock unlocks the previously held lock -func (l *CNILock) releaseCNILock() { - l.Unlock() -} diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go deleted file mode 100644 index d6c33740e..000000000 --- a/libpod/network/netconflist.go +++ /dev/null @@ -1,312 +0,0 @@ -package network - -import ( - "net" - "os" - "path/filepath" - "strings" - "syscall" - "time" - - "github.com/containernetworking/cni/libcni" - "github.com/containers/common/pkg/config" - "github.com/containers/podman/v3/pkg/network" - "github.com/containers/podman/v3/pkg/util" - "github.com/pkg/errors" -) - -const ( - defaultIPv4Route = "0.0.0.0/0" - defaultIPv6Route = "::/0" -) - -// NcList describes a generic map -type NcList map[string]interface{} - -// NcArgs describes the cni args field -type NcArgs map[string]NcLabels - -// NcLabels describes the label map -type NcLabels map[string]string - -// PodmanLabelKey key used to store the podman network label in a cni config -const PodmanLabelKey = "podman_labels" - -// NewNcList creates a generic map of values with string -// keys and adds in version and network name -func NewNcList(name, version string, labels NcLabels) NcList { - n := NcList{} - n["cniVersion"] = version - n["name"] = name - if len(labels) > 0 { - n["args"] = NcArgs{PodmanLabelKey: labels} - } - return n -} - -// NewHostLocalBridge creates a new LocalBridge for host-local -func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, mtu int, vlan int, ipamConf IPAMConfig) *HostLocalBridge { - hostLocalBridge := HostLocalBridge{ - PluginType: "bridge", - BrName: name, - IPMasq: ipMasq, - MTU: mtu, - HairpinMode: true, - Vlan: vlan, - IPAM: ipamConf, - } - if isGateWay { - hostLocalBridge.IsGW = true - } - if isDefaultGW { - hostLocalBridge.IsDefaultGW = true - } - return &hostLocalBridge -} - -// NewIPAMHostLocalConf creates a new IPAMHostLocal configuration -func NewIPAMHostLocalConf(routes []IPAMRoute, ipamRanges [][]IPAMLocalHostRangeConf) (IPAMConfig, error) { - ipamConf := IPAMConfig{ - PluginType: "host-local", - Routes: routes, - // Possible future support ? Leaving for clues - //ResolveConf: "", - //DataDir: "" - } - - ipamConf.Ranges = ipamRanges - return ipamConf, nil -} - -// NewIPAMLocalHostRange create a new IPAM range -func NewIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw net.IP) ([]IPAMLocalHostRangeConf, error) { //nolint:interfacer - var ranges []IPAMLocalHostRangeConf - hostRange := IPAMLocalHostRangeConf{ - Subnet: subnet.String(), - } - // an user provided a range, we add it here - if ipRange != nil && ipRange.IP != nil { - first, err := FirstIPInSubnet(ipRange) - if err != nil { - return nil, err - } - last, err := LastIPInSubnet(ipRange) - if err != nil { - return nil, err - } - hostRange.RangeStart = first.String() - hostRange.RangeEnd = last.String() - } - if gw != nil { - hostRange.Gateway = gw.String() - } else { - // Add first ip in subnet as gateway. It is not required - // by cni but should be included because of network inspect. - hostRange.Gateway = CalcGatewayIP(subnet).String() - } - ranges = append(ranges, hostRange) - return ranges, nil -} - -// NewIPAMRoute creates a new IPAM route configuration -func NewIPAMRoute(r *net.IPNet) IPAMRoute { //nolint:interfacer - return IPAMRoute{Dest: r.String()} -} - -// NewIPAMDefaultRoute creates a new IPAMDefault route of -// 0.0.0.0/0 for IPv4 or ::/0 for IPv6 -func NewIPAMDefaultRoute(isIPv6 bool) (IPAMRoute, error) { - route := defaultIPv4Route - if isIPv6 { - route = defaultIPv6Route - } - _, n, err := net.ParseCIDR(route) - if err != nil { - return IPAMRoute{}, err - } - return NewIPAMRoute(n), nil -} - -// NewPortMapPlugin creates a predefined, default portmapping -// configuration -func NewPortMapPlugin() PortMapConfig { - caps := make(map[string]bool) - caps["portMappings"] = true - p := PortMapConfig{ - PluginType: "portmap", - Capabilities: caps, - } - return p -} - -// NewFirewallPlugin creates a generic firewall plugin -func NewFirewallPlugin() FirewallConfig { - return FirewallConfig{ - PluginType: "firewall", - } -} - -// NewTuningPlugin creates a generic tuning section -func NewTuningPlugin() TuningConfig { - return TuningConfig{ - PluginType: "tuning", - } -} - -// NewDNSNamePlugin creates the dnsname config with a given -// domainname -func NewDNSNamePlugin(domainName string) DNSNameConfig { - caps := make(map[string]bool, 1) - caps["aliases"] = true - return DNSNameConfig{ - PluginType: "dnsname", - DomainName: domainName, - Capabilities: caps, - } -} - -// HasDNSNamePlugin looks to see if the dnsname cni plugin is present -func HasDNSNamePlugin(paths []string) bool { - for _, p := range paths { - if _, err := os.Stat(filepath.Join(p, "dnsname")); err == nil { - return true - } - } - return false -} - -// NewMacVLANPlugin creates a macvlanconfig with a given device name -func NewMacVLANPlugin(device string, gateway net.IP, ipRange *net.IPNet, subnet *net.IPNet, mtu int) (MacVLANConfig, error) { - i := IPAMConfig{PluginType: "dhcp"} - if gateway != nil || - (ipRange != nil && ipRange.IP != nil && ipRange.Mask != nil) || - (subnet != nil && subnet.IP != nil && subnet.Mask != nil) { - ipam, err := NewIPAMLocalHostRange(subnet, ipRange, gateway) - if err != nil { - return MacVLANConfig{}, err - } - ranges := make([][]IPAMLocalHostRangeConf, 0) - ranges = append(ranges, ipam) - i.Ranges = ranges - route, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP)) - if err != nil { - return MacVLANConfig{}, err - } - i.Routes = []IPAMRoute{route} - i.PluginType = "host-local" - } - - m := MacVLANConfig{ - PluginType: "macvlan", - IPAM: i, - } - if mtu > 0 { - m.MTU = mtu - } - // CNI is supposed to use the default route if a - // parent device is not provided - if len(device) > 0 { - m.Master = device - } - return m, nil -} - -// IfPassesFilter filters NetworkListReport and returns true if the filter match the given config -func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]string) (bool, error) { - result := true - for key, filterValues := range filters { - result = false - switch strings.ToLower(key) { - case "name": - // matches one name, regex allowed - result = util.StringMatchRegexSlice(netconf.Name, filterValues) - - case "plugin": - // match one plugin - plugins := network.GetCNIPlugins(netconf) - for _, val := range filterValues { - if strings.Contains(plugins, val) { - result = true - break - } - } - - case "label": - // matches all labels - result = util.MatchLabelFilters(filterValues, GetNetworkLabels(netconf)) - - case "driver": - // matches only for the DefaultNetworkDriver - for _, filterValue := range filterValues { - plugins := network.GetCNIPlugins(netconf) - if filterValue == DefaultNetworkDriver && - strings.Contains(plugins, DefaultNetworkDriver) { - result = true - } - } - - case "id": - // matches part of one id - for _, filterValue := range filterValues { - if strings.Contains(network.GetNetworkID(netconf.Name), filterValue) { - result = true - break - } - } - - // TODO: add dangling filter - - default: - return false, errors.Errorf("invalid filter %q", key) - } - } - return result, nil -} - -// IfPassesPruneFilter filters NetworkListReport and returns true if the prune filter match the given config -func IfPassesPruneFilter(config *config.Config, netconf *libcni.NetworkConfigList, f map[string][]string) (bool, error) { - for key, filterValues := range f { - switch strings.ToLower(key) { - case "label": - return util.MatchLabelFilters(filterValues, GetNetworkLabels(netconf)), nil - case "until": - until, err := util.ComputeUntilTimestamp(filterValues) - if err != nil { - return false, err - } - created, err := getCreatedTimestamp(config, netconf) - if err != nil { - return false, err - } - if created.Before(until) { - return true, nil - } - default: - return false, errors.Errorf("invalid filter %q", key) - } - } - return false, nil -} - -func getCreatedTimestamp(config *config.Config, netconf *libcni.NetworkConfigList) (*time.Time, error) { - networkConfigPath, err := GetCNIConfigPathByNameOrID(config, netconf.Name) - if err != nil { - return nil, err - } - f, err := os.Stat(networkConfigPath) - if err != nil { - return nil, err - } - stat := f.Sys().(*syscall.Stat_t) - created := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) // nolint: unconvert - return &created, nil -} - -func NewPodmanMachinePlugin() PodmanMachineConfig { - caps := make(map[string]bool, 1) - caps["portMappings"] = true - return PodmanMachineConfig{ - PluginType: "podman-machine", - Capabilities: caps, - } -} diff --git a/libpod/network/netconflist_test.go b/libpod/network/netconflist_test.go deleted file mode 100644 index 161764ed9..000000000 --- a/libpod/network/netconflist_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package network - -import ( - "net" - "reflect" - "testing" -) - -func TestNewIPAMDefaultRoute(t *testing.T) { - tests := []struct { - name string - isIPv6 bool - want IPAMRoute - }{ - { - name: "IPv4 default route", - isIPv6: false, - want: IPAMRoute{defaultIPv4Route}, - }, - { - name: "IPv6 default route", - isIPv6: true, - want: IPAMRoute{defaultIPv6Route}, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - got, err := NewIPAMDefaultRoute(tt.isIPv6) - if err != nil { - t.Errorf("no error expected: %v", err) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewIPAMDefaultRoute() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestNewIPAMLocalHostRange(t *testing.T) { - tests := []struct { - name string - subnet *net.IPNet - ipRange *net.IPNet - gw net.IP - want []IPAMLocalHostRangeConf - }{ - { - name: "IPv4 subnet", - subnet: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, - want: []IPAMLocalHostRangeConf{ - { - Subnet: "192.168.0.0/24", - Gateway: "192.168.0.1", - }, - }, - }, - { - name: "IPv4 subnet, range and gateway", - subnet: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, - ipRange: &net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, - gw: net.ParseIP("192.168.0.10"), - want: []IPAMLocalHostRangeConf{ - { - Subnet: "192.168.0.0/24", - RangeStart: "192.168.0.129", - RangeEnd: "192.168.0.255", - Gateway: "192.168.0.10", - }, - }, - }, - { - name: "IPv6 subnet", - subnet: &net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - want: []IPAMLocalHostRangeConf{ - { - Subnet: "2001:db8::/48", - Gateway: "2001:db8::1", - }, - }, - }, - { - name: "IPv6 subnet, range and gateway", - subnet: &net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, - ipRange: &net.IPNet{IP: net.ParseIP("2001:DB8:1:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, - gw: net.ParseIP("2001:DB8::2"), - want: []IPAMLocalHostRangeConf{ - { - Subnet: "2001:db8::/48", - RangeStart: "2001:db8:1:1::1", - RangeEnd: "2001:db8:1:1:ffff:ffff:ffff:ffff", - Gateway: "2001:db8::2", - }, - }, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - got, err := NewIPAMLocalHostRange(tt.subnet, tt.ipRange, tt.gw) - if err != nil { - t.Errorf("no error expected: %v", err) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewIPAMLocalHostRange() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/libpod/network/network.go b/libpod/network/network.go deleted file mode 100644 index 3b81ce776..000000000 --- a/libpod/network/network.go +++ /dev/null @@ -1,288 +0,0 @@ -package network - -import ( - "encoding/json" - "net" - "os" - - "github.com/containernetworking/cni/libcni" - "github.com/containernetworking/cni/pkg/types" - "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" - "github.com/containers/common/pkg/config" - "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/rootless" - "github.com/containers/podman/v3/pkg/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -var ( - // BridgeNetworkDriver defines the bridge cni driver - BridgeNetworkDriver = "bridge" - // DefaultNetworkDriver is the default network type used - DefaultNetworkDriver = BridgeNetworkDriver - // MacVLANNetworkDriver defines the macvlan cni driver - MacVLANNetworkDriver = "macvlan" -) - -// SupportedNetworkDrivers describes the list of supported drivers -var SupportedNetworkDrivers = []string{BridgeNetworkDriver, MacVLANNetworkDriver} - -// isSupportedDriver checks if the user provided driver is supported -func isSupportedDriver(driver string) error { - if util.StringInSlice(driver, SupportedNetworkDrivers) { - return nil - } - return errors.Errorf("driver '%s' is not supported", driver) -} - -// GetLiveNetworks returns a slice of networks representing what the system -// has defined as network interfaces -func GetLiveNetworks() ([]*net.IPNet, error) { - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil, err - } - nets := make([]*net.IPNet, 0, len(addrs)) - for _, address := range addrs { - _, n, err := net.ParseCIDR(address.String()) - if err != nil { - return nil, err - } - nets = append(nets, n) - } - return nets, nil -} - -// GetLiveNetworkNames returns a list of network interfaces on the system -func GetLiveNetworkNames() ([]string, error) { - liveInterfaces, err := net.Interfaces() - if err != nil { - return nil, err - } - interfaceNames := make([]string, 0, len(liveInterfaces)) - for _, i := range liveInterfaces { - interfaceNames = append(interfaceNames, i.Name) - } - return interfaceNames, nil -} - -// GetFreeNetwork looks for a free network according to existing cni configuration -// files and network interfaces. -func GetFreeNetwork(config *config.Config) (*net.IPNet, error) { - networks, err := GetNetworksFromFilesystem(config) - if err != nil { - return nil, err - } - liveNetworks, err := GetLiveNetworks() - if err != nil { - return nil, err - } - nextNetwork, err := GetDefaultPodmanNetwork() - if err != nil { - return nil, err - } - logrus.Debugf("default network is %s", nextNetwork.String()) - for { - newNetwork, err := NextSubnet(nextNetwork) - if err != nil { - return nil, err - } - logrus.Debugf("checking if network %s intersects with other cni networks", nextNetwork.String()) - if intersectsConfig, _ := networkIntersectsWithNetworks(newNetwork, allocatorToIPNets(networks)); intersectsConfig { - logrus.Debugf("network %s is already being used by a cni configuration", nextNetwork.String()) - nextNetwork = newNetwork - continue - } - logrus.Debugf("checking if network %s intersects with any network interfaces", nextNetwork.String()) - if intersectsLive, _ := networkIntersectsWithNetworks(newNetwork, liveNetworks); !intersectsLive { - break - } - logrus.Debugf("network %s is being used by a network interface", nextNetwork.String()) - nextNetwork = newNetwork - } - return nextNetwork, nil -} - -func allocatorToIPNets(networks []*allocator.Net) []*net.IPNet { - var nets []*net.IPNet - for _, network := range networks { - if len(network.IPAM.Ranges) > 0 { - // this is the new IPAM range style - // append each subnet from ipam the rangeset - for _, allocatorRange := range network.IPAM.Ranges { - for _, r := range allocatorRange { - nets = append(nets, newIPNetFromSubnet(r.Subnet)) - } - } - } else { - // looks like the old, deprecated style - nets = append(nets, newIPNetFromSubnet(network.IPAM.Subnet)) - } - } - return nets -} - -func newIPNetFromSubnet(subnet types.IPNet) *net.IPNet { - n := net.IPNet{ - IP: subnet.IP, - Mask: subnet.Mask, - } - return &n -} - -func networkIntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) (bool, *net.IPNet) { - for _, nw := range networklist { - if networkIntersect(n, nw) { - return true, nw - } - } - return false, nil -} - -func networkIntersect(n1, n2 *net.IPNet) bool { - return n2.Contains(n1.IP) || n1.Contains(n2.IP) -} - -// ValidateUserNetworkIsAvailable returns via an error if a network is available -// to be used -func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) error { - if len(userNet.IP) == 0 || len(userNet.Mask) == 0 { - return errors.Errorf("network %s's ip or mask cannot be empty", userNet.String()) - } - - ones, bit := userNet.Mask.Size() - if ones == 0 || bit == 0 { - return errors.Errorf("network %s's mask is invalid", userNet.String()) - } - - networks, err := GetNetworksFromFilesystem(config) - if err != nil { - return err - } - liveNetworks, err := GetLiveNetworks() - if err != nil { - return err - } - logrus.Debugf("checking if network %s exists in cni networks", userNet.String()) - if intersectsConfig, _ := networkIntersectsWithNetworks(userNet, allocatorToIPNets(networks)); intersectsConfig { - return errors.Errorf("network %s is already being used by a cni configuration", userNet.String()) - } - logrus.Debugf("checking if network %s exists in any network interfaces", userNet.String()) - if intersectsLive, _ := networkIntersectsWithNetworks(userNet, liveNetworks); intersectsLive { - return errors.Errorf("network %s is being used by a network interface", userNet.String()) - } - return nil -} - -// removeNetwork is removes a cni network without a lock and should only be called -// when a lock was otherwise acquired. -func removeNetwork(config *config.Config, name string) error { - cniPath, err := GetCNIConfigPathByNameOrID(config, name) - if err != nil { - return err - } - // Before we delete the configuration file, we need to make sure we can read and parse - // it to get the network interface name so we can remove that too - interfaceName, err := GetInterfaceNameFromConfig(cniPath) - if err == nil { - // Don't try to remove the network interface if we are not root - if !rootless.IsRootless() { - liveNetworkNames, err := GetLiveNetworkNames() - if err != nil { - return errors.Wrapf(err, "failed to get live network names") - } - if util.StringInSlice(interfaceName, liveNetworkNames) { - if err = RemoveInterface(interfaceName); err != nil { - // only log the error, it is not fatal - logrus.Infof("failed to remove network interface %s: %v", interfaceName, err) - } - } - } - } else if err != ErrNoSuchNetworkInterface { - // Don't error if we couldn't find the network interface name - return err - } - // Remove the configuration file - if err := os.Remove(cniPath); err != nil { - return errors.Wrap(err, "failed to remove network configuration") - } - return nil -} - -// RemoveNetwork removes a given network by name. If the network has container associated with it, that -// must be handled outside the context of this. -func RemoveNetwork(config *config.Config, name string) error { - l, err := acquireCNILock(config) - if err != nil { - return err - } - defer l.releaseCNILock() - return removeNetwork(config, name) -} - -// InspectNetwork reads a CNI config and returns its configuration -func InspectNetwork(config *config.Config, name string) (map[string]interface{}, error) { - b, err := ReadRawCNIConfByNameOrID(config, name) - if err != nil { - return nil, err - } - rawList := make(map[string]interface{}) - err = json.Unmarshal(b, &rawList) - return rawList, err -} - -// Exists says whether a given network exists or not; it meant -// specifically for restful responses so 404s can be used -func Exists(config *config.Config, name string) (bool, error) { - _, err := ReadRawCNIConfByNameOrID(config, name) - if err != nil { - if errors.Cause(err) == define.ErrNoSuchNetwork { - return false, nil - } - return false, err - } - return true, nil -} - -// PruneNetworks removes networks that are not being used and that is not the default -// network. To keep proper fencing for imports, you must provide the used networks -// to this function as a map. the key is meaningful in the map, the book is a no-op -func PruneNetworks(rtc *config.Config, usedNetworks map[string]bool) ([]*entities.NetworkPruneReport, error) { - var reports []*entities.NetworkPruneReport - lock, err := acquireCNILock(rtc) - if err != nil { - return nil, err - } - defer lock.releaseCNILock() - nets, err := GetNetworkNamesFromFileSystem(rtc) - if err != nil { - return nil, err - } - for _, n := range nets { - _, found := usedNetworks[n] - // Remove is not default network and not found in the used list - if n != rtc.Network.DefaultNetwork && !found { - reports = append(reports, &entities.NetworkPruneReport{ - Name: n, - Error: removeNetwork(rtc, n), - }) - } - } - return reports, nil -} - -// NormalizeName translates a network ID into a name. -// If the input is a name the name is returned. -func NormalizeName(config *config.Config, nameOrID string) (string, error) { - path, err := GetCNIConfigPathByNameOrID(config, nameOrID) - if err != nil { - return "", err - } - conf, err := libcni.ConfListFromFile(path) - if err != nil { - return "", err - } - return conf.Name, nil -} diff --git a/libpod/network/network_test.go b/libpod/network/network_test.go deleted file mode 100644 index 1969e792c..000000000 --- a/libpod/network/network_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package network - -import ( - "net" - "testing" -) - -func parseCIDR(n string) *net.IPNet { - _, parsedNet, _ := net.ParseCIDR(n) - return parsedNet -} - -func Test_networkIntersect(t *testing.T) { - type args struct { - n1 *net.IPNet - n2 *net.IPNet - } - tests := []struct { - name string - args args - want bool - }{ - {"16 and 24 intersects", args{n1: parseCIDR("192.168.0.0/16"), n2: parseCIDR("192.168.1.0/24")}, true}, - {"24 and 25 intersects", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.1.0/25")}, true}, - {"Two 24s", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.2.0/24")}, false}, - } - for _, tt := range tests { - test := tt - t.Run(tt.name, func(t *testing.T) { - if got := networkIntersect(test.args.n1, test.args.n2); got != test.want { - t.Errorf("networkIntersect() = %v, want %v", got, test.want) - } - }) - } -} diff --git a/libpod/network/subnet.go b/libpod/network/subnet.go deleted file mode 100644 index 120038e57..000000000 --- a/libpod/network/subnet.go +++ /dev/null @@ -1,78 +0,0 @@ -package network - -/* - The code in this was kindly contributed by Dan Williams(dcbw@redhat.com). Many thanks - for his contributions. -*/ - -import ( - "fmt" - "net" -) - -func incByte(subnet *net.IPNet, idx int, shift uint) error { - if idx < 0 { - return fmt.Errorf("no more subnets left") - } - if subnet.IP[idx] == 255 { - subnet.IP[idx] = 0 - return incByte(subnet, idx-1, 0) - } - subnet.IP[idx] += 1 << shift - return nil -} - -// NextSubnet returns subnet incremented by 1 -func NextSubnet(subnet *net.IPNet) (*net.IPNet, error) { - newSubnet := &net.IPNet{ - IP: subnet.IP, - Mask: subnet.Mask, - } - ones, bits := newSubnet.Mask.Size() - if ones == 0 { - return nil, fmt.Errorf("%s has only one subnet", subnet.String()) - } - zeroes := uint(bits - ones) - shift := zeroes % 8 - idx := ones/8 - 1 - if idx < 0 { - idx = 0 - } - if err := incByte(newSubnet, idx, shift); err != nil { - return nil, err - } - return newSubnet, nil -} - -// LastIPInSubnet gets the last IP in a subnet -func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer - // re-parse to ensure clean network address - _, cidr, err := net.ParseCIDR(addr.String()) - if err != nil { - return nil, err - } - - ones, bits := cidr.Mask.Size() - if ones == bits { - return cidr.IP, nil - } - for i := range cidr.IP { - cidr.IP[i] = cidr.IP[i] | ^cidr.Mask[i] - } - return cidr.IP, nil -} - -// FirstIPInSubnet gets the first IP in a subnet -func FirstIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer - // re-parse to ensure clean network address - _, cidr, err := net.ParseCIDR(addr.String()) - if err != nil { - return nil, err - } - ones, bits := cidr.Mask.Size() - if ones == bits { - return cidr.IP, nil - } - cidr.IP[len(cidr.IP)-1]++ - return cidr.IP, nil -} diff --git a/libpod/network/subnet_test.go b/libpod/network/subnet_test.go deleted file mode 100644 index 55b2443bd..000000000 --- a/libpod/network/subnet_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package network - -import ( - "net" - "reflect" - "testing" -) - -func TestNextSubnet(t *testing.T) { - type args struct { - subnet *net.IPNet - } - tests := []struct { - name string - args args - want *net.IPNet - wantErr bool - }{ - {"class b", args{subnet: parseCIDR("192.168.0.0/16")}, parseCIDR("192.169.0.0/16"), false}, - {"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false}, - } - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - got, err := NextSubnet(test.args.subnet) - if (err != nil) != test.wantErr { - t.Errorf("NextSubnet() error = %v, wantErr %v", err, test.wantErr) - return - } - if !reflect.DeepEqual(got, test.want) { - t.Errorf("NextSubnet() got = %v, want %v", got, test.want) - } - }) - } -} - -func TestFirstIPInSubnet(t *testing.T) { - tests := []struct { - name string - args *net.IPNet - want net.IP - wantErr bool - }{ - {"class b", parseCIDR("192.168.0.0/16"), net.ParseIP("192.168.0.1"), false}, - {"class c", parseCIDR("192.168.1.0/24"), net.ParseIP("192.168.1.1"), false}, - {"cidr /23", parseCIDR("192.168.0.0/23"), net.ParseIP("192.168.0.1"), false}, - {"cidr /25", parseCIDR("192.168.1.0/25"), net.ParseIP("192.168.1.1"), false}, - {"cidr /26", parseCIDR("172.16.1.128/26"), net.ParseIP("172.16.1.129"), false}, - {"class a", parseCIDR("10.0.0.0/8"), net.ParseIP("10.0.0.1"), false}, - {"cidr /32", parseCIDR("192.168.255.4/32"), net.ParseIP("192.168.255.4"), false}, - {"cidr /31", parseCIDR("192.168.255.4/31"), net.ParseIP("192.168.255.5"), false}, - } - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - got, err := FirstIPInSubnet(test.args) - if (err != nil) != test.wantErr { - t.Errorf("FirstIPInSubnet() error = %v, wantErr %v", err, test.wantErr) - return - } - if !got.Equal(test.want) { - t.Errorf("FirstIPInSubnet() got = %v, want %v", got, test.want) - } - }) - } -} - -func TestLastIPInSubnet(t *testing.T) { - tests := []struct { - name string - args *net.IPNet - want net.IP - wantErr bool - }{ - {"class b", parseCIDR("192.168.0.0/16"), net.ParseIP("192.168.255.255"), false}, - {"class c", parseCIDR("192.168.1.0/24"), net.ParseIP("192.168.1.255"), false}, - {"cidr /23", parseCIDR("192.168.0.0/23"), net.ParseIP("192.168.1.255"), false}, - {"cidr /25", parseCIDR("192.168.1.0/25"), net.ParseIP("192.168.1.127"), false}, - {"cidr /26", parseCIDR("172.16.1.128/26"), net.ParseIP("172.16.1.191"), false}, - {"class a", parseCIDR("10.0.0.0/8"), net.ParseIP("10.255.255.255"), false}, - {"cidr /32", parseCIDR("192.168.255.4/32"), net.ParseIP("192.168.255.4"), false}, - {"cidr /31", parseCIDR("192.168.255.4/31"), net.ParseIP("192.168.255.5"), false}, - } - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - got, err := LastIPInSubnet(test.args) - if (err != nil) != test.wantErr { - t.Errorf("LastIPInSubnet() error = %v, wantErr %v", err, test.wantErr) - return - } - if !got.Equal(test.want) { - t.Errorf("LastIPInSubnet() got = %v, want %v", got, test.want) - } - }) - } -} diff --git a/libpod/network/types/network.go b/libpod/network/types/network.go index c2c598f46..56bde716e 100644 --- a/libpod/network/types/network.go +++ b/libpod/network/types/network.go @@ -36,8 +36,7 @@ type Network struct { // InterfaceName is the network interface name on the host. NetworkInterface string `json:"network_interface,omitempty"` // Created contains the timestamp when this network was created. - // This is not guaranteed to stay exactly the same. - Created time.Time + Created time.Time `json:"created,omitempty"` // Subnets to use. Subnets []Subnet `json:"subnets,omitempty"` // IPv6Enabled if set to true an ipv6 subnet should be created for this net. @@ -92,9 +91,11 @@ func (n *IPNet) UnmarshalText(text []byte) error { } type Subnet struct { - // Subnet for this Network. + // Subnet for this Network in CIDR form. + // swagger:strfmt string Subnet IPNet `json:"subnet,omitempty"` // Gateway IP for this Network. + // swagger:strfmt string Gateway net.IP `json:"gateway,omitempty"` // LeaseRange contains the range where IP are leased. Optional. LeaseRange *LeaseRange `json:"lease_range,omitempty"` @@ -103,8 +104,10 @@ type Subnet struct { // LeaseRange contains the range where IP are leased. type LeaseRange struct { // StartIP first IP in the subnet which should be used to assign ips. + // swagger:strfmt string StartIP net.IP `json:"start_ip,omitempty"` // EndIP last IP in the subnet which should be used to assign ips. + // swagger:strfmt string EndIP net.IP `json:"end_ip,omitempty"` } diff --git a/libpod/network/util/filters.go b/libpod/network/util/filters.go index 48e769196..c3c80b352 100644 --- a/libpod/network/util/filters.go +++ b/libpod/network/util/filters.go @@ -28,12 +28,6 @@ func createFilterFuncs(key string, filterValues []string) (types.FilterFunc, err return util.StringMatchRegexSlice(net.Name, filterValues) }, nil - case "label": - // matches all labels - return func(net types.Network) bool { - return util.MatchLabelFilters(filterValues, net.Labels) - }, nil - case "driver": // matches network driver return func(net types.Network) bool { @@ -46,9 +40,39 @@ func createFilterFuncs(key string, filterValues []string) (types.FilterFunc, err return util.StringMatchRegexSlice(net.ID, filterValues) }, nil - // FIXME: What should we do with the old plugin filter - // TODO: add dangling, dns enabled, internal filter + // TODO: add dns enabled, internal filter + } + return createPruneFilterFuncs(key, filterValues) +} + +func GenerateNetworkPruneFilters(filters map[string][]string) ([]types.FilterFunc, error) { + filterFuncs := make([]types.FilterFunc, 0, len(filters)) + for key, filterValues := range filters { + filterFunc, err := createPruneFilterFuncs(key, filterValues) + if err != nil { + return nil, err + } + filterFuncs = append(filterFuncs, filterFunc) + } + return filterFuncs, nil +} + +func createPruneFilterFuncs(key string, filterValues []string) (types.FilterFunc, error) { + switch strings.ToLower(key) { + case "label": + // matches all labels + return func(net types.Network) bool { + return util.MatchLabelFilters(filterValues, net.Labels) + }, nil + case "until": + until, err := util.ComputeUntilTimestamp(filterValues) + if err != nil { + return nil, err + } + return func(net types.Network) bool { + return net.Created.Before(until) + }, nil default: return nil, errors.Errorf("invalid filter %q", key) } |