android: Make fetching OCSP/CRL interruptible

This allows cancelling connecting if e.g. the OCSP server is not
reachable. Previously this caused some delay in disconnecting state but
even worse it cause an ANR if the user tried reconnecting during that
time as the main thread would get struck in setNextProfile() (we could
probably find a better solution there too in the future).
This commit is contained in:
Tobias Brunner 2018-06-21 11:17:22 +02:00
parent 8a09350f9f
commit ad2d20e5f0
2 changed files with 104 additions and 20 deletions

View File

@ -283,6 +283,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
startConnection(mCurrentProfile);
mIsDisconnecting = false;
SimpleFetcher.enable();
addNotification();
mBuilderAdapter.setProfile(mCurrentProfile);
if (initializeCharon(mBuilderAdapter, mLogFile, mAppDir, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
@ -350,6 +351,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
setState(State.DISCONNECTING);
mIsDisconnecting = true;
SimpleFetcher.disable();
deinitializeCharon();
Log.i(TAG, "charon stopped");
mCurrentProfile = null;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 Tobias Brunner
* Copyright (C) 2017-2018 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@ -23,36 +23,118 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Keep
public class SimpleFetcher
{
public static byte[] fetch(String uri, byte[] data, String contentType) throws IOException
private static ExecutorService mExecutor = Executors.newCachedThreadPool();
private static Object mLock = new Object();
private static ArrayList<Future> mFutures = new ArrayList<>();
private static boolean mDisabled;
public static byte[] fetch(String uri, byte[] data, String contentType)
{
URL url = new URL(uri);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
Future<byte[]> future;
synchronized (mLock)
{
if (mDisabled)
{
return null;
}
future = mExecutor.submit(() -> {
URL url = new URL(uri);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
try
{
if (contentType != null)
{
conn.setRequestProperty("Content-Type", contentType);
}
if (data != null)
{
conn.setDoOutput(true);
conn.setFixedLengthStreamingMode(data.length);
OutputStream out = new BufferedOutputStream(conn.getOutputStream());
out.write(data);
out.close();
}
return streamToArray(conn.getInputStream());
}
catch (SocketTimeoutException e)
{
return null;
}
finally
{
conn.disconnect();
}
});
mFutures.add(future);
}
try
{
if (contentType != null)
{
conn.setRequestProperty("Content-Type", contentType);
}
if (data != null)
{
conn.setDoOutput(true);
conn.setFixedLengthStreamingMode(data.length);
OutputStream out = new BufferedOutputStream(conn.getOutputStream());
out.write(data);
out.close();
}
return streamToArray(conn.getInputStream());
/* this enforces a timeout as the ones set on HttpURLConnection might not work reliably */
return future.get(10000, TimeUnit.MILLISECONDS);
}
catch (InterruptedException|ExecutionException|TimeoutException|CancellationException e)
{
return null;
}
finally
{
conn.disconnect();
synchronized (mLock)
{
mFutures.remove(future);
}
}
}
/**
* Enable fetching after it has been disabled.
*/
public static void enable()
{
synchronized (mLock)
{
mDisabled = false;
}
}
/**
* Disable the fetcher and abort any future requests.
*
* The native thread is not cancelable as it is working on an IKE_SA (cancelling the methods of
* HttpURLConnection is not reliably possible anyway), so to abort while fetching we cancel the
* Future (causing a return from fetch() immediately) and let the executor thread continue its
* thing in the background.
*
* Also prevents future fetches until enabled again (e.g. if we aborted OCSP but would then
* block in the subsequent fetch for a CRL).
*/
public static void disable()
{
synchronized (mLock)
{
mDisabled = true;
for (Future future : mFutures)
{
future.cancel(true);
}
}
}