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.
This commit is contained in:
Nathan Freitas 2015-04-03 12:27:59 -04:00
parent d14dabb4f3
commit 75426bb9e2
6 changed files with 359 additions and 140 deletions

View File

@ -36,9 +36,16 @@
<item>ru</item> <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> </string-array>
</resources> </resources>

View File

@ -315,15 +315,16 @@
<string name="bridges_updated">Bridges Updated</string> <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="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="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> <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="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="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> </resources>

View File

@ -16,7 +16,7 @@ public interface OrbotConstants {
//path to check Tor against //path to check Tor against
public final static String URL_TOR_CHECK = "https://check.torproject.org"; 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"; public final static String NEWLINE = "\n";

View File

@ -661,6 +661,8 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon
setResult(RESULT_OK); setResult(RESULT_OK);
mBtnBridges.setChecked(true); mBtnBridges.setChecked(true);
enableBridges(true);
} }
private boolean showWizard = true; private boolean showWizard = true;
@ -707,7 +709,7 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon
.setIcon(R.drawable.onion32) .setIcon(R.drawable.onion32)
.setTitle(R.string.install_apps_) .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_) .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 @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 @Override
@ -860,41 +862,56 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setTitle(R.string.bridge_mode) .setTitle(R.string.bridge_mode)
.setView(view) .setView(view)
.setNeutralButton(R.string.get_bridges, new Dialog.OnClickListener () .setItems(R.array.bridge_options, new DialogInterface.OnClickListener() {
{
@Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
// The 'which' argument contains the index position
// of the selected item
//openBrowser(URL_TOR_BRIDGES); switch (which)
sendGetBridgeEmail();
}
})
.setPositiveButton(R.string.activate, new Dialog.OnClickListener ()
{ {
case 0: //obfs 4;
showGetBridgePrompt("obfs4");
@Override break;
public void onClick(DialogInterface dialog, int which) { case 1: //obfs3
showGetBridgePrompt("obfs3");
enableBridges (true); 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()
.setNegativeButton(android.R.string.cancel, new Dialog.OnClickListener()
{ {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
mBtnBridges.setChecked(false); //mBtnBridges.setChecked(false);
} }
}) })
.show(); .show();
} }
else else
{ {
@ -903,12 +920,71 @@ 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 intent = new Intent(Intent.ACTION_SEND);
intent.setType("message/rfc822"); intent.setType("message/rfc822");
intent.putExtra(Intent.EXTRA_EMAIL , new String[]{"bridges@torproject.org"}); 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))); 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(); Editor edit = mPrefs.edit();
edit.putBoolean("pref_bridges_enabled", enable); edit.putBoolean(OrbotConstants.PREF_BRIDGES_ENABLED, enable);
edit.commit(); edit.commit();
updateSettings(); 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 () 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 //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 //might be best to just call updateStatus() instead of directly manipulating UI in this method - yep makes sense
imgStatus.setImageResource(R.drawable.torstarting); 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 //we send a message here to the progressDialog i believe, but we can clarify that shortly
Message msg = mHandler.obtainMessage(TorServiceConstants.ENABLE_TOR_MSG); Message msg = mHandler.obtainMessage(TorServiceConstants.ENABLE_TOR_MSG);
@ -1168,7 +1279,6 @@ public class OrbotMainActivity extends Activity implements OrbotConstants, OnLon
mHandler.sendMessage(msg); mHandler.sendMessage(msg);
} }
//now we stop Tor! amazing! //now we stop Tor! amazing!

View File

@ -96,7 +96,7 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
private int mPortHTTP = 8118; private int mPortHTTP = 8118;
private int mPortSOCKS = 9050; private int mPortSOCKS = 9050;
private int mVpnProxyPort = 7231; private int mVpnProxyPort = 9099;
private static final int NOTIFY_ID = 1; private static final int NOTIFY_ID = 1;
private static final int TRANSPROXY_NOTIFY_ID = 2; 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 () public void clearVpnProxy ()
{ {
debug ("clearing VPN Proxy"); debug ("clearing VPN Proxy");
@ -1911,6 +1923,31 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
return false; 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 () public void newIdentity ()
{ {
//it is possible to not have a connection yet, and someone might try to newnym //it is possible to not have a connection yet, and someone might try to newnym
@ -2048,19 +2085,26 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo netInfo = cm.getActiveNetworkInfo(); final NetworkInfo netInfo = cm.getActiveNetworkInfo();
boolean newConnectivityState = false;
if(netInfo != null && netInfo.isConnected()) { if(netInfo != null && netInfo.isConnected()) {
// WE ARE CONNECTED: DO SOMETHING // WE ARE CONNECTED: DO SOMETHING
mConnectivity = true; newConnectivityState = true;
} }
else { else {
// WE ARE NOT: DO SOMETHING ELSE // WE ARE NOT: DO SOMETHING ELSE
mConnectivity = false; newConnectivityState = false;
} }
//is this a change in state?
if (mConnectivity != newConnectivityState)
{
if (doNetworKSleep) if (doNetworKSleep)
{ {
try { try {
updateConfiguration("DisableNetwork", mConnectivity ? "0" : "1", false);
setTorNetworkEnabled (mConnectivity);
if (mCurrentStatus != STATUS_OFF) if (mCurrentStatus != STATUS_OFF)
{ {
@ -2089,11 +2133,27 @@ public class TorService extends Service implements TorServiceConstants, OrbotCon
} }
} }
saveConfiguration();
} catch (Exception e) { } catch (Exception e) {
logException ("error updating state after network restart",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;
} }
}; };

View File

@ -57,6 +57,10 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
private final static int VPN_MTU = 1500; 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 @Override
public int onStartCommand(Intent intent, int flags, int startId) { 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. // Stop the previous session by interrupting the thread.
if (mThreadVPN == null || (!mThreadVPN.isAlive())) if (mThreadVPN == null || (!mThreadVPN.isAlive()))
{ {
boolean isLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
if (!isLollipop) if (!isLollipop)
startSocksBypass(); startSocksBypass();
@ -89,6 +93,13 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
if (mHandler != null) if (mHandler != null)
mHandler.postDelayed(new Runnable () { public void run () { stopSelf(); }}, 1000); mHandler.postDelayed(new Runnable () { public void run () { stopSelf(); }}, 1000);
} }
else if (action.equals("refresh"))
{
if (!isLollipop)
startSocksBypass();
setupTun2Socks();
}
return START_NOT_STICKY; return START_NOT_STICKY;
@ -102,6 +113,12 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
{ {
try { try {
if (mSocksProxyServer != null)
{
stopSocksBypass ();
}
mSocksProxyServer = new ProxyServer(new ServerAuthenticatorNone(null, null)); mSocksProxyServer = new ProxyServer(new ServerAuthenticatorNone(null, null));
ProxyServer.setVpnService(OrbotVpnService.this); ProxyServer.setVpnService(OrbotVpnService.this);
mSocksProxyServer.start(mSocksProxyPort, 5, InetAddress.getLocalHost()); 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 @Override
public void onDestroy() { public void onDestroy() {
stopVPN(); stopVPN();
} }
private void stopVPN () private void stopVPN ()
@ -126,10 +153,7 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
Tun2Socks.Stop(); Tun2Socks.Stop();
if (mSocksProxyServer != null){ stopSocksBypass ();
mSocksProxyServer.stop();
mSocksProxyServer = null;
}
if (mInterface != null){ if (mInterface != null){
try try
@ -166,8 +190,6 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
{ {
public void run () public void run ()
{
if (mInterface == null)
{ {
try try
{ {
@ -203,10 +225,22 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
} }
// Create a new interface using the builder and save the parameters. // Create a new interface using the builder and save the parameters.
mInterface = builder.setSession(mSessionName) ParcelFileDescriptor newInterface = builder.setSession(mSessionName)
.setConfigureIntent(mConfigureIntent) .setConfigureIntent(mConfigureIntent)
.establish(); .establish();
if (mInterface != null)
{
isRestart = true;
Tun2Socks.Stop();
mInterface.close();
}
mInterface = newInterface;
Tun2Socks.Start(mInterface, VPN_MTU, virtualIP, virtualNetMask, localSocks , localDNS , true); Tun2Socks.Start(mInterface, VPN_MTU, virtualIP, virtualNetMask, localSocks , localDNS , true);
} }
catch (Exception e) catch (Exception e)
@ -214,7 +248,7 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
Log.d(TAG,"tun2Socks has stopped",e); Log.d(TAG,"tun2Socks has stopped",e);
} }
} }
}
}; };
mThreadVPN.start(); mThreadVPN.start();
@ -223,21 +257,22 @@ public class OrbotVpnService extends VpnService implements Handler.Callback {
@TargetApi(Build.VERSION_CODES.LOLLIPOP) @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void doLollipopAppRouting (Builder builder) throws NameNotFoundException private void doLollipopAppRouting (Builder builder) throws NameNotFoundException
{ {
builder.addDisallowedApplication("org.torproject.android"); builder.addDisallowedApplication("org.torproject.android");
} }
@Override @Override
public void onRevoke() { public void onRevoke() {
if (!isRestart)
{
SharedPreferences prefs = TorServiceUtils.getSharedPrefs(getApplicationContext()); SharedPreferences prefs = TorServiceUtils.getSharedPrefs(getApplicationContext());
prefs.edit().putBoolean("pref_vpn", false).commit(); prefs.edit().putBoolean("pref_vpn", false).commit();
stopVPN(); stopVPN();
}
isRestart = false;
super.onRevoke(); super.onRevoke();
} }