eclipse / paho.mqtt.java

Eclipse Paho Java MQTT client library. Paho is an Eclipse IoT project.
https://eclipse.org/paho
Other
2.13k stars 886 forks source link

Improve the handling of multiple IP addresses when returned in a DNS Lookup #475

Open jonquark opened 6 years ago

jonquark commented 6 years ago

This is not a bug (but I think it applies to all current branches).

When the client connects to a server, under the covers a DNS lookup is performed. At the moment if multiple IP addresses are returned, the client will attempt to connect to just one.

If we cannot connect to that address, if the other addresses were attempted in turn as well then the client would be more fault tolerant of a server with multiple IP addresses.

One subtlety is the interaction of this feature with the HA feature. I propose that if two server names are provided, each of which resolves to multiple IP addresses then all the addresses of server1 would be attempted before attempting to connect to server2.

Here are a couple of links I have found describing handling connections to multiple IP addresses: https://bugs.openjdk.java.net/browse/JDK-8051854 https://stackoverflow.com/questions/4648803/java-outgoing-tcp-connection-failover-based-on-multiple-dns-results

danielperna84 commented 6 years ago

A practical example that caused me some headaches and I believe woulde be solved by fixing this: My router (AVM FritzBox) has an integrated DynDNS feature provided by the manufacturer. Hence with every reconnect my hostname will be updated with the new public IP. The problem however is, that I have native IPv4 and IPv6. As a result the DNS A record has my public IPv4 address, and the DNS AAAA has the IP of the router. The problem is, that my MQTT broker of course isn't running on the router, but instead on a Pi for which I have port forwarding for IPv4 enabled. For IPv6 there is no port forwarding, so for this case the firewall (just the port to be more precise) of the router is opened up to the globally valid IPv6 address of my Pi. This however does not match the IPv6 address associated with my hostname (which has the IPv6 of the router).

Technically all this is working as intended. It would be my responsibility to modify the AAAA record to point to my Pi if I wanted this to work. But sadly my router does not allow this.

In practice the problem that I currently have is that I can't connect to my broker from IPv6 enabled devices because the hosename resolves to 2 addresses. Since IPv6 generally is available and functional on my smartphone, the MQTT client chooses to connect to the IPv6 address. But that's my router, not the Pi. So the solutions for that would be:

hostops commented 1 year ago

This is awful for IPv6-only devices and dualstack servers. https://stackoverflow.com/questions/11974232/return-ipv6-in-java This -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=true solves the issue for one device. But the solution should work for every device. We have a lot of IOT devices, and we do not know what kind of IP addresses will be provided by mobile network providers.

hostops commented 1 year ago

You can use opts.setServerURIs() to use working IP/host out of many. This is how I overcame my problem in IPv6 only network.

Because you cannot use IPv6 literals (https://github.com/eclipse/paho.mqtt.java/issues/322):

This code solved my issue:

            URI uri = new URI(mqttProperties.getUri());
            String[] serverURIs = Stream.concat(
                            Arrays.stream(InetAddress.getAllByName(uri.getHost()))
                                    .filter(inetAddress -> inetAddress instanceof Inet4Address)
                                    .map(InetAddress::getHostAddress), // IPv4 addresses
                            Stream.of(uri.getHost())) // Hostname - now preferres IPv6
                    .map(host -> {
                        try {
                            return new URI(uri.getScheme(), uri.getUserInfo(), host, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()).toString();
                        } catch (URISyntaxException e) {
                            throw new RuntimeException(e);
                        }
                    }).toArray(String[]::new);
            opts.setServerURIs(serverURIs); // overrides serverURI constructor parameter