Colleague Paul Marks reports:
The canonical algorithm for a TCP client is as follows:
- Call getaddrinfo(hostname), which returns all IP addresses sorted by RFC6724, or some approximation thereof.
- Loop over the addresses, connect() to each, and break on success.
For example: http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#simpleclient
However, Java clients typically use this over-simplified algorithm:
- Call getaddrinfo(hostname), and use -Djava.net.preferIPv6Addresses to place either IPv4 or IPv6 first in line.
- Pick the [0]th address, and connect() or fail.
These are the primary APIs that encourage this behavior:
- Socket(String host, int port)
- InetSocketAddress(String hostname, int port)
- InetAddress.getByName(String host)
The Socket() constructor can be fixed by adding a loop around connect(), as Android did a few years ago. But the latter two purport to resolve a hostname to exactly one IP address, which is an ill-defined operation that should be deprecated.
One case where the algorithm breaks down is when running a client with IPv6-only connectivity using the default JVM flags. Connecting to a dual-stack hostname picks only the IPv4 address, and the connection fails immediately. Setting preferIPv6Addresses=true yields the opposite problem for IPv4-only clients.
The direct solution would be to make Inet6AddressImpl.c preserve getaddrinfo()'s ordering, and ignore "preferIPv6Addresses". This was partially implemented byJDK-8016521, but it did not change the default to "preferIPv6Addresses=system", and in fact such a default would be risky because getaddrinfo() is not always right. There are networks in the wild where the first TCP connect() fails, and fallback to a later address is required. (In the extreme case, some pathological networks only work because of RFC6555 Happy Eyeballs, but let's ignore those for now.) If clients simply switch from "try IPv4 or die" to "try IPv6 or die", then some people are bound to encounter IPv6 connect() failures and complain.
So, what actions do I propose?
1) Remove callers of the above "hostname to one IP address" APIs from OpenJDK, replacing them with getAllByName() and a connect() loop. The Socket(...host...) and SSLSocketImpl(...host...) constructors are good places to begin.
2) Make InetAddress.getAllByName() never modify getaddrinfo()'s ordering.
3) The "hostname to one IP address" methods are forever cursed, but they could be changed to directly follow the preferIPv6Addresses flag, instead of returning getAllByName()[0]. This would maintain compatibility for most legacy code, while allowing getAllByName() to expose proper address selection by default.
The canonical algorithm for a TCP client is as follows:
- Call getaddrinfo(hostname), which returns all IP addresses sorted by RFC6724, or some approximation thereof.
- Loop over the addresses, connect() to each, and break on success.
For example: http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#simpleclient
However, Java clients typically use this over-simplified algorithm:
- Call getaddrinfo(hostname), and use -Djava.net.preferIPv6Addresses to place either IPv4 or IPv6 first in line.
- Pick the [0]th address, and connect() or fail.
These are the primary APIs that encourage this behavior:
- Socket(String host, int port)
- InetSocketAddress(String hostname, int port)
- InetAddress.getByName(String host)
The Socket() constructor can be fixed by adding a loop around connect(), as Android did a few years ago. But the latter two purport to resolve a hostname to exactly one IP address, which is an ill-defined operation that should be deprecated.
One case where the algorithm breaks down is when running a client with IPv6-only connectivity using the default JVM flags. Connecting to a dual-stack hostname picks only the IPv4 address, and the connection fails immediately. Setting preferIPv6Addresses=true yields the opposite problem for IPv4-only clients.
The direct solution would be to make Inet6AddressImpl.c preserve getaddrinfo()'s ordering, and ignore "preferIPv6Addresses". This was partially implemented by
So, what actions do I propose?
1) Remove callers of the above "hostname to one IP address" APIs from OpenJDK, replacing them with getAllByName() and a connect() loop. The Socket(...host...) and SSLSocketImpl(...host...) constructors are good places to begin.
2) Make InetAddress.getAllByName() never modify getaddrinfo()'s ordering.
3) The "hostname to one IP address" methods are forever cursed, but they could be changed to directly follow the preferIPv6Addresses flag, instead of returning getAllByName()[0]. This would maintain compatibility for most legacy code, while allowing getAllByName() to expose proper address selection by default.
- duplicates
-
JDK-8313356 Improve address selection in sun.net.NetworkClient
- Closed
-
JDK-8257080 Java does not try all DNS results when opening a socket
- Closed
- relates to
-
JDK-8313356 Improve address selection in sun.net.NetworkClient
- Closed
-
JDK-8179037 Improve IPv6 support
- Draft