commit 75426bb9e25d11fa84636481aac59094eec41b26 Author: Nathan Freitas nathan@freitas.net Date: Fri Apr 3 12:27:59 2015 -0400
Improve VPN service support - fix network switching handling We now refresh the VPN and tun2socks interfaces when the network type switches, and we do so in a way that does not cause traffic to leak. The new interface is established before we close the old one. --- res/values/arrays.xml | 13 +- res/values/strings.xml | 13 +- src/org/torproject/android/OrbotConstants.java | 2 +- src/org/torproject/android/OrbotMainActivity.java | 180 ++++++++++++++++---- src/org/torproject/android/service/TorService.java | 134 +++++++++++---- .../torproject/android/vpn/OrbotVpnService.java | 157 ++++++++++------- 6 files changed, 359 insertions(+), 140 deletions(-)
diff --git a/res/values/arrays.xml b/res/values/arrays.xml index fb0a7c3..00eb3a4 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -36,9 +36,16 @@ <item>ru</item> - - - + </string-array> + + <string-array name="bridge_options"> + <item>Obfs4 (Recommended)</item> + <item>Obfs3</item> + <item>ScrambleSuit</item> + <item>Tunnel through Azure</item> + <item>Tunnel through Amazon</item> + <item>Tunnel through Google</item> + <item></item> </string-array>
</resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 92e1e35..e76ce28 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -315,15 +315,16 @@
<string name="bridges_updated">Bridges Updated</string>
- <string name="restart_orbot_to_use_this_bridge_">"Restart Orbot to use these bridges: "</string> + <string name="restart_orbot_to_use_this_bridge_">Please restart Orbot to enable the changes</string>
<string name="menu_qr">QR Codes</string>
- <string name="if_your_mobile_network_actively_blocks_tor_you_can_use_a_tor_bridge_to_access_the_network_another_way_to_get_bridges_is_to_send_an_email_to_bridges_torproject_org_please_note_that_you_must_send_the_email_using_an_address_from_one_of_the_following_email_providers_riseup_gmail_or_yahoo_">If your mobile network actively blocks Tor, you can use a Tor Bridge to access the network.\n\nYou can get a bridge address from https://bridges.torproject.org or scan a bridge QR code from a friend.\n\nAnother way to get bridges is to send an email to bridges@torproject.org. Please note that you must send the email using an address from one of the following email providers: Riseup, Gmail or Yahoo.</string> + <string name="if_your_mobile_network_actively_blocks_tor_you_can_use_a_tor_bridge_to_access_the_network_another_way_to_get_bridges_is_to_send_an_email_to_bridges_torproject_org_please_note_that_you_must_send_the_email_using_an_address_from_one_of_the_following_email_providers_riseup_gmail_or_yahoo_">If your mobile network actively blocks Tor, you can use a Tor Bridge to access the network.\n\nYou can get a bridge address from https://bridges.torproject.org, by emailing bridges@torproject.org, or by scanning a bridge QR code.</string>
<string name="bridge_mode">Bridge Mode</string>
- <string name="get_bridges">Get Bridges</string> + <string name="get_bridges_email">Email</string> + <string name="get_bridges_web">Web</string>
<string name="activate">Activate</string>
@@ -332,4 +333,10 @@ <string name="you_can_enable_all_apps_on_your_device_to_run_through_the_tor_network_using_the_vpn_feature_of_android_">You can enable all apps on your device to run through the Tor network using the VPN feature of Android.\n\n*WARNING* This is a new, experimental feature and in some cases may not start automatically, or may stop. It should NOT be used for anonymity, and ONLY used for getting through firewalls and filters.</string>
<string name="send_email">Send Email</string> + + <string name="you_must_get_a_bridge_address_by_email_web_or_from_a_friend_once_you_have_this_address_please_paste_it_into_the_bridges_preference_in_orbot_s_setting_and_restart_">You must can a bridge address by email, web or by scanning a bridge QR code. Once you have this address, please paste it into the "Bridges" preference in Orbot's setting and restart.</string> + + <string name="install_orweb">Install Orweb</string> + + <string name="standard_browser">Standard Browser</string> </resources> diff --git a/src/org/torproject/android/OrbotConstants.java b/src/org/torproject/android/OrbotConstants.java index 745ace4..fa67d92 100644 --- a/src/org/torproject/android/OrbotConstants.java +++ b/src/org/torproject/android/OrbotConstants.java @@ -16,7 +16,7 @@ public interface OrbotConstants { //path to check Tor against public final static String URL_TOR_CHECK = "https://check.torproject.org"; - public final static String URL_TOR_BRIDGES = "https://bridges.torproject.org"; + public final static String URL_TOR_BRIDGES = "https://bridges.torproject.org/bridges?transport=";
public final static String NEWLINE = "\n";
diff --git a/src/org/torproject/android/OrbotMainActivity.java b/src/org/torproject/android/OrbotMainActivity.java index be05d69..2e22fb2 100644 --- a/src/org/torproject/android/OrbotMainActivity.java +++ b/src/org/torproject/android/OrbotMainActivity.java @@ -661,6 +661,8 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon setResult(RESULT_OK); mBtnBridges.setChecked(true); + + enableBridges(true); }
private boolean showWizard = true; @@ -707,7 +709,7 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon .setIcon(R.drawable.onion32) .setTitle(R.string.install_apps_) .setMessage(R.string.it_doesn_t_seem_like_you_have_orweb_installed_want_help_with_that_or_should_we_just_open_the_browser_) - .setPositiveButton(android.R.string.ok, new Dialog.OnClickListener () + .setPositiveButton(R.string.install_orweb, new Dialog.OnClickListener () {
@Override @@ -720,7 +722,7 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon } }) - .setNegativeButton(android.R.string.no, new Dialog.OnClickListener () + .setNegativeButton(R.string.standard_browser, new Dialog.OnClickListener () {
@Override @@ -860,41 +862,56 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon new AlertDialog.Builder(this) .setTitle(R.string.bridge_mode) .setView(view) - .setNeutralButton(R.string.get_bridges, new Dialog.OnClickListener () - { - - @Override - public void onClick(DialogInterface dialog, int which) { - - //openBrowser(URL_TOR_BRIDGES); - - sendGetBridgeEmail(); - } - - - }) - .setPositiveButton(R.string.activate, new Dialog.OnClickListener () - { - - @Override - public void onClick(DialogInterface dialog, int which) { - - enableBridges (true); - - } - - - }) - .setNegativeButton(android.R.string.cancel, new Dialog.OnClickListener() + .setItems(R.array.bridge_options, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // The 'which' argument contains the index position + // of the selected item + + switch (which) + { + case 0: //obfs 4; + showGetBridgePrompt("obfs4"); + + break; + case 1: //obfs3 + showGetBridgePrompt("obfs3"); + + break; + case 2: //scramblesuit + showGetBridgePrompt("scramblesuit"); + + break; + case 3: //azure + mPrefs.edit().putString(OrbotConstants.PREF_BRIDGES_LIST,"2").commit(); + enableBridges(true); + + break; + case 4: //amazon + mPrefs.edit().putString(OrbotConstants.PREF_BRIDGES_LIST,"1").commit(); + enableBridges(true); + + break; + case 5: //google + mPrefs.edit().putString(OrbotConstants.PREF_BRIDGES_LIST,"0").commit(); + enableBridges(true); + + break; + + } + + } + }).setNegativeButton(android.R.string.cancel, new Dialog.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mBtnBridges.setChecked(false); + //mBtnBridges.setChecked(false); } }) .show(); + + } else { @@ -903,13 +920,72 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon
}
- private void sendGetBridgeEmail () + private void showGetBridgePrompt (final String type) + { + LayoutInflater li = LayoutInflater.from(this); + View view = li.inflate(R.layout.layout_diag, null); + + TextView versionName = (TextView)view.findViewById(R.id.diaglog); + versionName.setText(R.string.you_must_get_a_bridge_address_by_email_web_or_from_a_friend_once_you_have_this_address_please_paste_it_into_the_bridges_preference_in_orbot_s_setting_and_restart_); + + new AlertDialog.Builder(this) + .setTitle(R.string.bridge_mode) + .setView(view) + .setNegativeButton(android.R.string.cancel, new Dialog.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) { + + //mBtnBridges.setChecked(false); + + } + }) + .setNeutralButton(R.string.get_bridges_email, new Dialog.OnClickListener () + { + + @Override + public void onClick(DialogInterface dialog, int which) { + + + sendGetBridgeEmail(type); + + } + + + }) + .setPositiveButton(R.string.get_bridges_web, new Dialog.OnClickListener () + { + + @Override + public void onClick(DialogInterface dialog, int which) { + + openBrowser(URL_TOR_BRIDGES + type); + + } + + + }).show(); + } + + private void sendGetBridgeEmail (String type) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("message/rfc822"); intent.putExtra(Intent.EXTRA_EMAIL , new String[]{"bridges@torproject.org"}); - intent.putExtra(Intent.EXTRA_SUBJECT, "Tor Bridge Request"); - + + if (type != null) + { + intent.putExtra(Intent.EXTRA_SUBJECT, "get transport " + type); + intent.putExtra(Intent.EXTRA_TEXT, "get transport " + type); + + } + else + { + intent.putExtra(Intent.EXTRA_SUBJECT, "get bridges"); + intent.putExtra(Intent.EXTRA_TEXT, "get bridges"); + + } + startActivity(Intent.createChooser(intent, getString(R.string.send_email))); }
@@ -917,10 +993,45 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon {
Editor edit = mPrefs.edit(); - edit.putBoolean("pref_bridges_enabled", enable); + edit.putBoolean(OrbotConstants.PREF_BRIDGES_ENABLED, enable); edit.commit(); updateSettings(); + + if (torStatus == TorServiceConstants.STATUS_ON) + { + String bridgeList = mPrefs.getString(OrbotConstants.PREF_BRIDGES_LIST,null); + if (bridgeList != null && bridgeList.length() > 0) + { + try + { + //do auto restart + stopTor (); + + mHandler.postDelayed(new Runnable () { + + public void run () + { + try + { + startTor(); + } + catch (Exception e) + { + Log.e(TAG,"can't start orbot",e); + } + } + }, 2000); + } + catch (Exception e) + { + Log.e(TAG,"can't stop orbot",e); + } + } + + } + + }
public void promptStartVpnService () @@ -1160,7 +1271,7 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon //here we update the UI which is a bit sloppy and mixed up code wise //might be best to just call updateStatus() instead of directly manipulating UI in this method - yep makes sense imgStatus.setImageResource(R.drawable.torstarting); - // lblStatus.setText(getString(R.string.status_starting_up)); + lblStatus.setText(getString(R.string.status_starting_up));
//we send a message here to the progressDialog i believe, but we can clarify that shortly Message msg = mHandler.obtainMessage(TorServiceConstants.ENABLE_TOR_MSG); @@ -1168,7 +1279,6 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon mHandler.sendMessage(msg);
- }
//now we stop Tor! amazing! diff --git a/src/org/torproject/android/service/TorService.java b/src/org/torproject/android/service/TorService.java index 9529d6d..cae1697 100644 --- a/src/org/torproject/android/service/TorService.java +++ b/src/org/torproject/android/service/TorService.java @@ -96,7 +96,7 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon private int mPortHTTP = 8118; private int mPortSOCKS = 9050;
- private int mVpnProxyPort = 7231; + private int mVpnProxyPort = 9099;
private static final int NOTIFY_ID = 1; private static final int TRANSPROXY_NOTIFY_ID = 2; @@ -1475,6 +1475,18 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
}
+ public void refreshVpnProxy () { + + debug ("refreshing VPN Proxy"); + + Intent intent = new Intent(TorService.this, OrbotVpnService.class); + intent.setAction("refresh"); + startService(intent); + + } + + + public void clearVpnProxy () { debug ("clearing VPN Proxy"); @@ -1911,6 +1923,31 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon return false; }
+ public void setTorNetworkEnabled (final boolean isEnabled) + { + + + //it is possible to not have a connection yet, and someone might try to newnym + if (conn != null) + { + new Thread () + { + public void run () + { + try { + + conn.setConf("DisableNetwork", isEnabled ? "0" : "1"); + + } + catch (Exception ioe){ + debug("error requesting newnym: " + ioe.getLocalizedMessage()); + } + } + }.start(); + } + + } + public void newIdentity () { //it is possible to not have a connection yet, and someone might try to newnym @@ -2048,52 +2085,75 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo netInfo = cm.getActiveNetworkInfo();
+ boolean newConnectivityState = false; + if(netInfo != null && netInfo.isConnected()) { // WE ARE CONNECTED: DO SOMETHING - mConnectivity = true; + newConnectivityState = true; } else { // WE ARE NOT: DO SOMETHING ELSE - mConnectivity = false; + newConnectivityState = false; }
- if (doNetworKSleep) + //is this a change in state? + if (mConnectivity != newConnectivityState) { - try { - updateConfiguration("DisableNetwork", mConnectivity ? "0" : "1", false); - - if (mCurrentStatus != STATUS_OFF) - { - if (!mConnectivity) - { - logNotice(context.getString(R.string.no_network_connectivity_putting_tor_to_sleep_)); - showToolbarNotification(getString(R.string.no_internet_connection_tor),NOTIFY_ID,R.drawable.ic_stat_tor_off); - - } - else - { - logNotice(context.getString(R.string.network_connectivity_is_good_waking_tor_up_)); - showToolbarNotification(getString(R.string.status_activated),NOTIFY_ID,R.drawable.ic_stat_tor); - - if (mHasRoot && mEnableTransparentProxy && mTransProxyNetworkRefresh) - { - - Shell shell = Shell.startRootShell(); - - disableTransparentProxy(shell); - enableTransparentProxy(shell); - - shell.close(); - } - - } - } - - } catch (Exception e) { - logException ("error updating state after network restart",e); - } + + if (doNetworKSleep) + { + try { + + setTorNetworkEnabled (mConnectivity); + + if (mCurrentStatus != STATUS_OFF) + { + if (!mConnectivity) + { + logNotice(context.getString(R.string.no_network_connectivity_putting_tor_to_sleep_)); + showToolbarNotification(getString(R.string.no_internet_connection_tor),NOTIFY_ID,R.drawable.ic_stat_tor_off); + + } + else + { + logNotice(context.getString(R.string.network_connectivity_is_good_waking_tor_up_)); + showToolbarNotification(getString(R.string.status_activated),NOTIFY_ID,R.drawable.ic_stat_tor); + + if (mHasRoot && mEnableTransparentProxy && mTransProxyNetworkRefresh) + { + + Shell shell = Shell.startRootShell(); + + disableTransparentProxy(shell); + enableTransparentProxy(shell); + + shell.close(); + } + + } + } + + saveConfiguration(); + + } catch (Exception e) { + logException ("error updating state after network restart",e); + } + } + + if (mUseVPN && mConnectivity && (mCurrentStatus != STATUS_OFF)) //we need to turn on VPN here so the proxy is running + { + setTorNetworkEnabled (false); + refreshVpnProxy(); + setTorNetworkEnabled (true); + + + } }
+ mConnectivity = newConnectivityState; + + + } };
diff --git a/src/org/torproject/android/vpn/OrbotVpnService.java b/src/org/torproject/android/vpn/OrbotVpnService.java index 2127562..b495937 100644 --- a/src/org/torproject/android/vpn/OrbotVpnService.java +++ b/src/org/torproject/android/vpn/OrbotVpnService.java @@ -57,6 +57,10 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
private final static int VPN_MTU = 1500;
+ private final static boolean isLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + + private boolean isRestart = false; + @Override public int onStartCommand(Intent intent, int flags, int startId) {
@@ -76,7 +80,7 @@ public class OrbotVpnService extends VpnService implements Handler.Callback { // Stop the previous session by interrupting the thread. if (mThreadVPN == null || (!mThreadVPN.isAlive())) { - boolean isLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + if (!isLollipop) startSocksBypass(); @@ -89,6 +93,13 @@ public class OrbotVpnService extends VpnService implements Handler.Callback { if (mHandler != null) mHandler.postDelayed(new Runnable () { public void run () { stopSelf(); }}, 1000); } + else if (action.equals("refresh")) + { + if (!isLollipop) + startSocksBypass(); + + setupTun2Socks(); + }
return START_NOT_STICKY; @@ -102,6 +113,12 @@ public class OrbotVpnService extends VpnService implements Handler.Callback { {
try { + + if (mSocksProxyServer != null) + { + stopSocksBypass (); + } + mSocksProxyServer = new ProxyServer(new ServerAuthenticatorNone(null, null)); ProxyServer.setVpnService(OrbotVpnService.this); mSocksProxyServer.start(mSocksProxyPort, 5, InetAddress.getLocalHost()); @@ -115,10 +132,20 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
}
+ private void stopSocksBypass () + { + + if (mSocksProxyServer != null){ + mSocksProxyServer.stop(); + mSocksProxyServer = null; + } + + + } + @Override public void onDestroy() { stopVPN(); - }
private void stopVPN () @@ -126,10 +153,7 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
Tun2Socks.Stop();
- if (mSocksProxyServer != null){ - mSocksProxyServer.stop(); - mSocksProxyServer = null; - } + stopSocksBypass ();
if (mInterface != null){ try @@ -167,54 +191,64 @@ public class OrbotVpnService extends VpnService implements Handler.Callback { public void run () { - if (mInterface == null) - { - try + try + { + + // Set the locale to English (or probably any other language that^M + // uses Hindu-Arabic (aka Latin) numerals).^M + // We have found that VpnService.Builder does something locale-dependent^M + // internally that causes errors when the locale uses its own numerals^M + // (i.e., Farsi and Arabic).^M + Locale.setDefault(new Locale("en")); + + //String localhost = InetAddress.getLocalHost().getHostAddress(); + + String vpnName = "OrbotVPN"; + String virtualGateway = "10.0.0.1"; + String virtualIP = "10.0.0.2"; + String virtualNetMask = "255.255.255.0"; + String localSocks = "127.0.0.1" + ':' + TorServiceConstants.PORT_SOCKS_DEFAULT; + String localDNS = "10.0.0.1" + ':' + TorServiceConstants.TOR_DNS_PORT_DEFAULT; + + + Builder builder = new Builder(); + + builder.setMtu(VPN_MTU); + builder.addAddress(virtualGateway,28); + builder.setSession(vpnName); + builder.addRoute("0.0.0.0",0); + // builder.addDnsServer("8.8.8.8"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - - // Set the locale to English (or probably any other language that^M - // uses Hindu-Arabic (aka Latin) numerals).^M - // We have found that VpnService.Builder does something locale-dependent^M - // internally that causes errors when the locale uses its own numerals^M - // (i.e., Farsi and Arabic).^M - Locale.setDefault(new Locale("en")); - - //String localhost = InetAddress.getLocalHost().getHostAddress(); - - String vpnName = "OrbotVPN"; - String virtualGateway = "10.0.0.1"; - String virtualIP = "10.0.0.2"; - String virtualNetMask = "255.255.255.0"; - String localSocks = "127.0.0.1" + ':' + TorServiceConstants.PORT_SOCKS_DEFAULT; - String localDNS = "10.0.0.1" + ':' + TorServiceConstants.TOR_DNS_PORT_DEFAULT; - - - Builder builder = new Builder(); - - builder.setMtu(VPN_MTU); - builder.addAddress(virtualGateway,28); - builder.setSession(vpnName); - builder.addRoute("0.0.0.0",0); - // builder.addDnsServer("8.8.8.8"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - { - doLollipopAppRouting(builder); - } - - // Create a new interface using the builder and save the parameters. - mInterface = builder.setSession(mSessionName) - .setConfigureIntent(mConfigureIntent) - .establish(); - - Tun2Socks.Start(mInterface, VPN_MTU, virtualIP, virtualNetMask, localSocks , localDNS , true); + doLollipopAppRouting(builder); } - catch (Exception e) + + // Create a new interface using the builder and save the parameters. + ParcelFileDescriptor newInterface = builder.setSession(mSessionName) + .setConfigureIntent(mConfigureIntent) + .establish(); + + if (mInterface != null) { - Log.d(TAG,"tun2Socks has stopped",e); + isRestart = true; + + Tun2Socks.Stop(); + mInterface.close(); + } - } - } + + + mInterface = newInterface; + + Tun2Socks.Start(mInterface, VPN_MTU, virtualIP, virtualNetMask, localSocks , localDNS , true); + } + catch (Exception e) + { + Log.d(TAG,"tun2Socks has stopped",e); + } + } + }; mThreadVPN.start(); @@ -222,22 +256,23 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
@TargetApi(Build.VERSION_CODES.LOLLIPOP) private void doLollipopAppRouting (Builder builder) throws NameNotFoundException - { - - - builder.addDisallowedApplication("org.torproject.android"); - - + { + builder.addDisallowedApplication("org.torproject.android"); }
@Override public void onRevoke() {
- SharedPreferences prefs = TorServiceUtils.getSharedPrefs(getApplicationContext()); - prefs.edit().putBoolean("pref_vpn", false).commit(); - - stopVPN(); - + if (!isRestart) + { + SharedPreferences prefs = TorServiceUtils.getSharedPrefs(getApplicationContext()); + prefs.edit().putBoolean("pref_vpn", false).commit(); + stopVPN(); + + } + + isRestart = false; + super.onRevoke(); }