mirror of
https://github.com/moby/moby.git
synced 2026-08-07 16:41:50 +00:00
Merge pull request #48594 from robmry/48365_iptables_forward_policy
Only set ip6tables filter-FORWARD DROP if necessary
This commit is contained in:
@@ -27,7 +27,8 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
|
||||
flags.Var(opts.NewNamedUlimitOpt("default-ulimits", &conf.Ulimits), "default-ulimit", "Default ulimits for containers")
|
||||
flags.BoolVar(&conf.BridgeConfig.EnableIPTables, "iptables", true, "Enable addition of iptables rules")
|
||||
flags.BoolVar(&conf.BridgeConfig.EnableIP6Tables, "ip6tables", true, "Enable addition of ip6tables rules")
|
||||
flags.BoolVar(&conf.BridgeConfig.EnableIPForward, "ip-forward", true, "Enable net.ipv4.ip_forward")
|
||||
flags.BoolVar(&conf.BridgeConfig.EnableIPForward, "ip-forward", true, "Enable IP forwarding in system configuration")
|
||||
flags.BoolVar(&conf.BridgeConfig.DisableFilterForwardDrop, "ip-forward-no-drop", false, "Do not set the filter-FORWARD policy to DROP when enabling IP forwarding")
|
||||
flags.BoolVar(&conf.BridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading")
|
||||
flags.BoolVar(&conf.BridgeConfig.EnableIPv6, "ipv6", false, "Enable IPv6 networking")
|
||||
flags.StringVar(&conf.BridgeConfig.IP, "bip", "", "Specify network bridge IP")
|
||||
|
||||
@@ -41,12 +41,13 @@ const (
|
||||
type BridgeConfig struct {
|
||||
DefaultBridgeConfig
|
||||
|
||||
EnableIPTables bool `json:"iptables,omitempty"`
|
||||
EnableIP6Tables bool `json:"ip6tables,omitempty"`
|
||||
EnableIPForward bool `json:"ip-forward,omitempty"`
|
||||
EnableIPMasq bool `json:"ip-masq,omitempty"`
|
||||
EnableUserlandProxy bool `json:"userland-proxy,omitempty"`
|
||||
UserlandProxyPath string `json:"userland-proxy-path,omitempty"`
|
||||
EnableIPTables bool `json:"iptables,omitempty"`
|
||||
EnableIP6Tables bool `json:"ip6tables,omitempty"`
|
||||
EnableIPForward bool `json:"ip-forward,omitempty"`
|
||||
DisableFilterForwardDrop bool `json:"ip-forward-no-drop,omitempty"`
|
||||
EnableIPMasq bool `json:"ip-masq,omitempty"`
|
||||
EnableUserlandProxy bool `json:"userland-proxy,omitempty"`
|
||||
UserlandProxyPath string `json:"userland-proxy-path,omitempty"`
|
||||
}
|
||||
|
||||
// DefaultBridgeConfig stores all the parameters for the default bridge network.
|
||||
|
||||
@@ -915,12 +915,13 @@ func setHostGatewayIP(controller *libnetwork.Controller, config *config.Config)
|
||||
func driverOptions(config *config.Config) nwconfig.Option {
|
||||
return nwconfig.OptionDriverConfig("bridge", options.Generic{
|
||||
netlabel.GenericData: options.Generic{
|
||||
"EnableIPForwarding": config.BridgeConfig.EnableIPForward,
|
||||
"EnableIPTables": config.BridgeConfig.EnableIPTables,
|
||||
"EnableIP6Tables": config.BridgeConfig.EnableIP6Tables,
|
||||
"EnableUserlandProxy": config.BridgeConfig.EnableUserlandProxy,
|
||||
"UserlandProxyPath": config.BridgeConfig.UserlandProxyPath,
|
||||
"Rootless": config.Rootless,
|
||||
"EnableIPForwarding": config.BridgeConfig.EnableIPForward,
|
||||
"DisableFilterForwardDrop": config.BridgeConfig.DisableFilterForwardDrop,
|
||||
"EnableIPTables": config.BridgeConfig.EnableIPTables,
|
||||
"EnableIP6Tables": config.BridgeConfig.EnableIP6Tables,
|
||||
"EnableUserlandProxy": config.BridgeConfig.EnableUserlandProxy,
|
||||
"UserlandProxyPath": config.BridgeConfig.UserlandProxyPath,
|
||||
"Rootless": config.Rootless,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package network
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -12,14 +13,15 @@ import (
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
ctr "github.com/docker/docker/integration/internal/container"
|
||||
"github.com/docker/docker/integration/internal/network"
|
||||
"github.com/docker/docker/internal/testutils/networking"
|
||||
"github.com/docker/docker/testutil"
|
||||
"github.com/docker/docker/testutil/daemon"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
func TestCreateWithMultiNetworks(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
||||
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.44"), "requires API v1.44")
|
||||
|
||||
ctx := setupTest(t)
|
||||
@@ -49,9 +51,6 @@ func TestCreateWithMultiNetworks(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateWithIPv6DefaultsToULAPrefix(t *testing.T) {
|
||||
// On Windows, network creation fails with this error message: Error response from daemon: this request is not supported by the 'windows' ipam driver
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
|
||||
|
||||
ctx := setupTest(t)
|
||||
apiClient := testEnv.APIClient()
|
||||
|
||||
@@ -73,7 +72,6 @@ func TestCreateWithIPv6DefaultsToULAPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateWithIPv6WithoutEnableIPv6Flag(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows") // d.Start fails on Windows with `protocol not available`
|
||||
ctx := setupTest(t)
|
||||
|
||||
d := daemon.New(t)
|
||||
@@ -103,7 +101,6 @@ func TestCreateWithIPv6WithoutEnableIPv6Flag(t *testing.T) {
|
||||
// Check that it's possible to create IPv6 networks with a 64-bit ip-range,
|
||||
// in 64-bit and bigger subnets, with and without a gateway.
|
||||
func Test64BitIPRange(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no bridge or IPv6 on Windows")
|
||||
ctx := setupTest(t)
|
||||
c := testEnv.APIClient()
|
||||
|
||||
@@ -139,7 +136,6 @@ func Test64BitIPRange(t *testing.T) {
|
||||
// Demonstrate a limitation of the IP address allocator, it can't
|
||||
// allocate the last address in range that ends on a 64-bit boundary.
|
||||
func TestIPRangeAt64BitLimit(t *testing.T) {
|
||||
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "no bridge or IPv6 on Windows")
|
||||
ctx := setupTest(t)
|
||||
c := testEnv.APIClient()
|
||||
|
||||
@@ -200,3 +196,123 @@ func TestIPRangeAt64BitLimit(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFilterForwardPolicy tests that, if the daemon enables IP forwarding on the
|
||||
// host, it also sets the iptables filter-FORWARD policy to DROP (unless it's
|
||||
// told not to).
|
||||
func TestFilterForwardPolicy(t *testing.T) {
|
||||
skip.If(t, testEnv.IsRootless, "rootless has its own netns")
|
||||
ctx := setupTest(t)
|
||||
|
||||
// Set up a netns for each test to avoid sysctl and iptables pollution.
|
||||
addr4 := netip.MustParseAddr("192.168.125.1")
|
||||
addr6 := netip.MustParseAddr("fd76:c828:41f9::1")
|
||||
l3 := networking.NewL3Segment(t, "test-ffp",
|
||||
netip.PrefixFrom(addr4, 24),
|
||||
netip.PrefixFrom(addr6, 64),
|
||||
)
|
||||
t.Cleanup(func() { l3.Destroy(t) })
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
initForwarding string
|
||||
daemonArgs []string
|
||||
expForwarding string
|
||||
expPolicy string
|
||||
}{
|
||||
{
|
||||
name: "enable forwarding",
|
||||
initForwarding: "0",
|
||||
expForwarding: "1",
|
||||
expPolicy: "DROP",
|
||||
},
|
||||
{
|
||||
name: "forwarding already enabled",
|
||||
initForwarding: "1",
|
||||
expForwarding: "1",
|
||||
expPolicy: "ACCEPT",
|
||||
},
|
||||
{
|
||||
name: "no drop",
|
||||
initForwarding: "0",
|
||||
daemonArgs: []string{"--ip-forward-no-drop"},
|
||||
expForwarding: "1",
|
||||
expPolicy: "ACCEPT",
|
||||
},
|
||||
{
|
||||
name: "no forwarding",
|
||||
initForwarding: "0",
|
||||
daemonArgs: []string{"--ip-forward=false"},
|
||||
expForwarding: "0",
|
||||
expPolicy: "ACCEPT",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := testutil.StartSpan(ctx, t)
|
||||
|
||||
// Create a netns for this test.
|
||||
addr4, addr6 = addr4.Next(), addr6.Next()
|
||||
hostname := fmt.Sprintf("docker%d", i)
|
||||
l3.AddHost(t, hostname, hostname+"-host", "eth0",
|
||||
netip.PrefixFrom(addr4, 24),
|
||||
netip.PrefixFrom(addr6, 64),
|
||||
)
|
||||
host := l3.Hosts[hostname]
|
||||
|
||||
getFwdPolicy := func(cmd string) string {
|
||||
t.Helper()
|
||||
out := host.Run(t, cmd, "-S", "FORWARD")
|
||||
if strings.HasPrefix(out, "-P FORWARD ACCEPT") {
|
||||
return "ACCEPT"
|
||||
}
|
||||
if strings.HasPrefix(out, "-P FORWARD DROP") {
|
||||
return "DROP"
|
||||
}
|
||||
t.Fatalf("Failed to determine %s FORWARD policy: %s", cmd, out)
|
||||
return ""
|
||||
}
|
||||
|
||||
type sysctls struct{ v4, v6def, v6all string }
|
||||
getSysctls := func() sysctls {
|
||||
t.Helper()
|
||||
return sysctls{
|
||||
host.Run(t, "sysctl", "-n", "net.ipv4.ip_forward")[:1],
|
||||
host.Run(t, "sysctl", "-n", "net.ipv6.conf.default.forwarding")[:1],
|
||||
host.Run(t, "sysctl", "-n", "net.ipv6.conf.all.forwarding")[:1],
|
||||
}
|
||||
}
|
||||
|
||||
// Initial settings for IP forwarding params.
|
||||
host.Run(t, "sysctl", "-w", "net.ipv4.ip_forward="+tc.initForwarding)
|
||||
host.Run(t, "sysctl", "-w", "net.ipv6.conf.all.forwarding="+tc.initForwarding)
|
||||
|
||||
// Start the daemon in its own network namespace.
|
||||
var d *daemon.Daemon
|
||||
host.Do(t, func() {
|
||||
// Run without OTel because there's no routing from this netns for it - which
|
||||
// means the daemon doesn't shut down cleanly, causing the test to fail.
|
||||
d = daemon.New(t, daemon.WithEnvVars("OTEL_EXPORTER_OTLP_ENDPOINT="))
|
||||
d.StartWithBusybox(ctx, t, tc.daemonArgs...)
|
||||
t.Cleanup(func() { d.Stop(t) })
|
||||
})
|
||||
c := d.NewClientT(t)
|
||||
t.Cleanup(func() { c.Close() })
|
||||
|
||||
// If necessary, the IPv4 policy should have been updated when the default bridge network was created.
|
||||
assert.Check(t, is.Equal(getFwdPolicy("iptables"), tc.expPolicy))
|
||||
// IPv6 policy should not have been updated yet.
|
||||
assert.Check(t, is.Equal(getFwdPolicy("ip6tables"), "ACCEPT"))
|
||||
assert.Check(t, is.Equal(getSysctls(), sysctls{tc.expForwarding, tc.initForwarding, tc.initForwarding}))
|
||||
|
||||
// If necessary, creating an IPv6 network should update the sysctls and policy.
|
||||
const netName = "testnetffp"
|
||||
network.CreateNoError(ctx, t, c, netName, network.WithIPv6())
|
||||
t.Cleanup(func() { network.RemoveNoError(ctx, t, c, netName) })
|
||||
assert.Check(t, is.Equal(getFwdPolicy("iptables"), tc.expPolicy))
|
||||
assert.Check(t, is.Equal(getFwdPolicy("ip6tables"), tc.expPolicy))
|
||||
assert.Check(t, is.Equal(getSysctls(), sysctls{tc.expForwarding, tc.expForwarding, tc.expForwarding}))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,8 +68,10 @@ Table `filter`:
|
||||
The FORWARD chain's policy shown above is ACCEPT. However:
|
||||
|
||||
- For IPv4, [setupIPForwarding][1] sets the POLICY to DROP if the sysctl
|
||||
net.ipv4.ip_forward was not set to '1', and the daemon set it itself.
|
||||
- For IPv6, the policy is always DROP.
|
||||
net.ipv4.ip_forward was not set to '1', and the daemon set it itself when
|
||||
an IPv4-enabled bridge network was created.
|
||||
- For IPv6, similar, but for sysctls "/proc/sys/net/ipv6/conf/default/forwarding"
|
||||
and "/proc/sys/net/ipv6/conf/all/forwarding".
|
||||
|
||||
[1]: https://github.com/moby/moby/blob/cff4f20c44a3a7c882ed73934dec6a77246c6323/libnetwork/drivers/bridge/setup_ip_forwarding.go#L44
|
||||
|
||||
|
||||
@@ -17,8 +17,10 @@ Table `filter`:
|
||||
The FORWARD chain's policy shown above is ACCEPT. However:
|
||||
|
||||
- For IPv4, [setupIPForwarding][1] sets the POLICY to DROP if the sysctl
|
||||
net.ipv4.ip_forward was not set to '1', and the daemon set it itself.
|
||||
- For IPv6, the policy is always DROP.
|
||||
net.ipv4.ip_forward was not set to '1', and the daemon set it itself when
|
||||
an IPv4-enabled bridge network was created.
|
||||
- For IPv6, similar, but for sysctls "/proc/sys/net/ipv6/conf/default/forwarding"
|
||||
and "/proc/sys/net/ipv6/conf/all/forwarding".
|
||||
|
||||
[1]: https://github.com/moby/moby/blob/cff4f20c44a3a7c882ed73934dec6a77246c6323/libnetwork/drivers/bridge/setup_ip_forwarding.go#L44
|
||||
|
||||
|
||||
56
integration/network/bridge/main_test.go
Normal file
56
integration/network/bridge/main_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package bridge // import "github.com/docker/docker/integration/network/bridge"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/testutil"
|
||||
"github.com/docker/docker/testutil/environment"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
testEnv *environment.Execution
|
||||
baseContext context.Context
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
shutdown := testutil.ConfigureTracing()
|
||||
ctx, span := otel.Tracer("").Start(context.Background(), "integration/network/bridge.TestMain")
|
||||
baseContext = ctx
|
||||
|
||||
var err error
|
||||
testEnv, err = environment.New(ctx)
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
span.End()
|
||||
shutdown(ctx)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = environment.EnsureFrozenImagesLinux(ctx, testEnv)
|
||||
if err != nil {
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
span.End()
|
||||
shutdown(ctx)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
testEnv.Print()
|
||||
code := m.Run()
|
||||
if code != 0 {
|
||||
span.SetStatus(codes.Error, "m.Run() returned non-zero exit code")
|
||||
}
|
||||
span.End()
|
||||
shutdown(ctx)
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func setupTest(t *testing.T) context.Context {
|
||||
ctx := testutil.StartSpan(baseContext, t)
|
||||
environment.ProtectAll(ctx, t, testEnv)
|
||||
t.Cleanup(func() { testEnv.Clean(ctx, t) })
|
||||
return ctx
|
||||
}
|
||||
@@ -51,12 +51,13 @@ type (
|
||||
|
||||
// configuration info for the "bridge" driver.
|
||||
type configuration struct {
|
||||
EnableIPForwarding bool
|
||||
EnableIPTables bool
|
||||
EnableIP6Tables bool
|
||||
EnableUserlandProxy bool
|
||||
UserlandProxyPath string
|
||||
Rootless bool
|
||||
EnableIPForwarding bool
|
||||
DisableFilterForwardDrop bool
|
||||
EnableIPTables bool
|
||||
EnableIP6Tables bool
|
||||
EnableUserlandProxy bool
|
||||
UserlandProxyPath string
|
||||
Rootless bool
|
||||
}
|
||||
|
||||
// networkConfiguration for network specific configuration
|
||||
@@ -552,14 +553,6 @@ func (d *driver) configure(option map[string]interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
if config.EnableIPForwarding {
|
||||
err = setupIPForwarding(config.EnableIPTables, config.EnableIP6Tables)
|
||||
if err != nil {
|
||||
log.G(context.TODO()).Warn(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if config.Rootless {
|
||||
var err error
|
||||
pdc, err = newPortDriverClient(context.TODO())
|
||||
@@ -890,8 +883,6 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) {
|
||||
bridgeSetup.queueStep(setupMTU)
|
||||
}
|
||||
|
||||
enableIPv6Forwarding := config.EnableIPv6 && d.config.EnableIPForwarding
|
||||
|
||||
// Module br_netfilter needs to be loaded with net.bridge.bridge-nf-call-ip[6]tables
|
||||
// enabled to implement icc=false, or DNAT when the userland-proxy is disabled.
|
||||
enableBrNfCallIptables := !config.EnableICC || !d.config.EnableUserlandProxy
|
||||
@@ -914,8 +905,17 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) {
|
||||
// existing device.
|
||||
{config.EnableIPv4 && bridgeAlreadyExists && !config.InhibitIPv4, setupVerifyAndReconcileIPv4},
|
||||
|
||||
// Enable IPv6 Forwarding
|
||||
{enableIPv6Forwarding, setupIPv6Forwarding},
|
||||
// Enable IP Forwarding
|
||||
{config.EnableIPv4 && d.config.EnableIPForwarding,
|
||||
func(*networkConfiguration, *bridgeInterface) error {
|
||||
return setupIPv4Forwarding(d.config.EnableIPTables && !d.config.DisableFilterForwardDrop)
|
||||
},
|
||||
},
|
||||
{config.EnableIPv6 && d.config.EnableIPForwarding,
|
||||
func(*networkConfiguration, *bridgeInterface) error {
|
||||
return setupIPv6Forwarding(d.config.EnableIP6Tables && !d.config.DisableFilterForwardDrop)
|
||||
},
|
||||
},
|
||||
|
||||
// Setup Loopback Addresses Routing
|
||||
{!d.config.EnableUserlandProxy, setupLoopbackAddressesRouting},
|
||||
|
||||
@@ -12,63 +12,106 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ipv4ForwardConf = "/proc/sys/net/ipv4/ip_forward"
|
||||
ipv4ForwardConfPerm = 0o644
|
||||
ipv4ForwardConf = "/proc/sys/net/ipv4/ip_forward"
|
||||
ipv6ForwardConfDefault = "/proc/sys/net/ipv6/conf/default/forwarding"
|
||||
ipv6ForwardConfAll = "/proc/sys/net/ipv6/conf/all/forwarding"
|
||||
)
|
||||
|
||||
func configureIPForwarding(enable bool) error {
|
||||
var val byte
|
||||
if enable {
|
||||
val = '1'
|
||||
func setupIPv4Forwarding(wantFilterForwardDrop bool) (retErr error) {
|
||||
changed, err := configureIPForwarding(ipv4ForwardConf, '1')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(ipv4ForwardConf, []byte{val, '\n'}, ipv4ForwardConfPerm)
|
||||
if changed {
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if _, err := configureIPForwarding(ipv4ForwardConf, '0'); err != nil {
|
||||
log.G(context.TODO()).WithError(err).Error("Cannot disable IPv4 forwarding")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// When enabling ip_forward set the default policy on forward chain to drop.
|
||||
if changed && wantFilterForwardDrop {
|
||||
if err := setFilterForwardDrop(iptables.IPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupIPForwarding(enableIPTables bool, enableIP6Tables bool) error {
|
||||
// Get current IPv4 forward setup
|
||||
ipv4ForwardData, err := os.ReadFile(ipv4ForwardConf)
|
||||
func setupIPv6Forwarding(wantFilterForwardDrop bool) (retErr error) {
|
||||
// Set IPv6 default.forwarding, if needed.
|
||||
// FIXME(robmry) - is it necessary to set this, setting "all" (below) does the job?
|
||||
changedDef, err := configureIPForwarding(ipv6ForwardConfDefault, '1')
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot read IP forwarding setup: %v", err)
|
||||
return err
|
||||
}
|
||||
if changedDef {
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if _, err := configureIPForwarding(ipv6ForwardConfDefault, '0'); err != nil {
|
||||
log.G(context.TODO()).WithError(err).Error("Cannot disable IPv6 default.forwarding")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Enable IPv4 forwarding only if it is not already enabled
|
||||
if ipv4ForwardData[0] != '1' {
|
||||
// Enable IPv4 forwarding
|
||||
if err := configureIPForwarding(true); err != nil {
|
||||
return fmt.Errorf("Enabling IP forwarding failed: %v", err)
|
||||
}
|
||||
// When enabling ip_forward set the default policy on forward chain to
|
||||
// drop only if the daemon option iptables is not set to false.
|
||||
if enableIPTables {
|
||||
iptable := iptables.GetIptable(iptables.IPv4)
|
||||
if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil {
|
||||
if err := configureIPForwarding(false); err != nil {
|
||||
log.G(context.TODO()).Errorf("Disabling IP forwarding failed, %v", err)
|
||||
// Set IPv6 all.forwarding, if needed.
|
||||
changedAll, err := configureIPForwarding(ipv6ForwardConfAll, '1')
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if changedAll {
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if _, err := configureIPForwarding(ipv6ForwardConfAll, '0'); err != nil {
|
||||
log.G(context.TODO()).WithError(err).Error("Cannot disable IPv6 all.forwarding")
|
||||
}
|
||||
return err
|
||||
}
|
||||
iptables.OnReloaded(func() {
|
||||
log.G(context.TODO()).Debug("Setting the default DROP policy on firewall reload")
|
||||
if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil {
|
||||
log.G(context.TODO()).Warnf("Setting the default DROP policy on firewall reload failed, %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// add only iptables rules - forwarding is handled by setupIPv6Forwarding in setup_ipv6
|
||||
if enableIP6Tables {
|
||||
iptable := iptables.GetIptable(iptables.IPv6)
|
||||
if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil {
|
||||
log.G(context.TODO()).Warnf("Setting the default DROP policy on firewall reload failed, %v", err)
|
||||
if (changedAll || changedDef) && wantFilterForwardDrop {
|
||||
if err := setFilterForwardDrop(iptables.IPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
iptables.OnReloaded(func() {
|
||||
log.G(context.TODO()).Debug("Setting the default DROP policy on firewall reload")
|
||||
if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil {
|
||||
log.G(context.TODO()).Warnf("Setting the default DROP policy on firewall reload failed, %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureIPForwarding(file string, val byte) (changed bool, _ error) {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil || len(data) == 0 {
|
||||
return false, fmt.Errorf("cannot read IP forwarding setup from '%s': %w", file, err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return false, fmt.Errorf("cannot read IP forwarding setup from '%s': 0 bytes", file)
|
||||
}
|
||||
if data[0] == val {
|
||||
return false, nil
|
||||
}
|
||||
if err := os.WriteFile(file, []byte{val, '\n'}, 0o644); err != nil {
|
||||
return false, fmt.Errorf("failed to set IP forwarding '%s' = '%c': %w", file, val, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func setFilterForwardDrop(ipv iptables.IPVersion) error {
|
||||
iptable := iptables.GetIptable(ipv)
|
||||
if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil {
|
||||
return err
|
||||
}
|
||||
iptables.OnReloaded(func() {
|
||||
log.G(context.TODO()).WithFields(log.Fields{"ipv": ipv}).Debug("Setting the default DROP policy on firewall reload")
|
||||
if err := iptable.SetDefaultPolicy(iptables.Filter, "FORWARD", iptables.Drop); err != nil {
|
||||
log.G(context.TODO()).WithFields(log.Fields{
|
||||
"error": err,
|
||||
"ipv": ipv,
|
||||
}).Warn("Failed to set the default DROP policy on firewall reload")
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,51 +3,27 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/internal/testutils/netnsutils"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestSetupIPForwarding(t *testing.T) {
|
||||
// Read current setting and ensure the original value gets restored
|
||||
procSetting := readCurrentIPForwardingSetting(t)
|
||||
defer reconcileIPForwardingSetting(t, procSetting)
|
||||
defer netnsutils.SetupTestOSContext(t)()
|
||||
|
||||
// Disable IP Forwarding if enabled
|
||||
if bytes.Equal(procSetting, []byte("1\n")) {
|
||||
writeIPForwardingSetting(t, []byte{'0', '\n'})
|
||||
}
|
||||
_, err := configureIPForwarding(ipv4ForwardConf, '0')
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Set IP Forwarding
|
||||
if err := setupIPForwarding(true, true); err != nil {
|
||||
t.Fatalf("Failed to setup IP forwarding: %v", err)
|
||||
}
|
||||
err = setupIPv4Forwarding(true)
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Read new setting
|
||||
procSetting = readCurrentIPForwardingSetting(t)
|
||||
if !bytes.Equal(procSetting, []byte("1\n")) {
|
||||
t.Fatal("Failed to effectively setup IP forwarding")
|
||||
}
|
||||
}
|
||||
|
||||
func readCurrentIPForwardingSetting(t *testing.T) []byte {
|
||||
procSetting, err := os.ReadFile(ipv4ForwardConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't execute test: Failed to read current IP forwarding setting: %v", err)
|
||||
}
|
||||
return procSetting
|
||||
}
|
||||
|
||||
func writeIPForwardingSetting(t *testing.T, chars []byte) {
|
||||
err := os.WriteFile(ipv4ForwardConf, chars, ipv4ForwardConfPerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't execute or cleanup after test: Failed to reset IP forwarding: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func reconcileIPForwardingSetting(t *testing.T, original []byte) {
|
||||
current := readCurrentIPForwardingSetting(t)
|
||||
if !bytes.Equal(original, current) {
|
||||
writeIPForwardingSetting(t, original)
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(procSetting, []byte{'1', '\n'}))
|
||||
}
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/log"
|
||||
)
|
||||
|
||||
// Standard link local prefix
|
||||
var linkLocalPrefix = netip.MustParsePrefix("fe80::/64")
|
||||
|
||||
const (
|
||||
ipv6ForwardConfPerm = 0o644
|
||||
ipv6ForwardConfDefault = "/proc/sys/net/ipv6/conf/default/forwarding"
|
||||
ipv6ForwardConfAll = "/proc/sys/net/ipv6/conf/all/forwarding"
|
||||
)
|
||||
|
||||
func setupBridgeIPv6(config *networkConfiguration, i *bridgeInterface) error {
|
||||
procFile := "/proc/sys/net/ipv6/conf/" + config.BridgeName + "/disable_ipv6"
|
||||
ipv6BridgeData, err := os.ReadFile(procFile)
|
||||
@@ -26,7 +17,7 @@ func setupBridgeIPv6(config *networkConfiguration, i *bridgeInterface) error {
|
||||
}
|
||||
// Enable IPv6 on the bridge only if it isn't already enabled
|
||||
if ipv6BridgeData[0] != '0' {
|
||||
if err := os.WriteFile(procFile, []byte{'0', '\n'}, ipv6ForwardConfPerm); err != nil {
|
||||
if err := os.WriteFile(procFile, []byte{'0', '\n'}, 0o644); err != nil {
|
||||
return fmt.Errorf("Unable to enable IPv6 addresses on bridge: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -49,31 +40,3 @@ func setupGatewayIPv6(config *networkConfiguration, i *bridgeInterface) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupIPv6Forwarding(config *networkConfiguration, i *bridgeInterface) error {
|
||||
// Get current IPv6 default forwarding setup
|
||||
ipv6ForwardDataDefault, err := os.ReadFile(ipv6ForwardConfDefault)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot read IPv6 default forwarding setup: %v", err)
|
||||
}
|
||||
// Enable IPv6 default forwarding only if it is not already enabled
|
||||
if ipv6ForwardDataDefault[0] != '1' {
|
||||
if err := os.WriteFile(ipv6ForwardConfDefault, []byte{'1', '\n'}, ipv6ForwardConfPerm); err != nil {
|
||||
log.G(context.TODO()).Warnf("Unable to enable IPv6 default forwarding: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get current IPv6 all forwarding setup
|
||||
ipv6ForwardDataAll, err := os.ReadFile(ipv6ForwardConfAll)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot read IPv6 all forwarding setup: %v", err)
|
||||
}
|
||||
// Enable IPv6 all forwarding only if it is not already enabled
|
||||
if ipv6ForwardDataAll[0] != '1' {
|
||||
if err := os.WriteFile(ipv6ForwardConfAll, []byte{'1', '\n'}, ipv6ForwardConfPerm); err != nil {
|
||||
log.G(context.TODO()).Warnf("Unable to enable IPv6 all forwarding: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ dockerd - Enable daemon mode
|
||||
[**--insecure-registry**[=*[]*]]
|
||||
[**--ip**[=*0.0.0.0*]]
|
||||
[**--ip-forward**[=**true**]]
|
||||
[**--ip-forward-no-drop**[=**true**]]
|
||||
[**--ip-masq**[=**true**]]
|
||||
[**--iptables**[=**true**]]
|
||||
[**--ipv6**]
|
||||
@@ -289,11 +290,20 @@ unix://[/path/to/socket] to use.
|
||||
has no effect.
|
||||
|
||||
This setting will also enable IPv6 forwarding if you have both
|
||||
**--ip-forward=true** and **--fixed-cidr-v6** set. Note that this may reject
|
||||
Router Advertisements and interfere with the host's existing IPv6
|
||||
**--ip-forward=true** and an IPv6 enabled bridge network. Note that this
|
||||
may reject Router Advertisements and interfere with the host's existing IPv6
|
||||
configuration. For more information, consult the documentation about
|
||||
"Advanced Networking - IPv6".
|
||||
|
||||
**--ip-forward-no-drop**=**true**|**false**
|
||||
When **false**, the default, if Docker enables IP forwarding itself (see
|
||||
**--ip-forward**), and **--iptables** or **--ip6tables** are enabled, it
|
||||
also sets the default policy for the FORWARD chain in the iptables or
|
||||
ip6tables filter table to DROP.
|
||||
|
||||
When **true**, and when IP forwarding is already enabled, Docker does
|
||||
not modify the default policy of the FORWARD chain.
|
||||
|
||||
**--ip-masq**=**true**|**false**
|
||||
Enable IP masquerading for bridge's IP range. Default is **true**.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user