From: Tobias Brunner Date: Mon, 18 Jun 2018 17:04:03 +0000 (+0200) Subject: android: Use capped exponential backoff for automatic retries X-Git-Tag: 5.7.0dr5~20^2~28 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1350ee1ec7cff8aa24187f7e959709ef3c2f4547;p=thirdparty%2Fstrongswan.git android: Use capped exponential backoff for automatic retries --- diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java index a0db087bfe..63b54cd2b9 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java @@ -50,7 +50,10 @@ public class VpnStateService extends Service private ImcState mImcState = ImcState.UNKNOWN; private final LinkedList mRemediationInstructions = new LinkedList(); private static long RETRY_INTERVAL = 1000; + /* cap the retry interval at 2 minutes */ + private static long MAX_RETRY_INTERVAL = 120000; private static int RETRY_MSG = 1; + private RetryTimeoutProvider mTimeoutProvider = new RetryTimeoutProvider(); private long mRetryTimeout; private long mRetryIn; @@ -269,6 +272,33 @@ public class VpnStateService extends Service context.startService(intent); } + /** + * Connect (or reconnect) a profile + * @param profileInfo optional profile info (basically the UUID and password), taken from the + * previous profile if null + * @param fromScratch true if this is a manual retry/reconnect or a completely new connection + */ + public void connect(Bundle profileInfo, boolean fromScratch) + { + /* we assume we have the necessary permission */ + Context context = getApplicationContext(); + Intent intent = new Intent(context, CharonVpnService.class); + if (profileInfo == null) + { + profileInfo = new Bundle(); + profileInfo.putLong(VpnProfileDataSource.KEY_ID, mProfile.getId()); + /* pass the previous password along */ + profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, mProfile.getPassword()); + } + if (fromScratch) + { + /* reset if this is a manual retry or a new connection */ + mTimeoutProvider.reset(); + } + intent.putExtras(profileInfo); + context.startService(intent); + } + /** * Reconnect to the previous profile. */ @@ -278,15 +308,7 @@ public class VpnStateService extends Service { return; } - Bundle profileInfo = new Bundle(); - profileInfo.putLong(VpnProfileDataSource.KEY_ID, mProfile.getId()); - /* pass the previous password along */ - profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, mProfile.getPassword()); - /* we assume we have the necessary permission */ - Context context = getApplicationContext(); - Intent intent = new Intent(context, CharonVpnService.class); - intent.putExtras(profileInfo); - context.startService(intent); + connect(null, true); } /** @@ -361,6 +383,10 @@ public class VpnStateService extends Service @Override public Boolean call() throws Exception { + if (state == State.CONNECTED) + { /* reset counter in case there is an error later on */ + mTimeoutProvider.reset(); + } if (VpnStateService.this.mState != state) { VpnStateService.this.mState = state; @@ -457,36 +483,8 @@ public class VpnStateService extends Service */ private void setRetryTimer(ErrorState error) { - long timeout; - - switch (error) - { - case AUTH_FAILED: - timeout = 20000; - break; - case PEER_AUTH_FAILED: - timeout = 20000; - break; - case LOOKUP_FAILED: - timeout = 10000; - break; - case UNREACHABLE: - timeout = 10000; - break; - case PASSWORD_MISSING: - /* this needs user intervention (entering the password) */ - timeout = 0; - break; - case CERTIFICATE_UNAVAILABLE: - /* if this is because the device has to be unlocked we might be able to reconnect */ - timeout = 10000; - break; - default: - timeout = 20000; - break; - } - mRetryTimeout = mRetryIn = timeout; - if (timeout <= 0) + mRetryTimeout = mRetryIn = mTimeoutProvider.getTimeout(error); + if (mRetryTimeout <= 0) { return; } @@ -535,8 +533,59 @@ public class VpnStateService extends Service } else { - mService.get().reconnect(); + mService.get().connect(null, false); } } } + + /** + * Class that handles an exponential backoff for retry timeouts + */ + private static class RetryTimeoutProvider + { + private long mRetry; + + private long getBaseTimeout(ErrorState error) + { + switch (error) + { + case AUTH_FAILED: + return 10000; + case PEER_AUTH_FAILED: + return 5000; + case LOOKUP_FAILED: + return 5000; + case UNREACHABLE: + return 5000; + case PASSWORD_MISSING: + /* this needs user intervention (entering the password) */ + return 0; + case CERTIFICATE_UNAVAILABLE: + /* if this is because the device has to be unlocked we might be able to reconnect */ + return 5000; + default: + return 10000; + } + } + + /** + * Called each time a new retry timeout is started. The timeout increases until reset() is + * called and the base timeout is returned again. + * @param error Error state + */ + public long getTimeout(ErrorState error) + { + long timeout = (long)(getBaseTimeout(error) * Math.pow(2, mRetry++)); + /* return the result rounded to seconds */ + return Math.min((timeout / 1000) * 1000, MAX_RETRY_INTERVAL); + } + + /** + * Reset the retry counter. + */ + public void reset() + { + mRetry = 0; + } + } } 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 6f5cddd95e..a6cad4ecdb 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 @@ -40,7 +40,6 @@ import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; import org.strongswan.android.data.VpnType.VpnTypeFeature; -import org.strongswan.android.logic.CharonVpnService; import org.strongswan.android.logic.VpnStateService; import org.strongswan.android.logic.VpnStateService.State; @@ -176,9 +175,10 @@ public class VpnProfileControlActivity extends AppCompatActivity case PREPARE_VPN_SERVICE: if (resultCode == RESULT_OK && mProfileInfo != null) { - Intent intent = new Intent(this, CharonVpnService.class); - intent.putExtras(mProfileInfo); - this.startService(intent); + if (mService != null) + { + mService.connect(mProfileInfo, true); + } finish(); } else