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

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

Implement VCN error callback use.

This CL updates VcnGatewayConnection to notify VcnManagementService
(through Vcn) when errors occur with a gateway. VcnManagementService
then notifies registered, permissioned VcnStatusCallbacks via

Bug: 163433613
Test: atest FrameworksVcnTests
Change-Id: I3be3cac4b591b19a0b0075767fde0ba2eb6e12a2
parent fe052394
Loading
Loading
Loading
Loading
+64 −25
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.net.vcn.IVcnManagementService;
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
import android.net.wifi.WifiInfo;
import android.os.Binder;
@@ -304,8 +305,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                @NonNull ParcelUuid subscriptionGroup,
                @NonNull VcnConfig config,
                @NonNull TelephonySubscriptionSnapshot snapshot,
                @NonNull VcnSafeModeCallback safeModeCallback) {
            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safeModeCallback);
                @NonNull VcnCallback vcnCallback) {
            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, vcnCallback);
        }

        /** Gets the subId indicated by the given {@link WifiInfo}. */
@@ -457,12 +458,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
        //                    VCN.

        final VcnSafeModeCallbackImpl safeModeCallback =
                new VcnSafeModeCallbackImpl(subscriptionGroup);
        final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup);

        final Vcn newInstance =
                mDeps.newVcn(
                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safeModeCallback);
                mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot, vcnCallback);
        mVcns.put(subscriptionGroup, newInstance);

        // Now that a new VCN has started, notify all registered listeners to refresh their
@@ -784,20 +783,47 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    }

    // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
    /** Callback for signalling when a Vcn has entered safe mode. */
    public interface VcnSafeModeCallback {
    /** Callback for Vcn signals sent up to VcnManagementService. */
    public interface VcnCallback {
        /** Called by a Vcn to signal that it has entered safe mode. */
        void onEnteredSafeMode();

        /** Called by a Vcn to signal that an error occurred. */
        void onGatewayConnectionError(
                @NonNull int[] networkCapabilities,
                @VcnErrorCode int errorCode,
                @Nullable String exceptionClass,
                @Nullable String exceptionMessage);
    }

    /** VcnSafeModeCallback is used by Vcns to notify VcnManagementService on entering safe mode. */
    private class VcnSafeModeCallbackImpl implements VcnSafeModeCallback {
    /** VcnCallbackImpl for Vcn signals sent up to VcnManagementService. */
    private class VcnCallbackImpl implements VcnCallback {
        @NonNull private final ParcelUuid mSubGroup;

        private VcnSafeModeCallbackImpl(@NonNull final ParcelUuid subGroup) {
        private VcnCallbackImpl(@NonNull final ParcelUuid subGroup) {
            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
        }

        private boolean isCallbackPermissioned(@NonNull VcnStatusCallbackInfo cbInfo) {
            if (!mSubGroup.equals(cbInfo.mSubGroup)) {
                return false;
            }

            if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(
                    mSubGroup, cbInfo.mPkgName)) {
                return false;
            }

            if (!mLocationPermissionChecker.checkLocationPermission(
                    cbInfo.mPkgName,
                    "VcnStatusCallback" /* featureId */,
                    cbInfo.mUid,
                    null /* message */)) {
                return false;
            }
            return true;
        }

        @Override
        public void onEnteredSafeMode() {
            synchronized (mLock) {
@@ -810,23 +836,36 @@ public class VcnManagementService extends IVcnManagementService.Stub {

                // Notify all registered StatusCallbacks for this subGroup
                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
                    if (!mSubGroup.equals(cbInfo.mSubGroup)) {
                        continue;
                    if (isCallbackPermissioned(cbInfo)) {
                        Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode());
                    }
                }
            }
                    if (!mLastSnapshot.packageHasPermissionsForSubscriptionGroup(
                            mSubGroup, cbInfo.mPkgName)) {
                        continue;
        }

                    if (!mLocationPermissionChecker.checkLocationPermission(
                            cbInfo.mPkgName,
                            "VcnStatusCallback" /* featureId */,
                            cbInfo.mUid,
                            null /* message */)) {
                        continue;
        @Override
        public void onGatewayConnectionError(
                @NonNull int[] networkCapabilities,
                @VcnErrorCode int errorCode,
                @Nullable String exceptionClass,
                @Nullable String exceptionMessage) {
            synchronized (mLock) {
                // Ignore if this subscription group doesn't exist anymore
                if (!mVcns.containsKey(mSubGroup)) {
                    return;
                }

                    Binder.withCleanCallingIdentity(() -> cbInfo.mCallback.onEnteredSafeMode());
                // Notify all registered StatusCallbacks for this subGroup
                for (VcnStatusCallbackInfo cbInfo : mRegisteredStatusCallbacks.values()) {
                    if (isCallbackPermissioned(cbInfo)) {
                        Binder.withCleanCallingIdentity(
                                () ->
                                        cbInfo.mCallback.onGatewayConnectionError(
                                                networkCapabilities,
                                                errorCode,
                                                exceptionClass,
                                                exceptionMessage));
                    }
                }
            }
        }
+26 −14
Original line number Diff line number Diff line
@@ -19,10 +19,12 @@ package com.android.server.vcn;
import static com.android.server.VcnManagementService.VDBG;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
@@ -30,7 +32,7 @@ import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.VcnManagementService.VcnSafeModeCallback;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;

import java.util.Collections;
@@ -97,7 +99,7 @@ public class Vcn extends Handler {
    @NonNull private final ParcelUuid mSubscriptionGroup;
    @NonNull private final Dependencies mDeps;
    @NonNull private final VcnNetworkRequestListener mRequestListener;
    @NonNull private final VcnSafeModeCallback mVcnSafeModeCallback;
    @NonNull private final VcnCallback mVcnCallback;

    @NonNull
    private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
@@ -125,14 +127,8 @@ public class Vcn extends Handler {
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull VcnConfig config,
            @NonNull TelephonySubscriptionSnapshot snapshot,
            @NonNull VcnSafeModeCallback vcnSafeModeCallback) {
        this(
                vcnContext,
                subscriptionGroup,
                config,
                snapshot,
                vcnSafeModeCallback,
                new Dependencies());
            @NonNull VcnCallback vcnCallback) {
        this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -141,13 +137,12 @@ public class Vcn extends Handler {
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull VcnConfig config,
            @NonNull TelephonySubscriptionSnapshot snapshot,
            @NonNull VcnSafeModeCallback vcnSafeModeCallback,
            @NonNull VcnCallback vcnCallback,
            @NonNull Dependencies deps) {
        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
        mVcnContext = vcnContext;
        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
        mVcnSafeModeCallback =
                Objects.requireNonNull(vcnSafeModeCallback, "Missing vcnSafeModeCallback");
        mVcnCallback = Objects.requireNonNull(vcnCallback, "Missing vcnCallback");
        mDeps = Objects.requireNonNull(deps, "Missing deps");
        mRequestListener = new VcnNetworkRequestListener();

@@ -246,7 +241,7 @@ public class Vcn extends Handler {
    private void handleEnterSafeMode() {
        handleTeardown();

        mVcnSafeModeCallback.onEnteredSafeMode();
        mVcnCallback.onEnteredSafeMode();
    }

    private void handleNetworkRequested(
@@ -337,6 +332,13 @@ public class Vcn extends Handler {
    public interface VcnGatewayStatusCallback {
        /** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */
        void onEnteredSafeMode();

        /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
        void onGatewayConnectionError(
                @NonNull int[] networkCapabilities,
                @VcnErrorCode int errorCode,
                @Nullable String exceptionClass,
                @Nullable String exceptionMessage);
    }

    private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
@@ -344,6 +346,16 @@ public class Vcn extends Handler {
        public void onEnteredSafeMode() {
            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
        }

        @Override
        public void onGatewayConnectionError(
                @NonNull int[] networkCapabilities,
                @VcnErrorCode int errorCode,
                @Nullable String exceptionClass,
                @Nullable String exceptionMessage) {
            mVcnCallback.onGatewayConnectionError(
                    networkCapabilities, errorCode, exceptionClass, exceptionMessage);
        }
    }

    /** External dependencies used by Vcn, for injection in tests */
+64 −2
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;

import static com.android.server.VcnManagementService.VDBG;

@@ -52,7 +55,9 @@ import android.net.ipsec.ike.IkeSession;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionConfiguration;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.exceptions.AuthenticationFailedException;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeInternalException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnTransportInfo;
@@ -951,15 +956,68 @@ public class VcnGatewayConnection extends StateMachine {
        removeEqualMessages(EVENT_SAFE_MODE_TIMEOUT_EXCEEDED);
    }

    private void sessionLost(int token, @Nullable Exception exception) {
    private void sessionLostWithoutCallback(int token, @Nullable Exception exception) {
        sendMessageAndAcquireWakeLock(
                EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
    }

    private void sessionLost(int token, @Nullable Exception exception) {
        // Only notify mGatewayStatusCallback if the session was lost with an error. All
        // authentication and DNS failures are sent through
        // IkeSessionCallback.onClosedExceptionally(), which calls sessionClosed()
        if (exception != null) {
            mGatewayStatusCallback.onGatewayConnectionError(
                    mConnectionConfig.getRequiredUnderlyingCapabilities(),
                    VCN_ERROR_CODE_INTERNAL_ERROR,
                    "java.lang.RuntimeException",
                    "Received "
                            + exception.getClass().getSimpleName()
                            + " with message: "
                            + exception.getMessage());
        }

        sessionLostWithoutCallback(token, exception);
    }

    private void notifyStatusCallbackForSessionClosed(@NonNull Exception exception) {
        final int errorCode;
        final String exceptionClass;
        final String exceptionMessage;

        if (exception instanceof AuthenticationFailedException) {
            errorCode = VCN_ERROR_CODE_CONFIG_ERROR;
            exceptionClass = exception.getClass().getName();
            exceptionMessage = exception.getMessage();
        } else if (exception instanceof IkeInternalException
                && exception.getCause() instanceof IOException) {
            errorCode = VCN_ERROR_CODE_NETWORK_ERROR;
            exceptionClass = "java.io.IOException";
            exceptionMessage = exception.getCause().getMessage();
        } else {
            errorCode = VCN_ERROR_CODE_INTERNAL_ERROR;
            exceptionClass = "java.lang.RuntimeException";
            exceptionMessage =
                    "Received "
                            + exception.getClass().getSimpleName()
                            + " with message: "
                            + exception.getMessage();
        }

        mGatewayStatusCallback.onGatewayConnectionError(
                mConnectionConfig.getRequiredUnderlyingCapabilities(),
                errorCode,
                exceptionClass,
                exceptionMessage);
    }

    private void sessionClosed(int token, @Nullable Exception exception) {
        if (exception != null) {
            notifyStatusCallbackForSessionClosed(exception);
        }

        // SESSION_LOST MUST be sent before SESSION_CLOSED to ensure that the SM moves to the
        // Disconnecting state.
        sessionLost(token, exception);
        sessionLostWithoutCallback(token, exception);
        sendMessageAndAcquireWakeLock(EVENT_SESSION_CLOSED, token);
    }

@@ -1084,6 +1142,8 @@ public class VcnGatewayConnection extends StateMachine {
        }

        protected void handleDisconnectRequested(String msg) {
            // TODO(b/180526152): notify VcnStatusCallback for Network loss

            Slog.v(TAG, "Tearing down. Cause: " + msg);
            mIsRunning = false;

@@ -1228,6 +1288,8 @@ public class VcnGatewayConnection extends StateMachine {

                    String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
                    if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
                        // TODO(b/180526152): notify VcnStatusCallback for Network loss

                        // Will trigger EVENT_SESSION_CLOSED immediately.
                        mIkeSession.kill();
                        break;
+10 −10
Original line number Diff line number Diff line
@@ -75,7 +75,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.util.LocationPermissionChecker;
import com.android.server.VcnManagementService.VcnSafeModeCallback;
import com.android.server.VcnManagementService.VcnCallback;
import com.android.server.VcnManagementService.VcnStatusCallbackInfo;
import com.android.server.vcn.TelephonySubscriptionTracker;
import com.android.server.vcn.Vcn;
@@ -156,8 +156,8 @@ public class VcnManagementServiceTest {
    private final LocationPermissionChecker mLocationPermissionChecker =
            mock(LocationPermissionChecker.class);

    private final ArgumentCaptor<VcnSafeModeCallback> mSafeModeCallbackCaptor =
            ArgumentCaptor.forClass(VcnSafeModeCallback.class);
    private final ArgumentCaptor<VcnCallback> mVcnCallbackCaptor =
            ArgumentCaptor.forClass(VcnCallback.class);

    private final VcnManagementService mVcnMgmtSvc;

@@ -721,7 +721,7 @@ public class VcnManagementServiceTest {
        verify(mMockPolicyListener).onPolicyChanged();
    }

    private void verifyVcnSafeModeCallback(
    private void verifyVcnCallback(
            @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot)
            throws Exception {
        verify(mMockDeps)
@@ -730,22 +730,22 @@ public class VcnManagementServiceTest {
                        eq(subGroup),
                        eq(TEST_VCN_CONFIG),
                        eq(snapshot),
                        mSafeModeCallbackCaptor.capture());
                        mVcnCallbackCaptor.capture());

        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);

        VcnSafeModeCallback safeModeCallback = mSafeModeCallbackCaptor.getValue();
        safeModeCallback.onEnteredSafeMode();
        VcnCallback vcnCallback = mVcnCallbackCaptor.getValue();
        vcnCallback.onEnteredSafeMode();

        verify(mMockPolicyListener).onPolicyChanged();
    }

    @Test
    public void testVcnSafeModeCallbackOnEnteredSafeMode() throws Exception {
    public void testVcnCallbackOnEnteredSafeMode() throws Exception {
        TelephonySubscriptionSnapshot snapshot =
                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));

        verifyVcnSafeModeCallback(TEST_UUID_1, snapshot);
        verifyVcnCallback(TEST_UUID_1, snapshot);
    }

    private void triggerVcnStatusCallbackOnEnteredSafeMode(
@@ -771,7 +771,7 @@ public class VcnManagementServiceTest {
        // Trigger systemReady() to set up LocationPermissionChecker
        mVcnMgmtSvc.systemReady();

        verifyVcnSafeModeCallback(subGroup, snapshot);
        verifyVcnCallback(subGroup, snapshot);
    }

    @Test
+66 −0
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import static android.net.IpSecManager.DIRECTION_IN;
import static android.net.IpSecManager.DIRECTION_OUT;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;

import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
@@ -39,6 +42,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.ipsec.ike.exceptions.AuthenticationFailedException;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeInternalException;
import android.net.ipsec.ike.exceptions.TemporaryFailureException;
import android.net.vcn.VcnManager.VcnErrorCode;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -48,6 +56,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Collections;

/** Tests for VcnGatewayConnection.ConnectedState */
@@ -208,6 +218,25 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection

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

        // The child session was closed without exception, so verify that the GatewayStatusCallback
        // was not notified
        verifyNoMoreInteractions(mGatewayStatusCallback);
    }

    @Test
    public void testChildSessionClosedExceptionallyNotifiesGatewayStatusCallback()
            throws Exception {
        final IkeInternalException exception = new IkeInternalException(mock(IOException.class));
        getChildSessionCallback().onClosedExceptionally(exception);
        mTestLooper.dispatchAll();

        verify(mGatewayStatusCallback)
                .onGatewayConnectionError(
                        eq(mConfig.getRequiredUnderlyingCapabilities()),
                        eq(VCN_ERROR_CODE_INTERNAL_ERROR),
                        any(),
                        any());
    }

    @Test
@@ -223,5 +252,42 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection

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

        // IkeSession closed with no error, so verify that the GatewayStatusCallback was not
        // notified
        verifyNoMoreInteractions(mGatewayStatusCallback);
    }

    private void verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
            IkeException cause, @VcnErrorCode int expectedErrorType) {
        getIkeSessionCallback().onClosedExceptionally(cause);
        mTestLooper.dispatchAll();

        verify(mIkeSession).close();

        verify(mGatewayStatusCallback)
                .onGatewayConnectionError(
                        eq(mConfig.getRequiredUnderlyingCapabilities()),
                        eq(expectedErrorType),
                        any(),
                        any());
    }

    @Test
    public void testIkeSessionClosedExceptionallyAuthenticationFailure() throws Exception {
        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
                new AuthenticationFailedException("vcn test"), VCN_ERROR_CODE_CONFIG_ERROR);
    }

    @Test
    public void testIkeSessionClosedExceptionallyDnsFailure() throws Exception {
        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
                new IkeInternalException(new UnknownHostException()), VCN_ERROR_CODE_NETWORK_ERROR);
    }

    @Test
    public void testIkeSessionClosedExceptionallyInternalFailure() throws Exception {
        verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
                new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR);
    }
}
Loading