diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 02ac484da..aab2d31e5 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -63,7 +63,7 @@ import rocks.xmpp.addr.Jid; public class DatabaseBackend extends SQLiteOpenHelper { private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 46; + private static final int DATABASE_VERSION = 47; private static DatabaseBackend instance = null; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -160,6 +160,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Resolver.Result.DIRECT_TLS + " NUMBER," + Resolver.Result.AUTHENTICATED + " NUMBER," + Resolver.Result.PORT + " NUMBER," + + Resolver.Result.TIME_REQUESTED + " NUMBER," + "UNIQUE(" + Resolver.Result.DOMAIN + ") ON CONFLICT REPLACE" + ");"; @@ -513,10 +514,6 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.MARKABLE + " NUMBER DEFAULT 0"); } - if (oldVersion < 39 && newVersion >= 39) { - db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); - } - if (oldVersion < 41 && newVersion >= 41) { db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); @@ -555,6 +552,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { final long diff = SystemClock.elapsedRealtime() - start; Log.d(Config.LOGTAG,"deleted old edit information in "+diff+"ms"); } + + if (oldVersion < 47 && newVersion >= 47) { + // values in resolver_result are cache and not worth to store + db.execSQL("DROP TABLE IF EXISTS " + RESOLVER_RESULTS_TABLENAME); + db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); + } + } private void canonicalizeJids(SQLiteDatabase db) { diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index 247e77fae..7c1ff5837 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -7,11 +7,17 @@ import android.util.Log; import java.io.IOException; import java.lang.reflect.Field; -import java.net.Inet4Address; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.List; import de.measite.minidns.AbstractDNSClient; @@ -33,6 +39,7 @@ import de.measite.minidns.record.InternetAddressRR; import de.measite.minidns.record.SRV; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.XmppConnectionService; public class Resolver { @@ -40,7 +47,7 @@ public class Resolver { public static final int DEFAULT_PORT_XMPP = 5222; private static final String DIRECT_TLS_SERVICE = "_xmpps-client"; - private static final String STARTTLS_SERICE = "_xmpp-client"; + private static final String STARTTLS_SERVICE = "_xmpp-client"; private static XmppConnectionService SERVICE = null; @@ -74,13 +81,13 @@ public class Resolver { } } - public static List fromHardCoded(String hostname, int port) { - Result result = new Result(); - result.hostname = DNSName.from(hostname); - result.port = port; - result.directTls = useDirectTls(port); - result.authenticated = true; - return Collections.singletonList(result); + public static Result fromHardCoded(String hostname, int port) { + final Result ipResult = fromIpAddress(hostname, port); + if (ipResult != null) { + ipResult.connect(); + return ipResult; + } + return happyEyeball(resolveNoSrvRecords(DNSName.from(hostname), port, true)); } @@ -88,10 +95,11 @@ public class Resolver { return port == 443 || port == 5223; } - public static List resolve(String domain) { - final List ipResults = fromIpAddress(domain); - if (ipResults.size() > 0) { - return ipResults; + public static Result resolve(String domain) { + final Result ipResult = fromIpAddress(domain, DEFAULT_PORT_XMPP); + if (ipResult != null) { + ipResult.connect(); + return ipResult; } final List results = new ArrayList<>(); final List fallbackResults = new ArrayList<>(); @@ -117,7 +125,7 @@ public class Resolver { } }); threads[2] = new Thread(() -> { - List list = resolveNoSrvRecords(DNSName.from(domain), true); + List list = resolveNoSrvRecords(DNSName.from(domain), DEFAULT_PORT_XMPP, true); synchronized (fallbackResults) { fallbackResults.addAll(list); } @@ -132,66 +140,80 @@ public class Resolver { threads[2].interrupt(); synchronized (results) { Collections.sort(results); - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results.toString()); - return new ArrayList<>(results); + return happyEyeball(results); } } else { threads[2].join(); synchronized (fallbackResults) { Collections.sort(fallbackResults); - Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults.toString()); - return new ArrayList<>(fallbackResults); + return happyEyeball(fallbackResults); } } } catch (InterruptedException e) { for (Thread thread : threads) { thread.interrupt(); } - return Collections.emptyList(); + return null; } } - private static List fromIpAddress(String domain) { + private static Result fromIpAddress(String domain, int port) { if (!IP.matches(domain)) { - return Collections.emptyList(); + return null; } try { Result result = new Result(); result.ip = InetAddress.getByName(domain); - result.port = DEFAULT_PORT_XMPP; - return Collections.singletonList(result); + result.port = port; + result.authenticated = true; + return result; } catch (UnknownHostException e) { - return Collections.emptyList(); + return null; } } private static List resolveSrv(String domain, final boolean directTls) throws IOException { - DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE) + "._tcp." + domain); + DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain); ResolverResult result = resolveWithFallback(dnsName, SRV.class); final List results = new ArrayList<>(); final List threads = new ArrayList<>(); + + final List fallbackResults = new ArrayList<>(); + final List fallbackThreads = new ArrayList<>(); for (SRV record : result.getAnswersOrEmptySet()) { - if (record.name.length() == 0 && record.priority == 0) { + if (record.name.length() == 0) { continue; } - threads.add(new Thread(() -> { - final List ipv4s = resolveIp(record, A.class, result.isAuthenticData(), directTls); - if (ipv4s.size() == 0) { - Result resolverResult = Result.fromRecord(record, directTls); - resolverResult.authenticated = result.isAuthenticData(); - ipv4s.add(resolverResult); - } - synchronized (results) { - results.addAll(ipv4s); - } - - })); threads.add(new Thread(() -> { final List ipv6s = resolveIp(record, AAAA.class, result.isAuthenticData(), directTls); synchronized (results) { results.addAll(ipv6s); } })); + threads.add(new Thread(() -> { + final List ipv4s = resolveIp(record, A.class, result.isAuthenticData(), directTls); + synchronized (results) { + results.addAll(ipv4s); + } + })); + fallbackThreads.add(new Thread(() -> { + try { + ResolverResult cnames = resolveWithFallback(record.name, CNAME.class, result.isAuthenticData()); + for (CNAME cname : cnames.getAnswersOrEmptySet()) { + final List ipv6s = resolveIp(record, cname.name, AAAA.class, cnames.isAuthenticData(), directTls); + synchronized (fallbackResults) { + fallbackResults.addAll(ipv6s); + } + final List ipv4s = resolveIp(record, cname.name, A.class, cnames.isAuthenticData(), directTls); + synchronized (results) { + fallbackResults.addAll(ipv4s); + } + } + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "cname in srv (agains RFC2782) - run slow fallback"); + } catch (Throwable throwable) { + Log.i(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving srv cname-fallback records", throwable); + } + })); } for (Thread thread : threads) { thread.start(); @@ -203,13 +225,30 @@ public class Resolver { return Collections.emptyList(); } } - return results; + if (results.size() > 0) { + return results; + } + + for (Thread thread : fallbackThreads) { + thread.start(); + } + for (Thread thread : fallbackThreads) { + try { + thread.join(); + } catch (InterruptedException e) { + return Collections.emptyList(); + } + } + return fallbackResults; } private static List resolveIp(SRV srv, Class type, boolean authenticated, boolean directTls) { + return resolveIp(srv, srv.name, type, authenticated, directTls); + } + private static List resolveIp(SRV srv, DNSName hostname, Class type, boolean authenticated, boolean directTls) { List list = new ArrayList<>(); try { - ResolverResult results = resolveWithFallback(srv.name, type, authenticated); + ResolverResult results = resolveWithFallback(hostname, type, authenticated); for (D record : results.getAnswersOrEmptySet()) { Result resolverResult = Result.fromRecord(srv, directTls); resolverResult.authenticated = results.isAuthenticData() && authenticated; //TODO technically it doesn’t matter if the IP was authenticated @@ -222,24 +261,23 @@ public class Resolver { return list; } - private static List resolveNoSrvRecords(DNSName dnsName, boolean withCnames) { + private static List resolveNoSrvRecords(DNSName dnsName, int port, boolean withCnames) { List 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())); + results.add(Result.createDefault(dnsName, aaaa.getInetAddress(), port)); + } + for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) { + results.add(Result.createDefault(dnsName, a.getInetAddress(), port)); } if (results.size() == 0 && withCnames) { for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) { - results.addAll(resolveNoSrvRecords(cname.name, false)); + results.addAll(resolveNoSrvRecords(cname.name, port, false)); } } } catch (Throwable throwable) { Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable); } - results.add(Result.createDefault(dnsName)); return results; } @@ -264,11 +302,57 @@ public class Resolver { return ResolverApi.INSTANCE.resolve(question); } + private static Result happyEyeball(List r) { + String logID = Long.toHexString(Double.doubleToLongBits(Math.random())); + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": happy eyeball (" + logID + ") with " + r.toString()); + if (r.size() == 0) return null; + + Result result; + if (r.size() == 1) { + result = r.get(0); + result.setLogID(logID); + result.connect(); + return result; + } + + for (Result res : r) { + res.setLogID(logID); + } + + ExecutorService executor = Executors.newFixedThreadPool(4); + + try { + result = executor.invokeAny(r); + executor.shutdown(); + Thread disconnector = new Thread(() -> { + while (true) { + try { + if (executor.awaitTermination(5, TimeUnit.SECONDS)) break; + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": happy eyeball (" + logID + ") wait for cleanup ..."); + } catch (InterruptedException e) {} + } + Log.i(Config.LOGTAG, Resolver.class.getSimpleName() + ": happy eyeball (" + logID + ") cleanup"); + for (Result re : r) { + if(!re.equals(result)) re.disconnect(); + } + }); + disconnector.start(); + Log.i(Config.LOGTAG, Resolver.class.getSimpleName() + ": happy eyeball (" + logID + ") used: " + result.toString()); + return result; + } catch (InterruptedException e) { + Log.e(Config.LOGTAG, Resolver.class.getSimpleName() + ": happy eyeball (" + logID + ") failed: ", e); + return null; + } catch (ExecutionException e) { + Log.i(Config.LOGTAG, Resolver.class.getSimpleName() + ": happy eyeball (" + logID + ") unable to connect to one address"); + return null; + } + } + private static boolean validateHostname() { return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname); } - public static class Result implements Comparable { + public static class Result implements Comparable, Callable { public static final String DOMAIN = "domain"; public static final String IP = "ip"; public static final String HOSTNAME = "hostname"; @@ -276,50 +360,38 @@ public class Resolver { public static final String PRIORITY = "priority"; public static final String DIRECT_TLS = "directTls"; public static final String AUTHENTICATED = "authenticated"; + public static final String TIME_REQUESTED = "time_requested"; + private InetAddress ip; private DNSName hostname; private int port = DEFAULT_PORT_XMPP; private boolean directTls = false; private boolean authenticated = false; private int priority; + private long timeRequested; + private Socket socket; + + private String logID = ""; static Result fromRecord(SRV srv, boolean directTls) { Result result = new Result(); + result.timeRequested = System.currentTimeMillis(); result.port = srv.port; result.hostname = srv.name; result.directTls = directTls; result.priority = srv.priority; return result; } - - static Result createDefault(DNSName hostname, InetAddress ip) { + + static Result createDefault(DNSName hostname, InetAddress ip, int port) { Result result = new Result(); - result.port = DEFAULT_PORT_XMPP; + result.timeRequested = System.currentTimeMillis(); + result.port = port; 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; - } - final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME)); - result.hostname = hostname == null ? null : DNSName.from(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; @@ -346,14 +418,6 @@ public class Resolver { return result; } - public InetAddress getIp() { - return ip; - } - - public int getPort() { - return port; - } - public DNSName getHostname() { return hostname; } @@ -366,11 +430,19 @@ public class Resolver { return authenticated; } + public boolean isOutdated() { + return (System.currentTimeMillis() - timeRequested) > 300_000; + } + + public Socket getSocket() { + return socket; + } + @Override public String toString() { return "Result{" + "ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' + - ", hostame='" + (hostname == null ? null : hostname.toString()) + '\'' + + ", hostname='" + (hostname == null ? null : hostname.toString()) + '\'' + ", port=" + port + ", directTls=" + directTls + ", authenticated=" + authenticated + @@ -378,21 +450,47 @@ public class Resolver { '}'; } + public void connect() { + if (this.socket != null) { + this.disconnect(); + } + final InetSocketAddress addr = new InetSocketAddress(this.ip, this.port); + this.socket = new Socket(); + try { + long time = System.currentTimeMillis(); + this.socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); + time = System.currentTimeMillis() - time; + if (!this.logID.isEmpty()) { + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result (" + this.logID + ") connect: " + toString() + " after: " + time + " ms"); + } else { + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result connect: " + toString() + " after: " + time + " ms"); + } + } catch (IOException e) { + this.disconnect(); + } + } + + public void disconnect() { + if (this.socket != null ) { + FileBackend.close(this.socket); + this.socket = null; + if (!this.logID.isEmpty()) { + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result (" + this.logID + ") disconnect: " + toString()); + } else { + Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": Result disconnect: " + toString()); + } + } + } + + public void setLogID(String logID) { + this.logID = logID; + } + @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; - } + return 0; } else { return directTls ? -1 : 1; } @@ -400,6 +498,32 @@ public class Resolver { return priority - result.priority; } } + @Override + public Result call() throws Exception { + this.connect(); + if (this.socket != null && this.socket.isConnected()) { + return this; + } + throw new Exception("Resolver.Result was not possible to connect - should be catched by executor"); + } + + 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; + } + final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME)); + result.hostname = hostname == null ? null : DNSName.from(hostname); + result.port = cursor.getInt(cursor.getColumnIndex(PORT)); + result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0; + result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0; + result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY)); + result.timeRequested = cursor.getLong(cursor.getColumnIndex(TIME_REQUESTED)); + return result; + } + public ContentValues toContentValues() { final ContentValues contentValues = new ContentValues(); @@ -409,6 +533,7 @@ public class Resolver { contentValues.put(PRIORITY, priority); contentValues.put(DIRECT_TLS, directTls ? 1 : 0); contentValues.put(AUTHENTICATED, authenticated ? 1 : 0); + contentValues.put(TIME_REQUESTED, timeRequested); return contentValues; } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index da0eb9656..b9868cb7f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -291,86 +291,59 @@ public class XmppConnection implements Runnable { } } else { final String domain = account.getJid().getDomain(); - final List results; + final Resolver.Result storedBackupResult = mXmppConnectionService.databaseBackend.findResolverResult(domain); + Resolver.Result result = null; final boolean hardcoded = extended && !account.getHostname().isEmpty(); if (hardcoded) { - results = Resolver.fromHardCoded(account.getHostname(), account.getPort()); - } else { - results = Resolver.resolve(domain); + result = Resolver.fromHardCoded(account.getHostname(), account.getPort()); + } else if (storedBackupResult != null && !storedBackupResult.isOutdated()) { + storedBackupResult.connect(); + result = storedBackupResult; + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": loaded backup resolver result from db: " + storedBackupResult); + } + if (result == null || result.getSocket() == null) { + result = Resolver.resolve(domain); + } + if (result == null) { + throw new UnknownHostException(); } if (Thread.currentThread().isInterrupted()) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Thread was interrupted"); return; } - if (results.size() == 0) { - Log.e(Config.LOGTAG,account.getJid().asBareJid()+": Resolver results were empty"); + try { + // if tls is true, encryption is implied and must not be started + features.encryptionEnabled = result.isDirectTls(); + verifiedHostname = result.isAuthenticated() ? result.getHostname().toString() : null; + Log.d(Config.LOGTAG,"verified hostname " + verifiedHostname); + Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + + ": using values from resolver " + result.toString()); + + localSocket = result.getSocket(); + + if (features.encryptionEnabled) { + localSocket = upgradeSocketToTls(localSocket); + } + + localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); + if (startXmpp(localSocket)) { + localSocket.setSoTimeout(0); //reset to 0; once the connection is established we don’t want this + if (!hardcoded && !result.equals(storedBackupResult)) { + mXmppConnectionService.databaseBackend.saveResolverResult(domain, result); + } + // successfully connected to server that speaks xmpp + } else { + FileBackend.close(localSocket); + throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); + } + } catch (final StateChangingException e) { + throw e; + } catch (InterruptedException e) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": thread was interrupted before beginning stream"); return; - } - final Resolver.Result storedBackupResult; - if (hardcoded) { - storedBackupResult = null; - } else { - storedBackupResult = mXmppConnectionService.databaseBackend.findResolverResult(domain); - if (storedBackupResult != null && !results.contains(storedBackupResult)) { - results.add(storedBackupResult); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": loaded backup resolver result from db: " + storedBackupResult); - } - } - for (Iterator iterator = results.iterator(); iterator.hasNext(); ) { - final Resolver.Result result = iterator.next(); - if (Thread.currentThread().isInterrupted()) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Thread was interrupted"); - return; - } - try { - // if tls is true, encryption is implied and must not be started - features.encryptionEnabled = result.isDirectTls(); - verifiedHostname = result.isAuthenticated() ? result.getHostname().toString() : null; - Log.d(Config.LOGTAG,"verified hostname "+verifiedHostname); - final InetSocketAddress addr; - if (result.getIp() != null) { - addr = new InetSocketAddress(result.getIp(), result.getPort()); - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() - + ": using values from resolver " + (result.getHostname() == null ? "" : result.getHostname().toString() - + "/") + result.getIp().getHostAddress() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); - } else { - addr = new InetSocketAddress(IDN.toASCII(result.getHostname().toString()), result.getPort()); - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() - + ": using values from resolver " - + result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); - } - - localSocket = new Socket(); - localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000); - - if (features.encryptionEnabled) { - localSocket = upgradeSocketToTls(localSocket); - } - - localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); - if (startXmpp(localSocket)) { - localSocket.setSoTimeout(0); //reset to 0; once the connection is established we don’t want this - if (!hardcoded && !result.equals(storedBackupResult)) { - mXmppConnectionService.databaseBackend.saveResolverResult(domain, result); - } - break; // successfully connected to server that speaks xmpp - } else { - FileBackend.close(localSocket); - throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); - } - } catch (final StateChangingException e) { - if (!iterator.hasNext()) { - throw e; - } - } catch (InterruptedException e) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": thread was interrupted before beginning stream"); - return; - } catch (final Throwable e) { - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")"); - if (!iterator.hasNext()) { - throw new UnknownHostException(); - } - } + } catch (final Throwable e) { + Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")"); + throw new UnknownHostException(); } } processStream(); @@ -384,8 +357,12 @@ public class XmppConnection implements Runnable { this.changeStatus(Account.State.SERVER_NOT_FOUND); } catch (final SocksSocketFactory.SocksProxyNotFoundException e) { this.changeStatus(Account.State.TOR_NOT_AVAILABLE); - } catch (final IOException | XmlPullParserException e) { - Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": " + e.getMessage()); + } catch (final IOException e) { + Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": socket io :" + e.getMessage()); + this.changeStatus(Account.State.OFFLINE); + this.attempt = Math.max(0, this.attempt - 1); + } catch (final XmlPullParserException e) { + Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": xml parser :" + e.getMessage()); this.changeStatus(Account.State.OFFLINE); this.attempt = Math.max(0, this.attempt - 1); } finally {