]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
android: Use capped exponential backoff for automatic retries
authorTobias Brunner <tobias@strongswan.org>
Mon, 18 Jun 2018 17:04:03 +0000 (19:04 +0200)
committerTobias Brunner <tobias@strongswan.org>
Tue, 3 Jul 2018 09:31:39 +0000 (11:31 +0200)
src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java

index a0db087bfe7c35cb1533c7a428115da31b46c37e..63b54cd2b9a091a31839e25949aa62274548581b 100644 (file)
@@ -50,7 +50,10 @@ public class VpnStateService extends Service
        private ImcState mImcState = ImcState.UNKNOWN;
        private final LinkedList<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>();
        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;
+               }
+       }
 }
index 6f5cddd95e117b3a8f1c3844a94d578b1ce69ac8..a6cad4ecdb9fb8519a9fcb2d580993bae30d01c7 100644 (file)
@@ -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