speed up DNS

Daniel Gultsch created

run queries in parallel
decrease timeout
do not fall back to google

Change summary

src/main/java/eu/siacs/conversations/entities/Account.java               |   5 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |   4 
src/main/java/eu/siacs/conversations/utils/Resolver.java                 | 625 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java            |   2 
src/main/res/values/strings.xml                                          |   1 
5 files changed, 335 insertions(+), 302 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Account.java 🔗

@@ -132,8 +132,7 @@ public class Account extends AbstractEntity {
 		STREAM_ERROR,
 		POLICY_VIOLATION,
 		PAYMENT_REQUIRED,
-		MISSING_INTERNET_PERMISSION(false),
-		NETWORK_IS_UNREACHABLE(false);
+		MISSING_INTERNET_PERMISSION(false);
 
 		private final boolean isError;
 		private final boolean attemptReconnect;
@@ -211,8 +210,6 @@ public class Account extends AbstractEntity {
 					return R.string.payment_required;
 				case MISSING_INTERNET_PERMISSION:
 					return R.string.missing_internet_permission;
-				case NETWORK_IS_UNREACHABLE:
-					return R.string.network_is_unreachable;
 				default:
 					return R.string.account_status_unknown;
 			}

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -3846,7 +3846,7 @@ public class XmppConnectionService extends Service {
 						ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult(response);
 						if (presence.getVer().equals(discoveryResult.getVer())) {
 							databaseBackend.insertDiscoveryResult(discoveryResult);
-							injectServiceDiscorveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult);
+							injectServiceDiscoveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult);
 						} else {
 							Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer());
 						}
@@ -3857,7 +3857,7 @@ public class XmppConnectionService extends Service {
 		}
 	}
 
-	private void injectServiceDiscorveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) {
+	private void injectServiceDiscoveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) {
 		for (Contact contact : roster.getContacts()) {
 			for (Presence presence : contact.getPresences().getPresences().values()) {
 				if (hash.equals(presence.getHash()) && ver.equals(presence.getVer())) {

src/main/java/eu/siacs/conversations/utils/Resolver.java 🔗

@@ -6,14 +6,15 @@ import android.support.annotation.NonNull;
 import android.util.Log;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 
+import de.measite.minidns.AbstractDNSClient;
 import de.measite.minidns.DNSClient;
 import de.measite.minidns.DNSName;
 import de.measite.minidns.Question;
@@ -23,309 +24,347 @@ import de.measite.minidns.dnsserverlookup.AndroidUsingExec;
 import de.measite.minidns.hla.DnssecResolverApi;
 import de.measite.minidns.hla.ResolverApi;
 import de.measite.minidns.hla.ResolverResult;
+import de.measite.minidns.iterative.ReliableDNSClient;
 import de.measite.minidns.record.A;
 import de.measite.minidns.record.AAAA;
 import de.measite.minidns.record.CNAME;
 import de.measite.minidns.record.Data;
 import de.measite.minidns.record.InternetAddressRR;
 import de.measite.minidns.record.SRV;
-import de.measite.minidns.util.MultipleIoException;
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.services.XmppConnectionService;
 
 public class Resolver {
 
-	private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
-	private static final String STARTTLS_SERICE = "_xmpp-client";
-
-	private static final String NETWORK_IS_UNREACHABLE = "Network is unreachable";
-
-	private static XmppConnectionService SERVICE = null;
-
-
-	public static void init(XmppConnectionService service) {
-		Resolver.SERVICE = service;
-		DNSClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE);
-		DNSClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE);
-		DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service));
-	}
-
-	public static List<Result> resolve(String domain) throws NetworkIsUnreachableException {
-		List<Result> results = new ArrayList<>();
-		HashSet<String> messages = new HashSet<>();
-		try {
-			results.addAll(resolveSrv(domain, true));
-		} catch (MultipleIoException e) {
-			messages.addAll(extractMessages(e));
-		} catch (Throwable throwable) {
-			Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable);
-		}
-		try {
-			results.addAll(resolveSrv(domain, false));
-		} catch (MultipleIoException e) {
-			messages.addAll(extractMessages(e));
-		} catch (Throwable throwable) {
-			Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable);
-		}
-		if (results.size() == 0) {
-			if (messages.size() == 1 && messages.contains(NETWORK_IS_UNREACHABLE)) {
-				throw new NetworkIsUnreachableException();
-			}
-			results.addAll(resolveNoSrvRecords(DNSName.from(domain), true));
-		}
-		Collections.sort(results);
-		Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results.toString());
-		return results;
-	}
-
-	private static HashSet<String> extractMessages(MultipleIoException e) {
-		HashSet<String> messages = new HashSet<>();
-		for (Exception inner : e.getExceptions()) {
-			if (inner instanceof MultipleIoException) {
-				messages.addAll(extractMessages((MultipleIoException) inner));
-			} else {
-				messages.add(inner.getMessage());
-			}
-		}
-		return messages;
-	}
-
-	private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
-		if (Thread.currentThread().isInterrupted()) {
-			return Collections.emptyList();
-		}
-		DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE) + "._tcp." + domain);
-		ResolverResult<SRV> result = resolveWithFallback(dnsName, SRV.class);
-		List<Result> results = new ArrayList<>();
-		for (SRV record : result.getAnswersOrEmptySet()) {
-			if (record.name.length() == 0 && record.priority == 0) {
-				continue;
-			}
-			final boolean addedIPv4 = results.addAll(resolveIp(record, A.class, result.isAuthenticData(), directTls));
-			results.addAll(resolveIp(record, AAAA.class, result.isAuthenticData(), directTls));
-			if (!addedIPv4 && !Thread.currentThread().isInterrupted()) {
-				Result resolverResult = Result.fromRecord(record, directTls);
-				resolverResult.authenticated = resolverResult.isAuthenticated();
-				results.add(resolverResult);
-			}
-		}
-		return results;
-	}
-
-	private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
-		if (Thread.currentThread().isInterrupted()) {
-			return Collections.emptyList();
-		}
-		List<Result> list = new ArrayList<>();
-		try {
-			ResolverResult<D> results = resolveWithFallback(srv.name, type, authenticated);
-			for (D record : results.getAnswersOrEmptySet()) {
-				Result resolverResult = Result.fromRecord(srv, directTls);
-				resolverResult.authenticated = results.isAuthenticData() && authenticated;
-				resolverResult.ip = record.getInetAddress();
-				list.add(resolverResult);
-			}
-		} catch (Throwable t) {
-			Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage());
-		}
-		return list;
-	}
-
-	private static List<Result> resolveNoSrvRecords(DNSName dnsName, boolean withCnames) {
-		List<Result> results = new ArrayList<>();
-		try {
-			for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) {
-				results.add(Result.createDefault(dnsName, a.getInetAddress()));
-			}
-			for (AAAA aaaa : resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) {
-				results.add(Result.createDefault(dnsName, aaaa.getInetAddress()));
-			}
-			if (results.size() == 0 && withCnames) {
-				for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
-					results.addAll(resolveNoSrvRecords(cname.name, false));
-				}
-			}
-		} catch (Throwable throwable) {
-			Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable);
-		}
-		results.add(Result.createDefault(dnsName));
-		return results;
-	}
-
-	private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type) throws IOException {
-		return resolveWithFallback(dnsName, type, validateHostname());
-	}
-
-	private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type, boolean validateHostname) throws IOException {
-		final Question question = new Question(dnsName, Record.TYPE.getType(type));
-		if (!validateHostname) {
-			return ResolverApi.INSTANCE.resolve(question);
-		}
-		try {
-			return DnssecResolverApi.INSTANCE.resolveDnssecReliable(question);
-		} catch (DNSSECResultNotAuthenticException e) {
-			Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", e);
-		} catch (IOException e) {
-			throw e;
-		} catch (Throwable throwable) {
-			Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable);
-		}
-		return ResolverApi.INSTANCE.resolve(question);
-	}
-
-	private static boolean validateHostname() {
-		return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname);
-	}
-
-	public static class Result implements Comparable<Result> {
-		@Override
-		public boolean equals(Object o) {
-			if (this == o) return true;
-			if (o == null || getClass() != o.getClass()) return false;
-
-			Result result = (Result) o;
-
-			if (port != result.port) return false;
-			if (directTls != result.directTls) return false;
-			if (authenticated != result.authenticated) return false;
-			if (priority != result.priority) return false;
-			if (ip != null ? !ip.equals(result.ip) : result.ip != null) return false;
-			return hostname != null ? hostname.equals(result.hostname) : result.hostname == null;
-		}
-
-		@Override
-		public int hashCode() {
-			int result = ip != null ? ip.hashCode() : 0;
-			result = 31 * result + (hostname != null ? hostname.hashCode() : 0);
-			result = 31 * result + port;
-			result = 31 * result + (directTls ? 1 : 0);
-			result = 31 * result + (authenticated ? 1 : 0);
-			result = 31 * result + priority;
-			return result;
-		}
-
-		public static final String DOMAIN = "domain";
-
-		public static final String IP = "ip";
-		public static final String HOSTNAME = "hostname";
-		public static final String PORT = "port";
-		public static final String PRIORITY = "priority";
-		public static final String DIRECT_TLS = "directTls";
-		public static final String AUTHENTICATED = "authenticated";
-
-		private InetAddress ip;
-		private DNSName hostname;
-		private int port = 5222;
-		private boolean directTls = false;
-		private boolean authenticated = false;
-		private int priority;
-
-		public InetAddress getIp() {
-			return ip;
-		}
-
-		public int getPort() {
-			return port;
-		}
-
-		public DNSName getHostname() {
-			return hostname;
-		}
-
-		public boolean isDirectTls() {
-			return directTls;
-		}
-
-		public boolean isAuthenticated() {
-			return authenticated;
-		}
-
-		@Override
-		public String toString() {
-			return "Result{" +
-					"ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' +
-					", hostame='" + hostname.toString() + '\'' +
-					", port=" + port +
-					", directTls=" + directTls +
-					", authenticated=" + authenticated +
-					", priority=" + priority +
-					'}';
-		}
-
-		@Override
-		public int compareTo(@NonNull Result result) {
-			if (result.priority == priority) {
-				if (directTls == result.directTls) {
-					if (ip == null && result.ip == null) {
-						return 0;
-					} else if (ip != null && result.ip != null) {
-						if (ip instanceof Inet4Address && result.ip instanceof Inet4Address) {
-							return 0;
-						} else {
-							return ip instanceof Inet4Address ? -1 : 1;
-						}
-					} else {
-						return ip != null ? -1 : 1;
-					}
-				} else {
-					return directTls ? -1 : 1;
-				}
-			} else {
-				return priority - result.priority;
-			}
-		}
-
-		public static Result fromRecord(SRV srv, boolean directTls) {
-			Result result = new Result();
-			result.port = srv.port;
-			result.hostname = srv.name;
-			result.directTls = directTls;
-			result.priority = srv.priority;
-			return result;
-		}
-
-		public static Result createDefault(DNSName hostname, InetAddress ip) {
-			Result result = new Result();
-			result.port = 5222;
-			result.hostname = hostname;
-			result.ip = ip;
-			return result;
-		}
-
-		public static Result createDefault(DNSName hostname) {
-			return createDefault(hostname, null);
-		}
-
-		public static Result fromCursor(Cursor cursor) {
-			final Result result = new Result();
-			try {
-				result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
-			} catch (UnknownHostException e) {
-				result.ip = null;
-			}
-			result.hostname = DNSName.from(cursor.getString(cursor.getColumnIndex(HOSTNAME)));
-			result.port = cursor.getInt(cursor.getColumnIndex(PORT));
-			result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
-			result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
-			result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
-			return result;
-		}
-
-		public ContentValues toContentValues() {
-			final ContentValues contentValues = new ContentValues();
-			contentValues.put(IP, ip == null ? null : ip.getAddress());
-			contentValues.put(HOSTNAME, hostname.toString());
-			contentValues.put(PORT, port);
-			contentValues.put(PRIORITY, priority);
-			contentValues.put(DIRECT_TLS, directTls ? 1 : 0);
-			contentValues.put(AUTHENTICATED, authenticated ? 1 : 0);
-			return contentValues;
-		}
-	}
-
-	public static class NetworkIsUnreachableException extends Exception {
-
-	}
+    private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
+    private static final String STARTTLS_SERICE = "_xmpp-client";
+
+    private static XmppConnectionService SERVICE = null;
+
+
+    public static void init(XmppConnectionService service) {
+        Resolver.SERVICE = service;
+        DNSClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE);
+        DNSClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE);
+        DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service));
+        final AbstractDNSClient client = ResolverApi.INSTANCE.getClient();
+        if (client instanceof ReliableDNSClient) {
+            disableHardcodedDnsServers((ReliableDNSClient) client);
+        }
+    }
+
+    private static void disableHardcodedDnsServers(ReliableDNSClient reliableDNSClient) {
+        try {
+            final Field dnsClientField = ReliableDNSClient.class.getDeclaredField("dnsClient");
+            dnsClientField.setAccessible(true);
+            final DNSClient dnsClient = (DNSClient) dnsClientField.get(reliableDNSClient);
+            dnsClient.getDataSource().setTimeout(3000);
+            final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers");
+            useHardcodedDnsServers.setAccessible(true);
+            useHardcodedDnsServers.setBoolean(dnsClient, false);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e);
+        }
+    }
+
+    public static List<Result> resolve(String domain) {
+        final List<Result> results = new ArrayList<>();
+        final List<Result> fallbackResults = new ArrayList<>();
+        Thread[] threads = new Thread[3];
+        threads[0] = new Thread(() -> {
+            try {
+                final List<Result> list = resolveSrv(domain, true);
+                synchronized (results) {
+                    results.addAll(list);
+                }
+            } catch (Throwable throwable) {
+                Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable);
+            }
+        });
+        threads[1] = new Thread(() -> {
+            try {
+                final List<Result> list = resolveSrv(domain, false);
+                synchronized (results) {
+                    results.addAll(list);
+                }
+            } catch (Throwable throwable) {
+                Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable);
+            }
+        });
+        threads[2] = new Thread(() -> {
+            List<Result> list = resolveNoSrvRecords(DNSName.from(domain), true);
+            synchronized (fallbackResults) {
+                fallbackResults.addAll(list);
+            }
+        });
+        for (Thread thread : threads) {
+            thread.start();
+        }
+        try {
+            threads[0].join();
+            threads[1].join();
+            if (results.size() > 0) {
+                threads[2].interrupt();
+                Collections.sort(results);
+                Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results.toString());
+                return results;
+            } else {
+                threads[2].join();
+                Collections.sort(fallbackResults);
+                Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults.toString());
+                return fallbackResults;
+            }
+        } catch (InterruptedException e) {
+            return results;
+        }
+    }
+
+    private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
+        DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE) + "._tcp." + domain);
+        ResolverResult<SRV> result = resolveWithFallback(dnsName, SRV.class);
+        final List<Result> results = new ArrayList<>();
+        final List<Thread> threads = new ArrayList<>();
+        for (SRV record : result.getAnswersOrEmptySet()) {
+            if (record.name.length() == 0 && record.priority == 0) {
+                continue;
+            }
+            threads.add(new Thread(() -> {
+                final List<Result> ipv4s = resolveIp(record, A.class, result.isAuthenticData(), directTls);
+                if (ipv4s.size() == 0) {
+                    Result resolverResult = Result.fromRecord(record, directTls);
+                    resolverResult.authenticated = resolverResult.isAuthenticated();
+                    ipv4s.add(resolverResult);
+                }
+                synchronized (results) {
+                    results.addAll(ipv4s);
+                }
+
+            }));
+            threads.add(new Thread(() -> {
+                final List<Result> ipv6s = resolveIp(record, AAAA.class, result.isAuthenticData(), directTls);
+                synchronized (results) {
+                    results.addAll(ipv6s);
+                }
+            }));
+        }
+        for (Thread thread : threads) {
+            thread.start();
+        }
+        for (Thread thread : threads) {
+            try {
+                thread.join();
+            } catch (InterruptedException e) {
+                return Collections.emptyList();
+            }
+        }
+        return results;
+    }
+
+    private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
+        List<Result> list = new ArrayList<>();
+        try {
+            ResolverResult<D> results = resolveWithFallback(srv.name, type, authenticated);
+            for (D record : results.getAnswersOrEmptySet()) {
+                Result resolverResult = Result.fromRecord(srv, directTls);
+                resolverResult.authenticated = results.isAuthenticData() && authenticated;
+                resolverResult.ip = record.getInetAddress();
+                list.add(resolverResult);
+            }
+        } catch (Throwable t) {
+            Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage());
+        }
+        return list;
+    }
+
+    private static List<Result> resolveNoSrvRecords(DNSName dnsName, boolean withCnames) {
+        List<Result> results = new ArrayList<>();
+        try {
+            for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) {
+                results.add(Result.createDefault(dnsName, a.getInetAddress()));
+            }
+            for (AAAA aaaa : resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) {
+                results.add(Result.createDefault(dnsName, aaaa.getInetAddress()));
+            }
+            if (results.size() == 0 && withCnames) {
+                for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
+                    results.addAll(resolveNoSrvRecords(cname.name, false));
+                }
+            }
+        } catch (Throwable throwable) {
+            Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable);
+        }
+        results.add(Result.createDefault(dnsName));
+        return results;
+    }
+
+    private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type) throws IOException {
+        return resolveWithFallback(dnsName, type, validateHostname());
+    }
+
+    private static <D extends Data> ResolverResult<D> resolveWithFallback(DNSName dnsName, Class<D> type, boolean validateHostname) throws IOException {
+        final Question question = new Question(dnsName, Record.TYPE.getType(type));
+        if (!validateHostname) {
+            return ResolverApi.INSTANCE.resolve(question);
+        }
+        try {
+            return DnssecResolverApi.INSTANCE.resolveDnssecReliable(question);
+        } catch (DNSSECResultNotAuthenticException e) {
+            Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", e);
+        } catch (IOException e) {
+            throw e;
+        } catch (Throwable throwable) {
+            Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable);
+        }
+        return ResolverApi.INSTANCE.resolve(question);
+    }
+
+    private static boolean validateHostname() {
+        return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname);
+    }
+
+    public static class Result implements Comparable<Result> {
+        public static final String DOMAIN = "domain";
+        public static final String IP = "ip";
+        public static final String HOSTNAME = "hostname";
+        public static final String PORT = "port";
+        public static final String PRIORITY = "priority";
+        public static final String DIRECT_TLS = "directTls";
+        public static final String AUTHENTICATED = "authenticated";
+        private InetAddress ip;
+        private DNSName hostname;
+        private int port = 5222;
+        private boolean directTls = false;
+        private boolean authenticated = false;
+        private int priority;
+
+        static Result fromRecord(SRV srv, boolean directTls) {
+            Result result = new Result();
+            result.port = srv.port;
+            result.hostname = srv.name;
+            result.directTls = directTls;
+            result.priority = srv.priority;
+            return result;
+        }
+
+        static Result createDefault(DNSName hostname, InetAddress ip) {
+            Result result = new Result();
+            result.port = 5222;
+            result.hostname = hostname;
+            result.ip = ip;
+            return result;
+        }
+
+        static Result createDefault(DNSName hostname) {
+            return createDefault(hostname, null);
+        }
+
+        public static Result fromCursor(Cursor cursor) {
+            final Result result = new Result();
+            try {
+                result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
+            } catch (UnknownHostException e) {
+                result.ip = null;
+            }
+            result.hostname = DNSName.from(cursor.getString(cursor.getColumnIndex(HOSTNAME)));
+            result.port = cursor.getInt(cursor.getColumnIndex(PORT));
+            result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
+            result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
+            result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Result result = (Result) o;
+
+            if (port != result.port) return false;
+            if (directTls != result.directTls) return false;
+            if (authenticated != result.authenticated) return false;
+            if (priority != result.priority) return false;
+            if (ip != null ? !ip.equals(result.ip) : result.ip != null) return false;
+            return hostname != null ? hostname.equals(result.hostname) : result.hostname == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = ip != null ? ip.hashCode() : 0;
+            result = 31 * result + (hostname != null ? hostname.hashCode() : 0);
+            result = 31 * result + port;
+            result = 31 * result + (directTls ? 1 : 0);
+            result = 31 * result + (authenticated ? 1 : 0);
+            result = 31 * result + priority;
+            return result;
+        }
+
+        public InetAddress getIp() {
+            return ip;
+        }
+
+        public int getPort() {
+            return port;
+        }
+
+        public DNSName getHostname() {
+            return hostname;
+        }
+
+        public boolean isDirectTls() {
+            return directTls;
+        }
+
+        public boolean isAuthenticated() {
+            return authenticated;
+        }
+
+        @Override
+        public String toString() {
+            return "Result{" +
+                    "ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' +
+                    ", hostame='" + hostname.toString() + '\'' +
+                    ", port=" + port +
+                    ", directTls=" + directTls +
+                    ", authenticated=" + authenticated +
+                    ", priority=" + priority +
+                    '}';
+        }
+
+        @Override
+        public int compareTo(@NonNull Result result) {
+            if (result.priority == priority) {
+                if (directTls == result.directTls) {
+                    if (ip == null && result.ip == null) {
+                        return 0;
+                    } else if (ip != null && result.ip != null) {
+                        if (ip instanceof Inet4Address && result.ip instanceof Inet4Address) {
+                            return 0;
+                        } else {
+                            return ip instanceof Inet4Address ? -1 : 1;
+                        }
+                    } else {
+                        return ip != null ? -1 : 1;
+                    }
+                } else {
+                    return directTls ? -1 : 1;
+                }
+            } else {
+                return priority - result.priority;
+            }
+        }
+
+        public ContentValues toContentValues() {
+            final ContentValues contentValues = new ContentValues();
+            contentValues.put(IP, ip == null ? null : ip.getAddress());
+            contentValues.put(HOSTNAME, hostname.toString());
+            contentValues.put(PORT, port);
+            contentValues.put(PRIORITY, priority);
+            contentValues.put(DIRECT_TLS, directTls ? 1 : 0);
+            contentValues.put(AUTHENTICATED, authenticated ? 1 : 0);
+            return contentValues;
+        }
+    }
 
 }

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -415,8 +415,6 @@ public class XmppConnection implements Runnable {
 			this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION);
 		} catch (final StateChangingException e) {
 			this.changeStatus(e.state);
-		} catch (final Resolver.NetworkIsUnreachableException e) {
-			this.changeStatus(Account.State.NETWORK_IS_UNREACHABLE);
 		} catch (final UnknownHostException | ConnectException e) {
 			this.changeStatus(Account.State.SERVER_NOT_FOUND);
 		} catch (final SocksSocketFactory.SocksProxyNotFoundException e) {

src/main/res/values/strings.xml 🔗

@@ -648,7 +648,6 @@
     <string name="yesterday">Yesterday</string>
     <string name="pref_validate_hostname">Validate hostname with DNSSEC</string>
     <string name="pref_validate_hostname_summary">Server certificates that contain the validated hostname are considered verified</string>
-    <string name="network_is_unreachable">Network is unreachable</string>
     <string name="certificate_does_not_contain_jid">Certificate does not contain a Jabber ID</string>
     <string name="server_info_partial">partial</string>
     <string name="attach_record_video">Record video</string>