summaryrefslogtreecommitdiff
path: root/libpod/networking_linux.go
diff options
context:
space:
mode:
authorMatthew Heon <matthew.heon@pm.me>2020-06-09 17:10:37 -0400
committerPaul Holzinger <paul.holzinger@web.de>2020-12-07 19:26:23 +0100
commitb0286d6b43ebec367c0d9ed87bc6566d76ece8f8 (patch)
treea0629a333e8b1c28dbd78ad75a8a4ccc537fb1f8 /libpod/networking_linux.go
parente74072e742a427fbd8577fdc98daf1133cf13c48 (diff)
downloadpodman-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/networking_linux.go')
-rw-r--r--libpod/networking_linux.go85
1 files changed, 81 insertions, 4 deletions
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 == "":