mirror of
https://github.com/containers/podman.git
synced 2025-05-17 23:26:08 +08:00
libpod: Move NetworkDisconnect and NetworkConnect to networking_common.go
This also moves Runtime methods ConnectContainerToNetwork and DisconnectContainerFromNetwork as well as support functions getFreeInterfaceName and normalizeNetworkName. [NO NEW TESTS NEEDED] Signed-off-by: Doug Rabson <dfr@rabson.org> libpod: Move (Connect|Disconnect)Container(To|From)Network and normalizeNetworkName to networking_common.go [NO NEW TESTS NEEDED] Signed-off-by: Doug Rabson <dfr@rabson.org>
This commit is contained in:
@ -4,14 +4,21 @@
|
|||||||
package libpod
|
package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/containers/common/libnetwork/etchosts"
|
||||||
"github.com/containers/common/libnetwork/types"
|
"github.com/containers/common/libnetwork/types"
|
||||||
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/common/pkg/machine"
|
"github.com/containers/common/pkg/machine"
|
||||||
|
"github.com/containers/common/pkg/util"
|
||||||
"github.com/containers/podman/v4/libpod/define"
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
|
"github.com/containers/podman/v4/libpod/events"
|
||||||
"github.com/containers/podman/v4/pkg/namespaces"
|
"github.com/containers/podman/v4/pkg/namespaces"
|
||||||
|
"github.com/containers/podman/v4/pkg/rootless"
|
||||||
|
"github.com/containers/storage/pkg/lockfile"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -345,3 +352,303 @@ func resultToBasicNetworkConfig(result types.StatusBlock) define.InspectBasicNet
|
|||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetworkDisconnect removes a container from the network
|
||||||
|
func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
|
||||||
|
// only the bridge mode supports cni networks
|
||||||
|
if err := isBridgeNetMode(c.config.NetMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
networks, err := c.networks()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if network exists and if the input is a ID we get the name
|
||||||
|
// CNI only uses names so it is important that we only use the name
|
||||||
|
netName, err = c.runtime.normalizeNetworkName(netName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, nameExists := networks[netName]
|
||||||
|
if !nameExists && len(networks) > 0 {
|
||||||
|
return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.syncContainer(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// get network status before we disconnect
|
||||||
|
networkStatus := c.getNetworkStatus()
|
||||||
|
|
||||||
|
if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.newNetworkEvent(events.NetworkDisconnect, netName)
|
||||||
|
if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.state.NetNS == nil {
|
||||||
|
return fmt.Errorf("unable to disconnect %s from %s: %w", nameOrID, netName, define.ErrNoNetwork)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := types.NetworkOptions{
|
||||||
|
ContainerID: c.config.ID,
|
||||||
|
ContainerName: getCNIPodName(c),
|
||||||
|
}
|
||||||
|
opts.PortMappings = c.convertPortMappings()
|
||||||
|
opts.Networks = map[string]types.PerNetworkOptions{
|
||||||
|
netName: networks[netName],
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.runtime.teardownNetwork(c.state.NetNS.Path(), opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update network status if container is running
|
||||||
|
oldStatus, statusExist := networkStatus[netName]
|
||||||
|
delete(networkStatus, netName)
|
||||||
|
c.state.NetworkStatus = networkStatus
|
||||||
|
err = c.save()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload ports when there are still connected networks, maybe we removed the network interface with the child ip.
|
||||||
|
// Reloading without connected networks does not make sense, so we can skip this step.
|
||||||
|
if rootless.IsRootless() && len(networkStatus) > 0 {
|
||||||
|
if err := c.reloadRootlessRLKPortMapping(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update resolv.conf if required
|
||||||
|
if statusExist {
|
||||||
|
stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs))
|
||||||
|
for _, ip := range oldStatus.DNSServerIPs {
|
||||||
|
stringIPs = append(stringIPs, ip.String())
|
||||||
|
}
|
||||||
|
if len(stringIPs) > 0 {
|
||||||
|
logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
|
||||||
|
if err := c.removeNameserver(stringIPs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update /etc/hosts file
|
||||||
|
if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
|
||||||
|
// sync the names with c.getHostsEntries()
|
||||||
|
names := []string{c.Hostname(), c.config.Name}
|
||||||
|
rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...)
|
||||||
|
if len(rm) > 0 {
|
||||||
|
// make sure to lock this file to prevent concurrent writes when
|
||||||
|
// this is used a net dependency container
|
||||||
|
lock, err := lockfile.GetLockfile(file)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lock hosts file: %w", err)
|
||||||
|
}
|
||||||
|
logrus.Debugf("Remove /etc/hosts entries %v", rm)
|
||||||
|
lock.Lock()
|
||||||
|
err = etchosts.Remove(file, rm)
|
||||||
|
lock.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectNetwork connects a container to a given network
|
||||||
|
func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
|
||||||
|
// only the bridge mode supports cni networks
|
||||||
|
if err := isBridgeNetMode(c.config.NetMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
networks, err := c.networks()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if network exists and if the input is a ID we get the name
|
||||||
|
// CNI only uses names so it is important that we only use the name
|
||||||
|
netName, err = c.runtime.normalizeNetworkName(netName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.syncContainer(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get network status before we connect
|
||||||
|
networkStatus := c.getNetworkStatus()
|
||||||
|
|
||||||
|
// always add the short id as alias for docker compat
|
||||||
|
netOpts.Aliases = append(netOpts.Aliases, c.config.ID[:12])
|
||||||
|
|
||||||
|
if netOpts.InterfaceName == "" {
|
||||||
|
netOpts.InterfaceName = getFreeInterfaceName(networks)
|
||||||
|
if netOpts.InterfaceName == "" {
|
||||||
|
return errors.New("could not find free network interface name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil {
|
||||||
|
// Docker compat: treat requests to attach already attached networks as a no-op, ignoring opts
|
||||||
|
if errors.Is(err, define.ErrNetworkConnected) && c.ensureState(define.ContainerStateConfigured) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.newNetworkEvent(events.NetworkConnect, netName)
|
||||||
|
if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.state.NetNS == nil {
|
||||||
|
return fmt.Errorf("unable to connect %s to %s: %w", nameOrID, netName, define.ErrNoNetwork)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := types.NetworkOptions{
|
||||||
|
ContainerID: c.config.ID,
|
||||||
|
ContainerName: getCNIPodName(c),
|
||||||
|
}
|
||||||
|
opts.PortMappings = c.convertPortMappings()
|
||||||
|
opts.Networks = map[string]types.PerNetworkOptions{
|
||||||
|
netName: netOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(results) != 1 {
|
||||||
|
return errors.New("when adding aliases, results must be of length 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to get the old host entries before we add the new one to the status
|
||||||
|
// if we do not add do it here we will get the wrong existing entries which will throw of the logic
|
||||||
|
// we could also copy the map but this does not seem worth it
|
||||||
|
// sync the hostNames with c.getHostsEntries()
|
||||||
|
hostNames := []string{c.Hostname(), c.config.Name}
|
||||||
|
oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...)
|
||||||
|
|
||||||
|
// update network status
|
||||||
|
if networkStatus == nil {
|
||||||
|
networkStatus = make(map[string]types.StatusBlock, 1)
|
||||||
|
}
|
||||||
|
networkStatus[netName] = results[netName]
|
||||||
|
c.state.NetworkStatus = networkStatus
|
||||||
|
|
||||||
|
err = c.save()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first network needs a port reload to set the correct child ip for the rootlessport process.
|
||||||
|
// Adding a second network does not require a port reload because the child ip is still valid.
|
||||||
|
if rootless.IsRootless() && len(networks) == 0 {
|
||||||
|
if err := c.reloadRootlessRLKPortMapping(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv6, err := c.checkForIPv6(networkStatus)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update resolv.conf if required
|
||||||
|
stringIPs := make([]string, 0, len(results[netName].DNSServerIPs))
|
||||||
|
for _, ip := range results[netName].DNSServerIPs {
|
||||||
|
if (ip.To4() == nil) && !ipv6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stringIPs = append(stringIPs, ip.String())
|
||||||
|
}
|
||||||
|
if len(stringIPs) > 0 {
|
||||||
|
logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
|
||||||
|
if err := c.addNameserver(stringIPs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update /etc/hosts file
|
||||||
|
if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
|
||||||
|
// make sure to lock this file to prevent concurrent writes when
|
||||||
|
// this is used a net dependency container
|
||||||
|
lock, err := lockfile.GetLockfile(file)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lock hosts file: %w", err)
|
||||||
|
}
|
||||||
|
new := etchosts.GetNetworkHostEntries(results, hostNames...)
|
||||||
|
logrus.Debugf("Add /etc/hosts entries %v", new)
|
||||||
|
// use special AddIfExists API to make sure we only add new entries if an old one exists
|
||||||
|
// see the AddIfExists() comment for more information
|
||||||
|
lock.Lock()
|
||||||
|
err = etchosts.AddIfExists(file, oldHostEntries, new)
|
||||||
|
lock.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a free interface name for a new network
|
||||||
|
// return an empty string if no free name was found
|
||||||
|
func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string {
|
||||||
|
ifNames := make([]string, 0, len(networks))
|
||||||
|
for _, opts := range networks {
|
||||||
|
ifNames = append(ifNames, opts.InterfaceName)
|
||||||
|
}
|
||||||
|
for i := 0; i < 100000; i++ {
|
||||||
|
ifName := fmt.Sprintf("eth%d", i)
|
||||||
|
if !util.StringInSlice(ifName, ifNames) {
|
||||||
|
return ifName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisconnectContainerFromNetwork removes a container from its CNI network
|
||||||
|
func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
|
||||||
|
ctr, err := r.LookupContainer(nameOrID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctr.NetworkDisconnect(nameOrID, netName, force)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectContainerToNetwork connects a container to a CNI network
|
||||||
|
func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
|
||||||
|
ctr, err := r.LookupContainer(nameOrID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ctr.NetworkConnect(nameOrID, netName, netOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name.
|
||||||
|
// If the network is not found a errors is returned.
|
||||||
|
func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
|
||||||
|
net, err := r.network.NetworkInspect(nameOrID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return net.Name, nil
|
||||||
|
}
|
||||||
|
@ -15,9 +15,6 @@ import (
|
|||||||
|
|
||||||
"github.com/containers/buildah/pkg/jail"
|
"github.com/containers/buildah/pkg/jail"
|
||||||
"github.com/containers/common/libnetwork/types"
|
"github.com/containers/common/libnetwork/types"
|
||||||
"github.com/containers/podman/v4/libpod/define"
|
|
||||||
"github.com/containers/podman/v4/libpod/events"
|
|
||||||
"github.com/containers/podman/v4/pkg/util"
|
|
||||||
"github.com/containers/storage/pkg/lockfile"
|
"github.com/containers/storage/pkg/lockfile"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -263,232 +260,8 @@ func (c *Container) inspectJoinedNetworkNS(networkns string) (q types.StatusBloc
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkDisconnect removes a container from the network
|
func (c *Container) reloadRootlessRLKPortMapping() error {
|
||||||
func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
|
return errors.New("unsupported (*Container).reloadRootlessRLKPortMapping")
|
||||||
// only the bridge mode supports cni networks
|
|
||||||
if err := isBridgeNetMode(c.config.NetMode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
networks, err := c.networks()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if network exists and if the input is a ID we get the name
|
|
||||||
// CNI only uses names so it is important that we only use the name
|
|
||||||
netName, err = c.runtime.normalizeNetworkName(netName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, nameExists := networks[netName]
|
|
||||||
if !nameExists && len(networks) > 0 {
|
|
||||||
return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.syncContainer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// get network status before we disconnect
|
|
||||||
networkStatus := c.getNetworkStatus()
|
|
||||||
|
|
||||||
if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.newNetworkEvent(events.NetworkDisconnect, netName)
|
|
||||||
if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := types.NetworkOptions{
|
|
||||||
ContainerID: c.config.ID,
|
|
||||||
ContainerName: getCNIPodName(c),
|
|
||||||
}
|
|
||||||
opts.PortMappings = c.convertPortMappings()
|
|
||||||
opts.Networks = map[string]types.PerNetworkOptions{
|
|
||||||
netName: networks[netName],
|
|
||||||
}
|
|
||||||
|
|
||||||
// update network status if container is running
|
|
||||||
oldStatus, statusExist := networkStatus[netName]
|
|
||||||
delete(networkStatus, netName)
|
|
||||||
c.state.NetworkStatus = networkStatus
|
|
||||||
err = c.save()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update resolv.conf if required
|
|
||||||
if statusExist {
|
|
||||||
stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs))
|
|
||||||
for _, ip := range oldStatus.DNSServerIPs {
|
|
||||||
stringIPs = append(stringIPs, ip.String())
|
|
||||||
}
|
|
||||||
if len(stringIPs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
|
|
||||||
if err := c.removeNameserver(stringIPs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectNetwork connects a container to a given network
|
|
||||||
func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
|
|
||||||
// only the bridge mode supports cni networks
|
|
||||||
if err := isBridgeNetMode(c.config.NetMode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
networks, err := c.networks()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if network exists and if the input is a ID we get the name
|
|
||||||
// CNI only uses names so it is important that we only use the name
|
|
||||||
netName, err = c.runtime.normalizeNetworkName(netName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.syncContainer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get network status before we connect
|
|
||||||
networkStatus := c.getNetworkStatus()
|
|
||||||
|
|
||||||
// always add the short id as alias for docker compat
|
|
||||||
netOpts.Aliases = append(netOpts.Aliases, c.config.ID[:12])
|
|
||||||
|
|
||||||
if netOpts.InterfaceName == "" {
|
|
||||||
netOpts.InterfaceName = getFreeInterfaceName(networks)
|
|
||||||
if netOpts.InterfaceName == "" {
|
|
||||||
return errors.New("could not find free network interface name")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.newNetworkEvent(events.NetworkConnect, netName)
|
|
||||||
if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := types.NetworkOptions{
|
|
||||||
ContainerID: c.config.ID,
|
|
||||||
ContainerName: getCNIPodName(c),
|
|
||||||
}
|
|
||||||
opts.PortMappings = c.convertPortMappings()
|
|
||||||
opts.Networks = map[string]types.PerNetworkOptions{
|
|
||||||
netName: netOpts,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(results) != 1 {
|
|
||||||
return errors.New("when adding aliases, results must be of length 1")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
var results map[string]types.StatusBlock
|
|
||||||
|
|
||||||
// update network status
|
|
||||||
if networkStatus == nil {
|
|
||||||
networkStatus = make(map[string]types.StatusBlock, 1)
|
|
||||||
}
|
|
||||||
networkStatus[netName] = results[netName]
|
|
||||||
c.state.NetworkStatus = networkStatus
|
|
||||||
|
|
||||||
err = c.save()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The first network needs a port reload to set the correct child ip for the rootlessport process.
|
|
||||||
// Adding a second network does not require a port reload because the child ip is still valid.
|
|
||||||
|
|
||||||
ipv6, err := c.checkForIPv6(networkStatus)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update resolv.conf if required
|
|
||||||
stringIPs := make([]string, 0, len(results[netName].DNSServerIPs))
|
|
||||||
for _, ip := range results[netName].DNSServerIPs {
|
|
||||||
if (ip.To4() == nil) && !ipv6 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
stringIPs = append(stringIPs, ip.String())
|
|
||||||
}
|
|
||||||
if len(stringIPs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
|
|
||||||
if err := c.addNameserver(stringIPs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get a free interface name for a new network
|
|
||||||
// return an empty string if no free name was found
|
|
||||||
func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string {
|
|
||||||
ifNames := make([]string, 0, len(networks))
|
|
||||||
for _, opts := range networks {
|
|
||||||
ifNames = append(ifNames, opts.InterfaceName)
|
|
||||||
}
|
|
||||||
for i := 0; i < 100000; i++ {
|
|
||||||
ifName := fmt.Sprintf("eth%d", i)
|
|
||||||
if !util.StringInSlice(ifName, ifNames) {
|
|
||||||
return ifName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisconnectContainerFromNetwork removes a container from its CNI network
|
|
||||||
func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
|
|
||||||
ctr, err := r.LookupContainer(nameOrID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ctr.NetworkDisconnect(nameOrID, netName, force)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectContainerToNetwork connects a container to a CNI network
|
|
||||||
func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
|
|
||||||
ctr, err := r.LookupContainer(nameOrID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ctr.NetworkConnect(nameOrID, netName, netOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name.
|
|
||||||
// If the network is not found a errors is returned.
|
|
||||||
func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
|
|
||||||
net, err := r.network.NetworkInspect(nameOrID)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return net.Name, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ocicniPortsToNetTypesPorts convert the old port format to the new one
|
// ocicniPortsToNetTypesPorts convert the old port format to the new one
|
||||||
|
@ -20,14 +20,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
"github.com/containers/common/libnetwork/etchosts"
|
|
||||||
"github.com/containers/common/libnetwork/resolvconf"
|
"github.com/containers/common/libnetwork/resolvconf"
|
||||||
"github.com/containers/common/libnetwork/types"
|
"github.com/containers/common/libnetwork/types"
|
||||||
"github.com/containers/common/pkg/config"
|
|
||||||
"github.com/containers/common/pkg/netns"
|
"github.com/containers/common/pkg/netns"
|
||||||
"github.com/containers/common/pkg/util"
|
"github.com/containers/common/pkg/util"
|
||||||
"github.com/containers/podman/v4/libpod/define"
|
|
||||||
"github.com/containers/podman/v4/libpod/events"
|
|
||||||
"github.com/containers/podman/v4/pkg/errorhandling"
|
"github.com/containers/podman/v4/pkg/errorhandling"
|
||||||
"github.com/containers/podman/v4/pkg/rootless"
|
"github.com/containers/podman/v4/pkg/rootless"
|
||||||
"github.com/containers/podman/v4/utils"
|
"github.com/containers/podman/v4/utils"
|
||||||
@ -869,306 +865,6 @@ func (w *logrusDebugWriter) Write(p []byte) (int, error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkDisconnect removes a container from the network
|
|
||||||
func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
|
|
||||||
// only the bridge mode supports cni networks
|
|
||||||
if err := isBridgeNetMode(c.config.NetMode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
networks, err := c.networks()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if network exists and if the input is a ID we get the name
|
|
||||||
// CNI only uses names so it is important that we only use the name
|
|
||||||
netName, err = c.runtime.normalizeNetworkName(netName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, nameExists := networks[netName]
|
|
||||||
if !nameExists && len(networks) > 0 {
|
|
||||||
return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.syncContainer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// get network status before we disconnect
|
|
||||||
networkStatus := c.getNetworkStatus()
|
|
||||||
|
|
||||||
if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.newNetworkEvent(events.NetworkDisconnect, netName)
|
|
||||||
if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.state.NetNS == nil {
|
|
||||||
return fmt.Errorf("unable to disconnect %s from %s: %w", nameOrID, netName, define.ErrNoNetwork)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := types.NetworkOptions{
|
|
||||||
ContainerID: c.config.ID,
|
|
||||||
ContainerName: getCNIPodName(c),
|
|
||||||
}
|
|
||||||
opts.PortMappings = c.convertPortMappings()
|
|
||||||
opts.Networks = map[string]types.PerNetworkOptions{
|
|
||||||
netName: networks[netName],
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.runtime.teardownNetwork(c.state.NetNS.Path(), opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update network status if container is running
|
|
||||||
oldStatus, statusExist := networkStatus[netName]
|
|
||||||
delete(networkStatus, netName)
|
|
||||||
c.state.NetworkStatus = networkStatus
|
|
||||||
err = c.save()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload ports when there are still connected networks, maybe we removed the network interface with the child ip.
|
|
||||||
// Reloading without connected networks does not make sense, so we can skip this step.
|
|
||||||
if rootless.IsRootless() && len(networkStatus) > 0 {
|
|
||||||
if err := c.reloadRootlessRLKPortMapping(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update resolv.conf if required
|
|
||||||
if statusExist {
|
|
||||||
stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs))
|
|
||||||
for _, ip := range oldStatus.DNSServerIPs {
|
|
||||||
stringIPs = append(stringIPs, ip.String())
|
|
||||||
}
|
|
||||||
if len(stringIPs) > 0 {
|
|
||||||
logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
|
|
||||||
if err := c.removeNameserver(stringIPs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update /etc/hosts file
|
|
||||||
if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
|
|
||||||
// sync the names with c.getHostsEntries()
|
|
||||||
names := []string{c.Hostname(), c.config.Name}
|
|
||||||
rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...)
|
|
||||||
if len(rm) > 0 {
|
|
||||||
// make sure to lock this file to prevent concurrent writes when
|
|
||||||
// this is used a net dependency container
|
|
||||||
lock, err := lockfile.GetLockfile(file)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to lock hosts file: %w", err)
|
|
||||||
}
|
|
||||||
logrus.Debugf("Remove /etc/hosts entries %v", rm)
|
|
||||||
lock.Lock()
|
|
||||||
err = etchosts.Remove(file, rm)
|
|
||||||
lock.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectNetwork connects a container to a given network
|
|
||||||
func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
|
|
||||||
// only the bridge mode supports cni networks
|
|
||||||
if err := isBridgeNetMode(c.config.NetMode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
networks, err := c.networks()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if network exists and if the input is a ID we get the name
|
|
||||||
// CNI only uses names so it is important that we only use the name
|
|
||||||
netName, err = c.runtime.normalizeNetworkName(netName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.syncContainer(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get network status before we connect
|
|
||||||
networkStatus := c.getNetworkStatus()
|
|
||||||
|
|
||||||
// always add the short id as alias for docker compat
|
|
||||||
netOpts.Aliases = append(netOpts.Aliases, c.config.ID[:12])
|
|
||||||
|
|
||||||
if netOpts.InterfaceName == "" {
|
|
||||||
netOpts.InterfaceName = getFreeInterfaceName(networks)
|
|
||||||
if netOpts.InterfaceName == "" {
|
|
||||||
return errors.New("could not find free network interface name")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil {
|
|
||||||
// Docker compat: treat requests to attach already attached networks as a no-op, ignoring opts
|
|
||||||
if errors.Is(err, define.ErrNetworkConnected) && c.ensureState(define.ContainerStateConfigured) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.newNetworkEvent(events.NetworkConnect, netName)
|
|
||||||
if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if c.state.NetNS == nil {
|
|
||||||
return fmt.Errorf("unable to connect %s to %s: %w", nameOrID, netName, define.ErrNoNetwork)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := types.NetworkOptions{
|
|
||||||
ContainerID: c.config.ID,
|
|
||||||
ContainerName: getCNIPodName(c),
|
|
||||||
}
|
|
||||||
opts.PortMappings = c.convertPortMappings()
|
|
||||||
opts.Networks = map[string]types.PerNetworkOptions{
|
|
||||||
netName: netOpts,
|
|
||||||
}
|
|
||||||
|
|
||||||
results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(results) != 1 {
|
|
||||||
return errors.New("when adding aliases, results must be of length 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to get the old host entries before we add the new one to the status
|
|
||||||
// if we do not add do it here we will get the wrong existing entries which will throw of the logic
|
|
||||||
// we could also copy the map but this does not seem worth it
|
|
||||||
// sync the hostNames with c.getHostsEntries()
|
|
||||||
hostNames := []string{c.Hostname(), c.config.Name}
|
|
||||||
oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...)
|
|
||||||
|
|
||||||
// update network status
|
|
||||||
if networkStatus == nil {
|
|
||||||
networkStatus = make(map[string]types.StatusBlock, 1)
|
|
||||||
}
|
|
||||||
networkStatus[netName] = results[netName]
|
|
||||||
c.state.NetworkStatus = networkStatus
|
|
||||||
|
|
||||||
err = c.save()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The first network needs a port reload to set the correct child ip for the rootlessport process.
|
|
||||||
// Adding a second network does not require a port reload because the child ip is still valid.
|
|
||||||
if rootless.IsRootless() && len(networks) == 0 {
|
|
||||||
if err := c.reloadRootlessRLKPortMapping(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ipv6, err := c.checkForIPv6(networkStatus)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update resolv.conf if required
|
|
||||||
stringIPs := make([]string, 0, len(results[netName].DNSServerIPs))
|
|
||||||
for _, ip := range results[netName].DNSServerIPs {
|
|
||||||
if (ip.To4() == nil) && !ipv6 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
stringIPs = append(stringIPs, ip.String())
|
|
||||||
}
|
|
||||||
if len(stringIPs) > 0 {
|
|
||||||
logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
|
|
||||||
if err := c.addNameserver(stringIPs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update /etc/hosts file
|
|
||||||
if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
|
|
||||||
// make sure to lock this file to prevent concurrent writes when
|
|
||||||
// this is used a net dependency container
|
|
||||||
lock, err := lockfile.GetLockfile(file)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to lock hosts file: %w", err)
|
|
||||||
}
|
|
||||||
new := etchosts.GetNetworkHostEntries(results, hostNames...)
|
|
||||||
logrus.Debugf("Add /etc/hosts entries %v", new)
|
|
||||||
// use special AddIfExists API to make sure we only add new entries if an old one exists
|
|
||||||
// see the AddIfExists() comment for more information
|
|
||||||
lock.Lock()
|
|
||||||
err = etchosts.AddIfExists(file, oldHostEntries, new)
|
|
||||||
lock.Unlock()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get a free interface name for a new network
|
|
||||||
// return an empty string if no free name was found
|
|
||||||
func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string {
|
|
||||||
ifNames := make([]string, 0, len(networks))
|
|
||||||
for _, opts := range networks {
|
|
||||||
ifNames = append(ifNames, opts.InterfaceName)
|
|
||||||
}
|
|
||||||
for i := 0; i < 100000; i++ {
|
|
||||||
ifName := fmt.Sprintf("eth%d", i)
|
|
||||||
if !util.StringInSlice(ifName, ifNames) {
|
|
||||||
return ifName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisconnectContainerFromNetwork removes a container from its CNI network
|
|
||||||
func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
|
|
||||||
ctr, err := r.LookupContainer(nameOrID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ctr.NetworkDisconnect(nameOrID, netName, force)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectContainerToNetwork connects a container to a CNI network
|
|
||||||
func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
|
|
||||||
ctr, err := r.LookupContainer(nameOrID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ctr.NetworkConnect(nameOrID, netName, netOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name.
|
|
||||||
// If the network is not found a errors is returned.
|
|
||||||
func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
|
|
||||||
net, err := r.network.NetworkInspect(nameOrID)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return net.Name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ocicniPortsToNetTypesPorts convert the old port format to the new one
|
// ocicniPortsToNetTypesPorts convert the old port format to the new one
|
||||||
// while deduplicating ports into ranges
|
// while deduplicating ports into ranges
|
||||||
func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping {
|
func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping {
|
||||||
|
Reference in New Issue
Block a user