summaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
Diffstat (limited to 'vendor')
-rw-r--r--vendor/github.com/containers/common/libnetwork/cni/README.md10
-rw-r--r--vendor/github.com/containers/common/libnetwork/cni/cni_conversion.go378
-rw-r--r--vendor/github.com/containers/common/libnetwork/cni/cni_exec.go110
-rw-r--r--vendor/github.com/containers/common/libnetwork/cni/cni_types.go281
-rw-r--r--vendor/github.com/containers/common/libnetwork/cni/config.go208
-rw-r--r--vendor/github.com/containers/common/libnetwork/cni/network.go275
-rw-r--r--vendor/github.com/containers/common/libnetwork/cni/run.go274
-rw-r--r--vendor/github.com/containers/common/libnetwork/internal/util/bridge.go68
-rw-r--r--vendor/github.com/containers/common/libnetwork/internal/util/create.go41
-rw-r--r--vendor/github.com/containers/common/libnetwork/internal/util/interface.go19
-rw-r--r--vendor/github.com/containers/common/libnetwork/internal/util/interfaces.go34
-rw-r--r--vendor/github.com/containers/common/libnetwork/internal/util/ip.go70
-rw-r--r--vendor/github.com/containers/common/libnetwork/internal/util/parse.go37
-rw-r--r--vendor/github.com/containers/common/libnetwork/internal/util/util.go123
-rw-r--r--vendor/github.com/containers/common/libnetwork/internal/util/validate.go124
-rw-r--r--vendor/github.com/containers/common/libnetwork/netavark/config.go252
-rw-r--r--vendor/github.com/containers/common/libnetwork/netavark/const.go5
-rw-r--r--vendor/github.com/containers/common/libnetwork/netavark/exec.go161
-rw-r--r--vendor/github.com/containers/common/libnetwork/netavark/ipam.go372
-rw-r--r--vendor/github.com/containers/common/libnetwork/netavark/network.go293
-rw-r--r--vendor/github.com/containers/common/libnetwork/netavark/run.go127
-rw-r--r--vendor/github.com/containers/common/libnetwork/network/interface.go132
-rw-r--r--vendor/github.com/containers/common/libnetwork/types/const.go47
-rw-r--r--vendor/github.com/containers/common/libnetwork/types/define.go25
-rw-r--r--vendor/github.com/containers/common/libnetwork/types/network.go282
-rw-r--r--vendor/github.com/containers/common/libnetwork/util/filters.go80
-rw-r--r--vendor/github.com/containers/common/libnetwork/util/ip.go56
-rw-r--r--vendor/github.com/containers/common/libnetwork/util/ip_calc.go53
-rw-r--r--vendor/modules.txt6
29 files changed, 3943 insertions, 0 deletions
diff --git a/vendor/github.com/containers/common/libnetwork/cni/README.md b/vendor/github.com/containers/common/libnetwork/cni/README.md
new file mode 100644
index 000000000..6f57feff5
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/cni/README.md
@@ -0,0 +1,10 @@
+This package abstracts CNI from libpod.
+It implements the `ContainerNetwork` interface defined in [libpod/network/types/network.go](../types/network.go) for the CNI backend.
+
+
+## Testing
+Run the tests with:
+```
+go test -v -mod=vendor -cover ./libpod/network/cni/
+```
+Run the tests as root to also test setup/teardown. This will execute CNI and therefore the cni plugins have to be installed.
diff --git a/vendor/github.com/containers/common/libnetwork/cni/cni_conversion.go b/vendor/github.com/containers/common/libnetwork/cni/cni_conversion.go
new file mode 100644
index 000000000..dedb40ad3
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/cni/cni_conversion.go
@@ -0,0 +1,378 @@
+// +build linux
+
+package cni
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+
+ "github.com/containernetworking/cni/libcni"
+ internalutil "github.com/containers/common/libnetwork/internal/util"
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/libnetwork/util"
+ pkgutil "github.com/containers/common/pkg/util"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+func createNetworkFromCNIConfigList(conf *libcni.NetworkConfigList, confPath string) (*types.Network, error) {
+ network := types.Network{
+ Name: conf.Name,
+ ID: getNetworkIDFromName(conf.Name),
+ Labels: map[string]string{},
+ Options: map[string]string{},
+ IPAMOptions: map[string]string{},
+ }
+
+ cniJSON := make(map[string]interface{})
+ err := json.Unmarshal(conf.Bytes, &cniJSON)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to unmarshal network config %s", conf.Name)
+ }
+ if args, ok := cniJSON["args"]; ok {
+ if key, ok := args.(map[string]interface{}); ok {
+ // read network labels and options from the conf file
+ network.Labels = getNetworkArgsFromConfList(key, podmanLabelKey)
+ network.Options = getNetworkArgsFromConfList(key, podmanOptionsKey)
+ }
+ }
+
+ f, err := os.Stat(confPath)
+ if err != nil {
+ return nil, err
+ }
+ stat := f.Sys().(*syscall.Stat_t)
+ network.Created = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
+
+ firstPlugin := conf.Plugins[0]
+ network.Driver = firstPlugin.Network.Type
+
+ switch firstPlugin.Network.Type {
+ case types.BridgeNetworkDriver:
+ var bridge hostLocalBridge
+ err := json.Unmarshal(firstPlugin.Bytes, &bridge)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to unmarshal the bridge plugin config in %s", confPath)
+ }
+ network.NetworkInterface = bridge.BrName
+
+ // if isGateway is false we have an internal network
+ if !bridge.IsGW {
+ network.Internal = true
+ }
+
+ // set network options
+ if bridge.MTU != 0 {
+ network.Options["mtu"] = strconv.Itoa(bridge.MTU)
+ }
+ if bridge.Vlan != 0 {
+ network.Options["vlan"] = strconv.Itoa(bridge.Vlan)
+ }
+
+ err = convertIPAMConfToNetwork(&network, &bridge.IPAM, confPath)
+ if err != nil {
+ return nil, err
+ }
+
+ case types.MacVLANNetworkDriver, types.IPVLANNetworkDriver:
+ var vlan VLANConfig
+ err := json.Unmarshal(firstPlugin.Bytes, &vlan)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to unmarshal the macvlan plugin config in %s", confPath)
+ }
+ network.NetworkInterface = vlan.Master
+
+ // set network options
+ if vlan.MTU != 0 {
+ network.Options["mtu"] = strconv.Itoa(vlan.MTU)
+ }
+
+ if vlan.Mode != "" {
+ network.Options["mode"] = vlan.Mode
+ }
+
+ err = convertIPAMConfToNetwork(&network, &vlan.IPAM, confPath)
+ if err != nil {
+ return nil, err
+ }
+
+ default:
+ // A warning would be good but users would get this warning every time so keep this at info level.
+ logrus.Infof("Unsupported CNI config type %s in %s, this network can still be used but inspect or list cannot show all information",
+ firstPlugin.Network.Type, confPath)
+ }
+
+ // check if the dnsname plugin is configured
+ network.DNSEnabled = findPluginByName(conf.Plugins, "dnsname")
+
+ return &network, nil
+}
+
+func findPluginByName(plugins []*libcni.NetworkConfig, name string) bool {
+ for _, plugin := range plugins {
+ if plugin.Network.Type == name {
+ return true
+ }
+ }
+ return false
+}
+
+// convertIPAMConfToNetwork converts A cni IPAMConfig to libpod network subnets.
+// It returns an array of subnets and an extra bool if dhcp is configured.
+func convertIPAMConfToNetwork(network *types.Network, ipam *ipamConfig, confPath string) error {
+ if ipam.PluginType == types.DHCPIPAMDriver {
+ network.IPAMOptions["driver"] = types.DHCPIPAMDriver
+ return nil
+ }
+
+ if ipam.PluginType != types.HostLocalIPAMDriver {
+ return errors.Errorf("unsupported ipam plugin %s in %s", ipam.PluginType, confPath)
+ }
+
+ network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
+ for _, r := range ipam.Ranges {
+ for _, ipam := range r {
+ s := types.Subnet{}
+
+ // Do not use types.ParseCIDR() because we want the ip to be
+ // the network address and not a random ip in the sub.
+ _, sub, err := net.ParseCIDR(ipam.Subnet)
+ if err != nil {
+ return err
+ }
+ s.Subnet = types.IPNet{IPNet: *sub}
+
+ // gateway
+ var gateway net.IP
+ if ipam.Gateway != "" {
+ gateway = net.ParseIP(ipam.Gateway)
+ if gateway == nil {
+ return errors.Errorf("failed to parse gateway ip %s", ipam.Gateway)
+ }
+ // convert to 4 byte if ipv4
+ util.NormalizeIP(&gateway)
+ } else if !network.Internal {
+ // only add a gateway address if the network is not internal
+ gateway, err = util.FirstIPInSubnet(sub)
+ if err != nil {
+ return errors.Errorf("failed to get first ip in subnet %s", sub.String())
+ }
+ }
+ s.Gateway = gateway
+
+ var rangeStart net.IP
+ var rangeEnd net.IP
+ if ipam.RangeStart != "" {
+ rangeStart = net.ParseIP(ipam.RangeStart)
+ if rangeStart == nil {
+ return errors.Errorf("failed to parse range start ip %s", ipam.RangeStart)
+ }
+ }
+ if ipam.RangeEnd != "" {
+ rangeEnd = net.ParseIP(ipam.RangeEnd)
+ if rangeEnd == nil {
+ return errors.Errorf("failed to parse range end ip %s", ipam.RangeEnd)
+ }
+ }
+ if rangeStart != nil || rangeEnd != nil {
+ s.LeaseRange = &types.LeaseRange{}
+ s.LeaseRange.StartIP = rangeStart
+ s.LeaseRange.EndIP = rangeEnd
+ }
+ if util.IsIPv6(s.Subnet.IP) {
+ network.IPv6Enabled = true
+ }
+ network.Subnets = append(network.Subnets, s)
+ }
+ }
+ return nil
+}
+
+// getNetworkArgsFromConfList returns the map of args in a conflist, argType should be labels or options
+func getNetworkArgsFromConfList(args map[string]interface{}, argType string) map[string]string {
+ if args, ok := args[argType]; ok {
+ if labels, ok := args.(map[string]interface{}); ok {
+ result := make(map[string]string, len(labels))
+ for k, v := range labels {
+ if v, ok := v.(string); ok {
+ result[k] = v
+ }
+ }
+ return result
+ }
+ }
+ return map[string]string{}
+}
+
+// createCNIConfigListFromNetwork will create a cni config file from the given network.
+// It returns the cni config and the path to the file where the config was written.
+// Set writeToDisk to false to only add this network into memory.
+func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writeToDisk bool) (*libcni.NetworkConfigList, string, error) {
+ var (
+ routes []ipamRoute
+ ipamRanges [][]ipamLocalHostRangeConf
+ ipamConf ipamConfig
+ err error
+ )
+ if len(network.Subnets) > 0 {
+ for _, subnet := range network.Subnets {
+ route, err := newIPAMDefaultRoute(util.IsIPv6(subnet.Subnet.IP))
+ if err != nil {
+ return nil, "", err
+ }
+ routes = append(routes, route)
+ ipam := newIPAMLocalHostRange(subnet.Subnet, subnet.LeaseRange, subnet.Gateway)
+ ipamRanges = append(ipamRanges, []ipamLocalHostRangeConf{*ipam})
+ }
+ ipamConf = newIPAMHostLocalConf(routes, ipamRanges)
+ } else {
+ ipamConf = ipamConfig{PluginType: "dhcp"}
+ }
+
+ vlan := 0
+ mtu := 0
+ vlanPluginMode := ""
+ for k, v := range network.Options {
+ switch k {
+ case "mtu":
+ mtu, err = internalutil.ParseMTU(v)
+ if err != nil {
+ return nil, "", err
+ }
+
+ case "vlan":
+ vlan, err = internalutil.ParseVlan(v)
+ if err != nil {
+ return nil, "", err
+ }
+
+ case "mode":
+ switch network.Driver {
+ case types.MacVLANNetworkDriver:
+ if !pkgutil.StringInSlice(v, types.ValidMacVLANModes) {
+ return nil, "", errors.Errorf("unknown macvlan mode %q", v)
+ }
+ case types.IPVLANNetworkDriver:
+ if !pkgutil.StringInSlice(v, types.ValidIPVLANModes) {
+ return nil, "", errors.Errorf("unknown ipvlan mode %q", v)
+ }
+ default:
+ return nil, "", errors.Errorf("cannot set option \"mode\" with driver %q", network.Driver)
+ }
+ vlanPluginMode = v
+
+ default:
+ return nil, "", errors.Errorf("unsupported network option %s", k)
+ }
+ }
+
+ isGateway := true
+ ipMasq := true
+ if network.Internal {
+ isGateway = false
+ ipMasq = false
+ }
+ // create CNI plugin configuration
+ // explicitly use CNI version 0.4.0 here, to use v1.0.0 at least containernetwork-plugins-1.0.1 has to be installed
+ // the dnsname plugin also needs to be updated for 1.0.0
+ // TODO change to 1.0.0 when most distros support it
+ ncList := newNcList(network.Name, "0.4.0", network.Labels, network.Options)
+ var plugins []interface{}
+
+ switch network.Driver {
+ case types.BridgeNetworkDriver:
+ bridge := newHostLocalBridge(network.NetworkInterface, isGateway, ipMasq, mtu, vlan, &ipamConf)
+ plugins = append(plugins, bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin())
+ // if we find the dnsname plugin we add configuration for it
+ if hasDNSNamePlugin(n.cniPluginDirs) && network.DNSEnabled {
+ // Note: in the future we might like to allow for dynamic domain names
+ plugins = append(plugins, newDNSNamePlugin(defaultPodmanDomainName))
+ }
+
+ case types.MacVLANNetworkDriver:
+ plugins = append(plugins, newVLANPlugin(types.MacVLANNetworkDriver, network.NetworkInterface, vlanPluginMode, mtu, &ipamConf))
+
+ case types.IPVLANNetworkDriver:
+ plugins = append(plugins, newVLANPlugin(types.IPVLANNetworkDriver, network.NetworkInterface, vlanPluginMode, mtu, &ipamConf))
+
+ default:
+ return nil, "", errors.Errorf("driver %q is not supported by cni", network.Driver)
+ }
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return nil, "", err
+ }
+ cniPathName := ""
+ if writeToDisk {
+ cniPathName = filepath.Join(n.cniConfigDir, network.Name+".conflist")
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ if err != nil {
+ return nil, "", err
+ }
+ f, err := os.Stat(cniPathName)
+ if err != nil {
+ return nil, "", err
+ }
+ stat := f.Sys().(*syscall.Stat_t)
+ network.Created = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
+ } else {
+ network.Created = time.Now()
+ }
+ config, err := libcni.ConfListFromBytes(b)
+ if err != nil {
+ return nil, "", err
+ }
+ return config, cniPathName, nil
+}
+
+func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry, error) {
+ cniPorts := make([]cniPortMapEntry, 0, len(ports))
+ for _, port := range ports {
+ if port.Protocol == "" {
+ return nil, errors.New("port protocol should not be empty")
+ }
+ protocols := strings.Split(port.Protocol, ",")
+
+ for _, protocol := range protocols {
+ if !pkgutil.StringInSlice(protocol, []string{"tcp", "udp", "sctp"}) {
+ return nil, errors.Errorf("unknown port protocol %s", protocol)
+ }
+ cniPort := cniPortMapEntry{
+ HostPort: int(port.HostPort),
+ ContainerPort: int(port.ContainerPort),
+ HostIP: port.HostIP,
+ Protocol: protocol,
+ }
+ cniPorts = append(cniPorts, cniPort)
+ for i := 1; i < int(port.Range); i++ {
+ cniPort := cniPortMapEntry{
+ HostPort: int(port.HostPort) + i,
+ ContainerPort: int(port.ContainerPort) + i,
+ HostIP: port.HostIP,
+ Protocol: protocol,
+ }
+ cniPorts = append(cniPorts, cniPort)
+ }
+ }
+ }
+ return cniPorts, nil
+}
+
+func removeMachinePlugin(conf *libcni.NetworkConfigList) *libcni.NetworkConfigList {
+ plugins := make([]*libcni.NetworkConfig, 0, len(conf.Plugins))
+ for _, net := range conf.Plugins {
+ if net.Network.Type != "podman-machine" {
+ plugins = append(plugins, net)
+ }
+ }
+ conf.Plugins = plugins
+ return conf
+}
diff --git a/vendor/github.com/containers/common/libnetwork/cni/cni_exec.go b/vendor/github.com/containers/common/libnetwork/cni/cni_exec.go
new file mode 100644
index 000000000..c66e7ef5d
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/cni/cni_exec.go
@@ -0,0 +1,110 @@
+// Copyright 2016 CNI authors
+// Copyright 2021 Podman authors
+//
+// This code has been originally copied from github.com/containernetworking/cni
+// but has been changed to better fit the Podman use case.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build linux
+
+package cni
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/containernetworking/cni/pkg/invoke"
+ "github.com/containernetworking/cni/pkg/version"
+ "github.com/containers/storage/pkg/unshare"
+)
+
+type cniExec struct {
+ version.PluginDecoder
+}
+
+type cniPluginError struct {
+ plugin string
+ Code uint `json:"code"`
+ Msg string `json:"msg"`
+ Details string `json:"details,omitempty"`
+}
+
+// Error returns a nicely formatted error message for the cni plugin errors.
+func (e *cniPluginError) Error() string {
+ err := fmt.Sprintf("cni plugin %s failed", e.plugin)
+ if e.Msg != "" {
+ err = fmt.Sprintf("%s: %s", err, e.Msg)
+ } else if e.Code > 0 {
+ err = fmt.Sprintf("%s with error code %d", err, e.Code)
+ }
+ if e.Details != "" {
+ err = fmt.Sprintf("%s: %s", err, e.Details)
+ }
+ return err
+}
+
+// ExecPlugin execute the cni plugin. Returns the stdout of the plugin or an error.
+func (e *cniExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
+ stdout := &bytes.Buffer{}
+ stderr := &bytes.Buffer{}
+ c := exec.CommandContext(ctx, pluginPath)
+ c.Env = environ
+ c.Stdin = bytes.NewBuffer(stdinData)
+ c.Stdout = stdout
+ c.Stderr = stderr
+
+ // The dnsname plugin tries to use XDG_RUNTIME_DIR to store files.
+ // podman run will have XDG_RUNTIME_DIR set and thus the cni plugin can use
+ // it. The problem is that XDG_RUNTIME_DIR is unset for the conmon process
+ // for rootful users. This causes issues since the cleanup process is spawned
+ // by conmon and thus not have XDG_RUNTIME_DIR set to same value as podman run.
+ // Because of it dnsname will not find the config files and cannot correctly cleanup.
+ // To fix this we should also unset XDG_RUNTIME_DIR for the cni plugins as rootful.
+ if !unshare.IsRootless() {
+ c.Env = append(c.Env, "XDG_RUNTIME_DIR=")
+ }
+
+ err := c.Run()
+ if err != nil {
+ return nil, annotatePluginError(err, pluginPath, stdout.Bytes(), stderr.Bytes())
+ }
+ return stdout.Bytes(), nil
+}
+
+// annotatePluginError parses the common cni plugin error json.
+func annotatePluginError(err error, plugin string, stdout, stderr []byte) error {
+ pluginName := filepath.Base(plugin)
+ emsg := cniPluginError{
+ plugin: pluginName,
+ }
+ if len(stdout) == 0 {
+ if len(stderr) == 0 {
+ emsg.Msg = err.Error()
+ } else {
+ emsg.Msg = string(stderr)
+ }
+ } else if perr := json.Unmarshal(stdout, &emsg); perr != nil {
+ emsg.Msg = fmt.Sprintf("failed to unmarshal error message %q: %v", string(stdout), perr)
+ }
+ return &emsg
+}
+
+// FindInPath finds the plugin in the given paths.
+func (e *cniExec) FindInPath(plugin string, paths []string) (string, error) {
+ return invoke.FindInPath(plugin, paths)
+}
diff --git a/vendor/github.com/containers/common/libnetwork/cni/cni_types.go b/vendor/github.com/containers/common/libnetwork/cni/cni_types.go
new file mode 100644
index 000000000..fbfcd49ad
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/cni/cni_types.go
@@ -0,0 +1,281 @@
+// +build linux
+
+package cni
+
+import (
+ "net"
+ "os"
+ "path/filepath"
+
+ "github.com/containers/common/libnetwork/types"
+)
+
+const (
+ defaultIPv4Route = "0.0.0.0/0"
+ defaultIPv6Route = "::/0"
+ // defaultPodmanDomainName is used for the dnsname plugin to define
+ // a localized domain name for a created network
+ defaultPodmanDomainName = "dns.podman"
+
+ // cniDeviceName is the default name for a new bridge, it should be suffixed with an integer
+ cniDeviceName = "cni-podman"
+
+ // podmanLabelKey key used to store the podman network label in a cni config
+ podmanLabelKey = "podman_labels"
+
+ // podmanOptionsKey key used to store the podman network options in a cni config
+ podmanOptionsKey = "podman_options"
+)
+
+// cniPortMapEntry struct is used by the portmap plugin
+// https://github.com/containernetworking/plugins/blob/649e0181fe7b3a61e708f3e4249a798f57f25cc5/plugins/meta/portmap/main.go#L43-L50
+type cniPortMapEntry struct {
+ HostPort int `json:"hostPort"`
+ ContainerPort int `json:"containerPort"`
+ Protocol string `json:"protocol"`
+ HostIP string `json:"hostIP,omitempty"`
+}
+
+// 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"`
+ Capabilities map[string]bool `json:"capabilities,omitempty"`
+}
+
+// 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"`
+}
+
+// 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"`
+}
+
+// VLANConfig describes the macvlan config
+type VLANConfig struct {
+ PluginType string `json:"type"`
+ Master string `json:"master"`
+ IPAM ipamConfig `json:"ipam"`
+ MTU int `json:"mtu,omitempty"`
+ Mode string `json:"mode,omitempty"`
+ Capabilities map[string]bool `json:"capabilities,omitempty"`
+}
+
+// firewallConfig describes the firewall plugin
+type firewallConfig struct {
+ PluginType string `json:"type"`
+ Backend string `json:"backend"`
+}
+
+// tuningConfig describes the tuning plugin
+type tuningConfig struct {
+ PluginType string `json:"type"`
+}
+
+// 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"`
+}
+
+// ncList describes a generic map
+type ncList map[string]interface{}
+
+// newNcList creates a generic map of values with string
+// keys and adds in version and network name
+func newNcList(name, version string, labels, options map[string]string) ncList {
+ n := ncList{}
+ n["cniVersion"] = version
+ n["name"] = name
+ args := map[string]map[string]string{}
+ if len(labels) > 0 {
+ args[podmanLabelKey] = labels
+ }
+ if len(options) > 0 {
+ args[podmanOptionsKey] = options
+ }
+ if len(args) > 0 {
+ n["args"] = args
+ }
+ return n
+}
+
+// newHostLocalBridge creates a new LocalBridge for host-local
+func newHostLocalBridge(name string, isGateWay, ipMasq bool, mtu, vlan int, ipamConf *ipamConfig) *hostLocalBridge {
+ caps := make(map[string]bool)
+ caps["ips"] = true
+ bridge := hostLocalBridge{
+ PluginType: "bridge",
+ BrName: name,
+ IsGW: isGateWay,
+ IPMasq: ipMasq,
+ MTU: mtu,
+ HairpinMode: true,
+ Vlan: vlan,
+ IPAM: *ipamConf,
+ }
+ // if we use host-local set the ips cap to ensure we can set static ips via runtime config
+ if ipamConf.PluginType == types.HostLocalIPAMDriver {
+ bridge.Capabilities = caps
+ }
+ return &bridge
+}
+
+// newIPAMHostLocalConf creates a new IPAMHostLocal configuration
+func newIPAMHostLocalConf(routes []ipamRoute, ipamRanges [][]ipamLocalHostRangeConf) ipamConfig {
+ ipamConf := ipamConfig{
+ PluginType: "host-local",
+ Routes: routes,
+ }
+
+ ipamConf.Ranges = ipamRanges
+ return ipamConf
+}
+
+// newIPAMLocalHostRange create a new IPAM range
+func newIPAMLocalHostRange(subnet types.IPNet, leaseRange *types.LeaseRange, gw net.IP) *ipamLocalHostRangeConf {
+ hostRange := &ipamLocalHostRangeConf{
+ Subnet: subnet.String(),
+ }
+
+ // a user provided a range, we add it here
+ if leaseRange != nil {
+ if leaseRange.StartIP != nil {
+ hostRange.RangeStart = leaseRange.StartIP.String()
+ }
+ if leaseRange.EndIP != nil {
+ hostRange.RangeEnd = leaseRange.EndIP.String()
+ }
+ }
+
+ if gw != nil {
+ hostRange.Gateway = gw.String()
+ }
+ return hostRange
+}
+
+// newIPAMRoute creates a new IPAM route configuration
+// nolint:interfacer
+func newIPAMRoute(r *net.IPNet) ipamRoute {
+ 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
+}
+
+// newVLANPlugin creates a macvlanconfig with a given device name
+func newVLANPlugin(pluginType, device, mode string, mtu int, ipam *ipamConfig) VLANConfig {
+ m := VLANConfig{
+ PluginType: pluginType,
+ IPAM: *ipam,
+ }
+ if mtu > 0 {
+ m.MTU = mtu
+ }
+ if len(mode) > 0 {
+ m.Mode = mode
+ }
+ // CNI is supposed to use the default route if a
+ // parent device is not provided
+ if len(device) > 0 {
+ m.Master = device
+ }
+ caps := make(map[string]bool)
+ caps["ips"] = true
+ // if we use host-local set the ips cap to ensure we can set static ips via runtime config
+ if ipam.PluginType == types.HostLocalIPAMDriver {
+ m.Capabilities = caps
+ }
+ return m
+}
diff --git a/vendor/github.com/containers/common/libnetwork/cni/config.go b/vendor/github.com/containers/common/libnetwork/cni/config.go
new file mode 100644
index 000000000..e34daa3c5
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/cni/config.go
@@ -0,0 +1,208 @@
+// +build linux
+
+package cni
+
+import (
+ "net"
+ "os"
+
+ internalutil "github.com/containers/common/libnetwork/internal/util"
+ "github.com/containers/common/libnetwork/types"
+ pkgutil "github.com/containers/common/pkg/util"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+// NetworkCreate will take a partial filled Network and fill the
+// missing fields. It creates the Network and returns the full Network.
+// nolint:gocritic
+func (n *cniNetwork) NetworkCreate(net types.Network) (types.Network, error) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return types.Network{}, err
+ }
+ network, err := n.networkCreate(&net, false)
+ if err != nil {
+ return types.Network{}, err
+ }
+ // add the new network to the map
+ n.networks[network.libpodNet.Name] = network
+ return *network.libpodNet, nil
+}
+
+// networkCreate will fill out the given network struct and return the new network entry.
+// If defaultNet is true it will not validate against used subnets and it will not write the cni config to disk.
+func (n *cniNetwork) networkCreate(newNetwork *types.Network, defaultNet bool) (*network, error) {
+ // if no driver is set use the default one
+ if newNetwork.Driver == "" {
+ newNetwork.Driver = types.DefaultNetworkDriver
+ }
+
+ // FIXME: Should we use a different type for network create without the ID field?
+ // the caller is not allowed to set a specific ID
+ if newNetwork.ID != "" {
+ return nil, errors.Wrap(types.ErrInvalidArg, "ID can not be set for network create")
+ }
+
+ err := internalutil.CommonNetworkCreate(n, newNetwork)
+ if err != nil {
+ return nil, err
+ }
+
+ // Only get the used networks for validation if we do not create the default network.
+ // The default network should not be validated against used subnets, we have to ensure
+ // that this network can always be created even when a subnet is already used on the host.
+ // This could happen if you run a container on this net, then the cni interface will be
+ // created on the host and "block" this subnet from being used again.
+ // Therefore the next podman command tries to create the default net again and it would
+ // fail because it thinks the network is used on the host.
+ var usedNetworks []*net.IPNet
+ if !defaultNet {
+ usedNetworks, err = internalutil.GetUsedSubnets(n)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ switch newNetwork.Driver {
+ case types.BridgeNetworkDriver:
+ err = internalutil.CreateBridge(n, newNetwork, usedNetworks)
+ if err != nil {
+ return nil, err
+ }
+ case types.MacVLANNetworkDriver, types.IPVLANNetworkDriver:
+ err = createIPMACVLAN(newNetwork)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ return nil, errors.Wrapf(types.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver)
+ }
+
+ err = internalutil.ValidateSubnets(newNetwork, usedNetworks)
+ if err != nil {
+ return nil, err
+ }
+
+ // generate the network ID
+ newNetwork.ID = getNetworkIDFromName(newNetwork.Name)
+
+ // FIXME: Should this be a hard error?
+ if newNetwork.DNSEnabled && newNetwork.Internal && hasDNSNamePlugin(n.cniPluginDirs) {
+ logrus.Warnf("dnsname and internal networks are incompatible. dnsname plugin not configured for network %s", newNetwork.Name)
+ newNetwork.DNSEnabled = false
+ }
+
+ cniConf, path, err := n.createCNIConfigListFromNetwork(newNetwork, !defaultNet)
+ if err != nil {
+ return nil, err
+ }
+ return &network{cniNet: cniConf, libpodNet: newNetwork, filename: path}, nil
+}
+
+// NetworkRemove will remove the Network with the given name or ID.
+// It does not ensure that the network is unused.
+func (n *cniNetwork) NetworkRemove(nameOrID string) error {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return err
+ }
+
+ network, err := n.getNetwork(nameOrID)
+ if err != nil {
+ return err
+ }
+
+ // Removing the default network is not allowed.
+ if network.libpodNet.Name == n.defaultNetwork {
+ return errors.Errorf("default network %s cannot be removed", n.defaultNetwork)
+ }
+
+ // Remove the bridge network interface on the host.
+ if network.libpodNet.Driver == types.BridgeNetworkDriver {
+ link, err := netlink.LinkByName(network.libpodNet.NetworkInterface)
+ if err == nil {
+ err = netlink.LinkDel(link)
+ // only log the error, it is not fatal
+ if err != nil {
+ logrus.Infof("Failed to remove network interface %s: %v", network.libpodNet.NetworkInterface, err)
+ }
+ }
+ }
+
+ file := network.filename
+ delete(n.networks, network.libpodNet.Name)
+
+ // make sure to not error for ErrNotExist
+ if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ return nil
+}
+
+// NetworkList will return all known Networks. Optionally you can
+// supply a list of filter functions. Only if a network matches all
+// functions it is returned.
+func (n *cniNetwork) NetworkList(filters ...types.FilterFunc) ([]types.Network, error) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return nil, err
+ }
+
+ networks := make([]types.Network, 0, len(n.networks))
+outer:
+ for _, net := range n.networks {
+ for _, filter := range filters {
+ // All filters have to match, if one does not match we can skip to the next network.
+ if !filter(*net.libpodNet) {
+ continue outer
+ }
+ }
+ networks = append(networks, *net.libpodNet)
+ }
+ return networks, nil
+}
+
+// NetworkInspect will return the Network with the given name or ID.
+func (n *cniNetwork) NetworkInspect(nameOrID string) (types.Network, error) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return types.Network{}, err
+ }
+
+ network, err := n.getNetwork(nameOrID)
+ if err != nil {
+ return types.Network{}, err
+ }
+ return *network.libpodNet, nil
+}
+
+func createIPMACVLAN(network *types.Network) error {
+ if network.Internal {
+ return errors.New("internal is not supported with macvlan")
+ }
+ if network.NetworkInterface != "" {
+ interfaceNames, err := internalutil.GetLiveNetworkNames()
+ if err != nil {
+ return err
+ }
+ if !pkgutil.StringInSlice(network.NetworkInterface, interfaceNames) {
+ return errors.Errorf("parent interface %s does not exist", network.NetworkInterface)
+ }
+ }
+ if len(network.Subnets) == 0 {
+ network.IPAMOptions["driver"] = types.DHCPIPAMDriver
+ } else {
+ network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
+ }
+ return nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/cni/network.go b/vendor/github.com/containers/common/libnetwork/cni/network.go
new file mode 100644
index 000000000..958227235
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/cni/network.go
@@ -0,0 +1,275 @@
+// +build linux
+
+package cni
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/storage/pkg/lockfile"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+type cniNetwork struct {
+ // cniConfigDir is directory where the cni config files are stored.
+ cniConfigDir string
+ // cniPluginDirs is a list of directories where cni should look for the plugins.
+ cniPluginDirs []string
+
+ cniConf *libcni.CNIConfig
+
+ // defaultNetwork is the name for the default network.
+ defaultNetwork string
+ // defaultSubnet is the default subnet for the default network.
+ defaultSubnet types.IPNet
+
+ // isMachine describes whenever podman runs in a podman machine environment.
+ isMachine bool
+
+ // lock is a internal lock for critical operations
+ lock lockfile.Locker
+
+ // modTime is the timestamp when the config dir was modified
+ modTime time.Time
+
+ // networks is a map with loaded networks, the key is the network name
+ networks map[string]*network
+}
+
+type network struct {
+ // filename is the full path to the cni config file on disk
+ filename string
+ libpodNet *types.Network
+ cniNet *libcni.NetworkConfigList
+}
+
+type InitConfig struct {
+ // CNIConfigDir is directory where the cni config files are stored.
+ CNIConfigDir string
+ // CNIPluginDirs is a list of directories where cni should look for the plugins.
+ CNIPluginDirs []string
+
+ // DefaultNetwork is the name for the default network.
+ DefaultNetwork string
+ // DefaultSubnet is the default subnet for the default network.
+ DefaultSubnet string
+
+ // IsMachine describes whenever podman runs in a podman machine environment.
+ IsMachine bool
+}
+
+// NewCNINetworkInterface creates the ContainerNetwork interface for the CNI backend.
+// Note: The networks are not loaded from disk until a method is called.
+func NewCNINetworkInterface(conf *InitConfig) (types.ContainerNetwork, error) {
+ // TODO: consider using a shared memory lock
+ lock, err := lockfile.GetLockfile(filepath.Join(conf.CNIConfigDir, "cni.lock"))
+ if err != nil {
+ return nil, err
+ }
+
+ defaultNetworkName := conf.DefaultNetwork
+ if defaultNetworkName == "" {
+ defaultNetworkName = types.DefaultNetworkName
+ }
+
+ defaultSubnet := conf.DefaultSubnet
+ if defaultSubnet == "" {
+ defaultSubnet = types.DefaultSubnet
+ }
+ defaultNet, err := types.ParseCIDR(defaultSubnet)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to parse default subnet")
+ }
+
+ cni := libcni.NewCNIConfig(conf.CNIPluginDirs, &cniExec{})
+ n := &cniNetwork{
+ cniConfigDir: conf.CNIConfigDir,
+ cniPluginDirs: conf.CNIPluginDirs,
+ cniConf: cni,
+ defaultNetwork: defaultNetworkName,
+ defaultSubnet: defaultNet,
+ isMachine: conf.IsMachine,
+ lock: lock,
+ }
+
+ return n, nil
+}
+
+// Drivers will return the list of supported network drivers
+// for this interface.
+func (n *cniNetwork) Drivers() []string {
+ return []string{types.BridgeNetworkDriver, types.MacVLANNetworkDriver, types.IPVLANNetworkDriver}
+}
+
+// DefaultNetworkName will return the default cni network name.
+func (n *cniNetwork) DefaultNetworkName() string {
+ return n.defaultNetwork
+}
+
+func (n *cniNetwork) loadNetworks() error {
+ // check the mod time of the config dir
+ f, err := os.Stat(n.cniConfigDir)
+ if err != nil {
+ return err
+ }
+ modTime := f.ModTime()
+
+ // skip loading networks if they are already loaded and
+ // if the config dir was not modified since the last call
+ if n.networks != nil && modTime.Equal(n.modTime) {
+ return nil
+ }
+ // make sure the remove all networks before we reload them
+ n.networks = nil
+ n.modTime = modTime
+
+ // FIXME: do we have to support other file types as well, e.g. .conf?
+ files, err := libcni.ConfFiles(n.cniConfigDir, []string{".conflist"})
+ if err != nil {
+ return err
+ }
+ networks := make(map[string]*network, len(files))
+ for _, file := range files {
+ conf, err := libcni.ConfListFromFile(file)
+ if err != nil {
+ // do not log ENOENT errors
+ if !errors.Is(err, os.ErrNotExist) {
+ logrus.Warnf("Error loading CNI config file %s: %v", file, err)
+ }
+ continue
+ }
+
+ if !types.NameRegex.MatchString(conf.Name) {
+ logrus.Warnf("CNI config list %s has invalid name, skipping: %v", file, types.RegexError)
+ continue
+ }
+
+ // podman < v4.0 used the podman-machine cni plugin for podman machine port forwarding
+ // since this is now build into podman we no longer use the plugin
+ // old configs may still contain it so we just remove it here
+ if n.isMachine {
+ conf = removeMachinePlugin(conf)
+ }
+
+ if _, err := n.cniConf.ValidateNetworkList(context.Background(), conf); err != nil {
+ logrus.Warnf("Error validating CNI config file %s: %v", file, err)
+ continue
+ }
+
+ if val, ok := networks[conf.Name]; ok {
+ logrus.Warnf("CNI config list %s has the same network name as %s, skipping", file, val.filename)
+ continue
+ }
+
+ net, err := createNetworkFromCNIConfigList(conf, file)
+ if err != nil {
+ logrus.Errorf("CNI config list %s could not be converted to a libpod config, skipping: %v", file, err)
+ continue
+ }
+ logrus.Debugf("Successfully loaded network %s: %v", net.Name, net)
+ networkInfo := network{
+ filename: file,
+ cniNet: conf,
+ libpodNet: net,
+ }
+ networks[net.Name] = &networkInfo
+ }
+
+ // create the default network in memory if it did not exists on disk
+ if networks[n.defaultNetwork] == nil {
+ networkInfo, err := n.createDefaultNetwork()
+ if err != nil {
+ return errors.Wrapf(err, "failed to create default network %s", n.defaultNetwork)
+ }
+ networks[n.defaultNetwork] = networkInfo
+ }
+
+ logrus.Debugf("Successfully loaded %d networks", len(networks))
+ n.networks = networks
+ return nil
+}
+
+func (n *cniNetwork) createDefaultNetwork() (*network, error) {
+ net := types.Network{
+ Name: n.defaultNetwork,
+ NetworkInterface: "cni-podman0",
+ Driver: types.BridgeNetworkDriver,
+ Subnets: []types.Subnet{
+ {Subnet: n.defaultSubnet},
+ },
+ }
+ return n.networkCreate(&net, true)
+}
+
+// getNetwork will lookup a network by name or ID. It returns an
+// error when no network was found or when more than one network
+// with the given (partial) ID exists.
+// getNetwork will read from the networks map, therefore the caller
+// must ensure that n.lock is locked before using it.
+func (n *cniNetwork) getNetwork(nameOrID string) (*network, error) {
+ // fast path check the map key, this will only work for names
+ if val, ok := n.networks[nameOrID]; ok {
+ return val, nil
+ }
+ // If there was no match we might got a full or partial ID.
+ var net *network
+ for _, val := range n.networks {
+ // This should not happen because we already looked up the map by name but check anyway.
+ if val.libpodNet.Name == nameOrID {
+ return val, nil
+ }
+
+ if strings.HasPrefix(val.libpodNet.ID, nameOrID) {
+ if net != nil {
+ return nil, errors.Errorf("more than one result for network ID %s", nameOrID)
+ }
+ net = val
+ }
+ }
+ if net != nil {
+ return net, nil
+ }
+ return nil, errors.Wrapf(types.ErrNoSuchNetwork, "unable to find network with name or ID %s", nameOrID)
+}
+
+// getNetworkIDFromName creates a network ID from the name. It is just the
+// sha256 hash so it is not safe but it should be safe enough for our use case.
+func getNetworkIDFromName(name string) string {
+ hash := sha256.Sum256([]byte(name))
+ return hex.EncodeToString(hash[:])
+}
+
+// Implement the NetUtil interface for easy code sharing with other network interfaces.
+
+// ForEach call the given function for each network
+func (n *cniNetwork) ForEach(run func(types.Network)) {
+ for _, val := range n.networks {
+ run(*val.libpodNet)
+ }
+}
+
+// Len return the number of networks
+func (n *cniNetwork) Len() int {
+ return len(n.networks)
+}
+
+// DefaultInterfaceName return the default cni bridge name, must be suffixed with a number.
+func (n *cniNetwork) DefaultInterfaceName() string {
+ return cniDeviceName
+}
+
+func (n *cniNetwork) Network(nameOrID string) (*types.Network, error) {
+ network, err := n.getNetwork(nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return network.libpodNet, err
+}
diff --git a/vendor/github.com/containers/common/libnetwork/cni/run.go b/vendor/github.com/containers/common/libnetwork/cni/run.go
new file mode 100644
index 000000000..af05d9d9d
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/cni/run.go
@@ -0,0 +1,274 @@
+// +build linux
+
+package cni
+
+import (
+ "context"
+ "net"
+ "os"
+ "strings"
+
+ "github.com/containernetworking/cni/libcni"
+ cnitypes "github.com/containernetworking/cni/pkg/types"
+ types040 "github.com/containernetworking/cni/pkg/types/040"
+ "github.com/containernetworking/plugins/pkg/ns"
+ "github.com/containers/common/libnetwork/internal/util"
+ "github.com/containers/common/libnetwork/types"
+ "github.com/hashicorp/go-multierror"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/vishvananda/netlink"
+)
+
+// Setup will setup the container network namespace. It returns
+// a map of StatusBlocks, the key is the network name.
+func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (map[string]types.StatusBlock, error) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return nil, err
+ }
+
+ err = util.ValidateSetupOptions(n, namespacePath, options)
+ if err != nil {
+ return nil, err
+ }
+
+ // set the loopback adapter up in the container netns
+ err = ns.WithNetNSPath(namespacePath, func(_ ns.NetNS) error {
+ link, err := netlink.LinkByName("lo")
+ if err == nil {
+ err = netlink.LinkSetUp(link)
+ }
+ return err
+ })
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to set the loopback adapter up")
+ }
+
+ var retErr error
+ teardownOpts := options
+ teardownOpts.Networks = map[string]types.PerNetworkOptions{}
+ // make sure to teardown the already connected networks on error
+ defer func() {
+ if retErr != nil {
+ if len(teardownOpts.Networks) > 0 {
+ err := n.teardown(namespacePath, types.TeardownOptions(teardownOpts))
+ if err != nil {
+ logrus.Warn(err)
+ }
+ }
+ }
+ }()
+
+ ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings)
+ if err != nil {
+ return nil, err
+ }
+
+ results := make(map[string]types.StatusBlock, len(options.Networks))
+ for name, netOpts := range options.Networks {
+ netOpts := netOpts
+ network := n.networks[name]
+ rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, &netOpts)
+
+ // If we have more than one static ip we need parse the ips via runtime config,
+ // make sure to add the ips capability to the first plugin otherwise it doesn't get the ips
+ if len(netOpts.StaticIPs) > 0 && !network.cniNet.Plugins[0].Network.Capabilities["ips"] {
+ caps := make(map[string]interface{})
+ caps["capabilities"] = map[string]bool{"ips": true}
+ network.cniNet.Plugins[0], retErr = libcni.InjectConf(network.cniNet.Plugins[0], caps)
+ if retErr != nil {
+ return nil, retErr
+ }
+ }
+
+ var res cnitypes.Result
+ res, retErr = n.cniConf.AddNetworkList(context.Background(), network.cniNet, rt)
+ // Add this network to teardown opts since it is now connected.
+ // Also add this if an errors was returned since we want to call teardown on this regardless.
+ teardownOpts.Networks[name] = netOpts
+ if retErr != nil {
+ return nil, retErr
+ }
+
+ logrus.Debugf("cni result for container %s network %s: %v", options.ContainerID, name, res)
+ var status types.StatusBlock
+ status, retErr = CNIResultToStatus(res)
+ if retErr != nil {
+ return nil, retErr
+ }
+ results[name] = status
+ }
+ return results, nil
+}
+
+// CNIResultToStatus convert the cni result to status block
+// nolint:golint
+func CNIResultToStatus(res cnitypes.Result) (types.StatusBlock, error) {
+ result := types.StatusBlock{}
+ cniResult, err := types040.GetResult(res)
+ if err != nil {
+ return result, err
+ }
+ nameservers := make([]net.IP, 0, len(cniResult.DNS.Nameservers))
+ for _, nameserver := range cniResult.DNS.Nameservers {
+ ip := net.ParseIP(nameserver)
+ if ip == nil {
+ return result, errors.Errorf("failed to parse cni nameserver ip %s", nameserver)
+ }
+ nameservers = append(nameservers, ip)
+ }
+ result.DNSServerIPs = nameservers
+ result.DNSSearchDomains = cniResult.DNS.Search
+
+ interfaces := make(map[string]types.NetInterface)
+ for _, ip := range cniResult.IPs {
+ if ip.Interface == nil {
+ // we do no expect ips without an interface
+ continue
+ }
+ if len(cniResult.Interfaces) <= *ip.Interface {
+ return result, errors.Errorf("invalid cni result, interface index %d out of range", *ip.Interface)
+ }
+ cniInt := cniResult.Interfaces[*ip.Interface]
+ netInt, ok := interfaces[cniInt.Name]
+ if ok {
+ netInt.Subnets = append(netInt.Subnets, types.NetAddress{
+ IPNet: types.IPNet{IPNet: ip.Address},
+ Gateway: ip.Gateway,
+ })
+ interfaces[cniInt.Name] = netInt
+ } else {
+ mac, err := net.ParseMAC(cniInt.Mac)
+ if err != nil {
+ return result, err
+ }
+ interfaces[cniInt.Name] = types.NetInterface{
+ MacAddress: types.HardwareAddr(mac),
+ Subnets: []types.NetAddress{{
+ IPNet: types.IPNet{IPNet: ip.Address},
+ Gateway: ip.Gateway,
+ }},
+ }
+ }
+ }
+ result.Interfaces = interfaces
+ return result, nil
+}
+
+func getRuntimeConfig(netns, conName, conID, networkName string, ports []cniPortMapEntry, opts *types.PerNetworkOptions) *libcni.RuntimeConf {
+ rt := &libcni.RuntimeConf{
+ ContainerID: conID,
+ NetNS: netns,
+ IfName: opts.InterfaceName,
+ Args: [][2]string{
+ {"IgnoreUnknown", "1"},
+ // Do not set the K8S env vars, see https://github.com/containers/podman/issues/12083.
+ // Only K8S_POD_NAME is used by dnsname to get the container name.
+ {"K8S_POD_NAME", conName},
+ },
+ CapabilityArgs: map[string]interface{}{},
+ }
+
+ // Propagate environment CNI_ARGS
+ for _, kvpairs := range strings.Split(os.Getenv("CNI_ARGS"), ";") {
+ if keyval := strings.SplitN(kvpairs, "=", 2); len(keyval) == 2 {
+ rt.Args = append(rt.Args, [2]string{keyval[0], keyval[1]})
+ }
+ }
+
+ // Add mac address to cni args
+ if len(opts.StaticMAC) > 0 {
+ rt.Args = append(rt.Args, [2]string{"MAC", opts.StaticMAC.String()})
+ }
+
+ if len(opts.StaticIPs) == 1 {
+ // Add a single IP to the args field. CNI plugins < 1.0.0
+ // do not support multiple ips via capability args.
+ rt.Args = append(rt.Args, [2]string{"IP", opts.StaticIPs[0].String()})
+ } else if len(opts.StaticIPs) > 1 {
+ // Set the static ips in the capability args
+ // to support more than one static ip per network.
+ rt.CapabilityArgs["ips"] = opts.StaticIPs
+ }
+
+ // Set network aliases for the dnsname plugin.
+ if len(opts.Aliases) > 0 {
+ rt.CapabilityArgs["aliases"] = map[string][]string{
+ networkName: opts.Aliases,
+ }
+ }
+
+ // Set PortMappings in Capabilities
+ if len(ports) > 0 {
+ rt.CapabilityArgs["portMappings"] = ports
+ }
+
+ return rt
+}
+
+// Teardown will teardown the container network namespace.
+func (n *cniNetwork) Teardown(namespacePath string, options types.TeardownOptions) error {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return err
+ }
+ return n.teardown(namespacePath, options)
+}
+
+func (n *cniNetwork) teardown(namespacePath string, options types.TeardownOptions) error {
+ // Note: An empty namespacePath is allowed because some plugins
+ // still need teardown, for example ipam should remove used ip allocations.
+
+ ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings)
+ if err != nil {
+ return err
+ }
+
+ var multiErr *multierror.Error
+ for name, netOpts := range options.Networks {
+ netOpts := netOpts
+ rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, &netOpts)
+
+ cniConfList, newRt, err := getCachedNetworkConfig(n.cniConf, name, rt)
+ if err == nil {
+ rt = newRt
+ } else {
+ logrus.Warnf("Failed to load cached network config: %v, falling back to loading network %s from disk", err, name)
+ network := n.networks[name]
+ if network == nil {
+ multiErr = multierror.Append(multiErr, errors.Wrapf(types.ErrNoSuchNetwork, "network %s", name))
+ continue
+ }
+ cniConfList = network.cniNet
+ }
+
+ err = n.cniConf.DelNetworkList(context.Background(), cniConfList, rt)
+ if err != nil {
+ multiErr = multierror.Append(multiErr, err)
+ }
+ }
+ return multiErr.ErrorOrNil()
+}
+
+func getCachedNetworkConfig(cniConf *libcni.CNIConfig, name string, rt *libcni.RuntimeConf) (*libcni.NetworkConfigList, *libcni.RuntimeConf, error) {
+ cniConfList := &libcni.NetworkConfigList{
+ Name: name,
+ }
+ confBytes, rt, err := cniConf.GetNetworkListCachedConfig(cniConfList, rt)
+ if err != nil {
+ return nil, nil, err
+ } else if confBytes == nil {
+ return nil, nil, errors.Errorf("network %s not found in CNI cache", name)
+ }
+
+ cniConfList, err = libcni.ConfListFromBytes(confBytes)
+ if err != nil {
+ return nil, nil, err
+ }
+ return cniConfList, rt, nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/bridge.go b/vendor/github.com/containers/common/libnetwork/internal/util/bridge.go
new file mode 100644
index 000000000..27ad0a4fb
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/internal/util/bridge.go
@@ -0,0 +1,68 @@
+package util
+
+import (
+ "net"
+
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/libnetwork/util"
+ pkgutil "github.com/containers/common/pkg/util"
+ "github.com/pkg/errors"
+)
+
+func CreateBridge(n NetUtil, network *types.Network, usedNetworks []*net.IPNet) error {
+ if network.NetworkInterface != "" {
+ bridges := GetBridgeInterfaceNames(n)
+ if pkgutil.StringInSlice(network.NetworkInterface, bridges) {
+ return errors.Errorf("bridge name %s already in use", network.NetworkInterface)
+ }
+ if !types.NameRegex.MatchString(network.NetworkInterface) {
+ return errors.Wrapf(types.RegexError, "bridge name %s invalid", network.NetworkInterface)
+ }
+ } else {
+ var err error
+ network.NetworkInterface, err = GetFreeDeviceName(n)
+ if err != nil {
+ return err
+ }
+ }
+
+ if network.IPAMOptions["driver"] != types.DHCPIPAMDriver {
+ if len(network.Subnets) == 0 {
+ freeSubnet, err := GetFreeIPv4NetworkSubnet(usedNetworks)
+ if err != nil {
+ return err
+ }
+ network.Subnets = append(network.Subnets, *freeSubnet)
+ }
+ // ipv6 enabled means dual stack, check if we already have
+ // a ipv4 or ipv6 subnet and add one if not.
+ if network.IPv6Enabled {
+ ipv4 := false
+ ipv6 := false
+ for _, subnet := range network.Subnets {
+ if util.IsIPv6(subnet.Subnet.IP) {
+ ipv6 = true
+ }
+ if util.IsIPv4(subnet.Subnet.IP) {
+ ipv4 = true
+ }
+ }
+ if !ipv4 {
+ freeSubnet, err := GetFreeIPv4NetworkSubnet(usedNetworks)
+ if err != nil {
+ return err
+ }
+ network.Subnets = append(network.Subnets, *freeSubnet)
+ }
+ if !ipv6 {
+ freeSubnet, err := GetFreeIPv6NetworkSubnet(usedNetworks)
+ if err != nil {
+ return err
+ }
+ network.Subnets = append(network.Subnets, *freeSubnet)
+ }
+ }
+ network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
+ }
+ return nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/create.go b/vendor/github.com/containers/common/libnetwork/internal/util/create.go
new file mode 100644
index 000000000..ccb0f001a
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/internal/util/create.go
@@ -0,0 +1,41 @@
+package util
+
+import (
+ "github.com/containers/common/libnetwork/types"
+ "github.com/pkg/errors"
+)
+
+func CommonNetworkCreate(n NetUtil, network *types.Network) error {
+ if network.Labels == nil {
+ network.Labels = map[string]string{}
+ }
+ if network.Options == nil {
+ network.Options = map[string]string{}
+ }
+ if network.IPAMOptions == nil {
+ network.IPAMOptions = map[string]string{}
+ }
+
+ var name string
+ var err error
+ // validate the name when given
+ if network.Name != "" {
+ if !types.NameRegex.MatchString(network.Name) {
+ return errors.Wrapf(types.RegexError, "network name %s invalid", network.Name)
+ }
+ if _, err := n.Network(network.Name); err == nil {
+ return errors.Wrapf(types.ErrNetworkExists, "network name %s already used", network.Name)
+ }
+ } else {
+ name, err = GetFreeDeviceName(n)
+ if err != nil {
+ return err
+ }
+ network.Name = name
+ // also use the name as interface name when we create a bridge network
+ if network.Driver == types.BridgeNetworkDriver && network.NetworkInterface == "" {
+ network.NetworkInterface = name
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/interface.go b/vendor/github.com/containers/common/libnetwork/internal/util/interface.go
new file mode 100644
index 000000000..650fcb193
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/internal/util/interface.go
@@ -0,0 +1,19 @@
+package util
+
+import "github.com/containers/common/libnetwork/types"
+
+// This is a helper package to allow code sharing between the different
+// network interfaces.
+
+// NetUtil is a helper interface which all network interfaces should implement to allow easy code sharing
+type NetUtil interface {
+ // ForEach eaxecutes the given function for each network
+ ForEach(func(types.Network))
+ // Len returns the number of networks
+ Len() int
+ // DefaultInterfaceName return the default interface name, this will be suffixed by a number
+ DefaultInterfaceName() string
+ // Network returns the network with the given name or ID.
+ // It returns an error if the network is not found
+ Network(nameOrID string) (*types.Network, error)
+}
diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/interfaces.go b/vendor/github.com/containers/common/libnetwork/internal/util/interfaces.go
new file mode 100644
index 000000000..20819f756
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/internal/util/interfaces.go
@@ -0,0 +1,34 @@
+package util
+
+import "net"
+
+// getLiveNetworkSubnets returns a slice of subnets representing what the system
+// has defined as network interfaces
+func getLiveNetworkSubnets() ([]*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 interface names 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
+}
diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/ip.go b/vendor/github.com/containers/common/libnetwork/internal/util/ip.go
new file mode 100644
index 000000000..8f00a2a55
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/internal/util/ip.go
@@ -0,0 +1,70 @@
+package util
+
+import (
+ "crypto/rand"
+ "net"
+
+ "github.com/pkg/errors"
+)
+
+func incByte(subnet *net.IPNet, idx int, shift uint) error {
+ if idx < 0 {
+ return errors.New("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, errors.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
+}
+
+func NetworkIntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) bool {
+ for _, nw := range networklist {
+ if networkIntersect(n, nw) {
+ return true
+ }
+ }
+ return false
+}
+
+func networkIntersect(n1, n2 *net.IPNet) bool {
+ return n2.Contains(n1.IP) || n1.Contains(n2.IP)
+}
+
+// getRandomIPv6Subnet returns a random internal ipv6 subnet as described in RFC3879.
+func getRandomIPv6Subnet() (net.IPNet, error) {
+ ip := make(net.IP, 8, net.IPv6len)
+ // read 8 random bytes
+ _, err := rand.Read(ip)
+ if err != nil {
+ return net.IPNet{}, err
+ }
+ // first byte must be FD as per RFC3879
+ ip[0] = 0xfd
+ // add 8 zero bytes
+ ip = append(ip, make([]byte, 8)...)
+ return net.IPNet{IP: ip, Mask: net.CIDRMask(64, 128)}, nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/parse.go b/vendor/github.com/containers/common/libnetwork/internal/util/parse.go
new file mode 100644
index 000000000..1f68df0bb
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/internal/util/parse.go
@@ -0,0 +1,37 @@
+package util
+
+import (
+ "strconv"
+
+ "github.com/pkg/errors"
+)
+
+// 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("mtu %d 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
+ }
+ v, err := strconv.Atoi(vlan)
+ if err != nil {
+ return 0, err
+ }
+ if v < 0 || v > 4094 {
+ return 0, errors.Errorf("vlan ID %d must be between 0 and 4094", v)
+ }
+ return v, nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/util.go b/vendor/github.com/containers/common/libnetwork/internal/util/util.go
new file mode 100644
index 000000000..8138d9fbc
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/internal/util/util.go
@@ -0,0 +1,123 @@
+package util
+
+import (
+ "errors"
+ "fmt"
+ "net"
+
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/pkg/util"
+ "github.com/sirupsen/logrus"
+)
+
+// GetBridgeInterfaceNames returns all bridge interface names
+// already used by network configs
+func GetBridgeInterfaceNames(n NetUtil) []string {
+ names := make([]string, 0, n.Len())
+ n.ForEach(func(net types.Network) {
+ if net.Driver == types.BridgeNetworkDriver {
+ names = append(names, net.NetworkInterface)
+ }
+ })
+ return names
+}
+
+// GetUsedNetworkNames returns all network names already used
+// by network configs
+func GetUsedNetworkNames(n NetUtil) []string {
+ names := make([]string, 0, n.Len())
+ n.ForEach(func(net types.Network) {
+ if net.Driver == types.BridgeNetworkDriver {
+ names = append(names, net.NetworkInterface)
+ }
+ })
+ return names
+}
+
+// GetFreeDeviceName returns a free device name which can
+// be used for new configs as name and bridge interface name.
+// The base name is suffixed by a number
+func GetFreeDeviceName(n NetUtil) (string, error) {
+ bridgeNames := GetBridgeInterfaceNames(n)
+ netNames := GetUsedNetworkNames(n)
+ liveInterfaces, err := GetLiveNetworkNames()
+ if err != nil {
+ return "", nil
+ }
+ names := make([]string, 0, len(bridgeNames)+len(netNames)+len(liveInterfaces))
+ names = append(names, bridgeNames...)
+ names = append(names, netNames...)
+ names = append(names, liveInterfaces...)
+ // FIXME: Is a limit fine?
+ // Start by 1, 0 is reserved for the default network
+ for i := 1; i < 1000000; i++ {
+ deviceName := fmt.Sprintf("%s%d", n.DefaultInterfaceName(), i)
+ if !util.StringInSlice(deviceName, names) {
+ logrus.Debugf("found free device name %s", deviceName)
+ return deviceName, nil
+ }
+ }
+ return "", errors.New("could not find free device name, to many iterations")
+}
+
+// GetUsedSubnets returns a list of all used subnets by network
+// configs and interfaces on the host.
+func GetUsedSubnets(n NetUtil) ([]*net.IPNet, error) {
+ // first, load all used subnets from network configs
+ subnets := make([]*net.IPNet, 0, n.Len())
+ n.ForEach(func(n types.Network) {
+ for i := range n.Subnets {
+ subnets = append(subnets, &n.Subnets[i].Subnet.IPNet)
+ }
+ })
+ // second, load networks from the current system
+ liveSubnets, err := getLiveNetworkSubnets()
+ if err != nil {
+ return nil, err
+ }
+ return append(subnets, liveSubnets...), nil
+}
+
+// GetFreeIPv4NetworkSubnet returns a unused ipv4 subnet
+func GetFreeIPv4NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) {
+ // the default podman network is 10.88.0.0/16
+ // start locking for free /24 networks
+ network := &net.IPNet{
+ IP: net.IP{10, 89, 0, 0},
+ Mask: net.IPMask{255, 255, 255, 0},
+ }
+
+ // TODO: make sure to not use public subnets
+ for {
+ if intersectsConfig := NetworkIntersectsWithNetworks(network, usedNetworks); !intersectsConfig {
+ logrus.Debugf("found free ipv4 network subnet %s", network.String())
+ return &types.Subnet{
+ Subnet: types.IPNet{IPNet: *network},
+ }, nil
+ }
+ var err error
+ network, err = NextSubnet(network)
+ if err != nil {
+ return nil, err
+ }
+ }
+}
+
+// GetFreeIPv6NetworkSubnet returns a unused ipv6 subnet
+func GetFreeIPv6NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) {
+ // FIXME: Is 10000 fine as limit? We should prevent an endless loop.
+ for i := 0; i < 10000; i++ {
+ // RFC4193: Choose the ipv6 subnet random and NOT sequentially.
+ network, err := getRandomIPv6Subnet()
+ if err != nil {
+ return nil, err
+ }
+ if intersectsConfig := NetworkIntersectsWithNetworks(&network, usedNetworks); !intersectsConfig {
+ logrus.Debugf("found free ipv6 network subnet %s", network.String())
+ return &types.Subnet{
+ Subnet: types.IPNet{IPNet: network},
+ }, nil
+ }
+ }
+ return nil, errors.New("failed to get random ipv6 subnet")
+}
diff --git a/vendor/github.com/containers/common/libnetwork/internal/util/validate.go b/vendor/github.com/containers/common/libnetwork/internal/util/validate.go
new file mode 100644
index 000000000..bfc5e2247
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/internal/util/validate.go
@@ -0,0 +1,124 @@
+package util
+
+import (
+ "net"
+
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/libnetwork/util"
+ "github.com/pkg/errors"
+)
+
+// ValidateSubnet will validate a given Subnet. It checks if the
+// given gateway and lease range are part of this subnet. If the
+// gateway is empty and addGateway is true it will get the first
+// available ip in the subnet assigned.
+func ValidateSubnet(s *types.Subnet, addGateway bool, usedNetworks []*net.IPNet) error {
+ if s == nil {
+ return errors.New("subnet is nil")
+ }
+ if s.Subnet.IP == nil {
+ return errors.New("subnet ip is nil")
+ }
+
+ // Reparse to ensure subnet is valid.
+ // Do not use types.ParseCIDR() because we want the ip to be
+ // the network address and not a random ip in the subnet.
+ _, n, err := net.ParseCIDR(s.Subnet.String())
+ if err != nil {
+ return errors.Wrap(err, "subnet invalid")
+ }
+
+ // check that the new subnet does not conflict with existing ones
+ if NetworkIntersectsWithNetworks(n, usedNetworks) {
+ return errors.Errorf("subnet %s is already used on the host or by another config", n.String())
+ }
+
+ s.Subnet = types.IPNet{IPNet: *n}
+ if s.Gateway != nil {
+ if !s.Subnet.Contains(s.Gateway) {
+ return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
+ }
+ util.NormalizeIP(&s.Gateway)
+ } else if addGateway {
+ ip, err := util.FirstIPInSubnet(n)
+ if err != nil {
+ return err
+ }
+ s.Gateway = ip
+ }
+
+ if s.LeaseRange != nil {
+ if s.LeaseRange.StartIP != nil {
+ if !s.Subnet.Contains(s.LeaseRange.StartIP) {
+ return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
+ }
+ util.NormalizeIP(&s.LeaseRange.StartIP)
+ }
+ if s.LeaseRange.EndIP != nil {
+ if !s.Subnet.Contains(s.LeaseRange.EndIP) {
+ return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
+ }
+ util.NormalizeIP(&s.LeaseRange.EndIP)
+ }
+ }
+ return nil
+}
+
+// ValidateSubnets will validate the subnets for this network.
+// It also sets the gateway if the gateway is empty and it sets
+// IPv6Enabled to true if at least one subnet is ipv6.
+func ValidateSubnets(network *types.Network, usedNetworks []*net.IPNet) error {
+ for i := range network.Subnets {
+ err := ValidateSubnet(&network.Subnets[i], !network.Internal, usedNetworks)
+ if err != nil {
+ return err
+ }
+ if util.IsIPv6(network.Subnets[i].Subnet.IP) {
+ network.IPv6Enabled = true
+ }
+ }
+ return nil
+}
+
+func ValidateSetupOptions(n NetUtil, namespacePath string, options types.SetupOptions) error {
+ if namespacePath == "" {
+ return errors.New("namespacePath is empty")
+ }
+ if options.ContainerID == "" {
+ return errors.New("ContainerID is empty")
+ }
+ if len(options.Networks) == 0 {
+ return errors.New("must specify at least one network")
+ }
+ for name, netOpts := range options.Networks {
+ netOpts := netOpts
+ network, err := n.Network(name)
+ if err != nil {
+ return err
+ }
+ err = validatePerNetworkOpts(network, &netOpts)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// validatePerNetworkOpts checks that all given static ips are in a subnet on this network
+func validatePerNetworkOpts(network *types.Network, netOpts *types.PerNetworkOptions) error {
+ if netOpts.InterfaceName == "" {
+ return errors.Errorf("interface name on network %s is empty", network.Name)
+ }
+ if network.IPAMOptions["driver"] == types.HostLocalIPAMDriver {
+ outer:
+ for _, ip := range netOpts.StaticIPs {
+ for _, s := range network.Subnets {
+ if s.Subnet.Contains(ip) {
+ continue outer
+ }
+ }
+ return errors.Errorf("requested static ip %s not in any subnet on network %s", ip.String(), network.Name)
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/netavark/config.go b/vendor/github.com/containers/common/libnetwork/netavark/config.go
new file mode 100644
index 000000000..6d2daf299
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/netavark/config.go
@@ -0,0 +1,252 @@
+// +build linux
+
+package netavark
+
+import (
+ "encoding/json"
+ "net"
+ "os"
+ "path/filepath"
+ "time"
+
+ internalutil "github.com/containers/common/libnetwork/internal/util"
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/pkg/util"
+ "github.com/containers/storage/pkg/stringid"
+ "github.com/pkg/errors"
+)
+
+// NetworkCreate will take a partial filled Network and fill the
+// missing fields. It creates the Network and returns the full Network.
+// nolint:gocritic
+func (n *netavarkNetwork) NetworkCreate(net types.Network) (types.Network, error) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return types.Network{}, err
+ }
+ network, err := n.networkCreate(&net, false)
+ if err != nil {
+ return types.Network{}, err
+ }
+ // add the new network to the map
+ n.networks[network.Name] = network
+ return *network, nil
+}
+
+func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bool) (*types.Network, error) {
+ // if no driver is set use the default one
+ if newNetwork.Driver == "" {
+ newNetwork.Driver = types.DefaultNetworkDriver
+ }
+ if !defaultNet {
+ // FIXME: Should we use a different type for network create without the ID field?
+ // the caller is not allowed to set a specific ID
+ if newNetwork.ID != "" {
+ return nil, errors.Wrap(types.ErrInvalidArg, "ID can not be set for network create")
+ }
+
+ // generate random network ID
+ var i int
+ for i = 0; i < 1000; i++ {
+ id := stringid.GenerateNonCryptoID()
+ if _, err := n.getNetwork(id); err != nil {
+ newNetwork.ID = id
+ break
+ }
+ }
+ if i == 1000 {
+ return nil, errors.New("failed to create random network ID")
+ }
+ }
+
+ err := internalutil.CommonNetworkCreate(n, newNetwork)
+ if err != nil {
+ return nil, err
+ }
+
+ // Only get the used networks for validation if we do not create the default network.
+ // The default network should not be validated against used subnets, we have to ensure
+ // that this network can always be created even when a subnet is already used on the host.
+ // This could happen if you run a container on this net, then the cni interface will be
+ // created on the host and "block" this subnet from being used again.
+ // Therefore the next podman command tries to create the default net again and it would
+ // fail because it thinks the network is used on the host.
+ var usedNetworks []*net.IPNet
+ if !defaultNet {
+ usedNetworks, err = internalutil.GetUsedSubnets(n)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ switch newNetwork.Driver {
+ case types.BridgeNetworkDriver:
+ err = internalutil.CreateBridge(n, newNetwork, usedNetworks)
+ if err != nil {
+ return nil, err
+ }
+ // validate the given options, we do not need them but just check to make sure they are valid
+ for key, value := range newNetwork.Options {
+ switch key {
+ case "mtu":
+ _, err = internalutil.ParseMTU(value)
+ if err != nil {
+ return nil, err
+ }
+
+ case "vlan":
+ _, err = internalutil.ParseVlan(value)
+ if err != nil {
+ return nil, err
+ }
+
+ default:
+ return nil, errors.Errorf("unsupported bridge network option %s", key)
+ }
+ }
+ case types.MacVLANNetworkDriver:
+ err = createMacvlan(newNetwork)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ return nil, errors.Wrapf(types.ErrInvalidArg, "unsupported driver %s", newNetwork.Driver)
+ }
+
+ err = internalutil.ValidateSubnets(newNetwork, usedNetworks)
+ if err != nil {
+ return nil, err
+ }
+
+ // FIXME: If we have a working solution for internal networks with dns this check should be removed.
+ if newNetwork.DNSEnabled && newNetwork.Internal {
+ return nil, errors.New("cannot set internal and dns enabled")
+ }
+
+ newNetwork.Created = time.Now()
+
+ if !defaultNet {
+ confPath := filepath.Join(n.networkConfigDir, newNetwork.Name+".json")
+ f, err := os.Create(confPath)
+ if err != nil {
+ return nil, err
+ }
+ enc := json.NewEncoder(f)
+ enc.SetIndent("", " ")
+ err = enc.Encode(newNetwork)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return newNetwork, nil
+}
+
+func createMacvlan(network *types.Network) error {
+ if network.Internal {
+ return errors.New("internal is not supported with macvlan")
+ }
+ if network.NetworkInterface != "" {
+ interfaceNames, err := internalutil.GetLiveNetworkNames()
+ if err != nil {
+ return err
+ }
+ if !util.StringInSlice(network.NetworkInterface, interfaceNames) {
+ return errors.Errorf("parent interface %s does not exist", network.NetworkInterface)
+ }
+ }
+ if len(network.Subnets) == 0 {
+ return errors.Errorf("macvlan driver needs at least one subnet specified, DHCP is not supported with netavark")
+ }
+ network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
+
+ // validate the given options, we do not need them but just check to make sure they are valid
+ for key, value := range network.Options {
+ switch key {
+ case "mode":
+ if !util.StringInSlice(value, types.ValidMacVLANModes) {
+ return errors.Errorf("unknown macvlan mode %q", value)
+ }
+ case "mtu":
+ _, err := internalutil.ParseMTU(value)
+ if err != nil {
+ return err
+ }
+ default:
+ return errors.Errorf("unsupported macvlan network option %s", key)
+ }
+ }
+ return nil
+}
+
+// NetworkRemove will remove the Network with the given name or ID.
+// It does not ensure that the network is unused.
+func (n *netavarkNetwork) NetworkRemove(nameOrID string) error {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return err
+ }
+
+ network, err := n.getNetwork(nameOrID)
+ if err != nil {
+ return err
+ }
+
+ // Removing the default network is not allowed.
+ if network.Name == n.defaultNetwork {
+ return errors.Errorf("default network %s cannot be removed", n.defaultNetwork)
+ }
+
+ file := filepath.Join(n.networkConfigDir, network.Name+".json")
+ // make sure to not error for ErrNotExist
+ if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ delete(n.networks, network.Name)
+ return nil
+}
+
+// NetworkList will return all known Networks. Optionally you can
+// supply a list of filter functions. Only if a network matches all
+// functions it is returned.
+func (n *netavarkNetwork) NetworkList(filters ...types.FilterFunc) ([]types.Network, error) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return nil, err
+ }
+
+ networks := make([]types.Network, 0, len(n.networks))
+outer:
+ for _, net := range n.networks {
+ for _, filter := range filters {
+ // All filters have to match, if one does not match we can skip to the next network.
+ if !filter(*net) {
+ continue outer
+ }
+ }
+ networks = append(networks, *net)
+ }
+ return networks, nil
+}
+
+// NetworkInspect will return the Network with the given name or ID.
+func (n *netavarkNetwork) NetworkInspect(nameOrID string) (types.Network, error) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return types.Network{}, err
+ }
+
+ network, err := n.getNetwork(nameOrID)
+ if err != nil {
+ return types.Network{}, err
+ }
+ return *network, nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/netavark/const.go b/vendor/github.com/containers/common/libnetwork/netavark/const.go
new file mode 100644
index 000000000..9709315c6
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/netavark/const.go
@@ -0,0 +1,5 @@
+// +build linux
+
+package netavark
+
+const defaultBridgeName = "podman"
diff --git a/vendor/github.com/containers/common/libnetwork/netavark/exec.go b/vendor/github.com/containers/common/libnetwork/netavark/exec.go
new file mode 100644
index 000000000..69466a423
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/netavark/exec.go
@@ -0,0 +1,161 @@
+// +build linux
+
+package netavark
+
+import (
+ "encoding/json"
+ "errors"
+ "io"
+ "os"
+ "os/exec"
+ "strconv"
+
+ "github.com/sirupsen/logrus"
+)
+
+type netavarkError struct {
+ exitCode int
+ // Set the json key to "error" so we can directly unmarshal into this struct
+ Msg string `json:"error"`
+ err error
+}
+
+func (e *netavarkError) Error() string {
+ ec := ""
+ // only add the exit code the the error message if we have at least info log level
+ // the normal user does not need to care about the number
+ if e.exitCode > 0 && logrus.IsLevelEnabled(logrus.InfoLevel) {
+ ec = " (exit code " + strconv.Itoa(e.exitCode) + ")"
+ }
+ msg := "netavark" + ec
+ if len(msg) > 0 {
+ msg += ": " + e.Msg
+ }
+ if e.err != nil {
+ msg += ": " + e.err.Error()
+ }
+ return msg
+}
+
+func (e *netavarkError) Unwrap() error {
+ return e.err
+}
+
+func newNetavarkError(msg string, err error) error {
+ return &netavarkError{
+ Msg: msg,
+ err: err,
+ }
+}
+
+// Type to implement io.Writer interface
+// This will write the logrus at info level
+type logrusNetavarkWriter struct{}
+
+func (l *logrusNetavarkWriter) Write(b []byte) (int, error) {
+ logrus.Info("netavark: ", string(b))
+ return len(b), nil
+}
+
+// getRustLogEnv returns the RUST_LOG env var based on the current logrus level
+func getRustLogEnv() string {
+ level := logrus.GetLevel().String()
+ // rust env_log uses warn instead of warning
+ if level == "warning" {
+ level = "warn"
+ }
+ // the rust netlink library is very verbose
+ // make sure to only log netavark logs
+ return "RUST_LOG=netavark=" + level
+}
+
+// execNetavark will execute netavark with the following arguments
+// It takes the path to the binary, the list of args and an interface which is
+// marshaled to json and send via stdin to netavark. The result interface is
+// used to marshal the netavark output into it. This can be nil.
+// All errors return by this function should be of the type netavarkError
+// to provide a helpful error message.
+func (n *netavarkNetwork) execNetavark(args []string, stdin, result interface{}) error {
+ stdinR, stdinW, err := os.Pipe()
+ if err != nil {
+ return newNetavarkError("failed to create stdin pipe", err)
+ }
+ stdinWClosed := false
+ defer func() {
+ stdinR.Close()
+ if !stdinWClosed {
+ stdinW.Close()
+ }
+ }()
+
+ stdoutR, stdoutW, err := os.Pipe()
+ if err != nil {
+ return newNetavarkError("failed to create stdout pipe", err)
+ }
+ stdoutWClosed := false
+ defer func() {
+ stdoutR.Close()
+ if !stdoutWClosed {
+ stdoutW.Close()
+ }
+ }()
+
+ // connect stderr to the podman stderr for logging
+ var logWriter io.Writer = os.Stderr
+ if n.syslog {
+ // connect logrus to stderr as well so that the logs will be written to the syslog as well
+ logWriter = io.MultiWriter(logWriter, &logrusNetavarkWriter{})
+ }
+
+ cmd := exec.Command(n.netavarkBinary, args...)
+ // connect the pipes to stdin and stdout
+ cmd.Stdin = stdinR
+ cmd.Stdout = stdoutW
+ cmd.Stderr = logWriter
+ // set the netavark log level to the same as the podman
+ cmd.Env = append(os.Environ(), getRustLogEnv())
+ // if we run with debug log level lets also set RUST_BACKTRACE=1 so we can get the full stack trace in case of panics
+ if logrus.IsLevelEnabled(logrus.DebugLevel) {
+ cmd.Env = append(cmd.Env, "RUST_BACKTRACE=1")
+ }
+
+ err = cmd.Start()
+ if err != nil {
+ return newNetavarkError("failed to start process", err)
+ }
+ err = json.NewEncoder(stdinW).Encode(stdin)
+ // we have to close stdinW so netavark gets the EOF and does not hang forever
+ stdinW.Close()
+ stdinWClosed = true
+ if err != nil {
+ return newNetavarkError("failed to encode stdin data", err)
+ }
+
+ dec := json.NewDecoder(stdoutR)
+
+ err = cmd.Wait()
+ // we have to close stdoutW so we can decode the json without hanging forever
+ stdoutW.Close()
+ stdoutWClosed = true
+ if err != nil {
+ exitError := &exec.ExitError{}
+ if errors.As(err, &exitError) {
+ ne := &netavarkError{}
+ // lets disallow unknown fields to make sure we do not get some unexpected stuff
+ dec.DisallowUnknownFields()
+ // this will unmarshal the error message into the error struct
+ ne.err = dec.Decode(ne)
+ ne.exitCode = exitError.ExitCode()
+ return ne
+ }
+ return newNetavarkError("unexpected failure during execution", err)
+ }
+
+ if result != nil {
+ err = dec.Decode(result)
+ if err != nil {
+ return newNetavarkError("failed to decode result", err)
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/netavark/ipam.go b/vendor/github.com/containers/common/libnetwork/netavark/ipam.go
new file mode 100644
index 000000000..f99d099ca
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/netavark/ipam.go
@@ -0,0 +1,372 @@
+// +build linux
+
+package netavark
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/libnetwork/util"
+ "github.com/pkg/errors"
+ "go.etcd.io/bbolt"
+)
+
+// IPAM boltdb structure
+// Each network has their own bucket with the network name as bucket key.
+// Inside the network bucket there is an ID bucket which maps the container ID (key)
+// to a json array of ip addresses (value).
+// The network bucket also has a bucket for each subnet, the subnet is used as key.
+// Inside the subnet bucket an ip is used as key and the container ID as value.
+
+const (
+ idBucket = "ids"
+ // lastIP this is used as key to store the last allocated ip
+ // note that this string should not be 4 or 16 byte long
+ lastIP = "lastIP"
+)
+
+var (
+ idBucketKey = []byte(idBucket)
+ lastIPKey = []byte(lastIP)
+)
+
+type ipamError struct {
+ msg string
+ cause error
+}
+
+func (e *ipamError) Error() string {
+ msg := "IPAM error"
+ if e.msg != "" {
+ msg += ": " + e.msg
+ }
+ if e.cause != nil {
+ msg += ": " + e.cause.Error()
+ }
+ return msg
+}
+
+func newIPAMError(cause error, msg string, args ...interface{}) *ipamError {
+ return &ipamError{
+ msg: fmt.Sprintf(msg, args...),
+ cause: cause,
+ }
+}
+
+// openDB will open the ipam database
+// Note that the caller has to Close it.
+func (n *netavarkNetwork) openDB() (*bbolt.DB, error) {
+ // linter complains about the octal value
+ // nolint:gocritic
+ db, err := bbolt.Open(n.ipamDBPath, 0600, nil)
+ if err != nil {
+ return nil, newIPAMError(err, "failed to open database %s", n.ipamDBPath)
+ }
+ return db, nil
+}
+
+// allocIPs will allocate ips for the the container. It will change the
+// NetworkOptions in place. When static ips are given it will validate
+// that these are free to use and will allocate them to the container.
+func (n *netavarkNetwork) allocIPs(opts *types.NetworkOptions) error {
+ db, err := n.openDB()
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ err = db.Update(func(tx *bbolt.Tx) error {
+ for netName, netOpts := range opts.Networks {
+ network := n.networks[netName]
+ if network == nil {
+ return newIPAMError(nil, "could not find network %q", netName)
+ }
+
+ // check if we have to alloc ips
+ if !requiresIPAMAlloc(network) {
+ continue
+ }
+
+ // create/get network bucket
+ netBkt, err := tx.CreateBucketIfNotExists([]byte(netName))
+ if err != nil {
+ return newIPAMError(err, "failed to create/get network bucket for network %s", netName)
+ }
+
+ // requestIPs is the list of ips which should be used for this container
+ requestIPs := make([]net.IP, 0, len(network.Subnets))
+
+ for i := range network.Subnets {
+ subnetBkt, err := netBkt.CreateBucketIfNotExists([]byte(network.Subnets[i].Subnet.String()))
+ if err != nil {
+ return newIPAMError(err, "failed to create/get subnet bucket for network %s", netName)
+ }
+
+ // search for a static ip which matches the current subnet
+ // in this case the user wants this one and we should not assign a free one
+ var ip net.IP
+ for _, staticIP := range netOpts.StaticIPs {
+ if network.Subnets[i].Subnet.Contains(staticIP) {
+ ip = staticIP
+ break
+ }
+ }
+
+ // when static ip is requested for this network
+ if ip != nil {
+ // convert to 4 byte ipv4 if needed
+ util.NormalizeIP(&ip)
+ id := subnetBkt.Get(ip)
+ if id != nil {
+ return newIPAMError(nil, "requested ip address %s is already allocated to container ID %s", ip.String(), string(id))
+ }
+ } else {
+ ip, err = getFreeIPFromBucket(subnetBkt, &network.Subnets[i])
+ if err != nil {
+ return err
+ }
+ err = subnetBkt.Put(lastIPKey, ip)
+ if err != nil {
+ return newIPAMError(err, "failed to store last ip in database")
+ }
+ }
+
+ err = subnetBkt.Put(ip, []byte(opts.ContainerID))
+ if err != nil {
+ return newIPAMError(err, "failed to store ip in database")
+ }
+
+ requestIPs = append(requestIPs, ip)
+ }
+
+ idsBucket, err := netBkt.CreateBucketIfNotExists(idBucketKey)
+ if err != nil {
+ return newIPAMError(err, "failed to create/get id bucket for network %s", netName)
+ }
+
+ ipsBytes, err := json.Marshal(requestIPs)
+ if err != nil {
+ return newIPAMError(err, "failed to marshal ips")
+ }
+
+ err = idsBucket.Put([]byte(opts.ContainerID), ipsBytes)
+ if err != nil {
+ return newIPAMError(err, "failed to store ips in database")
+ }
+
+ netOpts.StaticIPs = requestIPs
+ opts.Networks[netName] = netOpts
+ }
+ return nil
+ })
+ return err
+}
+
+func getFreeIPFromBucket(bucket *bbolt.Bucket, subnet *types.Subnet) (net.IP, error) {
+ var rangeStart net.IP
+ var rangeEnd net.IP
+ if subnet.LeaseRange != nil {
+ rangeStart = subnet.LeaseRange.StartIP
+ rangeEnd = subnet.LeaseRange.EndIP
+ }
+
+ if rangeStart == nil {
+ // let start with the first ip in subnet
+ rangeStart = util.NextIP(subnet.Subnet.IP)
+ }
+
+ if rangeEnd == nil {
+ lastIP, err := util.LastIPInSubnet(&subnet.Subnet.IPNet)
+ // this error should never happen but lets check anyways to prevent panics
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to get lastIP")
+ }
+ // ipv4 uses the last ip in a subnet for broadcast so we cannot use it
+ if util.IsIPv4(lastIP) {
+ lastIP = util.PrevIP(lastIP)
+ }
+ rangeEnd = lastIP
+ }
+
+ lastIPByte := bucket.Get(lastIPKey)
+ curIP := net.IP(lastIPByte)
+ if curIP == nil {
+ curIP = rangeStart
+ } else {
+ curIP = util.NextIP(curIP)
+ }
+
+ // store the start ip to make sure we know when we looped over all available ips
+ startIP := curIP
+
+ for {
+ // skip the gateway
+ if subnet.Gateway != nil {
+ if util.Cmp(curIP, subnet.Gateway) == 0 {
+ curIP = util.NextIP(curIP)
+ continue
+ }
+ }
+
+ // if we are at the end we need to jump back to the start ip
+ if util.Cmp(curIP, rangeEnd) > 0 {
+ if util.Cmp(rangeStart, startIP) == 0 {
+ return nil, newIPAMError(nil, "failed to find free IP in range: %s - %s", rangeStart.String(), rangeEnd.String())
+ }
+ curIP = rangeStart
+ continue
+ }
+
+ // check if ip is already used by another container
+ // if not return it
+ if bucket.Get(curIP) == nil {
+ return curIP, nil
+ }
+
+ curIP = util.NextIP(curIP)
+
+ if util.Cmp(curIP, startIP) == 0 {
+ return nil, newIPAMError(nil, "failed to find free IP in range: %s - %s", rangeStart.String(), rangeEnd.String())
+ }
+ }
+}
+
+// getAssignedIPs will read the ipam database and will fill in the used ips for this container.
+// It will change the NetworkOptions in place.
+func (n *netavarkNetwork) getAssignedIPs(opts *types.NetworkOptions) error {
+ db, err := n.openDB()
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ err = db.View(func(tx *bbolt.Tx) error {
+ for netName, netOpts := range opts.Networks {
+ network := n.networks[netName]
+ if network == nil {
+ return newIPAMError(nil, "could not find network %q", netName)
+ }
+
+ // check if we have to alloc ips
+ if !requiresIPAMAlloc(network) {
+ continue
+ }
+ // get network bucket
+ netBkt := tx.Bucket([]byte(netName))
+ if netBkt == nil {
+ return newIPAMError(nil, "failed to get network bucket for network %s", netName)
+ }
+
+ idBkt := netBkt.Bucket(idBucketKey)
+ if idBkt == nil {
+ return newIPAMError(nil, "failed to get id bucket for network %s", netName)
+ }
+
+ ipJSON := idBkt.Get([]byte(opts.ContainerID))
+ if ipJSON == nil {
+ return newIPAMError(nil, "failed to get ips for container ID %s on network %s", opts.ContainerID, netName)
+ }
+
+ // assignedIPs is the list of ips which should be used for this container
+ assignedIPs := make([]net.IP, 0, len(network.Subnets))
+
+ err = json.Unmarshal(ipJSON, &assignedIPs)
+ if err != nil {
+ return newIPAMError(err, "failed to unmarshal ips from database")
+ }
+
+ for i := range assignedIPs {
+ util.NormalizeIP(&assignedIPs[i])
+ }
+
+ netOpts.StaticIPs = assignedIPs
+ opts.Networks[netName] = netOpts
+ }
+ return nil
+ })
+ return err
+}
+
+// deallocIPs will release the ips in the network options from the DB so that
+// they can be reused by other containers. It expects that the network options
+// are already filled with the correct ips. Use getAssignedIPs() for this.
+func (n *netavarkNetwork) deallocIPs(opts *types.NetworkOptions) error {
+ db, err := n.openDB()
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ err = db.Update(func(tx *bbolt.Tx) error {
+ for netName, netOpts := range opts.Networks {
+ network := n.networks[netName]
+ if network == nil {
+ return newIPAMError(nil, "could not find network %q", netName)
+ }
+
+ // check if we have to alloc ips
+ if !requiresIPAMAlloc(network) {
+ continue
+ }
+ // get network bucket
+ netBkt := tx.Bucket([]byte(netName))
+ if netBkt == nil {
+ return newIPAMError(nil, "failed to get network bucket for network %s", netName)
+ }
+
+ for _, subnet := range network.Subnets {
+ subnetBkt := netBkt.Bucket([]byte(subnet.Subnet.String()))
+ if subnetBkt == nil {
+ return newIPAMError(nil, "failed to get subnet bucket for network %s", netName)
+ }
+
+ // search for a static ip which matches the current subnet
+ // in this case the user wants this one and we should not assign a free one
+ var ip net.IP
+ for _, staticIP := range netOpts.StaticIPs {
+ if subnet.Subnet.Contains(staticIP) {
+ ip = staticIP
+ break
+ }
+ }
+ if ip == nil {
+ return newIPAMError(nil, "failed to find ip for subnet %s on network %s", subnet.Subnet.String(), netName)
+ }
+ util.NormalizeIP(&ip)
+
+ err = subnetBkt.Delete(ip)
+ if err != nil {
+ return newIPAMError(err, "failed to remove ip %s from subnet bucket for network %s", ip.String(), netName)
+ }
+ }
+
+ idBkt := netBkt.Bucket(idBucketKey)
+ if idBkt == nil {
+ return newIPAMError(nil, "failed to get id bucket for network %s", netName)
+ }
+
+ err = idBkt.Delete([]byte(opts.ContainerID))
+ if err != nil {
+ return newIPAMError(err, "failed to remove allocated ips for container ID %s on network %s", opts.ContainerID, netName)
+ }
+ }
+ return nil
+ })
+ return err
+}
+
+// requiresIPAMAlloc return true when we have to allocate ips for this network
+// it checks the ipam driver and if subnets are set
+func requiresIPAMAlloc(network *types.Network) bool {
+ // only do host allocation when driver is set to HostLocalIPAMDriver or unset
+ switch network.IPAMOptions["driver"] {
+ case "", types.HostLocalIPAMDriver:
+ default:
+ return false
+ }
+
+ // no subnets == no ips to assign
+ return len(network.Subnets) > 0
+}
diff --git a/vendor/github.com/containers/common/libnetwork/netavark/network.go b/vendor/github.com/containers/common/libnetwork/netavark/network.go
new file mode 100644
index 000000000..ce252bc1d
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/netavark/network.go
@@ -0,0 +1,293 @@
+// +build linux
+
+package netavark
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/containers/common/libnetwork/internal/util"
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/storage/pkg/lockfile"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+type netavarkNetwork struct {
+ // networkConfigDir is directory where the network config files are stored.
+ networkConfigDir string
+
+ // netavarkBinary is the path to the netavark binary.
+ netavarkBinary string
+
+ // defaultNetwork is the name for the default network.
+ defaultNetwork string
+ // defaultSubnet is the default subnet for the default network.
+ defaultSubnet types.IPNet
+
+ // ipamDBPath is the path to the ip allocation bolt db
+ ipamDBPath string
+
+ // syslog describes whenever the netavark debbug output should be log to the syslog as well.
+ // This will use logrus to do so, make sure logrus is set up to log to the syslog.
+ syslog bool
+
+ // lock is a internal lock for critical operations
+ lock lockfile.Locker
+
+ // modTime is the timestamp when the config dir was modified
+ modTime time.Time
+
+ // networks is a map with loaded networks, the key is the network name
+ networks map[string]*types.Network
+}
+
+type InitConfig struct {
+ // NetworkConfigDir is directory where the network config files are stored.
+ NetworkConfigDir string
+
+ // NetavarkBinary is the path to the netavark binary.
+ NetavarkBinary string
+
+ // NetworkRunDir is where temporary files are stored, i.e.the ipam db.
+ NetworkRunDir string
+
+ // DefaultNetwork is the name for the default network.
+ DefaultNetwork string
+ // DefaultSubnet is the default subnet for the default network.
+ DefaultSubnet string
+
+ // Syslog describes whenever the netavark debbug output should be log to the syslog as well.
+ // This will use logrus to do so, make sure logrus is set up to log to the syslog.
+ Syslog bool
+}
+
+// NewNetworkInterface creates the ContainerNetwork interface for the netavark backend.
+// Note: The networks are not loaded from disk until a method is called.
+func NewNetworkInterface(conf *InitConfig) (types.ContainerNetwork, error) {
+ // TODO: consider using a shared memory lock
+ lock, err := lockfile.GetLockfile(filepath.Join(conf.NetworkConfigDir, "netavark.lock"))
+ if err != nil {
+ return nil, err
+ }
+
+ defaultNetworkName := conf.DefaultNetwork
+ if defaultNetworkName == "" {
+ defaultNetworkName = types.DefaultNetworkName
+ }
+
+ defaultSubnet := conf.DefaultSubnet
+ if defaultSubnet == "" {
+ defaultSubnet = types.DefaultSubnet
+ }
+ defaultNet, err := types.ParseCIDR(defaultSubnet)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to parse default subnet")
+ }
+
+ if err := os.MkdirAll(conf.NetworkConfigDir, 0755); err != nil {
+ return nil, err
+ }
+
+ if err := os.MkdirAll(conf.NetworkRunDir, 0755); err != nil {
+ return nil, err
+ }
+
+ n := &netavarkNetwork{
+ networkConfigDir: conf.NetworkConfigDir,
+ netavarkBinary: conf.NetavarkBinary,
+ ipamDBPath: filepath.Join(conf.NetworkRunDir, "ipam.db"),
+ defaultNetwork: defaultNetworkName,
+ defaultSubnet: defaultNet,
+ lock: lock,
+ syslog: conf.Syslog,
+ }
+
+ return n, nil
+}
+
+// Drivers will return the list of supported network drivers
+// for this interface.
+func (n *netavarkNetwork) Drivers() []string {
+ return []string{types.BridgeNetworkDriver, types.MacVLANNetworkDriver}
+}
+
+// DefaultNetworkName will return the default netavark network name.
+func (n *netavarkNetwork) DefaultNetworkName() string {
+ return n.defaultNetwork
+}
+
+func (n *netavarkNetwork) loadNetworks() error {
+ // check the mod time of the config dir
+ f, err := os.Stat(n.networkConfigDir)
+ if err != nil {
+ return err
+ }
+ modTime := f.ModTime()
+
+ // skip loading networks if they are already loaded and
+ // if the config dir was not modified since the last call
+ if n.networks != nil && modTime.Equal(n.modTime) {
+ return nil
+ }
+ // make sure the remove all networks before we reload them
+ n.networks = nil
+ n.modTime = modTime
+
+ files, err := ioutil.ReadDir(n.networkConfigDir)
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+
+ networks := make(map[string]*types.Network, len(files))
+ for _, f := range files {
+ if f.IsDir() {
+ continue
+ }
+ if filepath.Ext(f.Name()) != ".json" {
+ continue
+ }
+
+ path := filepath.Join(n.networkConfigDir, f.Name())
+ file, err := os.Open(path)
+ if err != nil {
+ // do not log ENOENT errors
+ if !errors.Is(err, os.ErrNotExist) {
+ logrus.Warnf("Error loading network config file %q: %v", path, err)
+ }
+ continue
+ }
+ network := new(types.Network)
+ err = json.NewDecoder(file).Decode(network)
+ if err != nil {
+ logrus.Warnf("Error reading network config file %q: %v", path, err)
+ continue
+ }
+
+ // check that the filename matches the network name
+ if network.Name+".json" != f.Name() {
+ logrus.Warnf("Network config name %q does not match file name %q, skipping", network.Name, f.Name())
+ continue
+ }
+
+ if !types.NameRegex.MatchString(network.Name) {
+ logrus.Warnf("Network config %q has invalid name: %q, skipping: %v", path, network.Name, types.RegexError)
+ continue
+ }
+
+ err = parseNetwork(network)
+ if err != nil {
+ logrus.Warnf("Network config %q could not be parsed, skipping: %v", path, err)
+ continue
+ }
+
+ logrus.Debugf("Successfully loaded network %s: %v", network.Name, network)
+ networks[network.Name] = network
+ }
+
+ // create the default network in memory if it did not exists on disk
+ if networks[n.defaultNetwork] == nil {
+ networkInfo, err := n.createDefaultNetwork()
+ if err != nil {
+ return errors.Wrapf(err, "failed to create default network %s", n.defaultNetwork)
+ }
+ networks[n.defaultNetwork] = networkInfo
+ }
+ logrus.Debugf("Successfully loaded %d networks", len(networks))
+ n.networks = networks
+ return nil
+}
+
+func parseNetwork(network *types.Network) error {
+ if network.Labels == nil {
+ network.Labels = map[string]string{}
+ }
+ if network.Options == nil {
+ network.Options = map[string]string{}
+ }
+ if network.IPAMOptions == nil {
+ network.IPAMOptions = map[string]string{}
+ }
+
+ if len(network.ID) != 64 {
+ return errors.Errorf("invalid network ID %q", network.ID)
+ }
+
+ return util.ValidateSubnets(network, nil)
+}
+
+func (n *netavarkNetwork) createDefaultNetwork() (*types.Network, error) {
+ net := types.Network{
+ Name: n.defaultNetwork,
+ NetworkInterface: defaultBridgeName + "0",
+ // Important do not change this ID
+ ID: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
+ Driver: types.BridgeNetworkDriver,
+ Subnets: []types.Subnet{
+ {Subnet: n.defaultSubnet},
+ },
+ }
+ return n.networkCreate(&net, true)
+}
+
+// getNetwork will lookup a network by name or ID. It returns an
+// error when no network was found or when more than one network
+// with the given (partial) ID exists.
+// getNetwork will read from the networks map, therefore the caller
+// must ensure that n.lock is locked before using it.
+func (n *netavarkNetwork) getNetwork(nameOrID string) (*types.Network, error) {
+ // fast path check the map key, this will only work for names
+ if val, ok := n.networks[nameOrID]; ok {
+ return val, nil
+ }
+ // If there was no match we might got a full or partial ID.
+ var net *types.Network
+ for _, val := range n.networks {
+ // This should not happen because we already looked up the map by name but check anyway.
+ if val.Name == nameOrID {
+ return val, nil
+ }
+
+ if strings.HasPrefix(val.ID, nameOrID) {
+ if net != nil {
+ return nil, errors.Errorf("more than one result for network ID %s", nameOrID)
+ }
+ net = val
+ }
+ }
+ if net != nil {
+ return net, nil
+ }
+ return nil, errors.Wrapf(types.ErrNoSuchNetwork, "unable to find network with name or ID %s", nameOrID)
+}
+
+// Implement the NetUtil interface for easy code sharing with other network interfaces.
+
+// ForEach call the given function for each network
+func (n *netavarkNetwork) ForEach(run func(types.Network)) {
+ for _, val := range n.networks {
+ run(*val)
+ }
+}
+
+// Len return the number of networks
+func (n *netavarkNetwork) Len() int {
+ return len(n.networks)
+}
+
+// DefaultInterfaceName return the default cni bridge name, must be suffixed with a number.
+func (n *netavarkNetwork) DefaultInterfaceName() string {
+ return defaultBridgeName
+}
+
+func (n *netavarkNetwork) Network(nameOrID string) (*types.Network, error) {
+ network, err := n.getNetwork(nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return network, nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/netavark/run.go b/vendor/github.com/containers/common/libnetwork/netavark/run.go
new file mode 100644
index 000000000..c6f2007e2
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/netavark/run.go
@@ -0,0 +1,127 @@
+// +build linux
+
+package netavark
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/containers/common/libnetwork/internal/util"
+ "github.com/containers/common/libnetwork/types"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+type netavarkOptions struct {
+ types.NetworkOptions
+ Networks map[string]*types.Network `json:"network_info"`
+}
+
+// Setup will setup the container network namespace. It returns
+// a map of StatusBlocks, the key is the network name.
+func (n *netavarkNetwork) Setup(namespacePath string, options types.SetupOptions) (map[string]types.StatusBlock, error) {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return nil, err
+ }
+
+ err = util.ValidateSetupOptions(n, namespacePath, options)
+ if err != nil {
+ return nil, err
+ }
+
+ // allocate IPs in the IPAM db
+ err = n.allocIPs(&options.NetworkOptions)
+ if err != nil {
+ return nil, err
+ }
+
+ netavarkOpts, err := n.convertNetOpts(options.NetworkOptions)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to convert net opts")
+ }
+
+ // trace output to get the json
+ if logrus.IsLevelEnabled(logrus.TraceLevel) {
+ b, err := json.Marshal(&netavarkOpts)
+ if err != nil {
+ return nil, err
+ }
+ // show the full netavark command so we can easily reproduce errors from the cli
+ logrus.Tracef("netavark command: printf '%s' | %s setup %s", string(b), n.netavarkBinary, namespacePath)
+ }
+
+ result := map[string]types.StatusBlock{}
+ err = n.execNetavark([]string{"setup", namespacePath}, netavarkOpts, &result)
+ if err != nil {
+ // lets dealloc ips to prevent leaking
+ if err := n.deallocIPs(&options.NetworkOptions); err != nil {
+ logrus.Error(err)
+ }
+ return nil, err
+ }
+
+ // make sure that the result makes sense
+ if len(result) != len(options.Networks) {
+ logrus.Errorf("unexpected netavark result: %v", result)
+ return nil, fmt.Errorf("unexpected netavark result length, want (%d), got (%d) networks", len(options.Networks), len(result))
+ }
+
+ return result, err
+}
+
+// Teardown will teardown the container network namespace.
+func (n *netavarkNetwork) Teardown(namespacePath string, options types.TeardownOptions) error {
+ n.lock.Lock()
+ defer n.lock.Unlock()
+ err := n.loadNetworks()
+ if err != nil {
+ return err
+ }
+
+ // get IPs from the IPAM db
+ err = n.getAssignedIPs(&options.NetworkOptions)
+ if err != nil {
+ // when there is an error getting the ips we should still continue
+ // to call teardown for netavark to prevent leaking network interfaces
+ logrus.Error(err)
+ }
+
+ netavarkOpts, err := n.convertNetOpts(options.NetworkOptions)
+ if err != nil {
+ return errors.Wrap(err, "failed to convert net opts")
+ }
+
+ retErr := n.execNetavark([]string{"teardown", namespacePath}, netavarkOpts, nil)
+
+ // when netavark returned an error we still free the used ips
+ // otherwise we could end up in a state where block the ips forever
+ err = n.deallocIPs(&netavarkOpts.NetworkOptions)
+ if err != nil {
+ if retErr != nil {
+ logrus.Error(err)
+ } else {
+ retErr = err
+ }
+ }
+
+ return retErr
+}
+
+func (n *netavarkNetwork) convertNetOpts(opts types.NetworkOptions) (*netavarkOptions, error) {
+ netavarkOptions := netavarkOptions{
+ NetworkOptions: opts,
+ Networks: make(map[string]*types.Network, len(opts.Networks)),
+ }
+
+ for network := range opts.Networks {
+ net, err := n.getNetwork(network)
+ if err != nil {
+ return nil, err
+ }
+ netavarkOptions.Networks[network] = net
+ }
+ return &netavarkOptions, nil
+}
diff --git a/vendor/github.com/containers/common/libnetwork/network/interface.go b/vendor/github.com/containers/common/libnetwork/network/interface.go
new file mode 100644
index 000000000..190e6945b
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/network/interface.go
@@ -0,0 +1,132 @@
+// +build linux
+
+package network
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/containers/common/libnetwork/cni"
+ "github.com/containers/common/libnetwork/netavark"
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/storage"
+ "github.com/containers/storage/pkg/ioutils"
+ "github.com/sirupsen/logrus"
+)
+
+const defaultNetworkBackendFileName = "defaultNetworkBackend"
+
+// NetworkBackend returns the network backend name and interface
+// It returns either the CNI or netavark backend depending on what is set in the config.
+// If the the backend is set to "" we will automatically assign the backend on the following conditions:
+// 1. read ${graphroot}/defaultNetworkBackend
+// 2. find netavark binary (if not installed use CNI)
+// 3. check containers, images and CNI networks and if there are some we have an existing install and should continue to use CNI
+func NetworkBackend(store storage.Store, conf *config.Config, syslog bool) (types.NetworkBackend, types.ContainerNetwork, error) {
+ backend := types.NetworkBackend(conf.Network.NetworkBackend)
+ if backend == "" {
+ var err error
+ backend, err = defaultNetworkBackend(store, conf)
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to get default network backend: %w", err)
+ }
+ }
+
+ switch backend {
+ case types.Netavark:
+ netavarkBin, err := conf.FindHelperBinary("netavark", false)
+ if err != nil {
+ return "", nil, err
+ }
+ netInt, err := netavark.NewNetworkInterface(&netavark.InitConfig{
+ NetworkConfigDir: filepath.Join(store.GraphRoot(), "networks"),
+ NetworkRunDir: filepath.Join(store.RunRoot(), "networks"),
+ NetavarkBinary: netavarkBin,
+ DefaultNetwork: conf.Network.DefaultNetwork,
+ DefaultSubnet: conf.Network.DefaultSubnet,
+ Syslog: syslog,
+ })
+ return types.Netavark, netInt, err
+ case types.CNI:
+ netInt, err := getCniInterface(conf)
+ return types.CNI, netInt, err
+
+ default:
+ return "", nil, fmt.Errorf("unsupported network backend %q, check network_backend in containers.conf", backend)
+ }
+}
+
+func defaultNetworkBackend(store storage.Store, conf *config.Config) (backend types.NetworkBackend, err error) {
+ // read defaultNetworkBackend file
+ file := filepath.Join(store.GraphRoot(), defaultNetworkBackendFileName)
+ b, err := ioutil.ReadFile(file)
+ if err == nil {
+ val := string(b)
+ if val == string(types.Netavark) {
+ return types.Netavark, nil
+ }
+ if val == string(types.CNI) {
+ return types.CNI, nil
+ }
+ return "", fmt.Errorf("unknown network backend value %q in %q", val, file)
+ }
+ // fail for all errors except ENOENT
+ if !errors.Is(err, os.ErrNotExist) {
+ return "", fmt.Errorf("could not read network backend value: %w", err)
+ }
+
+ // cache the network backend to make sure always the same one will be used
+ defer func() {
+ // only write when there is no error
+ if err == nil {
+ // nolint:gocritic
+ if err := ioutils.AtomicWriteFile(file, []byte(backend), 0644); err != nil {
+ logrus.Errorf("could not write network backend to file: %v", err)
+ }
+ }
+ }()
+
+ _, err = conf.FindHelperBinary("netavark", false)
+ if err != nil {
+ // if we cannot find netavark use CNI
+ return types.CNI, nil
+ }
+
+ // now check if there are already containers, images and CNI networks (new install?)
+ cons, err := store.Containers()
+ if err != nil {
+ return "", err
+ }
+ if len(cons) == 0 {
+ imgs, err := store.Images()
+ if err != nil {
+ return "", err
+ }
+ if len(imgs) == 0 {
+ cniInterface, err := getCniInterface(conf)
+ if err == nil {
+ nets, err := cniInterface.NetworkList()
+ // there is always a default network so check <= 1
+ if err == nil && len(nets) <= 1 {
+ // we have a fresh system so use netavark
+ return types.Netavark, nil
+ }
+ }
+ }
+ }
+ return types.CNI, nil
+}
+
+func getCniInterface(conf *config.Config) (types.ContainerNetwork, error) {
+ return cni.NewCNINetworkInterface(&cni.InitConfig{
+ CNIConfigDir: conf.Network.NetworkConfigDir,
+ CNIPluginDirs: conf.Network.CNIPluginDirs,
+ DefaultNetwork: conf.Network.DefaultNetwork,
+ DefaultSubnet: conf.Network.DefaultSubnet,
+ IsMachine: conf.Engine.MachineEnabled,
+ })
+}
diff --git a/vendor/github.com/containers/common/libnetwork/types/const.go b/vendor/github.com/containers/common/libnetwork/types/const.go
new file mode 100644
index 000000000..b2d4a4538
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/types/const.go
@@ -0,0 +1,47 @@
+package types
+
+const (
+ // BridgeNetworkDriver defines the bridge driver
+ BridgeNetworkDriver = "bridge"
+ // DefaultNetworkDriver is the default network type used
+ DefaultNetworkDriver = BridgeNetworkDriver
+ // MacVLANNetworkDriver defines the macvlan driver
+ MacVLANNetworkDriver = "macvlan"
+ // MacVLANNetworkDriver defines the macvlan driver
+ IPVLANNetworkDriver = "ipvlan"
+
+ // IPAM drivers
+ // HostLocalIPAMDriver store the ip
+ HostLocalIPAMDriver = "host-local"
+ // DHCPIPAMDriver get subnet and ip from dhcp server
+ DHCPIPAMDriver = "dhcp"
+
+ // DefaultSubnet is the name that will be used for the default CNI network.
+ DefaultNetworkName = "podman"
+ // DefaultSubnet is the subnet that will be used for the default CNI network.
+ DefaultSubnet = "10.88.0.0/16"
+
+ // valid macvlan driver mode values
+ MacVLANModeBridge = "bridge"
+ MacVLANModePrivate = "private"
+ MacVLANModeVepa = "vepa"
+ MacVLANModePassthru = "passthru"
+
+ // valid ipvlan driver modes
+ IPVLANModeL2 = "l2"
+ IPVLANModeL3 = "l3"
+ IPVLANModeL3s = "l3s"
+)
+
+type NetworkBackend string
+
+const (
+ CNI NetworkBackend = "cni"
+ Netavark NetworkBackend = "netavark"
+)
+
+// ValidMacVLANModes is the list of valid mode options for the macvlan driver
+var ValidMacVLANModes = []string{MacVLANModeBridge, MacVLANModePrivate, MacVLANModeVepa, MacVLANModePassthru}
+
+// ValidIPVLANModes is the list of valid mode options for the ipvlan driver
+var ValidIPVLANModes = []string{IPVLANModeL2, IPVLANModeL3, IPVLANModeL3s}
diff --git a/vendor/github.com/containers/common/libnetwork/types/define.go b/vendor/github.com/containers/common/libnetwork/types/define.go
new file mode 100644
index 000000000..d37e529df
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/types/define.go
@@ -0,0 +1,25 @@
+package types
+
+import (
+ "regexp"
+
+ "github.com/pkg/errors"
+)
+
+var (
+ // ErrNoSuchNetwork indicates the requested network does not exist
+ ErrNoSuchNetwork = errors.New("network not found")
+
+ // ErrInvalidArg indicates that an invalid argument was passed
+ ErrInvalidArg = errors.New("invalid argument")
+
+ // ErrNetworkExists indicates that a network with the given name already
+ // exists.
+ ErrNetworkExists = errors.New("network already exists")
+
+ // NameRegex is a regular expression to validate names.
+ // This must NOT be changed.
+ NameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")
+ // RegexError is thrown in presence of an invalid name.
+ RegexError = errors.Wrapf(ErrInvalidArg, "names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*")
+)
diff --git a/vendor/github.com/containers/common/libnetwork/types/network.go b/vendor/github.com/containers/common/libnetwork/types/network.go
new file mode 100644
index 000000000..de8655377
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/types/network.go
@@ -0,0 +1,282 @@
+package types
+
+import (
+ "encoding/json"
+ "net"
+ "time"
+)
+
+type ContainerNetwork interface {
+ // NetworkCreate will take a partial filled Network and fill the
+ // missing fields. It creates the Network and returns the full Network.
+ NetworkCreate(Network) (Network, error)
+ // NetworkRemove will remove the Network with the given name or ID.
+ NetworkRemove(nameOrID string) error
+ // NetworkList will return all known Networks. Optionally you can
+ // supply a list of filter functions. Only if a network matches all
+ // functions it is returned.
+ NetworkList(...FilterFunc) ([]Network, error)
+ // NetworkInspect will return the Network with the given name or ID.
+ NetworkInspect(nameOrID string) (Network, error)
+
+ // Setup will setup the container network namespace. It returns
+ // a map of StatusBlocks, the key is the network name.
+ Setup(namespacePath string, options SetupOptions) (map[string]StatusBlock, error)
+ // Teardown will teardown the container network namespace.
+ Teardown(namespacePath string, options TeardownOptions) error
+
+ // Drivers will return the list of supported network drivers
+ // for this interface.
+ Drivers() []string
+
+ // DefaultNetworkName will return the default network name
+ // for this interface.
+ DefaultNetworkName() string
+}
+
+// Network describes the Network attributes.
+type Network struct {
+ // Name of the Network.
+ Name string `json:"name"`
+ // ID of the Network.
+ ID string `json:"id"`
+ // Driver for this Network, e.g. bridge, macvlan...
+ Driver string `json:"driver"`
+ // NetworkInterface is the network interface name on the host.
+ NetworkInterface string `json:"network_interface,omitempty"`
+ // Created contains the timestamp when this network was created.
+ Created time.Time `json:"created,omitempty"`
+ // Subnets to use for this network.
+ Subnets []Subnet `json:"subnets,omitempty"`
+ // IPv6Enabled if set to true an ipv6 subnet should be created for this net.
+ IPv6Enabled bool `json:"ipv6_enabled"`
+ // Internal is whether the Network should not have external routes
+ // to public or other Networks.
+ Internal bool `json:"internal"`
+ // DNSEnabled is whether name resolution is active for container on
+ // this Network.
+ DNSEnabled bool `json:"dns_enabled"`
+ // Labels is a set of key-value labels that have been applied to the
+ // Network.
+ Labels map[string]string `json:"labels,omitempty"`
+ // Options is a set of key-value options that have been applied to
+ // the Network.
+ Options map[string]string `json:"options,omitempty"`
+ // IPAMOptions contains options used for the ip assignment.
+ IPAMOptions map[string]string `json:"ipam_options,omitempty"`
+}
+
+// IPNet is used as custom net.IPNet type to add Marshal/Unmarshal methods.
+type IPNet struct {
+ net.IPNet
+}
+
+// ParseCIDR parse a string to IPNet
+func ParseCIDR(cidr string) (IPNet, error) {
+ ip, subnet, err := net.ParseCIDR(cidr)
+ if err != nil {
+ return IPNet{}, err
+ }
+ // convert to 4 bytes if ipv4
+ ipv4 := ip.To4()
+ if ipv4 != nil {
+ ip = ipv4
+ }
+ subnet.IP = ip
+ return IPNet{*subnet}, err
+}
+
+func (n *IPNet) MarshalText() ([]byte, error) {
+ return []byte(n.String()), nil
+}
+
+func (n *IPNet) UnmarshalText(text []byte) error {
+ subnet, err := ParseCIDR(string(text))
+ if err != nil {
+ return err
+ }
+ *n = subnet
+ return nil
+}
+
+// HardwareAddr is the same as net.HardwareAddr except
+// that it adds the json marshal/unmarshal methods.
+// This allows us to read the mac from a json string
+// and a byte array.
+// swagger:model MacAddress
+type HardwareAddr net.HardwareAddr
+
+func (h *HardwareAddr) String() string {
+ return (*net.HardwareAddr)(h).String()
+}
+
+func (h HardwareAddr) MarshalText() ([]byte, error) {
+ return []byte(h.String()), nil
+}
+
+func (h *HardwareAddr) UnmarshalJSON(text []byte) error {
+ if len(text) == 0 {
+ *h = nil
+ return nil
+ }
+
+ // if the json string start with a quote we got a string
+ // unmarshal the string and parse the mac from this string
+ if string(text[0]) == `"` {
+ var macString string
+ err := json.Unmarshal(text, &macString)
+ if err == nil {
+ mac, err := net.ParseMAC(macString)
+ if err == nil {
+ *h = HardwareAddr(mac)
+ return nil
+ }
+ }
+ }
+ // not a string or got an error fallback to the normal parsing
+ mac := make(net.HardwareAddr, 0, 6)
+ // use the standard json unmarshal for backwards compat
+ err := json.Unmarshal(text, &mac)
+ if err != nil {
+ return err
+ }
+ *h = HardwareAddr(mac)
+ return nil
+}
+
+type Subnet struct {
+ // Subnet for this Network in CIDR form.
+ // swagger:strfmt string
+ Subnet IPNet `json:"subnet"`
+ // 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"`
+}
+
+// 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"`
+}
+
+// StatusBlock contains the network information about a container
+// connected to one Network.
+type StatusBlock struct {
+ // Interfaces contains the created network interface in the container.
+ // The map key is the interface name.
+ Interfaces map[string]NetInterface `json:"interfaces,omitempty"`
+ // DNSServerIPs nameserver addresses which should be added to
+ // the containers resolv.conf file.
+ DNSServerIPs []net.IP `json:"dns_server_ips,omitempty"`
+ // DNSSearchDomains search domains which should be added to
+ // the containers resolv.conf file.
+ DNSSearchDomains []string `json:"dns_search_domains,omitempty"`
+}
+
+// NetInterface contains the settings for a given network interface.
+type NetInterface struct {
+ // Subnets list of assigned subnets with their gateway.
+ Subnets []NetAddress `json:"subnets,omitempty"`
+ // MacAddress for this Interface.
+ MacAddress HardwareAddr `json:"mac_address"`
+}
+
+// NetAddress contains the ip address, subnet and gateway.
+type NetAddress struct {
+ // IPNet of this NetAddress. Note that this is a subnet but it has to contain the
+ // actual ip of the network interface and not the network address.
+ IPNet IPNet `json:"ipnet"`
+ // Gateway for the network. This can be empty if there is no gateway, e.g. internal network.
+ Gateway net.IP `json:"gateway,omitempty"`
+}
+
+// PerNetworkOptions are options which should be set on a per network basis.
+type PerNetworkOptions struct {
+ // StaticIPs for this container. Optional.
+ StaticIPs []net.IP `json:"static_ips,omitempty"`
+ // Aliases contains a list of names which the dns server should resolve
+ // to this container. Should only be set when DNSEnabled is true on the Network.
+ // If aliases are set but there is no dns support for this network the
+ // network interface implementation should ignore this and NOT error.
+ // Optional.
+ Aliases []string `json:"aliases,omitempty"`
+ // StaticMac for this container. Optional.
+ StaticMAC HardwareAddr `json:"static_mac,omitempty"`
+ // InterfaceName for this container. Required in the backend.
+ // Optional in the frontend. Will be filled with ethX (where X is a integer) when empty.
+ InterfaceName string `json:"interface_name"`
+}
+
+// NetworkOptions for a given container.
+type NetworkOptions struct {
+ // ContainerID is the container id, used for iptables comments and ipam allocation.
+ ContainerID string `json:"container_id"`
+ // ContainerName is the container name, used as dns name.
+ ContainerName string `json:"container_name"`
+ // PortMappings contains the port mappings for this container
+ PortMappings []PortMapping `json:"port_mappings,omitempty"`
+ // Networks contains all networks with the PerNetworkOptions.
+ // The map should contain at least one element.
+ Networks map[string]PerNetworkOptions `json:"networks"`
+}
+
+// PortMapping is one or more ports that will be mapped into the container.
+type PortMapping struct {
+ // HostIP is the IP that we will bind to on the host.
+ // If unset, assumed to be 0.0.0.0 (all interfaces).
+ HostIP string `json:"host_ip"`
+ // ContainerPort is the port number that will be exposed from the
+ // container.
+ // Mandatory.
+ ContainerPort uint16 `json:"container_port"`
+ // HostPort is the port number that will be forwarded from the host into
+ // the container.
+ // If omitted, a random port on the host (guaranteed to be over 1024)
+ // will be assigned.
+ HostPort uint16 `json:"host_port"`
+ // Range is the number of ports that will be forwarded, starting at
+ // HostPort and ContainerPort and counting up.
+ // This is 1-indexed, so 1 is assumed to be a single port (only the
+ // Hostport:Containerport mapping will be added), 2 is two ports (both
+ // Hostport:Containerport and Hostport+1:Containerport+1), etc.
+ // If unset, assumed to be 1 (a single port).
+ // Both hostport + range and containerport + range must be less than
+ // 65536.
+ Range uint16 `json:"range"`
+ // Protocol is the protocol forward.
+ // Must be either "tcp", "udp", and "sctp", or some combination of these
+ // separated by commas.
+ // If unset, assumed to be TCP.
+ Protocol string `json:"protocol"`
+}
+
+// OCICNIPortMapping maps to the standard CNI portmapping Capability.
+// Deprecated: Do not use this struct for new fields. This only exists
+// for backwards compatibility.
+type OCICNIPortMapping struct {
+ // HostPort is the port number on the host.
+ HostPort int32 `json:"hostPort"`
+ // ContainerPort is the port number inside the sandbox.
+ ContainerPort int32 `json:"containerPort"`
+ // Protocol is the protocol of the port mapping.
+ Protocol string `json:"protocol"`
+ // HostIP is the host ip to use.
+ HostIP string `json:"hostIP"`
+}
+
+type SetupOptions struct {
+ NetworkOptions
+}
+
+type TeardownOptions struct {
+ NetworkOptions
+}
+
+// FilterFunc can be passed to NetworkList to filter the networks.
+type FilterFunc func(Network) bool
diff --git a/vendor/github.com/containers/common/libnetwork/util/filters.go b/vendor/github.com/containers/common/libnetwork/util/filters.go
new file mode 100644
index 000000000..b27ca1f9a
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/util/filters.go
@@ -0,0 +1,80 @@
+package util
+
+import (
+ "strings"
+
+ "github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/pkg/filters"
+ "github.com/containers/common/pkg/util"
+ "github.com/pkg/errors"
+)
+
+func GenerateNetworkFilters(f map[string][]string) ([]types.FilterFunc, error) {
+ filterFuncs := make([]types.FilterFunc, 0, len(f))
+ for key, filterValues := range f {
+ filterFunc, err := createFilterFuncs(key, filterValues)
+ if err != nil {
+ return nil, err
+ }
+ filterFuncs = append(filterFuncs, filterFunc)
+ }
+ return filterFuncs, nil
+}
+
+func createFilterFuncs(key string, filterValues []string) (types.FilterFunc, error) {
+ switch strings.ToLower(key) {
+ case "name":
+ // matches one name, regex allowed
+ return func(net types.Network) bool {
+ return util.StringMatchRegexSlice(net.Name, filterValues)
+ }, nil
+
+ case "driver":
+ // matches network driver
+ return func(net types.Network) bool {
+ return util.StringInSlice(net.Driver, filterValues)
+ }, nil
+
+ case "id":
+ // matches part of one id
+ return func(net types.Network) bool {
+ return util.StringMatchRegexSlice(net.ID, filterValues)
+ }, nil
+
+ // TODO: add dns enabled, internal filter
+ }
+ return createPruneFilterFuncs(key, filterValues)
+}
+
+func GenerateNetworkPruneFilters(f map[string][]string) ([]types.FilterFunc, error) {
+ filterFuncs := make([]types.FilterFunc, 0, len(f))
+ for key, filterValues := range f {
+ 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 filters.MatchLabelFilters(filterValues, net.Labels)
+ }, nil
+
+ case "until":
+ until, err := filters.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)
+ }
+}
diff --git a/vendor/github.com/containers/common/libnetwork/util/ip.go b/vendor/github.com/containers/common/libnetwork/util/ip.go
new file mode 100644
index 000000000..7c315e312
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/util/ip.go
@@ -0,0 +1,56 @@
+package util
+
+import (
+ "net"
+)
+
+// IsIPv6 returns true if netIP is IPv6.
+func IsIPv6(netIP net.IP) bool {
+ return netIP != nil && netIP.To4() == nil
+}
+
+// IsIPv4 returns true if netIP is IPv4.
+func IsIPv4(netIP net.IP) bool {
+ return netIP != nil && netIP.To4() != 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.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
+}
+
+// NormalizeIP will transform the given ip to the 4 byte len ipv4 if possible
+func NormalizeIP(ip *net.IP) {
+ ipv4 := ip.To4()
+ if ipv4 != nil {
+ *ip = ipv4
+ }
+}
diff --git a/vendor/github.com/containers/common/libnetwork/util/ip_calc.go b/vendor/github.com/containers/common/libnetwork/util/ip_calc.go
new file mode 100644
index 000000000..a27ddf78b
--- /dev/null
+++ b/vendor/github.com/containers/common/libnetwork/util/ip_calc.go
@@ -0,0 +1,53 @@
+// Copyright 2015 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package util
+
+import (
+ "math/big"
+ "net"
+)
+
+// NextIP returns IP incremented by 1
+func NextIP(ip net.IP) net.IP {
+ i := ipToInt(ip)
+ return intToIP(i.Add(i, big.NewInt(1)))
+}
+
+// PrevIP returns IP decremented by 1
+func PrevIP(ip net.IP) net.IP {
+ i := ipToInt(ip)
+ return intToIP(i.Sub(i, big.NewInt(1)))
+}
+
+// Cmp compares two IPs, returning the usual ordering:
+// a < b : -1
+// a == b : 0
+// a > b : 1
+func Cmp(a, b net.IP) int {
+ aa := ipToInt(a)
+ bb := ipToInt(b)
+ return aa.Cmp(bb)
+}
+
+func ipToInt(ip net.IP) *big.Int {
+ if v := ip.To4(); v != nil {
+ return big.NewInt(0).SetBytes(v)
+ }
+ return big.NewInt(0).SetBytes(ip.To16())
+}
+
+func intToIP(i *big.Int) net.IP {
+ return net.IP(i.Bytes())
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 54d49db42..a09984ec0 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -110,6 +110,12 @@ github.com/containers/buildah/util
## explicit
github.com/containers/common/libimage
github.com/containers/common/libimage/manifests
+github.com/containers/common/libnetwork/cni
+github.com/containers/common/libnetwork/internal/util
+github.com/containers/common/libnetwork/netavark
+github.com/containers/common/libnetwork/network
+github.com/containers/common/libnetwork/types
+github.com/containers/common/libnetwork/util
github.com/containers/common/pkg/apparmor
github.com/containers/common/pkg/apparmor/internal/supported
github.com/containers/common/pkg/auth