mirror of
https://github.com/containers/podman.git
synced 2025-10-18 19:53:58 +08:00
@ -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")
|
||||
}
|
||||
|
10
libpod/network/cni/README.md
Normal file
10
libpod/network/cni/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
This package abstracts CNI from libpod.
|
||||
It implements the `ContainerNetwork` interface defined in [libpod/network/types/network.go](../types/network.go) for the CNI backend.
|
||||
|
||||
|
||||
## Testing
|
||||
Run the tests with:
|
||||
```
|
||||
go test -v -mod=vendor -cover ./libpod/network/cni/
|
||||
```
|
||||
Run the tests as root to also test setup/teardown. This will execute CNI and therefore the cni plugins have to be installed.
|
375
libpod/network/cni/cni_conversion.go
Normal file
375
libpod/network/cni/cni_conversion.go
Normal file
@ -0,0 +1,375 @@
|
||||
// +build linux
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/containers/podman/v3/libpod/network/util"
|
||||
pkgutil "github.com/containers/podman/v3/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func createNetworkFromCNIConfigList(conf *libcni.NetworkConfigList, confPath string) (*types.Network, error) {
|
||||
network := types.Network{
|
||||
Name: conf.Name,
|
||||
ID: getNetworkIDFromName(conf.Name),
|
||||
Labels: map[string]string{},
|
||||
Options: map[string]string{},
|
||||
IPAMOptions: map[string]string{},
|
||||
}
|
||||
|
||||
cniJSON := make(map[string]interface{})
|
||||
err := json.Unmarshal(conf.Bytes, &cniJSON)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal network config %s", conf.Name)
|
||||
}
|
||||
if args, ok := cniJSON["args"]; ok {
|
||||
if key, ok := args.(map[string]interface{}); ok {
|
||||
// read network labels and options from the conf file
|
||||
network.Labels = getNetworkArgsFromConfList(key, podmanLabelKey)
|
||||
network.Options = getNetworkArgsFromConfList(key, podmanOptionsKey)
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.Stat(confPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stat := f.Sys().(*syscall.Stat_t)
|
||||
network.Created = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
|
||||
|
||||
firstPlugin := conf.Plugins[0]
|
||||
network.Driver = firstPlugin.Network.Type
|
||||
|
||||
switch firstPlugin.Network.Type {
|
||||
case types.BridgeNetworkDriver:
|
||||
var bridge hostLocalBridge
|
||||
err := json.Unmarshal(firstPlugin.Bytes, &bridge)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal the bridge plugin config in %s", confPath)
|
||||
}
|
||||
network.NetworkInterface = bridge.BrName
|
||||
|
||||
// if isGateway is false we have an internal network
|
||||
if !bridge.IsGW {
|
||||
network.Internal = true
|
||||
}
|
||||
|
||||
// set network options
|
||||
if bridge.MTU != 0 {
|
||||
network.Options["mtu"] = strconv.Itoa(bridge.MTU)
|
||||
}
|
||||
if bridge.Vlan != 0 {
|
||||
network.Options["vlan"] = strconv.Itoa(bridge.Vlan)
|
||||
}
|
||||
|
||||
err = convertIPAMConfToNetwork(&network, bridge.IPAM, confPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case types.MacVLANNetworkDriver:
|
||||
var macvlan macVLANConfig
|
||||
err := json.Unmarshal(firstPlugin.Bytes, &macvlan)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal the macvlan plugin config in %s", confPath)
|
||||
}
|
||||
network.NetworkInterface = macvlan.Master
|
||||
|
||||
// set network options
|
||||
if macvlan.MTU != 0 {
|
||||
network.Options["mtu"] = strconv.Itoa(macvlan.MTU)
|
||||
}
|
||||
|
||||
err = convertIPAMConfToNetwork(&network, macvlan.IPAM, confPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
// A warning would be good but users would get this warning everytime so keep this at info level.
|
||||
logrus.Infof("unsupported CNI config type %s in %s, this network can still be used but inspect or list cannot show all information",
|
||||
firstPlugin.Network.Type, confPath)
|
||||
}
|
||||
|
||||
// check if the dnsname plugin is configured
|
||||
network.DNSEnabled = findPluginByName(conf.Plugins, "dnsname")
|
||||
|
||||
return &network, nil
|
||||
}
|
||||
|
||||
func findPluginByName(plugins []*libcni.NetworkConfig, name string) bool {
|
||||
for _, plugin := range plugins {
|
||||
if plugin.Network.Type == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// convertIPAMConfToNetwork converts A cni IPAMConfig to libpod network subnets.
|
||||
// It returns an array of subnets and an extra bool if dhcp is configured.
|
||||
func convertIPAMConfToNetwork(network *types.Network, ipam ipamConfig, confPath string) error {
|
||||
if ipam.PluginType == types.DHCPIPAMDriver {
|
||||
network.IPAMOptions["driver"] = types.DHCPIPAMDriver
|
||||
return nil
|
||||
}
|
||||
|
||||
if ipam.PluginType != types.HostLocalIPAMDriver {
|
||||
return errors.Errorf("unsupported ipam plugin %s in %s", ipam.PluginType, confPath)
|
||||
}
|
||||
|
||||
network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
|
||||
for _, r := range ipam.Ranges {
|
||||
for _, ipam := range r {
|
||||
s := types.Subnet{}
|
||||
|
||||
// Do not use types.ParseCIDR() because we want the ip to be
|
||||
// the network address and not a random ip in the sub.
|
||||
_, sub, err := net.ParseCIDR(ipam.Subnet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Subnet = types.IPNet{IPNet: *sub}
|
||||
|
||||
// gateway
|
||||
var gateway net.IP
|
||||
if ipam.Gateway != "" {
|
||||
gateway = net.ParseIP(ipam.Gateway)
|
||||
if gateway == nil {
|
||||
return errors.Errorf("failed to parse gateway ip %s", ipam.Gateway)
|
||||
}
|
||||
// convert to 4 byte if ipv4
|
||||
ipv4 := gateway.To4()
|
||||
if ipv4 != nil {
|
||||
gateway = ipv4
|
||||
}
|
||||
} else if !network.Internal {
|
||||
// only add a gateway address if the network is not internal
|
||||
gateway, err = util.FirstIPInSubnet(sub)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to get first ip in subnet %s", sub.String())
|
||||
}
|
||||
}
|
||||
s.Gateway = gateway
|
||||
|
||||
var rangeStart net.IP
|
||||
var rangeEnd net.IP
|
||||
if ipam.RangeStart != "" {
|
||||
rangeStart = net.ParseIP(ipam.RangeStart)
|
||||
if rangeStart == nil {
|
||||
return errors.Errorf("failed to parse range start ip %s", ipam.RangeStart)
|
||||
}
|
||||
}
|
||||
if ipam.RangeEnd != "" {
|
||||
rangeEnd = net.ParseIP(ipam.RangeEnd)
|
||||
if rangeEnd == nil {
|
||||
return errors.Errorf("failed to parse range end ip %s", ipam.RangeEnd)
|
||||
}
|
||||
}
|
||||
if rangeStart != nil || rangeEnd != nil {
|
||||
s.LeaseRange = &types.LeaseRange{}
|
||||
s.LeaseRange.StartIP = rangeStart
|
||||
s.LeaseRange.EndIP = rangeEnd
|
||||
}
|
||||
network.Subnets = append(network.Subnets, s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNetworkArgsFromConfList returns the map of args in a conflist, argType should be labels or options
|
||||
func getNetworkArgsFromConfList(args map[string]interface{}, argType string) map[string]string {
|
||||
if args, ok := args[argType]; ok {
|
||||
if labels, ok := args.(map[string]interface{}); ok {
|
||||
result := make(map[string]string, len(labels))
|
||||
for k, v := range labels {
|
||||
if v, ok := v.(string); ok {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createCNIConfigListFromNetwork will create a cni config file from the given network.
|
||||
// It returns the cni config and the path to the file where the config was written.
|
||||
// Set writeToDisk to false to only add this network into memory.
|
||||
func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writeToDisk bool) (*libcni.NetworkConfigList, string, error) {
|
||||
var (
|
||||
routes []ipamRoute
|
||||
ipamRanges [][]ipamLocalHostRangeConf
|
||||
ipamConf ipamConfig
|
||||
err error
|
||||
)
|
||||
if len(network.Subnets) > 0 {
|
||||
for _, subnet := range network.Subnets {
|
||||
route, err := newIPAMDefaultRoute(util.IsIPv6(subnet.Subnet.IP))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
routes = append(routes, route)
|
||||
ipam := newIPAMLocalHostRange(subnet.Subnet, subnet.LeaseRange, subnet.Gateway)
|
||||
ipamRanges = append(ipamRanges, []ipamLocalHostRangeConf{*ipam})
|
||||
}
|
||||
ipamConf = newIPAMHostLocalConf(routes, ipamRanges)
|
||||
} else {
|
||||
ipamConf = ipamConfig{PluginType: "dhcp"}
|
||||
}
|
||||
|
||||
vlan := 0
|
||||
mtu := 0
|
||||
for k, v := range network.Options {
|
||||
switch k {
|
||||
case "mtu":
|
||||
mtu, err = parseMTU(v)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
case "vlan":
|
||||
vlan, err = parseVlan(v)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, "", errors.Errorf("unsupported network option %s", k)
|
||||
}
|
||||
}
|
||||
|
||||
isGateway := true
|
||||
ipMasq := true
|
||||
if network.Internal {
|
||||
isGateway = false
|
||||
ipMasq = false
|
||||
}
|
||||
// create CNI plugin configuration
|
||||
ncList := newNcList(network.Name, version.Current(), network.Labels, network.Options)
|
||||
var plugins []interface{}
|
||||
|
||||
switch network.Driver {
|
||||
case types.BridgeNetworkDriver:
|
||||
bridge := newHostLocalBridge(network.NetworkInterface, isGateway, ipMasq, mtu, vlan, ipamConf)
|
||||
plugins = append(plugins, bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin())
|
||||
// if we find the dnsname plugin we add configuration for it
|
||||
if hasDNSNamePlugin(n.cniPluginDirs) && network.DNSEnabled {
|
||||
// Note: in the future we might like to allow for dynamic domain names
|
||||
plugins = append(plugins, newDNSNamePlugin(defaultPodmanDomainName))
|
||||
}
|
||||
// Add the podman-machine CNI plugin if we are in a machine
|
||||
if n.isMachine {
|
||||
plugins = append(plugins, newPodmanMachinePlugin())
|
||||
}
|
||||
|
||||
case types.MacVLANNetworkDriver:
|
||||
plugins = append(plugins, newMacVLANPlugin(network.NetworkInterface, mtu, ipamConf))
|
||||
|
||||
default:
|
||||
return nil, "", errors.Errorf("driver %q is not supported by cni", network.Driver)
|
||||
}
|
||||
ncList["plugins"] = plugins
|
||||
b, err := json.MarshalIndent(ncList, "", " ")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
cniPathName := ""
|
||||
if writeToDisk {
|
||||
cniPathName = filepath.Join(n.cniConfigDir, network.Name+".conflist")
|
||||
err = ioutil.WriteFile(cniPathName, b, 0644)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
f, err := os.Stat(cniPathName)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
stat := f.Sys().(*syscall.Stat_t)
|
||||
network.Created = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
|
||||
} else {
|
||||
network.Created = time.Now()
|
||||
}
|
||||
config, err := libcni.ConfListFromBytes(b)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return config, cniPathName, nil
|
||||
}
|
||||
|
||||
// parseMTU parses the mtu option
|
||||
func parseMTU(mtu string) (int, error) {
|
||||
if mtu == "" {
|
||||
return 0, nil // default
|
||||
}
|
||||
m, err := strconv.Atoi(mtu)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if m < 0 {
|
||||
return 0, errors.Errorf("mtu %d is less than zero", m)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// parseVlan parses the vlan option
|
||||
func parseVlan(vlan string) (int, error) {
|
||||
if vlan == "" {
|
||||
return 0, nil // default
|
||||
}
|
||||
v, err := strconv.Atoi(vlan)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v < 0 || v > 4094 {
|
||||
return 0, errors.Errorf("vlan ID %d must be between 0 and 4094", v)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry, error) {
|
||||
cniPorts := make([]cniPortMapEntry, 0, len(ports))
|
||||
for _, port := range ports {
|
||||
if port.Protocol == "" {
|
||||
return nil, errors.New("port protocol should not be empty")
|
||||
}
|
||||
protocols := strings.Split(port.Protocol, ",")
|
||||
|
||||
for _, protocol := range protocols {
|
||||
if !pkgutil.StringInSlice(protocol, []string{"tcp", "udp", "sctp"}) {
|
||||
return nil, errors.Errorf("unknown port protocol %s", protocol)
|
||||
}
|
||||
cniPort := cniPortMapEntry{
|
||||
HostPort: int(port.HostPort),
|
||||
ContainerPort: int(port.ContainerPort),
|
||||
HostIP: port.HostIP,
|
||||
Protocol: protocol,
|
||||
}
|
||||
cniPorts = append(cniPorts, cniPort)
|
||||
for i := 1; i < int(port.Range); i++ {
|
||||
cniPort := cniPortMapEntry{
|
||||
HostPort: int(port.HostPort) + i,
|
||||
ContainerPort: int(port.ContainerPort) + i,
|
||||
HostIP: port.HostIP,
|
||||
Protocol: protocol,
|
||||
}
|
||||
cniPorts = append(cniPorts, cniPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cniPorts, nil
|
||||
}
|
98
libpod/network/cni/cni_exec.go
Normal file
98
libpod/network/cni/cni_exec.go
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2016 CNI authors
|
||||
// Copyright 2021 Podman authors
|
||||
//
|
||||
// This code has been originally copied from github.com/containernetworking/cni
|
||||
// but has been changed to better fit the Podman use case.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
)
|
||||
|
||||
type cniExec struct {
|
||||
version.PluginDecoder
|
||||
}
|
||||
|
||||
type cniPluginError struct {
|
||||
plugin string
|
||||
Code uint `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// Error returns a nicely formatted error message for the cni plugin errors.
|
||||
func (e *cniPluginError) Error() string {
|
||||
err := fmt.Sprintf("cni plugin %s failed", e.plugin)
|
||||
if e.Msg != "" {
|
||||
err = fmt.Sprintf("%s: %s", err, e.Msg)
|
||||
} else if e.Code > 0 {
|
||||
err = fmt.Sprintf("%s with error code %d", err, e.Code)
|
||||
}
|
||||
if e.Details != "" {
|
||||
err = fmt.Sprintf("%s: %s", err, e.Details)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecPlugin execute the cni plugin. Returns the stdout of the plugin or an error.
|
||||
func (e *cniExec) ExecPlugin(ctx context.Context, pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
c := exec.CommandContext(ctx, pluginPath)
|
||||
c.Env = environ
|
||||
c.Stdin = bytes.NewBuffer(stdinData)
|
||||
c.Stdout = stdout
|
||||
c.Stderr = stderr
|
||||
|
||||
err := c.Run()
|
||||
if err != nil {
|
||||
return nil, annotatePluginError(err, pluginPath, stdout.Bytes(), stderr.Bytes())
|
||||
}
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
// annotatePluginError parses the common cni plugin error json.
|
||||
func annotatePluginError(err error, plugin string, stdout []byte, stderr []byte) error {
|
||||
pluginName := filepath.Base(plugin)
|
||||
emsg := cniPluginError{
|
||||
plugin: pluginName,
|
||||
}
|
||||
if len(stdout) == 0 {
|
||||
if len(stderr) == 0 {
|
||||
emsg.Msg = err.Error()
|
||||
} else {
|
||||
emsg.Msg = string(stderr)
|
||||
}
|
||||
} else if perr := json.Unmarshal(stdout, &emsg); perr != nil {
|
||||
emsg.Msg = fmt.Sprintf("failed to unmarshal error message %q: %v", string(stdout), perr)
|
||||
}
|
||||
return &emsg
|
||||
}
|
||||
|
||||
// FindInPath finds the plugin in the given paths.
|
||||
func (e *cniExec) FindInPath(plugin string, paths []string) (string, error) {
|
||||
return invoke.FindInPath(plugin, paths)
|
||||
}
|
53
libpod/network/cni/cni_suite_test.go
Normal file
53
libpod/network/cni/cni_suite_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
// +build linux
|
||||
|
||||
package cni_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v3/libpod/network/cni"
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/containers/podman/v3/test/utils"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var cniPluginDirs = []string{
|
||||
"/usr/libexec/cni",
|
||||
"/usr/lib/cni",
|
||||
"/usr/local/lib/cni",
|
||||
"/opt/cni/bin",
|
||||
}
|
||||
|
||||
func TestCni(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "CNI Suite")
|
||||
}
|
||||
|
||||
func getNetworkInterface(cniConfDir string, machine bool) (types.ContainerNetwork, error) {
|
||||
return cni.NewCNINetworkInterface(cni.InitConfig{
|
||||
CNIConfigDir: cniConfDir,
|
||||
CNIPluginDirs: cniPluginDirs,
|
||||
IsMachine: machine,
|
||||
LockFile: filepath.Join(cniConfDir, "cni.lock"),
|
||||
})
|
||||
}
|
||||
|
||||
func SkipIfNoDnsname() {
|
||||
for _, path := range cniPluginDirs {
|
||||
f, err := os.Stat(filepath.Join(path, "dnsname"))
|
||||
if err == nil && f.Mode().IsRegular() {
|
||||
return
|
||||
}
|
||||
}
|
||||
Skip("dnsname cni plugin needs to be installed for this test")
|
||||
}
|
||||
|
||||
func SkipIfNotFedora(msg string) {
|
||||
info := utils.GetHostDistributionInfo()
|
||||
if info.Distribution != "fedora" {
|
||||
Skip("Test can only run on Fedora: " + msg)
|
||||
}
|
||||
}
|
292
libpod/network/cni/cni_types.go
Normal file
292
libpod/network/cni/cni_types.go
Normal file
@ -0,0 +1,292 @@
|
||||
// +build linux
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultIPv4Route = "0.0.0.0/0"
|
||||
defaultIPv6Route = "::/0"
|
||||
// defaultPodmanDomainName is used for the dnsname plugin to define
|
||||
// a localized domain name for a created network
|
||||
defaultPodmanDomainName = "dns.podman"
|
||||
|
||||
// cniDeviceName is the default name for a new bridge, it should be suffixed with an integer
|
||||
cniDeviceName = "cni-podman"
|
||||
|
||||
// podmanLabelKey key used to store the podman network label in a cni config
|
||||
podmanLabelKey = "podman_labels"
|
||||
|
||||
// podmanOptionsKey key used to store the podman network options in a cni config
|
||||
podmanOptionsKey = "podman_options"
|
||||
)
|
||||
|
||||
// cniPortMapEntry struct is used by the portmap plugin
|
||||
// https://github.com/containernetworking/plugins/blob/649e0181fe7b3a61e708f3e4249a798f57f25cc5/plugins/meta/portmap/main.go#L43-L50
|
||||
type cniPortMapEntry struct {
|
||||
HostPort int `json:"hostPort"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
HostIP string `json:"hostIP,omitempty"`
|
||||
}
|
||||
|
||||
// hostLocalBridge describes a configuration for a bridge plugin
|
||||
// https://github.com/containernetworking/plugins/tree/master/plugins/main/bridge#network-configuration-reference
|
||||
type hostLocalBridge struct {
|
||||
PluginType string `json:"type"`
|
||||
BrName string `json:"bridge,omitempty"`
|
||||
IsGW bool `json:"isGateway"`
|
||||
IsDefaultGW bool `json:"isDefaultGateway,omitempty"`
|
||||
ForceAddress bool `json:"forceAddress,omitempty"`
|
||||
IPMasq bool `json:"ipMasq,omitempty"`
|
||||
MTU int `json:"mtu,omitempty"`
|
||||
HairpinMode bool `json:"hairpinMode,omitempty"`
|
||||
PromiscMode bool `json:"promiscMode,omitempty"`
|
||||
Vlan int `json:"vlan,omitempty"`
|
||||
IPAM ipamConfig `json:"ipam"`
|
||||
Capabilities map[string]bool `json:"capabilities"`
|
||||
}
|
||||
|
||||
// ipamConfig describes an IPAM configuration
|
||||
// https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local#network-configuration-reference
|
||||
type ipamConfig struct {
|
||||
PluginType string `json:"type"`
|
||||
Routes []ipamRoute `json:"routes,omitempty"`
|
||||
ResolveConf string `json:"resolveConf,omitempty"`
|
||||
DataDir string `json:"dataDir,omitempty"`
|
||||
Ranges [][]ipamLocalHostRangeConf `json:"ranges,omitempty"`
|
||||
}
|
||||
|
||||
// ipamLocalHostRangeConf describes the new style IPAM ranges
|
||||
type ipamLocalHostRangeConf struct {
|
||||
Subnet string `json:"subnet"`
|
||||
RangeStart string `json:"rangeStart,omitempty"`
|
||||
RangeEnd string `json:"rangeEnd,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
}
|
||||
|
||||
// ipamRoute describes a route in an ipam config
|
||||
type ipamRoute struct {
|
||||
Dest string `json:"dst"`
|
||||
}
|
||||
|
||||
// portMapConfig describes the default portmapping config
|
||||
type portMapConfig struct {
|
||||
PluginType string `json:"type"`
|
||||
Capabilities map[string]bool `json:"capabilities"`
|
||||
}
|
||||
|
||||
// macVLANConfig describes the macvlan config
|
||||
type macVLANConfig struct {
|
||||
PluginType string `json:"type"`
|
||||
Master string `json:"master"`
|
||||
IPAM ipamConfig `json:"ipam"`
|
||||
MTU int `json:"mtu,omitempty"`
|
||||
Capabilities map[string]bool `json:"capabilities"`
|
||||
}
|
||||
|
||||
// firewallConfig describes the firewall plugin
|
||||
type firewallConfig struct {
|
||||
PluginType string `json:"type"`
|
||||
Backend string `json:"backend"`
|
||||
}
|
||||
|
||||
// tuningConfig describes the tuning plugin
|
||||
type tuningConfig struct {
|
||||
PluginType string `json:"type"`
|
||||
}
|
||||
|
||||
// dnsNameConfig describes the dns container name resolution plugin config
|
||||
type dnsNameConfig struct {
|
||||
PluginType string `json:"type"`
|
||||
DomainName string `json:"domainName"`
|
||||
Capabilities map[string]bool `json:"capabilities"`
|
||||
}
|
||||
|
||||
// podmanMachineConfig enables port handling on the host OS
|
||||
type podmanMachineConfig struct {
|
||||
PluginType string `json:"type"`
|
||||
Capabilities map[string]bool `json:"capabilities"`
|
||||
}
|
||||
|
||||
// ncList describes a generic map
|
||||
type ncList map[string]interface{}
|
||||
|
||||
// newNcList creates a generic map of values with string
|
||||
// keys and adds in version and network name
|
||||
func newNcList(name, version string, labels, options map[string]string) ncList {
|
||||
n := ncList{}
|
||||
n["cniVersion"] = version
|
||||
n["name"] = name
|
||||
args := map[string]map[string]string{}
|
||||
if len(labels) > 0 {
|
||||
args[podmanLabelKey] = labels
|
||||
}
|
||||
if len(options) > 0 {
|
||||
args[podmanOptionsKey] = options
|
||||
}
|
||||
if len(args) > 0 {
|
||||
n["args"] = args
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// newHostLocalBridge creates a new LocalBridge for host-local
|
||||
func newHostLocalBridge(name string, isGateWay, ipMasq bool, mtu int, vlan int, ipamConf ipamConfig) *hostLocalBridge {
|
||||
caps := make(map[string]bool)
|
||||
caps["ips"] = true
|
||||
bridge := hostLocalBridge{
|
||||
PluginType: "bridge",
|
||||
BrName: name,
|
||||
IsGW: isGateWay,
|
||||
IPMasq: ipMasq,
|
||||
MTU: mtu,
|
||||
HairpinMode: true,
|
||||
Vlan: vlan,
|
||||
IPAM: ipamConf,
|
||||
}
|
||||
// if we use host-local set the ips cap to ensure we can set static ips via runtime config
|
||||
if ipamConf.PluginType == types.HostLocalIPAMDriver {
|
||||
bridge.Capabilities = caps
|
||||
}
|
||||
return &bridge
|
||||
}
|
||||
|
||||
// newIPAMHostLocalConf creates a new IPAMHostLocal configuration
|
||||
func newIPAMHostLocalConf(routes []ipamRoute, ipamRanges [][]ipamLocalHostRangeConf) ipamConfig {
|
||||
ipamConf := ipamConfig{
|
||||
PluginType: "host-local",
|
||||
Routes: routes,
|
||||
}
|
||||
|
||||
ipamConf.Ranges = ipamRanges
|
||||
return ipamConf
|
||||
}
|
||||
|
||||
// newIPAMLocalHostRange create a new IPAM range
|
||||
func newIPAMLocalHostRange(subnet types.IPNet, leaseRange *types.LeaseRange, gw net.IP) *ipamLocalHostRangeConf {
|
||||
hostRange := &ipamLocalHostRangeConf{
|
||||
Subnet: subnet.String(),
|
||||
}
|
||||
|
||||
// an user provided a range, we add it here
|
||||
if leaseRange != nil {
|
||||
if leaseRange.StartIP != nil {
|
||||
hostRange.RangeStart = leaseRange.StartIP.String()
|
||||
}
|
||||
if leaseRange.EndIP != nil {
|
||||
hostRange.RangeStart = leaseRange.EndIP.String()
|
||||
}
|
||||
}
|
||||
|
||||
if gw != nil {
|
||||
hostRange.Gateway = gw.String()
|
||||
}
|
||||
return hostRange
|
||||
}
|
||||
|
||||
// newIPAMRoute creates a new IPAM route configuration
|
||||
// nolint:interfacer
|
||||
func newIPAMRoute(r *net.IPNet) ipamRoute {
|
||||
return ipamRoute{Dest: r.String()}
|
||||
}
|
||||
|
||||
// newIPAMDefaultRoute creates a new IPAMDefault route of
|
||||
// 0.0.0.0/0 for IPv4 or ::/0 for IPv6
|
||||
func newIPAMDefaultRoute(isIPv6 bool) (ipamRoute, error) {
|
||||
route := defaultIPv4Route
|
||||
if isIPv6 {
|
||||
route = defaultIPv6Route
|
||||
}
|
||||
_, n, err := net.ParseCIDR(route)
|
||||
if err != nil {
|
||||
return ipamRoute{}, err
|
||||
}
|
||||
return newIPAMRoute(n), nil
|
||||
}
|
||||
|
||||
// newPortMapPlugin creates a predefined, default portmapping
|
||||
// configuration
|
||||
func newPortMapPlugin() portMapConfig {
|
||||
caps := make(map[string]bool)
|
||||
caps["portMappings"] = true
|
||||
p := portMapConfig{
|
||||
PluginType: "portmap",
|
||||
Capabilities: caps,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// newFirewallPlugin creates a generic firewall plugin
|
||||
func newFirewallPlugin() firewallConfig {
|
||||
return firewallConfig{
|
||||
PluginType: "firewall",
|
||||
}
|
||||
}
|
||||
|
||||
// newTuningPlugin creates a generic tuning section
|
||||
func newTuningPlugin() tuningConfig {
|
||||
return tuningConfig{
|
||||
PluginType: "tuning",
|
||||
}
|
||||
}
|
||||
|
||||
// newDNSNamePlugin creates the dnsname config with a given
|
||||
// domainname
|
||||
func newDNSNamePlugin(domainName string) dnsNameConfig {
|
||||
caps := make(map[string]bool, 1)
|
||||
caps["aliases"] = true
|
||||
return dnsNameConfig{
|
||||
PluginType: "dnsname",
|
||||
DomainName: domainName,
|
||||
Capabilities: caps,
|
||||
}
|
||||
}
|
||||
|
||||
// hasDNSNamePlugin looks to see if the dnsname cni plugin is present
|
||||
func hasDNSNamePlugin(paths []string) bool {
|
||||
for _, p := range paths {
|
||||
if _, err := os.Stat(filepath.Join(p, "dnsname")); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// newMacVLANPlugin creates a macvlanconfig with a given device name
|
||||
func newMacVLANPlugin(device string, mtu int, ipam ipamConfig) macVLANConfig {
|
||||
m := macVLANConfig{
|
||||
PluginType: "macvlan",
|
||||
IPAM: ipam,
|
||||
}
|
||||
if mtu > 0 {
|
||||
m.MTU = mtu
|
||||
}
|
||||
// CNI is supposed to use the default route if a
|
||||
// parent device is not provided
|
||||
if len(device) > 0 {
|
||||
m.Master = device
|
||||
}
|
||||
caps := make(map[string]bool)
|
||||
caps["ips"] = true
|
||||
// if we use host-local set the ips cap to ensure we can set static ips via runtime config
|
||||
if ipam.PluginType == types.HostLocalIPAMDriver {
|
||||
m.Capabilities = caps
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func newPodmanMachinePlugin() podmanMachineConfig {
|
||||
caps := make(map[string]bool, 1)
|
||||
caps["portMappings"] = true
|
||||
return podmanMachineConfig{
|
||||
PluginType: "podman-machine",
|
||||
Capabilities: caps,
|
||||
}
|
||||
}
|
313
libpod/network/cni/config.go
Normal file
313
libpod/network/cni/config.go
Normal file
@ -0,0 +1,313 @@
|
||||
// +build linux
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/containers/podman/v3/libpod/network/util"
|
||||
pkgutil "github.com/containers/podman/v3/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// NetworkCreate will take a partial filled Network and fill the
|
||||
// missing fields. It creates the Network and returns the full Network.
|
||||
func (n *cniNetwork) NetworkCreate(net types.Network) (types.Network, error) {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
err := n.loadNetworks()
|
||||
if err != nil {
|
||||
return types.Network{}, err
|
||||
}
|
||||
network, err := n.networkCreate(net, true)
|
||||
if err != nil {
|
||||
return types.Network{}, err
|
||||
}
|
||||
// add the new network to the map
|
||||
n.networks[network.libpodNet.Name] = network
|
||||
return *network.libpodNet, nil
|
||||
}
|
||||
|
||||
func (n *cniNetwork) networkCreate(net types.Network, writeToDisk bool) (*network, error) {
|
||||
// if no driver is set use the default one
|
||||
if net.Driver == "" {
|
||||
net.Driver = types.DefaultNetworkDriver
|
||||
}
|
||||
|
||||
// FIXME: Should we use a different type for network create without the ID field?
|
||||
// the caller is not allowed to set a specific ID
|
||||
if net.ID != "" {
|
||||
return nil, errors.Wrap(define.ErrInvalidArg, "ID can not be set for network create")
|
||||
}
|
||||
|
||||
if net.Labels == nil {
|
||||
net.Labels = map[string]string{}
|
||||
}
|
||||
if net.Options == nil {
|
||||
net.Options = map[string]string{}
|
||||
}
|
||||
if net.IPAMOptions == nil {
|
||||
net.IPAMOptions = map[string]string{}
|
||||
}
|
||||
|
||||
var name string
|
||||
var err error
|
||||
// validate the name when given
|
||||
if net.Name != "" {
|
||||
if !define.NameRegex.MatchString(net.Name) {
|
||||
return nil, errors.Wrapf(define.RegexError, "network name %s invalid", net.Name)
|
||||
}
|
||||
if _, ok := n.networks[net.Name]; ok {
|
||||
return nil, errors.Wrapf(define.ErrNetworkExists, "network name %s already used", net.Name)
|
||||
}
|
||||
} else {
|
||||
name, err = n.getFreeDeviceName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
net.Name = name
|
||||
}
|
||||
|
||||
switch net.Driver {
|
||||
case types.BridgeNetworkDriver:
|
||||
// if the name was created with getFreeDeviceName set the interface to it as well
|
||||
if name != "" && net.NetworkInterface == "" {
|
||||
net.NetworkInterface = name
|
||||
}
|
||||
err = n.createBridge(&net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case types.MacVLANNetworkDriver:
|
||||
err = createMacVLAN(&net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported driver %s", net.Driver)
|
||||
}
|
||||
|
||||
for i := range net.Subnets {
|
||||
err := validateSubnet(&net.Subnets[i], !net.Internal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if util.IsIPv6(net.Subnets[i].Subnet.IP) {
|
||||
net.IPv6Enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// generate the network ID
|
||||
net.ID = getNetworkIDFromName(net.Name)
|
||||
|
||||
// FIXME: Should this be a hard error?
|
||||
if net.DNSEnabled && net.Internal && hasDNSNamePlugin(n.cniPluginDirs) {
|
||||
logrus.Warnf("dnsname and internal networks are incompatible. dnsname plugin not configured for network %s", net.Name)
|
||||
net.DNSEnabled = false
|
||||
}
|
||||
|
||||
cniConf, path, err := n.createCNIConfigListFromNetwork(&net, writeToDisk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &network{cniNet: cniConf, libpodNet: &net, filename: path}, nil
|
||||
}
|
||||
|
||||
// NetworkRemove will remove the Network with the given name or ID.
|
||||
// It does not ensure that the network is unused.
|
||||
func (n *cniNetwork) NetworkRemove(nameOrID string) error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
err := n.loadNetworks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
network, err := n.getNetwork(nameOrID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Removing the default network is not allowed.
|
||||
if network.libpodNet.Name == n.defaultNetwork {
|
||||
return errors.Errorf("default network %s cannot be removed", n.defaultNetwork)
|
||||
}
|
||||
|
||||
// Remove the bridge network interface on the host.
|
||||
if network.libpodNet.Driver == types.BridgeNetworkDriver {
|
||||
link, err := netlink.LinkByName(network.libpodNet.NetworkInterface)
|
||||
if err == nil {
|
||||
err = netlink.LinkDel(link)
|
||||
// only log the error, it is not fatal
|
||||
if err != nil {
|
||||
logrus.Infof("failed to remove network interface %s: %v", network.libpodNet.NetworkInterface, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file := network.filename
|
||||
delete(n.networks, network.libpodNet.Name)
|
||||
|
||||
return os.Remove(file)
|
||||
}
|
||||
|
||||
// NetworkList will return all known Networks. Optionally you can
|
||||
// supply a list of filter functions. Only if a network matches all
|
||||
// functions it is returned.
|
||||
func (n *cniNetwork) NetworkList(filters ...types.FilterFunc) ([]types.Network, error) {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
err := n.loadNetworks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networks := make([]types.Network, 0, len(n.networks))
|
||||
outer:
|
||||
for _, net := range n.networks {
|
||||
for _, filter := range filters {
|
||||
// All filters have to match, if one does not match we can skip to the next network.
|
||||
if !filter(*net.libpodNet) {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
networks = append(networks, *net.libpodNet)
|
||||
}
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
// NetworkInspect will return the Network with the given name or ID.
|
||||
func (n *cniNetwork) NetworkInspect(nameOrID string) (types.Network, error) {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
err := n.loadNetworks()
|
||||
if err != nil {
|
||||
return types.Network{}, err
|
||||
}
|
||||
|
||||
network, err := n.getNetwork(nameOrID)
|
||||
if err != nil {
|
||||
return types.Network{}, err
|
||||
}
|
||||
return *network.libpodNet, nil
|
||||
}
|
||||
|
||||
func createMacVLAN(network *types.Network) error {
|
||||
if network.Internal {
|
||||
return errors.New("internal is not supported with macvlan")
|
||||
}
|
||||
if network.NetworkInterface != "" {
|
||||
interfaceNames, err := util.GetLiveNetworkNames()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !pkgutil.StringInSlice(network.NetworkInterface, interfaceNames) {
|
||||
return errors.Errorf("parent interface %s does not exists", network.NetworkInterface)
|
||||
}
|
||||
}
|
||||
if len(network.Subnets) == 0 {
|
||||
network.IPAMOptions["driver"] = types.DHCPIPAMDriver
|
||||
} else {
|
||||
network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *cniNetwork) createBridge(network *types.Network) error {
|
||||
if network.NetworkInterface != "" {
|
||||
bridges := n.getBridgeInterfaceNames()
|
||||
if pkgutil.StringInSlice(network.NetworkInterface, bridges) {
|
||||
return errors.Errorf("bridge name %s already in use", network.NetworkInterface)
|
||||
}
|
||||
if !define.NameRegex.MatchString(network.NetworkInterface) {
|
||||
return errors.Wrapf(define.RegexError, "bridge name %s invalid", network.NetworkInterface)
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
network.NetworkInterface, err = n.getFreeDeviceName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(network.Subnets) == 0 {
|
||||
freeSubnet, err := n.getFreeIPv4NetworkSubnet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
network.Subnets = append(network.Subnets, *freeSubnet)
|
||||
}
|
||||
// ipv6 enabled means dual stack, check if we already have
|
||||
// a ipv4 or ipv6 subnet and add one if not.
|
||||
if network.IPv6Enabled {
|
||||
ipv4 := false
|
||||
ipv6 := false
|
||||
for _, subnet := range network.Subnets {
|
||||
if util.IsIPv6(subnet.Subnet.IP) {
|
||||
ipv6 = true
|
||||
}
|
||||
if util.IsIPv4(subnet.Subnet.IP) {
|
||||
ipv4 = true
|
||||
}
|
||||
}
|
||||
if !ipv4 {
|
||||
freeSubnet, err := n.getFreeIPv4NetworkSubnet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
network.Subnets = append(network.Subnets, *freeSubnet)
|
||||
}
|
||||
if !ipv6 {
|
||||
freeSubnet, err := n.getFreeIPv6NetworkSubnet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
network.Subnets = append(network.Subnets, *freeSubnet)
|
||||
}
|
||||
}
|
||||
network.IPAMOptions["driver"] = types.HostLocalIPAMDriver
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSubnet will validate a given Subnet. It checks if the
|
||||
// given gateway and lease range are part of this subnet. If the
|
||||
// gateway is empty and addGateway is true it will get the first
|
||||
// available ip in the subnet assigned.
|
||||
func validateSubnet(s *types.Subnet, addGateway bool) error {
|
||||
if s == nil {
|
||||
return errors.New("subnet is nil")
|
||||
}
|
||||
// Reparse to ensure subnet is valid.
|
||||
// Do not use types.ParseCIDR() because we want the ip to be
|
||||
// the network address and not a random ip in the subnet.
|
||||
_, net, err := net.ParseCIDR(s.Subnet.String())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "subnet invalid")
|
||||
}
|
||||
s.Subnet = types.IPNet{IPNet: *net}
|
||||
if s.Gateway != nil {
|
||||
if !s.Subnet.Contains(s.Gateway) {
|
||||
return errors.Errorf("gateway %s not in subnet %s", s.Gateway, &s.Subnet)
|
||||
}
|
||||
} else if addGateway {
|
||||
ip, err := util.FirstIPInSubnet(net)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Gateway = ip
|
||||
}
|
||||
if s.LeaseRange != nil {
|
||||
if s.LeaseRange.StartIP != nil && !s.Subnet.Contains(s.LeaseRange.StartIP) {
|
||||
return errors.Errorf("lease range start ip %s not in subnet %s", s.LeaseRange.StartIP, &s.Subnet)
|
||||
}
|
||||
if s.LeaseRange.EndIP != nil && !s.Subnet.Contains(s.LeaseRange.EndIP) {
|
||||
return errors.Errorf("lease range end ip %s not in subnet %s", s.LeaseRange.EndIP, &s.Subnet)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
1241
libpod/network/cni/config_test.go
Normal file
1241
libpod/network/cni/config_test.go
Normal file
File diff suppressed because it is too large
Load Diff
340
libpod/network/cni/network.go
Normal file
340
libpod/network/cni/network.go
Normal file
@ -0,0 +1,340 @@
|
||||
// +build linux
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/containers/podman/v3/libpod/network/util"
|
||||
pkgutil "github.com/containers/podman/v3/pkg/util"
|
||||
"github.com/containers/storage/pkg/lockfile"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type cniNetwork struct {
|
||||
// cniConfigDir is directory where the cni config files are stored.
|
||||
cniConfigDir string
|
||||
// cniPluginDirs is a list of directories where cni should look for the plugins.
|
||||
cniPluginDirs []string
|
||||
|
||||
cniConf *libcni.CNIConfig
|
||||
|
||||
// defaultNetwork is the name for the default network.
|
||||
defaultNetwork string
|
||||
// defaultSubnet is the default subnet for the default network.
|
||||
defaultSubnet types.IPNet
|
||||
|
||||
// isMachine describes whenever podman runs in a podman machine environment.
|
||||
isMachine bool
|
||||
|
||||
// lock is a internal lock for critical operations
|
||||
lock lockfile.Locker
|
||||
|
||||
// networks is a map with loaded networks, the key is the network name
|
||||
networks map[string]*network
|
||||
}
|
||||
|
||||
type network struct {
|
||||
// filename is the full path to the cni config file on disk
|
||||
filename string
|
||||
libpodNet *types.Network
|
||||
cniNet *libcni.NetworkConfigList
|
||||
}
|
||||
|
||||
type InitConfig struct {
|
||||
// CNIConfigDir is directory where the cni config files are stored.
|
||||
CNIConfigDir string
|
||||
// CNIPluginDirs is a list of directories where cni should look for the plugins.
|
||||
CNIPluginDirs []string
|
||||
|
||||
// DefaultNetwork is the name for the default network.
|
||||
DefaultNetwork string
|
||||
// DefaultSubnet is the default subnet for the default network.
|
||||
DefaultSubnet string
|
||||
|
||||
// IsMachine describes whenever podman runs in a podman machine environment.
|
||||
IsMachine bool
|
||||
|
||||
// LockFile is the path to lock file.
|
||||
LockFile string
|
||||
}
|
||||
|
||||
// NewCNINetworkInterface creates the ContainerNetwork interface for the CNI backend.
|
||||
// Note: The networks are not loaded from disk until a method is called.
|
||||
func NewCNINetworkInterface(conf InitConfig) (types.ContainerNetwork, error) {
|
||||
// TODO: consider using a shared memory lock
|
||||
lock, err := lockfile.GetLockfile(conf.LockFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultNetworkName := conf.DefaultNetwork
|
||||
if defaultNetworkName == "" {
|
||||
defaultNetworkName = types.DefaultNetworkName
|
||||
}
|
||||
|
||||
defaultSubnet := conf.DefaultSubnet
|
||||
if defaultSubnet == "" {
|
||||
defaultSubnet = types.DefaultSubnet
|
||||
}
|
||||
defaultNet, err := types.ParseCIDR(defaultSubnet)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse default subnet")
|
||||
}
|
||||
|
||||
cni := libcni.NewCNIConfig(conf.CNIPluginDirs, &cniExec{})
|
||||
n := &cniNetwork{
|
||||
cniConfigDir: conf.CNIConfigDir,
|
||||
cniPluginDirs: conf.CNIPluginDirs,
|
||||
cniConf: cni,
|
||||
defaultNetwork: defaultNetworkName,
|
||||
defaultSubnet: defaultNet,
|
||||
isMachine: conf.IsMachine,
|
||||
lock: lock,
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (n *cniNetwork) loadNetworks() error {
|
||||
// skip loading networks if they are already loaded
|
||||
if n.networks != nil {
|
||||
return nil
|
||||
}
|
||||
// FIXME: do we have to support other file types as well, e.g. .conf?
|
||||
files, err := libcni.ConfFiles(n.cniConfigDir, []string{".conflist"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
networks := make(map[string]*network, len(files))
|
||||
for _, file := range files {
|
||||
conf, err := libcni.ConfListFromFile(file)
|
||||
if err != nil {
|
||||
// do not log ENOENT errors
|
||||
if !os.IsNotExist(err) {
|
||||
logrus.Warnf("Error loading CNI config file %s: %v", file, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !define.NameRegex.MatchString(conf.Name) {
|
||||
logrus.Warnf("CNI config list %s has invalid name, skipping: %v", file, define.RegexError)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := n.cniConf.ValidateNetworkList(context.Background(), conf); err != nil {
|
||||
logrus.Warnf("Error validating CNI config file %s: %v", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if val, ok := networks[conf.Name]; ok {
|
||||
logrus.Warnf("CNI config list %s has the same network name as %s, skipping", file, val.filename)
|
||||
continue
|
||||
}
|
||||
|
||||
net, err := createNetworkFromCNIConfigList(conf, file)
|
||||
if err != nil {
|
||||
logrus.Errorf("CNI config list %s could not be converted to a libpod config, skipping: %v", file, err)
|
||||
continue
|
||||
}
|
||||
logrus.Tracef("Successfully loaded network %s: %v", net.Name, net)
|
||||
networkInfo := network{
|
||||
filename: file,
|
||||
cniNet: conf,
|
||||
libpodNet: net,
|
||||
}
|
||||
networks[net.Name] = &networkInfo
|
||||
}
|
||||
|
||||
// create the default network in memory if it did not exists on disk
|
||||
if networks[n.defaultNetwork] == nil {
|
||||
networkInfo, err := n.createDefaultNetwork()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create default network %s", n.defaultNetwork)
|
||||
}
|
||||
networks[n.defaultNetwork] = networkInfo
|
||||
}
|
||||
|
||||
logrus.Debugf("Successfully loaded %d networks", len(networks))
|
||||
n.networks = networks
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *cniNetwork) createDefaultNetwork() (*network, error) {
|
||||
net := types.Network{
|
||||
Name: n.defaultNetwork,
|
||||
NetworkInterface: "cni-podman0",
|
||||
Driver: types.BridgeNetworkDriver,
|
||||
Subnets: []types.Subnet{
|
||||
{Subnet: n.defaultSubnet},
|
||||
},
|
||||
}
|
||||
return n.networkCreate(net, false)
|
||||
}
|
||||
|
||||
// getNetwork will lookup a network by name or ID. It returns an
|
||||
// error when no network was found or when more than one network
|
||||
// with the given (partial) ID exists.
|
||||
// getNetwork will read from the networks map, therefore the caller
|
||||
// must ensure that n.lock is locked before using it.
|
||||
func (n *cniNetwork) getNetwork(nameOrID string) (*network, error) {
|
||||
// fast path check the map key, this will only work for names
|
||||
if val, ok := n.networks[nameOrID]; ok {
|
||||
return val, nil
|
||||
}
|
||||
// If there was no match we might got a full or partial ID.
|
||||
var net *network
|
||||
for _, val := range n.networks {
|
||||
// This should not happen because we already looked up the map by name but check anyway.
|
||||
if val.libpodNet.Name == nameOrID {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(val.libpodNet.ID, nameOrID) {
|
||||
if net != nil {
|
||||
return nil, errors.Errorf("more than one result for network ID %s", nameOrID)
|
||||
}
|
||||
net = val
|
||||
}
|
||||
}
|
||||
if net != nil {
|
||||
return net, nil
|
||||
}
|
||||
return nil, errors.Wrapf(define.ErrNoSuchNetwork, "unable to find network with name or ID %s", nameOrID)
|
||||
}
|
||||
|
||||
// getNetworkIDFromName creates a network ID from the name. It is just the
|
||||
// sha256 hash so it is not safe but it should be safe enough for our use case.
|
||||
func getNetworkIDFromName(name string) string {
|
||||
hash := sha256.Sum256([]byte(name))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// getFreeIPv6NetworkSubnet returns a unused ipv4 subnet
|
||||
func (n *cniNetwork) getFreeIPv4NetworkSubnet() (*types.Subnet, error) {
|
||||
networks, err := n.getUsedSubnets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// the default podman network is 10.88.0.0/16
|
||||
// start locking for free /24 networks
|
||||
network := &net.IPNet{
|
||||
IP: net.IP{10, 89, 0, 0},
|
||||
Mask: net.IPMask{255, 255, 255, 0},
|
||||
}
|
||||
|
||||
// TODO: make sure to not use public subnets
|
||||
for {
|
||||
if intersectsConfig := util.NetworkIntersectsWithNetworks(network, networks); !intersectsConfig {
|
||||
logrus.Debugf("found free ipv4 network subnet %s", network.String())
|
||||
return &types.Subnet{
|
||||
Subnet: types.IPNet{IPNet: *network},
|
||||
}, nil
|
||||
}
|
||||
network, err = util.NextSubnet(network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getFreeIPv6NetworkSubnet returns a unused ipv6 subnet
|
||||
func (n *cniNetwork) getFreeIPv6NetworkSubnet() (*types.Subnet, error) {
|
||||
networks, err := n.getUsedSubnets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// FIXME: Is 10000 fine as limit? We should prevent an endless loop.
|
||||
for i := 0; i < 10000; i++ {
|
||||
// RFC4193: Choose the ipv6 subnet random and NOT sequentially.
|
||||
network, err := util.GetRandomIPv6Subnet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if intersectsConfig := util.NetworkIntersectsWithNetworks(&network, networks); !intersectsConfig {
|
||||
logrus.Debugf("found free ipv6 network subnet %s", network.String())
|
||||
return &types.Subnet{
|
||||
Subnet: types.IPNet{IPNet: network},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("failed to get random ipv6 subnet")
|
||||
}
|
||||
|
||||
// getUsedSubnets returns a list of all used subnets by network
|
||||
// configs and interfaces on the host.
|
||||
func (n *cniNetwork) getUsedSubnets() ([]*net.IPNet, error) {
|
||||
// first, load all used subnets from network configs
|
||||
subnets := make([]*net.IPNet, 0, len(n.networks))
|
||||
for _, val := range n.networks {
|
||||
for _, subnet := range val.libpodNet.Subnets {
|
||||
// nolint:exportloopref
|
||||
subnets = append(subnets, &subnet.Subnet.IPNet)
|
||||
}
|
||||
}
|
||||
// second, load networks from the current system
|
||||
liveSubnets, err := util.GetLiveNetworkSubnets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(subnets, liveSubnets...), nil
|
||||
}
|
||||
|
||||
// getFreeDeviceName returns a free device name which can
|
||||
// be used for new configs as name and bridge interface name
|
||||
func (n *cniNetwork) getFreeDeviceName() (string, error) {
|
||||
bridgeNames := n.getBridgeInterfaceNames()
|
||||
netNames := n.getUsedNetworkNames()
|
||||
liveInterfaces, err := util.GetLiveNetworkNames()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
names := make([]string, 0, len(bridgeNames)+len(netNames)+len(liveInterfaces))
|
||||
names = append(names, bridgeNames...)
|
||||
names = append(names, netNames...)
|
||||
names = append(names, liveInterfaces...)
|
||||
// FIXME: Is a limit fine?
|
||||
// Start by 1, 0 is reserved for the default network
|
||||
for i := 1; i < 1000000; i++ {
|
||||
deviceName := fmt.Sprintf("%s%d", cniDeviceName, i)
|
||||
if !pkgutil.StringInSlice(deviceName, names) {
|
||||
logrus.Debugf("found free device name %s", deviceName)
|
||||
return deviceName, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("could not find free device name, to many iterations")
|
||||
}
|
||||
|
||||
// getUsedNetworkNames returns all network names already used
|
||||
// by network configs
|
||||
func (n *cniNetwork) getUsedNetworkNames() []string {
|
||||
names := make([]string, 0, len(n.networks))
|
||||
for _, val := range n.networks {
|
||||
names = append(names, val.libpodNet.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// getUsedNetworkNames returns all bridge device names already used
|
||||
// by network configs
|
||||
func (n *cniNetwork) getBridgeInterfaceNames() []string {
|
||||
names := make([]string, 0, len(n.networks))
|
||||
for _, val := range n.networks {
|
||||
if val.libpodNet.Driver == types.BridgeNetworkDriver {
|
||||
names = append(names, val.libpodNet.NetworkInterface)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
309
libpod/network/cni/run.go
Normal file
309
libpod/network/cni/run.go
Normal file
@ -0,0 +1,309 @@
|
||||
// +build linux
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
cnitypes "github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containers/podman/v3/libpod/define"
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// Setup will setup the container network namespace. It returns
|
||||
// a map of StatusBlocks, the key is the network name.
|
||||
func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (map[string]types.StatusBlock, error) {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
err := n.loadNetworks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if namespacePath == "" {
|
||||
return nil, errors.New("namespacePath is empty")
|
||||
}
|
||||
if options.ContainerID == "" {
|
||||
return nil, errors.New("ContainerID is empty")
|
||||
}
|
||||
if len(options.Networks) == 0 {
|
||||
return nil, errors.New("must specify at least one network")
|
||||
}
|
||||
for name, netOpts := range options.Networks {
|
||||
network := n.networks[name]
|
||||
if network == nil {
|
||||
return nil, errors.Wrapf(define.ErrNoSuchNetwork, "network %s", name)
|
||||
}
|
||||
err := validatePerNetworkOpts(network, netOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// set the loopback adapter up in the container netns
|
||||
err = ns.WithNetNSPath(namespacePath, func(_ ns.NetNS) error {
|
||||
link, err := netlink.LinkByName("lo")
|
||||
if err == nil {
|
||||
err = netlink.LinkSetUp(link)
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to set the loopback adapter up")
|
||||
}
|
||||
|
||||
var retErr error
|
||||
teardownOpts := options
|
||||
teardownOpts.Networks = map[string]types.PerNetworkOptions{}
|
||||
// make sure to teardown the already connected networks on error
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if len(teardownOpts.Networks) > 0 {
|
||||
err := n.teardown(namespacePath, types.TeardownOptions(teardownOpts))
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make(map[string]types.StatusBlock, len(options.Networks))
|
||||
for name, netOpts := range options.Networks {
|
||||
network := n.networks[name]
|
||||
rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, netOpts)
|
||||
|
||||
// If we have more than one static ip we need parse the ips via runtime config,
|
||||
// make sure to add the ips capability to the first plugin otherwise it doesn't get the ips
|
||||
if len(netOpts.StaticIPs) > 0 && !network.cniNet.Plugins[0].Network.Capabilities["ips"] {
|
||||
caps := make(map[string]interface{})
|
||||
caps["capabilities"] = map[string]bool{"ips": true}
|
||||
network.cniNet.Plugins[0], retErr = libcni.InjectConf(network.cniNet.Plugins[0], caps)
|
||||
if retErr != nil {
|
||||
return nil, retErr
|
||||
}
|
||||
}
|
||||
|
||||
var res cnitypes.Result
|
||||
res, retErr = n.cniConf.AddNetworkList(context.Background(), network.cniNet, rt)
|
||||
// Add this network to teardown opts since it is now connected.
|
||||
// Also add this if an errors was returned since we want to call teardown on this regardless.
|
||||
teardownOpts.Networks[name] = netOpts
|
||||
if retErr != nil {
|
||||
return nil, retErr
|
||||
}
|
||||
|
||||
var cnires *current.Result
|
||||
cnires, retErr = current.GetResult(res)
|
||||
if retErr != nil {
|
||||
return nil, retErr
|
||||
}
|
||||
logrus.Debugf("cni result for container %s network %s: %v", options.ContainerID, name, cnires)
|
||||
var status types.StatusBlock
|
||||
status, retErr = cniResultToStatus(cnires)
|
||||
if retErr != nil {
|
||||
return nil, retErr
|
||||
}
|
||||
results[name] = status
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// cniResultToStatus convert the cni result to status block
|
||||
func cniResultToStatus(cniResult *current.Result) (types.StatusBlock, error) {
|
||||
result := types.StatusBlock{}
|
||||
nameservers := make([]net.IP, 0, len(cniResult.DNS.Nameservers))
|
||||
for _, nameserver := range cniResult.DNS.Nameservers {
|
||||
ip := net.ParseIP(nameserver)
|
||||
if ip == nil {
|
||||
return result, errors.Errorf("failed to parse cni nameserver ip %s", nameserver)
|
||||
}
|
||||
nameservers = append(nameservers, ip)
|
||||
}
|
||||
result.DNSServerIPs = nameservers
|
||||
result.DNSSearchDomains = cniResult.DNS.Search
|
||||
|
||||
interfaces := make(map[string]types.NetInterface)
|
||||
for _, ip := range cniResult.IPs {
|
||||
if ip.Interface == nil {
|
||||
// we do no expect ips without an interface
|
||||
continue
|
||||
}
|
||||
if len(cniResult.Interfaces) <= *ip.Interface {
|
||||
return result, errors.Errorf("invalid cni result, interface index %d out of range", *ip.Interface)
|
||||
}
|
||||
cniInt := cniResult.Interfaces[*ip.Interface]
|
||||
netInt, ok := interfaces[cniInt.Name]
|
||||
if ok {
|
||||
netInt.Networks = append(netInt.Networks, types.NetAddress{
|
||||
Subnet: types.IPNet{IPNet: ip.Address},
|
||||
Gateway: ip.Gateway,
|
||||
})
|
||||
interfaces[cniInt.Name] = netInt
|
||||
} else {
|
||||
mac, err := net.ParseMAC(cniInt.Mac)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
interfaces[cniInt.Name] = types.NetInterface{
|
||||
MacAddress: mac,
|
||||
Networks: []types.NetAddress{{
|
||||
Subnet: types.IPNet{IPNet: ip.Address},
|
||||
Gateway: ip.Gateway,
|
||||
}},
|
||||
}
|
||||
}
|
||||
}
|
||||
result.Interfaces = interfaces
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// validatePerNetworkOpts checks that all given static ips are in a subnet on this network
|
||||
func validatePerNetworkOpts(network *network, netOpts types.PerNetworkOptions) error {
|
||||
if netOpts.InterfaceName == "" {
|
||||
return errors.Errorf("interface name on network %s is empty", network.libpodNet.Name)
|
||||
}
|
||||
outer:
|
||||
for _, ip := range netOpts.StaticIPs {
|
||||
for _, s := range network.libpodNet.Subnets {
|
||||
if s.Subnet.Contains(ip) {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
return errors.Errorf("requested static ip %s not in any subnet on network %s", ip.String(), network.libpodNet.Name)
|
||||
}
|
||||
if len(netOpts.Aliases) > 0 && !network.libpodNet.DNSEnabled {
|
||||
return errors.New("cannot set aliases on a network without dns enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRuntimeConfig(netns, conName, conID, networkName string, ports []cniPortMapEntry, opts types.PerNetworkOptions) *libcni.RuntimeConf {
|
||||
rt := &libcni.RuntimeConf{
|
||||
ContainerID: conID,
|
||||
NetNS: netns,
|
||||
IfName: opts.InterfaceName,
|
||||
Args: [][2]string{
|
||||
{"IgnoreUnknown", "1"},
|
||||
// FIXME: Should we set the K8S args?
|
||||
//{"K8S_POD_NAMESPACE", conName},
|
||||
//{"K8S_POD_INFRA_CONTAINER_ID", conID},
|
||||
// K8S_POD_NAME is used by dnsname to get the container name
|
||||
{"K8S_POD_NAME", conName},
|
||||
},
|
||||
CapabilityArgs: map[string]interface{}{},
|
||||
}
|
||||
|
||||
// Propagate environment CNI_ARGS
|
||||
for _, kvpairs := range strings.Split(os.Getenv("CNI_ARGS"), ";") {
|
||||
if keyval := strings.SplitN(kvpairs, "=", 2); len(keyval) == 2 {
|
||||
rt.Args = append(rt.Args, [2]string{keyval[0], keyval[1]})
|
||||
}
|
||||
}
|
||||
|
||||
// Add mac address to cni args
|
||||
if len(opts.StaticMAC) > 0 {
|
||||
rt.Args = append(rt.Args, [2]string{"MAC", opts.StaticMAC.String()})
|
||||
}
|
||||
|
||||
if len(opts.StaticIPs) == 1 {
|
||||
// Add a single IP to the args field. CNI plugins < 1.0.0
|
||||
// do not support multiple ips via capability args.
|
||||
rt.Args = append(rt.Args, [2]string{"IP", opts.StaticIPs[0].String()})
|
||||
} else if len(opts.StaticIPs) > 1 {
|
||||
// Set the static ips in the capability args
|
||||
// to support more than one static ip per network.
|
||||
rt.CapabilityArgs["ips"] = opts.StaticIPs
|
||||
}
|
||||
|
||||
// Set network aliases for the dnsname plugin.
|
||||
if len(opts.Aliases) > 0 {
|
||||
rt.CapabilityArgs["aliases"] = map[string][]string{
|
||||
networkName: opts.Aliases,
|
||||
}
|
||||
}
|
||||
|
||||
// Set PortMappings in Capabilities
|
||||
if len(ports) > 0 {
|
||||
rt.CapabilityArgs["portMappings"] = ports
|
||||
}
|
||||
|
||||
return rt
|
||||
}
|
||||
|
||||
// Teardown will teardown the container network namespace.
|
||||
func (n *cniNetwork) Teardown(namespacePath string, options types.TeardownOptions) error {
|
||||
n.lock.Lock()
|
||||
defer n.lock.Unlock()
|
||||
err := n.loadNetworks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return n.teardown(namespacePath, options)
|
||||
}
|
||||
|
||||
func (n *cniNetwork) teardown(namespacePath string, options types.TeardownOptions) error {
|
||||
// Note: An empty namespacePath is allowed because some plugins
|
||||
// still need teardown, for example ipam should remove used ip allocations.
|
||||
|
||||
ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var multiErr *multierror.Error
|
||||
for name, netOpts := range options.Networks {
|
||||
rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, netOpts)
|
||||
|
||||
cniConfList, newRt, err := getCachedNetworkConfig(n.cniConf, name, rt)
|
||||
if err == nil {
|
||||
rt = newRt
|
||||
} else {
|
||||
logrus.Warnf("failed to load cached network config: %v, falling back to loading network %s from disk", err, name)
|
||||
network := n.networks[name]
|
||||
if network == nil {
|
||||
multiErr = multierror.Append(multiErr, errors.Wrapf(define.ErrNoSuchNetwork, "network %s", name))
|
||||
continue
|
||||
}
|
||||
cniConfList = network.cniNet
|
||||
}
|
||||
|
||||
err = n.cniConf.DelNetworkList(context.Background(), cniConfList, rt)
|
||||
if err != nil {
|
||||
multiErr = multierror.Append(multiErr, err)
|
||||
}
|
||||
}
|
||||
return multiErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func getCachedNetworkConfig(cniConf *libcni.CNIConfig, name string, rt *libcni.RuntimeConf) (*libcni.NetworkConfigList, *libcni.RuntimeConf, error) {
|
||||
cniConfList := &libcni.NetworkConfigList{
|
||||
Name: name,
|
||||
}
|
||||
confBytes, rt, err := cniConf.GetNetworkListCachedConfig(cniConfList, rt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if confBytes == nil {
|
||||
return nil, nil, errors.Errorf("network %s not found in CNI cache", name)
|
||||
}
|
||||
|
||||
cniConfList, err = libcni.ConfListFromBytes(confBytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cniConfList, rt, nil
|
||||
}
|
1326
libpod/network/cni/run_test.go
Normal file
1326
libpod/network/cni/run_test.go
Normal file
File diff suppressed because it is too large
Load Diff
25
libpod/network/cni/testfiles/invalid/broken.conflist
Normal file
25
libpod/network/cni/testfiles/invalid/broken.conflist
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "bridge",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman9",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.8.0/24",
|
||||
"gateway": "10.89.8.1"
|
||||
}
|
||||
]
|
||||
]
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
49
libpod/network/cni/testfiles/invalid/invalidname.conflist
Normal file
49
libpod/network/cni/testfiles/invalid/invalidname.conflist
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "bridge@123",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman9",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.8.0/24",
|
||||
"gateway": "10.89.8.1"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
48
libpod/network/cni/testfiles/invalid/noname.conflist
Normal file
48
libpod/network/cni/testfiles/invalid/noname.conflist
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman9",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.8.0/24",
|
||||
"gateway": "10.89.8.1"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
5
libpod/network/cni/testfiles/invalid/noplugin.conflist
Normal file
5
libpod/network/cni/testfiles/invalid/noplugin.conflist
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "bridge",
|
||||
"plugins": []
|
||||
}
|
49
libpod/network/cni/testfiles/invalid/samename1.conflist
Normal file
49
libpod/network/cni/testfiles/invalid/samename1.conflist
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "bridge",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman9",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.8.0/24",
|
||||
"gateway": "10.89.8.1"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
49
libpod/network/cni/testfiles/invalid/samename2.conflist
Normal file
49
libpod/network/cni/testfiles/invalid/samename2.conflist
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "bridge",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman9",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.8.0/24",
|
||||
"gateway": "10.89.8.1"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
37
libpod/network/cni/testfiles/valid/87-podman.conflist
Normal file
37
libpod/network/cni/testfiles/valid/87-podman.conflist
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "podman",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman0",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [{ "dst": "0.0.0.0/0" }],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.88.0.0/16",
|
||||
"gateway": "10.88.0.1"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall"
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
}
|
||||
]
|
||||
}
|
51
libpod/network/cni/testfiles/valid/bridge.conflist
Normal file
51
libpod/network/cni/testfiles/valid/bridge.conflist
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "bridge",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman9",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.8.0/24",
|
||||
"gateway": "10.89.8.1",
|
||||
"rangeStart": "10.89.8.20",
|
||||
"rangeEnd": "10.89.8.50"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
58
libpod/network/cni/testfiles/valid/dualstack.conflist
Normal file
58
libpod/network/cni/testfiles/valid/dualstack.conflist
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "dualstack",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman21",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "::/0"
|
||||
},
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "fd10:88:a::/64",
|
||||
"gateway": "fd10:88:a::1"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.19.0/24",
|
||||
"gateway": "10.89.19.10"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
40
libpod/network/cni/testfiles/valid/internal.conflist
Normal file
40
libpod/network/cni/testfiles/valid/internal.conflist
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "internal",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman8",
|
||||
"isGateway": false,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.7.0/24"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
}
|
||||
]
|
||||
}
|
54
libpod/network/cni/testfiles/valid/label.conflist
Normal file
54
libpod/network/cni/testfiles/valid/label.conflist
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"args": {
|
||||
"podman_labels": {
|
||||
"mykey": "value"
|
||||
}
|
||||
},
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "label",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman15",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.13.0/24",
|
||||
"gateway": "10.89.13.1"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
13
libpod/network/cni/testfiles/valid/macvlan.conflist
Normal file
13
libpod/network/cni/testfiles/valid/macvlan.conflist
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "macvlan",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "macvlan",
|
||||
"master": "lo",
|
||||
"ipam": {
|
||||
"type": "dhcp"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
14
libpod/network/cni/testfiles/valid/macvlan_mtu.conflist
Normal file
14
libpod/network/cni/testfiles/valid/macvlan_mtu.conflist
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "macvlan_mtu",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "macvlan",
|
||||
"master": "lo",
|
||||
"ipam": {
|
||||
"type": "dhcp"
|
||||
},
|
||||
"mtu": 1300
|
||||
}
|
||||
]
|
||||
}
|
49
libpod/network/cni/testfiles/valid/mtu.conflist
Normal file
49
libpod/network/cni/testfiles/valid/mtu.conflist
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "mtu",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman13",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"mtu": 1500,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.11.0/24"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
50
libpod/network/cni/testfiles/valid/vlan.conflist
Normal file
50
libpod/network/cni/testfiles/valid/vlan.conflist
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "vlan",
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "cni-podman14",
|
||||
"isGateway": true,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"vlan": 5,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"routes": [
|
||||
{
|
||||
"dst": "0.0.0.0/0"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
[
|
||||
{
|
||||
"subnet": "10.89.12.0/24",
|
||||
"gateway": "10.89.12.1"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "portmap",
|
||||
"capabilities": {
|
||||
"portMappings": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": ""
|
||||
},
|
||||
{
|
||||
"type": "tuning"
|
||||
},
|
||||
{
|
||||
"type": "dnsname",
|
||||
"domainName": "dns.podman",
|
||||
"capabilities": {
|
||||
"aliases": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
21
libpod/network/types/const.go
Normal file
21
libpod/network/types/const.go
Normal file
@ -0,0 +1,21 @@
|
||||
package types
|
||||
|
||||
const (
|
||||
// BridgeNetworkDriver defines the bridge driver
|
||||
BridgeNetworkDriver = "bridge"
|
||||
// DefaultNetworkDriver is the default network type used
|
||||
DefaultNetworkDriver = BridgeNetworkDriver
|
||||
// MacVLANNetworkDriver defines the macvlan driver
|
||||
MacVLANNetworkDriver = "macvlan"
|
||||
|
||||
// IPAM drivers
|
||||
// HostLocalIPAMDriver store the ip
|
||||
HostLocalIPAMDriver = "host-local"
|
||||
// DHCPIPAMDriver get subnet and ip from dhcp server
|
||||
DHCPIPAMDriver = "dhcp"
|
||||
|
||||
// DefaultSubnet is the name that will be used for the default CNI network.
|
||||
DefaultNetworkName = "podman"
|
||||
// DefaultSubnet is the subnet that will be used for the default CNI network.
|
||||
DefaultSubnet = "10.88.0.0/16"
|
||||
)
|
208
libpod/network/types/network.go
Normal file
208
libpod/network/types/network.go
Normal file
@ -0,0 +1,208 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ContainerNetwork interface {
|
||||
// NetworkCreate will take a partial filled Network and fill the
|
||||
// missing fields. It creates the Network and returns the full Network.
|
||||
NetworkCreate(Network) (Network, error)
|
||||
// NetworkRemove will remove the Network with the given name or ID.
|
||||
NetworkRemove(nameOrID string) error
|
||||
// NetworkList will return all known Networks. Optionally you can
|
||||
// supply a list of filter functions. Only if a network matches all
|
||||
// functions it is returned.
|
||||
NetworkList(...FilterFunc) ([]Network, error)
|
||||
// NetworkInspect will return the Network with the given name or ID.
|
||||
NetworkInspect(nameOrID string) (Network, error)
|
||||
|
||||
// Setup will setup the container network namespace. It returns
|
||||
// a map of StatusBlocks, the key is the network name.
|
||||
Setup(namespacePath string, options SetupOptions) (map[string]StatusBlock, error)
|
||||
// Teardown will teardown the container network namespace.
|
||||
Teardown(namespacePath string, options TeardownOptions) error
|
||||
}
|
||||
|
||||
// Network describes the Network attributes.
|
||||
type Network struct {
|
||||
// Name of the Network.
|
||||
Name string `json:"name,omitempty"`
|
||||
// ID of the Network.
|
||||
ID string `json:"id,omitempty"`
|
||||
// Driver for this Network, e.g. bridge, macvlan...
|
||||
Driver string `json:"driver,omitempty"`
|
||||
// InterfaceName is the network interface name on the host.
|
||||
NetworkInterface string `json:"network_interface,omitempty"`
|
||||
// Created contains the timestamp when this network was created.
|
||||
// This is not guaranteed to stay exactly the same.
|
||||
Created time.Time
|
||||
// Subnets to use.
|
||||
Subnets []Subnet `json:"subnets,omitempty"`
|
||||
// IPv6Enabled if set to true an ipv6 subnet should be created for this net.
|
||||
IPv6Enabled bool `json:"ipv6_enabled"`
|
||||
// Internal is whether the Network should not have external routes
|
||||
// to public or other Networks.
|
||||
Internal bool `json:"internal"`
|
||||
// DNSEnabled is whether name resolution is active for container on
|
||||
// this Network.
|
||||
DNSEnabled bool `json:"dns_enabled"`
|
||||
// Labels is a set of key-value labels that have been applied to the
|
||||
// Network.
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
// Options is a set of key-value options that have been applied to
|
||||
// the Network.
|
||||
Options map[string]string `json:"options,omitempty"`
|
||||
// IPAMOptions contains options used for the ip assignment.
|
||||
IPAMOptions map[string]string `json:"ipam_options,omitempty"`
|
||||
}
|
||||
|
||||
// IPNet is used as custom net.IPNet type to add Marshal/Unmarshal methods.
|
||||
type IPNet struct {
|
||||
net.IPNet
|
||||
}
|
||||
|
||||
// ParseCIDR parse a string to IPNet
|
||||
func ParseCIDR(cidr string) (IPNet, error) {
|
||||
ip, net, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return IPNet{}, err
|
||||
}
|
||||
// convert to 4 bytes if ipv4
|
||||
ipv4 := ip.To4()
|
||||
if ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
net.IP = ip
|
||||
return IPNet{*net}, err
|
||||
}
|
||||
|
||||
func (n *IPNet) MarshalText() ([]byte, error) {
|
||||
return []byte(n.String()), nil
|
||||
}
|
||||
|
||||
func (n *IPNet) UnmarshalText(text []byte) error {
|
||||
net, err := ParseCIDR(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*n = net
|
||||
return nil
|
||||
}
|
||||
|
||||
type Subnet struct {
|
||||
// Subnet for this Network.
|
||||
Subnet IPNet `json:"subnet,omitempty"`
|
||||
// Gateway IP for this Network.
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
// LeaseRange contains the range where IP are leased. Optional.
|
||||
LeaseRange *LeaseRange `json:"lease_range,omitempty"`
|
||||
}
|
||||
|
||||
// LeaseRange contains the range where IP are leased.
|
||||
type LeaseRange struct {
|
||||
// StartIP first IP in the subnet which should be used to assign ips.
|
||||
StartIP net.IP `json:"start_ip,omitempty"`
|
||||
// EndIP last IP in the subnet which should be used to assign ips.
|
||||
EndIP net.IP `json:"end_ip,omitempty"`
|
||||
}
|
||||
|
||||
// StatusBlock contains the network information about a container
|
||||
// connected to one Network.
|
||||
type StatusBlock struct {
|
||||
// Interfaces contains the created network interface in the container.
|
||||
// The map key is the interface name.
|
||||
Interfaces map[string]NetInterface `json:"interfaces,omitempty"`
|
||||
// DNSServerIPs nameserver addresses which should be added to
|
||||
// the containers resolv.conf file.
|
||||
DNSServerIPs []net.IP `json:"dns_server_ips,omitempty"`
|
||||
// DNSSearchDomains search domains which should be added to
|
||||
// the containers resolv.conf file.
|
||||
DNSSearchDomains []string `json:"dns_search_domains,omitempty"`
|
||||
}
|
||||
|
||||
// NetInterface contains the settings for a given network interface.
|
||||
type NetInterface struct {
|
||||
// Networks list of assigned subnets with their gateway.
|
||||
Networks []NetAddress `json:"networks,omitempty"`
|
||||
// MacAddress for this Interface.
|
||||
MacAddress net.HardwareAddr `json:"mac_address,omitempty"`
|
||||
}
|
||||
|
||||
// NetAddress contains the subnet and gatway.
|
||||
type NetAddress struct {
|
||||
// Subnet of this NetAddress. Note that the subnet contains the
|
||||
// actual ip of the net interface and not the network address.
|
||||
Subnet IPNet `json:"subnet,omitempty"`
|
||||
// Gateway for the Subnet. This can be nil if there is no gateway, e.g. internal network.
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
}
|
||||
|
||||
// PerNetworkOptions are options which should be set on a per network basis.
|
||||
type PerNetworkOptions struct {
|
||||
// StaticIPv4 for this container. Optional.
|
||||
StaticIPs []net.IP `json:"static_ips,omitempty"`
|
||||
// Aliases contains a list of names which the dns server should resolve
|
||||
// to this container. Can only be set when DNSEnabled is true on the Network.
|
||||
// Optional.
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
// StaticMac for this container. Optional.
|
||||
StaticMAC net.HardwareAddr `json:"static_mac,omitempty"`
|
||||
// InterfaceName for this container. Required.
|
||||
InterfaceName string `json:"interface_name,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkOptions for a given container.
|
||||
type NetworkOptions struct {
|
||||
// ContainerID is the container id, used for iptables comments and ipam allocation.
|
||||
ContainerID string `json:"container_id,omitempty"`
|
||||
// ContainerName is the container name, used as dns name.
|
||||
ContainerName string `json:"container_name,omitempty"`
|
||||
// PortMappings contains the port mappings for this container
|
||||
PortMappings []PortMapping `json:"port_mappings,omitempty"`
|
||||
// Networks contains all networks with the PerNetworkOptions.
|
||||
// The map should contain at least one element.
|
||||
Networks map[string]PerNetworkOptions `json:"networks,omitempty"`
|
||||
}
|
||||
|
||||
// PortMapping is one or more ports that will be mapped into the container.
|
||||
type PortMapping struct {
|
||||
// HostIP is the IP that we will bind to on the host.
|
||||
// If unset, assumed to be 0.0.0.0 (all interfaces).
|
||||
HostIP string `json:"host_ip,omitempty"`
|
||||
// ContainerPort is the port number that will be exposed from the
|
||||
// container.
|
||||
// Mandatory.
|
||||
ContainerPort uint16 `json:"container_port"`
|
||||
// HostPort is the port number that will be forwarded from the host into
|
||||
// the container.
|
||||
// If omitted, a random port on the host (guaranteed to be over 1024)
|
||||
// will be assigned.
|
||||
HostPort uint16 `json:"host_port,omitempty"`
|
||||
// Range is the number of ports that will be forwarded, starting at
|
||||
// HostPort and ContainerPort and counting up.
|
||||
// This is 1-indexed, so 1 is assumed to be a single port (only the
|
||||
// Hostport:Containerport mapping will be added), 2 is two ports (both
|
||||
// Hostport:Containerport and Hostport+1:Containerport+1), etc.
|
||||
// If unset, assumed to be 1 (a single port).
|
||||
// Both hostport + range and containerport + range must be less than
|
||||
// 65536.
|
||||
Range uint16 `json:"range,omitempty"`
|
||||
// Protocol is the protocol forward.
|
||||
// Must be either "tcp", "udp", and "sctp", or some combination of these
|
||||
// separated by commas.
|
||||
// If unset, assumed to be TCP.
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
}
|
||||
|
||||
type SetupOptions struct {
|
||||
NetworkOptions
|
||||
}
|
||||
|
||||
type TeardownOptions struct {
|
||||
NetworkOptions
|
||||
}
|
||||
|
||||
// FilterFunc can be passed to NetworkList to filter the networks.
|
||||
type FilterFunc func(Network) bool
|
55
libpod/network/util/filters.go
Normal file
55
libpod/network/util/filters.go
Normal file
@ -0,0 +1,55 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v3/libpod/network/types"
|
||||
"github.com/containers/podman/v3/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func GenerateNetworkFilters(filters map[string][]string) ([]types.FilterFunc, error) {
|
||||
filterFuncs := make([]types.FilterFunc, 0, len(filters))
|
||||
for key, filterValues := range filters {
|
||||
filterFunc, err := createFilterFuncs(key, filterValues)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterFuncs = append(filterFuncs, filterFunc)
|
||||
}
|
||||
return filterFuncs, nil
|
||||
}
|
||||
|
||||
func createFilterFuncs(key string, filterValues []string) (types.FilterFunc, error) {
|
||||
switch strings.ToLower(key) {
|
||||
case "name":
|
||||
// matches one name, regex allowed
|
||||
return func(net types.Network) bool {
|
||||
return util.StringMatchRegexSlice(net.Name, filterValues)
|
||||
}, nil
|
||||
|
||||
case "label":
|
||||
// matches all labels
|
||||
return func(net types.Network) bool {
|
||||
return util.MatchLabelFilters(filterValues, net.Labels)
|
||||
}, nil
|
||||
|
||||
case "driver":
|
||||
// matches network driver
|
||||
return func(net types.Network) bool {
|
||||
return util.StringInSlice(net.Driver, filterValues)
|
||||
}, nil
|
||||
|
||||
case "id":
|
||||
// matches part of one id
|
||||
return func(net types.Network) bool {
|
||||
return util.StringMatchRegexSlice(net.ID, filterValues)
|
||||
}, nil
|
||||
|
||||
// FIXME: What should we do with the old plugin filter
|
||||
// TODO: add dangling, dns enabled, internal filter
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("invalid filter %q", key)
|
||||
}
|
||||
}
|
34
libpod/network/util/interfaces.go
Normal file
34
libpod/network/util/interfaces.go
Normal file
@ -0,0 +1,34 @@
|
||||
package util
|
||||
|
||||
import "net"
|
||||
|
||||
// GetLiveNetworkSubnets returns a slice of subnets representing what the system
|
||||
// has defined as network interfaces
|
||||
func GetLiveNetworkSubnets() ([]*net.IPNet, error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nets := make([]*net.IPNet, 0, len(addrs))
|
||||
for _, address := range addrs {
|
||||
_, n, err := net.ParseCIDR(address.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nets = append(nets, n)
|
||||
}
|
||||
return nets, nil
|
||||
}
|
||||
|
||||
// GetLiveNetworkNames returns a list of network interface names on the system
|
||||
func GetLiveNetworkNames() ([]string, error) {
|
||||
liveInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
interfaceNames := make([]string, 0, len(liveInterfaces))
|
||||
for _, i := range liveInterfaces {
|
||||
interfaceNames = append(interfaceNames, i.Name)
|
||||
}
|
||||
return interfaceNames, nil
|
||||
}
|
113
libpod/network/util/ip.go
Normal file
113
libpod/network/util/ip.go
Normal file
@ -0,0 +1,113 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"net"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// IsIPv6 returns true if netIP is IPv6.
|
||||
func IsIPv6(netIP net.IP) bool {
|
||||
return netIP != nil && netIP.To4() == nil
|
||||
}
|
||||
|
||||
// IsIPv4 returns true if netIP is IPv4.
|
||||
func IsIPv4(netIP net.IP) bool {
|
||||
return netIP != nil && netIP.To4() != nil
|
||||
}
|
||||
|
||||
func incByte(subnet *net.IPNet, idx int, shift uint) error {
|
||||
if idx < 0 {
|
||||
return errors.New("no more subnets left")
|
||||
}
|
||||
if subnet.IP[idx] == 255 {
|
||||
subnet.IP[idx] = 0
|
||||
return incByte(subnet, idx-1, 0)
|
||||
}
|
||||
subnet.IP[idx] += 1 << shift
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextSubnet returns subnet incremented by 1
|
||||
func NextSubnet(subnet *net.IPNet) (*net.IPNet, error) {
|
||||
newSubnet := &net.IPNet{
|
||||
IP: subnet.IP,
|
||||
Mask: subnet.Mask,
|
||||
}
|
||||
ones, bits := newSubnet.Mask.Size()
|
||||
if ones == 0 {
|
||||
return nil, errors.Errorf("%s has only one subnet", subnet.String())
|
||||
}
|
||||
zeroes := uint(bits - ones)
|
||||
shift := zeroes % 8
|
||||
idx := ones/8 - 1
|
||||
if idx < 0 {
|
||||
idx = 0
|
||||
}
|
||||
if err := incByte(newSubnet, idx, shift); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSubnet, nil
|
||||
}
|
||||
|
||||
// LastIPInSubnet gets the last IP in a subnet
|
||||
func LastIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
|
||||
// re-parse to ensure clean network address
|
||||
_, cidr, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ones, bits := cidr.Mask.Size()
|
||||
if ones == bits {
|
||||
return cidr.IP, nil
|
||||
}
|
||||
for i := range cidr.IP {
|
||||
cidr.IP[i] = cidr.IP[i] | ^cidr.Mask[i]
|
||||
}
|
||||
return cidr.IP, nil
|
||||
}
|
||||
|
||||
// FirstIPInSubnet gets the first IP in a subnet
|
||||
func FirstIPInSubnet(addr *net.IPNet) (net.IP, error) { //nolint:interfacer
|
||||
// re-parse to ensure clean network address
|
||||
_, cidr, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ones, bits := cidr.Mask.Size()
|
||||
if ones == bits {
|
||||
return cidr.IP, nil
|
||||
}
|
||||
cidr.IP[len(cidr.IP)-1]++
|
||||
return cidr.IP, nil
|
||||
}
|
||||
|
||||
func NetworkIntersectsWithNetworks(n *net.IPNet, networklist []*net.IPNet) bool {
|
||||
for _, nw := range networklist {
|
||||
if networkIntersect(n, nw) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func networkIntersect(n1, n2 *net.IPNet) bool {
|
||||
return n2.Contains(n1.IP) || n1.Contains(n2.IP)
|
||||
}
|
||||
|
||||
// GetRandomIPv6Subnet returns a random internal ipv6 subnet as described in RFC3879.
|
||||
func GetRandomIPv6Subnet() (net.IPNet, error) {
|
||||
ip := make(net.IP, 8, net.IPv6len)
|
||||
// read 8 random bytes
|
||||
_, err := rand.Read(ip)
|
||||
if err != nil {
|
||||
return net.IPNet{}, nil
|
||||
}
|
||||
// first byte must be FD as per RFC3879
|
||||
ip[0] = 0xfd
|
||||
// add 8 zero bytes
|
||||
ip = append(ip, make([]byte, 8)...)
|
||||
return net.IPNet{IP: ip, Mask: net.CIDRMask(64, 128)}, nil
|
||||
}
|
125
libpod/network/util/ip_test.go
Normal file
125
libpod/network/util/ip_test.go
Normal file
@ -0,0 +1,125 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func parseCIDR(n string) *net.IPNet {
|
||||
_, parsedNet, _ := net.ParseCIDR(n)
|
||||
return parsedNet
|
||||
}
|
||||
|
||||
func TestNextSubnet(t *testing.T) {
|
||||
type args struct {
|
||||
subnet *net.IPNet
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *net.IPNet
|
||||
wantErr bool
|
||||
}{
|
||||
{"class b", args{subnet: parseCIDR("192.168.0.0/16")}, parseCIDR("192.169.0.0/16"), false},
|
||||
{"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
test := tt
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, err := NextSubnet(test.args.subnet)
|
||||
if (err != nil) != test.wantErr {
|
||||
t.Errorf("NextSubnet() error = %v, wantErr %v", err, test.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Errorf("NextSubnet() got = %v, want %v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstIPInSubnet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args *net.IPNet
|
||||
want net.IP
|
||||
wantErr bool
|
||||
}{
|
||||
{"class b", parseCIDR("192.168.0.0/16"), net.ParseIP("192.168.0.1"), false},
|
||||
{"class c", parseCIDR("192.168.1.0/24"), net.ParseIP("192.168.1.1"), false},
|
||||
{"cidr /23", parseCIDR("192.168.0.0/23"), net.ParseIP("192.168.0.1"), false},
|
||||
{"cidr /25", parseCIDR("192.168.1.0/25"), net.ParseIP("192.168.1.1"), false},
|
||||
{"cidr /26", parseCIDR("172.16.1.128/26"), net.ParseIP("172.16.1.129"), false},
|
||||
{"class a", parseCIDR("10.0.0.0/8"), net.ParseIP("10.0.0.1"), false},
|
||||
{"cidr /32", parseCIDR("192.168.255.4/32"), net.ParseIP("192.168.255.4"), false},
|
||||
{"cidr /31", parseCIDR("192.168.255.4/31"), net.ParseIP("192.168.255.5"), false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
test := tt
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, err := FirstIPInSubnet(test.args)
|
||||
if (err != nil) != test.wantErr {
|
||||
t.Errorf("FirstIPInSubnet() error = %v, wantErr %v", err, test.wantErr)
|
||||
return
|
||||
}
|
||||
if !got.Equal(test.want) {
|
||||
t.Errorf("FirstIPInSubnet() got = %v, want %v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLastIPInSubnet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args *net.IPNet
|
||||
want net.IP
|
||||
wantErr bool
|
||||
}{
|
||||
{"class b", parseCIDR("192.168.0.0/16"), net.ParseIP("192.168.255.255"), false},
|
||||
{"class c", parseCIDR("192.168.1.0/24"), net.ParseIP("192.168.1.255"), false},
|
||||
{"cidr /23", parseCIDR("192.168.0.0/23"), net.ParseIP("192.168.1.255"), false},
|
||||
{"cidr /25", parseCIDR("192.168.1.0/25"), net.ParseIP("192.168.1.127"), false},
|
||||
{"cidr /26", parseCIDR("172.16.1.128/26"), net.ParseIP("172.16.1.191"), false},
|
||||
{"class a", parseCIDR("10.0.0.0/8"), net.ParseIP("10.255.255.255"), false},
|
||||
{"cidr /32", parseCIDR("192.168.255.4/32"), net.ParseIP("192.168.255.4"), false},
|
||||
{"cidr /31", parseCIDR("192.168.255.4/31"), net.ParseIP("192.168.255.5"), false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
test := tt
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, err := LastIPInSubnet(test.args)
|
||||
if (err != nil) != test.wantErr {
|
||||
t.Errorf("LastIPInSubnet() error = %v, wantErr %v", err, test.wantErr)
|
||||
return
|
||||
}
|
||||
if !got.Equal(test.want) {
|
||||
t.Errorf("LastIPInSubnet() got = %v, want %v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomIPv6Subnet(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
t.Run(fmt.Sprintf("GetRandomIPv6Subnet %d", i), func(t *testing.T) {
|
||||
sub, err := GetRandomIPv6Subnet()
|
||||
if err != nil {
|
||||
t.Errorf("GetRandomIPv6Subnet() error should be nil: %v", err)
|
||||
return
|
||||
}
|
||||
if sub.IP.To4() != nil {
|
||||
t.Errorf("ip %s is not an ipv6 address", sub.IP)
|
||||
}
|
||||
if sub.IP[0] != 0xfd {
|
||||
t.Errorf("ipv6 %s does not start with fd", sub.IP)
|
||||
}
|
||||
ones, bytes := sub.Mask.Size()
|
||||
if ones != 64 || bytes != 128 {
|
||||
t.Errorf("wrong network mask %v, it should be /64", sub.Mask)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user