Loading services/core/java/com/android/server/connectivity/Vpn.java +95 −8 Original line number Diff line number Diff line Loading @@ -79,6 +79,7 @@ import android.net.NetworkScore; import android.net.RouteInfo; import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; import android.net.VpnProfileState; import android.net.VpnService; Loading Loading @@ -225,6 +226,16 @@ public class Vpn { */ private static final int VPN_DEFAULT_SCORE = 101; /** * The reset session timer for data stall. If a session has not successfully revalidated after * the delay, the session will be torn down and restarted in an attempt to recover. Delay * counter is reset on successful validation only. * * <p>If retries have exceeded the length of this array, the last entry in the array will be * used as a repeating interval. */ private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L}; /** * The initial token value of IKE session. */ Loading Loading @@ -271,6 +282,7 @@ public class Vpn { private final UserManager mUserManager; private final VpnProfileStore mVpnProfileStore; protected boolean mDataStallSuspected = false; @VisibleForTesting VpnProfileStore getVpnProfileStore() { Loading Loading @@ -522,10 +534,28 @@ public class Vpn { @NonNull LinkProperties lp, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { @Nullable NetworkProvider provider, @Nullable ValidationStatusCallback callback) { return new VpnNetworkAgentWrapper( context, looper, logTag, nc, lp, score, config, provider); context, looper, logTag, nc, lp, score, config, provider, callback); } /** * Get the length of time to wait before resetting the ike session when a data stall is * suspected. */ public long getDataStallResetSessionSeconds(int count) { if (count >= DATA_STALL_RESET_DELAYS_SEC.length) { return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1]; } else { return DATA_STALL_RESET_DELAYS_SEC[count]; } } } @VisibleForTesting interface ValidationStatusCallback { void onValidationStatus(int status); } public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd, Loading Loading @@ -1460,6 +1490,11 @@ public class Vpn { @GuardedBy("this") private void agentConnect() { agentConnect(null /* validationCallback */); } @GuardedBy("this") private void agentConnect(@Nullable ValidationStatusCallback validationCallback) { LinkProperties lp = makeLinkProperties(); // VPN either provide a default route (IPv4 or IPv6 or both), or they are a split tunnel Loading Loading @@ -1507,7 +1542,7 @@ public class Vpn { mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */, mNetworkCapabilities, lp, new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(), networkAgentConfig, mNetworkProvider); networkAgentConfig, mNetworkProvider, validationCallback); final long token = Binder.clearCallingIdentity(); try { mNetworkAgent.register(); Loading Loading @@ -2723,7 +2758,7 @@ public class Vpn { @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture; @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture; @Nullable private ScheduledFuture<?> mScheduledHandleDataStallFuture; /** Signal to ensure shutdown is honored even if a new Network is connected. */ private boolean mIsRunning = true; Loading @@ -2749,6 +2784,14 @@ public class Vpn { // mMobikeEnabled can only be updated after IKE AUTH is finished. private boolean mMobikeEnabled = false; /** * The number of attempts to reset the IKE session since the last successful connection. * * <p>This variable controls the retry delay, and is reset when the VPN pass network * validation. */ private int mDataStallRetryCount = 0; /** * The number of attempts since the last successful connection. * Loading Loading @@ -2931,7 +2974,7 @@ public class Vpn { if (isSettingsVpnLocked()) { prepareStatusIntent(); } agentConnect(); agentConnect(this::onValidationStatus); return; // Link properties are already sent. } else { // Underlying networks also set in agentConnect() Loading Loading @@ -3200,18 +3243,52 @@ public class Vpn { // Ignore stale runner. if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return; // Handle the report only for current VPN network. // Handle the report only for current VPN network. If data stall is already // reported, ignoring the other reports. It means that the stall is not // recovered by MOBIKE and should be on the way to reset the ike session. if (mNetworkAgent != null && mNetworkAgent.getNetwork().equals(report.getNetwork())) { && mNetworkAgent.getNetwork().equals(report.getNetwork()) && !mDataStallSuspected) { Log.d(TAG, "Data stall suspected"); // Trigger MOBIKE. maybeMigrateIkeSession(mActiveNetwork); mDataStallSuspected = true; } } } } public void onValidationStatus(int status) { if (status == NetworkAgent.VALIDATION_STATUS_VALID) { // No data stall now. Reset it. mExecutor.execute(() -> { mDataStallSuspected = false; mDataStallRetryCount = 0; if (mScheduledHandleDataStallFuture != null) { Log.d(TAG, "Recovered from stall. Cancel pending reset action."); mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */); mScheduledHandleDataStallFuture = null; } }); } else { // Skip other invalid status if the scheduled recovery exists. if (mScheduledHandleDataStallFuture != null) return; mScheduledHandleDataStallFuture = mExecutor.schedule(() -> { if (mDataStallSuspected) { Log.d(TAG, "Reset session to recover stalled network"); // This will reset old state if it exists. startIkeSession(mActiveNetwork); } // Reset mScheduledHandleDataStallFuture since it's already run on executor // thread. mScheduledHandleDataStallFuture = null; }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS); } } /** * Handles loss of the default underlying network * Loading Loading @@ -4339,6 +4416,7 @@ public class Vpn { // un-finalized. @VisibleForTesting public static class VpnNetworkAgentWrapper extends NetworkAgent { private final ValidationStatusCallback mCallback; /** Create an VpnNetworkAgentWrapper */ public VpnNetworkAgentWrapper( @NonNull Context context, Loading @@ -4348,8 +4426,10 @@ public class Vpn { @NonNull LinkProperties lp, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { @Nullable NetworkProvider provider, @Nullable ValidationStatusCallback callback) { super(context, looper, logTag, nc, lp, score, config, provider); mCallback = callback; } /** Update the LinkProperties */ Loading @@ -4371,6 +4451,13 @@ public class Vpn { public void onNetworkUnwanted() { // We are user controlled, not driven by NetworkRequest. } @Override public void onValidationStatus(int status, Uri redirectUri) { if (mCallback != null) { mCallback.onValidationStatus(status); } } } /** Loading Loading
services/core/java/com/android/server/connectivity/Vpn.java +95 −8 Original line number Diff line number Diff line Loading @@ -79,6 +79,7 @@ import android.net.NetworkScore; import android.net.RouteInfo; import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; import android.net.VpnProfileState; import android.net.VpnService; Loading Loading @@ -225,6 +226,16 @@ public class Vpn { */ private static final int VPN_DEFAULT_SCORE = 101; /** * The reset session timer for data stall. If a session has not successfully revalidated after * the delay, the session will be torn down and restarted in an attempt to recover. Delay * counter is reset on successful validation only. * * <p>If retries have exceeded the length of this array, the last entry in the array will be * used as a repeating interval. */ private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L}; /** * The initial token value of IKE session. */ Loading Loading @@ -271,6 +282,7 @@ public class Vpn { private final UserManager mUserManager; private final VpnProfileStore mVpnProfileStore; protected boolean mDataStallSuspected = false; @VisibleForTesting VpnProfileStore getVpnProfileStore() { Loading Loading @@ -522,10 +534,28 @@ public class Vpn { @NonNull LinkProperties lp, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { @Nullable NetworkProvider provider, @Nullable ValidationStatusCallback callback) { return new VpnNetworkAgentWrapper( context, looper, logTag, nc, lp, score, config, provider); context, looper, logTag, nc, lp, score, config, provider, callback); } /** * Get the length of time to wait before resetting the ike session when a data stall is * suspected. */ public long getDataStallResetSessionSeconds(int count) { if (count >= DATA_STALL_RESET_DELAYS_SEC.length) { return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1]; } else { return DATA_STALL_RESET_DELAYS_SEC[count]; } } } @VisibleForTesting interface ValidationStatusCallback { void onValidationStatus(int status); } public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd, Loading Loading @@ -1460,6 +1490,11 @@ public class Vpn { @GuardedBy("this") private void agentConnect() { agentConnect(null /* validationCallback */); } @GuardedBy("this") private void agentConnect(@Nullable ValidationStatusCallback validationCallback) { LinkProperties lp = makeLinkProperties(); // VPN either provide a default route (IPv4 or IPv6 or both), or they are a split tunnel Loading Loading @@ -1507,7 +1542,7 @@ public class Vpn { mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */, mNetworkCapabilities, lp, new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(), networkAgentConfig, mNetworkProvider); networkAgentConfig, mNetworkProvider, validationCallback); final long token = Binder.clearCallingIdentity(); try { mNetworkAgent.register(); Loading Loading @@ -2723,7 +2758,7 @@ public class Vpn { @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture; @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture; @Nullable private ScheduledFuture<?> mScheduledHandleDataStallFuture; /** Signal to ensure shutdown is honored even if a new Network is connected. */ private boolean mIsRunning = true; Loading @@ -2749,6 +2784,14 @@ public class Vpn { // mMobikeEnabled can only be updated after IKE AUTH is finished. private boolean mMobikeEnabled = false; /** * The number of attempts to reset the IKE session since the last successful connection. * * <p>This variable controls the retry delay, and is reset when the VPN pass network * validation. */ private int mDataStallRetryCount = 0; /** * The number of attempts since the last successful connection. * Loading Loading @@ -2931,7 +2974,7 @@ public class Vpn { if (isSettingsVpnLocked()) { prepareStatusIntent(); } agentConnect(); agentConnect(this::onValidationStatus); return; // Link properties are already sent. } else { // Underlying networks also set in agentConnect() Loading Loading @@ -3200,18 +3243,52 @@ public class Vpn { // Ignore stale runner. if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return; // Handle the report only for current VPN network. // Handle the report only for current VPN network. If data stall is already // reported, ignoring the other reports. It means that the stall is not // recovered by MOBIKE and should be on the way to reset the ike session. if (mNetworkAgent != null && mNetworkAgent.getNetwork().equals(report.getNetwork())) { && mNetworkAgent.getNetwork().equals(report.getNetwork()) && !mDataStallSuspected) { Log.d(TAG, "Data stall suspected"); // Trigger MOBIKE. maybeMigrateIkeSession(mActiveNetwork); mDataStallSuspected = true; } } } } public void onValidationStatus(int status) { if (status == NetworkAgent.VALIDATION_STATUS_VALID) { // No data stall now. Reset it. mExecutor.execute(() -> { mDataStallSuspected = false; mDataStallRetryCount = 0; if (mScheduledHandleDataStallFuture != null) { Log.d(TAG, "Recovered from stall. Cancel pending reset action."); mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */); mScheduledHandleDataStallFuture = null; } }); } else { // Skip other invalid status if the scheduled recovery exists. if (mScheduledHandleDataStallFuture != null) return; mScheduledHandleDataStallFuture = mExecutor.schedule(() -> { if (mDataStallSuspected) { Log.d(TAG, "Reset session to recover stalled network"); // This will reset old state if it exists. startIkeSession(mActiveNetwork); } // Reset mScheduledHandleDataStallFuture since it's already run on executor // thread. mScheduledHandleDataStallFuture = null; }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS); } } /** * Handles loss of the default underlying network * Loading Loading @@ -4339,6 +4416,7 @@ public class Vpn { // un-finalized. @VisibleForTesting public static class VpnNetworkAgentWrapper extends NetworkAgent { private final ValidationStatusCallback mCallback; /** Create an VpnNetworkAgentWrapper */ public VpnNetworkAgentWrapper( @NonNull Context context, Loading @@ -4348,8 +4426,10 @@ public class Vpn { @NonNull LinkProperties lp, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { @Nullable NetworkProvider provider, @Nullable ValidationStatusCallback callback) { super(context, looper, logTag, nc, lp, score, config, provider); mCallback = callback; } /** Update the LinkProperties */ Loading @@ -4371,6 +4451,13 @@ public class Vpn { public void onNetworkUnwanted() { // We are user controlled, not driven by NetworkRequest. } @Override public void onValidationStatus(int status, Uri redirectUri) { if (mCallback != null) { mCallback.onValidationStatus(status); } } } /** Loading