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

Commit c45eb287 authored by Lucas Lin's avatar Lucas Lin Committed by Gerrit Code Review
Browse files

Merge changes from topics "CATEGORY_ERROR_IKE", "CATEGORY_ERROR_NETWORK",...

Merge changes from topics "CATEGORY_ERROR_IKE", "CATEGORY_ERROR_NETWORK", "CATEGORY_ERROR_USER_DEACTIVATED", "CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED"

* changes:
  Send VPN manager event when there is a network error
  Send VPN manager event when there is an IkeProtocolException
  Send VPN manager event when VPN is deactivated
  Send VPN manager event when VPN always-on status is changed
parents b61b9e11 64eaff5d
Loading
Loading
Loading
Loading
+237 −21
Original line number Diff line number Diff line
@@ -86,7 +86,10 @@ import android.net.ipsec.ike.IkeSession;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.ipsec.ike.exceptions.IkeTimeoutException;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -122,6 +125,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.NetworkStackConstants;
import com.android.server.DeviceIdleInternal;
@@ -207,7 +211,9 @@ public class Vpn {
    private final Context mUserIdContext;
    @VisibleForTesting final Dependencies mDeps;
    private final NetworkInfo mNetworkInfo;
    @GuardedBy("this")
    private int mLegacyState;
    @GuardedBy("this")
    @VisibleForTesting protected String mPackage;
    private int mOwnerUID;
    private boolean mIsPackageTargetingAtLeastQ;
@@ -245,6 +251,7 @@ public class Vpn {
     * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
     * only applies to {@link VpnService} connections.
     */
    @GuardedBy("this")
    @VisibleForTesting protected boolean mAlwaysOn = false;

    /**
@@ -252,6 +259,7 @@ public class Vpn {
     * apps can still bypass by choosing explicit networks. Has no effect if {@link mAlwaysOn} is
     * not set. Applies to all types of VPNs.
     */
    @GuardedBy("this")
    @VisibleForTesting protected boolean mLockdown = false;

    /**
@@ -604,7 +612,7 @@ public class Vpn {
    }

    /** Returns the package name that is currently prepared. */
    public String getPackage() {
    public synchronized String getPackage() {
        return mPackage;
    }

@@ -685,6 +693,36 @@ public class Vpn {
        return true;
    }

    private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
            int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
            @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
            @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
        final Intent intent = new Intent(VpnManager.ACTION_VPN_MANAGER_EVENT);
        intent.setPackage(packageName);
        intent.addCategory(category);
        intent.putExtra(VpnManager.EXTRA_VPN_PROFILE_STATE, profileState);
        intent.putExtra(VpnManager.EXTRA_SESSION_KEY, sessionKey);
        intent.putExtra(VpnManager.EXTRA_UNDERLYING_NETWORK, underlyingNetwork);
        intent.putExtra(VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES, nc);
        intent.putExtra(VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES, lp);
        intent.putExtra(VpnManager.EXTRA_TIMESTAMP_MILLIS, System.currentTimeMillis());
        if (!VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER.equals(category)
                || !VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED.equals(category)) {
            intent.putExtra(VpnManager.EXTRA_ERROR_CLASS, errorClass);
            intent.putExtra(VpnManager.EXTRA_ERROR_CODE, errorCode);
        }
        try {
            return mUserIdContext.startService(intent) != null;
        } catch (RuntimeException e) {
            Log.e(TAG, "Service of VpnManager app " + intent + " failed to start", e);
            return false;
        }
    }

    private boolean isVpnApp(String packageName) {
        return packageName != null && !VpnConfig.LEGACY_VPN.equals(packageName);
    }

    /**
     * Configures an always-on VPN connection through a specific application. This connection is
     * automatically granted and persisted after a reboot.
@@ -707,9 +745,40 @@ public class Vpn {
            boolean lockdown,
            @Nullable List<String> lockdownAllowlist) {
        enforceControlPermissionOrInternalCaller();
        // Store mPackage since it might be reset or might be replaced with the other VPN app.
        final String oldPackage = mPackage;
        final boolean isPackageChanged = !Objects.equals(packageName, oldPackage);
        // TODO: Remove "SdkLevel.isAtLeastT()" check once VpnManagerService is decoupled from
        //  ConnectivityServiceTest.
        // Only notify VPN apps that were already always-on, and only if the always-on provider
        // changed, or the lockdown mode changed.
        final boolean shouldNotifyOldPkg = isVpnApp(oldPackage) && mAlwaysOn
                && (lockdown != mLockdown || isPackageChanged);
        // Also notify the new package if there was a provider change.
        final boolean shouldNotifyNewPkg = isVpnApp(packageName) && isPackageChanged;

        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist)) {
            saveAlwaysOnPackage();
            // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
            //  ConnectivityServiceTest.
            if (shouldNotifyOldPkg && SdkLevel.isAtLeastT()) {
                // If both of shouldNotifyOldPkg & isPackageChanged are true, which means the
                // always-on of old package is disabled or the old package is replaced with the new
                // package. In this case, VpnProfileState should be disconnected.
                sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED,
                        -1 /* errorClass */, -1 /* errorCode*/, oldPackage,
                        null /* sessionKey */, isPackageChanged ? makeDisconnectedVpnProfileState()
                                : makeVpnProfileStateLocked(),
                        null /* underlyingNetwork */, null /* nc */, null /* lp */);
            }
            // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
            //  ConnectivityServiceTest.
            if (shouldNotifyNewPkg && SdkLevel.isAtLeastT()) {
                sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED,
                        -1 /* errorClass */, -1 /* errorCode*/, packageName,
                        getSessionKeyLocked(), makeVpnProfileStateLocked(),
                        null /* underlyingNetwork */, null /* nc */, null /* lp */);
            }
            return true;
        }
        return false;
@@ -1006,6 +1075,7 @@ public class Vpn {
        return true;
    }

    @GuardedBy("this")
    private boolean isCurrentPreparedPackage(String packageName) {
        // We can't just check that packageName matches mPackage, because if the app was uninstalled
        // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
@@ -1043,6 +1113,17 @@ public class Vpn {
                if (!VpnConfig.LEGACY_VPN.equals(mPackage)) {
                    mAppOpsManager.finishOp(
                            AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, mOwnerUID, mPackage, null);
                    // The underlying network, NetworkCapabilities and LinkProperties are not
                    // necessary to send to VPN app since the purpose of this event is to notify
                    // VPN app that VPN is deactivated by the user.
                    // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
                    //  ConnectivityServiceTest.
                    if (SdkLevel.isAtLeastT()) {
                        sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
                                -1 /* errorClass */, -1 /* errorCode*/, mPackage,
                                getSessionKeyLocked(), makeVpnProfileStateLocked(),
                                null /* underlyingNetwork */, null /* nc */, null /* lp */);
                    }
                }
                // cleanupVpnStateLocked() is called from mVpnRunner.exit()
                mVpnRunner.exit();
@@ -1299,6 +1380,7 @@ public class Vpn {
        return true;
    }

    @GuardedBy("this")
    private void agentConnect() {
        LinkProperties lp = makeLinkProperties();

@@ -1993,6 +2075,7 @@ public class Vpn {
        return isIkev2VpnRunner() ? VpnManager.TYPE_VPN_PLATFORM : VpnManager.TYPE_VPN_LEGACY;
    }

    @GuardedBy("this")
    private void updateAlwaysOnNotification(DetailedState networkState) {
        final boolean visible = (mAlwaysOn && networkState != DetailedState.CONNECTED);

@@ -2431,6 +2514,21 @@ public class Vpn {
        }
    }

    @Nullable
    protected synchronized NetworkCapabilities getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
            NetworkCapabilities nc) {
        if (nc == null) return null;
        return mConnectivityManager.getRedactedNetworkCapabilitiesForPackage(
                nc, mOwnerUID, mPackage);
    }

    @Nullable
    protected synchronized LinkProperties getRedactedLinkPropertiesOfUnderlyingNetwork(
            LinkProperties lp) {
        if (lp == null) return null;
        return mConnectivityManager.getRedactedLinkPropertiesForPackage(lp, mOwnerUID, mPackage);
    }

    /** This class represents the common interface for all VPN runners. */
    @VisibleForTesting
    abstract class VpnRunner extends Thread {
@@ -2465,6 +2563,10 @@ public class Vpn {
    interface IkeV2VpnRunnerCallback {
        void onDefaultNetworkChanged(@NonNull Network network);

        void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc);

        void onDefaultNetworkLinkPropertiesChanged(@NonNull LinkProperties lp);

        void onChildOpened(
                @NonNull Network network, @NonNull ChildSessionConfiguration childConfig);

@@ -2521,6 +2623,8 @@ public class Vpn {
        @Nullable private IpSecTunnelInterface mTunnelIface;
        @Nullable private IkeSession mSession;
        @Nullable private Network mActiveNetwork;
        @Nullable private NetworkCapabilities mNetworkCapabilities;
        @Nullable private LinkProperties mLinkProperties;
        private final String mSessionKey;

        IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
@@ -2748,6 +2852,16 @@ public class Vpn {
            }
        }

        /** Called when the NetworkCapabilities of underlying network is changed */
        public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) {
            mNetworkCapabilities = nc;
        }

        /** Called when the LinkProperties of underlying network is changed */
        public void onDefaultNetworkLinkPropertiesChanged(@NonNull LinkProperties lp) {
            mLinkProperties = lp;
        }

        /** Marks the state as FAILED, and disconnects. */
        private void markFailedAndDisconnect(Exception exception) {
            synchronized (Vpn.this) {
@@ -2778,6 +2892,7 @@ public class Vpn {
                return;
            }

            synchronized (Vpn.this) {
                if (exception instanceof IkeProtocolException) {
                    final IkeProtocolException ikeException = (IkeProtocolException) exception;

@@ -2789,17 +2904,108 @@ public class Vpn {
                        case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
                        case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
                            // All the above failures are configuration errors, and are terminal
                            // TODO(b/230548427): Remove SDK check once VPN related stuff are
                            //  decoupled from ConnectivityServiceTest.
                            if (SdkLevel.isAtLeastT()) {
                                sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_IKE_ERROR,
                                        VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
                                        ikeException.getErrorType(),
                                        getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                        mActiveNetwork,
                                        getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                                this.mNetworkCapabilities),
                                        getRedactedLinkPropertiesOfUnderlyingNetwork(
                                                this.mLinkProperties));
                            }
                            markFailedAndDisconnect(exception);
                            return;
                        // All other cases possibly recoverable.
                        default:
                            // All the above failures are configuration errors, and are terminal
                            // TODO(b/230548427): Remove SDK check once VPN related stuff are
                            //  decoupled from ConnectivityServiceTest.
                            if (SdkLevel.isAtLeastT()) {
                                sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_IKE_ERROR,
                                        VpnManager.ERROR_CLASS_RECOVERABLE,
                                        ikeException.getErrorType(),
                                        getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                        mActiveNetwork,
                                        getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                                this.mNetworkCapabilities),
                                        getRedactedLinkPropertiesOfUnderlyingNetwork(
                                                this.mLinkProperties));
                            }
                    }
                } else if (exception instanceof IllegalArgumentException) {
                    // Failed to build IKE/ChildSessionParams; fatal profile configuration error
                    markFailedAndDisconnect(exception);
                    return;
                } else if (exception instanceof IkeNetworkLostException) {
                    // TODO(b/230548427): Remove SDK check once VPN related stuff are
                    //  decoupled from ConnectivityServiceTest.
                    if (SdkLevel.isAtLeastT()) {
                        sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
                                VpnManager.ERROR_CLASS_RECOVERABLE,
                                VpnManager.ERROR_CODE_NETWORK_LOST,
                                getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                mActiveNetwork,
                                getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                        this.mNetworkCapabilities),
                                getRedactedLinkPropertiesOfUnderlyingNetwork(
                                        this.mLinkProperties));
                    }
                } else if (exception instanceof IkeNonProtocolException) {
                    if (exception.getCause() instanceof UnknownHostException) {
                        // TODO(b/230548427): Remove SDK check once VPN related stuff are
                        //  decoupled from ConnectivityServiceTest.
                        if (SdkLevel.isAtLeastT()) {
                            sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
                                    VpnManager.ERROR_CLASS_RECOVERABLE,
                                    VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST,
                                    getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                    mActiveNetwork,
                                    getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                            this.mNetworkCapabilities),
                                    getRedactedLinkPropertiesOfUnderlyingNetwork(
                                            this.mLinkProperties));
                        }
                    } else if (exception.getCause() instanceof IkeTimeoutException) {
                        // TODO(b/230548427): Remove SDK check once VPN related stuff are
                        //  decoupled from ConnectivityServiceTest.
                        if (SdkLevel.isAtLeastT()) {
                            sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
                                    VpnManager.ERROR_CLASS_RECOVERABLE,
                                    VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT,
                                    getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                    mActiveNetwork,
                                    getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                            this.mNetworkCapabilities),
                                    getRedactedLinkPropertiesOfUnderlyingNetwork(
                                            this.mLinkProperties));
                        }
                    } else if (exception.getCause() instanceof IOException) {
                        // TODO(b/230548427): Remove SDK check once VPN related stuff are
                        //  decoupled from ConnectivityServiceTest.
                        if (SdkLevel.isAtLeastT()) {
                            sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
                                    VpnManager.ERROR_CLASS_RECOVERABLE,
                                    VpnManager.ERROR_CODE_NETWORK_IO,
                                    getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                                    mActiveNetwork,
                                    getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
                                            this.mNetworkCapabilities),
                                    getRedactedLinkPropertiesOfUnderlyingNetwork(
                                            this.mLinkProperties));
                        }
                    }
                } else if (exception != null) {
                    Log.wtf(TAG, "onSessionLost: exception = " + exception);
                }
            }

            mActiveNetwork = null;
            mNetworkCapabilities = null;
            mLinkProperties = null;

            // Close all obsolete state, but keep VPN alive incase a usable network comes up.
            // (Mirrors VpnService behavior)
@@ -2864,6 +3070,8 @@ public class Vpn {
         */
        private void disconnectVpnRunner() {
            mActiveNetwork = null;
            mNetworkCapabilities = null;
            mLinkProperties = null;
            mIsRunning = false;

            resetIkeState();
@@ -3490,11 +3698,19 @@ public class Vpn {
        }
    }

    private VpnProfileState makeVpnProfileState() {
    @GuardedBy("this")
    @NonNull
    private VpnProfileState makeVpnProfileStateLocked() {
        return new VpnProfileState(getStateFromLegacyState(mLegacyState),
                isIkev2VpnRunner() ? getSessionKeyLocked() : null, mAlwaysOn, mLockdown);
    }

    @NonNull
    private VpnProfileState makeDisconnectedVpnProfileState() {
        return new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, null /* sessionKey */,
                false /* alwaysOn */, false /* lockdown */);
    }

    /**
     * Retrieve the VpnProfileState for the profile provisioned by the given package.
     *
@@ -3506,7 +3722,7 @@ public class Vpn {
            @NonNull String packageName) {
        requireNonNull(packageName, "No package name provided");
        enforceNotRestrictedUser();
        return isCurrentIkev2VpnLocked(packageName) ? makeVpnProfileState() : null;
        return isCurrentIkev2VpnLocked(packageName) ? makeVpnProfileStateLocked() : null;
    }

    /**
+18 −0
Original line number Diff line number Diff line
@@ -50,7 +50,9 @@ import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.IpSecAlgorithm;
import android.net.IpSecTransform;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
import android.net.eap.EapSessionConfig;
import android.net.ipsec.ike.ChildSaProposal;
@@ -392,6 +394,22 @@ public class VpnIkev2Utils {
            mExecutor.execute(() -> mCallback.onDefaultNetworkChanged(network));
        }

        @Override
        public void onCapabilitiesChanged(@NonNull Network network,
                @NonNull NetworkCapabilities networkCapabilities) {
            Log.d(mTag, "NC changed for net " + network + " : " + networkCapabilities);
            mExecutor.execute(
                    () -> mCallback.onDefaultNetworkCapabilitiesChanged(networkCapabilities));
        }

        @Override
        public void onLinkPropertiesChanged(@NonNull Network network,
                @NonNull LinkProperties linkProperties) {
            Log.d(mTag, "LP changed for net " + network + " : " + linkProperties);
            mExecutor.execute(
                    () -> mCallback.onDefaultNetworkLinkPropertiesChanged(linkProperties));
        }

        @Override
        public void onLost(@NonNull Network network) {
            Log.d(mTag, "Tearing down; lost network: " + network);