diff --git a/api/server/router/system/system_routes.go b/api/server/router/system/system_routes.go
index 8213780aac..9324a8e643 100644
--- a/api/server/router/system/system_routes.go
+++ b/api/server/router/system/system_routes.go
@@ -106,6 +106,10 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht
info.RegistryConfig.AllowNondistributableArtifactsCIDRs = []*registry.NetIPNet{}
info.RegistryConfig.AllowNondistributableArtifactsHostnames = []string{}
}
+ if versions.LessThan(version, "1.49") {
+ // FirewallBackend field introduced in API v1.49.
+ info.FirewallBackend = nil
+ }
// TODO(thaJeztah): Expected commits are deprecated, and should no longer be set in API 1.49.
info.ContainerdCommit.Expected = info.ContainerdCommit.ID //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.49.
diff --git a/api/swagger.yaml b/api/swagger.yaml
index b5f9eaf210..00e2ab5885 100644
--- a/api/swagger.yaml
+++ b/api/swagger.yaml
@@ -6856,6 +6856,8 @@ definitions:
description: "The network pool size"
type: "integer"
example: "24"
+ FirewallBackend:
+ $ref: "#/definitions/FirewallInfo"
Warnings:
description: |
List of warnings / informational messages about missing features, or
@@ -6939,6 +6941,37 @@ definitions:
default: "plugins.moby"
example: "plugins.moby"
+ FirewallInfo:
+ description: |
+ Information about the daemon's firewalling configuration.
+
+ This field is currently only used on Linux, and omitted on other platforms.
+ type: "object"
+ x-nullable: true
+ properties:
+ Driver:
+ description: |
+ The name of the firewall backend driver.
+ type: "string"
+ example: "nftables"
+ Info:
+ description: |
+ Information about the firewall backend, provided as
+ "label" / "value" pairs.
+
+
+
+ > **Note**: The information returned in this field, including the
+ > formatting of values and labels, should not be considered stable,
+ > and may change without notice.
+ type: "array"
+ items:
+ type: "array"
+ items:
+ type: "string"
+ example:
+ - ["ReloadedAt", "2025-01-01T00:00:00Z"]
+
# PluginsInfo is a temp struct holding Plugins name
# registered with docker daemon. It is used by Info struct
PluginsInfo:
diff --git a/api/types/system/info.go b/api/types/system/info.go
index 8a2444da28..7320582950 100644
--- a/api/types/system/info.go
+++ b/api/types/system/info.go
@@ -73,6 +73,7 @@ type Info struct {
SecurityOptions []string
ProductLicense string `json:",omitempty"`
DefaultAddressPools []NetworkAddressPool `json:",omitempty"`
+ FirewallBackend *FirewallInfo `json:"FirewallBackend,omitempty"`
CDISpecDirs []string
Containerd *ContainerdInfo `json:",omitempty"`
@@ -151,3 +152,11 @@ type NetworkAddressPool struct {
Base string
Size int
}
+
+// FirewallInfo describes the firewall backend.
+type FirewallInfo struct {
+ // Driver is the name of the firewall backend driver.
+ Driver string `json:"Driver"`
+ // Info is a list of label/value pairs, containing information related to the firewall.
+ Info [][2]string `json:"Info,omitempty"`
+}
diff --git a/daemon/info.go b/daemon/info.go
index ebc3fa280c..3c38c49afb 100644
--- a/daemon/info.go
+++ b/daemon/info.go
@@ -93,6 +93,7 @@ func (daemon *Daemon) SystemInfo(ctx context.Context) (*system.Info, error) {
daemon.fillSecurityOptions(v, sysInfo, &cfg.Config)
daemon.fillLicense(v)
daemon.fillDefaultAddressPools(ctx, v, &cfg.Config)
+ daemon.fillFirewallInfo(v)
return v, nil
}
@@ -284,6 +285,13 @@ func (daemon *Daemon) fillDefaultAddressPools(ctx context.Context, v *system.Inf
}
}
+func (daemon *Daemon) fillFirewallInfo(v *system.Info) {
+ if daemon.netController == nil {
+ return
+ }
+ v.FirewallBackend = daemon.netController.FirewallBackend()
+}
+
func hostName(ctx context.Context) string {
ctx, span := tracing.StartSpan(ctx, "hostName")
defer span.End()
diff --git a/docs/api/version-history.md b/docs/api/version-history.md
index a78bba88ee..45cc07e10a 100644
--- a/docs/api/version-history.md
+++ b/docs/api/version-history.md
@@ -21,6 +21,8 @@ keywords: "API, Docker, rcli, REST, documentation"
encoded OCI Platform type) allowing to specify a platform of the multi-platform
image to inspect.
This option is mutually exclusive with the `manifests` option.
+* `GET /info` now returns a `FirewallBackend` containing information about
+ the daemon's firewalling configuration.
## v1.48 API changes
diff --git a/integration/networking/firewall_linux_test.go b/integration/networking/firewall_linux_test.go
new file mode 100644
index 0000000000..9820a5ffbc
--- /dev/null
+++ b/integration/networking/firewall_linux_test.go
@@ -0,0 +1,37 @@
+package networking
+
+import (
+ "testing"
+
+ "github.com/docker/docker/client"
+ "github.com/docker/docker/internal/testutils/networking"
+ "github.com/docker/docker/testutil/request"
+ "gotest.tools/v3/assert"
+ is "gotest.tools/v3/assert/cmp"
+)
+
+func TestInfoFirewallBackend(t *testing.T) {
+ ctx := setupTest(t)
+ c := testEnv.APIClient()
+
+ expDriver := "iptables"
+ if !testEnv.IsRootless() && networking.FirewalldRunning() {
+ expDriver = "iptables+firewalld"
+ }
+ info, err := c.Info(ctx)
+ assert.NilError(t, err)
+ assert.Assert(t, info.FirewallBackend != nil, "expected firewall backend in info response")
+ t.Log("FirewallBackend: Driver:", info.FirewallBackend.Driver)
+ for _, kv := range info.FirewallBackend.Info {
+ t.Logf("FirewallBackend: %s: %s", kv[0], kv[1])
+ }
+ assert.Check(t, is.Equal(info.FirewallBackend.Driver, expDriver))
+
+ // Check FirewallBackend is omitted for API <= 1.48.
+ t.Run("api 1.48", func(t *testing.T) {
+ c148 := request.NewAPIClient(t, client.WithVersion("1.48"))
+ info148, err := c148.Info(ctx)
+ assert.NilError(t, err)
+ assert.Check(t, is.Nil(info148.FirewallBackend))
+ })
+}
diff --git a/libnetwork/controller_linux.go b/libnetwork/controller_linux.go
index 502c9c22d1..45c5dcd9c3 100644
--- a/libnetwork/controller_linux.go
+++ b/libnetwork/controller_linux.go
@@ -6,12 +6,25 @@ import (
"sync"
"github.com/containerd/log"
+ "github.com/docker/docker/api/types/system"
"github.com/docker/docker/libnetwork/iptables"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/libnetwork/osl"
)
+// FirewallBackend returns the name of the firewall backend for "docker info".
+func (c *Controller) FirewallBackend() *system.FirewallInfo {
+ usingFirewalld, err := iptables.UsingFirewalld()
+ if err != nil {
+ return nil
+ }
+ if usingFirewalld {
+ return &system.FirewallInfo{Driver: "iptables+firewalld"}
+ }
+ return &system.FirewallInfo{Driver: "iptables"}
+}
+
// enabledIptablesVersions returns the iptables versions that are enabled
// for the controller.
func (c *Controller) enabledIptablesVersions() []iptables.IPVersion {
diff --git a/libnetwork/controller_others.go b/libnetwork/controller_others.go
index d837c7cf3d..c8c8cc9ed2 100644
--- a/libnetwork/controller_others.go
+++ b/libnetwork/controller_others.go
@@ -2,6 +2,13 @@
package libnetwork
+import "github.com/docker/docker/api/types/system"
+
+// FirewallBackend returns the name of the firewall backend for "docker info".
+func (c *Controller) FirewallBackend() *system.FirewallInfo {
+ return nil
+}
+
// enabledIptablesVersions is a no-op on non-Linux systems.
func (c *Controller) enabledIptablesVersions() []any {
return nil
diff --git a/libnetwork/iptables/firewalld.go b/libnetwork/iptables/firewalld.go
index 54ee975ddd..aa11a34897 100644
--- a/libnetwork/iptables/firewalld.go
+++ b/libnetwork/iptables/firewalld.go
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/containerd/log"
+ "github.com/docker/docker/pkg/rootless"
dbus "github.com/godbus/dbus/v5"
"github.com/pkg/errors"
)
@@ -40,7 +41,10 @@ var (
// passthrough interface. The error return is non-nil if the status cannot be
// determined because the initialisation function has not been called.
func UsingFirewalld() (bool, error) {
- if !firewalldInitCalled {
+ // If called before startup has completed, the firewall backend is unknown.
+ // But, if running rootless, the init function is not called because
+ // firewalld will be running in the host's netns, not in rootlesskit's.
+ if !firewalldInitCalled && !rootless.RunningWithRootlessKit() {
return false, fmt.Errorf("iptables.firewalld is not initialised")
}
return firewalldRunning, nil
diff --git a/testutil/daemon/daemon.go b/testutil/daemon/daemon.go
index d24e4b329f..d83f62556c 100644
--- a/testutil/daemon/daemon.go
+++ b/testutil/daemon/daemon.go
@@ -982,6 +982,13 @@ func (d *Daemon) Info(t testing.TB) system.Info {
return info
}
+func (d *Daemon) FirewallBackendDriver(t testing.TB) string {
+ t.Helper()
+ info := d.Info(t)
+ assert.Assert(t, info.FirewallBackend != nil, "no firewall backend reported")
+ return info.FirewallBackend.Driver
+}
+
// TamperWithContainerConfig modifies the on-disk config of a container.
func (d *Daemon) TamperWithContainerConfig(t testing.TB, containerID string, tamper func(*container.Container)) {
t.Helper()