mirror of
https://github.com/containers/podman.git
synced 2025-09-28 09:15:26 +08:00

The libpod/network packages were moved to c/common so that buildah can use it as well. To prevent duplication use it in podman as well and remove it from here. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
275 lines
8.2 KiB
Go
275 lines
8.2 KiB
Go
// +build linux
|
|
|
|
package cni
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/containernetworking/cni/libcni"
|
|
cnitypes "github.com/containernetworking/cni/pkg/types"
|
|
types040 "github.com/containernetworking/cni/pkg/types/040"
|
|
"github.com/containernetworking/plugins/pkg/ns"
|
|
"github.com/containers/common/libnetwork/internal/util"
|
|
"github.com/containers/common/libnetwork/types"
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/vishvananda/netlink"
|
|
)
|
|
|
|
// Setup will setup the container network namespace. It returns
|
|
// a map of StatusBlocks, the key is the network name.
|
|
func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (map[string]types.StatusBlock, error) {
|
|
n.lock.Lock()
|
|
defer n.lock.Unlock()
|
|
err := n.loadNetworks()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = util.ValidateSetupOptions(n, namespacePath, options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// set the loopback adapter up in the container netns
|
|
err = ns.WithNetNSPath(namespacePath, func(_ ns.NetNS) error {
|
|
link, err := netlink.LinkByName("lo")
|
|
if err == nil {
|
|
err = netlink.LinkSetUp(link)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to set the loopback adapter up")
|
|
}
|
|
|
|
var retErr error
|
|
teardownOpts := options
|
|
teardownOpts.Networks = map[string]types.PerNetworkOptions{}
|
|
// make sure to teardown the already connected networks on error
|
|
defer func() {
|
|
if retErr != nil {
|
|
if len(teardownOpts.Networks) > 0 {
|
|
err := n.teardown(namespacePath, types.TeardownOptions(teardownOpts))
|
|
if err != nil {
|
|
logrus.Warn(err)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
results := make(map[string]types.StatusBlock, len(options.Networks))
|
|
for name, netOpts := range options.Networks {
|
|
netOpts := netOpts
|
|
network := n.networks[name]
|
|
rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, &netOpts)
|
|
|
|
// If we have more than one static ip we need parse the ips via runtime config,
|
|
// make sure to add the ips capability to the first plugin otherwise it doesn't get the ips
|
|
if len(netOpts.StaticIPs) > 0 && !network.cniNet.Plugins[0].Network.Capabilities["ips"] {
|
|
caps := make(map[string]interface{})
|
|
caps["capabilities"] = map[string]bool{"ips": true}
|
|
network.cniNet.Plugins[0], retErr = libcni.InjectConf(network.cniNet.Plugins[0], caps)
|
|
if retErr != nil {
|
|
return nil, retErr
|
|
}
|
|
}
|
|
|
|
var res cnitypes.Result
|
|
res, retErr = n.cniConf.AddNetworkList(context.Background(), network.cniNet, rt)
|
|
// Add this network to teardown opts since it is now connected.
|
|
// Also add this if an errors was returned since we want to call teardown on this regardless.
|
|
teardownOpts.Networks[name] = netOpts
|
|
if retErr != nil {
|
|
return nil, retErr
|
|
}
|
|
|
|
logrus.Debugf("cni result for container %s network %s: %v", options.ContainerID, name, res)
|
|
var status types.StatusBlock
|
|
status, retErr = CNIResultToStatus(res)
|
|
if retErr != nil {
|
|
return nil, retErr
|
|
}
|
|
results[name] = status
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// CNIResultToStatus convert the cni result to status block
|
|
// nolint:golint
|
|
func CNIResultToStatus(res cnitypes.Result) (types.StatusBlock, error) {
|
|
result := types.StatusBlock{}
|
|
cniResult, err := types040.GetResult(res)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
nameservers := make([]net.IP, 0, len(cniResult.DNS.Nameservers))
|
|
for _, nameserver := range cniResult.DNS.Nameservers {
|
|
ip := net.ParseIP(nameserver)
|
|
if ip == nil {
|
|
return result, errors.Errorf("failed to parse cni nameserver ip %s", nameserver)
|
|
}
|
|
nameservers = append(nameservers, ip)
|
|
}
|
|
result.DNSServerIPs = nameservers
|
|
result.DNSSearchDomains = cniResult.DNS.Search
|
|
|
|
interfaces := make(map[string]types.NetInterface)
|
|
for _, ip := range cniResult.IPs {
|
|
if ip.Interface == nil {
|
|
// we do no expect ips without an interface
|
|
continue
|
|
}
|
|
if len(cniResult.Interfaces) <= *ip.Interface {
|
|
return result, errors.Errorf("invalid cni result, interface index %d out of range", *ip.Interface)
|
|
}
|
|
cniInt := cniResult.Interfaces[*ip.Interface]
|
|
netInt, ok := interfaces[cniInt.Name]
|
|
if ok {
|
|
netInt.Subnets = append(netInt.Subnets, types.NetAddress{
|
|
IPNet: types.IPNet{IPNet: ip.Address},
|
|
Gateway: ip.Gateway,
|
|
})
|
|
interfaces[cniInt.Name] = netInt
|
|
} else {
|
|
mac, err := net.ParseMAC(cniInt.Mac)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
interfaces[cniInt.Name] = types.NetInterface{
|
|
MacAddress: types.HardwareAddr(mac),
|
|
Subnets: []types.NetAddress{{
|
|
IPNet: types.IPNet{IPNet: ip.Address},
|
|
Gateway: ip.Gateway,
|
|
}},
|
|
}
|
|
}
|
|
}
|
|
result.Interfaces = interfaces
|
|
return result, nil
|
|
}
|
|
|
|
func getRuntimeConfig(netns, conName, conID, networkName string, ports []cniPortMapEntry, opts *types.PerNetworkOptions) *libcni.RuntimeConf {
|
|
rt := &libcni.RuntimeConf{
|
|
ContainerID: conID,
|
|
NetNS: netns,
|
|
IfName: opts.InterfaceName,
|
|
Args: [][2]string{
|
|
{"IgnoreUnknown", "1"},
|
|
// Do not set the K8S env vars, see https://github.com/containers/podman/issues/12083.
|
|
// Only K8S_POD_NAME is used by dnsname to get the container name.
|
|
{"K8S_POD_NAME", conName},
|
|
},
|
|
CapabilityArgs: map[string]interface{}{},
|
|
}
|
|
|
|
// Propagate environment CNI_ARGS
|
|
for _, kvpairs := range strings.Split(os.Getenv("CNI_ARGS"), ";") {
|
|
if keyval := strings.SplitN(kvpairs, "=", 2); len(keyval) == 2 {
|
|
rt.Args = append(rt.Args, [2]string{keyval[0], keyval[1]})
|
|
}
|
|
}
|
|
|
|
// Add mac address to cni args
|
|
if len(opts.StaticMAC) > 0 {
|
|
rt.Args = append(rt.Args, [2]string{"MAC", opts.StaticMAC.String()})
|
|
}
|
|
|
|
if len(opts.StaticIPs) == 1 {
|
|
// Add a single IP to the args field. CNI plugins < 1.0.0
|
|
// do not support multiple ips via capability args.
|
|
rt.Args = append(rt.Args, [2]string{"IP", opts.StaticIPs[0].String()})
|
|
} else if len(opts.StaticIPs) > 1 {
|
|
// Set the static ips in the capability args
|
|
// to support more than one static ip per network.
|
|
rt.CapabilityArgs["ips"] = opts.StaticIPs
|
|
}
|
|
|
|
// Set network aliases for the dnsname plugin.
|
|
if len(opts.Aliases) > 0 {
|
|
rt.CapabilityArgs["aliases"] = map[string][]string{
|
|
networkName: opts.Aliases,
|
|
}
|
|
}
|
|
|
|
// Set PortMappings in Capabilities
|
|
if len(ports) > 0 {
|
|
rt.CapabilityArgs["portMappings"] = ports
|
|
}
|
|
|
|
return rt
|
|
}
|
|
|
|
// Teardown will teardown the container network namespace.
|
|
func (n *cniNetwork) Teardown(namespacePath string, options types.TeardownOptions) error {
|
|
n.lock.Lock()
|
|
defer n.lock.Unlock()
|
|
err := n.loadNetworks()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return n.teardown(namespacePath, options)
|
|
}
|
|
|
|
func (n *cniNetwork) teardown(namespacePath string, options types.TeardownOptions) error {
|
|
// Note: An empty namespacePath is allowed because some plugins
|
|
// still need teardown, for example ipam should remove used ip allocations.
|
|
|
|
ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var multiErr *multierror.Error
|
|
for name, netOpts := range options.Networks {
|
|
netOpts := netOpts
|
|
rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, &netOpts)
|
|
|
|
cniConfList, newRt, err := getCachedNetworkConfig(n.cniConf, name, rt)
|
|
if err == nil {
|
|
rt = newRt
|
|
} else {
|
|
logrus.Warnf("Failed to load cached network config: %v, falling back to loading network %s from disk", err, name)
|
|
network := n.networks[name]
|
|
if network == nil {
|
|
multiErr = multierror.Append(multiErr, errors.Wrapf(types.ErrNoSuchNetwork, "network %s", name))
|
|
continue
|
|
}
|
|
cniConfList = network.cniNet
|
|
}
|
|
|
|
err = n.cniConf.DelNetworkList(context.Background(), cniConfList, rt)
|
|
if err != nil {
|
|
multiErr = multierror.Append(multiErr, err)
|
|
}
|
|
}
|
|
return multiErr.ErrorOrNil()
|
|
}
|
|
|
|
func getCachedNetworkConfig(cniConf *libcni.CNIConfig, name string, rt *libcni.RuntimeConf) (*libcni.NetworkConfigList, *libcni.RuntimeConf, error) {
|
|
cniConfList := &libcni.NetworkConfigList{
|
|
Name: name,
|
|
}
|
|
confBytes, rt, err := cniConf.GetNetworkListCachedConfig(cniConfList, rt)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
} else if confBytes == nil {
|
|
return nil, nil, errors.Errorf("network %s not found in CNI cache", name)
|
|
}
|
|
|
|
cniConfList, err = libcni.ConfListFromBytes(confBytes)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return cniConfList, rt, nil
|
|
}
|