Skip to content

Commit

Permalink
Check kernel params for bridge traffic filtering
Browse files Browse the repository at this point in the history
Check whether bridge traffic is passed to iptables' chains if started
with `--icc=false`. Inter-container communication can be restricted only
if following parameters are set.

- `/proc/sys/net/bridge/bridge-nf-call-iptables`
- `/proc/sys/net/bridge/bridge-nf-call-ip6tables`

Resolves issue moby#11404

Signed-off-by: Michal Minar <miminar@redhat.com>
  • Loading branch information
Michal Minar committed Apr 8, 2015
1 parent 06433cf commit a2c1690
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 10 deletions.
121 changes: 115 additions & 6 deletions daemon/networkdriver/bridge/driver.go
Expand Up @@ -27,6 +27,16 @@ const (
MaxAllocatedPortAttempts = 10
)

// Enumeration type saying which versions of IP protocol to process.
type ipVersion int

const (
ipvnone ipVersion = iota
ipv4
ipv6
ipvboth
)

// Network interface represents the networking stack of a container
type networkInterface struct {
IP net.IP
Expand Down Expand Up @@ -91,6 +101,78 @@ func initPortMapper() {
})
}

// Get kernel param path saying whether IPv${ipVer} traffic is being forwarded
// on particular interface. Interface may be specified for IPv6 only. If
// `iface` is empty, `default` will be assumed, which represents default value
// for new interfaces.
func getForwardingKernelParam(ipVer ipVersion, iface string) string {
switch ipVer {
case ipv4:
return "/proc/sys/net/ipv4/ip_forward"
case ipv6:
if iface == "" {
iface = "default"
}
return fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/forwarding", iface)
default:
return ""
}
}

// Get kernel param path saying whether bridged IPv${ipVer} traffic shall be
// passed to ip${ipVer}tables' chains.
func getBridgeNFKernelParam(ipVer ipVersion) string {
switch ipVer {
case ipv4:
return "/proc/sys/net/bridge/bridge-nf-call-iptables"
case ipv6:
return "/proc/sys/net/bridge/bridge-nf-call-ip6tables"
default:
return ""
}
}

func getKernelBoolParam(path string) (bool, error) {
enabled := false
line, err := ioutil.ReadFile(path)
if err != nil {
err = fmt.Errorf("Failed to read kernel parameter %q: %v", path, err)
} else if len(line) > 0 {
enabled = line[0] == '1'
}
return enabled, err
}

func isPacketForwardingEnabled(ipVer ipVersion, iface string) (bool, error) {
switch ipVer {
case ipv4, ipv6:
return getKernelBoolParam(getForwardingKernelParam(ipVer, iface))
case ipvboth:
enabled, err := getKernelBoolParam(getForwardingKernelParam(ipv4, ""))
if err != nil || !enabled {
return enabled, err
}
return getKernelBoolParam(getForwardingKernelParam(ipv6, iface))
default:
return true, nil
}
}

func isBridgeNetFilteringEnabled(ipVer ipVersion) (bool, error) {
switch ipVer {
case ipv4, ipv6:
return getKernelBoolParam(getBridgeNFKernelParam(ipVer))
case ipvboth:
enabled, err := getKernelBoolParam(getBridgeNFKernelParam(ipv4))
if err != nil || !enabled {
return enabled, err
}
return getKernelBoolParam(getBridgeNFKernelParam(ipv6))
default:
return true, nil
}
}

func InitDriver(job *engine.Job) error {
var (
networkv4 *net.IPNet
Expand Down Expand Up @@ -215,17 +297,44 @@ func InitDriver(job *engine.Job) error {

if ipForward {
// Enable IPv4 forwarding
if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("WARNING: unable to enable IPv4 forwarding: %s\n", err)
if err := ioutil.WriteFile(getForwardingKernelParam(ipv4, ""), []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("Unable to enable IPv4 forwarding: %v", err)
}

if fixedCIDRv6 != "" {
// Enable IPv6 forwarding
if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("WARNING: unable to enable IPv6 default forwarding: %s\n", err)
if err := ioutil.WriteFile(getForwardingKernelParam(ipv6, ""), []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("Unable to enable IPv6 default forwarding: %v", err)
}
if err := ioutil.WriteFile(getForwardingKernelParam(ipv6, "all"), []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("Unable to enable IPv6 all forwarding: %v", err)
}
}
}

if !icc && enableIPTables {
ipVer := ipvnone
checkParams := []string{}
enabled, err := isPacketForwardingEnabled(ipv4, "")
if err != nil {
logrus.Warnf("Failed to check IPv4 forwarding: %v", err)
} else if enabled {
ipVer = ipv4
checkParams = append(checkParams, getBridgeNFKernelParam(ipv4))
}
if fixedCIDRv6 != "" || enableIPv6 {
if enabled, err = isPacketForwardingEnabled(ipv6, bridgeIface); err != nil {
logrus.Warnf("Failed to check IPv6 forwarding of %q interface: %v", bridgeIface, err)
} else if enabled {
ipVer |= ipv6
checkParams = append(checkParams, getBridgeNFKernelParam(ipv6))
}
if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte{'1', '\n'}, 0644); err != nil {
logrus.Warnf("WARNING: unable to enable IPv6 all forwarding: %s\n", err)
}
if ipVer != ipvnone {
if enabled, err = isBridgeNetFilteringEnabled(ipVer); err != nil {
logrus.Warnf("Failed to check whether bridge net filtering is enabled: %v", err)
} else if !enabled {
return fmt.Errorf("Bridge traffic is not filtered, which prevents inter-container communication to be restricted. Check {%s} kernel parameters.", strings.Join(checkParams, ", "))
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions daemon/networkdriver/bridge/driver_test.go
Expand Up @@ -52,6 +52,15 @@ func newPortAllocationJobWithInvalidHostIP(eng *engine.Engine, port int) (job *e
return
}

func TestIPConstantValues(t *testing.T) {
if ipv4|ipv6 != ipvboth {
t.Fatal("bitwise or of ipv4(%#04b) and ipv6(%#04b) must yield ipvboth(%#04b)", ipv4, ipv6, ipvboth)
}
if ipvboth&(^(ipv4 | ipv6)) != ipvnone {
t.Fatal("ipvboth(%#04b) with unset ipv4(%#04b) and ipv6(%#04b) bits shall equal to ipvnone", ipvboth, ipv4, ipv6)
}
}

func TestAllocatePortDetection(t *testing.T) {
eng := engine.New()
eng.Logging = false
Expand Down
17 changes: 13 additions & 4 deletions docs/sources/articles/networking.md
Expand Up @@ -208,7 +208,7 @@ the daemon filters out all localhost IP address `nameserver` entries from
the host's original file.

Filtering is necessary because all localhost addresses on the host are
unreachable from the container's network. After this filtering, if there
unreachable from the container's network. After this filtering, if there
are no more `nameserver` entries left in the container's `/etc/resolv.conf`
file, the daemon adds public Google DNS nameservers
(8.8.8.8 and 8.8.4.4) to the container's DNS configuration. If IPv6 is
Expand All @@ -226,7 +226,7 @@ notifier active which will watch for changes to the host DNS configuration.

> **Note**:
> The file change notifier relies on the Linux kernel's inotify feature.
> Because this feature is currently incompatible with the overlay filesystem
> Because this feature is currently incompatible with the overlay filesystem
> driver, a Docker daemon using "overlay" will not be able to take advantage
> of the `/etc/resolv.conf` auto-update feature.
Expand All @@ -238,7 +238,7 @@ of a facility to ensure atomic writes of the `resolv.conf` file while the
container is running. If the container's `resolv.conf` has been edited since
it was started with the default configuration, no replacement will be
attempted as it would overwrite the changes performed by the container.
If the options (`--dns` or `--dns-search`) have been used to modify the
If the options (`--dns` or `--dns-search`) have been used to modify the
default host configuration, then the replacement with an updated host's
`/etc/resolv.conf` will not happen as well.

Expand Down Expand Up @@ -340,6 +340,15 @@ page for further details.
> hostname, which Docker will not recognize in the context of the
> `--link=` option.
> **Note**:
> For the `--icc=false` option to work, the bridge's traffic must be passed to
> iptables' chains. This is controled by the kernel parameters
> `net.bridge.bridge-nf-call-iptables` and
> `net.bridge.bridge-nf-call-ip6tables`. If they are set to 1, traffic will be
> processed with `iptables` rules. However, several distributions set them to 0
> by default. If the `--icc=false` is set, Docker will check these parameters
> upon its initialization and will refuse to start unless thery are enabled.
You can run the `iptables` command on your Docker host to see whether
the `FORWARD` chain has a default policy of `ACCEPT` or `DROP`:

Expand Down Expand Up @@ -654,7 +663,7 @@ on every host.

In this scenario containers of the same host can communicate directly with each
other. The traffic between containers on different hosts will be routed via
their hosts and the router. For example packet from `Container1-1` to
their hosts and the router. For example packet from `Container1-1` to
`Container2-1` will be routed through `Host1`, `Router` and `Host2` until it
arrives at `Container2-1`.

Expand Down

0 comments on commit a2c1690

Please sign in to comment.