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:
Tobias Brunner
2018-07-03 11:31:38 +02:00
parent 89a88d7c56
commit 68afdd3464
10 changed files with 191 additions and 24 deletions
@@ -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();
}
}
}
}
@@ -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>