summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java80
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java69
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java4
3 files changed, 132 insertions, 21 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java b/app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java
index 5f7fa74a..092635d0 100644
--- a/app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java
+++ b/app/src/main/java/se/leap/bitmaskclient/tor/ClientTransportPlugin.java
@@ -15,10 +15,17 @@ package se.leap.bitmaskclient.tor;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.RETRY_AMP_CACHE_RENDEZVOUS;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.RETRY_HTTP_RENDEZVOUS;
+
import android.content.Context;
import android.os.FileObserver;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.torproject.jni.ClientTransportPluginInterface;
@@ -31,6 +38,9 @@ import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashMap;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Random;
import java.util.Scanner;
import java.util.Vector;
import java.util.concurrent.TimeoutException;
@@ -39,7 +49,7 @@ import java.util.regex.Pattern;
import IPtProxy.IPtProxy;
-public class ClientTransportPlugin implements ClientTransportPluginInterface {
+public class ClientTransportPlugin implements ClientTransportPluginInterface, Observer {
public static String TAG = ClientTransportPlugin.class.getSimpleName();
private HashMap<String, String> mFronts;
@@ -47,9 +57,14 @@ public class ClientTransportPlugin implements ClientTransportPluginInterface {
private long snowflakePort = -1;
private FileObserver logFileObserver;
private static final Pattern SNOWFLAKE_LOG_TIMESTAMP_PATTERN = Pattern.compile("((19|2[0-9])[0-9]{2}\\/\\d{1,2}\\/\\d{1,2} \\d{1,2}:\\d{1,2}:\\d{1,2}) ([\\S|\\s]+)");
+ private TorStatusObservable.SnowflakeStatus snowflakeStatus;
+ private String logfilePath;
+ Handler handler;
+ HandlerThread handlerThread;
public ClientTransportPlugin(Context context) {
this.contextRef = new WeakReference<>(context);
+ handlerThread = new HandlerThread("clientTransportPlugin", Thread.MIN_PRIORITY);
loadCdnFronts(context);
}
@@ -59,6 +74,9 @@ public class ClientTransportPlugin implements ClientTransportPluginInterface {
if (context == null) {
return;
}
+ handlerThread.start();
+ handler = new Handler(handlerThread.getLooper());
+ TorStatusObservable.getInstance().addObserver(this);
File logfile = new File(context.getApplicationContext().getCacheDir(), "snowflake.log");
Log.d(TAG, "logfile at " + logfile.getAbsolutePath());
try {
@@ -69,15 +87,34 @@ public class ClientTransportPlugin implements ClientTransportPluginInterface {
} catch (IOException e) {
e.printStackTrace();
}
+ this.logfilePath = logfile.getAbsolutePath();
+ Random random = new Random();
+ boolean useAmpCache = random.nextInt(2) == 0;
+ startConnectionAttempt(useAmpCache, logfilePath);
+ watchLogFile(logfile);
+ }
+ private void startConnectionAttempt(boolean useAmpCache, @NonNull String logfilePath) {
//this is using the current, default Tor snowflake infrastructure
String target = getCdnFront("snowflake-target");
String front = getCdnFront("snowflake-front");
String stunServer = getCdnFront("snowflake-stun");
Log.d(TAG, "startSnowflake. target: " + target + ", front:" + front + ", stunServer" + stunServer);
- snowflakePort = IPtProxy.startSnowflake(stunServer, target, front, null, logfile.getAbsolutePath(), false, false, true, 5);
+ String ampCache = null;
+ if (useAmpCache) {
+ Log.d(TAG, "using ampcache for rendez-vous");
+ target = "https://snowflake-broker.torproject.net/";
+ ampCache = "https://cdn.ampproject.org/";
+ front = "www.google.com";
+ }
+ snowflakePort = IPtProxy.startSnowflake(stunServer, target, front, ampCache, logfilePath, false, false, true, 5);
Log.d(TAG, "startSnowflake running on port: " + snowflakePort);
- watchLogFile(logfile);
+ }
+
+ private void retryConnectionAttempt(boolean useAmpCache) {
+ Log.d(TAG, ">> retryConnectionAttempt - " + (useAmpCache ? "amp cache" : "http domain fronting"));
+ stopConnectionAttempt();
+ startConnectionAttempt(useAmpCache, logfilePath);
}
private void watchLogFile(File logfile) {
@@ -111,6 +148,18 @@ public class ClientTransportPlugin implements ClientTransportPluginInterface {
@Override
public void stop() {
+ stopConnectionAttempt();
+ if (logFileObserver != null) {
+ logFileObserver.stopWatching();
+ logFileObserver = null;
+ }
+ TorStatusObservable.getInstance().deleteObserver(this);
+ handlerThread.quit();
+ handler = null;
+ handlerThread = null;
+ }
+
+ private void stopConnectionAttempt() {
IPtProxy.stopSnowflake();
try {
TorStatusObservable.waitUntil(this::isSnowflakeOff, 10);
@@ -118,14 +167,10 @@ public class ClientTransportPlugin implements ClientTransportPluginInterface {
e.printStackTrace();
}
snowflakePort = -1;
- if (logFileObserver != null) {
- logFileObserver.stopWatching();
- logFileObserver = null;
- }
}
private boolean isSnowflakeOff() {
- return TorStatusObservable.getSnowflakeStatus() == TorStatusObservable.SnowflakeStatus.OFF;
+ return TorStatusObservable.getSnowflakeStatus() == TorStatusObservable.SnowflakeStatus.STOPPED;
}
@Override
@@ -170,11 +215,28 @@ public class ClientTransportPlugin implements ClientTransportPluginInterface {
if (strippedString.length() > 0) {
TorStatusObservable.logSnowflakeMessage(contextRef.get(), strippedString);
}
- } catch (IndexOutOfBoundsException | IllegalStateException e) {
+ } catch (IndexOutOfBoundsException | IllegalStateException | NullPointerException e) {
e.printStackTrace();
}
} else {
TorStatusObservable.logSnowflakeMessage(contextRef.get(), message);
}
}
+
+ @Override
+ public void update(Observable o, Object arg) {
+ if (o instanceof TorStatusObservable) {
+ TorStatusObservable.SnowflakeStatus snowflakeStatus = TorStatusObservable.getSnowflakeStatus();
+ if (snowflakeStatus == this.snowflakeStatus) {
+ return;
+ }
+ Log.d(TAG, "clientTransportPlugin: snowflake status " + this.snowflakeStatus);
+ if (snowflakeStatus == RETRY_HTTP_RENDEZVOUS) {
+ handler.post(() -> retryConnectionAttempt(false));
+ } else if (snowflakeStatus == RETRY_AMP_CACHE_RENDEZVOUS) {
+ handler.post(() -> retryConnectionAttempt(true));
+ }
+ this.snowflakeStatus = snowflakeStatus;
+ }
+ }
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
index 7eee1a9d..845d1789 100644
--- a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
+++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java
@@ -15,6 +15,14 @@ package se.leap.bitmaskclient.tor;
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.BROKER_REPLIED_SUCCESS;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.NEGOTIATING_RENDEZVOUS_VIA_AMP_CACHE;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.NEGOTIATING_RENDEZVOUS_VIA_HTTP;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.RETRY_AMP_CACHE_RENDEZVOUS;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.RETRY_HTTP_RENDEZVOUS;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.STARTED;
+import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.STOPPED;
+
import android.content.Context;
import android.util.Log;
@@ -46,8 +54,13 @@ public class TorStatusObservable extends Observable {
}
public enum SnowflakeStatus {
- ON,
- OFF
+ STARTED,
+ NEGOTIATING_RENDEZVOUS_VIA_HTTP,
+ NEGOTIATING_RENDEZVOUS_VIA_AMP_CACHE,
+ RETRY_HTTP_RENDEZVOUS,
+ RETRY_AMP_CACHE_RENDEZVOUS,
+ BROKER_REPLIED_SUCCESS,
+ STOPPED
}
// indicates if the user has cancelled Tor, the actual TorStatus can still be different until
@@ -60,17 +73,23 @@ public class TorStatusObservable extends Observable {
public static final String SNOWFLAKE_STOPPED_COLLECTING = "---- SnowflakeConn: end collecting snowflakes ---";
public static final String SNOWFLAKE_COPY_LOOP_STOPPED = "copy loop ended";
public static final String SNOWFLAKE_SOCKS_ERROR = "SOCKS accept error";
+ public static final String SNOWFLAKE_NEGOTIATING_HTTP = "Negotiating via HTTP rendezvous...";
+ public static final String SNOWFLAKE_NEGOTIATING_AMP_CACHE = "Negotiating via AMP cache rendezvous...";
+ public static final String SNOWFLAKE_CONNECTION_CLOSING = "WebRTC: Closing";
+ public static final String SNOWFLAKE_HTTP_RESPONSE_200 = "HTTP rendezvous response: 200";
+ public static final String SNOWFLAKE_AMP_CACHE_RESPONSE_200 = "AMP cache rendezvous response: 200";
private static TorStatusObservable instance;
private TorStatus status = TorStatus.OFF;
- private SnowflakeStatus snowflakeStatus = SnowflakeStatus.OFF;
+ private SnowflakeStatus snowflakeStatus = STOPPED;
private final TorNotificationManager torNotificationManager;
private String lastError;
private String lastTorLog = "";
private String lastSnowflakeLog = "";
private int port = -1;
private int bootstrapPercent = -1;
- private Vector<String> lastLogs = new Vector<>(100);
+ private int retrySnowflakeRendezVous = 0;
+ private final Vector<String> lastLogs = new Vector<>(100);
private TorStatusObservable() {
torNotificationManager = new TorNotificationManager();
@@ -128,14 +147,44 @@ public class TorStatusObservable extends Observable {
getInstance().torNotificationManager.buildTorNotification(context, getStringForCurrentStatus(context), getNotificationLog(), getBootstrapProgress());
}
//TODO: implement proper state signalling in IPtProxy
- if (SNOWFLAKE_STARTED.equals(message.trim())) {
+ message = message.trim();
+ if (SNOWFLAKE_STARTED.equals(message)) {
Log.d(TAG, "snowflakeStatus ON");
- getInstance().snowflakeStatus = SnowflakeStatus.ON;
- } else if (SNOWFLAKE_STOPPED_COLLECTING.equals(message.trim()) ||
- SNOWFLAKE_COPY_LOOP_STOPPED.equals(message.trim()) ||
- message.trim().contains(SNOWFLAKE_SOCKS_ERROR)) {
+ getInstance().snowflakeStatus = STARTED;
+ } else if (SNOWFLAKE_NEGOTIATING_HTTP.equals(message)) {
+ Log.d(TAG, "snowflake negotiating via http");
+ getInstance().snowflakeStatus = NEGOTIATING_RENDEZVOUS_VIA_HTTP;
+ } else if (SNOWFLAKE_NEGOTIATING_AMP_CACHE.equals(message)) {
+ Log.d(TAG, "snowflake negotiating via amp cache");
+ getInstance().snowflakeStatus = NEGOTIATING_RENDEZVOUS_VIA_AMP_CACHE;
+ } else if (SNOWFLAKE_STOPPED_COLLECTING.equals(message) ||
+ SNOWFLAKE_COPY_LOOP_STOPPED.equals(message) ||
+ message.contains(SNOWFLAKE_SOCKS_ERROR)) {
Log.d(TAG, "snowflakeStatus OFF");
- getInstance().snowflakeStatus = SnowflakeStatus.OFF;
+ getInstance().snowflakeStatus = STOPPED;
+ } else if (SNOWFLAKE_CONNECTION_CLOSING.equals(message)) {
+ Log.d(TAG, "snowflake connection closing...");
+ if (getInstance().snowflakeStatus == NEGOTIATING_RENDEZVOUS_VIA_HTTP) {
+ if (getInstance().retrySnowflakeRendezVous < 3) {
+ getInstance().retrySnowflakeRendezVous += 1;
+ } else {
+ getInstance().retrySnowflakeRendezVous = 0;
+ getInstance().snowflakeStatus = RETRY_AMP_CACHE_RENDEZVOUS;
+ Log.d(TAG, "snowflake retry amp cache");
+ }
+ } else if (getInstance().snowflakeStatus == NEGOTIATING_RENDEZVOUS_VIA_AMP_CACHE) {
+ if (getInstance().retrySnowflakeRendezVous < 3) {
+ getInstance().retrySnowflakeRendezVous += 1;
+ } else {
+ getInstance().retrySnowflakeRendezVous = 0;
+ getInstance().snowflakeStatus = RETRY_HTTP_RENDEZVOUS;
+ Log.d(TAG, "snowflake retry http domain fronting");
+ }
+ }
+ } else if (SNOWFLAKE_AMP_CACHE_RESPONSE_200.equals(message) || SNOWFLAKE_HTTP_RESPONSE_200.equals(message)) {
+ getInstance().snowflakeStatus = BROKER_REPLIED_SUCCESS;
+ getInstance().retrySnowflakeRendezVous = 0;
+ Log.d(TAG, "snowflake broker replied success");
}
instance.setChanged();
instance.notifyObservers();
diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
index e14fa4c3..651aa345 100644
--- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
+++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java
@@ -550,9 +550,9 @@ public class MockHelper {
});
when(TorStatusObservable.getSnowflakeStatus()).thenAnswer((Answer<TorStatusObservable.SnowflakeStatus>) invocation -> {
if (waitUntilSuccess.get()) {
- return TorStatusObservable.SnowflakeStatus.ON;
+ return TorStatusObservable.SnowflakeStatus.STARTED;
}
- return TorStatusObservable.SnowflakeStatus.OFF;
+ return TorStatusObservable.SnowflakeStatus.STOPPED;
});
if (exception != null) {