Signed-off-by: Rob Murray <rob.murray@docker.com>
| ... | ... |
@@ -51,6 +51,7 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
|
| 51 | 51 |
flags.BoolVar(&conf.NoNewPrivileges, "no-new-privileges", false, "Set no-new-privileges by default for new containers") |
| 52 | 52 |
flags.StringVar(&conf.IpcMode, "default-ipc-mode", conf.IpcMode, `Default mode for containers ipc ("shareable" | "private")`)
|
| 53 | 53 |
flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "Default address pools for node specific local networks") |
| 54 |
+ flags.StringVar(&conf.NetworkConfig.FirewallBackend, "firewall-backend", "", "Firewall backend to use, iptables or nftables") |
|
| 54 | 55 |
// rootless needs to be explicitly specified for running "rootful" dockerd in rootless dockerd (#38702) |
| 55 | 56 |
// Note that conf.BridgeConfig.UserlandProxyPath and honorXDG are configured according to the value of rootless.RunningWithRootlessKit, not the value of --rootless. |
| 56 | 57 |
flags.BoolVar(&conf.Rootless, "rootless", conf.Rootless, "Enable rootless mode; typically used with RootlessKit") |
| ... | ... |
@@ -148,6 +148,10 @@ type NetworkConfig struct {
|
| 148 | 148 |
NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"` |
| 149 | 149 |
// Default options for newly created networks |
| 150 | 150 |
DefaultNetworkOpts map[string]map[string]string `json:"default-network-opts,omitempty"` |
| 151 |
+ // FirewallBackend overrides the daemon's default selection of firewall |
|
| 152 |
+ // implementation. Currently only used on Linux, it is an error to |
|
| 153 |
+ // supply a value for other platforms. |
|
| 154 |
+ FirewallBackend string `json:"firewall-backend,omitempty"` |
|
| 151 | 155 |
} |
| 152 | 156 |
|
| 153 | 157 |
// TLSOptions defines TLS configuration for the daemon server. |
| ... | ... |
@@ -243,6 +243,10 @@ func validatePlatformConfig(conf *Config) error {
|
| 243 | 243 |
return errors.Wrap(err, "invalid fixed-cidr-v6") |
| 244 | 244 |
} |
| 245 | 245 |
|
| 246 |
+ if err := validateFirewallBackend(conf.FirewallBackend); err != nil {
|
|
| 247 |
+ return errors.Wrap(err, "invalid firewall-backend") |
|
| 248 |
+ } |
|
| 249 |
+ |
|
| 246 | 250 |
return verifyDefaultCgroupNsMode(conf.CgroupNamespaceMode) |
| 247 | 251 |
} |
| 248 | 252 |
|
| ... | ... |
@@ -294,6 +298,14 @@ func verifyDefaultIpcMode(mode string) error {
|
| 294 | 294 |
return nil |
| 295 | 295 |
} |
| 296 | 296 |
|
| 297 |
+func validateFirewallBackend(val string) error {
|
|
| 298 |
+ switch val {
|
|
| 299 |
+ case "", "iptables", "nftables": |
|
| 300 |
+ return nil |
|
| 301 |
+ } |
|
| 302 |
+ return errors.New(`allowed values are "iptables" and "nftables"`) |
|
| 303 |
+} |
|
| 304 |
+ |
|
| 297 | 305 |
func verifyDefaultCgroupNsMode(mode string) error {
|
| 298 | 306 |
cm := container.CgroupnsMode(mode) |
| 299 | 307 |
if !cm.Valid() {
|
| ... | ... |
@@ -2,6 +2,7 @@ package config |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 |
+ "errors" |
|
| 5 | 6 |
"fmt" |
| 6 | 7 |
"os" |
| 7 | 8 |
"path/filepath" |
| ... | ... |
@@ -88,6 +89,9 @@ func validatePlatformConfig(conf *Config) error {
|
| 88 | 88 |
if conf.MTU != 0 && conf.MTU != DefaultNetworkMtu {
|
| 89 | 89 |
log.G(context.TODO()).Warn(`WARNING: MTU for the default network is not configurable on Windows, and this option will be ignored.`) |
| 90 | 90 |
} |
| 91 |
+ if conf.FirewallBackend != "" {
|
|
| 92 |
+ return errors.New("firewall-backend can only be configured on Linux")
|
|
| 93 |
+ } |
|
| 91 | 94 |
return nil |
| 92 | 95 |
} |
| 93 | 96 |
|
| ... | ... |
@@ -1457,6 +1457,7 @@ func (daemon *Daemon) networkOptions(conf *config.Config, pg plugingetter.Plugin |
| 1457 | 1457 |
nwconfig.OptionDefaultNetwork(network.DefaultNetwork), |
| 1458 | 1458 |
nwconfig.OptionLabels(conf.Labels), |
| 1459 | 1459 |
nwconfig.OptionNetworkControlPlaneMTU(conf.NetworkControlPlaneMTU), |
| 1460 |
+ nwconfig.OptionFirewallBackend(conf.FirewallBackend), |
|
| 1460 | 1461 |
driverOptions(conf), |
| 1461 | 1462 |
} |
| 1462 | 1463 |
|
| ... | ... |
@@ -42,6 +42,7 @@ type Config struct {
|
| 42 | 42 |
DatastoreBucket string |
| 43 | 43 |
ActiveSandboxes map[string]any |
| 44 | 44 |
PluginGetter plugingetter.PluginGetter |
| 45 |
+ FirewallBackend string |
|
| 45 | 46 |
} |
| 46 | 47 |
|
| 47 | 48 |
// New creates a new Config and initializes it with the given Options. |
| ... | ... |
@@ -154,3 +155,10 @@ func OptionActiveSandboxes(sandboxes map[string]any) Option {
|
| 154 | 154 |
c.ActiveSandboxes = sandboxes |
| 155 | 155 |
} |
| 156 | 156 |
} |
| 157 |
+ |
|
| 158 |
+// OptionFirewallBackend returns an option setter for selection of the firewall backend. |
|
| 159 |
+func OptionFirewallBackend(val string) Option {
|
|
| 160 |
+ return func(c *Config) {
|
|
| 161 |
+ c.FirewallBackend = val |
|
| 162 |
+ } |
|
| 163 |
+} |
| ... | ... |
@@ -168,7 +168,9 @@ func New(ctx context.Context, cfgOptions ...config.Option) (_ *Controller, retEr |
| 168 | 168 |
diagnosticServer: diagnostic.New(), |
| 169 | 169 |
} |
| 170 | 170 |
|
| 171 |
- c.selectFirewallBackend() |
|
| 171 |
+ if err := c.selectFirewallBackend(); err != nil {
|
|
| 172 |
+ return nil, err |
|
| 173 |
+ } |
|
| 172 | 174 |
c.drvRegistry.Notify = c |
| 173 | 175 |
|
| 174 | 176 |
// External plugins don't need config passed through daemon. They can |
| ... | ... |
@@ -4,7 +4,6 @@ import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"errors" |
| 6 | 6 |
"fmt" |
| 7 |
- "os" |
|
| 8 | 7 |
|
| 9 | 8 |
"github.com/containerd/log" |
| 10 | 9 |
"github.com/docker/docker/daemon/libnetwork/internal/nftables" |
| ... | ... |
@@ -13,16 +12,23 @@ import ( |
| 13 | 13 |
|
| 14 | 14 |
const userChain = "DOCKER-USER" |
| 15 | 15 |
|
| 16 |
-func (c *Controller) selectFirewallBackend() {
|
|
| 17 |
- // Don't try to enable nftables if firewalld is running. |
|
| 18 |
- if iptables.UsingFirewalld() {
|
|
| 19 |
- return |
|
| 16 |
+func (c *Controller) selectFirewallBackend() error {
|
|
| 17 |
+ // If explicitly configured to use iptables, don't consider nftables. |
|
| 18 |
+ if c.cfg.FirewallBackend == "iptables" {
|
|
| 19 |
+ return nil |
|
| 20 | 20 |
} |
| 21 |
- // Only try to use nftables if explicitly enabled by env-var. |
|
| 22 |
- // TODO(robmry) - command line options? |
|
| 23 |
- if os.Getenv("DOCKER_FIREWALL_BACKEND") == "nftables" {
|
|
| 24 |
- _ = nftables.Enable() |
|
| 21 |
+ // If configured to use nftables, but it can't be initialised, return an error. |
|
| 22 |
+ if c.cfg.FirewallBackend == "nftables" {
|
|
| 23 |
+ // Don't try to enable nftables if firewalld is running. |
|
| 24 |
+ if iptables.UsingFirewalld() {
|
|
| 25 |
+ return errors.New("firewall-backend is set to nftables, but firewalld is running")
|
|
| 26 |
+ } |
|
| 27 |
+ if err := nftables.Enable(); err != nil {
|
|
| 28 |
+ return fmt.Errorf("firewall-backend is set to nftables: %v", err)
|
|
| 29 |
+ } |
|
| 30 |
+ return nil |
|
| 25 | 31 |
} |
| 32 |
+ return nil |
|
| 26 | 33 |
} |
| 27 | 34 |
|
| 28 | 35 |
// Sets up the DOCKER-USER chain for each iptables version (IPv4, IPv6) that's |
| ... | ... |
@@ -62,6 +62,8 @@ var ( |
| 62 | 62 |
// nftPath is the path of the "nft" tool, set by [Enable] and left empty if the tool |
| 63 | 63 |
// is not present - in which case, nftables is disabled. |
| 64 | 64 |
nftPath string |
| 65 |
+ // Error returned by Enable if nftables could not be initialised. |
|
| 66 |
+ nftEnableError error |
|
| 65 | 67 |
// incrementalUpdateTempl is a parsed text/template, used to apply incremental updates. |
| 66 | 68 |
incrementalUpdateTempl *template.Template |
| 67 | 69 |
// reloadTempl is a parsed text/template, used to apply a whole table. |
| ... | ... |
@@ -134,21 +136,23 @@ const ( |
| 134 | 134 |
nftTypeIfname nftType = "ifname" |
| 135 | 135 |
) |
| 136 | 136 |
|
| 137 |
-// Enable checks whether the "nft" tool is available, and returns true if it is. |
|
| 138 |
-// Subsequent calls to [Enabled] will return the same result. |
|
| 139 |
-func Enable() bool {
|
|
| 137 |
+// Enable tries once to initialise nftables. |
|
| 138 |
+func Enable() error {
|
|
| 140 | 139 |
enableOnce.Do(func() {
|
| 141 | 140 |
path, err := exec.LookPath("nft")
|
| 142 | 141 |
if err != nil {
|
| 143 | 142 |
log.G(context.Background()).WithError(err).Warnf("Failed to find nft tool")
|
| 143 |
+ nftEnableError = fmt.Errorf("failed to find nft tool: %w", err)
|
|
| 144 |
+ return |
|
| 144 | 145 |
} |
| 145 | 146 |
if err := parseTemplate(); err != nil {
|
| 146 | 147 |
log.G(context.Background()).WithError(err).Error("Internal error while initialising nftables")
|
| 148 |
+ nftEnableError = fmt.Errorf("internal error while initialising nftables: %w", err)
|
|
| 147 | 149 |
return |
| 148 | 150 |
} |
| 149 | 151 |
nftPath = path |
| 150 | 152 |
}) |
| 151 |
- return nftPath != "" |
|
| 153 |
+ return nftEnableError |
|
| 152 | 154 |
} |
| 153 | 155 |
|
| 154 | 156 |
// Enabled returns true if the "nft" tool is available and [Enable] has been called. |
| ... | ... |
@@ -14,7 +14,7 @@ import ( |
| 14 | 14 |
|
| 15 | 15 |
func testSetup(t *testing.T) func() {
|
| 16 | 16 |
t.Helper() |
| 17 |
- if !Enable() {
|
|
| 17 |
+ if err := Enable(); err != nil {
|
|
| 18 | 18 |
// Make sure it didn't fail because of a bug in the text/template. |
| 19 | 19 |
assert.NilError(t, parseTemplate()) |
| 20 | 20 |
// If this is not CI, skip. |
| ... | ... |
@@ -22,7 +22,7 @@ func testSetup(t *testing.T) func() {
|
| 22 | 22 |
t.Skip("Cannot enable nftables, no 'nft' command in $PATH ?")
|
| 23 | 23 |
} |
| 24 | 24 |
// In CI, nft should always be installed, fail the test. |
| 25 |
- t.Fatal("Failed to enable nftables")
|
|
| 25 |
+ t.Fatalf("Failed to enable nftables: %s", err)
|
|
| 26 | 26 |
} |
| 27 | 27 |
cleanupContext := netnsutils.SetupTestOSContext(t) |
| 28 | 28 |
return func() {
|
| ... | ... |
@@ -118,6 +118,7 @@ if [ -z "$DOCKER_TEST_HOST" ]; then |
| 118 | 118 |
--storage-driver "$DOCKER_GRAPHDRIVER" \ |
| 119 | 119 |
--pidfile "$DEST/docker.pid" \ |
| 120 | 120 |
--userland-proxy="$DOCKER_USERLANDPROXY" \ |
| 121 |
+ --firewall-backend="$DOCKER_FIREWALL_BACKEND" \ |
|
| 121 | 122 |
${storage_params} \
|
| 122 | 123 |
${extra_params} \
|
| 123 | 124 |
&> "$DEST/docker.log" |
| ... | ... |
@@ -47,6 +47,7 @@ test_env() {
|
| 47 | 47 |
DOCKER_CERT_PATH="$DOCKER_TEST_CERT_PATH" \ |
| 48 | 48 |
DOCKER_GRAPHDRIVER="$DOCKER_GRAPHDRIVER" \ |
| 49 | 49 |
DOCKER_USERLANDPROXY="$DOCKER_USERLANDPROXY" \ |
| 50 |
+ DOCKER_FIREWALL_BACKEND="$DOCKER_FIREWALL_BACKEND" \ |
|
| 50 | 51 |
DOCKER_HOST="$DOCKER_HOST" \ |
| 51 | 52 |
DOCKER_REMAP_ROOT="$DOCKER_REMAP_ROOT" \ |
| 52 | 53 |
DOCKER_REMOTE_DAEMON="$DOCKER_REMOTE_DAEMON" \ |
| ... | ... |
@@ -856,9 +856,8 @@ func TestFirewallBackendSwitch(t *testing.T) {
|
| 856 | 856 |
|
| 857 | 857 |
networkCreated := false |
| 858 | 858 |
runDaemon := func(backend string) {
|
| 859 |
- d.SetEnvVar("DOCKER_FIREWALL_BACKEND", backend)
|
|
| 860 | 859 |
host.Do(t, func() {
|
| 861 |
- d.StartWithBusybox(ctx, t) |
|
| 860 |
+ d.StartWithBusybox(ctx, t, "--firewall-backend="+backend) |
|
| 862 | 861 |
defer d.Stop(t) |
| 863 | 862 |
|
| 864 | 863 |
// Create a network (and its firewall rules) first time through. |
| ... | ... |
@@ -234,10 +234,6 @@ func New(t testing.TB, ops ...Option) *Daemon {
|
| 234 | 234 |
} |
| 235 | 235 |
ops = append(ops, WithOOMScoreAdjust(-500)) |
| 236 | 236 |
|
| 237 |
- if val, ok := os.LookupEnv("DOCKER_FIREWALL_BACKEND"); ok {
|
|
| 238 |
- ops = append(ops, WithEnvVars("DOCKER_FIREWALL_BACKEND="+val))
|
|
| 239 |
- } |
|
| 240 |
- |
|
| 241 | 237 |
d, err := NewDaemon(dest, ops...) |
| 242 | 238 |
assert.NilError(t, err, "could not create daemon at %q", dest) |
| 243 | 239 |
if d.rootlessUser != nil && d.dockerdBinary != defaultDockerdBinary {
|
| ... | ... |
@@ -534,6 +530,15 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
|
| 534 | 534 |
d.args = append(d.args, "--storage-driver", d.storageDriver) |
| 535 | 535 |
} |
| 536 | 536 |
|
| 537 |
+ hasFwBackendArg := !slices.ContainsFunc(providedArgs, func(s string) bool {
|
|
| 538 |
+ return strings.HasPrefix(s, "--firewall-backend") |
|
| 539 |
+ }) |
|
| 540 |
+ if hasFwBackendArg {
|
|
| 541 |
+ if fw := os.Getenv("DOCKER_FIREWALL_BACKEND"); fw != "" {
|
|
| 542 |
+ d.args = append(d.args, "--firewall-backend="+fw) |
|
| 543 |
+ } |
|
| 544 |
+ } |
|
| 545 |
+ |
|
| 537 | 546 |
d.args = append(d.args, providedArgs...) |
| 538 | 547 |
cmd := exec.Command(dockerdBinary, d.args...) |
| 539 | 548 |
cmd.Env = append(os.Environ(), "DOCKER_SERVICE_PREFER_OFFLINE_IMAGE=1") |