DEV Community

Christian Schärf
Christian Schärf

Posted on • Originally published at schaerf.tech

WiFi Not Working On The Train? Docker Might Be To Blame

A while ago I was travelling on the ICE high-speed train of Germany’s national railway operator Deutsche Bahn and tried to use the on-board WiFi, but, alas, to no avail.
Now, it is not exactly unheard of that the Internet connection does not work, but in this case, I could not even reach the captive portal.1

[!note]- Docker network types
When Docker networks are being referenced in this article, I am referring to networks of the bridge type.

[!TIP]- Resolution
The onboard WiFi on the ICE has the IP address range 172.18.0.0/16 which was already occupied by a previously created Docker network.
Thus, all packages meant for the onboard WiFi were routed into the Docker network.

Analysis

Previously, I was working with Docker and had created a network of type bridge.
With this type, all containers connected to the network as well as the host get IP addresses assigned with which they can be reached.
Per default, Docker uses a /16 block of the IP address range 172.16.0.0/122 for each network.
This range is reserved for private networking and thus not part of the global Internet.3

The first address block 172.16.0.0/16 is skipped used since it is commonly used for local networks.4
The next block 172.17.0.0/16 is the default address range of the default bridge network:

❯ docker network inspect bridge 
[
    {
        …
        "IPAM": {
            …
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "IPRange": "",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        …
    }
]
Enter fullscreen mode Exit fullscreen mode

All further blocks are used for networks created with docker network create, in ascending order. The first one (here named demo) is assigned:

❯ docker network inspect demo 
[
    {
        …
        "IPAM": {
            …
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "IPRange": "",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        …
    }
]
Enter fullscreen mode Exit fullscreen mode

As previously mentioned, the block 172.16.0.0/16 is skipped because it is commonly used for local networks.
All other ranges are usually unused. One consequential exception the the ICE’s onboard WiFi: It also uses the 172.18.0.0/16 range,
the same as the first user-created Docker network:

❯ ip a
…
2: wlp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP group default qlen 1000
    link/ether 01:23:45:67:89:ab brd ff:ff:ff:ff:ff:ff
    altname wlx74d83e3e31a4
    inet 172.18.0.1/16 brd 172.18.255.255 scope global dynamic noprefixroute wlp2s0
       valid_lft 598sec preferred_lft 598sec
    inet6 fe80::cdd8:684:4c94:dc3d/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
…
12: br-d9ec15037321: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 76:8c:41:da:45:99 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-d9ec15037321
       valid_lft forever preferred_lft forever
Enter fullscreen mode Exit fullscreen mode

Consequently, all packages that are meant for the onboard WiFi are routed to the Docker network and get lost.

Workaround

For a quick workaround it is sufficient to delete the offending Docker network with docker network rm and to recreate it with docker network create.
Afterwards, the containers have to be reconnected if required.

[!WARNING]
Before, all (including non-running) containers connected to the network should be disconnected manually.

This works because the Docker skips already used IP address ranges when (re)creating networks.
Understandably, Docker cannot know if a specific IP address ranges will maybe be needed at some point in the future.

Solution

The IP address ranges used by the Docker engine can be configured in /etc/docker/daemon.json.5
Here, usable address ranges are defined and the network sizes (number of prefix bits) are assigned.

IPv4

For IPv4 it is reasonable to use a part of the private networking IP address range 10.0.0.0/83, for example:

{
    "default-address-pools": [
        {
            "base": "10.128.0.0/9",
            "size": 16
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Here the second half of the previously mentioned range stating at 10.128.0.0 is used. Each Docker network will have a /16 bit prefix.
Thus, 128 networks with 2¹⁶ = 65536 addresses each are available.

Keep in mind, however, that this IP address range can also collide with other local networks, especially public WiFis and corporate networks.

IPv6

Since version 27 of the Docker engine, IPv6 support is no longer experimental.6 If no IPv6 address ranges are specified
(in /etc/docker/daemon.json or via the --subnet command line option) Docker assigns a subrange of the unique local addresses.7

When creating a network, you can specify to use IPv6 only:

docker network create --ipv6 --ipv4=false ipv6only
Enter fullscreen mode Exit fullscreen mode

For Compose, you can also set these settings:8

networks:
  ipv6only:
    enable_ipv4: false
    enable_ipv6: true
Enter fullscreen mode Exit fullscreen mode

Unfortunately, there are still some limitations at the time of writing:

  • Forwarding an IPv4 host port to the container is apparently not possible.9. This means that the container can be reached by IPv6 only from the outside. For operating a server, this is probably not acceptable in most cases, but it should be fine for local development. > [!note] > This limitation is not present in the reverse case: A host IPv6 port can be forwarded to a container having only an IPv4 address.
  • It is apparently not possible to set IPv6-only as default for new networks.
  • Also, it is apparently impossible to configure the predefined bridge network as IPv6-only.

Closing Remarks

None of the involved parties made a mistake. Chosing the 172.18.0.0/16 address range for the onboard WiFi cannot be criticised as this is the inteded purpose.
Docker has to take its IP addresses from somewhere as well, using ranges designated for private networking is a sensible choice.
Even in the larger address range 10.0.0.0/8 (which Podman uses per default for this purpose) collisions are, in principle, possible.
There are no IPv4 address ranges reserved for ‘host-local’ networks, after all.

Only the much larger address range of the unique local addresses of IPv6 presents a conclusive solution.
However, IPv6 is not used by Docker per default and is still subject to some limitations.
Apparantly, IPv6 is still Neuland (uncharted territory) for Docker.

Here, the number after the slash specifies, how many bits of the IP address are part of the network prefix.
Thus, the network 172.16.0.0/12 contains the IP addresses from 172.16.0.0 to 172.31.255.255 because they all share the same leading 12 bits.


  1. The captive portal is the page that public networks redirect you to for you to accept the terms of use. 

  2. We use the CIDR notation

  3. https://en.wikipedia.org/wiki/List_of_reserved_IP_addresses#IPv4 

  4. https://docs.docker.com/engine/network/#automatic-subnet-allocation 

  5. Assuming you use a reasonable operating system 

  6. https://docs.docker.com/engine/release-notes/27/#ipv6 

  7. https://docs.docker.com/engine/daemon/ipv6/#dynamic-ipv6-subnet-allocation 

  8. https://docs.docker.com/reference/compose-file/networks/#enable_ipv4 

  9. https://github.com/moby/moby/issues/49617 

Top comments (0)