android: Add an automatic reconnect on errors
This way the connection will be attempted to be kept up even on "fatal" errors like authentication failures.
This commit is contained in:
+124
-1
@@ -22,6 +22,8 @@ import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.strongswan.android.R;
|
||||
import org.strongswan.android.data.VpnProfile;
|
||||
@@ -29,6 +31,7 @@ import org.strongswan.android.data.VpnProfileDataSource;
|
||||
import org.strongswan.android.logic.imc.ImcState;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
@@ -46,6 +49,10 @@ public class VpnStateService extends Service
|
||||
private ErrorState mError = ErrorState.NO_ERROR;
|
||||
private ImcState mImcState = ImcState.UNKNOWN;
|
||||
private final LinkedList<RemediationInstruction> mRemediationInstructions = new LinkedList<RemediationInstruction>();
|
||||
private static long RETRY_INTERVAL = 1000;
|
||||
private static int RETRY_MSG = 1;
|
||||
private long mRetryTimeout;
|
||||
private long mRetryIn;
|
||||
|
||||
public enum State
|
||||
{
|
||||
@@ -93,7 +100,7 @@ public class VpnStateService extends Service
|
||||
{
|
||||
/* this handler allows us to notify listeners from the UI thread and
|
||||
* not from the threads that actually report any state changes */
|
||||
mHandler = new Handler();
|
||||
mHandler = new RetryHandler(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,6 +158,24 @@ public class VpnStateService extends Service
|
||||
return mConnectionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of seconds until there is an automatic retry to reconnect.
|
||||
* @return total number of seconds until the retry
|
||||
*/
|
||||
public int getRetryTimeout()
|
||||
{
|
||||
return (int)(mRetryTimeout / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of seconds until there is an automatic retry to reconnect.
|
||||
* @return number of seconds until the retry
|
||||
*/
|
||||
public int getRetryIn()
|
||||
{
|
||||
return (int)(mRetryIn / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state.
|
||||
*
|
||||
@@ -231,6 +256,7 @@ public class VpnStateService extends Service
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
resetRetryTimer();
|
||||
/* as soon as the TUN device is created by calling establish() on the
|
||||
* VpnService.Builder object the system binds to the service and keeps
|
||||
* bound until the file descriptor of the TUN device is closed. thus
|
||||
@@ -310,6 +336,7 @@ public class VpnStateService extends Service
|
||||
@Override
|
||||
public Boolean call() throws Exception
|
||||
{
|
||||
resetRetryTimer();
|
||||
VpnStateService.this.mConnectionID++;
|
||||
VpnStateService.this.mProfile = profile;
|
||||
VpnStateService.this.mState = State.CONNECTING;
|
||||
@@ -359,6 +386,14 @@ public class VpnStateService extends Service
|
||||
{
|
||||
if (VpnStateService.this.mError != error)
|
||||
{
|
||||
if (VpnStateService.this.mError == ErrorState.NO_ERROR)
|
||||
{
|
||||
setRetryTimer(error);
|
||||
}
|
||||
else if (error == ErrorState.NO_ERROR)
|
||||
{
|
||||
resetRetryTimer();
|
||||
}
|
||||
VpnStateService.this.mError = error;
|
||||
return true;
|
||||
}
|
||||
@@ -416,4 +451,92 @@ public class VpnStateService extends Service
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the retry timer
|
||||
*/
|
||||
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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
mHandler.sendMessageAtTime(mHandler.obtainMessage(RETRY_MSG), SystemClock.uptimeMillis() + RETRY_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the retry timer
|
||||
*/
|
||||
private void resetRetryTimer()
|
||||
{
|
||||
mRetryTimeout = 0;
|
||||
mRetryIn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Special Handler subclass that handles the retry countdown (more accurate than CountDownTimer)
|
||||
*/
|
||||
private static class RetryHandler extends Handler {
|
||||
WeakReference<VpnStateService> mService;
|
||||
|
||||
public RetryHandler(VpnStateService service)
|
||||
{
|
||||
mService = new WeakReference<>(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg)
|
||||
{
|
||||
/* handle retry countdown */
|
||||
if (mService.get().mRetryTimeout <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
mService.get().mRetryIn -= RETRY_INTERVAL;
|
||||
if (mService.get().mRetryIn > 0)
|
||||
{
|
||||
/* calculate next interval before notifying listeners */
|
||||
long next = SystemClock.uptimeMillis() + RETRY_INTERVAL;
|
||||
|
||||
for (VpnStateListener listener : mService.get().mListeners)
|
||||
{
|
||||
listener.stateChanged();
|
||||
}
|
||||
sendMessageAtTime(obtainMessage(RETRY_MSG), next);
|
||||
}
|
||||
else
|
||||
{
|
||||
mService.get().reconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-21
@@ -41,11 +41,6 @@ import org.strongswan.android.logic.VpnStateService;
|
||||
import org.strongswan.android.logic.VpnStateService.ErrorState;
|
||||
import org.strongswan.android.logic.VpnStateService.State;
|
||||
import org.strongswan.android.logic.VpnStateService.VpnStateListener;
|
||||
import org.strongswan.android.logic.imc.ImcState;
|
||||
import org.strongswan.android.logic.imc.RemediationInstruction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
{
|
||||
@@ -62,7 +57,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
private LinearLayout mErrorView;
|
||||
private TextView mErrorText;
|
||||
private Button mErrorRetry;
|
||||
private Button mDismissError;
|
||||
private Button mShowLog;
|
||||
private long mErrorConnectionID;
|
||||
private VpnStateService mService;
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection()
|
||||
@@ -128,12 +123,13 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
View view = inflater.inflate(R.layout.vpn_state_fragment, null);
|
||||
|
||||
mActionButton = (Button)view.findViewById(R.id.action);
|
||||
mActionButton.setOnClickListener(v -> clearError());
|
||||
enableActionButton(null);
|
||||
|
||||
mErrorView = view.findViewById(R.id.vpn_error);
|
||||
mErrorText = view.findViewById(R.id.vpn_error_text);
|
||||
mErrorRetry = view.findViewById(R.id.retry);
|
||||
mDismissError = view.findViewById(R.id.dismiss_error);
|
||||
mShowLog = view.findViewById(R.id.show_log);
|
||||
mProgress = (ProgressBar)view.findViewById(R.id.progress);
|
||||
mStateView = (TextView)view.findViewById(R.id.vpn_state);
|
||||
mColorStateBase = mStateView.getCurrentTextColor();
|
||||
@@ -146,7 +142,10 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
mService.reconnect();
|
||||
}
|
||||
});
|
||||
mDismissError.setOnClickListener(v -> clearError());
|
||||
mShowLog.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(getActivity(), LogActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -194,7 +193,6 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
VpnProfile profile = mService.getProfile();
|
||||
State state = mService.getState();
|
||||
ErrorState error = mService.getErrorState();
|
||||
ImcState imcState = mService.getImcState();
|
||||
String name = "";
|
||||
|
||||
if (getActivity() == null)
|
||||
@@ -207,12 +205,13 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
name = profile.getName();
|
||||
}
|
||||
|
||||
if (reportError(connectionID, name, error, imcState))
|
||||
if (reportError(connectionID, name, error))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mProfileNameView.setText(name);
|
||||
mProgress.setIndeterminate(true);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
@@ -247,7 +246,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
}
|
||||
}
|
||||
|
||||
private boolean reportError(long connectionID, String name, ErrorState error, ImcState imcState)
|
||||
private boolean reportError(long connectionID, String name, ErrorState error)
|
||||
{
|
||||
if (error == ErrorState.NO_ERROR)
|
||||
{
|
||||
@@ -257,15 +256,26 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
mErrorConnectionID = connectionID;
|
||||
mProfileNameView.setText(name);
|
||||
showProfile(true);
|
||||
mProgress.setVisibility(View.GONE);
|
||||
mStateView.setText(R.string.state_error);
|
||||
mStateView.setTextColor(mColorStateError);
|
||||
enableActionButton(getString(R.string.show_log));
|
||||
mActionButton.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(getActivity(), LogActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
mErrorText.setText(getString(R.string.error_format, getString(mService.getErrorText())));
|
||||
enableActionButton(getString(android.R.string.cancel));
|
||||
|
||||
int retry = mService.getRetryIn();
|
||||
if (retry > 0)
|
||||
{
|
||||
mProgress.setIndeterminate(false);
|
||||
mProgress.setMax(mService.getRetryTimeout());
|
||||
mProgress.setProgress(retry);
|
||||
mProgress.setVisibility(View.VISIBLE);
|
||||
mStateView.setText(getResources().getQuantityString(R.plurals.retry_in, retry, retry));
|
||||
}
|
||||
else if (mService.getRetryTimeout() <= 0)
|
||||
{
|
||||
mProgress.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
String text = getString(R.string.error_format, getString(mService.getErrorText()));
|
||||
mErrorText.setText(text);
|
||||
mErrorView.setVisibility(View.VISIBLE);
|
||||
return true;
|
||||
}
|
||||
@@ -281,19 +291,17 @@ public class VpnStateFragment extends Fragment implements VpnStateListener
|
||||
mActionButton.setText(text);
|
||||
mActionButton.setEnabled(text != null);
|
||||
mActionButton.setVisibility(text != null ? View.VISIBLE : View.GONE);
|
||||
mActionButton.setOnClickListener(mDisconnectListener);
|
||||
}
|
||||
|
||||
private void clearError()
|
||||
{
|
||||
if (mService != null)
|
||||
{
|
||||
mService.disconnect();
|
||||
if (mService.getConnectionID() == mErrorConnectionID)
|
||||
{
|
||||
mService.disconnect();
|
||||
mService.setError(ErrorState.NO_ERROR);
|
||||
}
|
||||
}
|
||||
updateView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="Failed to establish VPN: Server is unreachable"
|
||||
@@ -48,11 +49,11 @@
|
||||
android:gravity="end" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/dismiss_error"
|
||||
android:id="@+id/show_log"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:text="@string/dismiss"
|
||||
android:text="@string/show_log"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
|
||||
@@ -198,6 +198,11 @@
|
||||
<string name="disconnect_active_connection">Dies trennt die aktuelle VPN Verbindung!</string>
|
||||
<string name="connect">Verbinden</string>
|
||||
<string name="retry">Wiederholen</string>
|
||||
<plurals name="retry_in">
|
||||
<item quantity="one">Wiederholen in %1$d Sekunde</item>
|
||||
<item quantity="other">Wiederholen in %1$d Sekunden</item>
|
||||
</plurals>
|
||||
<string name="cancel_retry">Wiederholen abbrechen</string>
|
||||
|
||||
<!-- Quick Settings tile -->
|
||||
<string name="tile_default">VPN umschalten</string>
|
||||
|
||||
@@ -198,6 +198,11 @@
|
||||
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
|
||||
<string name="connect">Połącz</string>
|
||||
<string name="retry">Retry</string>
|
||||
<plurals name="retry_in">
|
||||
<item quantity="one">Retry in %1$d second</item>
|
||||
<item quantity="other">Retry in %1$d seconds</item>
|
||||
</plurals>
|
||||
<string name="cancel_retry">Cancel retry</string>
|
||||
|
||||
<!-- Quick Settings tile -->
|
||||
<string name="tile_default">Toggle VPN</string>
|
||||
|
||||
@@ -195,6 +195,11 @@
|
||||
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
|
||||
<string name="connect">Соединить</string>
|
||||
<string name="retry">Retry</string>
|
||||
<plurals name="retry_in">
|
||||
<item quantity="one">Retry in %1$d second</item>
|
||||
<item quantity="other">Retry in %1$d seconds</item>
|
||||
</plurals>
|
||||
<string name="cancel_retry">Cancel retry</string>
|
||||
|
||||
<!-- Quick Settings tile -->
|
||||
<string name="tile_default">Toggle VPN</string>
|
||||
|
||||
@@ -196,6 +196,11 @@
|
||||
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
|
||||
<string name="connect">Підключити</string>
|
||||
<string name="retry">Retry</string>
|
||||
<plurals name="retry_in">
|
||||
<item quantity="one">Retry in %1$d second</item>
|
||||
<item quantity="other">Retry in %1$d seconds</item>
|
||||
</plurals>
|
||||
<string name="cancel_retry">Cancel retry</string>
|
||||
|
||||
<!-- Quick Settings tile -->
|
||||
<string name="tile_default">Toggle VPN</string>
|
||||
|
||||
@@ -195,6 +195,11 @@
|
||||
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
|
||||
<string name="connect">连接</string>
|
||||
<string name="retry">Retry</string>
|
||||
<plurals name="retry_in">
|
||||
<item quantity="one">Retry in %1$d second</item>
|
||||
<item quantity="other">Retry in %1$d seconds</item>
|
||||
</plurals>
|
||||
<string name="cancel_retry">Cancel retry</string>
|
||||
|
||||
<!-- Quick Settings tile -->
|
||||
<string name="tile_default">Toggle VPN</string>
|
||||
|
||||
@@ -195,6 +195,11 @@
|
||||
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
|
||||
<string name="connect">連線</string>
|
||||
<string name="retry">Retry</string>
|
||||
<plurals name="retry_in">
|
||||
<item quantity="one">Retry in %1$d second</item>
|
||||
<item quantity="other">Retry in %1$d seconds</item>
|
||||
</plurals>
|
||||
<string name="cancel_retry">Cancel retry</string>
|
||||
|
||||
<!-- Quick Settings tile -->
|
||||
<string name="tile_default">Toggle VPN</string>
|
||||
|
||||
@@ -198,6 +198,11 @@
|
||||
<string name="disconnect_active_connection">This will disconnect the active VPN connection!</string>
|
||||
<string name="connect">Connect</string>
|
||||
<string name="retry">Retry</string>
|
||||
<plurals name="retry_in">
|
||||
<item quantity="one">Retry in %1$d second</item>
|
||||
<item quantity="other">Retry in %1$d seconds</item>
|
||||
</plurals>
|
||||
<string name="cancel_retry">Cancel retry</string>
|
||||
|
||||
<!-- Quick Settings tile -->
|
||||
<string name="tile_default">Toggle VPN</string>
|
||||
|
||||
Reference in New Issue
Block a user