commit
						e9daaf62e3
					
				|  | @ -10,6 +10,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/containers/common/pkg/config" | ||||
| 	"github.com/containers/podman/v3/cmd/podman/registry" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/pkg/api/handlers" | ||||
| 	"github.com/containers/podman/v3/pkg/cgroups" | ||||
| 	"github.com/containers/podman/v3/pkg/domain/entities" | ||||
|  | @ -150,7 +151,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c | |||
| 		cappDrop   []string | ||||
| 		entrypoint *string | ||||
| 		init       bool | ||||
| 		specPorts  []specgen.PortMapping | ||||
| 		specPorts  []types.PortMapping | ||||
| 	) | ||||
| 
 | ||||
| 	if cc.HostConfig.Init != nil { | ||||
|  | @ -240,7 +241,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c | |||
| 			if err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
| 			tmpPort := specgen.PortMapping{ | ||||
| 			tmpPort := types.PortMapping{ | ||||
| 				HostIP:        pb.HostIP, | ||||
| 				ContainerPort: uint16(port.Int()), | ||||
| 				HostPort:      uint16(hostport), | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import ( | |||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/containers/podman/v3/pkg/specgen" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | @ -90,10 +90,10 @@ func createExpose(expose []string) (map[uint16]string, error) { | |||
| } | ||||
| 
 | ||||
| // CreatePortBindings iterates ports mappings into SpecGen format.
 | ||||
| func CreatePortBindings(ports []string) ([]specgen.PortMapping, error) { | ||||
| func CreatePortBindings(ports []string) ([]types.PortMapping, error) { | ||||
| 	// --publish is formatted as follows:
 | ||||
| 	// [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol]
 | ||||
| 	toReturn := make([]specgen.PortMapping, 0, len(ports)) | ||||
| 	toReturn := make([]types.PortMapping, 0, len(ports)) | ||||
| 
 | ||||
| 	for _, p := range ports { | ||||
| 		var ( | ||||
|  | @ -169,8 +169,8 @@ func CreatePortBindings(ports []string) ([]specgen.PortMapping, error) { | |||
| 
 | ||||
| // parseSplitPort parses individual components of the --publish flag to produce
 | ||||
| // a single port mapping in SpecGen format.
 | ||||
| func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (specgen.PortMapping, error) { | ||||
| 	newPort := specgen.PortMapping{} | ||||
| func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (types.PortMapping, error) { | ||||
| 	newPort := types.PortMapping{} | ||||
| 	if ctrPort == "" { | ||||
| 		return newPort, errors.Errorf("must provide a non-empty container port to publish") | ||||
| 	} | ||||
|  |  | |||
|  | @ -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. | ||||
|  | @ -0,0 +1,375 @@ | |||
| // +build linux
 | ||||
| 
 | ||||
| package cni | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/containernetworking/cni/libcni" | ||||
| 	"github.com/containernetworking/cni/pkg/version" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/libpod/network/util" | ||||
| 	pkgutil "github.com/containers/podman/v3/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: | ||||
| 		var macvlan macVLANConfig | ||||
| 		err := json.Unmarshal(firstPlugin.Bytes, &macvlan) | ||||
| 		if err != nil { | ||||
| 			return nil, errors.Wrapf(err, "failed to unmarshal the macvlan plugin config in %s", confPath) | ||||
| 		} | ||||
| 		network.NetworkInterface = macvlan.Master | ||||
| 
 | ||||
| 		// set network options
 | ||||
| 		if macvlan.MTU != 0 { | ||||
| 			network.Options["mtu"] = strconv.Itoa(macvlan.MTU) | ||||
| 		} | ||||
| 
 | ||||
| 		err = convertIPAMConfToNetwork(&network, macvlan.IPAM, confPath) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 	default: | ||||
| 		// A warning would be good but users would get this warning everytime 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
 | ||||
| 				ipv4 := gateway.To4() | ||||
| 				if ipv4 != nil { | ||||
| 					gateway = ipv4 | ||||
| 				} | ||||
| 			} 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 | ||||
| 			} | ||||
| 			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 nil | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| 	for k, v := range network.Options { | ||||
| 		switch k { | ||||
| 		case "mtu": | ||||
| 			mtu, err = parseMTU(v) | ||||
| 			if err != nil { | ||||
| 				return nil, "", err | ||||
| 			} | ||||
| 
 | ||||
| 		case "vlan": | ||||
| 			vlan, err = parseVlan(v) | ||||
| 			if err != nil { | ||||
| 				return nil, "", err | ||||
| 			} | ||||
| 
 | ||||
| 		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
 | ||||
| 	ncList := newNcList(network.Name, version.Current(), 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)) | ||||
| 		} | ||||
| 		// Add the podman-machine CNI plugin if we are in a machine
 | ||||
| 		if n.isMachine { | ||||
| 			plugins = append(plugins, newPodmanMachinePlugin()) | ||||
| 		} | ||||
| 
 | ||||
| 	case types.MacVLANNetworkDriver: | ||||
| 		plugins = append(plugins, newMacVLANPlugin(network.NetworkInterface, 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 | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
| } | ||||
|  | @ -0,0 +1,98 @@ | |||
| // 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
 | ||||
| //
 | ||||
| //     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.
 | ||||
| 
 | ||||
| // +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" | ||||
| ) | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 	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 []byte, 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) | ||||
| } | ||||
|  | @ -0,0 +1,53 @@ | |||
| // +build linux
 | ||||
| 
 | ||||
| package cni_test | ||||
| 
 | ||||
| import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/containers/podman/v3/libpod/network/cni" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/test/utils" | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/gomega" | ||||
| ) | ||||
| 
 | ||||
| var cniPluginDirs = []string{ | ||||
| 	"/usr/libexec/cni", | ||||
| 	"/usr/lib/cni", | ||||
| 	"/usr/local/lib/cni", | ||||
| 	"/opt/cni/bin", | ||||
| } | ||||
| 
 | ||||
| func TestCni(t *testing.T) { | ||||
| 	RegisterFailHandler(Fail) | ||||
| 	RunSpecs(t, "CNI Suite") | ||||
| } | ||||
| 
 | ||||
| func getNetworkInterface(cniConfDir string, machine bool) (types.ContainerNetwork, error) { | ||||
| 	return cni.NewCNINetworkInterface(cni.InitConfig{ | ||||
| 		CNIConfigDir:  cniConfDir, | ||||
| 		CNIPluginDirs: cniPluginDirs, | ||||
| 		IsMachine:     machine, | ||||
| 		LockFile:      filepath.Join(cniConfDir, "cni.lock"), | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func SkipIfNoDnsname() { | ||||
| 	for _, path := range cniPluginDirs { | ||||
| 		f, err := os.Stat(filepath.Join(path, "dnsname")) | ||||
| 		if err == nil && f.Mode().IsRegular() { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	Skip("dnsname cni plugin needs to be installed for this test") | ||||
| } | ||||
| 
 | ||||
| func SkipIfNotFedora(msg string) { | ||||
| 	info := utils.GetHostDistributionInfo() | ||||
| 	if info.Distribution != "fedora" { | ||||
| 		Skip("Test can only run on Fedora: " + msg) | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,292 @@ | |||
| // +build linux
 | ||||
| 
 | ||||
| package cni | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/containers/podman/v3/libpod/network/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"` | ||||
| } | ||||
| 
 | ||||
| // 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"` | ||||
| } | ||||
| 
 | ||||
| // macVLANConfig describes the macvlan config
 | ||||
| type macVLANConfig struct { | ||||
| 	PluginType   string          `json:"type"` | ||||
| 	Master       string          `json:"master"` | ||||
| 	IPAM         ipamConfig      `json:"ipam"` | ||||
| 	MTU          int             `json:"mtu,omitempty"` | ||||
| 	Capabilities map[string]bool `json:"capabilities"` | ||||
| } | ||||
| 
 | ||||
| // 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"` | ||||
| } | ||||
| 
 | ||||
| //  podmanMachineConfig enables port handling on the host OS
 | ||||
| type podmanMachineConfig struct { | ||||
| 	PluginType   string          `json:"type"` | ||||
| 	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 int, 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(), | ||||
| 	} | ||||
| 
 | ||||
| 	// an 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.RangeStart = 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 | ||||
| } | ||||
| 
 | ||||
| // newMacVLANPlugin creates a macvlanconfig with a given device name
 | ||||
| func newMacVLANPlugin(device string, mtu int, ipam ipamConfig) macVLANConfig { | ||||
| 	m := macVLANConfig{ | ||||
| 		PluginType: "macvlan", | ||||
| 		IPAM:       ipam, | ||||
| 	} | ||||
| 	if mtu > 0 { | ||||
| 		m.MTU = mtu | ||||
| 	} | ||||
| 	// CNI is supposed to use the default route if a
 | ||||
| 	// parent device is not provided
 | ||||
| 	if len(device) > 0 { | ||||
| 		m.Master = device | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| func newPodmanMachinePlugin() podmanMachineConfig { | ||||
| 	caps := make(map[string]bool, 1) | ||||
| 	caps["portMappings"] = true | ||||
| 	return podmanMachineConfig{ | ||||
| 		PluginType:   "podman-machine", | ||||
| 		Capabilities: caps, | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,313 @@ | |||
| // +build linux
 | ||||
| 
 | ||||
| package cni | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/containers/podman/v3/libpod/define" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/libpod/network/util" | ||||
| 	pkgutil "github.com/containers/podman/v3/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.
 | ||||
| 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, true) | ||||
| 	if err != nil { | ||||
| 		return types.Network{}, err | ||||
| 	} | ||||
| 	// add the new network to the map
 | ||||
| 	n.networks[network.libpodNet.Name] = network | ||||
| 	return *network.libpodNet, nil | ||||
| } | ||||
| 
 | ||||
| func (n *cniNetwork) networkCreate(net types.Network, writeToDisk bool) (*network, error) { | ||||
| 	// if no driver is set use the default one
 | ||||
| 	if net.Driver == "" { | ||||
| 		net.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 net.ID != "" { | ||||
| 		return nil, errors.Wrap(define.ErrInvalidArg, "ID can not be set for network create") | ||||
| 	} | ||||
| 
 | ||||
| 	if net.Labels == nil { | ||||
| 		net.Labels = map[string]string{} | ||||
| 	} | ||||
| 	if net.Options == nil { | ||||
| 		net.Options = map[string]string{} | ||||
| 	} | ||||
| 	if net.IPAMOptions == nil { | ||||
| 		net.IPAMOptions = map[string]string{} | ||||
| 	} | ||||
| 
 | ||||
| 	var name string | ||||
| 	var err error | ||||
| 	// validate the name when given
 | ||||
| 	if net.Name != "" { | ||||
| 		if !define.NameRegex.MatchString(net.Name) { | ||||
| 			return nil, errors.Wrapf(define.RegexError, "network name %s invalid", net.Name) | ||||
| 		} | ||||
| 		if _, ok := n.networks[net.Name]; ok { | ||||
| 			return nil, errors.Wrapf(define.ErrNetworkExists, "network name %s already used", net.Name) | ||||
| 		} | ||||
| 	} else { | ||||
| 		name, err = n.getFreeDeviceName() | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		net.Name = name | ||||
| 	} | ||||
| 
 | ||||
| 	switch net.Driver { | ||||
| 	case types.BridgeNetworkDriver: | ||||
| 		// if the name was created with getFreeDeviceName set the interface to it as well
 | ||||
| 		if name != "" && net.NetworkInterface == "" { | ||||
| 			net.NetworkInterface = name | ||||
| 		} | ||||
| 		err = n.createBridge(&net) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	case types.MacVLANNetworkDriver: | ||||
| 		err = createMacVLAN(&net) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	default: | ||||
| 		return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported driver %s", net.Driver) | ||||
| 	} | ||||
| 
 | ||||
| 	for i := range net.Subnets { | ||||
| 		err := validateSubnet(&net.Subnets[i], !net.Internal) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if util.IsIPv6(net.Subnets[i].Subnet.IP) { | ||||
| 			net.IPv6Enabled = true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// generate the network ID
 | ||||
| 	net.ID = getNetworkIDFromName(net.Name) | ||||
| 
 | ||||
| 	// FIXME: Should this be a hard error?
 | ||||
| 	if net.DNSEnabled && net.Internal && hasDNSNamePlugin(n.cniPluginDirs) { | ||||
| 		logrus.Warnf("dnsname and internal networks are incompatible. dnsname plugin not configured for network %s", net.Name) | ||||
| 		net.DNSEnabled = false | ||||
| 	} | ||||
| 
 | ||||
| 	cniConf, path, err := n.createCNIConfigListFromNetwork(&net, writeToDisk) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &network{cniNet: cniConf, libpodNet: &net, 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) | ||||
| 
 | ||||
| 	return os.Remove(file) | ||||
| } | ||||
| 
 | ||||
| // 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 createMacVLAN(network *types.Network) error { | ||||
| 	if network.Internal { | ||||
| 		return errors.New("internal is not supported with macvlan") | ||||
| 	} | ||||
| 	if network.NetworkInterface != "" { | ||||
| 		interfaceNames, err := util.GetLiveNetworkNames() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if !pkgutil.StringInSlice(network.NetworkInterface, interfaceNames) { | ||||
| 			return errors.Errorf("parent interface %s does not exists", network.NetworkInterface) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(network.Subnets) == 0 { | ||||
| 		network.IPAMOptions["driver"] = types.DHCPIPAMDriver | ||||
| 	} else { | ||||
| 		network.IPAMOptions["driver"] = types.HostLocalIPAMDriver | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (n *cniNetwork) createBridge(network *types.Network) error { | ||||
| 	if network.NetworkInterface != "" { | ||||
| 		bridges := n.getBridgeInterfaceNames() | ||||
| 		if pkgutil.StringInSlice(network.NetworkInterface, bridges) { | ||||
| 			return errors.Errorf("bridge name %s already in use", network.NetworkInterface) | ||||
| 		} | ||||
| 		if !define.NameRegex.MatchString(network.NetworkInterface) { | ||||
| 			return errors.Wrapf(define.RegexError, "bridge name %s invalid", network.NetworkInterface) | ||||
| 		} | ||||
| 	} else { | ||||
| 		var err error | ||||
| 		network.NetworkInterface, err = n.getFreeDeviceName() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if len(network.Subnets) == 0 { | ||||
| 		freeSubnet, err := n.getFreeIPv4NetworkSubnet() | ||||
| 		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 := n.getFreeIPv4NetworkSubnet() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			network.Subnets = append(network.Subnets, *freeSubnet) | ||||
| 		} | ||||
| 		if !ipv6 { | ||||
| 			freeSubnet, err := n.getFreeIPv6NetworkSubnet() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			network.Subnets = append(network.Subnets, *freeSubnet) | ||||
| 		} | ||||
| 	} | ||||
| 	network.IPAMOptions["driver"] = types.HostLocalIPAMDriver | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // 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) error { | ||||
| 	if s == nil { | ||||
| 		return errors.New("subnet 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.
 | ||||
| 	_, net, err := net.ParseCIDR(s.Subnet.String()) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "subnet invalid") | ||||
| 	} | ||||
| 	s.Subnet = types.IPNet{IPNet: *net} | ||||
| 	if s.Gateway != nil { | ||||
| 		if !s.Subnet.Contains(s.Gateway) { | ||||
| 			return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet) | ||||
| 		} | ||||
| 	} else if addGateway { | ||||
| 		ip, err := util.FirstIPInSubnet(net) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		s.Gateway = ip | ||||
| 	} | ||||
| 	if s.LeaseRange != nil { | ||||
| 		if s.LeaseRange.StartIP != nil && !s.Subnet.Contains(s.LeaseRange.StartIP) { | ||||
| 			return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet) | ||||
| 		} | ||||
| 		if s.LeaseRange.EndIP != nil && !s.Subnet.Contains(s.LeaseRange.EndIP) { | ||||
| 			return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,340 @@ | |||
| // +build linux
 | ||||
| 
 | ||||
| package cni | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/sha256" | ||||
| 	"encoding/hex" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/containernetworking/cni/libcni" | ||||
| 	"github.com/containers/podman/v3/libpod/define" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/libpod/network/util" | ||||
| 	pkgutil "github.com/containers/podman/v3/pkg/util" | ||||
| 	"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 | ||||
| 
 | ||||
| 	// 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 | ||||
| 
 | ||||
| 	// LockFile is the path to lock file.
 | ||||
| 	LockFile string | ||||
| } | ||||
| 
 | ||||
| // 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(conf.LockFile) | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| func (n *cniNetwork) loadNetworks() error { | ||||
| 	// skip loading networks if they are already loaded
 | ||||
| 	if n.networks != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	// 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 !os.IsNotExist(err) { | ||||
| 				logrus.Warnf("Error loading CNI config file %s: %v", file, err) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if !define.NameRegex.MatchString(conf.Name) { | ||||
| 			logrus.Warnf("CNI config list %s has invalid name, skipping: %v", file, define.RegexError) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		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.Tracef("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, false) | ||||
| } | ||||
| 
 | ||||
| // 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(define.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[:]) | ||||
| } | ||||
| 
 | ||||
| // getFreeIPv6NetworkSubnet returns a unused ipv4 subnet
 | ||||
| func (n *cniNetwork) getFreeIPv4NetworkSubnet() (*types.Subnet, error) { | ||||
| 	networks, err := n.getUsedSubnets() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// 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 := util.NetworkIntersectsWithNetworks(network, networks); !intersectsConfig { | ||||
| 			logrus.Debugf("found free ipv4 network subnet %s", network.String()) | ||||
| 			return &types.Subnet{ | ||||
| 				Subnet: types.IPNet{IPNet: *network}, | ||||
| 			}, nil | ||||
| 		} | ||||
| 		network, err = util.NextSubnet(network) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // getFreeIPv6NetworkSubnet returns a unused ipv6 subnet
 | ||||
| func (n *cniNetwork) getFreeIPv6NetworkSubnet() (*types.Subnet, error) { | ||||
| 	networks, err := n.getUsedSubnets() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// 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 := util.GetRandomIPv6Subnet() | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if intersectsConfig := util.NetworkIntersectsWithNetworks(&network, networks); !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") | ||||
| } | ||||
| 
 | ||||
| // getUsedSubnets returns a list of all used subnets by network
 | ||||
| // configs and interfaces on the host.
 | ||||
| func (n *cniNetwork) getUsedSubnets() ([]*net.IPNet, error) { | ||||
| 	// first, load all used subnets from network configs
 | ||||
| 	subnets := make([]*net.IPNet, 0, len(n.networks)) | ||||
| 	for _, val := range n.networks { | ||||
| 		for _, subnet := range val.libpodNet.Subnets { | ||||
| 			// nolint:exportloopref
 | ||||
| 			subnets = append(subnets, &subnet.Subnet.IPNet) | ||||
| 		} | ||||
| 	} | ||||
| 	// second, load networks from the current system
 | ||||
| 	liveSubnets, err := util.GetLiveNetworkSubnets() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return append(subnets, liveSubnets...), nil | ||||
| } | ||||
| 
 | ||||
| // getFreeDeviceName returns a free device name which can
 | ||||
| // be used for new configs as name and bridge interface name
 | ||||
| func (n *cniNetwork) getFreeDeviceName() (string, error) { | ||||
| 	bridgeNames := n.getBridgeInterfaceNames() | ||||
| 	netNames := n.getUsedNetworkNames() | ||||
| 	liveInterfaces, err := util.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", cniDeviceName, i) | ||||
| 		if !pkgutil.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") | ||||
| } | ||||
| 
 | ||||
| // getUsedNetworkNames returns all network names already used
 | ||||
| // by network configs
 | ||||
| func (n *cniNetwork) getUsedNetworkNames() []string { | ||||
| 	names := make([]string, 0, len(n.networks)) | ||||
| 	for _, val := range n.networks { | ||||
| 		names = append(names, val.libpodNet.Name) | ||||
| 	} | ||||
| 	return names | ||||
| } | ||||
| 
 | ||||
| // getUsedNetworkNames returns all bridge device names already used
 | ||||
| // by network configs
 | ||||
| func (n *cniNetwork) getBridgeInterfaceNames() []string { | ||||
| 	names := make([]string, 0, len(n.networks)) | ||||
| 	for _, val := range n.networks { | ||||
| 		if val.libpodNet.Driver == types.BridgeNetworkDriver { | ||||
| 			names = append(names, val.libpodNet.NetworkInterface) | ||||
| 		} | ||||
| 	} | ||||
| 	return names | ||||
| } | ||||
|  | @ -0,0 +1,309 @@ | |||
| // +build linux
 | ||||
| 
 | ||||
| package cni | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/containernetworking/cni/libcni" | ||||
| 	cnitypes "github.com/containernetworking/cni/pkg/types" | ||||
| 	"github.com/containernetworking/cni/pkg/types/current" | ||||
| 	"github.com/containernetworking/plugins/pkg/ns" | ||||
| 	"github.com/containers/podman/v3/libpod/define" | ||||
| 	"github.com/containers/podman/v3/libpod/network/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 | ||||
| 	} | ||||
| 
 | ||||
| 	if namespacePath == "" { | ||||
| 		return nil, errors.New("namespacePath is empty") | ||||
| 	} | ||||
| 	if options.ContainerID == "" { | ||||
| 		return nil, errors.New("ContainerID is empty") | ||||
| 	} | ||||
| 	if len(options.Networks) == 0 { | ||||
| 		return nil, errors.New("must specify at least one network") | ||||
| 	} | ||||
| 	for name, netOpts := range options.Networks { | ||||
| 		network := n.networks[name] | ||||
| 		if network == nil { | ||||
| 			return nil, errors.Wrapf(define.ErrNoSuchNetwork, "network %s", name) | ||||
| 		} | ||||
| 		err := validatePerNetworkOpts(network, netOpts) | ||||
| 		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 { | ||||
| 		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 | ||||
| 		} | ||||
| 
 | ||||
| 		var cnires *current.Result | ||||
| 		cnires, retErr = current.GetResult(res) | ||||
| 		if retErr != nil { | ||||
| 			return nil, retErr | ||||
| 		} | ||||
| 		logrus.Debugf("cni result for container %s network %s: %v", options.ContainerID, name, cnires) | ||||
| 		var status types.StatusBlock | ||||
| 		status, retErr = cniResultToStatus(cnires) | ||||
| 		if retErr != nil { | ||||
| 			return nil, retErr | ||||
| 		} | ||||
| 		results[name] = status | ||||
| 	} | ||||
| 	return results, nil | ||||
| } | ||||
| 
 | ||||
| // cniResultToStatus convert the cni result to status block
 | ||||
| func cniResultToStatus(cniResult *current.Result) (types.StatusBlock, error) { | ||||
| 	result := types.StatusBlock{} | ||||
| 	nameservers := make([]net.IP, 0, len(cniResult.DNS.Nameservers)) | ||||
| 	for _, nameserver := range cniResult.DNS.Nameservers { | ||||
| 		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.Networks = append(netInt.Networks, types.NetAddress{ | ||||
| 				Subnet:  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: mac, | ||||
| 				Networks: []types.NetAddress{{ | ||||
| 					Subnet:  types.IPNet{IPNet: ip.Address}, | ||||
| 					Gateway: ip.Gateway, | ||||
| 				}}, | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	result.Interfaces = interfaces | ||||
| 	return result, nil | ||||
| } | ||||
| 
 | ||||
| // validatePerNetworkOpts checks that all given static ips are in a subnet on this network
 | ||||
| func validatePerNetworkOpts(network *network, netOpts types.PerNetworkOptions) error { | ||||
| 	if netOpts.InterfaceName == "" { | ||||
| 		return errors.Errorf("interface name on network %s is empty", network.libpodNet.Name) | ||||
| 	} | ||||
| outer: | ||||
| 	for _, ip := range netOpts.StaticIPs { | ||||
| 		for _, s := range network.libpodNet.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.libpodNet.Name) | ||||
| 	} | ||||
| 	if len(netOpts.Aliases) > 0 && !network.libpodNet.DNSEnabled { | ||||
| 		return errors.New("cannot set aliases on a network without dns enabled") | ||||
| 	} | ||||
| 	return 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"}, | ||||
| 			// FIXME: Should we set the K8S args?
 | ||||
| 			//{"K8S_POD_NAMESPACE", conName},
 | ||||
| 			//{"K8S_POD_INFRA_CONTAINER_ID", conID},
 | ||||
| 			// 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 { | ||||
| 		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(define.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 | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -0,0 +1,25 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "bridge", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman9", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.8.0/24", | ||||
|                      "gateway": "10.89.8.1" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|  | @ -0,0 +1,51 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "invalidgw", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman8", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.8.0/24", | ||||
|                      "gateway": "10.89.8", | ||||
|                      "rangeStart": "10.89.8.20", | ||||
|                      "rangeEnd": "10.89.8.50" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,49 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "bridge@123", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman9", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.8.0/24", | ||||
|                      "gateway": "10.89.8.1" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,48 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman9", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.8.0/24", | ||||
|                      "gateway": "10.89.8.1" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,5 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "bridge", | ||||
|    "plugins": [] | ||||
| } | ||||
|  | @ -0,0 +1,49 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "bridge", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman9", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.8.0/24", | ||||
|                      "gateway": "10.89.8.1" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,49 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "bridge", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman9", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.8.0/24", | ||||
|                      "gateway": "10.89.8.1" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,37 @@ | |||
| { | ||||
|   "cniVersion": "0.4.0", | ||||
|   "name": "podman", | ||||
|   "plugins": [ | ||||
|     { | ||||
|       "type": "bridge", | ||||
|       "bridge": "cni-podman0", | ||||
|       "isGateway": true, | ||||
|       "ipMasq": true, | ||||
|       "hairpinMode": true, | ||||
|       "ipam": { | ||||
|         "type": "host-local", | ||||
|         "routes": [{ "dst": "0.0.0.0/0" }], | ||||
|         "ranges": [ | ||||
|           [ | ||||
|             { | ||||
|               "subnet": "10.88.0.0/16", | ||||
|               "gateway": "10.88.0.1" | ||||
|             } | ||||
|           ] | ||||
|         ] | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "type": "portmap", | ||||
|       "capabilities": { | ||||
|         "portMappings": true | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "type": "firewall" | ||||
|     }, | ||||
|     { | ||||
|       "type": "tuning" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|  | @ -0,0 +1,51 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "bridge", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman9", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.8.0/24", | ||||
|                      "gateway": "10.89.8.1", | ||||
|                      "rangeStart": "10.89.8.20", | ||||
|                      "rangeEnd": "10.89.8.50" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,58 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "dualstack", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman21", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "::/0" | ||||
|                }, | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "fd10:88:a::/64", | ||||
|                      "gateway": "fd10:88:a::1" | ||||
|                   } | ||||
|                ], | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.19.0/24", | ||||
|                      "gateway": "10.89.19.10" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,40 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "internal", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman8", | ||||
|          "isGateway": false, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.7.0/24" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,54 @@ | |||
| { | ||||
|    "args": { | ||||
|       "podman_labels": { | ||||
|          "mykey": "value" | ||||
|       } | ||||
|    }, | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "label", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman15", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.13.0/24", | ||||
|                      "gateway": "10.89.13.1" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,13 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "macvlan", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "macvlan", | ||||
|          "master": "lo", | ||||
|          "ipam": { | ||||
|             "type": "dhcp" | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,14 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "macvlan_mtu", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "macvlan", | ||||
|          "master": "lo", | ||||
|          "ipam": { | ||||
|             "type": "dhcp" | ||||
|          }, | ||||
|          "mtu": 1300 | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,49 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "mtu", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman13", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "mtu": 1500, | ||||
|          "hairpinMode": true, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.11.0/24" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,50 @@ | |||
| { | ||||
|    "cniVersion": "0.4.0", | ||||
|    "name": "vlan", | ||||
|    "plugins": [ | ||||
|       { | ||||
|          "type": "bridge", | ||||
|          "bridge": "cni-podman14", | ||||
|          "isGateway": true, | ||||
|          "ipMasq": true, | ||||
|          "hairpinMode": true, | ||||
|          "vlan": 5, | ||||
|          "ipam": { | ||||
|             "type": "host-local", | ||||
|             "routes": [ | ||||
|                { | ||||
|                   "dst": "0.0.0.0/0" | ||||
|                } | ||||
|             ], | ||||
|             "ranges": [ | ||||
|                [ | ||||
|                   { | ||||
|                      "subnet": "10.89.12.0/24", | ||||
|                      "gateway": "10.89.12.1" | ||||
|                   } | ||||
|                ] | ||||
|             ] | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "portmap", | ||||
|          "capabilities": { | ||||
|             "portMappings": true | ||||
|          } | ||||
|       }, | ||||
|       { | ||||
|          "type": "firewall", | ||||
|          "backend": "" | ||||
|       }, | ||||
|       { | ||||
|          "type": "tuning" | ||||
|       }, | ||||
|       { | ||||
|          "type": "dnsname", | ||||
|          "domainName": "dns.podman", | ||||
|          "capabilities": { | ||||
|             "aliases": true | ||||
|          } | ||||
|       } | ||||
|    ] | ||||
| } | ||||
|  | @ -0,0 +1,21 @@ | |||
| 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" | ||||
| 
 | ||||
| 	// 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" | ||||
| ) | ||||
|  | @ -0,0 +1,208 @@ | |||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"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 | ||||
| } | ||||
| 
 | ||||
| // Network describes the Network attributes.
 | ||||
| type Network struct { | ||||
| 	// Name of the Network.
 | ||||
| 	Name string `json:"name,omitempty"` | ||||
| 	// ID of the Network.
 | ||||
| 	ID string `json:"id,omitempty"` | ||||
| 	// Driver for this Network, e.g. bridge, macvlan...
 | ||||
| 	Driver string `json:"driver,omitempty"` | ||||
| 	// InterfaceName is the network interface name on the host.
 | ||||
| 	NetworkInterface string `json:"network_interface,omitempty"` | ||||
| 	// Created contains the timestamp when this network was created.
 | ||||
| 	// This is not guaranteed to stay exactly the same.
 | ||||
| 	Created time.Time | ||||
| 	// Subnets to use.
 | ||||
| 	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, net, err := net.ParseCIDR(cidr) | ||||
| 	if err != nil { | ||||
| 		return IPNet{}, err | ||||
| 	} | ||||
| 	// convert to 4 bytes if ipv4
 | ||||
| 	ipv4 := ip.To4() | ||||
| 	if ipv4 != nil { | ||||
| 		ip = ipv4 | ||||
| 	} | ||||
| 	net.IP = ip | ||||
| 	return IPNet{*net}, err | ||||
| } | ||||
| 
 | ||||
| func (n *IPNet) MarshalText() ([]byte, error) { | ||||
| 	return []byte(n.String()), nil | ||||
| } | ||||
| 
 | ||||
| func (n *IPNet) UnmarshalText(text []byte) error { | ||||
| 	net, err := ParseCIDR(string(text)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	*n = net | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type Subnet struct { | ||||
| 	// Subnet for this Network.
 | ||||
| 	Subnet IPNet `json:"subnet,omitempty"` | ||||
| 	// Gateway IP for this Network.
 | ||||
| 	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.
 | ||||
| 	StartIP net.IP `json:"start_ip,omitempty"` | ||||
| 	// EndIP last IP in the subnet which should be used to assign ips.
 | ||||
| 	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 { | ||||
| 	// Networks list of assigned subnets with their gateway.
 | ||||
| 	Networks []NetAddress `json:"networks,omitempty"` | ||||
| 	// MacAddress for this Interface.
 | ||||
| 	MacAddress net.HardwareAddr `json:"mac_address,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // NetAddress contains the subnet and gatway.
 | ||||
| type NetAddress struct { | ||||
| 	// Subnet of this NetAddress. Note that the subnet contains the
 | ||||
| 	// actual ip of the net interface and not the network address.
 | ||||
| 	Subnet IPNet `json:"subnet,omitempty"` | ||||
| 	// Gateway for the Subnet. This can be nil 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 { | ||||
| 	// StaticIPv4 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. Can only be set when DNSEnabled is true on the Network.
 | ||||
| 	// Optional.
 | ||||
| 	Aliases []string `json:"aliases,omitempty"` | ||||
| 	// StaticMac for this container. Optional.
 | ||||
| 	StaticMAC net.HardwareAddr `json:"static_mac,omitempty"` | ||||
| 	// InterfaceName for this container. Required.
 | ||||
| 	InterfaceName string `json:"interface_name,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // 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,omitempty"` | ||||
| 	// ContainerName is the container name, used as dns name.
 | ||||
| 	ContainerName string `json:"container_name,omitempty"` | ||||
| 	// 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,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // 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,omitempty"` | ||||
| 	// 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,omitempty"` | ||||
| 	// 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,omitempty"` | ||||
| 	// 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,omitempty"` | ||||
| } | ||||
| 
 | ||||
| type SetupOptions struct { | ||||
| 	NetworkOptions | ||||
| } | ||||
| 
 | ||||
| type TeardownOptions struct { | ||||
| 	NetworkOptions | ||||
| } | ||||
| 
 | ||||
| // FilterFunc can be passed to NetworkList to filter the networks.
 | ||||
| type FilterFunc func(Network) bool | ||||
|  | @ -0,0 +1,55 @@ | |||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/pkg/util" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| func GenerateNetworkFilters(filters map[string][]string) ([]types.FilterFunc, error) { | ||||
| 	filterFuncs := make([]types.FilterFunc, 0, len(filters)) | ||||
| 	for key, filterValues := range filters { | ||||
| 		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 "label": | ||||
| 		// matches all labels
 | ||||
| 		return func(net types.Network) bool { | ||||
| 			return util.MatchLabelFilters(filterValues, net.Labels) | ||||
| 		}, nil | ||||
| 
 | ||||
| 	case "driver": | ||||
| 		// matches network driver
 | ||||
| 		return func(net types.Network) bool { | ||||
| 			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 | ||||
| 
 | ||||
| 	// FIXME: What should we do with the old plugin filter
 | ||||
| 	// TODO: add dangling, dns enabled, internal filter
 | ||||
| 
 | ||||
| 	default: | ||||
| 		return nil, errors.Errorf("invalid filter %q", key) | ||||
| 	} | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
|  | @ -0,0 +1,113 @@ | |||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/rand" | ||||
| 	"net" | ||||
| 
 | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
| } | ||||
| 
 | ||||
| // LastIPInSubnet gets the last IP in a subnet
 | ||||
| func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
 | ||||
| 	// re-parse to ensure clean network address
 | ||||
| 	_, cidr, err := net.ParseCIDR(addr.String()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ones, bits := cidr.Mask.Size() | ||||
| 	if ones == bits { | ||||
| 		return cidr.IP, nil | ||||
| 	} | ||||
| 	for i := range cidr.IP { | ||||
| 		cidr.IP[i] = cidr.IP[i] | ^cidr.Mask[i] | ||||
| 	} | ||||
| 	return cidr.IP, nil | ||||
| } | ||||
| 
 | ||||
| // FirstIPInSubnet gets the first IP in a subnet
 | ||||
| func FirstIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
 | ||||
| 	// re-parse to ensure clean network address
 | ||||
| 	_, cidr, err := net.ParseCIDR(addr.String()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	ones, bits := cidr.Mask.Size() | ||||
| 	if ones == bits { | ||||
| 		return cidr.IP, nil | ||||
| 	} | ||||
| 	cidr.IP[len(cidr.IP)-1]++ | ||||
| 	return cidr.IP, nil | ||||
| } | ||||
| 
 | ||||
| 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{}, nil | ||||
| 	} | ||||
| 	// 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 | ||||
| } | ||||
|  | @ -0,0 +1,125 @@ | |||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func parseCIDR(n string) *net.IPNet { | ||||
| 	_, parsedNet, _ := net.ParseCIDR(n) | ||||
| 	return parsedNet | ||||
| } | ||||
| 
 | ||||
| func TestNextSubnet(t *testing.T) { | ||||
| 	type args struct { | ||||
| 		subnet *net.IPNet | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		args    args | ||||
| 		want    *net.IPNet | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{"class b", args{subnet: parseCIDR("192.168.0.0/16")}, parseCIDR("192.169.0.0/16"), false}, | ||||
| 		{"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		test := tt | ||||
| 		t.Run(test.name, func(t *testing.T) { | ||||
| 			got, err := NextSubnet(test.args.subnet) | ||||
| 			if (err != nil) != test.wantErr { | ||||
| 				t.Errorf("NextSubnet() error = %v, wantErr %v", err, test.wantErr) | ||||
| 				return | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(got, test.want) { | ||||
| 				t.Errorf("NextSubnet() got = %v, want %v", got, test.want) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestFirstIPInSubnet(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		args    *net.IPNet | ||||
| 		want    net.IP | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{"class b", parseCIDR("192.168.0.0/16"), net.ParseIP("192.168.0.1"), false}, | ||||
| 		{"class c", parseCIDR("192.168.1.0/24"), net.ParseIP("192.168.1.1"), false}, | ||||
| 		{"cidr /23", parseCIDR("192.168.0.0/23"), net.ParseIP("192.168.0.1"), false}, | ||||
| 		{"cidr /25", parseCIDR("192.168.1.0/25"), net.ParseIP("192.168.1.1"), false}, | ||||
| 		{"cidr /26", parseCIDR("172.16.1.128/26"), net.ParseIP("172.16.1.129"), false}, | ||||
| 		{"class a", parseCIDR("10.0.0.0/8"), net.ParseIP("10.0.0.1"), false}, | ||||
| 		{"cidr /32", parseCIDR("192.168.255.4/32"), net.ParseIP("192.168.255.4"), false}, | ||||
| 		{"cidr /31", parseCIDR("192.168.255.4/31"), net.ParseIP("192.168.255.5"), false}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		test := tt | ||||
| 		t.Run(test.name, func(t *testing.T) { | ||||
| 			got, err := FirstIPInSubnet(test.args) | ||||
| 			if (err != nil) != test.wantErr { | ||||
| 				t.Errorf("FirstIPInSubnet() error = %v, wantErr %v", err, test.wantErr) | ||||
| 				return | ||||
| 			} | ||||
| 			if !got.Equal(test.want) { | ||||
| 				t.Errorf("FirstIPInSubnet() got = %v, want %v", got, test.want) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestLastIPInSubnet(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| 		args    *net.IPNet | ||||
| 		want    net.IP | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{"class b", parseCIDR("192.168.0.0/16"), net.ParseIP("192.168.255.255"), false}, | ||||
| 		{"class c", parseCIDR("192.168.1.0/24"), net.ParseIP("192.168.1.255"), false}, | ||||
| 		{"cidr /23", parseCIDR("192.168.0.0/23"), net.ParseIP("192.168.1.255"), false}, | ||||
| 		{"cidr /25", parseCIDR("192.168.1.0/25"), net.ParseIP("192.168.1.127"), false}, | ||||
| 		{"cidr /26", parseCIDR("172.16.1.128/26"), net.ParseIP("172.16.1.191"), false}, | ||||
| 		{"class a", parseCIDR("10.0.0.0/8"), net.ParseIP("10.255.255.255"), false}, | ||||
| 		{"cidr /32", parseCIDR("192.168.255.4/32"), net.ParseIP("192.168.255.4"), false}, | ||||
| 		{"cidr /31", parseCIDR("192.168.255.4/31"), net.ParseIP("192.168.255.5"), false}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		test := tt | ||||
| 		t.Run(test.name, func(t *testing.T) { | ||||
| 			got, err := LastIPInSubnet(test.args) | ||||
| 			if (err != nil) != test.wantErr { | ||||
| 				t.Errorf("LastIPInSubnet() error = %v, wantErr %v", err, test.wantErr) | ||||
| 				return | ||||
| 			} | ||||
| 			if !got.Equal(test.want) { | ||||
| 				t.Errorf("LastIPInSubnet() got = %v, want %v", got, test.want) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestGetRandomIPv6Subnet(t *testing.T) { | ||||
| 	for i := 0; i < 1000; i++ { | ||||
| 		t.Run(fmt.Sprintf("GetRandomIPv6Subnet %d", i), func(t *testing.T) { | ||||
| 			sub, err := GetRandomIPv6Subnet() | ||||
| 			if err != nil { | ||||
| 				t.Errorf("GetRandomIPv6Subnet() error should be nil: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 			if sub.IP.To4() != nil { | ||||
| 				t.Errorf("ip %s is not an ipv6 address", sub.IP) | ||||
| 			} | ||||
| 			if sub.IP[0] != 0xfd { | ||||
| 				t.Errorf("ipv6 %s does not start with fd", sub.IP) | ||||
| 			} | ||||
| 			ones, bytes := sub.Mask.Size() | ||||
| 			if ones != 64 || bytes != 128 { | ||||
| 				t.Errorf("wrong network mask %v, it should be /64", sub.Mask) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | @ -8,6 +8,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/containers/image/v5/types" | ||||
| 	"github.com/containers/podman/v3/libpod/define" | ||||
| 	nettypes "github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/pkg/specgen" | ||||
| 	"github.com/containers/storage/pkg/archive" | ||||
| 	"github.com/cri-o/ocicni/pkg/ocicni" | ||||
|  | @ -208,7 +209,7 @@ type RestoreOptions struct { | |||
| 	Name            string | ||||
| 	TCPEstablished  bool | ||||
| 	ImportPrevious  string | ||||
| 	PublishPorts    []specgen.PortMapping | ||||
| 	PublishPorts    []nettypes.PortMapping | ||||
| 	Pod             string | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import ( | |||
| 	buildahDefine "github.com/containers/buildah/define" | ||||
| 	"github.com/containers/podman/v3/libpod/define" | ||||
| 	"github.com/containers/podman/v3/libpod/events" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/pkg/specgen" | ||||
| 	"github.com/containers/storage/pkg/archive" | ||||
| ) | ||||
|  | @ -40,7 +41,7 @@ type NetOptions struct { | |||
| 	DNSServers         []net.IP | ||||
| 	Network            specgen.Namespace | ||||
| 	NoHosts            bool | ||||
| 	PublishPorts       []specgen.PortMapping | ||||
| 	PublishPorts       []types.PortMapping | ||||
| 	StaticIP           *net.IP | ||||
| 	StaticMAC          *net.HardwareAddr | ||||
| 	// NetworkOptions are additional options for each network
 | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import ( | |||
| 	"github.com/containers/common/pkg/parse" | ||||
| 	"github.com/containers/common/pkg/secrets" | ||||
| 	"github.com/containers/image/v5/manifest" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	ann "github.com/containers/podman/v3/pkg/annotations" | ||||
| 	"github.com/containers/podman/v3/pkg/specgen" | ||||
| 	"github.com/containers/podman/v3/pkg/specgen/generate" | ||||
|  | @ -588,8 +589,8 @@ func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) { | |||
| 
 | ||||
| // getPodPorts converts a slice of kube container descriptions to an
 | ||||
| // array of portmapping
 | ||||
| func getPodPorts(containers []v1.Container) []specgen.PortMapping { | ||||
| 	var infraPorts []specgen.PortMapping | ||||
| func getPodPorts(containers []v1.Container) []types.PortMapping { | ||||
| 	var infraPorts []types.PortMapping | ||||
| 	for _, container := range containers { | ||||
| 		for _, p := range container.Ports { | ||||
| 			if p.HostPort != 0 && p.ContainerPort == 0 { | ||||
|  | @ -598,7 +599,7 @@ func getPodPorts(containers []v1.Container) []specgen.PortMapping { | |||
| 			if p.Protocol == "" { | ||||
| 				p.Protocol = "tcp" | ||||
| 			} | ||||
| 			portBinding := specgen.PortMapping{ | ||||
| 			portBinding := types.PortMapping{ | ||||
| 				HostPort:      uint16(p.HostPort), | ||||
| 				ContainerPort: uint16(p.ContainerPort), | ||||
| 				Protocol:      strings.ToLower(string(p.Protocol)), | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/containers/common/libimage" | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/podman/v3/utils" | ||||
| 
 | ||||
| 	"github.com/containers/podman/v3/pkg/specgen" | ||||
|  | @ -24,7 +25,7 @@ const ( | |||
| // Parse port maps to OCICNI port mappings.
 | ||||
| // Returns a set of OCICNI port mappings, and maps of utilized container and
 | ||||
| // host ports.
 | ||||
| func ParsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) { | ||||
| func ParsePortMapping(portMappings []types.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) { | ||||
| 	// First, we need to validate the ports passed in the specgen, and then
 | ||||
| 	// convert them into CNI port mappings.
 | ||||
| 	type tempMapping struct { | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ package specgen | |||
| import ( | ||||
| 	"net" | ||||
| 
 | ||||
| 	"github.com/containers/podman/v3/libpod/network/types" | ||||
| 	spec "github.com/opencontainers/runtime-spec/specs-go" | ||||
| ) | ||||
| 
 | ||||
|  | @ -102,7 +103,7 @@ type PodNetworkConfig struct { | |||
| 	// container, this will forward the ports to the entire pod.
 | ||||
| 	// Only available if NetNS is set to Bridge or Slirp.
 | ||||
| 	// Optional.
 | ||||
| 	PortMappings []PortMapping `json:"portmappings,omitempty"` | ||||
| 	PortMappings []types.PortMapping `json:"portmappings,omitempty"` | ||||
| 	// CNINetworks is a list of CNI networks that the infra container will
 | ||||
| 	// join. As, by default, containers share their network with the infra
 | ||||
| 	// container, these networks will effectively be joined by the
 | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import ( | |||
| 	"syscall" | ||||
| 
 | ||||
| 	"github.com/containers/image/v5/manifest" | ||||
| 	nettypes "github.com/containers/podman/v3/libpod/network/types" | ||||
| 	"github.com/containers/storage/types" | ||||
| 	spec "github.com/opencontainers/runtime-spec/specs-go" | ||||
| 	"github.com/pkg/errors" | ||||
|  | @ -393,7 +394,7 @@ type ContainerNetworkConfig struct { | |||
| 	// PortBindings is a set of ports to map into the container.
 | ||||
| 	// Only available if NetNS is set to bridge or slirp.
 | ||||
| 	// Optional.
 | ||||
| 	PortMappings []PortMapping `json:"portmappings,omitempty"` | ||||
| 	PortMappings []nettypes.PortMapping `json:"portmappings,omitempty"` | ||||
| 	// PublishExposedPorts will publish ports specified in the image to
 | ||||
| 	// random unused ports (guaranteed to be above 1024) on the host.
 | ||||
| 	// This is based on ports set in Expose below, and any ports specified
 | ||||
|  | @ -506,36 +507,6 @@ type SpecGenerator struct { | |||
| 	ContainerHealthCheckConfig | ||||
| } | ||||
| 
 | ||||
| // 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,omitempty"` | ||||
| 	// 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,omitempty"` | ||||
| 	// 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,omitempty"` | ||||
| 	// 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,omitempty"` | ||||
| } | ||||
| 
 | ||||
| type Secret struct { | ||||
| 	Source string | ||||
| 	UID    uint32 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue