Repository: moby/moby (language: go)

Issue:
docker 20.10.x is not removing iptables entries
I am relaying this to you from a downstream user, so I don't have the specific information from his system.

He is telling me that docker 20.10.x is not cleaning up iptables rules when containers exit.
I don't know if this is a bug or if it is supposed to not do this, so I wanted to relay the information to you.

The downstream bug report is below. I don't have nvidia hardware so I don't know that I can run the container he is using for the test.

Please let me know if you need more information to assist with this and I'll do my best to get it.

Thanks much for your time.

William

https://bugs.gentoo.org/765487



Discussion / hints:
Thanks for creating this @williamh , I totally lost track of you asking me to do it!

This issue doesn't need the nvidia runtime to show the issue, I should have specified that.  Here's the same issue using `hello-world`...

"Empty" iptables to start:
```
ScottE-LT-NVIDIA ~ # iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy DROP)
target     prot opt source               destination
DOCKER-USER  all  --  anywhere             anywhere
DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain DOCKER (1 references)
target     prot opt source               destination

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target     prot opt source               destination
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target     prot opt source               destination
DROP       all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere

Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
```
Basic run of `hello-world` with a port mapping:
```
scotte@ScottE-LT-NVIDIA ~ $ docker run --rm -ti -p 9999:8888 hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

scotte@ScottE-LT-NVIDIA ~ $
```
Even though the container is exited (and removed), the mapping still remains in iptables:

```
ScottE-LT-NVIDIA ~ # iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy DROP)
target     prot opt source               destination
DOCKER-USER  all  --  anywhere             anywhere
DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain DOCKER (1 references)
target     prot opt source               destination
ACCEPT     tcp  --  anywhere             172.17.0.2           tcp dpt:8888

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target     prot opt source               destination
DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target     prot opt source               destination
DROP       all  --  anywhere             anywhere
RETURN     all  --  anywhere             anywhere

Chain DOCKER-USER (1 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
```

Docker configuration is pretty generic:
```
scotte@ScottE-LT-NVIDIA ~ $ docker info
Client:
 Context:    default
 Debug Mode: false

Server:
 Containers: 1
  Running: 0
  Paused: 0
  Stopped: 1
 Images: 7
 Server Version: 20.10.5
 Storage Driver: btrfs
  Build Version: Btrfs v5.11
  Library Version: 102
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: nvidia runc io.containerd.runc.v2 io.containerd.runtime.v1.linux
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e
 runc version: ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 init version: N/A (expected: de40ad007797e0dcd8b7126f27bb87401d224240)
 Security Options:
  apparmor
  seccomp
   Profile: default
  userns
 Kernel Version: 5.11.7-gentoo
 Operating System: Gentoo/Linux
 OSType: linux
 Architecture: x86_64
 CPUs: 12
 Total Memory: 30.98GiB
 Name: ScottE-LT-NVIDIA
 ID: XUWO:RML4:GTOX:KPWE:U5EF:FBTO:TCXF:AKV6:5VOM:K3JD:6H22:NEHT
 Docker Root Dir: /var/lib/docker/500000.500000
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false
```

Nothing fancy with iptables:
```
ScottE-LT-NVIDIA ~ # iptables --version
iptables v1.8.7 (legacy)
```

Not sure where else to look for sources of the problem, but this problem does not exist if I downgrade Docker to 19.xx, so seems related to something that changed with 20.xx.


Hi all, does anyone have any idea what is going on with this?

Thanks much.

William
Hi,

can we have an update on this?

Thanks,

William
I'm game for collecting more data or trying something new.  I was thinking maybe seeing if the `nftables` version made any difference from the legacy `iptables`, but haven't gotten around to building a kernel with the right bits for that yet.  That's the only place to look next that I can think of, so hoping for some direction. :-)
I've tested on a Debian system, and I'm not able to reproduce (and it's even using the nftables wrapper for iptables, which I'd expect to have more flaky behavior like this than the legacy iptables does):

```console
$ docker version
Client: Docker Engine - Community
 Version:           20.10.5
...
Server: Docker Engine - Community
 Engine:
  Version:          20.10.5
...
$ /usr/sbin/iptables --version
iptables v1.8.7 (nf_tables)
$ sudo iptables -nvL > before.txt
$ docker run --rm -ti -p 9999:8888 hello-world

Hello from Docker!
...

$ sudo iptables -nvL > after.txt
$ diff -u before.txt after.txt
$ 
```

It might be worth trying the published static binaries from https://download.docker.com/linux/static/stable/ to help narrow down where this is coming from. :confused:
I changed to iptables-nft, and updated to Docker 20.10.6, and still have the same issue.

My suspicion is that this is somehow cgroup related, as I get two errors in docker.log - one which looks suspiciously like https://github.com/containerd/cgroups/issues/83 (`error: read /proc/self/fd/14: file already closed`) followed immediately by `level=warning msg="Failed to delete conntrack state for 172.17.0.2: invalid argument"`

On Gentoo, I'm using OpenRC with "hybrid" cgroups (effectively cgroupv1).  Not sure where to look next, since it wasn't `legacy` vs `nft` backends.
> I changed to iptables-nft, and updated to Docker 20.10.6, and still have the same issue.
> 
> My suspicion is that this is somehow cgroup related, as I get two errors in docker.log - one which looks suspiciously like [containerd/cgroups#83](https://github.com/containerd/cgroups/issues/83) (`error: read /proc/self/fd/14: file already closed`) followed immediately by `level=warning msg="Failed to delete conntrack state for 172.17.0.2: invalid argument"`
> 
> On Gentoo, I'm using OpenRC with "hybrid" cgroups (effectively cgroupv1). Not sure where to look next, since it wasn't `legacy` vs `nft` backends.

I'm really sorry I didn't see this until now.

The only difference I can think of is I am running "unified" cgroups (cgroups v2).
You might want to try switching to that and see if it works.
Hmm, that's an interesting idea - I'll give "unified" a try and see what breaks. :-)
Restart docker on asustor NAS - the same problem with iptables but restart (start) clean rules.

After stop docker rules persistent. After start are clean and I have this Warnings.

It is docker or system specific??
```
INFO[2021-10-03T01:34:52.153030693+02:00] Loading containers: start.
WARN[2021-10-03T01:34:52.986256347+02:00] Running modprobe bridge br_netfilter failed with message: modprobe: invalid option -- 'a'
BusyBox v1.19.3 (2021-08-30 01:01:19 CST) multi-call binary.

Usage: modprobe [-qfwrsv] MODULE [symbol=value]...

        -r      Remove MODULE (stacks) or do autoclean
        -q      Quiet
        -v      Verbose
        -f      Force
        -w      Wait for unload
        -s      Report via syslog instead of stderr

, error: exit status 1
WARN[2021-10-03T01:34:52.992635398+02:00] Running iptables --wait -t nat -L -n failed with message: `iptables v1.4.13: unknown option "--wait"
Try `iptables -h' or 'iptables --help' for more information.`, error: exit status 2
WARN[2021-10-03T01:34:52.993892781+02:00] Failed to find ip6tables: exec: "ip6tables": executable file not found in $PATH
WARN[2021-10-03T01:34:53.240785543+02:00] Could not load necessary modules for Conntrack: Running modprobe nf_conntrack failed with message: `modprobe: invalid option -- 'a'
BusyBox v1.19.3 (2021-08-30 01:01:19 CST) multi-call binary.

Usage: modprobe [-qfwrsv] MODULE [symbol=value]...

        -r      Remove MODULE (stacks) or do autoclean
        -q      Quiet
        -v      Verbose
        -f      Force
        -w      Wait for unload
        -s      Report via syslog instead of stderr`, error: exit status 1
```
> It might be worth trying the published static binaries from https://download.docker.com/linux/static/stable/ to help narrow down where this is coming from. 😕

No change with those static 20.10.9 binaries.

```
ScottE-LT-NVIDIA /tmp # cksum /usr/bin/dockerd docker/dockerd
271407687 64758736 /usr/bin/dockerd
271407687 64758736 docker/dockerd
```

What I see post-run (either prebuilt bins or built on my Gentoo system) is...

```
...
time="2021-11-16T17:24:31.105225908-08:00" level=debug msg="container mounted via layerStore: &{/var/lib/docker/btrfs/subvolumes/a2accc27e1202cefca53da6a01317e37f13cb7f1f8be85b9b8bdba33a36c27dc 0x3988640 0x3988640}" container=5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068
time="2021-11-16T17:24:31.115314245-08:00" level=debug msg="Calling POST /v1.41/containers/5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068/attach?stderr=1&stdin=1&stdout=1&stream=1"
time="2021-11-16T17:24:31.115656174-08:00" level=debug msg="attach: stdout: begin"
time="2021-11-16T17:24:31.115661120-08:00" level=debug msg="attach: stdin: begin"
time="2021-11-16T17:24:31.115665185-08:00" level=debug msg="attach: stderr: begin"
time="2021-11-16T17:24:31.116208454-08:00" level=debug msg="Calling POST /v1.41/containers/5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068/wait?condition=removed"
time="2021-11-16T17:24:31.117466333-08:00" level=debug msg="Calling POST /v1.41/containers/5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068/start"
time="2021-11-16T17:24:31.118389406-08:00" level=debug msg="container mounted via layerStore: &{/var/lib/docker/btrfs/subvolumes/a2accc27e1202cefca53da6a01317e37f13cb7f1f8be85b9b8bdba33a36c27dc 0x3988640 0x3988640}" container=5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068
time="2021-11-16T17:24:31.119040782-08:00" level=debug msg="Assigning addresses for endpoint kind_austin's interface on network bridge"
time="2021-11-16T17:24:31.119095716-08:00" level=debug msg="RequestAddress(LocalDefault/172.17.0.0/16, <nil>, map[])"
time="2021-11-16T17:24:31.119147688-08:00" level=debug msg="Request address PoolID:172.17.0.0/16 App: ipam/default/data, ID: LocalDefault/172.17.0.0/16, DBIndex: 0x0, Bits: 65536, Unselected: 65533, Sequence: (0xc0000000, 1)->(0x0, 2046)->(0x1, 1)->end Curr:0 Serial:false PrefAddress:<nil> "
time="2021-11-16T17:24:31.134095409-08:00" level=debug msg="Assigning addresses for endpoint kind_austin's interface on network bridge"
time="2021-11-16T17:24:31.151614479-08:00" level=debug msg="Programming external connectivity on endpoint kind_austin (e61038559040edec396d909535b2d1e657da939bdae6c0c4da308f61b269b287)"
time="2021-11-16T17:24:31.152107082-08:00" level=debug msg="/sbin/iptables, [-t nat -A DOCKER -p tcp -d 0/0 --dport 9999 -j DNAT --to-destination 172.17.0.2:9996 ! -i docker0]"
time="2021-11-16T17:24:31.152975265-08:00" level=debug msg="/sbin/iptables, [-t filter -A DOCKER ! -i docker0 -o docker0 -p tcp -d 172.17.0.2 --dport 9996 -j ACCEPT]"
time="2021-11-16T17:24:31.153971090-08:00" level=debug msg="/sbin/iptables, [-t nat -A POSTROUTING -p tcp -s 172.17.0.2 -d 172.17.0.2 --dport 9996 -j MASQUERADE]"
time="2021-11-16T17:24:31.158063799-08:00" level=debug msg="port_mapping: v6Listenable=false (listen tcp6 [::1]:0: socket: address family not supported by protocol)"
time="2021-11-16T17:24:31.163770567-08:00" level=debug msg="EnableService 5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068 START"
time="2021-11-16T17:24:31.163820308-08:00" level=debug msg="EnableService 5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068 DONE"
time="2021-11-16T17:24:31.172377005-08:00" level=debug msg="bundle dir created" bundle=/var/run/docker/containerd/5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068 module=libcontainerd namespace=moby root=/var/lib/docker/btrfs/subvolumes/a2accc27e1202cefca53da6a01317e37f13cb7f1f8be85b9b8bdba33a36c27dc
time="2021-11-16T17:24:31.198270156-08:00" level=debug msg="event published" ns=moby topic=/containers/create type=containerd.events.ContainerCreate
time="2021-11-16T17:24:31.245615652-08:00" level=info msg="starting signal loop" namespace=moby path=/run/docker/containerd/daemon/io.containerd.runtime.v2.task/moby/5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068 pid=4688
time="2021-11-16T17:24:31.378820221-08:00" level=debug msg="garbage collected" d=6.644509ms
time="2021-11-16T17:24:31-08:00" level=warning msg="file does not exist: /proc/sys/net/ipv6/conf/all/disable_ipv6 : stat /proc/sys/net/ipv6/conf/all/disable_ipv6: no such file or directory Has IPv6 been disabled in this node's kernel?"
time="2021-11-16T17:24:31.423541819-08:00" level=debug msg="sandbox set key processing took 53.505935ms for container 5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068"
time="2021-11-16T17:24:31.624779273-08:00" level=debug msg="event forwarded" ns=moby topic=/tasks/create type=containerd.events.TaskCreate
time="2021-11-16T17:24:31.624938943-08:00" level=debug msg=event module=libcontainerd namespace=moby topic=/tasks/create
time="2021-11-16T17:24:31.642834526-08:00" level=debug msg="event forwarded" ns=moby topic=/tasks/start type=containerd.events.TaskStart
time="2021-11-16T17:24:31.642954156-08:00" level=debug msg=event module=libcontainerd namespace=moby topic=/tasks/start
time="2021-11-16T17:24:31.643536676-08:00" level=debug msg="event forwarded" ns=moby topic=/tasks/exit type=containerd.events.TaskExit
time="2021-11-16T17:24:31.643630093-08:00" level=debug msg=event module=libcontainerd namespace=moby topic=/tasks/exit
time="2021-11-16T17:24:31.650258552-08:00" level=debug msg="Calling POST /v1.41/containers/5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068/resize?h=43&w=170"
time="2021-11-16T17:24:31.667691280-08:00" level=debug msg="event forwarded" ns=moby topic=/tasks/delete type=containerd.events.TaskDelete
time="2021-11-16T17:24:31.667804829-08:00" level=debug msg=event module=libcontainerd namespace=moby topic=/tasks/delete
time="2021-11-16T17:24:31.667795950-08:00" level=info msg="shim disconnected" id=5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068
time="2021-11-16T17:24:31.667820089-08:00" level=info msg="ignoring event" container=5a89d9bbbcc31dde840ffe88655180257af5a9c672648212933ccb16b3a23068 module=libcontainerd namespace=moby topic=/tasks/delete type="*events.TaskDelete"
time="2021-11-16T17:24:31.667834413-08:00" level=error msg="copy shim log" error="read /proc/self/fd/14: file already closed"
time="2021-11-16T17:24:31.667968035-08:00" level=debug msg="attach: stdout: end"
time="2021-11-16T17:24:31.667977215-08:00" level=debug msg="attach: stderr: end"
time="2021-11-16T17:24:31.668005441-08:00" level=debug msg="attach: stdin: end"
time="2021-11-16T17:24:31.668018430-08:00" level=debug msg="attach done"
time="2021-11-16T17:24:31.668163284-08:00" level=debug msg="Revoking external connectivity on endpoint kind_austin (e61038559040edec396d909535b2d1e657da939bdae6c0c4da308f61b269b287)"
time="2021-11-16T17:24:31.670398168-08:00" level=warning msg="Failed to delete conntrack state for 172.17.0.2: invalid argument"
time="2021-11-16T17:24:31.670410671-08:00" level=debug msg="DeleteConntrackEntries purged ipv4:0, ipv6:0"
time="2021-11-16T17:24:31.744376143-08:00" level=debug msg="Releasing addresses for endpoint kind_austin's interface on network bridge"
time="2021-11-16T17:24:31.744441356-08:00" level=debug msg="ReleaseAddress(LocalDefault/172.17.0.0/16, 172.17.0.2)"
time="2021-11-16T17:24:31.744530448-08:00" level=debug msg="Released address PoolID:LocalDefault/172.17.0.0/16, Address:172.17.0.2 Sequence:App: ipam/default/data, ID: LocalDefault/172.17.0.0/16, DBIndex: 0x0, Bits: 65536, Unselected: 65532, Sequence: (0xe0000000, 1)->(0x0, 2046)->(0x1, 1)->end Curr:3"
time="2021-11-16T17:24:31.768076250-08:00" level=debug msg="event published" ns=moby topic=/containers/delete type=containerd.events.ContainerDelete
time="2021-11-16T17:24:31.788236744-08:00" level=debug msg="Closing buffered stdin pipe"
time="2021-11-16T17:24:32.001946090-08:00" level=debug msg="garbage collected" d=7.783251ms
```

The `"Failed to delete conntrack state for 172.17.0.2: invalid argument"` seems bad, as does `read /proc/self/fd/14: file already closed"`.

FWIW
```
ScottE-LT-NVIDIA /tmp # zcat /proc/config.gz | grep -i conntrack
CONFIG_NF_CONNTRACK=y
CONFIG_NF_CONNTRACK_MARK=y
# CONFIG_NF_CONNTRACK_ZONES is not set
# CONFIG_NF_CONNTRACK_PROCFS is not set
# CONFIG_NF_CONNTRACK_EVENTS is not set
# CONFIG_NF_CONNTRACK_TIMEOUT is not set
# CONFIG_NF_CONNTRACK_TIMESTAMP is not set
CONFIG_NF_CONNTRACK_LABELS=y
# CONFIG_NF_CONNTRACK_AMANDA is not set
# CONFIG_NF_CONNTRACK_FTP is not set
# CONFIG_NF_CONNTRACK_H323 is not set
# CONFIG_NF_CONNTRACK_IRC is not set
# CONFIG_NF_CONNTRACK_NETBIOS_NS is not set
# CONFIG_NF_CONNTRACK_SNMP is not set
# CONFIG_NF_CONNTRACK_PPTP is not set
# CONFIG_NF_CONNTRACK_SANE is not set
# CONFIG_NF_CONNTRACK_SIP is not set
# CONFIG_NF_CONNTRACK_TFTP is not set
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
# CONFIG_NF_CONNTRACK_BRIDGE is not set
```





Those log entries look like red herrings.  Full log for `docker run --rm -ti -p 9999:9999 hello-world` looks like:

```
time="2021-11-18T12:17:04.397131685-08:00" level=debug msg="Calling HEAD /_ping"
time="2021-11-18T12:17:04.397706525-08:00" level=debug msg="Calling POST /v1.41/containers/create"
time="2021-11-18T12:17:04.397871550-08:00" level=debug msg="form data: {\"AttachStderr\":true,\"AttachStdin\":true,\"AttachStdout\":true,\"Cmd\":null,\"Domainname\":\"\",\"Entrypoint\":null,\"Env\":null,\"ExposedPorts\":{\"9999/tcp\":{}},\"HostConfig\":{\"AutoRemove\":true,\"Binds\":null,\"BlkioDeviceReadBps\":null,\"BlkioDeviceReadIOps\":null,\"BlkioDeviceWriteBps\":null,\"BlkioDeviceWriteIOps\":null,\"BlkioWeight\":0,\"BlkioWeightDevice\":[],\"CapAdd\":null,\"CapDrop\":null,\"Cgroup\":\"\",\"CgroupParent\":\"\",\"CgroupnsMode\":\"\",\"ConsoleSize\":[0,0],\"ContainerIDFile\":\"\",\"CpuCount\":0,\"CpuPercent\":0,\"CpuPeriod\":0,\"CpuQuota\":0,\"CpuRealtimePeriod\":0,\"CpuRealtimeRuntime\":0,\"CpuShares\":0,\"CpusetCpus\":\"\",\"CpusetMems\":\"\",\"DeviceCgroupRules\":null,\"DeviceRequests\":null,\"Devices\":[],\"Dns\":[],\"DnsOptions\":[],\"DnsSearch\":[],\"ExtraHosts\":null,\"GroupAdd\":null,\"IOMaximumBandwidth\":0,\"IOMaximumIOps\":0,\"IpcMode\":\"\",\"Isolation\":\"\",\"KernelMemory\":0,\"KernelMemoryTCP\":0,\"Links\":null,\"LogConfig\":{\"Config\":{},\"Type\":\"\"},\"MaskedPaths\":null,\"Memory\":0,\"MemoryReservation\":0,\"MemorySwap\":0,\"MemorySwappiness\":-1,\"NanoCpus\":0,\"NetworkMode\":\"default\",\"OomKillDisable\":false,\"OomScoreAdj\":0,\"PidMode\":\"\",\"PidsLimit\":0,\"PortBindings\":{\"9999/tcp\":[{\"HostIp\":\"\",\"HostPort\":\"9999\"}]},\"Privileged\":false,\"PublishAllPorts\":false,\"ReadonlyPaths\":null,\"ReadonlyRootfs\":false,\"RestartPolicy\":{\"MaximumRetryCount\":0,\"Name\":\"no\"},\"SecurityOpt\":null,\"ShmSize\":0,\"UTSMode\":\"\",\"Ulimits\":null,\"UsernsMode\":\"\",\"VolumeDriver\":\"\",\"VolumesFrom\":null},\"Hostname\":\"\",\"Image\":\"hello-world\",\"Labels\":{},\"NetworkingConfig\":{\"EndpointsConfig\":{}},\"OnBuild\":null,\"OpenStdin\":true,\"Platform\":null,\"StdinOnce\":true,\"Tty\":true,\"User\":\"\",\"Volumes\":{},\"WorkingDir\":\"\"}"
time="2021-11-18T12:17:04.442298262-08:00" level=debug msg="container mounted via layerStore: &{/var/lib/docker/btrfs/subvolumes/b4508e12d1be34dcf69c53738ac5a1a58f6e1e996efc1239d26f9c9d2eaa6196 0x55eaee852360 0x55eaee852360}" container=ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3
time="2021-11-18T12:17:04.448704812-08:00" level=debug msg="Calling POST /v1.41/containers/ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3/attach?stderr=1&stdin=1&stdout=1&stream=1"
time="2021-11-18T12:17:04.448782249-08:00" level=debug msg="attach: stdin: begin"
time="2021-11-18T12:17:04.448793559-08:00" level=debug msg="attach: stdout: begin"
time="2021-11-18T12:17:04.448804780-08:00" level=debug msg="attach: stderr: begin"
time="2021-11-18T12:17:04.448910647-08:00" level=debug msg="Calling POST /v1.41/containers/ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3/wait?condition=removed"
time="2021-11-18T12:17:04.449484573-08:00" level=debug msg="Calling POST /v1.41/containers/ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3/start"
time="2021-11-18T12:17:04.449931664-08:00" level=debug msg="container mounted via layerStore: &{/var/lib/docker/btrfs/subvolumes/b4508e12d1be34dcf69c53738ac5a1a58f6e1e996efc1239d26f9c9d2eaa6196 0x55eaee852360 0x55eaee852360}" container=ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3
time="2021-11-18T12:17:04.450178491-08:00" level=debug msg="Assigning addresses for endpoint strange_turing's interface on network bridge"
time="2021-11-18T12:17:04.450193731-08:00" level=debug msg="RequestAddress(LocalDefault/172.17.0.0/16, <nil>, map[])"
time="2021-11-18T12:17:04.450219175-08:00" level=debug msg="Request address PoolID:172.17.0.0/16 App: ipam/default/data, ID: LocalDefault/172.17.0.0/16, DBIndex: 0x0, Bits: 65536, Unselected: 65533, Sequence: (0xc0000000, 1)->(0x0, 2046)->(0x1, 1)->end Curr:0 Serial:false PrefAddress:<nil> "
time="2021-11-18T12:17:04.462329669-08:00" level=debug msg="Assigning addresses for endpoint strange_turing's interface on network bridge"
time="2021-11-18T12:17:04.479435267-08:00" level=debug msg="Programming external connectivity on endpoint strange_turing (55f723c53e1654ad732605664e73b077738204ee4658d6894e145787ae1d9cd3)"
time="2021-11-18T12:17:04.480987331-08:00" level=debug msg="/sbin/iptables, [-t nat -A DOCKER -p tcp -d 0/0 --dport 9999 -j DNAT --to-destination 172.17.0.2:9999 ! -i docker0]"
time="2021-11-18T12:17:04.486955104-08:00" level=debug msg="/sbin/iptables, [-t filter -A DOCKER ! -i docker0 -o docker0 -p tcp -d 172.17.0.2 --dport 9999 -j ACCEPT]"
time="2021-11-18T12:17:04.492584688-08:00" level=debug msg="/sbin/iptables, [-t nat -A POSTROUTING -p tcp -s 172.17.0.2 -d 172.17.0.2 --dport 9999 -j MASQUERADE]"
time="2021-11-18T12:17:04.497747322-08:00" level=debug msg="port_mapping: v6Listenable=false (listen tcp6 [::1]:0: socket: address family not supported by protocol)"
time="2021-11-18T12:17:04.503574215-08:00" level=debug msg="EnableService ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3 START"
time="2021-11-18T12:17:04.503585422-08:00" level=debug msg="EnableService ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3 DONE"
time="2021-11-18T12:17:04.506951819-08:00" level=debug msg="bundle dir created" bundle=/var/run/docker/containerd/ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3 module=libcontainerd namespace=moby root=/var/lib/docker/btrfs/subvolumes/b4508e12d1be34dcf69c53738ac5a1a58f6e1e996efc1239d26f9c9d2eaa6196
time="2021-11-18T12:17:04.519446244-08:00" level=debug msg="event published" ns=moby topic=/containers/create type=containerd.events.ContainerCreate
time="2021-11-18T12:17:04.533870533-08:00" level=info msg="starting signal loop" namespace=moby path=/run/docker/containerd/daemon/io.containerd.runtime.v2.task/moby/ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3 pid=3715
time="2021-11-18T12:17:04.553386404-08:00" level=debug msg="garbage collected" d=6.048082ms
time="2021-11-18T12:17:04-08:00" level=warning msg="file does not exist: /proc/sys/net/ipv6/conf/all/disable_ipv6 : stat /proc/sys/net/ipv6/conf/all/disable_ipv6: no such file or directory Has IPv6 been disabled in this node's kernel?"
time="2021-11-18T12:17:04.728954283-08:00" level=debug msg="sandbox set key processing took 61.85309ms for container ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3"
time="2021-11-18T12:17:04.750530528-08:00" level=debug msg="event forwarded" ns=moby topic=/tasks/create type=containerd.events.TaskCreate
time="2021-11-18T12:17:04.750719390-08:00" level=debug msg=event module=libcontainerd namespace=moby topic=/tasks/create
time="2021-11-18T12:17:04.769952259-08:00" level=debug msg="event forwarded" ns=moby topic=/tasks/start type=containerd.events.TaskStart
time="2021-11-18T12:17:04.770084939-08:00" level=debug msg=event module=libcontainerd namespace=moby topic=/tasks/start
time="2021-11-18T12:17:04.770484540-08:00" level=debug msg="event forwarded" ns=moby topic=/tasks/exit type=containerd.events.TaskExit
time="2021-11-18T12:17:04.770610513-08:00" level=debug msg=event module=libcontainerd namespace=moby topic=/tasks/exit
time="2021-11-18T12:17:04.776958294-08:00" level=debug msg="Calling POST /v1.41/containers/ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3/resize?h=43&w=170"
time="2021-11-18T12:17:04.786418207-08:00" level=debug msg="event forwarded" ns=moby topic=/tasks/delete type=containerd.events.TaskDelete
time="2021-11-18T12:17:04.786547481-08:00" level=info msg="shim disconnected" id=ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3
time="2021-11-18T12:17:04.786606395-08:00" level=warning msg="cleaning up after shim disconnected" id=ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3 namespace=moby
time="2021-11-18T12:17:04.786620358-08:00" level=info msg="cleaning up dead shim"
time="2021-11-18T12:17:04.786628606-08:00" level=debug msg=event module=libcontainerd namespace=moby topic=/tasks/delete
time="2021-11-18T12:17:04.786661707-08:00" level=info msg="ignoring event" container=ce255e2bdb7f4312a485e64bd11908f6984cff67d024fee518591b1fc1e941c3 module=libcontainerd namespace=moby topic=/tasks/delete type="*events.TaskDelete"
time="2021-11-18T12:17:04.808542902-08:00" level=warning msg="cleanup warnings time=\"2021-11-18T12:17:04-08:00\" level=info msg=\"starting signal loop\" namespace=moby pid=3829\n"
time="2021-11-18T12:17:04.809246192-08:00" level=debug msg="attach: stderr: end"
time="2021-11-18T12:17:04.809318559-08:00" level=debug msg="attach: stdin: end"
time="2021-11-18T12:17:04.809335775-08:00" level=debug msg="attach: stdout: end"
time="2021-11-18T12:17:04.809435401-08:00" level=debug msg="attach done"
time="2021-11-18T12:17:04.809836356-08:00" level=debug msg="Revoking external connectivity on endpoint strange_turing (55f723c53e1654ad732605664e73b077738204ee4658d6894e145787ae1d9cd3)"
time="2021-11-18T12:17:04.846185644-08:00" level=debug msg="DeleteConntrackEntries purged ipv4:0, ipv6:0"
time="2021-11-18T12:17:04.915391725-08:00" level=debug msg="Releasing addresses for endpoint strange_turing's interface on network bridge"
time="2021-11-18T12:17:04.915462381-08:00" level=debug msg="ReleaseAddress(LocalDefault/172.17.0.0/16, 172.17.0.2)"
time="2021-11-18T12:17:04.915519433-08:00" level=debug msg="Released address PoolID:LocalDefault/172.17.0.0/16, Address:172.17.0.2 Sequence:App: ipam/default/data, ID: LocalDefault/172.17.0.0/16, DBIndex: 0x0, Bits: 65536, Unselected: 65532, Sequence: (0xe0000000, 1)->(0x0, 2046)->(0x1, 1)->end Curr:3"
time="2021-11-18T12:17:04.948118948-08:00" level=debug msg="event published" ns=moby topic=/containers/delete type=containerd.events.ContainerDelete
time="2021-11-18T12:17:04.967818825-08:00" level=debug msg="Closing buffered stdin pipe"
time="2021-11-18T12:17:05.201776575-08:00" level=debug msg="garbage collected" d=7.597925ms
```

No more errors ( just just enabled "everything" in the kernel related to iptables and cgroups), but still growing stale iptables entries:
```
ScottE-LT-NVIDIA ~ # iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  anywhere            !127.0.0.0/8          ADDRTYPE match dst-type LOCAL
DOCKER     all  --  anywhere            !127.0.0.0/8          ADDRTYPE match dst-type LOCAL
DOCKER     all  --  anywhere            !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        anywhere
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:9999
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:9999
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:9999

Chain DOCKER (4 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere
DNAT       tcp  --  anywhere             anywhere             tcp dpt:9999 to:172.17.0.2:9999
DNAT       tcp  --  anywhere             anywhere             tcp dpt:9999 to:172.17.0.2:9999
```

After a ton of bisecting, it looks like the issue is that if `ip6tables` (and potentially `ip6tables-{apply|restore|save}` don't exist, then Docker exhibits the behavior I see.  I run an IPv6-less system, and Gentoo dutifully removes the softlinks that are `/sbin/ip6tables*` .  When those point to `xtables-legacy-multi`, then all is good.  If they aren't present, problems.
I was able to reproduce this issue by removing ip6tables symlink in Docker's dev container. After digging a bit, here's what I found:

* The first API call to `iptables` package initializes some internal state (including `supportsCOpt`) by calling `detectIptables()` ;
* This function checks if `iptables` and `ip6tables` bins are available and checks the version of `iptables` to know if it supports `-C` flag. This specific flag is used to ask `iptables` if a particular rule exists and it was introduced in v1.4.11 (released in May 2011). Note that these checks are done in that specific order and `detectIptables()` early exits when `ip6tables` isn't found.
* When deleting a port mapping, `iptables` package first checks if the given nat rule exists before deleting it. At this point there're two cases: either `iptables` supports `-C` flag and then the package uses it ; or it doesn't, and then it relies on string comparison between the output of `iptables -t nat -S POSTROUTING` and a formatted string. This comparison actually fails because the output contains more data than what's being looked for (ie. IP addresses use CIDR notation and `-p`/`-m` flags are dumped).

So the origin of this bug is the ordering in `detectIptables()`. The check on `ip6tables` availability was introduced in moby/libnetwork#2572 and has been released in v20.10.0.

I'll submit a PR to fix this, but given how old `iptables` v1.4.11 is, I'm also wondering if the whole string comparison is still a good idea.

Fix patch:
diff --git a/libnetwork/iptables/iptables.go b/libnetwork/iptables/iptables.go
index 3fc70c9f6cbe1..1eb15e18c1580 100644
--- a/libnetwork/iptables/iptables.go
+++ b/libnetwork/iptables/iptables.go
@@ -8,7 +8,6 @@ import (
 	"fmt"
 	"net"
 	"os/exec"
-	"regexp"
 	"strconv"
 	"strings"
 	"sync"
@@ -57,7 +56,6 @@ var (
 	iptablesPath  string
 	ip6tablesPath string
 	supportsXlock = false
-	supportsCOpt  = false
 	xLockWaitMsg  = "Another app is currently holding the xtables lock"
 	// used to lock iptables commands if xtables lock is not supported
 	bestEffortLock sync.Mutex
@@ -89,19 +87,27 @@ func (e ChainError) Error() string {
 	return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
 }
 
-func probe() {
+func detectIptables() {
 	path, err := exec.LookPath("iptables")
 	if err != nil {
-		logrus.Warnf("Failed to find iptables: %v", err)
+		logrus.WithError(err).Warnf("failed to find iptables")
 		return
 	}
-	if out, err := exec.Command(path, "--wait", "-t", "nat", "-L", "-n").CombinedOutput(); err != nil {
-		logrus.Warnf("Running iptables --wait -t nat -L -n failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
+	iptablesPath = path
+
+	// The --wait flag was added in iptables v1.6.0.
+	// TODO remove this check once we drop support for CentOS/RHEL 7, which uses an older version of iptables
+	if out, err := exec.Command(path, "--wait", "-L", "-n").CombinedOutput(); err != nil {
+		logrus.WithError(err).Infof("unable to detect if iptables supports xlock: 'iptables --wait -L -n': `%s`", strings.TrimSpace(string(out)))
+	} else {
+		supportsXlock = true
 	}
-	_, err = exec.LookPath("ip6tables")
+
+	path, err = exec.LookPath("ip6tables")
 	if err != nil {
-		logrus.Warnf("Failed to find ip6tables: %v", err)
-		return
+		logrus.WithError(err).Warnf("unable to find ip6tables")
+	} else {
+		ip6tablesPath = path
 	}
 }
 
@@ -113,32 +119,11 @@ func initFirewalld() {
 		return
 	}
 	if err := FirewalldInit(); err != nil {
-		logrus.Debugf("Fail to initialize firewalld: %v, using raw iptables instead", err)
-	}
-}
-
-func detectIptables() {
-	path, err := exec.LookPath("iptables")
-	if err != nil {
-		return
-	}
-	iptablesPath = path
-	path, err = exec.LookPath("ip6tables")
-	if err != nil {
-		return
+		logrus.WithError(err).Debugf("unable to initialize firewalld; using raw iptables instead")
 	}
-	ip6tablesPath = path
-	supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
-	mj, mn, mc, err := GetVersion()
-	if err != nil {
-		logrus.Warnf("Failed to read iptables version: %v", err)
-		return
-	}
-	supportsCOpt = supportsCOption(mj, mn, mc)
 }
 
 func initDependencies() {
-	probe()
 	initFirewalld()
 	detectIptables()
 }
@@ -478,26 +463,9 @@ func (iptable IPTable) exists(native bool, table Table, chain string, rule ...st
 		return false
 	}
 
-	if supportsCOpt {
-		// if exit status is 0 then return true, the rule exists
-		_, err := f(append([]string{"-t", string(table), "-C", chain}, rule...)...)
-		return err == nil
-	}
-
-	// parse "iptables -S" for the rule (it checks rules in a specific chain
-	// in a specific table and it is very unreliable)
-	return iptable.existsRaw(table, chain, rule...)
-}
-
-func (iptable IPTable) existsRaw(table Table, chain string, rule ...string) bool {
-	path := iptablesPath
-	if iptable.Version == IPv6 {
-		path = ip6tablesPath
-	}
-	ruleString := fmt.Sprintf("%s %s\n", chain, strings.Join(rule, " "))
-	existingRules, _ := exec.Command(path, "-t", string(table), "-S", chain).Output()
-
-	return strings.Contains(string(existingRules), ruleString)
+	// if exit status is 0 then return true, the rule exists
+	_, err := f(append([]string{"-t", string(table), "-C", chain}, rule...)...)
+	return err == nil
 }
 
 // Maximum duration that an iptables operation can take
@@ -551,6 +519,9 @@ func (iptable IPTable) raw(args ...string) ([]byte, error) {
 	path := iptablesPath
 	commandName := "iptables"
 	if iptable.Version == IPv6 {
+		if ip6tablesPath == "" {
+			return nil, fmt.Errorf("ip6tables is missing")
+		}
 		path = ip6tablesPath
 		commandName = "ip6tables"
 	}
@@ -592,15 +563,6 @@ func (iptable IPTable) ExistChain(chain string, table Table) bool {
 	return false
 }
 
-// GetVersion reads the iptables version numbers during initialization
-func GetVersion() (major, minor, micro int, err error) {
-	out, err := exec.Command(iptablesPath, "--version").CombinedOutput()
-	if err == nil {
-		major, minor, micro = parseVersionNumbers(string(out))
-	}
-	return
-}
-
 // SetDefaultPolicy sets the passed default policy for the table/chain
 func (iptable IPTable) SetDefaultPolicy(table Table, chain string, policy Policy) error {
 	if err := iptable.RawCombinedOutput("-t", string(table), "-P", chain, string(policy)); err != nil {
@@ -609,19 +571,6 @@ func (iptable IPTable) SetDefaultPolicy(table Table, chain string, policy Policy
 	return nil
 }
 
-func parseVersionNumbers(input string) (major, minor, micro int) {
-	re := regexp.MustCompile(`v\d*.\d*.\d*`)
-	line := re.FindString(input)
-	fmt.Sscanf(line, "v%d.%d.%d", &major, &minor, &micro)
-	return
-}
-
-// iptables -C, --check option was added in v.1.4.11
-// http://ftp.netfilter.org/pub/iptables/changes-iptables-1.4.11.txt
-func supportsCOption(mj, mn, mc int) bool {
-	return mj > 1 || (mj == 1 && (mn > 4 || (mn == 4 && mc >= 11)))
-}
-
 // AddReturnRule adds a return rule for the chain in the filter table
 func (iptable IPTable) AddReturnRule(chain string) error {
 	var (


Test patch:
diff --git a/libnetwork/iptables/iptables_test.go b/libnetwork/iptables/iptables_test.go
index d46e5153cc36a..decf948033f09 100644
--- a/libnetwork/iptables/iptables_test.go
+++ b/libnetwork/iptables/iptables_test.go
@@ -13,19 +13,16 @@ import (
 	"golang.org/x/sync/errgroup"
 )
 
-const chainName = "DOCKEREST"
-
-var natChain *ChainInfo
-var filterChain *ChainInfo
-var bridgeName string
-
-func TestNewChain(t *testing.T) {
-	var err error
-
+const (
+	chainName  = "DOCKEREST"
 	bridgeName = "lo"
+)
+
+func createNewChain(t *testing.T) (*IPTable, *ChainInfo, *ChainInfo) {
+	t.Helper()
 	iptable := GetIptable(IPv4)
 
-	natChain, err = iptable.NewChain(chainName, Nat, false)
+	natChain, err := iptable.NewChain(chainName, Nat, false)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -34,7 +31,7 @@ func TestNewChain(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	filterChain, err = iptable.NewChain(chainName, Filter, false)
+	filterChain, err := iptable.NewChain(chainName, Filter, false)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -42,18 +39,23 @@ func TestNewChain(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+
+	return iptable, natChain, filterChain
+}
+
+func TestNewChain(t *testing.T) {
+	createNewChain(t)
 }
 
 func TestForward(t *testing.T) {
+	iptable, natChain, filterChain := createNewChain(t)
+
 	ip := net.ParseIP("192.168.1.1")
 	port := 1234
 	dstAddr := "172.17.0.1"
 	dstPort := 4321
 	proto := "tcp"
 
-	bridgeName := "lo"
-	iptable := GetIptable(IPv4)
-
 	err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort, bridgeName)
 	if err != nil {
 		t.Fatal(err)
@@ -99,16 +101,13 @@ func TestForward(t *testing.T) {
 }
 
 func TestLink(t *testing.T) {
-	var err error
-
-	bridgeName := "lo"
-	iptable := GetIptable(IPv4)
+	iptable, _, filterChain := createNewChain(t)
 	ip1 := net.ParseIP("192.168.1.1")
 	ip2 := net.ParseIP("192.168.1.2")
 	port := 1234
 	proto := "tcp"
 
-	err = filterChain.Link(Append, ip1, ip2, port, proto, bridgeName)
+	err := filterChain.Link(Append, ip1, ip2, port, proto, bridgeName)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -141,10 +140,11 @@ func TestLink(t *testing.T) {
 }
 
 func TestPrerouting(t *testing.T) {
+	iptable, natChain, _ := createNewChain(t)
+
 	args := []string{
 		"-i", "lo",
 		"-d", "192.168.1.1"}
-	iptable := GetIptable(IPv4)
 
 	err := natChain.Prerouting(Insert, args...)
 	if err != nil {
@@ -162,10 +162,11 @@ func TestPrerouting(t *testing.T) {
 }
 
 func TestOutput(t *testing.T) {
+	iptable, natChain, _ := createNewChain(t)
+
 	args := []string{
 		"-o", "lo",
 		"-d", "192.168.1.1"}
-	iptable := GetIptable(IPv4)
 
 	err := natChain.Output(Insert, args...)
 	if err != nil {
@@ -196,6 +197,8 @@ func TestConcurrencyNoWait(t *testing.T) {
 // Note that if iptables does not support the xtable lock on this
 // system, then allowXlock has no effect -- it will always be off.
 func RunConcurrencyTest(t *testing.T, allowXlock bool) {
+	_, natChain, _ := createNewChain(t)
+
 	if !allowXlock && supportsXlock {
 		supportsXlock = false
 		defer func() { supportsXlock = true }()
@@ -219,7 +222,8 @@ func RunConcurrencyTest(t *testing.T, allowXlock bool) {
 }
 
 func TestCleanup(t *testing.T) {
-	var err error
+	iptable, _, filterChain := createNewChain(t)
+
 	var rules []byte
 
 	// Cleanup filter/FORWARD first otherwise output of iptables-save is dirty
@@ -227,14 +231,13 @@ func TestCleanup(t *testing.T) {
 		string(Delete), "FORWARD",
 		"-o", bridgeName,
 		"-j", filterChain.Name}
-	iptable := GetIptable(IPv4)
 
-	if _, err = iptable.Raw(link...); err != nil {
+	if _, err := iptable.Raw(link...); err != nil {
 		t.Fatal(err)
 	}
 	filterChain.Remove()
 
-	err = iptable.RemoveExistingChain(chainName, Nat)
+	err := iptable.RemoveExistingChain(chainName, Nat)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -284,44 +287,15 @@ func TestExistsRaw(t *testing.T) {
 		if err != nil {
 			t.Fatalf("i=%d, err: %v", i, err)
 		}
-		if !iptable.existsRaw(Filter, testChain1, r.rule...) {
+		if !iptable.exists(true, Filter, testChain1, r.rule...) {
 			t.Fatalf("Failed to detect rule. i=%d", i)
 		}
 		// Truncate the rule
 		trg := r.rule[len(r.rule)-1]
 		trg = trg[:len(trg)-2]
 		r.rule[len(r.rule)-1] = trg
-		if iptable.existsRaw(Filter, testChain1, r.rule...) {
+		if iptable.exists(true, Filter, testChain1, r.rule...) {
 			t.Fatalf("Invalid detection. i=%d", i)
 		}
 	}
 }
-
-func TestGetVersion(t *testing.T) {
-	mj, mn, mc := parseVersionNumbers("iptables v1.4.19.1-alpha")
-	if mj != 1 || mn != 4 || mc != 19 {
-		t.Fatal("Failed to parse version numbers")
-	}
-}
-
-func TestSupportsCOption(t *testing.T) {
-	input := []struct {
-		mj int
-		mn int
-		mc int
-		ok bool
-	}{
-		{1, 4, 11, true},
-		{1, 4, 12, true},
-		{1, 5, 0, true},
-		{0, 4, 11, false},
-		{0, 5, 12, false},
-		{1, 3, 12, false},
-		{1, 4, 10, false},
-	}
-	for ind, inp := range input {
-		if inp.ok != supportsCOption(inp.mj, inp.mn, inp.mc) {
-			t.Fatalf("Incorrect check: %d", ind)
-		}
-	}
-}