Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a282ea50 authored by Cody Kesting's avatar Cody Kesting
Browse files

Implement Safemode alarm in VcnGatewayConnection.

This CL updates VcnGatewayConnection to use WakeupMessages to support
Safemode detection. Safemode is entered if the VcnGatewayConnection
instance takes more than SAFEMODE_TIMEOUT_SECONDS seconds to a) go from
DisconnectedState to ConnectedState, or b) exit and re-enter
ConnectedState. If this timeout is exceeded, the instance will notify
its managing Vcn via VcnGatewayStatusCallback#onEnteredSafemode.

Bug: 178140973
Test: atest FrameworksVcnTests
Change-Id: Iff40aba639b465c3959ef383f1001be419daba4d
parent 6d3bd2dc
Loading
Loading
Loading
Loading
+104 −4
Original line number Diff line number Diff line
@@ -38,10 +38,12 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgent.ValidationStatus;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
import android.net.annotations.PolicyDirection;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -151,6 +153,9 @@ public class VcnGatewayConnection extends StateMachine {
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM";

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM";

    private static final int[] MERGED_CAPABILITIES =
            new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
    private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
@@ -167,6 +172,9 @@ public class VcnGatewayConnection extends StateMachine {
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final int TEARDOWN_TIMEOUT_SECONDS = 5;

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    static final int SAFEMODE_TIMEOUT_SECONDS = 30;

    private interface EventInfo {}

    /**
@@ -409,6 +417,23 @@ public class VcnGatewayConnection extends StateMachine {
    // TODO(b/178426520): implement handling of this event
    private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;

    /**
     * Sent when this VcnGatewayConnection has entered Safemode.
     *
     * <p>A VcnGatewayConnection enters Safemode when it takes over {@link
     * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
     *
     * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
     * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down
     * its VcnGatewayConnectin(s).
     *
     * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
     * validated yet), and RetryTimeoutState.
     *
     * @param arg1 The "all" token; this signal is always honored.
     */
    private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10;

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    @NonNull
    final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -520,11 +545,13 @@ public class VcnGatewayConnection extends StateMachine {
     * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
     * otherwise.
     */
    private NetworkAgent mNetworkAgent;
    @VisibleForTesting(visibility = Visibility.PRIVATE)
    NetworkAgent mNetworkAgent;

    @Nullable private WakeupMessage mTeardownTimeoutAlarm;
    @Nullable private WakeupMessage mDisconnectRequestAlarm;
    @Nullable private WakeupMessage mRetryTimeoutAlarm;
    @Nullable private WakeupMessage mSafemodeTimeoutAlarm;

    public VcnGatewayConnection(
            @NonNull VcnContext vcnContext,
@@ -611,6 +638,7 @@ public class VcnGatewayConnection extends StateMachine {
        cancelTeardownTimeoutAlarm();
        cancelDisconnectRequestAlarm();
        cancelRetryTimeoutAlarm();
        cancelSafemodeAlarm();

        mUnderlyingNetworkTracker.teardown();
    }
@@ -899,6 +927,30 @@ public class VcnGatewayConnection extends StateMachine {
        removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    void setSafemodeAlarm() {
        // Only schedule a NEW alarm if none is already set.
        if (mSafemodeTimeoutAlarm != null) {
            return;
        }

        final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
        mSafemodeTimeoutAlarm =
                createScheduledAlarm(
                        SAFEMODE_TIMEOUT_ALARM,
                        delayedMessage,
                        TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
    }

    private void cancelSafemodeAlarm() {
        if (mSafemodeTimeoutAlarm != null) {
            mSafemodeTimeoutAlarm.cancel();
            mSafemodeTimeoutAlarm = null;
        }

        removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED);
    }

    private void sessionLost(int token, @Nullable Exception exception) {
        sendMessageAndAcquireWakeLock(
                EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
@@ -1072,6 +1124,8 @@ public class VcnGatewayConnection extends StateMachine {
            if (mIkeSession != null || mNetworkAgent != null) {
                Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
            }

            cancelSafemodeAlarm();
        }

        @Override
@@ -1095,6 +1149,12 @@ public class VcnGatewayConnection extends StateMachine {
                    break;
            }
        }

        @Override
        protected void exitState() {
            // Safe to blindly set up, as it is cancelled and cleared on entering this state
            setSafemodeAlarm();
        }
    }

    private abstract class ActiveBaseState extends BaseState {
@@ -1185,6 +1245,10 @@ public class VcnGatewayConnection extends StateMachine {
                        transitionTo(mDisconnectedState);
                    }
                    break;
                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
                    mGatewayStatusCallback.onEnteredSafemode();
                    mSafemodeTimeoutAlarm = null;
                    break;
                default:
                    logUnhandledMessage(msg);
                    break;
@@ -1267,6 +1331,10 @@ public class VcnGatewayConnection extends StateMachine {
                case EVENT_DISCONNECT_REQUESTED:
                    handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                    break;
                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
                    mGatewayStatusCallback.onEnteredSafemode();
                    mSafemodeTimeoutAlarm = null;
                    break;
                default:
                    logUnhandledMessage(msg);
                    break;
@@ -1310,6 +1378,14 @@ public class VcnGatewayConnection extends StateMachine {
                        public void unwanted() {
                            teardownAsynchronously();
                        }

                        @Override
                        public void onValidationStatus(
                                @ValidationStatus int status, @Nullable Uri redirectUri) {
                            if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
                                clearFailedAttemptCounterAndSafeModeAlarm();
                            }
                        }
                    };

            agent.register();
@@ -1318,6 +1394,14 @@ public class VcnGatewayConnection extends StateMachine {
            return agent;
        }

        protected void clearFailedAttemptCounterAndSafeModeAlarm() {
            mVcnContext.ensureRunningOnLooperThread();

            // Validated connection, clear failed attempt counter
            mFailedAttempts = 0;
            cancelSafemodeAlarm();
        }

        protected void applyTransform(
                int token,
                @NonNull IpSecTunnelInterface tunnelIface,
@@ -1397,9 +1481,6 @@ public class VcnGatewayConnection extends StateMachine {
                    teardownAsynchronously();
                }
            }

            // Successful connection, clear failed attempt counter
            mFailedAttempts = 0;
        }

        @Override
@@ -1436,6 +1517,10 @@ public class VcnGatewayConnection extends StateMachine {
                case EVENT_DISCONNECT_REQUESTED:
                    handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                    break;
                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
                    mGatewayStatusCallback.onEnteredSafemode();
                    mSafemodeTimeoutAlarm = null;
                    break;
                default:
                    logUnhandledMessage(msg);
                    break;
@@ -1478,7 +1563,18 @@ public class VcnGatewayConnection extends StateMachine {
                mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
            } else {
                updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig);

                // mNetworkAgent not null, so the VCN Network has already been established. Clear
                // the failed attempt counter and safe mode alarm since this transition is complete.
                clearFailedAttemptCounterAndSafeModeAlarm();
            }
        }

        @Override
        protected void exitState() {
            // Attempt to set the safe mode alarm - this requires the Vcn Network being validated
            // while in ConnectedState (which cancels the previous alarm)
            setSafemodeAlarm();
        }
    }

@@ -1526,6 +1622,10 @@ public class VcnGatewayConnection extends StateMachine {
                case EVENT_DISCONNECT_REQUESTED:
                    handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
                    break;
                case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
                    mGatewayStatusCallback.onEnteredSafemode();
                    mSafemodeTimeoutAlarm = null;
                    break;
                default:
                    logUnhandledMessage(msg);
                    break;
+28 −1
Original line number Diff line number Diff line
@@ -34,8 +34,10 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;

import androidx.test.filters.SmallTest;
@@ -72,6 +74,11 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
        verify(mDeps).newIkeSession(any(), any(), any(), any(), any());
    }

    @Test
    public void testEnterStateDoesNotCancelSafemodeAlarm() {
        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
    }

    @Test
    public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
        mGatewayConnection
@@ -122,6 +129,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection

    @Test
    public void testChildOpenedRegistersNetwork() throws Exception {
        // Verify scheduled but not canceled when entering ConnectedState
        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);

        final VcnChildSessionConfiguration mMockChildSessionConfig =
                mock(VcnChildSessionConfiguration.class);
        doReturn(Collections.singletonList(TEST_INTERNAL_ADDR))
@@ -163,24 +173,41 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
        for (int cap : mConfig.getAllExposedCapabilities()) {
            assertTrue(nc.hasCapability(cap));
        }

        // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is
        // canceled
        mGatewayConnection.mNetworkAgent.onValidationStatus(
                NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
        verify(mSafemodeTimeoutAlarm).cancel();
    }

    @Test
    public void testChildSessionClosedTriggersDisconnect() throws Exception {
        // Verify scheduled but not canceled when entering ConnectedState
        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);

        getChildSessionCallback().onClosed();
        mTestLooper.dispatchAll();

        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);

        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
    }

    @Test
    public void testIkeSessionClosedTriggersDisconnect() throws Exception {
        // Verify scheduled but not canceled when entering ConnectedState
        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);

        getIkeSessionCallback().onClosed();
        mTestLooper.dispatchAll();

        assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
        verify(mIkeSession).close();
        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);

        // Since network never validated, verify mSafemodeTimeoutAlarm not canceled
        verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -106,4 +106,9 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio
        verify(mIkeSession).close();
        verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
    }

    @Test
    public void testSafemodeTimeoutNotifiesCallback() {
        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -48,7 +48,8 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect
                        .createIpSecTunnelInterface(
                                DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
        mGatewayConnection.setTunnelInterface(tunnelIface);
        mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);

        // Don't need to transition to DisconnectedState because it is the starting state
        mTestLooper.dispatchAll();
    }

@@ -78,6 +79,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect
        mTestLooper.dispatchAll();

        assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
        verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
    }

    @Test
@@ -98,5 +100,6 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect

        assertNull(mGatewayConnection.getCurrentState());
        verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
        verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -80,4 +80,9 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec
        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
        verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
    }

    @Test
    public void testSafemodeTimeoutNotifiesCallback() {
        verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
    }
}
Loading