From a382c9ca0cf2afb53bcc5ea3c94410ed9da33dbd Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Tue, 12 Jan 2021 15:01:33 -0800 Subject: [PATCH 1/3] Register NetworkCallbacks in UnderlyingNetworkTracker. This CL updates UnderlyingNetworkTracker to register NetworkCallbacks. UnderlyingNetworkTracker uses two types of NetworkCallbacks: - bringup requests - selection requests Bringup requests ensure that VCN-managed Networks are not reaped. Separate bringup requests are filed for wifi and cell Networks. One cell request is filed for each subscription ID (subId) in the VCN's subscription group. Selection requests are used to allow ConnectivityService to perform Network selection for the VCN's selected underlying Network (the Network that VCN traffic will be routed through). Bug: 177364490 Test: atest FrameworksVcnTests Change-Id: Ic4dc13a3f7acf08589ddb25b343af2677cd7cf65 --- core/java/android/net/vcn/VcnManager.java | 1 - .../server/vcn/UnderlyingNetworkTracker.java | 367 +++++++++++++++++- .../com/android/server/vcn/VcnContext.java | 11 + .../server/vcn/VcnGatewayConnection.java | 10 +- .../vcn/VcnGatewayConnectionTestBase.java | 2 +- 5 files changed, 376 insertions(+), 15 deletions(-) diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 33beb6a9d1881..fa090f59a8b9f 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -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> diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index 6427ae2dc13c4..fd12c2d2ebb83 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -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 mCellBringupCallbacks = new SparseArray<>(); + @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback(); + @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback(); + + @NonNull private final Set mSubIds = new ArraySet<>(); + + @NonNull private final Set mRequiredUnderlyingNetworkCapabilities; + + @Nullable private UnderlyingNetworkRecord mCurrentRecord; + @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; public UnderlyingNetworkTracker( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull Set 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 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 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 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(); - /** An record of a single underlying network, caching relevant fields. */ + 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); + } + + 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. + * + *

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. + * + *

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 { * *

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 {} diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java index dba59bdbee7d7..7399e56b3a950 100644 --- a/services/core/java/com/android/server/vcn/VcnContext.java +++ b/services/core/java/com/android/server/vcn/VcnContext.java @@ -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"); + } + } } diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 3cfa00eb60799..39c96069f9c6e 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -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 requiredUnderlyingNetworkCapabilities, UnderlyingNetworkTrackerCallback callback) { - return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback); + return new UnderlyingNetworkTracker( + vcnContext, subscriptionGroup, requiredUnderlyingNetworkCapabilities, callback); } /** Builds a new IkeSession. */ diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index b4d39bf74a4b1..4d92fb9c42f2a 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -94,7 +94,7 @@ public class VcnGatewayConnectionTestBase { doReturn(mUnderlyingNetworkTracker) .when(mDeps) - .newUnderlyingNetworkTracker(any(), any(), any()); + .newUnderlyingNetworkTracker(any(), any(), any(), any()); } @Before From f029daa404ca0e5bc9eab91050e8461a582cddae Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Thu, 14 Jan 2021 12:57:15 -0800 Subject: [PATCH 2/3] Unit test UnderlyingNetworkTracker. Bug: 177364490 Test: atest FrameworksVcnTests Change-Id: I681b0bef27cdd1974a574e81b4b435578cd6e9cd --- .../vcn/UnderlyingNetworkTrackerTest.java | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java new file mode 100644 index 0000000000000..48e068d141820 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static com.android.server.vcn.VcnTestUtils.setupSystemService; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.net.TelephonyNetworkSpecifier; +import android.os.ParcelUuid; +import android.os.test.TestLooper; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; + +import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback; +import com.android.server.vcn.UnderlyingNetworkTracker.RouteSelectionCallback; +import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; +import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class UnderlyingNetworkTrackerTest { + private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); + private static final int INITIAL_SUB_ID_1 = 1; + private static final int INITIAL_SUB_ID_2 = 2; + + private static final NetworkCapabilities INITIAL_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .build(); + private static final NetworkCapabilities SUSPENDED_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder(INITIAL_NETWORK_CAPABILITIES) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .build(); + private static final NetworkCapabilities UPDATED_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .build(); + + private static final LinkProperties INITIAL_LINK_PROPERTIES = + getLinkPropertiesWithName("initial_iface"); + private static final LinkProperties UPDATED_LINK_PROPERTIES = + getLinkPropertiesWithName("updated_iface"); + + @Mock private Context mContext; + @Mock private VcnNetworkProvider mVcnNetworkProvider; + @Mock private ConnectivityManager mConnectivityManager; + @Mock private SubscriptionManager mSubscriptionManager; + @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb; + @Mock private Network mNetwork; + + @Captor private ArgumentCaptor mRouteSelectionCallbackCaptor; + + private TestLooper mTestLooper; + private VcnContext mVcnContext; + private UnderlyingNetworkTracker mUnderlyingNetworkTracker; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mTestLooper = new TestLooper(); + mVcnContext = spy(new VcnContext(mContext, mTestLooper.getLooper(), mVcnNetworkProvider)); + doNothing().when(mVcnContext).ensureRunningOnLooperThread(); + + setupSystemService( + mContext, + mConnectivityManager, + Context.CONNECTIVITY_SERVICE, + ConnectivityManager.class); + setupSystemService( + mContext, + mSubscriptionManager, + Context.TELEPHONY_SUBSCRIPTION_SERVICE, + SubscriptionManager.class); + + List initialSubInfos = + Arrays.asList( + getSubscriptionInfoForSubId(INITIAL_SUB_ID_1), + getSubscriptionInfoForSubId(INITIAL_SUB_ID_2)); + when(mSubscriptionManager.getSubscriptionsInGroup(eq(SUB_GROUP))) + .thenReturn(initialSubInfos); + + mUnderlyingNetworkTracker = + new UnderlyingNetworkTracker( + mVcnContext, + SUB_GROUP, + Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET), + mNetworkTrackerCb); + } + + private static LinkProperties getLinkPropertiesWithName(String iface) { + LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(iface); + return linkProperties; + } + + private SubscriptionInfo getSubscriptionInfoForSubId(int subId) { + SubscriptionInfo subInfo = mock(SubscriptionInfo.class); + when(subInfo.getSubscriptionId()).thenReturn(subId); + return subInfo; + } + + @Test + public void testNetworkCallbacksRegisteredOnStartup() { + // verify NetworkCallbacks registered when instantiated + verify(mConnectivityManager) + .requestBackgroundNetwork( + eq(getWifiRequest()), + any(), + any(NetworkBringupCallback.class)); + verify(mConnectivityManager) + .requestBackgroundNetwork( + eq(getCellRequestForSubId(INITIAL_SUB_ID_1)), + any(), + any(NetworkBringupCallback.class)); + verify(mConnectivityManager) + .requestBackgroundNetwork( + eq(getCellRequestForSubId(INITIAL_SUB_ID_2)), + any(), + any(NetworkBringupCallback.class)); + verify(mConnectivityManager) + .requestBackgroundNetwork( + eq(getRouteSelectionRequest()), + any(), + any(RouteSelectionCallback.class)); + + verify(mSubscriptionManager).getSubscriptionsInGroup(eq(SUB_GROUP)); + } + + private NetworkRequest getWifiRequest() { + return getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .build(); + } + + private NetworkRequest getCellRequestForSubId(int subId) { + return getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) + .build(); + } + + private NetworkRequest getRouteSelectionRequest() { + return getExpectedRequestBase().build(); + } + + private NetworkRequest.Builder getExpectedRequestBase() { + return new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .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); + } + + @Test + public void testTeardown() { + mUnderlyingNetworkTracker.teardown(); + + // Expect 3 NetworkBringupCallbacks to be unregistered: 1 for WiFi and 2 for Cellular (1x + // for each subId) + verify(mConnectivityManager, times(3)) + .unregisterNetworkCallback(any(NetworkBringupCallback.class)); + verify(mConnectivityManager).unregisterNetworkCallback(any(RouteSelectionCallback.class)); + } + + @Test + public void testUnderlyingNetworkRecordEquals() { + UnderlyingNetworkRecord recordA = + new UnderlyingNetworkRecord( + mNetwork, + INITIAL_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */); + UnderlyingNetworkRecord recordB = + new UnderlyingNetworkRecord( + mNetwork, + INITIAL_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */); + UnderlyingNetworkRecord recordC = + new UnderlyingNetworkRecord( + mNetwork, + UPDATED_NETWORK_CAPABILITIES, + UPDATED_LINK_PROPERTIES, + false /* isBlocked */); + + assertEquals(recordA, recordB); + assertNotEquals(recordA, recordC); + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkChange() { + verifyRegistrationOnAvailableAndGetCallback(); + } + + private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback() { + return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES); + } + + private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback( + NetworkCapabilities networkCapabilities) { + verify(mConnectivityManager) + .requestBackgroundNetwork( + eq(getRouteSelectionRequest()), + any(), + mRouteSelectionCallbackCaptor.capture()); + + RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue(); + cb.onAvailable(mNetwork); + cb.onCapabilitiesChanged(mNetwork, networkCapabilities); + cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES); + cb.onBlockedStatusChanged(mNetwork, false /* isFalse */); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + networkCapabilities, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */); + verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + return cb; + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() { + RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + UPDATED_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */); + verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForLinkPropertiesChange() { + RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + INITIAL_NETWORK_CAPABILITIES, + UPDATED_LINK_PROPERTIES, + false /* isBlocked */); + verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkSuspended() { + RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onNetworkSuspended(mNetwork); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + SUSPENDED_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */); + verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkResumed() { + RouteSelectionCallback cb = + verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES); + + cb.onNetworkResumed(mNetwork); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + INITIAL_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */); + verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForBlocked() { + RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */); + + UnderlyingNetworkRecord expectedRecord = + new UnderlyingNetworkRecord( + mNetwork, + INITIAL_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + true /* isBlocked */); + verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + + @Test + public void testRecordTrackerCallbackNotifiedForNetworkLoss() { + RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onLost(mNetwork); + + verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null); + } + + @Test + public void testRecordTrackerCallbackIgnoresDuplicateRecord() { + RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback(); + + cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); + + // Verify no more calls to the UnderlyingNetworkTrackerCallback when the + // UnderlyingNetworkRecord does not actually change + verifyNoMoreInteractions(mNetworkTrackerCb); + } +} From 07979a07cc7656e8c5bc16ef530ba0941a0c3405 Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Tue, 19 Jan 2021 15:19:15 -0800 Subject: [PATCH 3/3] Implement Network policy in VcnManagementService. This CL implements #getUnderlyingNetworkPolicy() for VcnManagementService. Specifically, it takes the provided NetworkCapabilities and checks for a corresponding VCN. If one exists, a VcnUnderlyingNetworkPolicy will be returned with a copy of the original NetworkCapabilities without the capability NOT_VCN_MANAGED and with the unwanted capability NOT_VCN_MANAGED. Any NetworkCapabilities without a corresponding VCN will be returned a VcnUnderlyingNetworkPolicy with the original NetworkCapabilities intact. Bug: 175914059 Test: atest FrameworksVcnTests Change-Id: Icdffc4f586f58a0e5b6290d5c6449e2bbed5cab0 --- .../android/server/VcnManagementService.java | 40 +++++++- .../server/VcnManagementServiceTest.java | 98 ++++++++++++++++--- 2 files changed, 125 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 8562b0d9cb828..6a72010738db2 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -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); } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index e26bf19488d03..e7d334ebd490e 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -22,7 +22,6 @@ import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -30,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; @@ -39,16 +39,20 @@ import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.net.NetworkCapabilities.Transport; +import android.net.TelephonyNetworkSpecifier; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnConfigTest; import android.net.vcn.VcnUnderlyingNetworkPolicy; +import android.net.wifi.WifiInfo; import android.os.IBinder; import android.os.ParcelUuid; import android.os.PersistableBundle; @@ -253,7 +257,14 @@ public class VcnManagementServiceTest { verify(mConfigReadWriteHelper).readFromDisk(); } - private void triggerSubscriptionTrackerCallback(Set activeSubscriptionGroups) { + private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( + Set activeSubscriptionGroups) { + return triggerSubscriptionTrackerCbAndGetSnapshot( + activeSubscriptionGroups, Collections.emptyMap()); + } + + private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot( + Set activeSubscriptionGroups, Map subIdToGroupMap) { final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class); doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups(); @@ -267,8 +278,14 @@ public class VcnManagementServiceTest { argThat(val -> activeSubscriptionGroups.contains(val)), eq(TEST_PACKAGE_NAME)); + doAnswer(invocation -> { + return subIdToGroupMap.get(invocation.getArgument(0)); + }).when(snapshot).getGroupForSubId(anyInt()); + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); cb.onNewSnapshot(snapshot); + + return snapshot; } private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() { @@ -287,7 +304,7 @@ public class VcnManagementServiceTest { @Test public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception { - triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1)); + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG)); } @@ -296,7 +313,7 @@ public class VcnManagementServiceTest { final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); - triggerSubscriptionTrackerCallback(Collections.emptySet()); + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet()); // Verify teardown after delay mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); @@ -311,13 +328,13 @@ public class VcnManagementServiceTest { final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); // Simulate SIM unloaded - triggerSubscriptionTrackerCallback(Collections.emptySet()); + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet()); // Simulate new SIM loaded right during teardown delay. mTestLooper.moveTimeForward( VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); mTestLooper.dispatchAll(); - triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2)); + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2)); // Verify that even after the full timeout duration, the VCN instance is not torn down mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); @@ -331,7 +348,7 @@ public class VcnManagementServiceTest { final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2); // Simulate SIM unloaded - triggerSubscriptionTrackerCallback(Collections.emptySet()); + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet()); // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new // vcnInstance. @@ -496,14 +513,73 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); } + private void setUpVcnSubscription(int subId, ParcelUuid subGroup) { + mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + + triggerSubscriptionTrackerCbAndGetSnapshot( + Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup)); + } + + private void verifyMergedNetworkCapabilities( + NetworkCapabilities mergedCapabilities, @Transport int transportType) { + assertTrue(mergedCapabilities.hasTransport(transportType)); + assertFalse( + mergedCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)); + } + @Test - public void testGetUnderlyingNetworkPolicy() throws Exception { + public void testGetUnderlyingNetworkPolicyTransportCell() throws Exception { + setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2); + + NetworkCapabilities nc = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID)) + .build(); + VcnUnderlyingNetworkPolicy policy = - mVcnMgmtSvc.getUnderlyingNetworkPolicy( - new NetworkCapabilities(), new LinkProperties()); + mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); assertFalse(policy.isTeardownRequested()); - assertNotNull(policy.getMergedNetworkCapabilities()); + verifyMergedNetworkCapabilities( + policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR); + } + + @Test + public void testGetUnderlyingNetworkPolicyTransportWifi() throws Exception { + setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2); + + WifiInfo wifiInfo = mock(WifiInfo.class); + when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo); + when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID); + NetworkCapabilities nc = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setTransportInfo(wifiInfo) + .build(); + + VcnUnderlyingNetworkPolicy policy = + mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); + + assertFalse(policy.isTeardownRequested()); + verifyMergedNetworkCapabilities( + policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI); + } + + @Test + public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception { + NetworkCapabilities nc = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID)) + .build(); + + VcnUnderlyingNetworkPolicy policy = + mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); + + assertFalse(policy.isTeardownRequested()); + assertEquals(nc, policy.getMergedNetworkCapabilities()); } @Test(expected = SecurityException.class)