Sidebar

IPv6 Access to Published Ports

Articles » Docker Networking for Container-Based Services » IPv6 Access to Published Ports

The previous section described the role of userland docker-proxy supporting containers connecting to published ports, or local processes connecting to loopback interface. We've also seen that when a published port is not bound to a specific IPv4 address, the proxy process listens on an IPv6 socket, enabling IPv6 access to services offered in IPv4-only containers.

Docker proxy is a simple TCP or UDP proxy, not a NAT64 implementation.

It's easy to check whether the expected IPv6 functionality works: connect to a published port on ::1 from a local process:

$ docker run --rm -d --name web_1 -p 8080:80 webapp
3398eae2649a55d2b9aa11c4979a50e025c035e34227537f4f4bd91c3ba44c9f
$ curl http://[::1]:8080/

<b>Hostname:</b> 3398eae2649a<br/>
<b>Remote IP:</b> 172.17.0.1

Published port 8080 is accessible through IPv6

As expected, the remote IP address seen by the web server running in a Docker container (our Flask application) is the source IPv4 address of the outgoing docker-proxy session.

If you want to see IPv6 and IPv4 TCP sessions going through the docker-proxy process, use telnet to connect to a published port and netstat to display the established sessions:

$ netstat -nt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address    Foreign Address State
tcp        0      0 172.17.0.1:52880 172.17.0.2:80   ESTABLISHED
tcp6       0      0 ::1:8080         ::1:41534       ESTABLISHED
tcp6       0      0 ::1:41534        ::1:8080        ESTABLISHED

Incoming IPv6 connection mapped into an outgoing IPv4 connection

IPv6 access to published ports is obviously not limited to the local processes; IPv6 clients can reach a container-based service as soon as you have an IPv6 address configured on an external interface.

$ ip addr show dev eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc...
    link/ether 08:00:27:9b:8a:66 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.2/24 brd 192.168.33.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 2001:db8::2/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe9b:8a66/64 scope link
       valid_lft forever preferred_lft forever

IPv6 address configured on eth1 interface of Docker host

worker-1$ curl http://[2001:db8::2]:8080/

<b>Hostname:</b> 3398eae2649a<br/>
<b>Remote IP:</b> 172.17.0.1

IPv6 access from an external client

Limitations

While the Docker proxy enables convenient IPv6 connectivity to published container ports, it does not provide the true client identity information - it's a simple TCP proxy and thus does not add HTTP headers or any other indication what the actual client IPv6 address might be.

You can easily check that claim with our Flask application, which returns original HTTP headers when invoked with the /headers path. You'll see the headers generated by the web client, but no extra headers like X-Forwarded-For a typical web proxy might insert.

$ curl http://[2001:db8::2]:8080/headers
User-Agent: curl/7.58.0
Host: [2001:db8::2]:8080
Accept: */*

HTTP headers received by the web server


More Information

If you're new to Docker, start with Introduction to Docker webinar; if you'd like to learn more about Docker networking, explore the Docker Networking Deep Dive webinar. Both webinars are part of ipSpace.net subscription.

The source code for all the examples used in this article is available on Github.

All printouts in this article were created on a Ubuntu host running Docker engine version 19.03.12.