mirror of https://github.com/containers/podman.git
Network interface
Implement a new network interface to abstract CNI from libpod. The interface is implemented for the CNI backend but in the future we can add more backends. The code is structured in three new packages: - `libpod/network/types`: contains the interface definition and the necessary types for it. - `libpod/network/cni` contains the interface implementation for the CNI backend. - `libpod/network/util` a set of utility functions related to networking. The CNI package uses ginkgo style unit tests. To test Setup/Teardown the test must be run as root. Each test will run in their own namespace to make the test independent from the host environment. New features with the CNI backend: - The default network will be created in memory if it does not exists on disk. - It can set more than one static IP per container network. - Networks are loaded once from disk and only if this interface is used, e.g. for commands such as `podman info` networks are not loaded. This reduces unnecessary disk IO. This commit only adds the interface it is not wired into libpod. This requires a lot of breaking changes which will be done in a followup commit. Once this is integrated into libpod the current network code under `libpod/network` should be removed. Also the dependency on OCICNI should be dropped. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
parent
e20ec47a59
commit
c0b1edd6a4
|
|
@ -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