check if thread was interrupted before doing operations on socket

This commit is contained in:
Daniel Gultsch 2016-11-19 10:44:40 +01:00
parent 1820b163a1
commit 3bf2876e09
2 changed files with 78 additions and 77 deletions

View file

@ -2897,8 +2897,6 @@ public class XmppConnectionService extends Service {
if (connection == null) { if (connection == null) {
connection = createConnection(account); connection = createConnection(account);
account.setXmppConnection(connection); account.setXmppConnection(connection);
} else {
connection.interrupt();
} }
if (!account.isOptionSet(Account.OPTION_DISABLED)) { if (!account.isOptionSet(Account.OPTION_DISABLED)) {
if (!force) { if (!force) {
@ -2907,6 +2905,7 @@ public class XmppConnectionService extends Service {
Thread thread = new Thread(connection); Thread thread = new Thread(connection);
connection.setInteractive(interactive); connection.setInteractive(interactive);
connection.prepareNewConnection(); connection.prepareNewConnection();
connection.interrupt();
thread.start(); thread.start();
scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
} else { } else {

View file

@ -218,7 +218,10 @@ public class XmppConnection implements Runnable {
mXmppConnectionService = service; mXmppConnectionService = service;
} }
protected void changeStatus(final Account.State nextStatus) { protected synchronized void changeStatus(final Account.State nextStatus) {
if (Thread.currentThread().isInterrupted()) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": not changing status to "+nextStatus+" because thread was interrupted");
}
if (account.getStatus() != nextStatus) { if (account.getStatus() != nextStatus) {
if ((nextStatus == Account.State.OFFLINE) if ((nextStatus == Account.State.OFFLINE)
&& (account.getStatus() != Account.State.CONNECTING) && (account.getStatus() != Account.State.CONNECTING)
@ -262,6 +265,7 @@ public class XmppConnection implements Runnable {
break; break;
} }
try { try {
Socket localSocket;
shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER); shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER);
tagReader = new XmlReader(wakeLock); tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter(); tagWriter = new TagWriter();
@ -276,8 +280,15 @@ public class XmppConnection implements Runnable {
destination = account.getHostname(); destination = account.getHostname();
} }
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via Tor"); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via Tor");
socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); localSocket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
startXmpp(); try {
startXmpp(localSocket);
} catch (InterruptedException e) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
return;
} catch (Exception e) {
throw new IOException(e.getMessage());
}
} else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) { } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
InetSocketAddress address = new InetSocketAddress(account.getHostname(), account.getPort()); InetSocketAddress address = new InetSocketAddress(account.getHostname(), account.getPort());
@ -288,33 +299,47 @@ public class XmppConnection implements Runnable {
if (features.encryptionEnabled) { if (features.encryptionEnabled) {
try { try {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
socket = tlsFactoryVerifier.factory.createSocket(); localSocket = tlsFactoryVerifier.factory.createSocket();
socket.connect(address, Config.SOCKET_TIMEOUT * 1000); localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000);
final SSLSession session = ((SSLSocket) socket).getSession(); final SSLSession session = ((SSLSocket) localSocket).getSession();
if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), session)) { if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), session)) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
throw new SecurityException(); throw new SecurityException();
} }
} catch (KeyManagementException e) { } catch (KeyManagementException e) {
features.encryptionEnabled = false; features.encryptionEnabled = false;
socket = new Socket(); localSocket = new Socket();
} }
} else { } else {
socket = new Socket(); localSocket = new Socket();
socket.connect(address, Config.SOCKET_TIMEOUT * 1000); localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000);
} }
} catch (IOException e) { } catch (IOException e) {
throw new UnknownHostException(); throw new UnknownHostException();
} }
startXmpp();
} else if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
try { try {
socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000); startXmpp(localSocket);
} catch (InterruptedException e) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
return;
} catch (Exception e) {
throw new IOException(e.getMessage());
}
} else if (DNSHelper.isIp(account.getServer().toString())) {
localSocket = new Socket();
try {
localSocket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) { } catch (IOException e) {
throw new UnknownHostException(); throw new UnknownHostException();
} }
startXmpp(); try {
startXmpp(localSocket);
} catch (InterruptedException e) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
return;
} catch (Exception e) {
throw new IOException(e.getMessage());
}
} else { } else {
final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService); final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
final ArrayList<Parcelable> values = result.getParcelableArrayList("values"); final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
@ -350,32 +375,34 @@ public class XmppConnection implements Runnable {
} }
if (!features.encryptionEnabled) { if (!features.encryptionEnabled) {
socket = new Socket(); localSocket = new Socket();
socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
} else { } else {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
socket = tlsFactoryVerifier.factory.createSocket(); localSocket = tlsFactoryVerifier.factory.createSocket();
if (socket == null) { if (localSocket == null) {
throw new IOException("could not initialize ssl socket"); throw new IOException("could not initialize ssl socket");
} }
SSLSocketHelper.setSecurity((SSLSocket) socket); SSLSocketHelper.setSecurity((SSLSocket) localSocket);
SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart()); SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) localSocket, account.getServer().getDomainpart());
SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client"); SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) localSocket, "xmpp-client");
socket.connect(addr, Config.SOCKET_TIMEOUT * 1000); localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) { if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) localSocket).getSession())) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed"); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
throw new SecurityException(); throw new SecurityException();
} }
} }
if (startXmpp(localSocket))
if (startXmpp())
break; // successfully connected to server that speaks xmpp break; // successfully connected to server that speaks xmpp
} catch (final SecurityException e) { } catch (final SecurityException e) {
throw e; throw e;
} catch (InterruptedException e) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
return;
} catch (final Throwable e) { } catch (final Throwable e) {
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")"); Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")");
if (!iterator.hasNext()) { if (!iterator.hasNext()) {
@ -387,6 +414,8 @@ public class XmppConnection implements Runnable {
processStream(); processStream();
} catch (final java.lang.SecurityException e) { } catch (final java.lang.SecurityException e) {
this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION); this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION);
} catch (final RegistrationNotSupportedException e) {
this.changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED);
} catch (final IncompatibleServerException e) { } catch (final IncompatibleServerException e) {
this.changeStatus(Account.State.INCOMPATIBLE_SERVER); this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
} catch (final SecurityException e) { } catch (final SecurityException e) {
@ -410,6 +439,7 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.OFFLINE); this.changeStatus(Account.State.OFFLINE);
this.attempt = Math.max(0, this.attempt - 1); this.attempt = Math.max(0, this.attempt - 1);
} finally { } finally {
if (!Thread.currentThread().isInterrupted()) {
forceCloseSocket(); forceCloseSocket();
if (wakeLock.isHeld()) { if (wakeLock.isHeld()) {
try { try {
@ -417,33 +447,27 @@ public class XmppConnection implements Runnable {
} catch (final RuntimeException ignored) { } catch (final RuntimeException ignored) {
} }
} }
} else {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": not force closing socket and releasing wake lock because thread was interrupted");
}
} }
} }
/** /**
* Starts xmpp protocol, call after connecting to socket * Starts xmpp protocol, call after connecting to socket
* @return true if server returns with valid xmpp, false otherwise * @return true if server returns with valid xmpp, false otherwise
* @throws IOException Unknown tag on connect
* @throws XmlPullParserException Bad Xml
* @throws NoSuchAlgorithmException Other error
*/ */
private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException { private boolean startXmpp(Socket socket) throws Exception {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
this.socket = socket;
tagWriter.setOutputStream(socket.getOutputStream()); tagWriter.setOutputStream(socket.getOutputStream());
tagReader.setInputStream(socket.getInputStream()); tagReader.setInputStream(socket.getInputStream());
tagWriter.beginDocument(); tagWriter.beginDocument();
sendStartStream(); sendStartStream();
Tag nextTag; final Tag tag = tagReader.readTag();
while ((nextTag = tagReader.readTag()) != null) { return tag != null && tag.isStart("stream");
if (nextTag.isStart("stream")) {
return true;
} else {
throw new IOException("unknown tag on connect");
}
}
if (socket.isConnected()) {
socket.close();
}
return false;
} }
private static class TlsFactoryVerifier { private static class TlsFactoryVerifier {
@ -812,10 +836,8 @@ public class XmppConnection implements Runnable {
} else { } else {
throw new IncompatibleServerException(); throw new IncompatibleServerException();
} }
} else if (!this.streamFeatures.hasChild("register") } else if (!this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) {
&& account.isOptionSet(Account.OPTION_REGISTER)) { throw new RegistrationNotSupportedException();
forceCloseSocket();
changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED);
} else if (this.streamFeatures.hasChild("mechanisms") } else if (this.streamFeatures.hasChild("mechanisms")
&& shouldAuthenticate && shouldAuthenticate
&& (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) { && (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) {
@ -1359,30 +1381,6 @@ public class XmppConnection implements Runnable {
} }
} }
public void waitForPush() {
if (tagWriter.isActive()) {
tagWriter.finish();
new Thread(new Runnable() {
@Override
public void run() {
try {
while(!tagWriter.finished()) {
Thread.sleep(10);
}
socket.close();
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
changeStatus(Account.State.OFFLINE);
} catch (IOException | InterruptedException e) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": error while closing socket for waitForPush()");
}
}
}).start();
} else {
forceCloseSocket();
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
}
}
private void forceCloseSocket() { private void forceCloseSocket() {
if (socket != null) { if (socket != null) {
try { try {
@ -1569,6 +1567,10 @@ public class XmppConnection implements Runnable {
} }
private class RegistrationNotSupportedException extends IOException {
}
public enum Identity { public enum Identity {
FACEBOOK, FACEBOOK,
SLACK, SLACK,