From 1f24533a6abf35f6a0b113310fb80278894c81fa Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Wed, 12 Jul 2023 16:01:54 +0200 Subject: [PATCH] feat: support tor and torsf proxies Unfortunately, because of https://github.com/ooni/probe/issues/2406, we are going to see crashes when using these proxies. This diff is part of https://github.com/ooni/probe/issues/2500. Since we're increasingly being blocked, it makes sense to exposes all the possible proxies we can feature. We're going to touch upon the same files again once we land the https://github.com/ooni/probe-cli/pull/1162 pull request. --- .../ooniprobe/activity/ProxyActivity.java | 58 +++++++++++++++++-- .../ooniprobe/common/ProxyProtocol.java | 2 + .../ooniprobe/common/ProxySettings.java | 23 ++++++-- app/src/main/res/layout/activity_proxy.xml | 16 ++++- app/src/main/res/values/strings.xml | 2 + 5 files changed, 88 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/ProxyActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/ProxyActivity.java index 46ced49d6..f6c562043 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/ProxyActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/ProxyActivity.java @@ -48,9 +48,15 @@ public class ProxyActivity extends AbstractActivity { * * 1. an empty string means no proxy; * - * 2. "psiphon://" means that we wanna use psiphon; + * 2. "psiphon:///" means that we want to use psiphon; * - * 3. "socks5://1.2.3.4:5678" or "socks5://[::1]:5678" or "socks5://d.com:5678" + * 3. "tor:///" means we want to use tor without any proxy (which is possible + * because oonimkall embeds `libtor.a` as a dependency); + * + * 4. "torsf:///" is like "tor:///" but additionally uses the snowflake + * library we also bundle inside of oonimkall; + * + * 5. "socks5://1.2.3.4:5678" or "socks5://[::1]:5678" or "socks5://d.com:5678" * means that we wanna use the given socks5 proxy. * * Future improvements @@ -67,8 +73,6 @@ public class ProxyActivity extends AbstractActivity { * This implies we can trivially support a vanilla socks5 proxy with username and * password by just replacing `psiphon+socks5` with `socks5`. * - * We also want to support vanilla tor, using `tor://`. - * * We also want to support vanilla tor with socks5, which is trivially doable * using as a scheme the `tor+socks5` scheme. * @@ -93,15 +97,20 @@ public class ProxyActivity extends AbstractActivity { * The design and implementation of this class owes to the code contributed * by and the suggestion from friendly anonymous users. Thank you! */ + + // logger is the injected AppLogger instance. @Inject AppLogger logger; + // TAG is the tag used for logging. private final static String TAG = "ProxyActivity"; + // preferenceManager is the injected PreferenceManager instance. @Inject PreferenceManager preferenceManager; - // The following radio group describes the top level choice - // in terms of proxying: no proxy, psiphon, or custom. + + // The following radio group describes the top level choice in terms of + // proxying: no proxy, psiphon, tor, torsf, or custom. // proxyRadioGroup is the top-level radio group. private RadioGroup proxyRadioGroup; @@ -112,12 +121,21 @@ public class ProxyActivity extends AbstractActivity { // proxyPsiphonRB is the radio button selecting the "psiphon" proxy. private RadioButton proxyPsiphonRB; + // proxyTorRB is the radio button selecting the "tor" proxy. + private RadioButton proxyTorRB; + + // proxyTorSfRB is the radio button selecting the "torsf" proxy. + private RadioButton proxyTorSfRB; + // proxyCustomRB is the radio button for the "custom" proxy. private RadioButton proxyCustomRB; // The following radio group allows users to choose which specific // custom proxy they would like to use. When writing this documentation, // only socks5 is available but we will add more options. + // + // TODO(bassosimone): we need to implement support for HTTP proxies + // once https://github.com/ooni/probe-cli/pull/1162 lands. // customProxyRadioGroup allows you to choose among the different // kinds of custom proxies that are available. @@ -153,6 +171,8 @@ public void onCreate(Bundle savedInstanceState) { proxyRadioGroup = findViewById(R.id.proxyRadioGroup); proxyNoneRB = findViewById(R.id.proxyNone); proxyPsiphonRB = findViewById(R.id.proxyPsiphon); + proxyTorRB = findViewById(R.id.proxyTor); + proxyTorSfRB = findViewById(R.id.proxyTorSf); proxyCustomRB = findViewById(R.id.proxyCustom); customProxyRadioGroup = findViewById(R.id.customProxyRadioGroup); customProxySOCKS5 = findViewById(R.id.customProxySOCKS5); @@ -196,6 +216,10 @@ private void configureInitialViewWithSettings(ProxySettings settings) { proxyNoneRB.setChecked(true); } else if (settings.protocol == ProxyProtocol.PSIPHON) { proxyPsiphonRB.setChecked(true); + } else if (settings.protocol == ProxyProtocol.TOR) { + proxyTorRB.setChecked(true); + } else if (settings.protocol == ProxyProtocol.TORSF) { + proxyTorSfRB.setChecked(true); } else if (settings.protocol == ProxyProtocol.SOCKS5) { proxyCustomRB.setChecked(true); } else { @@ -226,6 +250,10 @@ private void configureInitialViewWithSettings(ProxySettings settings) { customProxySetEnabled(false); } else if (checkedId == R.id.proxyPsiphon) { customProxySetEnabled(false); + } else if (checkedId == R.id.proxyTor) { + customProxySetEnabled(false); + } else if (checkedId == R.id.proxyTorSf) { + customProxySetEnabled(false); } else if (checkedId == R.id.proxyCustom) { customProxySetEnabled(true); customProxyRadioGroup.clearCheck(); @@ -364,6 +392,24 @@ public void onBackPressed() { return; } + // If the tor proxy is checked then write back the right + // proxy configuration for tor and move on. + if (proxyTorRB.isChecked()) { + settings.protocol = ProxyProtocol.TOR; + saveSettings(); + super.onBackPressed(); + return; + } + + // If the torsf proxy is checked then write back the right + // proxy configuration for torsf and move on. + if (proxyTorSfRB.isChecked()) { + settings.protocol = ProxyProtocol.TORSF; + saveSettings(); + super.onBackPressed(); + return; + } + // validate the hostname for the custom proxy. if (!isValidHostnameOrIP(hostname)) { customProxyHostname.setError("not a valid hostname or IP"); diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/ProxyProtocol.java b/app/src/main/java/org/openobservatory/ooniprobe/common/ProxyProtocol.java index ed5742a2e..ee7e8614e 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/ProxyProtocol.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/ProxyProtocol.java @@ -4,6 +4,8 @@ public enum ProxyProtocol { NONE("none"), PSIPHON("psiphon"), + TOR("tor"), + TORSF("torsf"), SOCKS5("socks5"); private String protocol; diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/ProxySettings.java b/app/src/main/java/org/openobservatory/ooniprobe/common/ProxySettings.java index e19867f94..78404b833 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/ProxySettings.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/ProxySettings.java @@ -13,8 +13,7 @@ * documentation of proxy activity for the design rationale. */ public class ProxySettings { - - /** scheme is the proxy scheme (e.g., "psiphon", "socks5"). */ + /** scheme is the proxy scheme (e.g., "psiphon", "tor", "torsf", "socks5"). */ public ProxyProtocol protocol = ProxyProtocol.NONE; /** hostname is the hostname for custom proxies. */ @@ -33,10 +32,14 @@ public static ProxySettings newProxySettings(PreferenceManager pm) throws Invali settings.protocol = ProxyProtocol.NONE; } else if (protocol.equals(ProxyProtocol.PSIPHON.getProtocol())) { settings.protocol = ProxyProtocol.PSIPHON; + } else if (protocol.equals(ProxyProtocol.TOR.getProtocol())) { + settings.protocol = ProxyProtocol.TOR; + } else if (protocol.equals(ProxyProtocol.TORSF.getProtocol())) { + settings.protocol = ProxyProtocol.TORSF; } else if (protocol.equals(ProxyProtocol.SOCKS5.getProtocol())) { settings.protocol = ProxyProtocol.SOCKS5; } else { - // This is where we will extend the code to add support for + // This exception indicates that we need to extend the code to support // more proxies, e.g., HTTP proxies. throw new InvalidProxyURL("unhandled URL scheme"); } @@ -72,10 +75,18 @@ private boolean isIPv6(String hostname) { /** getProxyString returns to you the proxy string you should pass to oonimkall. */ public String getProxyString() throws URISyntaxException { - if (protocol == ProxyProtocol.NONE) + if (protocol == ProxyProtocol.NONE) { return ""; - if (protocol == ProxyProtocol.PSIPHON) - return "psiphon://"; + } + if (protocol == ProxyProtocol.PSIPHON) { + return "psiphon:///"; + } + if (protocol == ProxyProtocol.TOR) { + return "tor:///"; + } + if (protocol == ProxyProtocol.TORSF) { + return "torsf:///"; + } if (protocol == ProxyProtocol.SOCKS5) { // Alright, we now need to construct a new SOCKS5 URL. We are going to defer // doing that to the Java standard library (er, the Android stdlib). diff --git a/app/src/main/res/layout/activity_proxy.xml b/app/src/main/res/layout/activity_proxy.xml index d3c565ed4..6b7da69b1 100644 --- a/app/src/main/res/layout/activity_proxy.xml +++ b/app/src/main/res/layout/activity_proxy.xml @@ -38,6 +38,20 @@ android:textColor="@color/color_black" android:text="@string/Settings_Proxy_Psiphon" /> + + + + - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index db87e2f35..a9a48c990 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -392,6 +392,8 @@ Proxy None Psiphon + Tor (vanilla) + Tor with Snowflake Custom Proxy Custom Proxy URL Custom proxy protocol