Repository: moby/moby (language: go)

Issue:
DOCKER-USER table is not created
### Description

DOCKER-USER table is not created when ip6tables support is enabled.

### Reproduce

```
/etc/docker/daemon.json
{
    "experimental": true,
    "ip6tables": true
}
```
systemctl start docker

### Expected behavior

DOCKER-USER chain is created.

```
Chain FORWARD (policy DROP 1 packets, 72 bytes)
 pkts bytes target     prot opt in     out     source               destination
 4143 2270K DOCKER-USER  all      *      *       ::/0                 ::/0

Chain DOCKER-USER (1 references)
 pkts bytes target     prot opt in     out     source               destination
 4143 2270K RETURN     all      *      *       ::/0                 ::/0
```

### docker version

```bash
Client: Docker Engine - Community
 Version:           20.10.21
 API version:       1.41
 Go version:        go1.18.7
 Git commit:        baeda1f
 Built:             Tue Oct 25 18:01:03 2022
 OS/Arch:           linux/arm64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.21
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.18.7
  Git commit:       3056208
  Built:            Tue Oct 25 17:59:22 2022
  OS/Arch:          linux/arm64
  Experimental:     true
 containerd:
  Version:          1.6.9
  GitCommit:        1c90a442489720eec95342e1789ee8a5e1b9536f
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```


### docker info

```bash
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Docker Buildx (Docker Inc., v0.9.1-docker)
  compose: Docker Compose (Docker Inc., v2.12.2)
  reg: Docker Reg (Docker Inc., v0.16.1)

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 6
 Server Version: 20.10.21
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 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: runc io.containerd.runc.v2 io.containerd.runtime.v1.linux
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 1c90a442489720eec95342e1789ee8a5e1b9536f
 runc version: v1.1.4-0-g5fd4c4d
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 5.10.110
 Operating System: Ubuntu 20.04.5 LTS
 OSType: linux
 Architecture: aarch64
 CPUs: 4
 Total Memory: 3.811GiB
 Name: Nano
 ID: Y2I5:TOTL:CXYQ:LQIQ:ULZ2:W3FT:X3AI:LDZI:2FWR:5B5M:6D2K:PUBL
 Docker Root Dir: /home/.docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: true
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false
```


### Additional Info

ip6tables -w -N DOCKER-USER
ip6tables -w -I DOCKER-USER -j RETURN
ip6tables -w -I FORWARD -j DOCKER-USER


Discussion / hints:

Fix patch:
diff --git a/libnetwork/controller.go b/libnetwork/controller.go
index 0878f1057af26..8ceb592d32f02 100644
--- a/libnetwork/controller.go
+++ b/libnetwork/controller.go
@@ -1301,3 +1301,23 @@ func (c *controller) iptablesEnabled() bool {
 	}
 	return enabled
 }
+
+func (c *controller) ip6tablesEnabled() bool {
+	c.Lock()
+	defer c.Unlock()
+
+	if c.cfg == nil {
+		return false
+	}
+	// parse map cfg["bridge"]["generic"]["EnableIP6Table"]
+	cfgBridge, ok := c.cfg.DriverCfg["bridge"].(map[string]interface{})
+	if !ok {
+		return false
+	}
+	cfgGeneric, ok := cfgBridge[netlabel.GenericData].(options.Generic)
+	if !ok {
+		return false
+	}
+	enabled, _ := cfgGeneric["EnableIP6Tables"].(bool)
+	return enabled
+}
diff --git a/libnetwork/firewall_linux.go b/libnetwork/firewall_linux.go
index a990ec8e91ab1..c28a65eea1eff 100644
--- a/libnetwork/firewall_linux.go
+++ b/libnetwork/firewall_linux.go
@@ -21,24 +21,40 @@ func setupArrangeUserFilterRule(c *controller) {
 // IPTableForwarding is disabled, because it contains rules configured by user that
 // are beyond docker engine's control.
 func arrangeUserFilterRule() {
-	if ctrl == nil || !ctrl.iptablesEnabled() {
-		return
-	}
-	// TODO IPv6 support
-	iptable := iptables.GetIptable(iptables.IPv4)
-	_, err := iptable.NewChain(userChain, iptables.Filter, false)
-	if err != nil {
-		logrus.Warnf("Failed to create %s chain: %v", userChain, err)
+	if ctrl == nil {
 		return
 	}
 
-	if err = iptable.AddReturnRule(userChain); err != nil {
-		logrus.Warnf("Failed to add the RETURN rule for %s: %v", userChain, err)
-		return
+	conds := []struct {
+		ipVer iptables.IPVersion
+		cond  bool
+	}{
+		{ipVer: iptables.IPv4, cond: ctrl.iptablesEnabled()},
+		{ipVer: iptables.IPv6, cond: ctrl.ip6tablesEnabled()},
 	}
 
-	err = iptable.EnsureJumpRule("FORWARD", userChain)
-	if err != nil {
-		logrus.Warnf("Failed to ensure the jump rule for %s: %v", userChain, err)
+	for _, ipVerCond := range conds {
+		cond := ipVerCond.cond
+		if !cond {
+			continue
+		}
+
+		ipVer := ipVerCond.ipVer
+		iptable := iptables.GetIptable(ipVer)
+		_, err := iptable.NewChain(userChain, iptables.Filter, false)
+		if err != nil {
+			logrus.WithError(err).Warnf("Failed to create %s %v chain", userChain, ipVer)
+			return
+		}
+
+		if err = iptable.AddReturnRule(userChain); err != nil {
+			logrus.WithError(err).Warnf("Failed to add the RETURN rule for %s %v", userChain, ipVer)
+			return
+		}
+
+		err = iptable.EnsureJumpRule("FORWARD", userChain)
+		if err != nil {
+			logrus.WithError(err).Warnf("Failed to ensure the jump rule for %s %v", userChain, ipVer)
+		}
 	}
 }


Test patch:
diff --git a/libnetwork/firewall_linux_test.go b/libnetwork/firewall_linux_test.go
index 37fcda1b2a786..fbac4e73e6af7 100644
--- a/libnetwork/firewall_linux_test.go
+++ b/libnetwork/firewall_linux_test.go
@@ -17,7 +17,8 @@ const (
 )
 
 func TestUserChain(t *testing.T) {
-	iptable := iptables.GetIptable(iptables.IPv4)
+	iptable4 := iptables.GetIptable(iptables.IPv4)
+	iptable6 := iptables.GetIptable(iptables.IPv6)
 
 	nc, err := New()
 	assert.NilError(t, err)
@@ -54,33 +55,41 @@ func TestUserChain(t *testing.T) {
 			c := nc.(*controller)
 			c.cfg.DriverCfg["bridge"] = map[string]interface{}{
 				netlabel.GenericData: options.Generic{
-					"EnableIPTables": tc.iptables,
+					"EnableIPTables":  tc.iptables,
+					"EnableIP6Tables": tc.iptables,
 				},
 			}
 
 			// init. condition, FORWARD chain empty DOCKER-USER not exist
-			assert.DeepEqual(t, getRules(t, fwdChainName), []string{"-P FORWARD ACCEPT"})
+			assert.DeepEqual(t, getRules(t, iptables.IPv4, fwdChainName), []string{"-P FORWARD ACCEPT"})
+			assert.DeepEqual(t, getRules(t, iptables.IPv6, fwdChainName), []string{"-P FORWARD ACCEPT"})
 
 			if tc.insert {
-				_, err = iptable.Raw("-A", fwdChainName, "-j", "DROP")
+				_, err = iptable4.Raw("-A", fwdChainName, "-j", "DROP")
+				assert.NilError(t, err)
+				_, err = iptable6.Raw("-A", fwdChainName, "-j", "DROP")
 				assert.NilError(t, err)
 			}
 			arrangeUserFilterRule()
 
-			assert.DeepEqual(t, getRules(t, fwdChainName), tc.fwdChain)
+			assert.DeepEqual(t, getRules(t, iptables.IPv4, fwdChainName), tc.fwdChain)
+			assert.DeepEqual(t, getRules(t, iptables.IPv6, fwdChainName), tc.fwdChain)
 			if tc.userChain != nil {
-				assert.DeepEqual(t, getRules(t, usrChainName), tc.userChain)
+				assert.DeepEqual(t, getRules(t, iptables.IPv4, usrChainName), tc.userChain)
+				assert.DeepEqual(t, getRules(t, iptables.IPv6, usrChainName), tc.userChain)
 			} else {
-				_, err := iptable.Raw("-S", usrChainName)
-				assert.Assert(t, err != nil, "chain %v: created unexpectedly", usrChainName)
+				_, err := iptable4.Raw("-S", usrChainName)
+				assert.Assert(t, err != nil, "ipv4 chain %v: created unexpectedly", usrChainName)
+				_, err = iptable6.Raw("-S", usrChainName)
+				assert.Assert(t, err != nil, "ipv6 chain %v: created unexpectedly", usrChainName)
 			}
 		})
 		resetIptables(t)
 	}
 }
 
-func getRules(t *testing.T, chain string) []string {
-	iptable := iptables.GetIptable(iptables.IPv4)
+func getRules(t *testing.T, ipVer iptables.IPVersion, chain string) []string {
+	iptable := iptables.GetIptable(ipVer)
 
 	t.Helper()
 	output, err := iptable.Raw("-S", chain)
@@ -94,10 +103,13 @@ func getRules(t *testing.T, chain string) []string {
 }
 
 func resetIptables(t *testing.T) {
-	iptable := iptables.GetIptable(iptables.IPv4)
-
 	t.Helper()
-	_, err := iptable.Raw("-F", fwdChainName)
-	assert.NilError(t, err)
-	_ = iptable.RemoveExistingChain(usrChainName, "")
+
+	for _, ipVer := range []iptables.IPVersion{iptables.IPv4, iptables.IPv6} {
+		iptable := iptables.GetIptable(ipVer)
+
+		_, err := iptable.Raw("-F", fwdChainName)
+		assert.Check(t, err)
+		_ = iptable.RemoveExistingChain(usrChainName, "")
+	}
 }