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:

Published port 8080 is accessible through IPv6
$ docker run --rm -d --name web_1 -p 8080:80 webapp
$ curl http://[::1]:8080/

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

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:

Incoming IPv6 connection mapped into an outgoing IPv4 connection
$ netstat -nt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address    Foreign Address State
tcp        0      0   ESTABLISHED
tcp6       0      0 ::1:8080         ::1:41534       ESTABLISHED
tcp6       0      0 ::1:41534        ::1:8080        ESTABLISHED

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.

IPv6 address configured on eth1 interface of Docker host
$ 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 brd 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 access from an external client
worker-1$ curl http://[2001:db8::2]:8080/

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


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.

HTTP headers received by the web server
$ curl http://[2001:db8::2]:8080/headers
User-Agent: curl/7.58.0
Host: [2001:db8::2]:8080
Accept: */*

More Information