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

Commit e980914d authored by Cody Kesting's avatar Cody Kesting Committed by Gerrit Code Review
Browse files

Merge changes from topic "vcn-network-policy"

* changes:
  Implement Network policy in VcnManagementService.
  Unit test UnderlyingNetworkTracker.
  Register NetworkCallbacks in UnderlyingNetworkTracker.
parents 10603d51 07979a07
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -67,7 +67,6 @@ import java.util.concurrent.Executor;
public class VcnManager {
    @NonNull private static final String TAG = VcnManager.class.getSimpleName();

    /** @hide */
    @VisibleForTesting
    public static final Map<
                    VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder>
+38 −2
Original line number Diff line number Diff line
@@ -27,10 +27,12 @@ import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
import android.net.vcn.VcnUnderlyingNetworkPolicy;
import android.net.wifi.WifiInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -291,6 +293,12 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                @NonNull VcnConfig config) {
            return new Vcn(vcnContext, subscriptionGroup, config);
        }

        /** Gets the subId indicated by the given {@link WifiInfo}. */
        public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) {
            // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
        }
    }

    /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -582,8 +590,36 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                "Must have permission NETWORK_FACTORY or be the SystemServer to get underlying"
                        + " Network policies");

        // TODO(b/175914059): implement policy generation once VcnManagementService is able to
        // determine policies
        // Defensive copy in case this call is in-process and the given NetworkCapabilities mutates
        networkCapabilities = new NetworkCapabilities(networkCapabilities);

        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
        if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
                && networkCapabilities.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
            TelephonyNetworkSpecifier telephonyNetworkSpecifier =
                    (TelephonyNetworkSpecifier) networkCapabilities.getNetworkSpecifier();
            subId = telephonyNetworkSpecifier.getSubscriptionId();
        } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
                && networkCapabilities.getTransportInfo() instanceof WifiInfo) {
            WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
            subId = mDeps.getSubIdForWifiInfo(wifiInfo);
        }

        boolean isVcnManagedNetwork = false;
        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
            synchronized (mLock) {
                ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);

                // TODO(b/178140910): only mark the Network as VCN-managed if not in safe mode
                if (mVcns.containsKey(subGroup)) {
                    isVcnManagedNetwork = true;
                }
            }
        }
        if (isVcnManagedNetwork) {
            networkCapabilities.removeCapability(
                    NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
        }

        return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
    }
+356 −11
Original line number Diff line number Diff line
@@ -18,16 +18,28 @@ package com.android.server.vcn;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkCapabilities.NetCapability;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.os.ParcelUuid;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;

import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * Tracks a set of Networks underpinning a VcnGatewayConnection.
@@ -38,53 +50,385 @@ import java.util.Objects;
 *
 * @hide
 */
public class UnderlyingNetworkTracker extends Handler {
public class UnderlyingNetworkTracker {
    @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();

    @NonNull private final VcnContext mVcnContext;
    @NonNull private final ParcelUuid mSubscriptionGroup;
    @NonNull private final UnderlyingNetworkTrackerCallback mCb;
    @NonNull private final Dependencies mDeps;
    @NonNull private final Handler mHandler;
    @NonNull private final ConnectivityManager mConnectivityManager;
    @NonNull private final SubscriptionManager mSubscriptionManager;

    @NonNull private final SparseArray<NetworkCallback> mCellBringupCallbacks = new SparseArray<>();
    @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback();
    @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback();

    @NonNull private final Set<Integer> mSubIds = new ArraySet<>();

    @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities;

    @Nullable private UnderlyingNetworkRecord mCurrentRecord;
    @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;

    public UnderlyingNetworkTracker(
            @NonNull VcnContext vcnContext,
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
            @NonNull UnderlyingNetworkTrackerCallback cb) {
        this(vcnContext, subscriptionGroup, cb, new Dependencies());
        this(
                vcnContext,
                subscriptionGroup,
                requiredUnderlyingNetworkCapabilities,
                cb,
                new Dependencies());
    }

    private UnderlyingNetworkTracker(
            @NonNull VcnContext vcnContext,
            @NonNull ParcelUuid subscriptionGroup,
            @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
            @NonNull UnderlyingNetworkTrackerCallback cb,
            @NonNull Dependencies deps) {
        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
        mVcnContext = vcnContext;
        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
        mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
        mRequiredUnderlyingNetworkCapabilities =
                Objects.requireNonNull(
                        requiredUnderlyingNetworkCapabilities,
                        "Missing requiredUnderlyingNetworkCapabilities");
        mCb = Objects.requireNonNull(cb, "Missing cb");
        mDeps = Objects.requireNonNull(deps, "Missing deps");

        mHandler = new Handler(mVcnContext.getLooper());

        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
        mSubscriptionManager = mVcnContext.getContext().getSystemService(SubscriptionManager.class);

        registerNetworkRequests();
    }

    private void registerNetworkRequests() {
        // register bringup requests for underlying Networks
        mConnectivityManager.requestBackgroundNetwork(
                getWifiNetworkRequest(), mHandler, mWifiBringupCallback);
        updateSubIdsAndCellularRequests();

        // register Network-selection request used to decide selected underlying Network
        mConnectivityManager.requestBackgroundNetwork(
                getNetworkRequestBase().build(), mHandler, mRouteSelectionCallback);
    }

    private NetworkRequest getWifiNetworkRequest() {
        return getNetworkRequestBase().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
    }

    private NetworkRequest getCellNetworkRequestForSubId(int subId) {
        return getNetworkRequestBase()
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
                .build();
    }

    private NetworkRequest.Builder getNetworkRequestBase() {
        NetworkRequest.Builder requestBase = new NetworkRequest.Builder();
        for (@NetCapability int capability : mRequiredUnderlyingNetworkCapabilities) {
            requestBase.addCapability(capability);
        }

        return requestBase
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
    }

    /**
     * Update the current subIds and Cellular bringup requests for this UnderlyingNetworkTracker.
     */
    private void updateSubIdsAndCellularRequests() {
        mVcnContext.ensureRunningOnLooperThread();

        Set<Integer> prevSubIds = new ArraySet<>(mSubIds);
        mSubIds.clear();

        // Ensure NetworkRequests filed for all current subIds in mSubscriptionGroup
        // STOPSHIP: b/177364490 use TelephonySubscriptionSnapshot to avoid querying Telephony
        List<SubscriptionInfo> subInfos =
                mSubscriptionManager.getSubscriptionsInGroup(mSubscriptionGroup);

        for (SubscriptionInfo subInfo : subInfos) {
            final int subId = subInfo.getSubscriptionId();
            mSubIds.add(subId);

            if (!mCellBringupCallbacks.contains(subId)) {
                final NetworkBringupCallback cb = new NetworkBringupCallback();
                mCellBringupCallbacks.put(subId, cb);

                mConnectivityManager.requestBackgroundNetwork(
                        getCellNetworkRequestForSubId(subId), mHandler, cb);
            }
        }

        // unregister all NetworkCallbacks for outdated subIds
        for (final int subId : prevSubIds) {
            if (!mSubIds.contains(subId)) {
                final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId);
                mConnectivityManager.unregisterNetworkCallback(cb);
            }
        }
    }

    /** Tears down this Tracker, and releases all underlying network requests. */
    public void teardown() {}
    public void teardown() {
        mVcnContext.ensureRunningOnLooperThread();

        mConnectivityManager.unregisterNetworkCallback(mWifiBringupCallback);
        mConnectivityManager.unregisterNetworkCallback(mRouteSelectionCallback);

        for (final int subId : mSubIds) {
            final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId);
            mConnectivityManager.unregisterNetworkCallback(cb);
        }
        mSubIds.clear();
    }

    /** Returns whether the currently selected Network matches the given network. */
    private static boolean isSameNetwork(
            @Nullable UnderlyingNetworkRecord.Builder recordInProgress, @NonNull Network network) {
        return recordInProgress != null && recordInProgress.getNetwork().equals(network);
    }

    /** Notify the Callback if a full UnderlyingNetworkRecord exists. */
    private void maybeNotifyCallback() {
        // Only forward this update if a complete record has been received
        if (!mRecordInProgress.isValid()) {
            return;
        }

        // Only forward this update if the updated record differs form the current record
        UnderlyingNetworkRecord updatedRecord = mRecordInProgress.build();
        if (!updatedRecord.equals(mCurrentRecord)) {
            mCurrentRecord = updatedRecord;

            mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
        }
    }

    private void handleNetworkAvailable(@NonNull Network network) {
        mVcnContext.ensureRunningOnLooperThread();

        mRecordInProgress = new UnderlyingNetworkRecord.Builder(network);
    }

    /** An record of a single underlying network, caching relevant fields. */
    private void handleNetworkLost(@NonNull Network network) {
        mVcnContext.ensureRunningOnLooperThread();

        if (!isSameNetwork(mRecordInProgress, network)) {
            Slog.wtf(TAG, "Non-underlying Network lost");
            return;
        }

        mRecordInProgress = null;
        mCurrentRecord = null;
        mCb.onSelectedUnderlyingNetworkChanged(null /* underlyingNetworkRecord */);
    }

    private void handleCapabilitiesChanged(
            @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
        mVcnContext.ensureRunningOnLooperThread();

        if (!isSameNetwork(mRecordInProgress, network)) {
            Slog.wtf(TAG, "Invalid update to NetworkCapabilities");
            return;
        }

        mRecordInProgress.setNetworkCapabilities(networkCapabilities);

        maybeNotifyCallback();
    }

    private void handleNetworkSuspended(@NonNull Network network, boolean isSuspended) {
        mVcnContext.ensureRunningOnLooperThread();

        if (!isSameNetwork(mRecordInProgress, network)) {
            Slog.wtf(TAG, "Invalid update to isSuspended");
            return;
        }

        final NetworkCapabilities newCaps =
                new NetworkCapabilities(mRecordInProgress.getNetworkCapabilities());
        if (isSuspended) {
            newCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
        } else {
            newCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
        }

        handleCapabilitiesChanged(network, newCaps);
    }

    private void handlePropertiesChanged(
            @NonNull Network network, @NonNull LinkProperties linkProperties) {
        mVcnContext.ensureRunningOnLooperThread();

        if (!isSameNetwork(mRecordInProgress, network)) {
            Slog.wtf(TAG, "Invalid update to LinkProperties");
            return;
        }

        mRecordInProgress.setLinkProperties(linkProperties);

        maybeNotifyCallback();
    }

    private void handleNetworkBlocked(@NonNull Network network, boolean isBlocked) {
        mVcnContext.ensureRunningOnLooperThread();

        if (!isSameNetwork(mRecordInProgress, network)) {
            Slog.wtf(TAG, "Invalid update to isBlocked");
            return;
        }

        mRecordInProgress.setIsBlocked(isBlocked);

        maybeNotifyCallback();
    }

    /**
     * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
     *
     * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
     * reaped, and no action is taken on any events firing.
     */
    @VisibleForTesting
    class NetworkBringupCallback extends NetworkCallback {}

    /**
     * RouteSelectionCallback is used to select the "best" underlying Network.
     *
     * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
     * truth.
     */
    @VisibleForTesting
    class RouteSelectionCallback extends NetworkCallback {
        @Override
        public void onAvailable(@NonNull Network network) {
            handleNetworkAvailable(network);
        }

        @Override
        public void onLost(@NonNull Network network) {
            handleNetworkLost(network);
        }

        @Override
        public void onCapabilitiesChanged(
                @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
            handleCapabilitiesChanged(network, networkCapabilities);
        }

        @Override
        public void onNetworkSuspended(@NonNull Network network) {
            handleNetworkSuspended(network, true /* isSuspended */);
        }

        @Override
        public void onNetworkResumed(@NonNull Network network) {
            handleNetworkSuspended(network, false /* isSuspended */);
        }

        @Override
        public void onLinkPropertiesChanged(
                @NonNull Network network, @NonNull LinkProperties linkProperties) {
            handlePropertiesChanged(network, linkProperties);
        }

        @Override
        public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
            handleNetworkBlocked(network, isBlocked);
        }
    }

    /** A record of a single underlying network, caching relevant fields. */
    public static class UnderlyingNetworkRecord {
        @NonNull public final Network network;
        @NonNull public final NetworkCapabilities networkCapabilities;
        @NonNull public final LinkProperties linkProperties;
        public final boolean blocked;
        public final boolean isBlocked;

        @VisibleForTesting(visibility = Visibility.PRIVATE)
        UnderlyingNetworkRecord(
                @NonNull Network network,
                @NonNull NetworkCapabilities networkCapabilities,
                @NonNull LinkProperties linkProperties,
                boolean blocked) {
                boolean isBlocked) {
            this.network = network;
            this.networkCapabilities = networkCapabilities;
            this.linkProperties = linkProperties;
            this.blocked = blocked;
            this.isBlocked = isBlocked;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof UnderlyingNetworkRecord)) return false;
            final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o;

            return network.equals(that.network)
                    && networkCapabilities.equals(that.networkCapabilities)
                    && linkProperties.equals(that.linkProperties)
                    && isBlocked == that.isBlocked;
        }

        @Override
        public int hashCode() {
            return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
        }

        /** Builder to incrementally construct an UnderlyingNetworkRecord. */
        private static class Builder {
            @NonNull private final Network mNetwork;

            @Nullable private NetworkCapabilities mNetworkCapabilities;
            @Nullable private LinkProperties mLinkProperties;
            boolean mIsBlocked;
            boolean mWasIsBlockedSet;

            private Builder(@NonNull Network network) {
                mNetwork = network;
            }

            @NonNull
            private Network getNetwork() {
                return mNetwork;
            }

            private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
                mNetworkCapabilities = networkCapabilities;
            }

            @Nullable
            private NetworkCapabilities getNetworkCapabilities() {
                return mNetworkCapabilities;
            }

            private void setLinkProperties(@NonNull LinkProperties linkProperties) {
                mLinkProperties = linkProperties;
            }

            private void setIsBlocked(boolean isBlocked) {
                mIsBlocked = isBlocked;
                mWasIsBlockedSet = true;
            }

            private boolean isValid() {
                return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
            }

            private UnderlyingNetworkRecord build() {
                return new UnderlyingNetworkRecord(
                        mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
            }
        }
    }

@@ -95,9 +439,10 @@ public class UnderlyingNetworkTracker extends Handler {
         *
         * <p>This callback does NOT signal a mobility event.
         *
         * @param underlying The details of the new underlying network
         * @param underlyingNetworkRecord The details of the new underlying network
         */
        void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying);
        void onSelectedUnderlyingNetworkChanged(
                @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
    }

    private static class Dependencies {}
+11 −0
Original line number Diff line number Diff line
@@ -55,4 +55,15 @@ public class VcnContext {
    public VcnNetworkProvider getVcnNetworkProvider() {
        return mVcnNetworkProvider;
    }

    /**
     * Verifies that the caller is running on the VcnContext Thread.
     *
     * @throwsIllegalStateException if the caller is not running on the VcnContext Thread.
     */
    public void ensureRunningOnLooperThread() {
        if (getLooper().getThread() != Thread.currentThread()) {
            throw new IllegalStateException("Not running on VcnMgmtSvc thread");
        }
    }
}
+8 −2
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
@@ -476,7 +477,10 @@ public class VcnGatewayConnection extends StateMachine {

        mUnderlyingNetworkTracker =
                mDeps.newUnderlyingNetworkTracker(
                        mVcnContext, subscriptionGroup, mUnderlyingNetworkTrackerCallback);
                        mVcnContext,
                        subscriptionGroup,
                        mConnectionConfig.getAllUnderlyingCapabilities(),
                        mUnderlyingNetworkTrackerCallback);
        mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);

        IpSecTunnelInterface iface;
@@ -1134,8 +1138,10 @@ public class VcnGatewayConnection extends StateMachine {
        public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
                VcnContext vcnContext,
                ParcelUuid subscriptionGroup,
                Set<Integer> requiredUnderlyingNetworkCapabilities,
                UnderlyingNetworkTrackerCallback callback) {
            return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
            return new UnderlyingNetworkTracker(
                    vcnContext, subscriptionGroup, requiredUnderlyingNetworkCapabilities, callback);
        }

        /** Builds a new IkeSession. */
Loading