Skip to content

Commit

Permalink
Merge pull request #8571 from Luap99/podman-network-reload
Browse files Browse the repository at this point in the history
Implement pod-network-reload
  • Loading branch information
openshift-merge-robot authored Dec 8, 2020
2 parents e2f9120 + b0286d6 commit 9b3a81a
Show file tree
Hide file tree
Showing 14 changed files with 376 additions and 13 deletions.
69 changes: 69 additions & 0 deletions cmd/podman/networks/reload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package network

import (
"fmt"

"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

var (
networkReloadDescription = `reload container networks, recreating firewall rules`
networkReloadCommand = &cobra.Command{
Use: "reload [options] [CONTAINER...]",
Short: "Reload firewall rules for one or more containers",
Long: networkReloadDescription,
RunE: networkReload,
Args: func(cmd *cobra.Command, args []string) error {
return validate.CheckAllLatestAndCIDFile(cmd, args, false, false)
},
ValidArgsFunction: common.AutocompleteContainers,
Example: `podman network reload --latest
podman network reload 3c13ef6dd843
podman network reload test1 test2`,
Annotations: map[string]string{
registry.ParentNSRequired: "",
},
}
)

var (
reloadOptions entities.NetworkReloadOptions
)

func reloadFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&reloadOptions.All, "all", "a", false, "Reload network configuration of all containers")
}

func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
Command: networkReloadCommand,
Parent: networkCmd,
})
reloadFlags(networkReloadCommand.Flags())
validate.AddLatestFlag(networkReloadCommand, &reloadOptions.Latest)
}

func networkReload(cmd *cobra.Command, args []string) error {
responses, err := registry.ContainerEngine().NetworkReload(registry.Context(), args, reloadOptions)
if err != nil {
return err
}

var errs utils.OutputErrors
for _, r := range responses {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}

return errs.PrintErrors()
}
62 changes: 62 additions & 0 deletions docs/source/markdown/podman-network-reload.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
% podman-network-reload(1)

## NAME
podman\-network\-reload - Reload network configuration for containers

## SYNOPSIS
**podman network reload** [*options*] [*container...*]

## DESCRIPTION
Reload one or more container network configurations.

Rootful Podman relies on iptables rules in order to provide network connectivity. If the iptables rules are deleted,
this happens for example with `firewall-cmd --reload`, the container loses network connectivity. This command restores
the network connectivity.

This command is not available for rootless users since rootless containers are not affected by such connectivity problems.

## OPTIONS
#### **--all**, **-a**

Reload network configuration of all containers.

#### **--latest**, **-l**

Instead of providing the container name or ID, use the last created container. If you use methods other than Podman
to run containers such as CRI-O, the last started container could be from either of those methods.

The latest option is not supported on the remote client.

## EXAMPLE

Reload the network configuration after a firewall reload.

```
# podman run -p 80:80 -d nginx
b1b538e8bc4078fc3ee1c95b666ebc7449b9a97bacd15bcbe464a29e1be59c1c
# curl 127.0.0.1
works
# sudo firewall-cmd --reload
success
# curl 127.0.0.1
hangs
# podman network reload b1b538e8bc40
b1b538e8bc4078fc3ee1c95b666ebc7449b9a97bacd15bcbe464a29e1be59c1c
# curl 127.0.0.1
works
```

Reload the network configuration for all containers.

```
# podman network reload --all
b1b538e8bc4078fc3ee1c95b666ebc7449b9a97bacd15bcbe464a29e1be59c1c
fe7e8eca56f844ec33af10f0aa3b31b44a172776e3277b9550a623ed5d96e72b
```


## SEE ALSO
podman(1), podman-network(1)

## HISTORY
December 2020, Originally compiled by Paul Holzinger <[email protected]>
17 changes: 9 additions & 8 deletions docs/source/markdown/podman-network.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ The network command manages CNI networks for Podman.

## COMMANDS

| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| connect | [podman-network-connect(1)](podman-network-connect.1.md)| Connect a container to a network|
| create | [podman-network-create(1)](podman-network-create.1.md)| Create a Podman CNI network|
| disconnect | [podman-network-disconnect(1)](podman-network-disconnect.1.md)| Disconnect a container from a network|
| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md)| Displays the raw CNI network configuration for one or more networks|
| ls | [podman-network-ls(1)](podman-network-ls.1.md)| Display a summary of CNI networks |
| rm | [podman-network-rm(1)](podman-network-rm.1.md)| Remove one or more CNI networks |
| Command | Man Page | Description |
| ---------- | -------------------------------------------------------------- | ------------------------------------------------------------------- |
| connect | [podman-network-connect(1)](podman-network-connect.1.md) | Connect a container to a network |
| create | [podman-network-create(1)](podman-network-create.1.md) | Create a Podman CNI network |
| disconnect | [podman-network-disconnect(1)](podman-network-disconnect.1.md) | Disconnect a container from a network |
| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md) | Displays the raw CNI network configuration for one or more networks |
| ls | [podman-network-ls(1)](podman-network-ls.1.md) | Display a summary of CNI networks |
| reload | [podman-network-reload(1)](podman-network-reload.1.md) | Reload network configuration for containers |
| rm | [podman-network-rm(1)](podman-network-rm.1.md) | Remove one or more CNI networks |

## SEE ALSO
podman(1)
2 changes: 2 additions & 0 deletions docs/source/network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ Network

:doc:`ls <markdown/podman-network-ls.1>` network list

:doc:`reload <markdown/podman-network-reload.1>` network reload

:doc:`rm <markdown/podman-network-rm.1>` network rm
26 changes: 26 additions & 0 deletions libpod/container_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,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{}
Expand Down
4 changes: 4 additions & 0 deletions libpod/container_internal_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
85 changes: 81 additions & 4 deletions libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"syscall"
Expand Down Expand Up @@ -741,8 +742,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
Expand Down Expand Up @@ -781,6 +783,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 {
Expand Down Expand Up @@ -821,6 +836,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
Expand Down Expand Up @@ -984,12 +1061,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 == "":
Expand Down
9 changes: 8 additions & 1 deletion libpod/networking_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
1 change: 1 addition & 0 deletions pkg/domain/entities/engine_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type ContainerEngine interface {
NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error
NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]NetworkInspectReport, []error, error)
NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error)
NetworkReload(ctx context.Context, names []string, options NetworkReloadOptions) ([]*NetworkReloadReport, error)
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
Expand Down
Loading

0 comments on commit 9b3a81a

Please sign in to comment.