blob: fad9669911bb13d73c83f5eb092d6ac656fa71e7 [file] [log] [blame]
/*
* 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.routeselection;
import static com.android.server.vcn.VcnTestUtils.setupSystemService;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT;
import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
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.reset;
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.net.vcn.VcnGatewayConnectionConfigTest;
import android.os.ParcelUuid;
import android.os.test.TestLooper;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import com.android.server.vcn.VcnContext;
import com.android.server.vcn.VcnNetworkProvider;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
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.Set;
import java.util.UUID;
public class UnderlyingNetworkControllerTest {
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 int UPDATED_SUB_ID = 3;
private static final Set<Integer> INITIAL_SUB_IDS =
new ArraySet<>(Arrays.asList(INITIAL_SUB_ID_1, INITIAL_SUB_ID_2));
private static final Set<Integer> UPDATED_SUB_IDS =
new ArraySet<>(Arrays.asList(UPDATED_SUB_ID));
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 TelephonyManager mTelephonyManager;
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb;
@Mock private Network mNetwork;
@Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor;
private TestLooper mTestLooper;
private VcnContext mVcnContext;
private UnderlyingNetworkController mUnderlyingNetworkController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestLooper = new TestLooper();
mVcnContext =
spy(
new VcnContext(
mContext,
mTestLooper.getLooper(),
mVcnNetworkProvider,
false /* isInTestMode */));
resetVcnContext();
setupSystemService(
mContext,
mConnectivityManager,
Context.CONNECTIVITY_SERVICE,
ConnectivityManager.class);
setupSystemService(
mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
setupSystemService(
mContext,
mCarrierConfigManager,
Context.CARRIER_CONFIG_SERVICE,
CarrierConfigManager.class);
when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS);
mUnderlyingNetworkController =
new UnderlyingNetworkController(
mVcnContext,
VcnGatewayConnectionConfigTest.buildTestConfig(),
SUB_GROUP,
mSubscriptionSnapshot,
mNetworkControllerCb);
}
private void resetVcnContext() {
reset(mVcnContext);
doNothing().when(mVcnContext).ensureRunningOnLooperThread();
}
// Package private for use in NetworkPriorityClassifierTest
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() {
verifyNetworkRequestsRegistered(INITIAL_SUB_IDS);
}
@Test
public void testNetworkCallbacksRegisteredOnStartupForTestMode() {
final ConnectivityManager cm = mock(ConnectivityManager.class);
setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
final VcnContext vcnContext =
new VcnContext(
mContext,
mTestLooper.getLooper(),
mVcnNetworkProvider,
true /* isInTestMode */);
new UnderlyingNetworkController(
vcnContext,
VcnGatewayConnectionConfigTest.buildTestConfig(),
SUB_GROUP,
mSubscriptionSnapshot,
mNetworkControllerCb);
verify(cm)
.registerNetworkCallback(
eq(getTestNetworkRequest(INITIAL_SUB_IDS)),
any(UnderlyingNetworkListener.class),
any());
}
private void verifyNetworkRequestsRegistered(Set<Integer> expectedSubIds) {
verify(mConnectivityManager)
.requestBackgroundNetwork(
eq(getWifiRequest(expectedSubIds)),
any(NetworkBringupCallback.class),
any());
for (final int subId : expectedSubIds) {
verify(mConnectivityManager)
.requestBackgroundNetwork(
eq(getCellRequestForSubId(subId)),
any(NetworkBringupCallback.class), any());
}
verify(mConnectivityManager)
.registerNetworkCallback(
eq(getRouteSelectionRequest(expectedSubIds)),
any(UnderlyingNetworkListener.class),
any());
verify(mConnectivityManager)
.registerNetworkCallback(
eq(getWifiEntryRssiThresholdRequest(expectedSubIds)),
any(NetworkBringupCallback.class),
any());
verify(mConnectivityManager)
.registerNetworkCallback(
eq(getWifiExitRssiThresholdRequest(expectedSubIds)),
any(NetworkBringupCallback.class),
any());
}
@Test
public void testUpdateSubscriptionSnapshot() {
// Verify initial cell background requests filed
verifyNetworkRequestsRegistered(INITIAL_SUB_IDS);
TelephonySubscriptionSnapshot subscriptionUpdate =
mock(TelephonySubscriptionSnapshot.class);
when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS);
mUnderlyingNetworkController.updateSubscriptionSnapshot(subscriptionUpdate);
// verify that initially-filed bringup requests are unregistered (cell + wifi)
verify(mConnectivityManager, times(INITIAL_SUB_IDS.size() + 3))
.unregisterNetworkCallback(any(NetworkBringupCallback.class));
verify(mConnectivityManager)
.unregisterNetworkCallback(any(UnderlyingNetworkListener.class));
verifyNetworkRequestsRegistered(UPDATED_SUB_IDS);
}
private NetworkRequest getWifiRequest(Set<Integer> netCapsSubIds) {
return getExpectedRequestBase()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setSubscriptionIds(netCapsSubIds)
.build();
}
private NetworkRequest getWifiEntryRssiThresholdRequest(Set<Integer> netCapsSubIds) {
// TODO (b/187991063): Add tests for carrier-config based thresholds
return getExpectedRequestBase()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setSubscriptionIds(netCapsSubIds)
.setSignalStrength(WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT)
.build();
}
private NetworkRequest getWifiExitRssiThresholdRequest(Set<Integer> netCapsSubIds) {
// TODO (b/187991063): Add tests for carrier-config based thresholds
return getExpectedRequestBase()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setSubscriptionIds(netCapsSubIds)
.setSignalStrength(WIFI_EXIT_RSSI_THRESHOLD_DEFAULT)
.build();
}
private NetworkRequest getCellRequestForSubId(int subId) {
return getExpectedRequestBase()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
.build();
}
private NetworkRequest getRouteSelectionRequest(Set<Integer> netCapsSubIds) {
return getExpectedRequestBase()
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
.setSubscriptionIds(netCapsSubIds)
.build();
}
private NetworkRequest getTestNetworkRequest(Set<Integer> netCapsSubIds) {
return new NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
.setSubscriptionIds(netCapsSubIds)
.build();
}
private NetworkRequest.Builder getExpectedRequestBase() {
final NetworkRequest.Builder builder =
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);
return builder;
}
@Test
public void testTeardown() {
mUnderlyingNetworkController.teardown();
// Expect 5 NetworkBringupCallbacks to be unregistered: 1 for WiFi, 2 for Cellular (1x for
// each subId), and 1 for each of the Wifi signal strength thresholds
verify(mConnectivityManager, times(5))
.unregisterNetworkCallback(any(NetworkBringupCallback.class));
verify(mConnectivityManager)
.unregisterNetworkCallback(any(UnderlyingNetworkListener.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 UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() {
return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
}
private static NetworkCapabilities buildResponseNwCaps(
NetworkCapabilities requestNetworkCaps, Set<Integer> netCapsSubIds) {
final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
new TelephonyNetworkSpecifier.Builder()
.setSubscriptionId(netCapsSubIds.iterator().next())
.build();
return new NetworkCapabilities.Builder(requestNetworkCaps)
.setNetworkSpecifier(telephonyNetworkSpecifier)
.build();
}
private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback(
NetworkCapabilities networkCapabilities) {
verify(mConnectivityManager)
.registerNetworkCallback(
eq(getRouteSelectionRequest(INITIAL_SUB_IDS)),
mUnderlyingNetworkListenerCaptor.capture(),
any());
UnderlyingNetworkListener cb = mUnderlyingNetworkListenerCaptor.getValue();
cb.onAvailable(mNetwork);
final NetworkCapabilities responseNetworkCaps =
buildResponseNwCaps(networkCapabilities, INITIAL_SUB_IDS);
cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES);
cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
responseNetworkCaps,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
return cb;
}
@Test
public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
final NetworkCapabilities responseNetworkCaps =
buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS);
cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
responseNetworkCaps,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@Test
public void testRecordTrackerCallbackNotifiedForLinkPropertiesChange() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
UPDATED_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@Test
public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
final NetworkCapabilities responseNetworkCaps =
buildResponseNwCaps(SUSPENDED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS);
cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
responseNetworkCaps,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb, times(1))
.onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
// onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
// change.
cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
verify(mNetworkControllerCb, times(1))
.onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@Test
public void testRecordTrackerCallbackNotifiedForNetworkResumed() {
UnderlyingNetworkListener cb =
verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
final NetworkCapabilities responseNetworkCaps =
buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS);
cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
responseNetworkCaps,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb, times(1))
.onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
// onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
// change.
cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
verify(mNetworkControllerCb, times(1))
.onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@Test
public void testRecordTrackerCallbackNotifiedForBlocked() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
INITIAL_LINK_PROPERTIES,
true /* isBlocked */);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@Test
public void testRecordTrackerCallbackNotifiedForNetworkLoss() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
cb.onLost(mNetwork);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
}
@Test
public void testRecordTrackerCallbackIgnoresDuplicateRecord() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
cb.onCapabilitiesChanged(
mNetwork, buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS));
// Verify no more calls to the UnderlyingNetworkControllerCallback when the
// UnderlyingNetworkRecord does not actually change
verifyNoMoreInteractions(mNetworkControllerCb);
}
@Test
public void testRecordTrackerCallbackNotifiedAfterTeardown() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
mUnderlyingNetworkController.teardown();
cb.onCapabilitiesChanged(
mNetwork, buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, INITIAL_SUB_IDS));
// Verify that the only call was during onAvailable()
verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any());
}
// TODO (b/187991063): Add tests for network prioritization
}