diff options
author | Matthew Heon <matthew.heon@pm.me> | 2020-06-09 17:10:37 -0400 |
---|---|---|
committer | Paul Holzinger <paul.holzinger@web.de> | 2020-12-07 19:26:23 +0100 |
commit | b0286d6b43ebec367c0d9ed87bc6566d76ece8f8 (patch) | |
tree | a0629a333e8b1c28dbd78ad75a8a4ccc537fb1f8 /libpod | |
parent | e74072e742a427fbd8577fdc98daf1133cf13c48 (diff) | |
download | podman-b0286d6b43ebec367c0d9ed87bc6566d76ece8f8.tar.gz podman-b0286d6b43ebec367c0d9ed87bc6566d76ece8f8.tar.bz2 podman-b0286d6b43ebec367c0d9ed87bc6566d76ece8f8.zip |
Implement pod-network-reload
This adds a new command, 'podman network reload', to reload the
networks of existing containers, forcing recreation of firewall
rules after e.g. `firewall-cmd --reload` wipes them out.
Under the hood, this works by calling CNI to tear down the
existing network, then recreate it using identical settings. We
request that CNI preserve the old IP and MAC address in most
cases (where the container only had 1 IP/MAC), but there will be
some downtime inherent to the teardown/bring-up approach. The
architecture of CNI doesn't really make doing this without
downtime easy (or maybe even possible...).
At present, this only works for root Podman, and only locally.
I don't think there is much of a point to adding remote support
(this is very much a local debugging command), but I think adding
rootless support (to kill/recreate slirp4netns) could be
valuable.
Signed-off-by: Matthew Heon <matthew.heon@pm.me>
Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container_api.go | 26 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 13 | ||||
-rw-r--r-- | libpod/container_internal_unsupported.go | 4 | ||||
-rw-r--r-- | libpod/networking_linux.go | 85 | ||||
-rw-r--r-- | libpod/networking_unsupported.go | 9 |
5 files changed, 132 insertions, 5 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go index 6a7ddc421..1b33f16b4 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -639,6 +639,32 @@ func (c *Container) Sync() error { return nil } +// ReloadNetwork reconfigures the container's network. +// Technically speaking, it will tear down and then reconfigure the container's +// network namespace, which will result in all firewall rules being recreated. +// It is mostly intended to be used in cases where the system firewall has been +// reloaded, and existing rules have been wiped out. It is expected that some +// downtime will result, as the rules are destroyed as part of this process. +// At present, this only works on root containers; it may be expanded to restart +// slirp4netns in the future to work with rootless containers as well. +// Requires that the container must be running or created. +func (c *Container) ReloadNetwork() error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { + return errors.Wrapf(define.ErrCtrStateInvalid, "cannot reload network unless container network has been configured") + } + + return c.reloadNetwork() +} + // Refresh is DEPRECATED and REMOVED. func (c *Container) Refresh(ctx context.Context) error { // This has been deprecated for a long while, and is in the process of diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 56575c195..a9a789ad8 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -230,6 +230,19 @@ func (c *Container) cleanupNetwork() error { return nil } +// reloadNetwork reloads the network for the given container, recreating +// firewall rules. +func (c *Container) reloadNetwork() error { + result, err := c.runtime.reloadContainerNetwork(c) + if err != nil { + return err + } + + c.state.NetworkStatus = result + + return c.save() +} + func (c *Container) getUserOverrides() *lookup.Overrides { var hasPasswdFile, hasGroupFile bool overrides := lookup.Overrides{} diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index c22e9a4a4..7f6fc9ec9 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -50,6 +50,10 @@ func (c *Container) cleanupOverlayMounts() error { return nil } +func (c *Container) reloadNetwork() error { + return define.ErrNotImplemented +} + func (c *Container) getUserOverrides() *lookup.Overrides { return nil } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 15e470c80..c7db95eb3 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "sort" "strings" "syscall" @@ -740,8 +741,9 @@ func (r *Runtime) closeNetNS(ctr *Container) error { return nil } -// Tear down a network namespace, undoing all state associated with it. -func (r *Runtime) teardownNetNS(ctr *Container) error { +// Tear down a container's CNI network configuration, but do not tear down the +// namespace itself. +func (r *Runtime) teardownCNI(ctr *Container) error { if ctr.state.NetNS == nil { // The container has no network namespace, we're set return nil @@ -780,6 +782,19 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID()) } } + return nil +} + +// Tear down a network namespace, undoing all state associated with it. +func (r *Runtime) teardownNetNS(ctr *Container) error { + if err := r.teardownCNI(ctr); err != nil { + return err + } + + networks, _, err := ctr.networks() + if err != nil { + return err + } // CNI-in-slirp4netns if rootless.IsRootless() && len(networks) != 0 { @@ -820,6 +835,68 @@ func getContainerNetNS(ctr *Container) (string, error) { return "", nil } +// Reload only works with containers with a configured network. +// It will tear down, and then reconfigure, the network of the container. +// This is mainly used when a reload of firewall rules wipes out existing +// firewall configuration. +// Efforts will be made to preserve MAC and IP addresses, but this only works if +// the container only joined a single CNI network, and was only assigned a +// single MAC or IP. +// Only works on root containers at present, though in the future we could +// extend this to stop + restart slirp4netns +func (r *Runtime) reloadContainerNetwork(ctr *Container) ([]*cnitypes.Result, error) { + if ctr.state.NetNS == nil { + return nil, errors.Wrapf(define.ErrCtrStateInvalid, "container %s network is not configured, refusing to reload", ctr.ID()) + } + if rootless.IsRootless() || ctr.config.NetMode.IsSlirp4netns() { + return nil, errors.Wrapf(define.ErrRootless, "network reload only supported for root containers") + } + + logrus.Infof("Going to reload container %s network", ctr.ID()) + + var requestedIP net.IP + var requestedMAC net.HardwareAddr + // Set requested IP and MAC address, if possible. + if len(ctr.state.NetworkStatus) == 1 { + result := ctr.state.NetworkStatus[0] + if len(result.IPs) == 1 { + resIP := result.IPs[0] + + requestedIP = resIP.Address.IP + ctr.requestedIP = requestedIP + logrus.Debugf("Going to preserve container %s IP address %s", ctr.ID(), ctr.requestedIP.String()) + + if resIP.Interface != nil && *resIP.Interface < len(result.Interfaces) && *resIP.Interface >= 0 { + var err error + requestedMAC, err = net.ParseMAC(result.Interfaces[*resIP.Interface].Mac) + if err != nil { + return nil, errors.Wrapf(err, "error parsing container %s MAC address %s", ctr.ID(), result.Interfaces[*resIP.Interface].Mac) + } + ctr.requestedMAC = requestedMAC + logrus.Debugf("Going to preserve container %s MAC address %s", ctr.ID(), ctr.requestedMAC.String()) + } + } + } + + err := r.teardownCNI(ctr) + if err != nil { + // teardownCNI will error if the iptables rules do not exists and this is the case after + // a firewall reload. The purpose of network reload is to recreate the rules if they do + // not exists so we should not log this specific error as error. This would confuse users otherwise. + b, rerr := regexp.MatchString("Couldn't load target `CNI-[a-f0-9]{24}':No such file or directory", err.Error()) + if rerr == nil && !b { + logrus.Error(err) + } else { + logrus.Info(err) + } + } + + // teardownCNI will clean the requested IP and MAC so we need to set them again + ctr.requestedIP = requestedIP + ctr.requestedMAC = requestedMAC + return r.configureNetNS(ctr, ctr.state.NetNS) +} + func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { var netStats *netlink.LinkStatistics // rootless v2 cannot seem to resolve its network connection to @@ -983,12 +1060,12 @@ func resultToBasicNetworkConfig(result *cnitypes.Result) (define.InspectBasicNet config.IPAddress = ctrIP.Address.IP.String() config.IPPrefixLen = size config.Gateway = ctrIP.Gateway.String() - if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface > 0 { + if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface >= 0 { config.MacAddress = result.Interfaces[*ctrIP.Interface].Mac } case ctrIP.Version == "4" && config.IPAddress != "": config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, ctrIP.Address.String()) - if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface > 0 { + if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface >= 0 { config.AdditionalMacAddresses = append(config.AdditionalMacAddresses, result.Interfaces[*ctrIP.Interface].Mac) } case ctrIP.Version == "6" && config.IPAddress == "": diff --git a/libpod/networking_unsupported.go b/libpod/networking_unsupported.go index 76bb01424..9e5c4adde 100644 --- a/libpod/networking_unsupported.go +++ b/libpod/networking_unsupported.go @@ -2,7 +2,10 @@ package libpod -import "github.com/containers/podman/v2/libpod/define" +import ( + cnitypes "github.com/containernetworking/cni/pkg/types/current" + "github.com/containers/podman/v2/libpod/define" +) func (r *Runtime) setupRootlessNetNS(ctr *Container) error { return define.ErrNotImplemented @@ -28,6 +31,10 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e return nil, define.ErrNotImplemented } +func (r *Runtime) reloadContainerNetwork(ctr *Container) ([]*cnitypes.Result, error) { + return nil, define.ErrNotImplemented +} + func getCNINetworksDir() (string, error) { return "", define.ErrNotImplemented } |