Merge pull request #11232 from Luap99/network

Network interface
This commit is contained in:
OpenShift Merge Robot
2021-08-24 10:30:11 -04:00
committed by GitHub
40 changed files with 5577 additions and 45 deletions

View File

@ -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),

View File

@ -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")
}

View File

@ -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.

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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,
}
}

View File

@ -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

View File

@ -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
}

309
libpod/network/cni/run.go Normal file
View File

@ -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

View File

@ -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"
}
]
]

View File

@ -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
}
}
]
}

View File

@ -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
}
}
]
}

View File

@ -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
}
}
]
}

View File

@ -0,0 +1,5 @@
{
"cniVersion": "0.4.0",
"name": "bridge",
"plugins": []
}

View File

@ -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
}
}
]
}

View File

@ -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
}
}
]
}

View File

@ -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"
}
]
}

View File

@ -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
}
}
]
}

View File

@ -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
}
}
]
}

View File

@ -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"
}
]
}

View File

@ -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
}
}
]
}

View File

@ -0,0 +1,13 @@
{
"cniVersion": "0.4.0",
"name": "macvlan",
"plugins": [
{
"type": "macvlan",
"master": "lo",
"ipam": {
"type": "dhcp"
}
}
]
}

View File

@ -0,0 +1,14 @@
{
"cniVersion": "0.4.0",
"name": "macvlan_mtu",
"plugins": [
{
"type": "macvlan",
"master": "lo",
"ipam": {
"type": "dhcp"
},
"mtu": 1300
}
]
}

View File

@ -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
}
}
]
}

View File

@ -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
}
}
]
}

View File

@ -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"
)

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
}

113
libpod/network/util/ip.go Normal file
View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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)),

View File

@ -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 {

View File

@ -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

View File

@ -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