diff --git a/docs/manual/network.md b/docs/manual/network.md index a1de21af8..f987484d0 100644 --- a/docs/manual/network.md +++ b/docs/manual/network.md @@ -191,7 +191,7 @@ mgmt: If the existing bridge has already been addressed with IPv4/6 address, containerlab will respect this address and use it in the IPAM configuration blob of the docker network. -If there is no existing IPv4/6 address defined for the custom bridge, Docker will assign the first interface from the subnet associated with the bridge. +If there is no existing IPv4/6 address defined for the custom bridge, docker will assign the first interface from the subnet associated with the bridge. It is possible to set the desired gateway IP (that is the IP assigned to the bridge) with the `ipv4-gw/ipv6-gw` setting under `mgmt` container: @@ -230,7 +230,15 @@ topology: # your regular topology definition ``` -1. When set to `false`, containerlab will not touch iptables rules. On most docker installation this will result in restricted external access. +1. When set to `false`, containerlab will not touch iptables rules. On most docker installations this will result in restricted external access. + +???error "'missing DOCKER-USER iptables chain' error" + Containerlab will throw an error "missing DOCKER-USER iptables chain" when this chain is not found. This error is typically caused by two factors + + 1. Old docker version installed. Typically seen on Centos systems. Minimum required docker version is 17.06. + 2. Docker is installed incorrectly. It is recommended to follow the [official installation procedures](https://docs.docker.com/engine/install/) by selecting "Installation per distro" menu option. + + When docker is correctly installed, additional iptables chains will become available and the error will not appear. ### connection details When containerlab needs to create the management network, it asks the docker daemon to do this. Docker will fulfill the request and will create a network with the underlying linux bridge interface backing it. The bridge interface name is generated by the docker daemon, but it is easy to find it: diff --git a/runtime/docker/docker.go b/runtime/docker/docker.go index f97a4f27f..20454b846 100644 --- a/runtime/docker/docker.go +++ b/runtime/docker/docker.go @@ -234,8 +234,12 @@ func (d *DockerRuntime) postCreateNetActions() (err error) { if err != nil { log.Warnf("failed to disable TX checksum offloading for the %s bridge interface: %v", d.mgmt.Bridge, err) } + err = d.installIPTablesFwdRule() + if err != nil { + log.Warnf("%v", err) + } - return d.installIPTablesFwdRule() + return nil } // DeleteNet deletes a docker bridge @@ -269,8 +273,12 @@ func (d *DockerRuntime) DeleteNet(ctx context.Context) (err error) { // bridge name associated with the network br := "br-" + nres.ID[:12] + err = d.deleteIPTablesFwdRule(br) + if err != nil { + log.Warnf("%v", err) + } - return d.deleteIPTablesFwdRule(br) + return nil } // CreateContainer creates a docker container (but does not start it) diff --git a/runtime/docker/iptables.go b/runtime/docker/iptables.go index 1197db991..be3e5c136 100644 --- a/runtime/docker/iptables.go +++ b/runtime/docker/iptables.go @@ -33,19 +33,22 @@ func (d *DockerRuntime) installIPTablesFwdRule() (err error) { log.Debugf("found iptables forwarding rule targeting the bridge %q. Skipping creation of the forwarding rule.", d.mgmt.Bridge) return err } - if err != nil { - return err + // non nil error typically means that DOCKER-USER chain doesn't exist + // this happens with old docker installations (centos7 hello) from default repos + return fmt.Errorf("missing DOCKER-USER iptables chain. See http://containerlab.srlinux.dev/manual/network/#external-access") } cmd := fmt.Sprintf(iptAllowCmd, d.mgmt.Bridge) - _, err = exec.Command("iptables", strings.Split(cmd, " ")...).Output() + log.Debugf("Installing iptables rules for bridge %q", d.mgmt.Bridge) + + stdOutErr, err := exec.Command("iptables", strings.Split(cmd, " ")...).CombinedOutput() if err != nil { - return + log.Warnf("Iptables install stdout/stderr result is: %s", stdOutErr) + return fmt.Errorf("unable to install iptables rules: %w", err) } - - return err + return nil } // deleteIPTablesFwdRule deletes `allow` rule installed with InstallIPTablesFwdRule when the bridge interface doesn't exist anymore @@ -60,11 +63,16 @@ func (d *DockerRuntime) deleteIPTablesFwdRule(br string) (err error) { } // first check if a rule exists before trying to delete it - res, _ := exec.Command("iptables", strings.Split(iptCheckCmd, " ")...).Output() + res, err := exec.Command("iptables", strings.Split(iptCheckCmd, " ")...).Output() if !bytes.Contains(res, []byte(d.mgmt.Bridge)) { log.Debug("external access iptables rule doesn't exist. Skipping deletion") return nil } + if err != nil { + // non nil error typically means that DOCKER-USER chain doesn't exist + // this happens with old docker installations (centos7 hello) from default repos + return fmt.Errorf("missing DOCKER-USER iptables chain. See http://containerlab.srlinux.dev/manual/network/#external-access") + } _, err = utils.BridgeByName(br) if err == nil { @@ -73,11 +81,11 @@ func (d *DockerRuntime) deleteIPTablesFwdRule(br string) (err error) { } cmd := fmt.Sprintf(iptDelCmd, br) - - _, err = exec.Command("iptables", strings.Split(cmd, " ")...).Output() + log.Debugf("Removing clab iptables rules for bridge %q", br) + stdOutErr, err := exec.Command("iptables", strings.Split(cmd, " ")...).CombinedOutput() if err != nil { - return + log.Warnf("Iptables delete stdout/stderr result is: %s", stdOutErr) + return fmt.Errorf("unable to delete iptables rules: %w", err) } - - return err + return nil }