Browse code

Add Info.FirewallBackend

Report FirewallBackend in "docker info".

It's currently "iptables" or "iptables+firewalld" on Linux, and
omitted on Windows.

Signed-off-by: Rob Murray <rob.murray@docker.com>

Rob Murray authored on 2025/03/19 19:23:45
Showing 10 changed files
... ...
@@ -106,6 +106,10 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht
106 106
 			info.RegistryConfig.AllowNondistributableArtifactsCIDRs = []*registry.NetIPNet{}
107 107
 			info.RegistryConfig.AllowNondistributableArtifactsHostnames = []string{}
108 108
 		}
109
+		if versions.LessThan(version, "1.49") {
110
+			// FirewallBackend field introduced in API v1.49.
111
+			info.FirewallBackend = nil
112
+		}
109 113
 
110 114
 		// TODO(thaJeztah): Expected commits are deprecated, and should no longer be set in API 1.49.
111 115
 		info.ContainerdCommit.Expected = info.ContainerdCommit.ID //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.49.
... ...
@@ -6856,6 +6856,8 @@ definitions:
6856 6856
               description: "The network pool size"
6857 6857
               type: "integer"
6858 6858
               example: "24"
6859
+      FirewallBackend:
6860
+        $ref: "#/definitions/FirewallInfo"
6859 6861
       Warnings:
6860 6862
         description: |
6861 6863
           List of warnings / informational messages about missing features, or
... ...
@@ -6939,6 +6941,37 @@ definitions:
6939 6939
             default: "plugins.moby"
6940 6940
             example: "plugins.moby"
6941 6941
 
6942
+  FirewallInfo:
6943
+    description: |
6944
+      Information about the daemon's firewalling configuration.
6945
+
6946
+      This field is currently only used on Linux, and omitted on other platforms.
6947
+    type: "object"
6948
+    x-nullable: true
6949
+    properties:
6950
+      Driver:
6951
+        description: |
6952
+          The name of the firewall backend driver.
6953
+        type: "string"
6954
+        example: "nftables"
6955
+      Info:
6956
+        description: |
6957
+          Information about the firewall backend, provided as
6958
+          "label" / "value" pairs.
6959
+
6960
+          <p><br /></p>
6961
+
6962
+          > **Note**: The information returned in this field, including the
6963
+          > formatting of values and labels, should not be considered stable,
6964
+          > and may change without notice.
6965
+        type: "array"
6966
+        items:
6967
+          type: "array"
6968
+          items:
6969
+            type: "string"
6970
+        example:
6971
+          - ["ReloadedAt", "2025-01-01T00:00:00Z"]
6972
+
6942 6973
   # PluginsInfo is a temp struct holding Plugins name
6943 6974
   # registered with docker daemon. It is used by Info struct
6944 6975
   PluginsInfo:
... ...
@@ -73,6 +73,7 @@ type Info struct {
73 73
 	SecurityOptions     []string
74 74
 	ProductLicense      string               `json:",omitempty"`
75 75
 	DefaultAddressPools []NetworkAddressPool `json:",omitempty"`
76
+	FirewallBackend     *FirewallInfo        `json:"FirewallBackend,omitempty"`
76 77
 	CDISpecDirs         []string
77 78
 
78 79
 	Containerd *ContainerdInfo `json:",omitempty"`
... ...
@@ -151,3 +152,11 @@ type NetworkAddressPool struct {
151 151
 	Base string
152 152
 	Size int
153 153
 }
154
+
155
+// FirewallInfo describes the firewall backend.
156
+type FirewallInfo struct {
157
+	// Driver is the name of the firewall backend driver.
158
+	Driver string `json:"Driver"`
159
+	// Info is a list of label/value pairs, containing information related to the firewall.
160
+	Info [][2]string `json:"Info,omitempty"`
161
+}
... ...
@@ -93,6 +93,7 @@ func (daemon *Daemon) SystemInfo(ctx context.Context) (*system.Info, error) {
93 93
 	daemon.fillSecurityOptions(v, sysInfo, &cfg.Config)
94 94
 	daemon.fillLicense(v)
95 95
 	daemon.fillDefaultAddressPools(ctx, v, &cfg.Config)
96
+	daemon.fillFirewallInfo(v)
96 97
 
97 98
 	return v, nil
98 99
 }
... ...
@@ -284,6 +285,13 @@ func (daemon *Daemon) fillDefaultAddressPools(ctx context.Context, v *system.Inf
284 284
 	}
285 285
 }
286 286
 
287
+func (daemon *Daemon) fillFirewallInfo(v *system.Info) {
288
+	if daemon.netController == nil {
289
+		return
290
+	}
291
+	v.FirewallBackend = daemon.netController.FirewallBackend()
292
+}
293
+
287 294
 func hostName(ctx context.Context) string {
288 295
 	ctx, span := tracing.StartSpan(ctx, "hostName")
289 296
 	defer span.End()
... ...
@@ -21,6 +21,8 @@ keywords: "API, Docker, rcli, REST, documentation"
21 21
   encoded OCI Platform type) allowing to specify a platform of the multi-platform
22 22
   image to inspect.
23 23
   This option is mutually exclusive with the `manifests` option.
24
+* `GET /info` now returns a `FirewallBackend` containing information about
25
+  the daemon's firewalling configuration.
24 26
 
25 27
 ## v1.48 API changes
26 28
 
27 29
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package networking
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/client"
6
+	"github.com/docker/docker/internal/testutils/networking"
7
+	"github.com/docker/docker/testutil/request"
8
+	"gotest.tools/v3/assert"
9
+	is "gotest.tools/v3/assert/cmp"
10
+)
11
+
12
+func TestInfoFirewallBackend(t *testing.T) {
13
+	ctx := setupTest(t)
14
+	c := testEnv.APIClient()
15
+
16
+	expDriver := "iptables"
17
+	if !testEnv.IsRootless() && networking.FirewalldRunning() {
18
+		expDriver = "iptables+firewalld"
19
+	}
20
+	info, err := c.Info(ctx)
21
+	assert.NilError(t, err)
22
+	assert.Assert(t, info.FirewallBackend != nil, "expected firewall backend in info response")
23
+	t.Log("FirewallBackend: Driver:", info.FirewallBackend.Driver)
24
+	for _, kv := range info.FirewallBackend.Info {
25
+		t.Logf("FirewallBackend: %s: %s", kv[0], kv[1])
26
+	}
27
+	assert.Check(t, is.Equal(info.FirewallBackend.Driver, expDriver))
28
+
29
+	// Check FirewallBackend is omitted for API <= 1.48.
30
+	t.Run("api 1.48", func(t *testing.T) {
31
+		c148 := request.NewAPIClient(t, client.WithVersion("1.48"))
32
+		info148, err := c148.Info(ctx)
33
+		assert.NilError(t, err)
34
+		assert.Check(t, is.Nil(info148.FirewallBackend))
35
+	})
36
+}
... ...
@@ -6,12 +6,25 @@ import (
6 6
 	"sync"
7 7
 
8 8
 	"github.com/containerd/log"
9
+	"github.com/docker/docker/api/types/system"
9 10
 	"github.com/docker/docker/libnetwork/iptables"
10 11
 	"github.com/docker/docker/libnetwork/netlabel"
11 12
 	"github.com/docker/docker/libnetwork/options"
12 13
 	"github.com/docker/docker/libnetwork/osl"
13 14
 )
14 15
 
16
+// FirewallBackend returns the name of the firewall backend for "docker info".
17
+func (c *Controller) FirewallBackend() *system.FirewallInfo {
18
+	usingFirewalld, err := iptables.UsingFirewalld()
19
+	if err != nil {
20
+		return nil
21
+	}
22
+	if usingFirewalld {
23
+		return &system.FirewallInfo{Driver: "iptables+firewalld"}
24
+	}
25
+	return &system.FirewallInfo{Driver: "iptables"}
26
+}
27
+
15 28
 // enabledIptablesVersions returns the iptables versions that are enabled
16 29
 // for the controller.
17 30
 func (c *Controller) enabledIptablesVersions() []iptables.IPVersion {
... ...
@@ -2,6 +2,13 @@
2 2
 
3 3
 package libnetwork
4 4
 
5
+import "github.com/docker/docker/api/types/system"
6
+
7
+// FirewallBackend returns the name of the firewall backend for "docker info".
8
+func (c *Controller) FirewallBackend() *system.FirewallInfo {
9
+	return nil
10
+}
11
+
5 12
 // enabledIptablesVersions is a no-op on non-Linux systems.
6 13
 func (c *Controller) enabledIptablesVersions() []any {
7 14
 	return nil
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"strings"
9 9
 
10 10
 	"github.com/containerd/log"
11
+	"github.com/docker/docker/pkg/rootless"
11 12
 	dbus "github.com/godbus/dbus/v5"
12 13
 	"github.com/pkg/errors"
13 14
 )
... ...
@@ -40,7 +41,10 @@ var (
40 40
 // passthrough interface. The error return is non-nil if the status cannot be
41 41
 // determined because the initialisation function has not been called.
42 42
 func UsingFirewalld() (bool, error) {
43
-	if !firewalldInitCalled {
43
+	// If called before startup has completed, the firewall backend is unknown.
44
+	// But, if running rootless, the init function is not called because
45
+	// firewalld will be running in the host's netns, not in rootlesskit's.
46
+	if !firewalldInitCalled && !rootless.RunningWithRootlessKit() {
44 47
 		return false, fmt.Errorf("iptables.firewalld is not initialised")
45 48
 	}
46 49
 	return firewalldRunning, nil
... ...
@@ -982,6 +982,13 @@ func (d *Daemon) Info(t testing.TB) system.Info {
982 982
 	return info
983 983
 }
984 984
 
985
+func (d *Daemon) FirewallBackendDriver(t testing.TB) string {
986
+	t.Helper()
987
+	info := d.Info(t)
988
+	assert.Assert(t, info.FirewallBackend != nil, "no firewall backend reported")
989
+	return info.FirewallBackend.Driver
990
+}
991
+
985 992
 // TamperWithContainerConfig modifies the on-disk config of a container.
986 993
 func (d *Daemon) TamperWithContainerConfig(t testing.TB, containerID string, tamper func(*container.Container)) {
987 994
 	t.Helper()