summaryrefslogtreecommitdiff
path: root/main/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/main/java')
-rw-r--r--main/src/main/java/de/blinkt/openvpn/LaunchVPN.java58
-rw-r--r--main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java16
-rw-r--r--main/src/main/java/de/blinkt/openvpn/VpnProfile.java16
-rw-r--r--main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java3
-rw-r--r--main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java157
-rw-r--r--main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java48
-rw-r--r--main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java10
-rw-r--r--main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java1
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java1
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java1
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java18
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/LogItem.java46
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java22
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java6
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java22
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java275
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java3
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java42
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java18
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/StatusListener.java55
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java4
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java49
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/X509Utils.java6
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/keepVPNAlive.java134
24 files changed, 749 insertions, 262 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java
index 86eef35a..90ea053a 100644
--- a/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java
+++ b/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java
@@ -73,6 +73,7 @@ public class LaunchVPN extends Activity {
public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID";
public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName";
public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow";
+ public static final String EXTRA_START_REASON = "de.blinkt.openvpn.start_reason";
public static final String CLEARLOG = "clearlogconnect";
@@ -85,6 +86,7 @@ public class LaunchVPN extends Activity {
private boolean mCmfixed = false;
private String mTransientAuthPW;
private String mTransientCertOrPCKS12PW;
+ private String mSelectedProfileReason;
@Override
public void onCreate(Bundle icicle) {
@@ -99,7 +101,6 @@ public class LaunchVPN extends Activity {
IServiceStatus service = IServiceStatus.Stub.asInterface(binder);
try {
if (mTransientAuthPW != null)
-
service.setCachedPassword(mSelectedProfile.getUUIDString(), PasswordCache.AUTHPASSWORD, mTransientAuthPW);
if (mTransientCertOrPCKS12PW != null)
service.setCachedPassword(mSelectedProfile.getUUIDString(), PasswordCache.PCKS12ORCERTPASSWORD, mTransientCertOrPCKS12PW);
@@ -126,38 +127,41 @@ public class LaunchVPN extends Activity {
final String action = intent.getAction();
// If the intent is a request to create a shortcut, we'll do that and exit
+ if (!Intent.ACTION_MAIN.equals(action)) {
+ return;
+ }
+ // Check if we need to clear the log
+ if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true))
+ VpnStatus.clearLog();
- if (Intent.ACTION_MAIN.equals(action)) {
- // Check if we need to clear the log
- if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true))
- VpnStatus.clearLog();
-
- // we got called to be the starting point, most likely a shortcut
- String shortcutUUID = intent.getStringExtra(EXTRA_KEY);
- String shortcutName = intent.getStringExtra(EXTRA_NAME);
- mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false);
+ // we got called to be the starting point, most likely a shortcut
+ String shortcutUUID = intent.getStringExtra(EXTRA_KEY);
+ String shortcutName = intent.getStringExtra(EXTRA_NAME);
+ String startReason = intent.getStringExtra(EXTRA_START_REASON);
+ mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false);
- VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID);
- if (shortcutName != null && profileToConnect == null) {
- profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName);
- if (!(new ExternalAppDatabase(this).checkRemoteActionPermission(this, getCallingPackage()))) {
- finish();
- return;
- }
+ VpnProfile profileToConnect = ProfileManager.get(this, shortcutUUID);
+ if (shortcutName != null && profileToConnect == null) {
+ profileToConnect = ProfileManager.getInstance(this).getProfileByName(shortcutName);
+ if (!(new ExternalAppDatabase(this).checkRemoteActionPermission(this, getCallingPackage()))) {
+ finish();
+ return;
}
+ }
- if (profileToConnect == null) {
- VpnStatus.logError(R.string.shortcut_profile_notfound);
- // show Log window to display error
- showLogWindow();
- finish();
- } else {
- mSelectedProfile = profileToConnect;
- launchVPN();
- }
+ if (profileToConnect == null) {
+ VpnStatus.logError(R.string.shortcut_profile_notfound);
+ // show Log window to display error
+ showLogWindow();
+ finish();
+ } else {
+ mSelectedProfile = profileToConnect;
+ mSelectedProfileReason = startReason;
+ launchVPN();
}
+
}
private void askForPW(final int type) {
@@ -251,7 +255,7 @@ public class LaunchVPN extends Activity {
if (!mhideLog && showLogWindow)
showLogWindow();
ProfileManager.updateLRU(this, mSelectedProfile);
- VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
+ VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext(), mSelectedProfileReason);
finish();
}
} else if (resultCode == Activity.RESULT_CANCELED) {
diff --git a/main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java b/main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java
index ebfab5b2..58c954c9 100644
--- a/main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java
+++ b/main/src/main/java/de/blinkt/openvpn/OnBootReceiver.java
@@ -12,12 +12,10 @@ import android.content.SharedPreferences;
import de.blinkt.openvpn.core.Preferences;
import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.core.VPNLaunchHelper;
public class OnBootReceiver extends BroadcastReceiver {
-
-
-
// Debug: am broadcast -a android.intent.action.BOOT_COMPLETED
@Override
public void onReceive(Context context, Intent intent) {
@@ -25,8 +23,8 @@ public class OnBootReceiver extends BroadcastReceiver {
final String action = intent.getAction();
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(context);
- boolean useStartOnBoot = prefs.getBoolean("restartvpnonboot", false);
- if (!useStartOnBoot)
+ boolean alwaysActive = prefs.getBoolean("restartvpnonboot", false);
+ if (!alwaysActive)
return;
if(Intent.ACTION_BOOT_COMPLETED.equals(action) || Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) {
@@ -38,12 +36,6 @@ public class OnBootReceiver extends BroadcastReceiver {
}
void launchVPN(VpnProfile profile, Context context) {
- Intent startVpnIntent = new Intent(Intent.ACTION_MAIN);
- startVpnIntent.setClass(context, LaunchVPN.class);
- startVpnIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUIDString());
- startVpnIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startVpnIntent.putExtra(LaunchVPN.EXTRA_HIDELOG, true);
-
- context.startActivity(startVpnIntent);
+ VPNLaunchHelper.startOpenVpn(profile, context.getApplicationContext(), "on Boot receiver");
}
}
diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 3163bf27..0da09eb0 100644
--- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -809,18 +809,6 @@ public class VpnProfile implements Serializable, Cloneable {
return parts[0] + " " + netmask;
}
- public Intent prepareStartService(Context context) {
- Intent intent = getStartServiceIntent(context);
-
- // TODO: Handle this?!
-// if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
-// if (getKeyStoreCertificates(context) == null)
-// return null;
-// }
-
- return intent;
- }
-
public void writeConfigFileOutput(Context context, OutputStream out) throws IOException {
OutputStreamWriter cfg = new OutputStreamWriter(out);
cfg.write(getConfigFile(context, false));
@@ -828,12 +816,14 @@ public class VpnProfile implements Serializable, Cloneable {
cfg.close();
}
- public Intent getStartServiceIntent(Context context) {
+ public Intent getStartServiceIntent(Context context, String startReason) {
String prefix = context.getPackageName();
Intent intent = new Intent(context, OpenVPNService.class);
intent.putExtra(prefix + ".profileUUID", mUuid.toString());
intent.putExtra(prefix + ".profileVersion", mVersion);
+ if (startReason != null)
+ intent.putExtra(prefix + ".startReason", startReason);
return intent;
}
diff --git a/main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java b/main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java
index 01dacfcd..6cc170fa 100644
--- a/main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java
+++ b/main/src/main/java/de/blinkt/openvpn/activities/DisconnectVPN.java
@@ -27,7 +27,7 @@ import de.blinkt.openvpn.core.VpnStatus;
*/
public class DisconnectVPN extends Activity implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
private IOpenVPNServiceInternal mService;
- private ServiceConnection mConnection = new ServiceConnection() {
+ private final ServiceConnection mConnection = new ServiceConnection() {
@@ -86,6 +86,7 @@ public class DisconnectVPN extends Activity implements DialogInterface.OnClickLi
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
Intent intent = new Intent(this, LaunchVPN.class);
intent.putExtra(LaunchVPN.EXTRA_KEY, VpnStatus.getLastConnectedVPNProfile());
+ intent.putExtra(LaunchVPN.EXTRA_START_REASON, "Reconnect button pressed.");
intent.setAction(Intent.ACTION_MAIN);
startActivity(intent);
}
diff --git a/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java b/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java
index 175ecb70..53919216 100644
--- a/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java
+++ b/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java
@@ -14,7 +14,7 @@ import android.text.TextUtils;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.ConfigParser;
-import de.blinkt.openvpn.core.Connection;
+import de.blinkt.openvpn.core.OpenVPNService;
import de.blinkt.openvpn.core.Preferences;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.core.VpnStatus;
@@ -22,6 +22,7 @@ import de.blinkt.openvpn.core.VpnStatus;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@@ -33,16 +34,15 @@ public class AppRestrictions {
final static int CONFIG_VERSION = 1;
static boolean alreadyChecked = false;
private static AppRestrictions mInstance;
- private RestrictionsManager mRestrictionsMgr;
private BroadcastReceiver mRestrictionsReceiver;
- private AppRestrictions(Context c) {
+ private AppRestrictions() {
}
public static AppRestrictions getInstance(Context c) {
if (mInstance == null)
- mInstance = new AppRestrictions(c);
+ mInstance = new AppRestrictions();
return mInstance;
}
@@ -62,11 +62,12 @@ public class AppRestrictions {
c.unregisterReceiver(mRestrictionsReceiver);
}
- private String hashConfig(String config) {
+ private String hashConfig(String rawconfig) {
+ String config = prepare(rawconfig);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA1");
- byte utf8_bytes[] = config.getBytes();
+ byte[] utf8_bytes = config.getBytes(StandardCharsets.UTF_8);
digest.update(utf8_bytes, 0, utf8_bytes.length);
return new BigInteger(1, digest.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
@@ -76,10 +77,14 @@ public class AppRestrictions {
}
private void applyRestrictions(Context c) {
- mRestrictionsMgr = (RestrictionsManager) c.getSystemService(Context.RESTRICTIONS_SERVICE);
- if (mRestrictionsMgr == null)
+ RestrictionsManager restrictionsMgr = (RestrictionsManager) c.getSystemService(Context.RESTRICTIONS_SERVICE);
+ if (restrictionsMgr == null)
return;
- Bundle restrictions = mRestrictionsMgr.getApplicationRestrictions();
+ Bundle restrictions = restrictionsMgr.getApplicationRestrictions();
+ parseRestrictionsBundle(c, restrictions);
+ }
+ public void parseRestrictionsBundle(Context c, Bundle restrictions)
+ {
if (restrictions == null)
return;
@@ -94,12 +99,57 @@ public class AppRestrictions {
VpnStatus.logError(String.format(Locale.US, "App restriction version %s does not match expected version %d", configVersion, CONFIG_VERSION));
return;
}
- Parcelable[] profileList = restrictions.getParcelableArray(("vpn_configuration_list"));
+ Parcelable[] profileList = restrictions.getParcelableArray("vpn_configuration_list");
if (profileList == null) {
- VpnStatus.logError("App restriction does not contain a profile list (vpn_configuration_list)");
+ VpnStatus.logInfo("App restriction does not contain a profile list. Removing previously added profiles. (vpn_configuration_list)");
+ profileList = new Parcelable[]{};
+ }
+
+ importVPNProfiles(c, restrictions, profileList);
+ setAllowedRemoteControl(c, restrictions);
+
+ setMiscSettings(c, restrictions);
+ }
+
+ private void setAllowedRemoteControl(Context c, Bundle restrictions) {
+ String allowedApps = restrictions.getString("allowed_remote_access", null);
+ ExternalAppDatabase extapps = new ExternalAppDatabase(c);
+
+ if (allowedApps == null)
+ {
+ extapps.setFlagManagedConfiguration(false);
return;
}
+ HashSet<String> restrictionApps = new HashSet<>();
+
+ for (String package_name:allowedApps.split("[, \n\r]")) {
+ if (!TextUtils.isEmpty(package_name)) {
+ restrictionApps.add(package_name);
+ }
+ }
+ extapps.setFlagManagedConfiguration(true);
+ extapps.clearAllApiApps();
+
+ if(!extapps.getExtAppList().equals(restrictionApps))
+ {
+ extapps.setAllowedApps(restrictionApps);
+ }
+ }
+
+ private static void setMiscSettings(Context c, Bundle restrictions) {
+ SharedPreferences defaultPrefs = Preferences.getDefaultSharedPreferences(c);
+
+ if(restrictions.containsKey("screenoffpausevpn"))
+ {
+ boolean pauseVPN = restrictions.getBoolean("screenoffpausevpn");
+ SharedPreferences.Editor editor = defaultPrefs.edit();
+ editor.putBoolean("screenoff", pauseVPN);
+ editor.apply();
+ }
+ }
+
+ private void importVPNProfiles(Context c, Bundle restrictions, Parcelable[] profileList) {
Set<String> provisionedUuids = new HashSet<>();
String defaultprofile = restrictions.getString("defaultprofile", null);
@@ -116,12 +166,19 @@ public class AppRestrictions {
String uuid = p.getString("uuid");
String ovpn = p.getString("ovpn");
String name = p.getString("name");
+ String certAlias = p.getString("certificate_alias");
- if (uuid == null || ovpn == null || name == null) {
+ if (TextUtils.isEmpty(uuid) || TextUtils.isEmpty(ovpn) || TextUtils.isEmpty(name)) {
VpnStatus.logError("App restriction profile misses uuid, ovpn or name key");
continue;
}
+ /* we always use lower case uuid since Android UUID class will use present
+ * them that way */
+ uuid = uuid.toLowerCase(Locale.US);
+ if (defaultprofile != null)
+ defaultprofile = defaultprofile.toLowerCase(Locale.US);
+
if (uuid.equals(defaultprofile))
defaultprofileProvisioned = true;
@@ -134,12 +191,15 @@ public class AppRestrictions {
if (vpnProfile != null) {
// Profile exists, check if need to update it
- if (ovpnHash.equals(vpnProfile.importedProfileHash))
+ if (ovpnHash.equals(vpnProfile.importedProfileHash)) {
+ addCertificateAlias(vpnProfile, certAlias, c);
+
// not modified skip to next profile
continue;
-
+ }
}
- addProfile(c, ovpn, uuid, name, vpnProfile);
+ vpnProfile = addProfile(c, ovpn, uuid, name, vpnProfile);
+ addCertificateAlias(vpnProfile, certAlias, c);
}
Vector<VpnProfile> profilesToRemove = new Vector<>();
@@ -155,22 +215,79 @@ public class AppRestrictions {
pm.removeProfile(c, vp);
}
+ SharedPreferences defaultPrefs = Preferences.getDefaultSharedPreferences(c);
+
if (!TextUtils.isEmpty(defaultprofile)) {
if (!defaultprofileProvisioned) {
VpnStatus.logError("App restrictions: Setting a default profile UUID without providing a profile with that UUID");
} else {
- SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
- String uuid = prefs.getString("alwaysOnVpn", null);
+ String uuid = defaultPrefs.getString("alwaysOnVpn", null);
if (!defaultprofile.equals(uuid))
{
- SharedPreferences.Editor editor = prefs.edit();
+ SharedPreferences.Editor editor = defaultPrefs.edit();
editor.putString("alwaysOnVpn", defaultprofile);
editor.apply();
+
}
}
}
}
+ /**
+ * If certAlias is non-null will modify the profile type to use the keystore variant of
+ * the authentication method and will also set the keystore alias
+ */
+ private void addCertificateAlias(VpnProfile vpnProfile, String certAlias, Context c) {
+ if (vpnProfile == null)
+ return;
+
+ if (certAlias == null)
+ certAlias = "";
+
+ int oldType = vpnProfile.mAuthenticationType;
+ String oldAlias = vpnProfile.mAlias;
+
+ if (!TextUtils.isEmpty(certAlias)) {
+ switch (vpnProfile.mAuthenticationType)
+ {
+ case VpnProfile.TYPE_PKCS12:
+ case VpnProfile.TYPE_CERTIFICATES:
+ vpnProfile.mAuthenticationType = VpnProfile.TYPE_KEYSTORE;
+ break;
+ case VpnProfile.TYPE_USERPASS_CERTIFICATES:
+ case VpnProfile.TYPE_USERPASS_PKCS12:
+ vpnProfile.mAuthenticationType = VpnProfile.TYPE_USERPASS_KEYSTORE;
+ break;
+ }
+
+ } else
+ {
+ /* Alias is null, return to non keystore method */
+ boolean pkcs12present = !TextUtils.isEmpty(vpnProfile.mPKCS12Filename);
+ switch (vpnProfile.mAuthenticationType) {
+ case VpnProfile.TYPE_USERPASS_KEYSTORE:
+ if (pkcs12present)
+ vpnProfile.mAuthenticationType = VpnProfile.TYPE_USERPASS_PKCS12;
+ else
+ vpnProfile.mAuthenticationType = VpnProfile.TYPE_USERPASS_CERTIFICATES;
+ break;
+ case VpnProfile.TYPE_KEYSTORE:
+ if (pkcs12present)
+ vpnProfile.mAuthenticationType = VpnProfile.TYPE_PKCS12;
+ else
+ vpnProfile.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES;
+ break;
+ }
+ }
+ vpnProfile.mAlias = certAlias;
+
+ if (!certAlias.equals(oldAlias) || oldType != vpnProfile.mAuthenticationType)
+ {
+ ProfileManager pm = ProfileManager.getInstance(c);
+ pm.saveProfile(c, vpnProfile);
+ }
+ }
+
private String prepare(String config) {
String newLine = System.getProperty("line.separator");
if (!config.contains(newLine) && !config.contains(" ")) {
@@ -187,7 +304,7 @@ public class AppRestrictions {
;
- private void addProfile(Context c, String config, String uuid, String name, VpnProfile vpnProfile) {
+ VpnProfile addProfile(Context c, String config, String uuid, String name, VpnProfile vpnProfile) {
config = prepare(config);
ConfigParser cp = new ConfigParser();
try {
@@ -213,9 +330,11 @@ public class AppRestrictions {
pm.addProfile(vp);
pm.saveProfile(c, vp);
pm.saveProfileList(c);
+ return vp;
} catch (ConfigParser.ConfigParseError | IOException | IllegalArgumentException e) {
VpnStatus.logException("Error during import of managed profile", e);
+ return null;
}
}
diff --git a/main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java b/main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java
index 57e82778..fe0afdb6 100644
--- a/main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java
+++ b/main/src/main/java/de/blinkt/openvpn/api/ExternalAppDatabase.java
@@ -13,11 +13,13 @@ import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.widget.Toast;
import java.util.HashSet;
import java.util.Set;
import de.blinkt.openvpn.core.Preferences;
+import de.blinkt.openvpn.core.VpnStatus;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -30,12 +32,34 @@ public class ExternalAppDatabase {
}
private final static String PREFERENCES_KEY = "allowed_apps";
+ private final static String PREFERENCES_KEY_MANAGED_CONFIG = "allowed_apps_managed";
+
+ public void setFlagManagedConfiguration(boolean managed)
+ {
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext);
+ Editor prefedit = prefs.edit();
+
+ prefedit.putBoolean(PREFERENCES_KEY_MANAGED_CONFIG, managed);
+ increaseWorkaroundCounter(prefs, prefedit);
+ prefedit.apply();
+ }
+
+ public boolean isManagedConfiguration()
+ {
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext);
+ return prefs.getBoolean(PREFERENCES_KEY_MANAGED_CONFIG, false);
+ }
+
+ private static void increaseWorkaroundCounter(SharedPreferences prefs, Editor prefedit) {
+ // Workaround for bug
+ int counter = prefs.getInt("counter", 0);
+ prefedit.putInt("counter", counter + 1);
+ }
boolean isAllowed(String packagename) {
Set<String> allowedapps = getExtAppList();
- return allowedapps.contains(packagename);
-
+ return allowedapps.contains(packagename);
}
public Set<String> getExtAppList() {
@@ -50,14 +74,22 @@ public class ExternalAppDatabase {
saveExtAppList(allowedapps);
}
+ public boolean checkAllowingModifyingRemoteControl(Context c) {
+ if (isManagedConfiguration()) {
+ Toast.makeText(c, "Remote control apps are manged by managed configuration, cannot change", Toast.LENGTH_LONG).show();
+ VpnStatus.logError("Remote control apps are manged by managed configuration, cannot change");
+ return false;
+ }
+ return true;
+ }
+
private void saveExtAppList( Set<String> allowedapps) {
SharedPreferences prefs = Preferences.getDefaultSharedPreferences(mContext);
Editor prefedit = prefs.edit();
// Workaround for bug
prefedit.putStringSet(PREFERENCES_KEY, allowedapps);
- int counter = prefs.getInt("counter", 0);
- prefedit.putInt("counter", counter + 1);
+ increaseWorkaroundCounter(prefs, prefedit);
prefedit.apply();
}
@@ -83,9 +115,9 @@ public class ExternalAppDatabase {
}
} catch (PackageManager.NameNotFoundException e) {
// App not found. Remove it from the list
- removeApp(appPackage);
+ if (!isManagedConfiguration())
+ removeApp(appPackage);
}
-
}
throw new SecurityException("Unauthorized OpenVPN API Caller");
}
@@ -105,4 +137,8 @@ public class ExternalAppDatabase {
return false;
}
}
+
+ public void setAllowedApps(Set<String> restrictionApps) {
+ saveExtAppList(restrictionApps);
+ }
}
diff --git a/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java
index 690c349e..ab71f00b 100644
--- a/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java
+++ b/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java
@@ -139,16 +139,18 @@ public class ExternalOpenVPNService extends Service implements StateListener {
* Check if we need to ask for username/password */
int neddPassword = vp.needUserPWInput(null, null);
+ String startReason = "external OpenVPN service by uid: " + Binder.getCallingUid();
if(vpnPermissionIntent != null || neddPassword != 0){
Intent shortVPNIntent = new Intent(Intent.ACTION_MAIN);
shortVPNIntent.setClass(getBaseContext(), de.blinkt.openvpn.LaunchVPN.class);
shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, vp.getUUIDString());
shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_HIDELOG, true);
+ shortVPNIntent.putExtra(de.blinkt.openvpn.LaunchVPN.EXTRA_START_REASON, startReason);
shortVPNIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(shortVPNIntent);
} else {
- VPNLaunchHelper.startOpenVpn(vp, getBaseContext());
+ VPNLaunchHelper.startOpenVpn(vp, getBaseContext(), startReason);
}
}
@@ -237,6 +239,8 @@ public class ExternalOpenVPNService extends Service implements StateListener {
mExtAppDb.checkOpenVPNPermission(getPackageManager());
ProfileManager pm = ProfileManager.getInstance(getBaseContext());
VpnProfile vp = ProfileManager.get(getBaseContext(), profileUUID);
+ if (vp == null)
+ throw new RemoteException("Profile not found");
pm.removeProfile(ExternalOpenVPNService.this, vp);
}
@@ -244,7 +248,7 @@ public class ExternalOpenVPNService extends Service implements StateListener {
public boolean protectSocket(ParcelFileDescriptor pfd) throws RemoteException {
mExtAppDb.checkOpenVPNPermission(getPackageManager());
try {
- boolean success= mService.protect(pfd.getFd());
+ boolean success = mService.protect(pfd.getFd());
pfd.close();
return success;
} catch (IOException e) {
@@ -339,7 +343,7 @@ public class ExternalOpenVPNService extends Service implements StateListener {
- class UpdateMessage {
+ static class UpdateMessage {
public String state;
public String logmessage;
public ConnectionStatus level;
diff --git a/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java b/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java
index 0554b88c..22110ad0 100644
--- a/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java
+++ b/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java
@@ -93,6 +93,7 @@ public class RemoteAction extends Activity {
} else {
Intent startVPN = new Intent(this, LaunchVPN.class);
startVPN.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString());
+ startVPN.putExtra(LaunchVPN.EXTRA_START_REASON, ".api.ConnectVPN call");
startVPN.setAction(Intent.ACTION_MAIN);
startActivity(startVPN);
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java b/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java
index d102dce2..39151646 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java
@@ -42,7 +42,6 @@ public class ExtAuthHelper {
public static void setExternalAuthProviderSpinnerList(Spinner spinner, String selectedApp) {
Context c = spinner.getContext();
- final PackageManager pm = c.getPackageManager();
ArrayList<ExternalAuthProvider> extProviders = getExternalAuthProviderList(c);
int selectedPos = -1;
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java b/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
index 1df46525..271ec139 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java
@@ -128,5 +128,4 @@ public class ICSOpenVPNApplication extends Application {
mChannel.setLightColor(Color.CYAN);
mNotificationManager.createNotificationChannel(mChannel);
}
-
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java b/main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java
index 516e025d..ea9bf9e3 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/LocaleHelper.java
@@ -15,11 +15,23 @@ import java.util.Locale;
public class LocaleHelper {
static private Locale desiredLocale = null;
- public static void setDesiredLocale(Context c)
- {
+ public static void setDesiredLocale(Context c) {
Locale current = Locale.getDefault();
boolean defForce = true;
- if (current.getLanguage().equals(new Locale("de").getLanguage()))
+
+ /* Languages that have proofreaders */
+ String[] whitelisted = {new Locale("de").getLanguage(), new Locale("ja").getLanguage(),
+ new Locale("tr").getLanguage(), new Locale("zh-TW").getLanguage()};
+
+ String currentLanguage = current.getLanguage();
+ for (String lang : whitelisted) {
+ if (lang.equals(currentLanguage)) {
+ defForce = false;
+ break;
+ }
+ }
+
+ if (current.toLanguageTag().startsWith("zh-Hant"))
defForce = false;
boolean allow_translation = Preferences.getDefaultSharedPreferences(c).getBoolean("allow_translation", defForce);
diff --git a/main/src/main/java/de/blinkt/openvpn/core/LogItem.java b/main/src/main/java/de/blinkt/openvpn/core/LogItem.java
index c61cbc44..65714c43 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/LogItem.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/LogItem.java
@@ -13,11 +13,14 @@ import android.content.pm.Signature;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
@@ -26,7 +29,9 @@ import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.FormatFlagsConversionMismatchException;
import java.util.Locale;
+import java.util.MissingFormatArgumentException;
import java.util.UnknownFormatConversionException;
+import java.util.Vector;
import de.blinkt.openvpn.R;
@@ -47,6 +52,14 @@ public class LogItem implements Parcelable {
mArgs = args;
}
+ public LogItem(VpnStatus.LogLevel level, int verblevel, String message, long eventLogTime) {
+ mMessage = message;
+ mLevel = level;
+ mVerbosityLevel = verblevel;
+ logtime = eventLogTime;
+ }
+
+
public LogItem(VpnStatus.LogLevel level, int verblevel, String message) {
mMessage = message;
mLevel = level;
@@ -84,8 +97,6 @@ public class LogItem implements Parcelable {
other.mLevel.equals(mLevel)) &&
mVerbosityLevel == other.mVerbosityLevel &&
logtime == other.logtime;
-
-
}
public byte[] getMarschaledBytes() throws UnsupportedEncodingException, BufferOverflowException {
@@ -195,7 +206,7 @@ public class LogItem implements Parcelable {
}
private void marschalString(String str, ByteBuffer bb) throws UnsupportedEncodingException {
- byte[] utf8bytes = str.getBytes("UTF-8");
+ byte[] utf8bytes = str.getBytes(StandardCharsets.UTF_8);
bb.putInt(utf8bytes.length);
bb.put(utf8bytes);
}
@@ -204,7 +215,7 @@ public class LogItem implements Parcelable {
int len = bb.getInt();
byte[] utf8bytes = new byte[len];
bb.get(utf8bytes);
- return new String(utf8bytes, "UTF-8");
+ return new String(utf8bytes, StandardCharsets.UTF_8);
}
@@ -240,6 +251,11 @@ public class LogItem implements Parcelable {
mMessage = msg;
}
+ public LogItem(VpnStatus.LogLevel loglevel, String msg, long logEventTime) {
+ mLevel = loglevel;
+ mMessage = msg;
+ logtime = logEventTime;
+ }
public LogItem(VpnStatus.LogLevel loglevel, int ressourceId) {
mRessourceId = ressourceId;
@@ -258,7 +274,11 @@ public class LogItem implements Parcelable {
if (mArgs == null)
return c.getString(mRessourceId);
else
- return c.getString(mRessourceId, mArgs);
+ try {
+ return c.getString(mRessourceId, mArgs);
+ } catch (MissingFormatArgumentException ie) {
+ return "ERROR MISSING ARGUMENT(" + ie.getMessage() + "): " + getString(null);
+ }
} catch (Resources.NotFoundException re) {
return getString(null);
}
@@ -324,10 +344,15 @@ public class LogItem implements Parcelable {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray()));
MessageDigest md = MessageDigest.getInstance("SHA-1");
+ MessageDigest mdsha256 = MessageDigest.getInstance("SHA-256");
+
byte[] der = cert.getEncoded();
md.update(der);
byte[] digest = md.digest();
+ mdsha256.update(der);
+ byte[] digestSha256 = mdsha256.digest();
+
if (Arrays.equals(digest, VpnStatus.officalkey))
apksign = c.getString(R.string.official_build);
else if (Arrays.equals(digest, VpnStatus.officaldebugkey))
@@ -336,8 +361,15 @@ public class LogItem implements Parcelable {
apksign = "amazon version";
else if (Arrays.equals(digest, VpnStatus.fdroidkey))
apksign = "F-Droid built and signed version";
- else
- apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName());
+ else if (Arrays.equals(digestSha256, VpnStatus.officialO2Key))
+ apksign = c.getString(R.string.official_o2build);
+ else {
+ Vector<String> hexnums = new Vector<>();
+ for (byte b: digestSha256) {
+ hexnums.add(String.format(Locale.US, "%02x", b));
+ }
+ apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName(), TextUtils.join(":", hexnums));
+ }
PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
version = packageinfo.versionName;
diff --git a/main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
index 72b2b784..9bfa5e67 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
@@ -30,18 +30,28 @@ public class NativeUtils {
public static native String getOpenVPN3GitVersion();
- static boolean rsspssloaded = false;
+ private static native String getOpenSSLVersionString();
+
+ public static String getOpenSSLVersion() {
+ loadOsslUtil();
+ return getOpenSSLVersionString();
+ }
+
+ static boolean osslutilloaded = false;
public static byte[] addRssPssPadding(int hashtype, int MSBits, int rsa_size, byte[] from)
{
- if (!rsspssloaded) {
- rsspssloaded = true;
- System.loadLibrary("rsapss");
- }
-
+ loadOsslUtil();
return rsapss(hashtype, MSBits, rsa_size, from);
}
+ private static void loadOsslUtil() {
+ if (!osslutilloaded) {
+ osslutilloaded = true;
+ System.loadLibrary("osslutil");
+ }
+ }
+
private static native byte[] rsapss(int hashtype, int MSBits, int rsa_size, byte[] from);
public final static int[] openSSLlengths = {
diff --git a/main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java b/main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java
index 9c8cf363..da53831b 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java
@@ -5,11 +5,13 @@
package de.blinkt.openvpn.core;
+import android.annotation.TargetApi;
import android.net.IpPrefix;
+import android.os.Build;
import androidx.annotation.NonNull;
-import java.lang.reflect.Array;
+import java.lang.annotation.Target;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -132,6 +134,7 @@ public class NetworkSpace {
}
+ @NonNull
@Override
public String toString() {
//String in = included ? "+" : "-";
@@ -210,6 +213,7 @@ public class NetworkSpace {
}
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
public IpPrefix getPrefix() throws UnknownHostException {
if (isV4){
/* add 0x01 00 00 00 00, so that all representations are 5 byte otherwise
diff --git a/main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java b/main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java
index 00f45ed4..f104d3b0 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/NetworkUtils.java
@@ -14,14 +14,18 @@ import android.text.TextUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Vector;
+import de.blinkt.openvpn.R;
+
public class NetworkUtils {
public static Vector<String> getLocalNetworks(Context c, boolean ipv6) {
- Vector<String> nets = new Vector<>();
ConnectivityManager conn = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
+ Vector<String> nets = new Vector<>();
Network[] networks = conn.getAllNetworks();
for (Network network : networks) {
NetworkInfo ni = conn.getNetworkInfo(network);
@@ -29,6 +33,10 @@ public class NetworkUtils {
NetworkCapabilities nc = conn.getNetworkCapabilities(network);
+ // Ignore network if it has no capabilities
+ if (nc == null)
+ continue;
+
// Skip VPN networks like ourselves
if (nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN))
continue;
@@ -40,8 +48,16 @@ public class NetworkUtils {
for (LinkAddress la : li.getLinkAddresses()) {
if ((la.getAddress() instanceof Inet4Address && !ipv6) ||
- (la.getAddress() instanceof Inet6Address && ipv6))
- nets.add(la.toString());
+ (la.getAddress() instanceof Inet6Address && ipv6)) {
+ //nets.add(la.toString());
+ NetworkSpace.IpAddress ipaddress;
+ if (la.getAddress() instanceof Inet6Address)
+ ipaddress = new NetworkSpace.IpAddress((Inet6Address) la.getAddress(), la.getPrefixLength(), true);
+ else
+ ipaddress = new NetworkSpace.IpAddress(new CIDRIP(la.getAddress().getHostAddress(), la.getPrefixLength()), true);
+
+ nets.add(ipaddress.toString());
+ }
}
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
index d3de35f9..0e8793e4 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -14,16 +14,18 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.UiModeManager;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
-import android.net.IpPrefix;
import android.net.ProxyInfo;
import android.net.Uri;
import android.net.VpnService;
@@ -35,6 +37,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.system.OsConstants;
import android.text.TextUtils;
@@ -84,17 +87,27 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private static final int PRIORITY_DEFAULT = 0;
private static final int PRIORITY_MAX = 2;
private static boolean mNotificationAlwaysVisible = false;
- private final Vector<String> mDnslist = new Vector<>();
- private final NetworkSpace mRoutes = new NetworkSpace();
- private final NetworkSpace mRoutesv6 = new NetworkSpace();
+
+ static class TunConfig {
+ private final Vector<String> mDnslist = new Vector<>();
+ private final NetworkSpace mRoutes = new NetworkSpace();
+ private final NetworkSpace mRoutesv6 = new NetworkSpace();
+ private String mDomain = null;
+ private CIDRIP mLocalIP = null;
+ private int mMtu;
+ private String mLocalIPv6 = null;
+
+ private ProxyInfo mProxyInfo;
+ };
+
+ private TunConfig tunConfig = new TunConfig();
+
private final Object mProcessLock = new Object();
private String lastChannel;
private Thread mProcessThread = null;
private VpnProfile mProfile;
- private String mDomain = null;
- private CIDRIP mLocalIP = null;
- private int mMtu;
- private String mLocalIPv6 = null;
+
+
private DeviceStateReceiver mDeviceStateReceiver;
private boolean mDisplayBytecount = false;
private boolean mStarting = false;
@@ -135,12 +148,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
};
- private String mLastTunCfg;
+ private TunConfig mLastTunCfg;
private String mRemoteGW;
private Handler guiHandler;
private Toast mlastToast;
private Runnable mOpenVPNThread;
- private ProxyInfo mProxyInfo;
private HandlerThread mCommandHandlerThread;
private Handler mCommandHandler;
@@ -180,10 +192,14 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
}
+
+
@Override
public void addAllowedExternalApp(String packagename) throws RemoteException {
ExternalAppDatabase extapps = new ExternalAppDatabase(OpenVPNService.this);
- extapps.addApp(packagename);
+ if(extapps.checkAllowingModifyingRemoteControl(this)) {
+ extapps.addApp(packagename);
+ }
}
@Override
@@ -213,7 +229,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
@Override
public void onRevoke() {
VpnStatus.logError(R.string.permission_revoked);
- mManagement.stopVPN(false);
+ final OpenVPNManagement managment = mManagement;
+ mCommandHandler.post(() -> managment.stopVPN(false));
+
endVpnService();
}
@@ -222,7 +240,25 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
endVpnService();
}
+ private boolean isAlwaysActiveEnabled()
+ {
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
+ return prefs.getBoolean("restartvpnonboot", false);
+ }
+
+ boolean isVpnAlwaysOnEnabled() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ return isAlwaysOn();
+ }
+ return false;
+ }
+
+
private void endVpnService() {
+ if (!isVpnAlwaysOnEnabled() && !isAlwaysActiveEnabled()) {
+ /* if we should be an always on VPN, keep the timer running */
+ keepVPNAlive.unscheduleKeepVPNAliveJobService(this);
+ }
synchronized (mProcessLock) {
mProcessThread = null;
}
@@ -519,9 +555,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private VpnProfile fetchVPNProfile(Intent intent)
{
+ String startReason;
if (intent != null && intent.hasExtra(getPackageName() + ".profileUUID")) {
String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID");
int profileVersion = intent.getIntExtra(getPackageName() + ".profileVersion", 0);
+ startReason = intent.getStringExtra(getPackageName() + ".startReason");
+ if (startReason == null)
+ startReason = "(unknown)";
// Try for 10s to get current version of the profile
mProfile = ProfileManager.get(this, profileUUID, profileVersion, 100);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
@@ -531,10 +571,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
} else {
/* The intent is null when we are set as always-on or the service has been restarted. */
mProfile = ProfileManager.getLastConnectedProfile(this);
+ startReason = "Using last connected profile (started with null intent, always-on or restart after crash)";
VpnStatus.logInfo(R.string.service_restarted);
/* Got no profile, just stop */
if (mProfile == null) {
+ startReason = "could not get last connected profile, using default (started with null intent, always-on or restart after crash)";
+
Log.d("OpenVPN", "Got no last connected profile on null intent. Assuming always on.");
mProfile = ProfileManager.getAlwaysOnVPN(this);
@@ -546,6 +589,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
/* Do the asynchronous keychain certificate stuff */
mProfile.checkForRestart(this);
}
+ String name = "(null)";
+ if (mProfile != null)
+ name = mProfile.getName();
+ VpnStatus.logDebug(String.format("Fetched VPN profile (%s) triggered by %s", name, startReason));
return mProfile;
}
@@ -558,6 +605,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
ProfileManager.setConnectedVpnProfile(this, mProfile);
VpnStatus.setConnectedVPNProfile(mProfile.getUUIDString());
+ keepVPNAlive.scheduleKeepVPNAliveJobService(this, vp);
String nativeLibraryDirectory = getApplicationInfo().nativeLibraryDir;
String tmpDir;
@@ -575,8 +623,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
// Set a flag that we are starting a new VPN
mStarting = true;
// Stop the previous session by interrupting the thread.
-
- stopOldOpenVPNProcess();
+ stopOldOpenVPNProcess(mManagement, mOpenVPNThread);
// An old running VPN should now be exited
mStarting = false;
@@ -635,11 +682,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
- private void stopOldOpenVPNProcess() {
- if (mManagement != null) {
- if (mOpenVPNThread != null)
- ((OpenVPNThread) mOpenVPNThread).setReplaceConnection();
- if (mManagement.stopVPN(true)) {
+ private void stopOldOpenVPNProcess(OpenVPNManagement management,
+ Runnable mamanagmentThread) {
+ if (management != null) {
+ if (mamanagmentThread != null)
+ ((OpenVPNThread) mamanagmentThread).setReplaceConnection();
+ if (management.stopVPN(true)) {
// an old was asked to exit, wait 1s
try {
Thread.sleep(1000);
@@ -708,27 +756,38 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
VpnStatus.flushLog();
}
- private String getTunConfigString() {
+ private static String getTunConfigString(TunConfig tc) {
// The format of the string is not important, only that
// two identical configurations produce the same result
+ if (tc == null)
+ return "NULL";
+
String cfg = "TUNCFG UNQIUE STRING ips:";
- if (mLocalIP != null)
- cfg += mLocalIP.toString();
- if (mLocalIPv6 != null)
- cfg += mLocalIPv6;
+ if (tc.mLocalIP != null)
+ cfg += tc.mLocalIP.toString();
+ if (tc.mLocalIPv6 != null)
+ cfg += tc.mLocalIPv6;
- cfg += "routes: " + TextUtils.join("|", mRoutes.getNetworks(true)) + TextUtils.join("|", mRoutesv6.getNetworks(true));
- cfg += "excl. routes:" + TextUtils.join("|", mRoutes.getNetworks(false)) + TextUtils.join("|", mRoutesv6.getNetworks(false));
- cfg += "dns: " + TextUtils.join("|", mDnslist);
- cfg += "domain: " + mDomain;
- cfg += "mtu: " + mMtu;
- cfg += "proxyInfo: " + mProxyInfo;
+ cfg += "routes: " + TextUtils.join("|", tc.mRoutes.getNetworks(true)) + TextUtils.join("|", tc.mRoutesv6.getNetworks(true));
+ cfg += "excl. routes:" + TextUtils.join("|", tc.mRoutes.getNetworks(false)) + TextUtils.join("|", tc.mRoutesv6.getNetworks(false));
+ cfg += "dns: " + TextUtils.join("|", tc.mDnslist);
+ cfg += "domain: " + tc.mDomain;
+ cfg += "mtu: " + tc.mMtu;
+ cfg += "proxyInfo: " + tc.mProxyInfo;
return cfg;
}
public ParcelFileDescriptor openTun() {
+ ParcelFileDescriptor pfd = openTun(tunConfig);
+
+ // Reset information
+ mLastTunCfg = tunConfig;
+ tunConfig = new TunConfig();
+ return pfd;
+ }
+ private ParcelFileDescriptor openTun(TunConfig tc) {
//Debug.startMethodTracing(getExternalFilesDir(null).toString() + "/opentun.trace", 40* 1024 * 1024);
@@ -747,36 +806,36 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
allowAllAFFamilies(builder);
}
- if (mLocalIP == null && mLocalIPv6 == null) {
+ if (tc.mLocalIP == null && tc.mLocalIPv6 == null) {
VpnStatus.logError(getString(R.string.opentun_no_ipaddr));
return null;
}
- if (mLocalIP != null) {
+ if (tc.mLocalIP != null) {
// OpenVPN3 manages excluded local networks by callback
if (!VpnProfile.doUseOpenVPN3(this))
- addLocalNetworksToRoutes();
+ addLocalNetworksToRoutes(tc);
try {
- builder.addAddress(mLocalIP.mIp, mLocalIP.len);
+ builder.addAddress(tc.mLocalIP.mIp, tc.mLocalIP.len);
} catch (IllegalArgumentException iae) {
- VpnStatus.logError(R.string.dns_add_error, mLocalIP, iae.getLocalizedMessage());
+ VpnStatus.logError(R.string.dns_add_error, tc.mLocalIP, iae.getLocalizedMessage());
return null;
}
}
- if (mLocalIPv6 != null) {
- String[] ipv6parts = mLocalIPv6.split("/");
+ if (tc.mLocalIPv6 != null) {
+ String[] ipv6parts = tc.mLocalIPv6.split("/");
try {
builder.addAddress(ipv6parts[0], Integer.parseInt(ipv6parts[1]));
} catch (IllegalArgumentException iae) {
- VpnStatus.logError(R.string.ip_add_error, mLocalIPv6, iae.getLocalizedMessage());
+ VpnStatus.logError(R.string.ip_add_error, tc.mLocalIPv6, iae.getLocalizedMessage());
return null;
}
}
- for (String dns : mDnslist) {
+ for (String dns : tc.mDnslist) {
try {
builder.addDnsServer(dns);
} catch (IllegalArgumentException iae) {
@@ -785,15 +844,15 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
String release = Build.VERSION.RELEASE;
- builder.setMtu(mMtu);
+ builder.setMtu(tc.mMtu);
- Collection<IpAddress> positiveIPv4Routes = mRoutes.getPositiveIPList();
- Collection<IpAddress> positiveIPv6Routes = mRoutesv6.getPositiveIPList();
+ Collection<IpAddress> positiveIPv4Routes = tc.mRoutes.getPositiveIPList();
+ Collection<IpAddress> positiveIPv6Routes = tc.mRoutesv6.getPositiveIPList();
- if ("samsung".equals(Build.BRAND) && mDnslist.size() >= 1) {
+ if ("samsung".equals(Build.BRAND) && tc.mDnslist.size() >= 1) {
// Check if the first DNS Server is in the VPN range
try {
- IpAddress dnsServer = new IpAddress(new CIDRIP(mDnslist.get(0), 32), true);
+ IpAddress dnsServer = new IpAddress(new CIDRIP(tc.mDnslist.get(0), 32), true);
boolean dnsIncluded = false;
for (IpAddress net : positiveIPv4Routes) {
if (net.containsNet(dnsServer)) {
@@ -801,28 +860,28 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
}
if (!dnsIncluded) {
- String samsungwarning = String.format("Warning Samsung Android 5.0+ devices ignore DNS servers outside the VPN range. To enable DNS resolution a route to your DNS Server (%s) has been added.", mDnslist.get(0));
+ String samsungwarning = String.format("Warning Samsung Android 5.0+ devices ignore DNS servers outside the VPN range. To enable DNS resolution a route to your DNS Server (%s) has been added.", tc.mDnslist.get(0));
VpnStatus.logWarning(samsungwarning);
positiveIPv4Routes.add(dnsServer);
}
} catch (Exception e) {
// If it looks like IPv6 ignore error
- if (!mDnslist.get(0).contains(":"))
- VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0));
+ if (!tc.mDnslist.get(0).contains(":"))
+ VpnStatus.logError("Error parsing DNS Server IP: " + tc.mDnslist.get(0));
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- installRoutesExcluded(builder, mRoutes);
- installRoutesExcluded(builder, mRoutesv6);
+ installRoutesExcluded(builder, tc.mRoutes);
+ installRoutesExcluded(builder, tc.mRoutesv6);
} else {
installRoutesPostiveOnly(builder, positiveIPv4Routes, positiveIPv6Routes);
}
- if (mDomain != null)
- builder.addSearchDomain(mDomain);
+ if (tc.mDomain != null)
+ builder.addSearchDomain(tc.mDomain);
String ipv4info;
String ipv6info;
@@ -835,32 +894,34 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
int ipv4len;
- if (mLocalIP != null) {
- ipv4len = mLocalIP.len;
- ipv4info = mLocalIP.mIp;
+ if (tc.mLocalIP != null) {
+ ipv4len = tc.mLocalIP.len;
+ ipv4info = tc.mLocalIP.mIp;
} else {
ipv4len = -1;
}
- if (mLocalIPv6 != null) {
- ipv6info = mLocalIPv6;
+ if (tc.mLocalIPv6 != null) {
+ ipv6info = tc.mLocalIPv6;
}
- if ((!mRoutes.getNetworks(false).isEmpty() || !mRoutesv6.getNetworks(false).isEmpty()) && isLockdownEnabledCompat()) {
+ if ((!tc.mRoutes.getNetworks(false).isEmpty() || !tc.mRoutesv6.getNetworks(false).isEmpty()) && isLockdownEnabledCompat()) {
VpnStatus.logInfo("VPN lockdown enabled (do not allow apps to bypass VPN) enabled. Route exclusion will not allow apps to bypass VPN (e.g. bypass VPN for local networks)");
}
- VpnStatus.logInfo(R.string.local_ip_info, ipv4info, ipv4len, ipv6info, mMtu);
- VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain);
- VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true)));
- VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)), TextUtils.join(", ", mRoutesv6.getNetworks(false)));
- if (mProxyInfo != null) {
- VpnStatus.logInfo(R.string.proxy_info, mProxyInfo.getHost(), mProxyInfo.getPort());
+ VpnStatus.logInfo(R.string.local_ip_info, ipv4info, ipv4len, ipv6info, tc.mMtu);
+ VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", tc.mDnslist), tc.mDomain);
+ VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", tc.mRoutes.getNetworks(true)), TextUtils.join(", ", tc.mRoutesv6.getNetworks(true)));
+ VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", tc.mRoutes.getNetworks(false)), TextUtils.join(", ", tc.mRoutesv6.getNetworks(false)));
+ if (tc.mProxyInfo != null) {
+ VpnStatus.logInfo(R.string.proxy_info, tc.mProxyInfo.getHost(), tc.mProxyInfo.getPort());
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
/* On Tiramisu we install the routes exactly like promised */
VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", positiveIPv4Routes), TextUtils.join(", ", positiveIPv6Routes));
}
+ //VpnStatus.logInfo(String.format("Always active %s", isAlwaysOn() ? "on" : "off"));
+
setAllowedVpnPackages(builder);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
// VPN always uses the default network
@@ -874,31 +935,20 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
String session = mProfile.mName;
- if (mLocalIP != null && mLocalIPv6 != null)
- session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6);
- else if (mLocalIP != null)
- session = getString(R.string.session_ipv4string, session, mLocalIP);
+ if (tc.mLocalIP != null && tc.mLocalIPv6 != null)
+ session = getString(R.string.session_ipv6string, session, tc.mLocalIP, tc.mLocalIPv6);
+ else if (tc.mLocalIP != null)
+ session = getString(R.string.session_ipv4string, session, tc.mLocalIP);
else
- session = getString(R.string.session_ipv4string, session, mLocalIPv6);
+ session = getString(R.string.session_ipv4string, session, tc.mLocalIPv6);
builder.setSession(session);
// No DNS Server, log a warning
- if (mDnslist.size() == 0)
+ if (tc.mDnslist.size() == 0)
VpnStatus.logInfo(R.string.warn_no_dns);
- setHttpProxy(builder);
-
- mLastTunCfg = getTunConfigString();
-
- // Reset information
- mDnslist.clear();
- mRoutes.clear();
- mRoutesv6.clear();
- mLocalIP = null;
- mLocalIPv6 = null;
- mDomain = null;
- mProxyInfo = null;
+ setHttpProxy(builder, tc);
builder.setConfigureIntent(getGraphPendingIntent());
@@ -959,10 +1009,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
}
- private void setHttpProxy(Builder builder) {
- if (mProxyInfo != null && Build.VERSION.SDK_INT >= 29) {
- builder.setHttpProxy(mProxyInfo);
- } else if (mProxyInfo != null) {
+ private void setHttpProxy(Builder builder, TunConfig tc) {
+ if (tc.mProxyInfo != null && Build.VERSION.SDK_INT >= 29) {
+ builder.setHttpProxy(tc.mProxyInfo);
+ } else if (tc.mProxyInfo != null) {
VpnStatus.logWarning("HTTP Proxy needs Android 10 or later.");
}
}
@@ -982,27 +1032,24 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
builder.allowFamily(OsConstants.AF_INET6);
}
- private void addLocalNetworksToRoutes() {
+ private void addLocalNetworksToRoutes(TunConfig tc) {
for (String net : NetworkUtils.getLocalNetworks(this, false)) {
String[] netparts = net.split("/");
String ipAddr = netparts[0];
int netMask = Integer.parseInt(netparts[1]);
- if (ipAddr.equals(mLocalIP.mIp))
+ if (ipAddr.equals(tc.mLocalIP.mIp))
continue;
if(mProfile.mAllowLocalLAN)
- mRoutes.addIP(new CIDRIP(ipAddr, netMask), false);
+ tc.mRoutes.addIP(new CIDRIP(ipAddr, netMask), false);
}
- // IPv6 is Lollipop+ only so we can skip the lower than KITKAT case
if (mProfile.mAllowLocalLAN) {
for (String net : NetworkUtils.getLocalNetworks(this, true)) {
addRoutev6(net, false);
;
}
}
-
-
}
private void setAllowedVpnPackages(Builder builder) {
@@ -1065,12 +1112,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
public void addDNS(String dns) {
- mDnslist.add(dns);
+ tunConfig.mDnslist.add(dns);
}
public void setDomain(String domain) {
- if (mDomain == null) {
- mDomain = domain;
+ if (tunConfig.mDomain == null) {
+ tunConfig.mDomain = domain;
}
}
@@ -1078,12 +1125,12 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
* Route that is always included, used by the v3 core
*/
public void addRoute(CIDRIP route, boolean include) {
- mRoutes.addIP(route, include);
+ tunConfig.mRoutes.addIP(route, include);
}
public boolean addHttpProxy(String proxy, int port) {
try {
- mProxyInfo = ProxyInfo.buildDirectProxy(proxy, port);
+ tunConfig.mProxyInfo = ProxyInfo.buildDirectProxy(proxy, port);
} catch (Exception e) {
VpnStatus.logError("Could not set proxy" + e.getLocalizedMessage());
return false;
@@ -1097,11 +1144,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
IpAddress gatewayIP = new IpAddress(new CIDRIP(gateway, 32), false);
- if (mLocalIP == null) {
+ if (tunConfig.mLocalIP == null) {
VpnStatus.logError("Local IP address unset and received. Neither pushed server config nor local config specifies an IP addresses. Opening tun device is most likely going to fail.");
return;
}
- IpAddress localNet = new IpAddress(mLocalIP, true);
+ IpAddress localNet = new IpAddress(tunConfig.mLocalIP, true);
if (localNet.containsNet(gatewayIP))
include = true;
@@ -1117,7 +1164,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
if (route.normalise())
VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp);
- mRoutes.addIP(route, include);
+ tunConfig.mRoutes.addIP(route, include);
}
public void addRoutev6(String network, String device) {
@@ -1132,7 +1179,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
try {
Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0];
int mask = Integer.parseInt(v6parts[1]);
- mRoutesv6.addIPv6(ip, mask, included);
+ tunConfig.mRoutesv6.addIPv6(ip, mask, included);
} catch (UnknownHostException e) {
VpnStatus.logException(e);
@@ -1147,21 +1194,21 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
public void setMtu(int mtu) {
- mMtu = mtu;
+ tunConfig.mMtu = mtu;
}
public void setLocalIP(CIDRIP cdrip) {
- mLocalIP = cdrip;
+ tunConfig.mLocalIP = cdrip;
}
public void setLocalIP(String local, String netmask, int mtu, String mode) {
- mLocalIP = new CIDRIP(local, netmask);
- mMtu = mtu;
+ tunConfig.mLocalIP = new CIDRIP(local, netmask);
+ tunConfig.mMtu = mtu;
mRemoteGW = null;
long netMaskAsInt = CIDRIP.getInt(netmask);
- if (mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) {
+ if (tunConfig.mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) {
// get the netmask as IP
int masklen;
@@ -1175,22 +1222,22 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
// Netmask is Ip address +/-1, assume net30/p2p with small net
- if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask)) {
- mLocalIP.len = masklen;
+ if ((netMaskAsInt & mask) == (tunConfig.mLocalIP.getInt() & mask)) {
+ tunConfig.mLocalIP.len = masklen;
} else {
- mLocalIP.len = 32;
+ tunConfig.mLocalIP.len = 32;
if (!"p2p".equals(mode))
VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode);
}
}
- if (("p2p".equals(mode) && mLocalIP.len < 32) || ("net30".equals(mode) && mLocalIP.len < 30)) {
+ if (("p2p".equals(mode) && tunConfig.mLocalIP.len < 32) || ("net30".equals(mode) && tunConfig.mLocalIP.len < 30)) {
VpnStatus.logWarning(R.string.ip_looks_like_subnet, local, netmask, mode);
}
/* Workaround for Lollipop and higher, it does not route traffic to the VPNs own network mask */
- if (mLocalIP.len <= 31) {
- CIDRIP interfaceRoute = new CIDRIP(mLocalIP.mIp, mLocalIP.len);
+ if (tunConfig.mLocalIP.len <= 31) {
+ CIDRIP interfaceRoute = new CIDRIP(tunConfig.mLocalIP.mIp, tunConfig.mLocalIP.len);
interfaceRoute.normalise();
addRoute(interfaceRoute, true);
}
@@ -1201,7 +1248,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
public void setLocalIPv6(String ipv6addr) {
- mLocalIPv6 = ipv6addr;
+ tunConfig.mLocalIPv6 = ipv6addr;
}
@Override
@@ -1279,8 +1326,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
}
public String getTunReopenStatus() {
- String currentConfiguration = getTunConfigString();
- if (currentConfiguration.equals(mLastTunCfg)) {
+ String currentConfiguration = getTunConfigString(tunConfig);
+ if (currentConfiguration.equals(getTunConfigString(mLastTunCfg))) {
return "NOACTION";
} else {
return "OPEN_BEFORE_CLOSE";
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java
index 51fc58eb..37eb34dd 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java
@@ -15,6 +15,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
@@ -135,7 +136,7 @@ public class OpenVPNThread implements Runnable {
InputStream in = mProcess.getInputStream();
OutputStream out = mProcess.getOutputStream();
- BufferedReader br = new BufferedReader(new InputStreamReader(in));
+ BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
mOutputStream = out;
mStreamFuture.run();
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
index 11b2608b..8edae328 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
@@ -47,12 +47,12 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
private pauseReason lastPauseReason = pauseReason.noNetwork;
private PausedStateCallback mPauseCallback;
private boolean mShuttingDown;
- private Runnable mResumeHoldRunnable = () -> {
+ private final Runnable mResumeHoldRunnable = () -> {
if (shouldBeRunning()) {
releaseHoldCmd();
}
};
- private Runnable orbotStatusTimeOutRunnable = new Runnable() {
+ private final Runnable orbotStatusTimeOutRunnable = new Runnable() {
@Override
public void run() {
sendProxyCMD(Connection.ProxyType.SOCKS5, "127.0.0.1", Integer.toString(OrbotHelper.SOCKS_PROXY_PORT_DEFAULT), false);
@@ -148,8 +148,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
VpnStatus.logException(e);
}
return false;
-
-
}
/**
@@ -402,7 +400,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
if (waittime > 1)
VpnStatus.updateStateString("CONNECTRETRY", String.valueOf(waittime),
R.string.state_waitconnectretry, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET);
- mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000);
+ mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000L);
if (waittime > 5)
VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime));
else
@@ -618,14 +616,8 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
managmentCommand(cmd);
}
- private boolean sendTunFD(String needed, String extra) {
- if (!extra.equals("tun")) {
- // We only support tun
- VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra));
- return false;
- }
- ParcelFileDescriptor pfd = mOpenVPNService.openTun();
+ private boolean sendCommandWithFd(String cmd, ParcelFileDescriptor pfd) {
if (pfd == null)
return false;
@@ -638,26 +630,38 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
setInt.invoke(fdtosend, fdint);
FileDescriptor[] fds = {fdtosend};
- mSocket.setFileDescriptorsForSend(fds);
// Trigger a send so we can close the fd on our side of the channel
// The API documentation fails to mention that it will not reset the file descriptor to
// be send and will happily send the file descriptor on every write ...
- String cmd = String.format("needok '%s' %s\n", needed, "ok");
+ mSocket.setFileDescriptorsForSend(fds);
+
managmentCommand(cmd);
// Set the FileDescriptor to null to stop this mad behavior
mSocket.setFileDescriptorsForSend(null);
-
pfd.close();
- return true;
- } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException |
- IOException | IllegalAccessException exp) {
+
+ } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException |
+ IOException exp) {
VpnStatus.logException("Could not send fd over socket", exp);
+ return false;
}
+ return true;
+ }
- return false;
+ private boolean sendTunFD(String needed, String extra) {
+ if (!extra.equals("tun")) {
+ // We only support tun
+ VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra));
+
+ return false;
+ }
+ ParcelFileDescriptor pfd = mOpenVPNService.openTun();
+
+ String cmd = String.format("needok '%s' %s\n", needed, "ok");
+ return sendCommandWithFd(cmd, pfd);
}
private void processPWCommand(String argument) {
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java b/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
index b46a8f10..9d59e26b 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
@@ -51,10 +51,10 @@ public class ProfileManager {
return instance.profiles.get(key);
}
- private static void checkInstance(Context context) {
+ private synchronized static void checkInstance(Context context) {
if (instance == null) {
instance = new ProfileManager();
- ProfileEncryption.initMasterCryptAlias();
+ ProfileEncryption.initMasterCryptAlias(context);
instance.loadVPNList(context);
}
}
@@ -146,7 +146,7 @@ public class ProfileManager {
if (encryptedFileOld.exists()) {
encryptedFileOld.delete();
}
- } catch (IOException ioe)
+ } catch (IOException | GeneralSecurityException ioe)
{
VpnStatus.logException(VpnStatus.LogLevel.INFO, "Error trying to write an encrypted VPN profile, disabling " +
"encryption", ioe);
@@ -174,7 +174,7 @@ public class ProfileManager {
}
- } catch (IOException | GeneralSecurityException e) {
+ } catch (IOException e) {
VpnStatus.logException("saving VPN profile", e);
throw new RuntimeException(e);
}
@@ -250,7 +250,7 @@ public class ProfileManager {
editor.apply();
}
- public void addProfile(VpnProfile profile) {
+ public synchronized void addProfile(VpnProfile profile) {
profiles.put(profile.getUUID().toString(), profile);
}
@@ -259,7 +259,7 @@ public class ProfileManager {
* profiles
* @param context
*/
- public void refreshVPNList(Context context)
+ public synchronized void refreshVPNList(Context context)
{
SharedPreferences listpref = Preferences.getSharedPreferencesMulti(PREFS_NAME, context);
Set<String> vlist = listpref.getStringSet("vpnlist", null);
@@ -283,7 +283,7 @@ public class ProfileManager {
}
}
- private void loadVPNList(Context context) {
+ private synchronized void loadVPNList(Context context) {
profiles = new HashMap<>();
SharedPreferences listpref = Preferences.getSharedPreferencesMulti(PREFS_NAME, context);
Set<String> vlist = listpref.getStringSet("vpnlist", null);
@@ -298,7 +298,7 @@ public class ProfileManager {
}
}
- private void loadVpnEntry(Context context, String vpnentry) {
+ private synchronized void loadVpnEntry(Context context, String vpnentry) {
ObjectInputStream vpnfile = null;
try {
FileInputStream vpInput;
@@ -339,7 +339,7 @@ public class ProfileManager {
}
}
- public void removeProfile(Context context, VpnProfile profile) {
+ public synchronized void removeProfile(Context context, VpnProfile profile) {
String vpnentry = profile.getUUID().toString();
profiles.remove(vpnentry);
saveProfileList(context);
diff --git a/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java b/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java
index 13a88974..293a6fd4 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java
@@ -5,29 +5,33 @@
package de.blinkt.openvpn.core;
-import android.app.PendingIntent;
+import android.app.ActivityManager;
+import android.app.ApplicationExitInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Build;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
import de.blinkt.openvpn.BuildConfig;
import de.blinkt.openvpn.core.VpnStatus.LogLevel;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
+import java.util.List;
/**
* Created by arne on 09.11.16.
*/
public class StatusListener implements VpnStatus.LogListener {
- private File mCacheDir;
- private Context mContext;
private final IStatusCallbacks mCallback = new IStatusCallbacks.Stub() {
@Override
public void newLogItem(LogItem item) throws RemoteException {
@@ -37,7 +41,19 @@ public class StatusListener implements VpnStatus.LogListener {
@Override
public void updateStateString(String state, String msg, int resid, ConnectionStatus
level, Intent intent) throws RemoteException {
- VpnStatus.updateStateString(state, msg, resid, level, intent);
+ Intent newIntent = reCreateIntent(intent);
+ VpnStatus.updateStateString(state, msg, resid, level, newIntent);
+ }
+
+ private Intent reCreateIntent(Intent intent) {
+ /* To avoid UnsafeIntentLaunchViolation we recreate the intent that we passed
+ * to ourselves via the AIDL interface */
+ if (intent == null)
+ return null;
+ Intent newIntent = new Intent(intent.getAction(), intent.getData());
+ if (intent.getExtras() != null)
+ newIntent.putExtras(intent.getExtras());
+ return newIntent;
}
@Override
@@ -50,6 +66,7 @@ public class StatusListener implements VpnStatus.LogListener {
VpnStatus.setConnectedVPNProfile(uuid);
}
};
+ private File mCacheDir;
private final ServiceConnection mConnection = new ServiceConnection() {
@@ -102,6 +119,7 @@ public class StatusListener implements VpnStatus.LogListener {
}
};
+ private Context mContext;
void init(Context c) {
@@ -112,6 +130,35 @@ public class StatusListener implements VpnStatus.LogListener {
c.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
this.mContext = c;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
+ logLatestExitReasons(c);
+ }
+
+ @RequiresApi(Build.VERSION_CODES.R)
+ private void logLatestExitReasons(Context c) {
+ ActivityManager activityManager = (ActivityManager) c.getSystemService(Context.ACTIVITY_SERVICE);
+ List<ApplicationExitInfo> exitReasons = activityManager.getHistoricalProcessExitReasons(null, 0, 5);
+ ApplicationExitInfo lastguiexit = null;
+ ApplicationExitInfo lastserviceexit = null;
+ for (ApplicationExitInfo aei : exitReasons) {
+ if (aei.getProcessName().endsWith(":openvpn")) {
+ if (lastserviceexit == null || aei.getTimestamp() > lastserviceexit.getTimestamp())
+ lastserviceexit = aei;
+ } else {
+ if (lastguiexit == null || aei.getTimestamp() > lastguiexit.getTimestamp())
+ lastguiexit = aei;
+ }
+ }
+ logExitNotification(lastserviceexit, "Last exit reason reported by Android for Service Process: ");
+ logExitNotification(lastguiexit, "Last exit reason reported by Android for UI Process: ");
+
+ }
+
+ private void logExitNotification(ApplicationExitInfo aei, String s) {
+ if (aei != null) {
+ LogItem li = new LogItem(LogLevel.DEBUG, s + aei, aei.getTimestamp());
+ VpnStatus.newLogItemIfUnique(li);
+ }
}
@Override
diff --git a/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
index c859e845..bc04bc5e 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java
@@ -100,8 +100,8 @@ public class VPNLaunchHelper {
}
- public static void startOpenVpn(VpnProfile startprofile, Context context) {
- Intent startVPN = startprofile.prepareStartService(context);
+ public static void startOpenVpn(VpnProfile startprofile, Context context, String startReason) {
+ Intent startVPN = startprofile.getStartServiceIntent(context, startReason);
if (startVPN != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
//noinspection NewApi
diff --git a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
index c8e69414..d1814fc2 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
@@ -15,14 +15,13 @@ import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.LinkedList;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Vector;
import de.blinkt.openvpn.R;
public class VpnStatus {
-
-
private static final LinkedList<LogItem> logbuffer;
private static Vector<LogListener> logListener;
@@ -150,7 +149,6 @@ public class VpnStatus {
VpnStatus.trafficHistory = trafficHistory;
}
-
public enum LogLevel {
INFO(2),
ERROR(-2),
@@ -192,6 +190,7 @@ public class VpnStatus {
static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43};
static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57};
static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104};
+ static final byte[] officialO2Key = {-50, -119, -11, 121, 121, 122, -115, 84, 90, -122, 27, -117, -14, 60, 54, 127, 41, -45, 27, 55, -14, 90, 31, 72, -26, -85, -85, 67, 35, 54, 100, 42};
private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED;
@@ -240,7 +239,7 @@ public class VpnStatus {
String nativeAPI;
try {
nativeAPI = NativeUtils.getNativeAPI();
- } catch (UnsatisfiedLinkError ignore) {
+ } catch (UnsatisfiedLinkError|NoClassDefFoundError ignore) {
nativeAPI = "error";
}
@@ -418,15 +417,23 @@ public class VpnStatus {
}
static void newLogItem(LogItem logItem) {
- newLogItem(logItem, false);
+ newLogItem(logItem, false, false);
+ }
+
+ public static void newLogItemIfUnique(LogItem li) {
+ newLogItem(li, false, true);
}
+ public static void newLogItem(LogItem logItem, boolean cachedLine)
+ {
+ newLogItem(logItem, cachedLine, false);
+ }
- synchronized static void newLogItem(LogItem logItem, boolean cachedLine) {
+ synchronized static void newLogItem(LogItem logItem, boolean cachedLine, boolean enforceUnique) {
if (cachedLine) {
logbuffer.addFirst(logItem);
} else {
- logbuffer.addLast(logItem);
+ insertLogItemByLogTime(logItem, enforceUnique);
if (mLogFileHandler != null) {
Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem);
mLogFileHandler.sendMessage(m);
@@ -445,6 +452,34 @@ public class VpnStatus {
}
}
+ private static void insertLogItemByLogTime(LogItem logItem, boolean enforceUnique) {
+ /* Shortcut for the shortcut that it should be added at the
+ * end to avoid traversing the list
+ */
+ if (!logbuffer.isEmpty() && logbuffer.getLast().getLogtime() <= logItem.getLogtime())
+ {
+ logbuffer.addLast(logItem);
+ return;
+ }
+
+ ListIterator<LogItem> itr = logbuffer.listIterator();
+ long newItemLogTime = logItem.getLogtime();
+ while(itr.hasNext()) {
+ LogItem laterLogItem = itr.next();
+ if (enforceUnique && laterLogItem.equals(logItem))
+ /* Identical object found, ignore new item */
+ return;
+
+ if (laterLogItem.getLogtime() > newItemLogTime) {
+ itr.previous();
+ itr.add(logItem);
+ return;
+ }
+ }
+ /* no hasNext, add at the end */
+ itr.add(logItem);
+ }
+
public static void logError(String msg) {
newLogItem(new LogItem(LogLevel.ERROR, msg));
diff --git a/main/src/main/java/de/blinkt/openvpn/core/X509Utils.java b/main/src/main/java/de/blinkt/openvpn/core/X509Utils.java
index eeb54675..e5ca561e 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/X509Utils.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/X509Utils.java
@@ -80,7 +80,7 @@ public class X509Utils {
try {
X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0];
String friendlycn = getCertificateFriendlyName(cert);
- friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn;
+ friendlycn = getCertificateValidityString(cert, c.getResources()) + ", " + friendlycn;
return friendlycn;
} catch (Exception e) {
@@ -146,9 +146,9 @@ public class X509Utils {
friendlyName= (String) toString.invoke(subjectName,true,defaultSymbols);
- } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException | NoSuchMethodException e) {
+ } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
exp =e ;
- } catch (InvocationTargetException e) {
+ } catch (InvocationTargetException | NoSuchMethodException e) {
/* Ignore this. Modern Android versions do not expose this */
exp = null;
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/keepVPNAlive.java b/main/src/main/java/de/blinkt/openvpn/core/keepVPNAlive.java
new file mode 100644
index 00000000..b4264aba
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/core/keepVPNAlive.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2012-2023 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.core;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.VpnService;
+import android.os.Build;
+import android.os.PersistableBundle;
+
+import de.blinkt.openvpn.LaunchVPN;
+import de.blinkt.openvpn.VpnProfile;
+
+/**
+ * This is a task that is run periodically to restart the VPN if tit has died for
+ * some reason in the background
+ */
+public class keepVPNAlive extends JobService implements VpnStatus.StateListener {
+ private ConnectionStatus mLevel = ConnectionStatus.UNKNOWN_LEVEL;
+ private static final int JOBID_KEEPVPNALIVE = 6231;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ VpnStatus.addStateListener(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ VpnStatus.removeStateListener(this);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters jobParameters) {
+ if (mLevel == ConnectionStatus.UNKNOWN_LEVEL || mLevel == ConnectionStatus.LEVEL_NOTCONNECTED) {
+ String vpnUUID = jobParameters.getExtras().getString(LaunchVPN.EXTRA_KEY);
+ VpnProfile vp = ProfileManager.get(this, vpnUUID);
+ if (vp == null) {
+ VpnStatus.logError("Keepalive service cannot find VPN");
+ unscheduleKeepVPNAliveJobService(this);
+ return false;
+ }
+ VPNLaunchHelper.startOpenVpn(vp, getApplicationContext(), "VPN keep alive Job");
+ } else {
+ VpnStatus.logDebug("Keepalive service called but VPN still connected.");
+ }
+
+ /* The job has finished */
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ /* not doing anything */
+ return true;
+ }
+
+ @Override
+ public void updateState(String state, String logmessage,
+ int localizedResId, ConnectionStatus level, Intent Intent) {
+ mLevel = level;
+ }
+
+ @Override
+ public void setConnectedVPN(String uuid) {
+
+ }
+
+ public static void scheduleKeepVPNAliveJobService(Context c, VpnProfile vp) {
+ ComponentName keepVPNAliveComponent = new ComponentName(c, keepVPNAlive.class);
+ JobInfo.Builder jib = new JobInfo.Builder(JOBID_KEEPVPNALIVE, keepVPNAliveComponent);
+
+ /* set the VPN that should be restarted if we get killed */
+ PersistableBundle extraBundle = new PersistableBundle();
+ extraBundle.putString(de.blinkt.openvpn.LaunchVPN.EXTRA_KEY, vp.getUUIDString());
+ jib.setExtras(extraBundle);
+
+ /* periodic timing */
+ /* The current limits are 15 minutes and 5 minutes for flex and periodic timer
+ * but we use a minimum of 5 minutes and 2 minutes to avoid problems if there is some
+ * strange Android build that allows lower lmits.
+ */
+ long initervalMillis = Math.max(getMinPeriodMillis(), 5 * 60 * 1000L);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ long flexMillis = Math.max(JobInfo.getMinFlexMillis(), 2 * 60 * 1000L);
+ jib.setPeriodic(initervalMillis, flexMillis);
+ }
+ else
+ {
+ jib.setPeriodic(initervalMillis);
+ }
+ jib.setPersisted(true);
+
+ JobScheduler jobScheduler = null;
+ jobScheduler = getJobScheduler(c);
+
+ jobScheduler.schedule(jib.build());
+ VpnStatus.logDebug("Scheduling VPN keep alive for VPN " + vp.mName);
+ }
+
+ private static JobScheduler getJobScheduler(Context c) {
+ JobScheduler jobScheduler;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ jobScheduler = c.getSystemService(JobScheduler.class);
+
+ } else {
+ jobScheduler = (JobScheduler) c.getSystemService(JOB_SCHEDULER_SERVICE);
+ }
+ return jobScheduler;
+ }
+
+ private static long getMinPeriodMillis() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return JobInfo.getMinPeriodMillis();
+ } else {
+ return 15 * 60 * 1000L; // 15 minutes
+ }
+ }
+
+ public static void unscheduleKeepVPNAliveJobService(Context c) {
+ JobScheduler jobScheduler = getJobScheduler(c);
+ jobScheduler.cancel(JOBID_KEEPVPNALIVE);
+ VpnStatus.logDebug("Unscheduling VPN keep alive");
+ }
+}