From 907a31db4cf36dcd76fa8de3ae98c295d6c2f0e0 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 21 Oct 2019 15:12:05 +0200 Subject: [PATCH 01/20] android: Again change how data source is handled in TileService Evidently, onClick() may be called either before onStartListening() or after onStopListening() has been called, which causes a crash when trying to load a VpnProfile via mDataSource. This partially reverts 3716af079e21 ("android: Avoid crash related to TileService on Huawei devices"). --- .../strongswan/android/ui/VpnTileService.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java index e754fe151..6016ebfbb 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Tobias Brunner + * Copyright (C) 2018-2019 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ import org.strongswan.android.utils.Constants; @TargetApi(Build.VERSION_CODES.N) public class VpnTileService extends TileService implements VpnStateService.VpnStateListener { + private boolean mListening; private VpnProfileDataSource mDataSource; private VpnStateService mService; private final ServiceConnection mServiceConnection = new ServiceConnection() @@ -53,7 +54,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt public void onServiceConnected(ComponentName name, IBinder service) { mService = ((VpnStateService.LocalBinder)service).getService(); - if (mDataSource != null) + if (mListening && mDataSource != null) { mService.registerListener(VpnTileService.this); updateTile(); @@ -69,6 +70,9 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt Context context = getApplicationContext(); context.bindService(new Intent(context, VpnStateService.class), mServiceConnection, Service.BIND_AUTO_CREATE); + + mDataSource = new VpnProfileDataSource(this); + mDataSource.open(); } @Override @@ -80,15 +84,15 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt { getApplicationContext().unbindService(mServiceConnection); } + mDataSource.close(); + mDataSource = null; } @Override public void onStartListening() { super.onStartListening(); - - mDataSource = new VpnProfileDataSource(this); - mDataSource.open(); + mListening = true; if (mService != null) { @@ -101,14 +105,12 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt public void onStopListening() { super.onStopListening(); + mListening = false; if (mService != null) { mService.unregisterListener(this); } - - mDataSource.close(); - mDataSource = null; } private VpnProfile getProfile() @@ -119,8 +121,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt { uuid = pref.getString(Constants.PREF_MRU_VPN_PROFILE, null); } - - return mDataSource.getVpnProfile(uuid); + return mDataSource != null ? mDataSource.getVpnProfile(uuid) : null; } @Override @@ -134,7 +135,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt { profile = getProfile(); } - else + else if (mDataSource != null) { /* always get the plain profile without cached password */ profile = mDataSource.getVpnProfile(profile.getId()); } From 3c8280960ca673bfa02541b7ecceeb2bee914dfb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 15 Jan 2020 13:58:47 +0100 Subject: [PATCH 02/20] android: Update Gradle plugin --- src/frontends/android/build.gradle | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/build.gradle b/src/frontends/android/build.gradle index 1edb179bb..b2d55abd8 100644 --- a/src/frontends/android/build.gradle +++ b/src/frontends/android/build.gradle @@ -4,7 +4,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.2' + classpath 'com.android.tools.build:gradle:3.6.3' } } diff --git a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties index 558ba36a3..aabd2d068 100644 --- a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties +++ b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Oct 07 16:41:25 CEST 2019 +#Wed Feb 26 10:22:42 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip From 491cdd59bdaaad3ce0e6414a8b6051af4ff4d3e1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 15 Jan 2020 14:12:07 +0100 Subject: [PATCH 03/20] android: Fix app icon on Android versions < 5.0 XML resources are apparently not supported there. Moving the icon to the mipmap folders should fix that. Aliases are defined for the icons on Android < 8.0. --- .../strongswan/android/ui/MainActivity.java | 2 +- .../ui/TrustedCertificateImportActivity.java | 2 +- .../src/main/res/mipmap-anydpi/ic_shortcut.xml | 17 ----------------- .../ic_launcher.png => mipmap-hdpi/ic_app.png} | Bin .../ic_launcher.png => mipmap-mdpi/ic_app.png} | Bin .../ic_app.png} | Bin .../ic_launcher.xml => values/mipmaps.xml} | 8 +++++--- 7 files changed, 7 insertions(+), 22 deletions(-) delete mode 100644 src/frontends/android/app/src/main/res/mipmap-anydpi/ic_shortcut.xml rename src/frontends/android/app/src/main/res/{drawable-hdpi/ic_launcher.png => mipmap-hdpi/ic_app.png} (100%) rename src/frontends/android/app/src/main/res/{drawable-mdpi/ic_launcher.png => mipmap-mdpi/ic_app.png} (100%) rename src/frontends/android/app/src/main/res/{drawable-xhdpi/ic_launcher.png => mipmap-xhdpi/ic_app.png} (100%) rename src/frontends/android/app/src/main/res/{mipmap-anydpi/ic_launcher.xml => values/mipmaps.xml} (78%) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java index b00f3b205..18953d185 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java @@ -66,7 +66,7 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec ActionBar bar = getSupportActionBar(); bar.setDisplayShowHomeEnabled(true); bar.setDisplayShowTitleEnabled(false); - bar.setIcon(R.drawable.ic_launcher); + bar.setIcon(R.mipmap.ic_app); /* load CA certificates in a background task */ new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java index 4581d4427..0beabd465 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java @@ -202,7 +202,7 @@ public class TrustedCertificateImportActivity extends AppCompatActivity certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE); return new AlertDialog.Builder(getActivity()) - .setIcon(R.drawable.ic_launcher) + .setIcon(R.mipmap.ic_app) .setTitle(R.string.import_certificate) .setMessage(certificate.getSubjectDN().toString()) .setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener() diff --git a/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_shortcut.xml b/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_shortcut.xml deleted file mode 100644 index 653d08006..000000000 --- a/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_shortcut.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_launcher.png b/src/frontends/android/app/src/main/res/mipmap-hdpi/ic_app.png similarity index 100% rename from src/frontends/android/app/src/main/res/drawable-hdpi/ic_launcher.png rename to src/frontends/android/app/src/main/res/mipmap-hdpi/ic_app.png diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_launcher.png b/src/frontends/android/app/src/main/res/mipmap-mdpi/ic_app.png similarity index 100% rename from src/frontends/android/app/src/main/res/drawable-mdpi/ic_launcher.png rename to src/frontends/android/app/src/main/res/mipmap-mdpi/ic_app.png diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_launcher.png b/src/frontends/android/app/src/main/res/mipmap-xhdpi/ic_app.png similarity index 100% rename from src/frontends/android/app/src/main/res/drawable-xhdpi/ic_launcher.png rename to src/frontends/android/app/src/main/res/mipmap-xhdpi/ic_app.png diff --git a/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/src/frontends/android/app/src/main/res/values/mipmaps.xml similarity index 78% rename from src/frontends/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml rename to src/frontends/android/app/src/main/res/values/mipmaps.xml index 653d08006..9711350a0 100644 --- a/src/frontends/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml +++ b/src/frontends/android/app/src/main/res/values/mipmaps.xml @@ -1,6 +1,6 @@ - \ No newline at end of file + + @mipmap/ic_app + @mipmap/ic_app + From 6b3bf7cdac0c6c954bccb915b64eeeb2a6d5d370 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 19 Mar 2020 11:25:37 +0100 Subject: [PATCH 04/20] ike: Track NAT-keepalives as outbound packets --- src/libcharon/sa/ike_sa.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index 0573bcf67..b44c157d9 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -669,6 +669,7 @@ METHOD(ike_sa_t, send_keepalive, void, packet->set_data(packet, data); DBG1(DBG_IKE, "sending keep alive to %#H", this->other_host); charon->sender->send_no_marker(charon->sender, packet); + this->stats[STAT_OUTBOUND] = now; diff = 0; } if (!this->keepalive_job) From 8b93510dace6b1e46dacc24525868362b7c9f32c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 1 Apr 2020 10:17:27 +0200 Subject: [PATCH 05/20] mutex: Don't use ...timedwait_monotonic() if clock is set via attribute This allows using clocks other than CLOCK_MONOTONIC. --- src/libstrongswan/threading/mutex.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libstrongswan/threading/mutex.c b/src/libstrongswan/threading/mutex.c index c54f661b5..70db30f21 100644 --- a/src/libstrongswan/threading/mutex.c +++ b/src/libstrongswan/threading/mutex.c @@ -239,7 +239,8 @@ METHOD(condvar_t, wait_, void, } /* use the monotonic clock based version of this function if available */ -#ifdef HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC +#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC) && \ + !defined(HAVE_CONDATTR_CLOCK_MONOTONIC) #define pthread_cond_timedwait pthread_cond_timedwait_monotonic #endif From 3e358475bb500dd3811616ee3c62f9a56070392a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 19 Mar 2020 14:19:22 +0100 Subject: [PATCH 06/20] time: Allow using different clocks On some systems it might be preferable to use e.g. CLOCK_BOOTTIME instead of CLOCK_MONOTONIC, which is also not affected by time adjustments but includes times when the system was suspended. --- src/libstrongswan/threading/mutex.c | 2 +- src/libstrongswan/utils/utils/time.c | 2 +- src/libstrongswan/utils/utils/time.h | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/libstrongswan/threading/mutex.c b/src/libstrongswan/threading/mutex.c index 70db30f21..888f11df0 100644 --- a/src/libstrongswan/threading/mutex.c +++ b/src/libstrongswan/threading/mutex.c @@ -337,7 +337,7 @@ condvar_t *condvar_create(condvar_type_t type) pthread_condattr_t condattr; pthread_condattr_init(&condattr); #ifdef HAVE_CONDATTR_CLOCK_MONOTONIC - pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC); + pthread_condattr_setclock(&condattr, TIME_CLOCK_ID); #endif pthread_cond_init(&this->condvar, &condattr); pthread_condattr_destroy(&condattr); diff --git a/src/libstrongswan/utils/utils/time.c b/src/libstrongswan/utils/utils/time.c index d96c918da..c1e055e91 100644 --- a/src/libstrongswan/utils/utils/time.c +++ b/src/libstrongswan/utils/utils/time.c @@ -52,7 +52,7 @@ time_t time_monotonic(timeval_t *tv) * monotonic time source only if it is also supported by pthread. */ timespec_t ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + if (clock_gettime(TIME_CLOCK_ID, &ts) == 0) { if (tv) { diff --git a/src/libstrongswan/utils/utils/time.h b/src/libstrongswan/utils/utils/time.h index 2e210fbef..9fac77729 100644 --- a/src/libstrongswan/utils/utils/time.h +++ b/src/libstrongswan/utils/utils/time.h @@ -32,6 +32,20 @@ */ #define TIME_32_BIT_SIGNED_MAX 0x7fffffff +/** + * The clock that should be used for time_monotonic() and conditional variables + */ +#ifdef HAVE_CLOCK_GETTIME +#ifdef HAVE_CONDATTR_CLOCK_MONOTONIC +/* only can use different clocks if we can set it via attribute */ +#ifndef TIME_CLOCK_ID +#define TIME_CLOCK_ID CLOCK_MONOTONIC +#endif +#else +#define TIME_CLOCK_ID CLOCK_MONOTONIC +#endif +#endif + /** * Handle struct timeval like an own type. */ From 31298187bfbec1bb0a056ae00aa4b4551129b460 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 19 Mar 2020 14:22:56 +0100 Subject: [PATCH 07/20] android: Switch to CLOCK_REALTIME on Android This allows measuring the delay between events more accurately if a device is often suspended. While CLOCK_BOOTTIME would be preferable, Android's bionic C library does not support it for condvars. --- src/libstrongswan/utils/compat/android.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libstrongswan/utils/compat/android.h b/src/libstrongswan/utils/compat/android.h index da8de6279..7fbcd9460 100644 --- a/src/libstrongswan/utils/compat/android.h +++ b/src/libstrongswan/utils/compat/android.h @@ -37,6 +37,9 @@ #define HAVE_PTHREAD_CONDATTR_INIT 1 #define HAVE_CONDATTR_CLOCK_MONOTONIC 1 +#undef TIME_CLOCK_ID +#define TIME_CLOCK_ID CLOCK_REALTIME + #define HAVE_SYS_CAPABILITY_H 1 #else /* __ANDROID_API__ */ From 0d4a5f6af6be7920c775d7d294af0767910cbf7b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 19 Mar 2020 13:39:48 +0100 Subject: [PATCH 08/20] ike: Add an option to trigger a DPD instead of a NAT keepalive This is useful on Android where the app might not be able to send keep-alives if the device is asleep for a while. If the NAT mapping has been deleted in the mean time, the NAT-D payloads allow detecting this and connectivity can be restored by doing a MOBIKE update or recreating the SA if the peer already deleted it because the client wasn't reachable. --- conf/options/charon.opt | 5 +++++ src/libcharon/sa/ike_sa.c | 22 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/conf/options/charon.opt b/conf/options/charon.opt index fd2d36a0b..b6e55e112 100644 --- a/conf/options/charon.opt +++ b/conf/options/charon.opt @@ -220,6 +220,11 @@ charon.interfaces_use charon.keep_alive = 20s NAT keep alive interval. +charon.keep_alive_dpd_margin = 0s + Number of seconds the keep alive interval may be exceeded before a DPD is + sent instead of a NAT keep alive (0 to disable). This is only useful if a + clock is used that includes time spent suspended (e.g. CLOCK_BOOTTIME). + charon.leak_detective.detailed = yes Includes source file names and line numbers in leak detective output. diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index b44c157d9..5e54208ec 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -236,6 +236,12 @@ struct private_ike_sa_t { */ uint32_t keepalive_interval; + /** + * Time the NAT keep alive interval may be exceeded before triggering a DPD + * instead of a NAT keep alive + */ + uint32_t keepalive_dpd_margin; + /** * The scheduled keep alive job, if any */ @@ -655,7 +661,19 @@ METHOD(ike_sa_t, send_keepalive, void, diff = now - last_out; - if (diff >= this->keepalive_interval) + if (this->keepalive_dpd_margin && + diff > (this->keepalive_interval + this->keepalive_dpd_margin)) + { + if (!this->task_manager->busy(this->task_manager)) + { + DBG1(DBG_IKE, "sending DPD instead of keep alive %ds after last " + "outbound message", diff); + this->task_manager->queue_dpd(this->task_manager); + this->task_manager->initiate(this->task_manager); + } + diff = 0; + } + else if (diff >= this->keepalive_interval) { packet_t *packet; chunk_t data; @@ -3183,6 +3201,8 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, .unique_id = ref_get(&unique_id), .keepalive_interval = lib->settings->get_time(lib->settings, "%s.keep_alive", KEEPALIVE_INTERVAL, lib->ns), + .keepalive_dpd_margin = lib->settings->get_time(lib->settings, + "%s.keep_alive_dpd_margin", 0, lib->ns), .retry_initiate_interval = lib->settings->get_time(lib->settings, "%s.retry_initiate_interval", 0, lib->ns), .flush_auth_cfg = lib->settings->get_bool(lib->settings, From 664389ebc4e3c6b7f324245fcf7ee908cc7efd6d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 19 Mar 2020 13:48:31 +0100 Subject: [PATCH 09/20] android: Enable switch from NAT interval to DPDs after 20 seconds --- .../app/src/main/jni/libandroidbridge/charonservice.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index 78abede58..e766b34ea 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2019 Tobias Brunner + * Copyright (C) 2012-2020 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * HSR Hochschule fuer Technik Rapperswil @@ -45,6 +45,7 @@ #define ANDROID_RETRANSMIT_TIMEOUT 2.0 #define ANDROID_RETRANSMIT_BASE 1.4 #define ANDROID_KEEPALIVE_INTERVAL 45 +#define ANDROID_KEEPALIVE_DPD_MARGIN 20 typedef struct private_charonservice_t private_charonservice_t; @@ -434,6 +435,10 @@ static void initiate(settings_t *settings) "charon.keep_alive", settings->get_int(settings, "global.nat_keepalive", ANDROID_KEEPALIVE_INTERVAL)); + /* send DPDs if above interval is exceeded, use a static value for now */ + lib->settings->set_int(lib->settings, + "charon.keep_alive_dpd_margin", + ANDROID_KEEPALIVE_DPD_MARGIN); /* reload plugins after changing settings */ lib->plugins->reload(lib->plugins, NULL); From 6524bd3cd5810874f1b9a799c9671ba05e3c909a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 19 Mar 2020 16:04:01 +0100 Subject: [PATCH 10/20] ike: Optionally use DPD to check if the current path still works We could maybe check the duration of the last stale condition or when the last packet was sent as filter to avoid unnecessary updates. --- conf/options/charon.opt | 11 +++++++++++ src/libcharon/sa/ike_sa.c | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/conf/options/charon.opt b/conf/options/charon.opt index b6e55e112..52983ee63 100644 --- a/conf/options/charon.opt +++ b/conf/options/charon.opt @@ -40,6 +40,17 @@ charon.cache_crls = no Certification Authority (CA) to **/etc/ipsec.d/crls** (stroke) or **/etc/swanctl/x509crl** (vici), respectively. +charon.check_current_path = no + Whether to use DPD to check if the current path still works after any + changes to interfaces/addresses. + + By default, after detecting any changes to interfaces and/or addresses no + action is taken if the current path to the remote peer still looks usable. + Enabling this option will use DPD to check if the path actually still works, + or, for instance, the peer removed the state after a longer phase without + connectivity. It will also trigger a MOBIKE update if NAT mappings were + removed during the downtime. + charon.cisco_unity = no Send Cisco Unity vendor ID payload (IKEv1 only). diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index 5e54208ec..e482d55ee 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -2708,6 +2708,14 @@ METHOD(ike_sa_t, roam, status_t, this->task_manager->queue_mobike(this->task_manager, FALSE, TRUE); return this->task_manager->initiate(this->task_manager); } + if (lib->settings->get_bool(lib->settings, + "%s.check_current_path", FALSE, lib->ns) && + !this->task_manager->busy(this->task_manager)) + { + DBG1(DBG_IKE, "checking if current path still works using DPD"); + this->task_manager->queue_dpd(this->task_manager); + return this->task_manager->initiate(this->task_manager); + } return SUCCESS; } From 070cd12dfb074ca31cf6508986b3f0a6f479aa7d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 19 Mar 2020 16:08:07 +0100 Subject: [PATCH 11/20] android: Check the current path using DPD after a roaming event A new NAT mapping might be created even if the IP stays the same. Due to the DPD fallback with NAT keep-alives this might only be necessary in corner cases, if at all. --- .../android/app/src/main/jni/libandroidbridge/charonservice.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index e766b34ea..5a3435a11 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -504,6 +504,8 @@ static void set_options(char *logfile) "charon.initiator_only", TRUE); lib->settings->set_bool(lib->settings, "charon.close_ike_on_child_failure", TRUE); + lib->settings->set_bool(lib->settings, + "charon.check_current_path", TRUE); /* setting the source address breaks the VpnService.protect() function which * uses SO_BINDTODEVICE internally. the addresses provided to the kernel as * auxiliary data have precedence over this option causing a routing loop if From f3695d089b22833c8287bf63f4a8ce336ba5569a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 31 Mar 2020 14:56:38 +0200 Subject: [PATCH 12/20] android: Change how initial log handler is registered Previously, if the two utility functions were called while the VPN connection was established (i.e. charon was initialized) the logger for libstrongswan would get reset to the initial log handler. So certain log messages would not get logged to the log file after the TUN device was created (one of the helpers is used to convert IPs there). --- .../src/main/jni/libandroidbridge/charonservice.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index 5a3435a11..d0f1d35ab 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -620,6 +620,14 @@ static void segv_handler(int signal) exit(1); } +/** + * Register this logger as default before we have the bus available + */ +static void __attribute__ ((constructor))register_logger() +{ + dbg = dbg_android; +} + /** * Initialize charon and the libraries via JNI */ @@ -630,9 +638,6 @@ JNI_METHOD(CharonVpnService, initializeCharon, jboolean, struct utsname utsname; char *logfile, *appdir, *plugins; - /* logging for library during initialization, as we have no bus yet */ - dbg = dbg_android; - /* initialize library */ if (!library_init(NULL, "charon")) { @@ -750,8 +755,6 @@ JNI_METHOD_P(org_strongswan_android_utils, Utils, isProposalValid, jboolean, char *str; bool valid; - dbg = dbg_android; - if (!library_init(NULL, "charon")) { library_deinit(); @@ -776,8 +779,6 @@ JNI_METHOD_P(org_strongswan_android_utils, Utils, parseInetAddressBytes, jbyteAr host_t *host; char *str; - dbg = dbg_android; - if (!library_init(NULL, "charon")) { library_deinit(); From 2edc73d84eb9c97ba610edaf75e48cfffd85d31d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 30 Apr 2020 17:42:07 +0200 Subject: [PATCH 13/20] ike: Only track actually sent retransmits as outbound packets Retransmission jobs for old requests for which we already received a response previously left the impression that messages were sent more recently than was actually the case. task_manager_t always defined INVALID_STATE as possible return value if no retransmit was sent, this just was never actually returned. I guess we could further differentiate between actual invalid states (e.g. if we already received the response) and when we don't send a retransmit for other reasons e.g. because the IKE_SA became stale. --- src/libcharon/sa/ike_sa.c | 151 ++++++++++++----------- src/libcharon/sa/ike_sa.h | 9 +- src/libcharon/sa/ikev1/task_manager_v1.c | 2 +- src/libcharon/sa/ikev2/task_manager_v2.c | 7 +- 4 files changed, 88 insertions(+), 81 deletions(-) diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index e482d55ee..f1e3e8fe5 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -2426,81 +2426,86 @@ METHOD(ike_sa_t, retransmit, status_t, { return INVALID_STATE; } - this->stats[STAT_OUTBOUND] = time_monotonic(NULL); - if (this->task_manager->retransmit(this->task_manager, message_id) != SUCCESS) + switch (this->task_manager->retransmit(this->task_manager, message_id)) { - /* send a proper signal to brief interested bus listeners */ - switch (this->state) - { - case IKE_CONNECTING: - { - /* retry IKE_SA_INIT/Main Mode if we have multiple keyingtries */ - uint32_t tries = this->peer_cfg->get_keyingtries(this->peer_cfg); - charon->bus->alert(charon->bus, ALERT_PEER_INIT_UNREACHABLE, - this->keyingtry); - this->keyingtry++; - if (tries == 0 || tries > this->keyingtry) - { - DBG1(DBG_IKE, "peer not responding, trying again (%d/%d)", - this->keyingtry + 1, tries); - reset(this, TRUE); - resolve_hosts(this); - return this->task_manager->initiate(this->task_manager); - } - DBG1(DBG_IKE, "establishing IKE_SA failed, peer not responding"); - - if (this->version == IKEV1 && array_count(this->child_sas)) - { - enumerator_t *enumerator; - child_sa_t *child_sa; - - /* if reauthenticating an IKEv1 SA failed (assumed for an SA - * in this state with CHILD_SAs), try again from scratch */ - DBG1(DBG_IKE, "reauthentication failed, trying to " - "reestablish IKE_SA"); - reestablish(this); - /* trigger down events for the CHILD_SAs, as no down event - * is triggered below for IKE SAs in this state */ - enumerator = array_create_enumerator(this->child_sas); - while (enumerator->enumerate(enumerator, &child_sa)) - { - if (child_sa->get_state(child_sa) != CHILD_REKEYED && - child_sa->get_state(child_sa) != CHILD_DELETED) - { - charon->bus->child_updown(charon->bus, child_sa, - FALSE); - } - } - enumerator->destroy(enumerator); - } - break; - } - case IKE_DELETING: - DBG1(DBG_IKE, "proper IKE_SA delete failed, peer not responding"); - if (has_condition(this, COND_REAUTHENTICATING) && - !lib->settings->get_bool(lib->settings, - "%s.make_before_break", FALSE, lib->ns)) - { - DBG1(DBG_IKE, "delete during reauthentication failed, " - "trying to reestablish IKE_SA anyway"); - reestablish(this); - } - break; - case IKE_REKEYING: - DBG1(DBG_IKE, "rekeying IKE_SA failed, peer not responding"); - /* FALL */ - default: - reestablish(this); - break; - } - if (this->state != IKE_CONNECTING && - this->state != IKE_REKEYED) - { - charon->bus->ike_updown(charon->bus, &this->public, FALSE); - } - return DESTROY_ME; + case SUCCESS: + this->stats[STAT_OUTBOUND] = time_monotonic(NULL); + return SUCCESS; + case INVALID_STATE: + return INVALID_STATE; + default: + break; } - return SUCCESS; + /* send a proper signal to brief interested bus listeners */ + switch (this->state) + { + case IKE_CONNECTING: + { + /* retry IKE_SA_INIT/Main Mode if we have multiple keyingtries */ + uint32_t tries = this->peer_cfg->get_keyingtries(this->peer_cfg); + charon->bus->alert(charon->bus, ALERT_PEER_INIT_UNREACHABLE, + this->keyingtry); + this->keyingtry++; + if (tries == 0 || tries > this->keyingtry) + { + DBG1(DBG_IKE, "peer not responding, trying again (%d/%d)", + this->keyingtry + 1, tries); + reset(this, TRUE); + resolve_hosts(this); + return this->task_manager->initiate(this->task_manager); + } + DBG1(DBG_IKE, "establishing IKE_SA failed, peer not responding"); + + if (this->version == IKEV1 && array_count(this->child_sas)) + { + enumerator_t *enumerator; + child_sa_t *child_sa; + + /* if reauthenticating an IKEv1 SA failed (assumed for an SA + * in this state with CHILD_SAs), try again from scratch */ + DBG1(DBG_IKE, "reauthentication failed, trying to " + "reestablish IKE_SA"); + reestablish(this); + /* trigger down events for the CHILD_SAs, as no down event + * is triggered below for IKE SAs in this state */ + enumerator = array_create_enumerator(this->child_sas); + while (enumerator->enumerate(enumerator, &child_sa)) + { + if (child_sa->get_state(child_sa) != CHILD_REKEYED && + child_sa->get_state(child_sa) != CHILD_DELETED) + { + charon->bus->child_updown(charon->bus, child_sa, + FALSE); + } + } + enumerator->destroy(enumerator); + } + break; + } + case IKE_DELETING: + DBG1(DBG_IKE, "proper IKE_SA delete failed, peer not responding"); + if (has_condition(this, COND_REAUTHENTICATING) && + !lib->settings->get_bool(lib->settings, + "%s.make_before_break", FALSE, lib->ns)) + { + DBG1(DBG_IKE, "delete during reauthentication failed, " + "trying to reestablish IKE_SA anyway"); + reestablish(this); + } + break; + case IKE_REKEYING: + DBG1(DBG_IKE, "rekeying IKE_SA failed, peer not responding"); + /* FALL */ + default: + reestablish(this); + break; + } + if (this->state != IKE_CONNECTING && + this->state != IKE_REKEYED) + { + charon->bus->ike_updown(charon->bus, &this->public, FALSE); + } + return DESTROY_ME; } METHOD(ike_sa_t, set_auth_lifetime, status_t, diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index 56dd53efe..b965a49c9 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2019 Tobias Brunner + * Copyright (C) 2006-2020 Tobias Brunner * Copyright (C) 2006 Daniel Roethlisberger * Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2005 Jan Hutter @@ -872,10 +872,11 @@ struct ike_sa_t { * * @param message_id ID of the request to retransmit * @return - * - SUCCESS - * - NOT_FOUND if request doesn't have to be retransmitted + * - SUCCESS if retransmit was sent + * - INVALID_STATE if no retransmit required + * - DESTROY_ME if this IKE_SA MUST be deleted */ - status_t (*retransmit) (ike_sa_t *this, uint32_t message_id); + status_t (*retransmit)(ike_sa_t *this, uint32_t message_id); /** * Sends a DPD request to the peer. diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c index 00f5e6f05..70cebe735 100644 --- a/src/libcharon/sa/ikev1/task_manager_v1.c +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -404,7 +404,7 @@ static status_t retransmit_packet(private_task_manager_t *this, uint32_t seqnr, METHOD(task_manager_t, retransmit, status_t, private_task_manager_t *this, uint32_t seqnr) { - status_t status = SUCCESS; + status_t status = INVALID_STATE; if (seqnr == this->initiating.seqnr && array_count(this->initiating.packets)) diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c index 6e1d00851..9c7a5378d 100644 --- a/src/libcharon/sa/ikev2/task_manager_v2.c +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -409,7 +409,7 @@ METHOD(task_manager_t, retransmit, status_t, "deferred"); this->ike_sa->set_condition(this->ike_sa, COND_STALE, TRUE); this->initiating.deferred = TRUE; - return SUCCESS; + return INVALID_STATE; } else if (mobike->is_probing(mobike)) { @@ -443,7 +443,7 @@ METHOD(task_manager_t, retransmit, status_t, "deferred"); this->ike_sa->set_condition(this->ike_sa, COND_STALE, TRUE); this->initiating.deferred = TRUE; - return SUCCESS; + return INVALID_STATE; } } @@ -451,8 +451,9 @@ METHOD(task_manager_t, retransmit, status_t, job = (job_t*)retransmit_job_create(this->initiating.mid, this->ike_sa->get_id(this->ike_sa)); lib->scheduler->schedule_job_ms(lib->scheduler, job, timeout); + return SUCCESS; } - return SUCCESS; + return INVALID_STATE; } METHOD(task_manager_t, initiate, status_t, From aaa908dc0a1ad4ba498e3b25d1b4cdc3f3facc6e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 27 Mar 2020 15:34:32 +0100 Subject: [PATCH 14/20] scheduler: Use timercmp(3) instead of a custom function --- src/libstrongswan/processing/scheduler.c | 42 ++++++------------------ 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/src/libstrongswan/processing/scheduler.c b/src/libstrongswan/processing/scheduler.c index 3d8e5e393..903b097a1 100644 --- a/src/libstrongswan/processing/scheduler.c +++ b/src/libstrongswan/processing/scheduler.c @@ -93,30 +93,6 @@ struct private_scheduler_t { condvar_t *condvar; }; -/** - * Compares two timevals, return >0 if a > b, <0 if a < b and =0 if equal - */ -static int timeval_cmp(timeval_t *a, timeval_t *b) -{ - if (a->tv_sec > b->tv_sec) - { - return 1; - } - if (a->tv_sec < b->tv_sec) - { - return -1; - } - if (a->tv_usec > b->tv_usec) - { - return 1; - } - if (a->tv_usec < b->tv_usec) - { - return -1; - } - return 0; -} - /** * Returns the top event without removing it. Returns NULL if the heap is empty. */ @@ -132,6 +108,7 @@ static event_t *peek_event(private_scheduler_t *this) static event_t *remove_event(private_scheduler_t *this) { event_t *event, *top; + if (!this->event_count) { return NULL; @@ -144,21 +121,22 @@ static event_t *remove_event(private_scheduler_t *this) if (--this->event_count > 1) { - /* seep down the top event */ u_int position = 1; + + /* seep down the top event */ while ((position << 1) <= this->event_count) { u_int child = position << 1; if ((child + 1) <= this->event_count && - timeval_cmp(&this->heap[child + 1]->time, - &this->heap[child]->time) < 0) + timercmp(&this->heap[child + 1]->time, + &this->heap[child]->time, <)) { /* the "right" child is smaller */ child++; } - if (timeval_cmp(&top->time, &this->heap[child]->time) <= 0) + if (!timercmp(&top->time, &this->heap[child]->time, >)) { /* the top event fires before the smaller of the two children, * stop */ @@ -189,7 +167,7 @@ static job_requeue_t schedule(private_scheduler_t * this) if ((event = peek_event(this)) != NULL) { - if (timeval_cmp(&now, &event->time) >= 0) + if (!timercmp(&now, &event->time, <)) { remove_event(this); this->mutex->unlock(this->mutex); @@ -231,6 +209,7 @@ METHOD(scheduler_t, get_job_load, u_int, private_scheduler_t *this) { int count; + this->mutex->lock(this->mutex); count = this->event_count; this->mutex->unlock(this->mutex); @@ -262,8 +241,8 @@ METHOD(scheduler_t, schedule_job_tv, void, position = this->event_count; /* then bubble it up */ - while (position > 1 && timeval_cmp(&this->heap[position >> 1]->time, - &event->time) > 0) + while (position > 1 && + timercmp(&this->heap[position >> 1]->time, &event->time, >)) { /* parent has to be fired after the new event, move up */ this->heap[position] = this->heap[position >> 1]; @@ -354,4 +333,3 @@ scheduler_t * scheduler_create() return &this->public; } - From b7d66ae2cd31e8071784efe2c9c1cdf3e2ab6aef Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 30 Apr 2020 18:55:23 +0200 Subject: [PATCH 15/20] android: Add Android-specific implementation of scheduler_t This uses AlarmManager to schedule events in a way that ensures the app is woken up (requires whitelisting when in Doze mode to be woken up at the exact time, otherwise there are delays of up to 15 minutes). --- .../strongswan/android/logic/Scheduler.java | 169 ++++++++++ .../src/main/jni/libandroidbridge/Android.mk | 1 + .../backend/android_scheduler.c | 307 ++++++++++++++++++ .../backend/android_scheduler.h | 36 ++ 4 files changed, 513 insertions(+) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/Scheduler.java create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.h diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/Scheduler.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/Scheduler.java new file mode 100644 index 000000000..8d4ea6cb7 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/Scheduler.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2020 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.logic; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; + +import java.util.ArrayList; +import java.util.PriorityQueue; +import java.util.UUID; + +import androidx.annotation.RequiresApi; + +public class Scheduler extends BroadcastReceiver +{ + private final String EXECUTE_JOB = "org.strongswan.android.Scheduler.EXECUTE_JOB"; + private final Context mContext; + private final AlarmManager mManager; + private final PriorityQueue mJobs; + + public Scheduler(Context context) + { + mContext = context; + mManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); + mJobs = new PriorityQueue<>(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(EXECUTE_JOB); + mContext.registerReceiver(this, filter); + } + + /** + * Remove all pending jobs and unregister the receiver. + * Called via JNI. + */ + public void Terminate() + { + synchronized (this) + { + mJobs.clear(); + } + mManager.cancel(createIntent()); + mContext.unregisterReceiver(this); + } + + /** + * Allocate a job ID. Called via JNI. + * + * @return random ID for a new job + */ + public String allocateId() + { + return UUID.randomUUID().toString(); + } + + /** + * Create a pending intent to execute a job. + * + * @return pending intent + */ + private PendingIntent createIntent() + { + /* using component/class doesn't work with dynamic broadcast receivers */ + Intent intent = new Intent(EXECUTE_JOB); + intent.setPackage(mContext.getPackageName()); + return PendingIntent.getBroadcast(mContext, 0, intent, 0); + } + + /** + * Schedule executing a job in the future. + * Called via JNI from different threads. + * + * @param id job ID + * @param ms delta in milliseconds when the job should be executed + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public void scheduleJob(String id, long ms) + { + synchronized (this) + { + ScheduledJob job = new ScheduledJob(id, System.currentTimeMillis() + ms); + mJobs.add(job); + + if (job == mJobs.peek()) + { /* update the alarm if the job has to be executed before all others */ + PendingIntent pending = createIntent(); + mManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, job.Time, pending); + } + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Override + public void onReceive(Context context, Intent intent) + { + ArrayList jobs = new ArrayList<>(); + long now = System.currentTimeMillis(); + + synchronized (this) + { + ScheduledJob job = mJobs.peek(); + while (job != null) + { + if (job.Time > now) + { + break; + } + jobs.add(mJobs.remove()); + job = mJobs.peek(); + } + if (job != null) + { + PendingIntent pending = createIntent(); + mManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, job.Time, pending); + } + } + + for (ScheduledJob job : jobs) + { + executeJob(job.Id); + } + } + + /** + * Execute the job with the given ID. + * + * @param id job ID + */ + public native void executeJob(String id); + + /** + * Keep track of scheduled jobs. + */ + private static class ScheduledJob implements Comparable + { + String Id; + long Time; + + ScheduledJob(String id, long time) + { + Id = id; + Time = time; + } + + @Override + public int compareTo(ScheduledJob o) + { + return Long.compare(Time, o.Time); + } + } +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/Android.mk b/src/frontends/android/app/src/main/jni/libandroidbridge/Android.mk index 49e313af5..a42a44e9b 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/Android.mk @@ -9,6 +9,7 @@ backend/android_creds.c \ backend/android_fetcher.c \ backend/android_dns_proxy.c \ backend/android_private_key.c \ +backend/android_scheduler.c \ backend/android_service.c \ charonservice.c \ kernel/android_ipsec.c \ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.c new file mode 100644 index 000000000..5a901fe15 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2020 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include + +#include "android_scheduler.h" +#include "../android_jni.h" + +#include +#include +#include + +typedef struct private_scheduler_t private_scheduler_t; + +/** + * Private data. + */ +struct private_scheduler_t { + + /** + * Public interface. + */ + scheduler_t public; + + /** + * Reference to Scheduler object. + */ + jobject obj; + + /** + * Java class for Scheduler. + */ + jclass cls; + + /** + * Hashtable that stores scheduled jobs (entry_t*). + */ + hashtable_t *jobs; + + /** + * Mutex to safely access the scheduled jobs. + */ + mutex_t *mutex; +}; + +/** + * Data for scheduled jobs. + */ +typedef struct { + + /** + * Random identifier as string. + */ + char *id; + + /** + * The scheduled job. + */ + job_t *job; + +} entry_t; + +CALLBACK(destroy_entry, void, + entry_t *this, const void *key) +{ + DESTROY_IF(this->job); + free(this->id); + free(this); +} + +JNI_METHOD(Scheduler, executeJob, void, + jstring jid) +{ + private_scheduler_t *sched; + entry_t *entry; + char *id; + + sched = (private_scheduler_t*)lib->scheduler; + id = androidjni_convert_jstring(env, jid); + sched->mutex->lock(sched->mutex); + entry = sched->jobs->remove(sched->jobs, id); + sched->mutex->unlock(sched->mutex); + free(id); + + if (entry) + { + lib->processor->queue_job(lib->processor, entry->job); + entry->job = NULL; + destroy_entry(entry, NULL); + } +} + +METHOD(scheduler_t, get_job_load, u_int, + private_scheduler_t *this) +{ + u_int count; + + this->mutex->lock(this->mutex); + count = this->jobs->get_count(this->jobs); + this->mutex->unlock(this->mutex); + return count; +} + +/** + * Allocate an ID for a new job. We do this via JNI so we don't have to rely + * on RNGs being available when we replace the default scheduler. + */ +static jstring allocate_id(private_scheduler_t *this, JNIEnv *env) +{ + jmethodID method_id; + + method_id = (*env)->GetMethodID(env, this->cls, "allocateId", + "()Ljava/lang/String;"); + if (!method_id) + { + return NULL; + } + return (*env)->CallObjectMethod(env, this->obj, method_id); +} + +METHOD(scheduler_t, schedule_job_ms, void, + private_scheduler_t *this, job_t *job, uint32_t ms) +{ + JNIEnv *env; + jmethodID method_id; + entry_t *entry = NULL; + jstring jid; + + androidjni_attach_thread(&env); + jid = allocate_id(this, env); + if (!jid) + { + goto failed; + } + method_id = (*env)->GetMethodID(env, this->cls, "scheduleJob", + "(Ljava/lang/String;J)V"); + if (!method_id) + { + goto failed; + } + + this->mutex->lock(this->mutex); + INIT(entry, + .id = androidjni_convert_jstring(env, jid), + .job = job, + ); + job->status = JOB_STATUS_QUEUED; + this->jobs->put(this->jobs, entry->id, entry); + this->mutex->unlock(this->mutex); + + (*env)->CallVoidMethod(env, this->obj, method_id, jid, (jlong)ms); + if (androidjni_exception_occurred(env)) + { + goto failed; + } + androidjni_detach_thread(); + return; + +failed: + DBG1(DBG_JOB, "unable to schedule job for execution in %u ms", ms); + if (entry) + { + this->mutex->lock(this->mutex); + this->jobs->remove(this->jobs, entry->id); + this->mutex->unlock(this->mutex); + destroy_entry(entry, NULL); + } + else + { + job->destroy(job); + } + androidjni_exception_occurred(env); + androidjni_detach_thread(); +} + +METHOD(scheduler_t, schedule_job_tv, void, + private_scheduler_t *this, job_t *job, timeval_t tv) +{ + timeval_t now; + + time_monotonic(&now); + + if (!timercmp(&now, &tv, <)) + { + /* already expired, just send it to the processor */ + lib->processor->queue_job(lib->processor, job); + return; + } + timersub(&tv, &now, &now); + schedule_job_ms(this, job, now.tv_sec * 1000 + now.tv_usec / 1000); +} + +METHOD(scheduler_t, schedule_job, void, + private_scheduler_t *this, job_t *job, uint32_t s) +{ + schedule_job_ms(this, job, s * 1000); +} + +METHOD(scheduler_t, flush, void, + private_scheduler_t *this) +{ + JNIEnv *env; + jmethodID method_id; + + this->mutex->lock(this->mutex); + this->jobs->destroy_function(this->jobs, destroy_entry); + this->jobs = hashtable_create(hashtable_hash_str, hashtable_equals_str, 16); + this->mutex->unlock(this->mutex); + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "Terminate", "()V"); + if (!method_id) + { + androidjni_exception_occurred(env); + } + else + { + (*env)->CallVoidMethod(env, this->obj, method_id); + androidjni_exception_occurred(env); + } + androidjni_detach_thread(); +} + +METHOD(scheduler_t, destroy, void, + private_scheduler_t *this) +{ + JNIEnv *env; + + androidjni_attach_thread(&env); + if (this->obj) + { + (*env)->DeleteGlobalRef(env, this->obj); + } + if (this->cls) + { + (*env)->DeleteGlobalRef(env, this->cls); + } + androidjni_detach_thread(); + this->mutex->destroy(this->mutex); + this->jobs->destroy(this->jobs); + free(this); +} + +/* + * Described in header + */ +scheduler_t *android_scheduler_create(jobject context) +{ + private_scheduler_t *this; + JNIEnv *env; + jmethodID method_id; + jobject obj; + jclass cls; + + INIT(this, + .public = { + .get_job_load = _get_job_load, + .schedule_job = _schedule_job, + .schedule_job_ms = _schedule_job_ms, + .schedule_job_tv = _schedule_job_tv, + .flush = _flush, + .destroy = _destroy, + }, + .jobs = hashtable_create(hashtable_hash_str, hashtable_equals_str, 16), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + ); + + androidjni_attach_thread(&env); + cls = (*env)->FindClass(env, JNI_PACKAGE_STRING "/Scheduler"); + if (!cls) + { + goto failed; + } + this->cls = (*env)->NewGlobalRef(env, cls); + method_id = (*env)->GetMethodID(env, cls, "", + "(Landroid/content/Context;)V"); + if (!method_id) + { + goto failed; + } + obj = (*env)->NewObject(env, cls, method_id, context); + if (!obj) + { + goto failed; + } + this->obj = (*env)->NewGlobalRef(env, obj); + androidjni_detach_thread(); + return &this->public; + +failed: + DBG1(DBG_JOB, "failed to create Scheduler object"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + destroy(this); + return NULL; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.h b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.h new file mode 100644 index 000000000..a27491084 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup android_scheduler android_scheduler + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_SCHEDULER_H_ +#define ANDROID_SCHEDULER_H_ + +#include + +#include + +/** + * Create an Android-specific scheduler_t implementation. + * + * @param context Context object + * @return scheduler_t instance + */ +scheduler_t *android_scheduler_create(jobject context); + +#endif /** ANDROID_SCHEDULER_H_ @}*/ From 1b4c4123c25c3d83a8904ddac137fc760c98bf60 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 1 May 2020 11:15:38 +0200 Subject: [PATCH 16/20] android: Use Android-specific scheduler on Android 6 and later --- .../src/main/jni/libandroidbridge/android_jni.h | 1 + .../main/jni/libandroidbridge/charonservice.c | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h b/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h index 918c14c5e..3aab3de28 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h @@ -58,6 +58,7 @@ typedef enum { ANDROID_JELLY_BEAN_MR1 = 17, ANDROID_JELLY_BEAN_MR2 = 18, ANDROID_LOLLIPOP = 21, + ANDROID_MARSHMALLOW = 23, } android_sdk_version_t; /** diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index d0f1d35ab..a459f9f3a 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -27,6 +27,7 @@ #include "backend/android_creds.h" #include "backend/android_fetcher.h" #include "backend/android_private_key.h" +#include "backend/android_scheduler.h" #include "backend/android_service.h" #include "kernel/android_ipsec.h" #include "kernel/android_net.h" @@ -93,6 +94,11 @@ struct private_charonservice_t { * Sockets that were bypassed and we keep track for */ linked_list_t *sockets; + + /** + * Default scheduler if we don't use it + */ + scheduler_t *default_scheduler; }; /** @@ -574,6 +580,15 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder, ); charonservice = &this->public; + if (android_sdk_version >= ANDROID_MARSHMALLOW) + { + /* use a custom scheduler so the app is woken when jobs have to run. + * we can't destroy the default scheduler here due to the scheduler + * job that's operating on it, so we stash it away until later */ + this->default_scheduler = lib->scheduler; + lib->scheduler = android_scheduler_create(service); + } + lib->plugins->add_static_features(lib->plugins, "androidbridge", features, countof(features), TRUE, NULL, NULL); @@ -600,6 +615,7 @@ static void charonservice_deinit(JNIEnv *env) { private_charonservice_t *this = (private_charonservice_t*)charonservice; + DESTROY_IF(this->default_scheduler); this->network_manager->destroy(this->network_manager); this->sockets->destroy(this->sockets); this->builder->destroy(this->builder); From d67a5b0c4d49ce19137b26ab658cd9ac759448ae Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 2 May 2020 09:20:59 +0200 Subject: [PATCH 17/20] android: Use the default scheduler for short-term events Using AlarmManager has quite some overhead, so we use our regular scheduler for events that are to be executed in the near future. --- .../backend/android_scheduler.c | 25 ++++++++++++++++++- .../backend/android_scheduler.h | 7 +++++- .../main/jni/libandroidbridge/charonservice.c | 21 +++++----------- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.c index 5a901fe15..3252082bb 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.c @@ -21,6 +21,12 @@ #include #include +/** + * Threshold in milliseconds up to which the default scheduler is used. + * This includes the roaming events (100 ms) and initial retransmits. + */ +#define DEFAULT_SCHEDULER_THRESHOLD 3000 + typedef struct private_scheduler_t private_scheduler_t; /** @@ -52,6 +58,11 @@ struct private_scheduler_t { * Mutex to safely access the scheduled jobs. */ mutex_t *mutex; + + /** + * Default scheduler used for short-term events. + */ + scheduler_t *default_scheduler; }; /** @@ -137,6 +148,14 @@ METHOD(scheduler_t, schedule_job_ms, void, entry_t *entry = NULL; jstring jid; + /* use the default scheduler for short-term events */ + if (ms <= DEFAULT_SCHEDULER_THRESHOLD) + { + this->default_scheduler->schedule_job_ms(this->default_scheduler, + job, ms); + return; + } + androidjni_attach_thread(&env); jid = allocate_id(this, env); if (!jid) @@ -213,6 +232,8 @@ METHOD(scheduler_t, flush, void, JNIEnv *env; jmethodID method_id; + this->default_scheduler->flush(this->default_scheduler); + this->mutex->lock(this->mutex); this->jobs->destroy_function(this->jobs, destroy_entry); this->jobs = hashtable_create(hashtable_hash_str, hashtable_equals_str, 16); @@ -247,6 +268,7 @@ METHOD(scheduler_t, destroy, void, (*env)->DeleteGlobalRef(env, this->cls); } androidjni_detach_thread(); + this->default_scheduler->destroy(this->default_scheduler); this->mutex->destroy(this->mutex); this->jobs->destroy(this->jobs); free(this); @@ -255,7 +277,7 @@ METHOD(scheduler_t, destroy, void, /* * Described in header */ -scheduler_t *android_scheduler_create(jobject context) +scheduler_t *android_scheduler_create(jobject context, scheduler_t *scheduler) { private_scheduler_t *this; JNIEnv *env; @@ -272,6 +294,7 @@ scheduler_t *android_scheduler_create(jobject context) .flush = _flush, .destroy = _destroy, }, + .default_scheduler = scheduler, .jobs = hashtable_create(hashtable_hash_str, hashtable_equals_str, 16), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), ); diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.h b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.h index a27491084..eea011211 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.h +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_scheduler.h @@ -28,9 +28,14 @@ /** * Create an Android-specific scheduler_t implementation. * + * The given scheduler is used for short-term events. We can't destroy it anyway + * because of the scheduler job operating on it, and this way we can use it to + * avoid the overhead of broadcasts for some events. + * * @param context Context object + * @param scheduler the default scheduler used as fallback * @return scheduler_t instance */ -scheduler_t *android_scheduler_create(jobject context); +scheduler_t *android_scheduler_create(jobject context, scheduler_t *scheduler); #endif /** ANDROID_SCHEDULER_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index a459f9f3a..bb8bdba82 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -94,11 +94,6 @@ struct private_charonservice_t { * Sockets that were bypassed and we keep track for */ linked_list_t *sockets; - - /** - * Default scheduler if we don't use it - */ - scheduler_t *default_scheduler; }; /** @@ -580,15 +575,6 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder, ); charonservice = &this->public; - if (android_sdk_version >= ANDROID_MARSHMALLOW) - { - /* use a custom scheduler so the app is woken when jobs have to run. - * we can't destroy the default scheduler here due to the scheduler - * job that's operating on it, so we stash it away until later */ - this->default_scheduler = lib->scheduler; - lib->scheduler = android_scheduler_create(service); - } - lib->plugins->add_static_features(lib->plugins, "androidbridge", features, countof(features), TRUE, NULL, NULL); @@ -615,7 +601,6 @@ static void charonservice_deinit(JNIEnv *env) { private_charonservice_t *this = (private_charonservice_t*)charonservice; - DESTROY_IF(this->default_scheduler); this->network_manager->destroy(this->network_manager); this->sockets->destroy(this->sockets); this->builder->destroy(this->builder); @@ -661,6 +646,12 @@ JNI_METHOD(CharonVpnService, initializeCharon, jboolean, return FALSE; } + if (android_sdk_version >= ANDROID_MARSHMALLOW) + { + /* use a custom scheduler so the app is woken when jobs have to run */ + lib->scheduler = android_scheduler_create(this, lib->scheduler); + } + /* set options before initializing other libraries that might read them */ logfile = androidjni_convert_jstring(env, jlogfile); From a0d32a2d13f86ab87c34245c957e192658a45fc0 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 8 May 2020 12:17:52 +0200 Subject: [PATCH 18/20] android: Ask user to add our app to the device's power whitelist This is necessary so we can actually schedule events accurately in Doze mode. Otherwise, we'd only get woken in intervals of several minutes (up to 15 according to the docs) after about an hour. --- .../android/app/src/main/AndroidManifest.xml | 3 +- .../android/ui/VpnProfileControlActivity.java | 74 +++++++++++++++++-- .../app/src/main/res/values-de/strings.xml | 2 + .../app/src/main/res/values-pl/strings.xml | 2 + .../app/src/main/res/values-ru/strings.xml | 2 + .../app/src/main/res/values-ua/strings.xml | 2 + .../src/main/res/values-zh-rCN/strings.xml | 2 + .../src/main/res/values-zh-rTW/strings.xml | 2 + .../app/src/main/res/values/strings.xml | 2 + 9 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml index 2b2ddfcdd..6c61e06f1 100644 --- a/src/frontends/android/app/src/main/AndroidManifest.xml +++ b/src/frontends/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ VPN umschalten diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index 6f4716ba8..2e1df077e 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -215,6 +215,8 @@ Retry in %1$d seconds Cancel retry + Disable battery optimizations + Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established. Toggle VPN diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 5def66df8..23fa95d38 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -212,6 +212,8 @@ Retry in %1$d seconds Cancel retry + Disable battery optimizations + Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established. Toggle VPN diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index 4920aa238..b4937364e 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -213,6 +213,8 @@ Retry in %1$d seconds Cancel retry + Disable battery optimizations + Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established. Toggle VPN diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml index 3408f5f55..c1dbefb66 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml @@ -212,6 +212,8 @@ Retry in %1$d seconds Cancel retry + Disable battery optimizations + Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established. Toggle VPN diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml index e3d7f0361..13e75a43b 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml @@ -212,6 +212,8 @@ Retry in %1$d seconds Cancel retry + Disable battery optimizations + Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established. Toggle VPN diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index 4d9fd879f..fb74d3f83 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -215,6 +215,8 @@ Retry in %1$d seconds Cancel retry + Disable battery optimizations + Please confirm the next dialog to add the app to the device\'s power whitelist so it can ignore battery optimizations and schedule NAT keep-alives and rekeyings accurately in order to constantly keep reachable while the VPN is established. Toggle VPN From 5d01aaf91d26a351e88b07189c717f220498c65e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 8 May 2020 14:33:05 +0200 Subject: [PATCH 19/20] android: Increase lifetimes a bit This should avoid clashes of soft and hard lifetimes even if the app is not whitelisted. --- .../src/main/jni/libandroidbridge/backend/android_service.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c index f37d99295..ba7a10ddb 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c @@ -754,13 +754,13 @@ static job_requeue_t initiate(private_android_service_t *this) .unique = UNIQUE_REPLACE, .rekey_time = 36000, /* 10h */ .jitter_time = 600, /* 10min */ - .over_time = 600, /* 10min */ + .over_time = 1800, /* 30min */ }; child_cfg_create_t child = { .lifetime = { .time = { - .life = 3600, /* 1h */ - .rekey = 3000, /* 50min */ + .life = 9000, /* 2.5h */ + .rekey = 7200, /* 2h */ .jitter = 300 /* 5min */ }, }, From 04f4bef2354dbc94b89af30206acbe3d473263d2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 11 May 2020 15:49:22 +0200 Subject: [PATCH 20/20] android: Add a preference flag to ignore battery optimizations This allows users to ignore whether the app is on the device's power whitelist without a warning. The flag is currently not set automatically if the user denies the request. --- .../strongswan/android/ui/VpnProfileControlActivity.java | 7 ++++++- .../main/java/org/strongswan/android/utils/Constants.java | 7 ++++++- .../android/app/src/main/res/values-de/strings.xml | 4 +++- .../android/app/src/main/res/values-pl/strings.xml | 2 ++ .../android/app/src/main/res/values-ru/strings.xml | 2 ++ .../android/app/src/main/res/values-ua/strings.xml | 2 ++ .../android/app/src/main/res/values-zh-rCN/strings.xml | 2 ++ .../android/app/src/main/res/values-zh-rTW/strings.xml | 2 ++ src/frontends/android/app/src/main/res/values/strings.xml | 2 ++ src/frontends/android/app/src/main/res/xml/settings.xml | 7 ++++++- 10 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java index 896c9de94..6cf91a5f6 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.net.Uri; import android.net.VpnService; import android.os.Build; @@ -41,6 +42,7 @@ import org.strongswan.android.data.VpnProfileDataSource; import org.strongswan.android.data.VpnType.VpnTypeFeature; import org.strongswan.android.logic.VpnStateService; import org.strongswan.android.logic.VpnStateService.State; +import org.strongswan.android.utils.Constants; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -49,6 +51,7 @@ import androidx.appcompat.app.AppCompatDialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.preference.PreferenceManager; public class VpnProfileControlActivity extends AppCompatActivity { @@ -198,7 +201,9 @@ public class VpnProfileControlActivity extends AppCompatActivity if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PowerManager pm = (PowerManager)this.getSystemService(Context.POWER_SERVICE); - if (!pm.isIgnoringBatteryOptimizations(this.getPackageName())) + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); + if (!pm.isIgnoringBatteryOptimizations(this.getPackageName()) && + !pref.getBoolean(Constants.PREF_IGNORE_POWER_WHITELIST, false)) { PowerWhitelistRequired whitelist = new PowerWhitelistRequired(); mWaitingForResult = true; diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java index 52234933b..71b6e0fce 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Tobias Brunner + * Copyright (C) 2016-2020 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -58,4 +58,9 @@ public final class Constants * Preference key to store the most recently used VPN profile */ public static final String PREF_MRU_VPN_PROFILE = "pref_mru_vpn_profile"; + + /** + * Preference key to store whether the user permanently dismissed our warning to add the app to the power whitelist + */ + public static final String PREF_IGNORE_POWER_WHITELIST = "pref_ignore_power_whitelist"; } diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml index 76652bc63..aaef5a6b9 100644 --- a/src/frontends/android/app/src/main/res/values-de/strings.xml +++ b/src/frontends/android/app/src/main/res/values-de/strings.xml @@ -1,6 +1,6 @@ Log diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index 2e1df077e..16ae3fc7d 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -36,6 +36,8 @@ Settings Default VPN profile Connect to most recently used profile + Ignore battery optimizations + Don\'t show a warning if the app is not on the device\'s power whitelist Log diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 23fa95d38..426084f22 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -33,6 +33,8 @@ Settings Default VPN profile Connect to most recently used profile + Ignore battery optimizations + Don\'t show a warning if the app is not on the device\'s power whitelist Журнал diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index b4937364e..7f414e524 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -34,6 +34,8 @@ Settings Default VPN profile Connect to most recently used profile + Ignore battery optimizations + Don\'t show a warning if the app is not on the device\'s power whitelist Журнал diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml index c1dbefb66..75a3bef33 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml @@ -33,6 +33,8 @@ Settings Default VPN profile Connect to most recently used profile + Ignore battery optimizations + Don\'t show a warning if the app is not on the device\'s power whitelist 日志 diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml index 13e75a43b..28e4e5ae1 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml @@ -33,6 +33,8 @@ Settings Default VPN profile Connect to most recently used profile + Ignore battery optimizations + Don\'t show a warning if the app is not on the device\'s power whitelist 日誌 diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index fb74d3f83..b72aad9c6 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -36,6 +36,8 @@ Settings Default VPN profile Connect to most recently used profile + Ignore battery optimizations + Don\'t show a warning if the app is not on the device\'s power whitelist Log diff --git a/src/frontends/android/app/src/main/res/xml/settings.xml b/src/frontends/android/app/src/main/res/xml/settings.xml index 908a888d5..3eb7343e1 100644 --- a/src/frontends/android/app/src/main/res/xml/settings.xml +++ b/src/frontends/android/app/src/main/res/xml/settings.xml @@ -1,6 +1,6 @@