Repository: moby/moby (language: go)

Issue:
Changing MTU of default bridge network doesn´t work
**Description:**
docker0 network interface doesn´t change de MTU settings according to /etc/docker/daemon.json file.

**Steps to reproduce the issue:**
1. Create a /etc/docker/daemon.json file with below content:

`
 {
  "bip": "172.17.0.200/24",
  "mtu": 1400
}
`

2. Restart the docker service;
3. Check docker0 interface MTU settings.

**Describe the results you received:**
docker0 interface remains with the default MTU configuration (1500).

> [xxx@yyy ~]$ sudo ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.200  netmask 255.255.255.0  broadcast 172.17.0.255
        ether 02:42:1c:cb:b1:be  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

**Describe the results you expected:**
I expected to update the mtu value of docker0 interface after restarting the service.

**Additional information you deem important (e.g. issue happens only occasionally):**
I´ve change the bip just to make sure that dockerd is reading the /etc/docker/daemon.json file.
I´m running docker on a server over OpenStack. The configuration of MTU is mandatory to work without huge performance impacts.

**Output of docker version:**
[xxx@yyy ~]$ sudo docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:23:03 2018
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       e68fc7a
  Built:            Tue Aug 21 17:25:29 2018
  OS/Arch:          linux/amd64
  Experimental:     false


**Output of docker info:**

[xxx@yyy ~]$ sudo docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 18.06.1-ce
Storage Driver: devicemapper
 Pool Name: docker-8:1-50437250-pool
 Pool Blocksize: 65.54kB
 Base Device Size: 10.74GB
 Backing Filesystem: xfs
 Udev Sync Supported: true
 Data file: /dev/loop0
 Metadata file: /dev/loop1
 Data loop file: /var/lib/docker/devicemapper/devicemapper/data
 Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
 Data Space Used: 11.73MB
 Data Space Total: 107.4GB
 Data Space Available: 51.7GB
 Metadata Space Used: 581.6kB
 Metadata Space Total: 2.147GB
 Metadata Space Available: 2.147GB
 Thin Pool Minimum Free Space: 10.74GB
 Deferred Removal Enabled: true
 Deferred Deletion Enabled: true
 Deferred Deleted Device Count: 0
 Library Version: 1.02.146-RHEL7 (2018-01-22)
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
 Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 468a545b9edcd5932818eb9de8e72413e616e86e
runc version: 69663f0bd4b60df09991c08812a60108003fa340
init version: fec3683
Security Options:
 seccomp
  Profile: default
Kernel Version: 3.10.0-862.14.4.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 15.51GiB
Name: ***SUPRESSED BY MYSELF***
ID: UTZ5:7H4Q:I7YK:F43L:B5HQ:Q24M:RJIR:7ZKR:DOAN:Y65M:M4KK:PTDY
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
 127.0.0.0/8
Live Restore Enabled: false

WARNING: devicemapper: usage of loopback devices is strongly discouraged for production use.
         Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.
Changing MTU of default bridge network doesn´t work
**Description:**
docker0 network interface doesn´t change de MTU settings according to /etc/docker/daemon.json file.

**Steps to reproduce the issue:**
1. Create a /etc/docker/daemon.json file with below content:

`
 {
  "bip": "172.17.0.200/24",
  "mtu": 1400
}
`

2. Restart the docker service;
3. Check docker0 interface MTU settings.

**Describe the results you received:**
docker0 interface remains with the default MTU configuration (1500).

> [xxx@yyy ~]$ sudo ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.200  netmask 255.255.255.0  broadcast 172.17.0.255
        ether 02:42:1c:cb:b1:be  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

**Describe the results you expected:**
I expected to update the mtu value of docker0 interface after restarting the service.

**Additional information you deem important (e.g. issue happens only occasionally):**
I´ve change the bip just to make sure that dockerd is reading the /etc/docker/daemon.json file.
I´m running docker on a server over OpenStack. The configuration of MTU is mandatory to work without huge performance impacts.

**Output of docker version:**
[xxx@yyy ~]$ sudo docker version
Client:
 Version:           18.06.1-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        e68fc7a
 Built:             Tue Aug 21 17:23:03 2018
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.1-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       e68fc7a
  Built:            Tue Aug 21 17:25:29 2018
  OS/Arch:          linux/amd64
  Experimental:     false


**Output of docker info:**

[xxx@yyy ~]$ sudo docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 18.06.1-ce
Storage Driver: devicemapper
 Pool Name: docker-8:1-50437250-pool
 Pool Blocksize: 65.54kB
 Base Device Size: 10.74GB
 Backing Filesystem: xfs
 Udev Sync Supported: true
 Data file: /dev/loop0
 Metadata file: /dev/loop1
 Data loop file: /var/lib/docker/devicemapper/devicemapper/data
 Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
 Data Space Used: 11.73MB
 Data Space Total: 107.4GB
 Data Space Available: 51.7GB
 Metadata Space Used: 581.6kB
 Metadata Space Total: 2.147GB
 Metadata Space Available: 2.147GB
 Thin Pool Minimum Free Space: 10.74GB
 Deferred Removal Enabled: true
 Deferred Deletion Enabled: true
 Deferred Deleted Device Count: 0
 Library Version: 1.02.146-RHEL7 (2018-01-22)
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
 Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 468a545b9edcd5932818eb9de8e72413e616e86e
runc version: 69663f0bd4b60df09991c08812a60108003fa340
init version: fec3683
Security Options:
 seccomp
  Profile: default
Kernel Version: 3.10.0-862.14.4.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 15.51GiB
Name: ***SUPRESSED BY MYSELF***
ID: UTZ5:7H4Q:I7YK:F43L:B5HQ:Q24M:RJIR:7ZKR:DOAN:Y65M:M4KK:PTDY
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
 127.0.0.0/8
Live Restore Enabled: false

WARNING: devicemapper: usage of loopback devices is strongly discouraged for production use.
         Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.


Discussion / hints:
Also true with 19.03. `--mtu` also doesn't take effect.
Bridges do initially have an MTU of 1500, regardless of the options given... which looks wrong. But, as soon as a container is attached to the network, the bridge's MTU is updated to the configured value. When the last container is detached, the bridge's MTU reverts to the default 1500. The same applies to any bridge network, not just 'docker0'.

That's because no MTU is set on the bridge device itself, but a Linux bridge's MTU is the min MTU of any device connected to it. When a 'veth' device is added to the bridge to connect a container, the MTU is set on that 'veth'.

So, it looks like the MTU options are working as expected. We could explicitly set an MTU on the bridge, just to make it a bit less disconcerting ... I'll try a PR for that to see what people think.
Also true with 19.03. `--mtu` also doesn't take effect.
Bridges do initially have an MTU of 1500, regardless of the options given... which looks wrong. But, as soon as a container is attached to the network, the bridge's MTU is updated to the configured value. When the last container is detached, the bridge's MTU reverts to the default 1500. The same applies to any bridge network, not just 'docker0'.

That's because no MTU is set on the bridge device itself, but a Linux bridge's MTU is the min MTU of any device connected to it. When a 'veth' device is added to the bridge to connect a container, the MTU is set on that 'veth'.

So, it looks like the MTU options are working as expected. We could explicitly set an MTU on the bridge, just to make it a bit less disconcerting ... I'll try a PR for that to see what people think.

Fix patch:
diff --git a/libnetwork/drivers/bridge/bridge_linux.go b/libnetwork/drivers/bridge/bridge_linux.go
index f438e5480c7d0..a9b54cfce3955 100644
--- a/libnetwork/drivers/bridge/bridge_linux.go
+++ b/libnetwork/drivers/bridge/bridge_linux.go
@@ -735,6 +735,14 @@ func (d *driver) createNetwork(config *networkConfiguration) (err error) {
 		bridgeSetup.queueStep(setupDefaultSysctl)
 	}
 
+	// Always set the bridge's MTU if specified. This is purely cosmetic; a bridge's
+	// MTU is the min MTU of device connected to it, and MTU will be set on each
+	// 'veth'. But, for a non-default MTU, the bridge's MTU will look wrong until a
+	// container is attached.
+	if config.Mtu > 0 {
+		bridgeSetup.queueStep(setupMTU)
+	}
+
 	// Even if a bridge exists try to setup IPv4.
 	bridgeSetup.queueStep(setupBridgeIPv4)
 
diff --git a/libnetwork/drivers/bridge/setup_device_linux.go b/libnetwork/drivers/bridge/setup_device_linux.go
index a90b8cea1d027..050aaa1710482 100644
--- a/libnetwork/drivers/bridge/setup_device_linux.go
+++ b/libnetwork/drivers/bridge/setup_device_linux.go
@@ -45,6 +45,14 @@ func setupDevice(config *networkConfiguration, i *bridgeInterface) error {
 	return nil
 }
 
+func setupMTU(config *networkConfiguration, i *bridgeInterface) error {
+	if err := i.nlh.LinkSetMTU(i.Link, config.Mtu); err != nil {
+		log.G(context.TODO()).WithError(err).Errorf("Failed to set bridge MTU %s via netlink", config.BridgeName)
+		return err
+	}
+	return nil
+}
+
 func setupDefaultSysctl(config *networkConfiguration, i *bridgeInterface) error {
 	// Disable IPv6 router advertisements originating on the bridge
 	sysPath := filepath.Join("/proc/sys/net/ipv6/conf/", config.BridgeName, "accept_ra")


Test patch:
diff --git a/integration/network/network_test.go b/integration/network/network_test.go
index 154c08d931b9b..3b80f37f9f117 100644
--- a/integration/network/network_test.go
+++ b/integration/network/network_test.go
@@ -240,7 +240,7 @@ func TestDefaultNetworkOpts(t *testing.T) {
 
 			// Create a new network
 			networkName := "testnet"
-			network.CreateNoError(ctx, t, c, networkName, func(create *types.NetworkCreate) {
+			networkId := network.CreateNoError(ctx, t, c, networkName, func(create *types.NetworkCreate) {
 				if tc.configFrom {
 					create.ConfigFrom = &ntypes.ConfigReference{
 						Network: "from-net",
@@ -249,6 +249,15 @@ func TestDefaultNetworkOpts(t *testing.T) {
 			})
 			defer c.NetworkRemove(ctx, networkName)
 
+			// Check the MTU of the bridge itself, before any devices are connected. (The
+			// bridge's MTU will be set to the minimum MTU of anything connected to it, but
+			// it's set explicitly on the bridge anyway - so it doesn't look like the option
+			// was ignored.)
+			cmd := exec.Command("ip", "link", "show", "br-"+networkId[:12])
+			output, err := cmd.CombinedOutput()
+			assert.NilError(t, err)
+			assert.Check(t, is.Contains(string(output), fmt.Sprintf(" mtu %d ", tc.mtu)), "Bridge MTU should have been set to %d", tc.mtu)
+
 			// Start a container to inspect the MTU of its network interface
 			id1 := container.Run(ctx, t, c, container.WithNetworkMode(networkName))
 			defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{Force: true})