diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-03-04 22:13:07 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-03-04 22:13:07 +0000 |
commit | 5ccab3298664225c06adb93c23ce90a21629bfac (patch) | |
tree | ba4afe9c2a2af13258fae45215208c27ed1fbfa8 | |
parent | 56b2e323413eda06ea9ae698c8b96f5156358bab (diff) | |
parent | e2e1dc10a30d1ca3884df3f88643596f26f4bd0b (diff) | |
download | Connectivity-simpleperf-release.tar.gz |
Snap for 11526323 from e2e1dc10a30d1ca3884df3f88643596f26f4bd0b to simpleperf-releasesimpleperf-release
Change-Id: I8defe9f064e3caf6d0c5a73b40671515de6fbda6
46 files changed, 1654 insertions, 4007 deletions
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp index 47227e38be..9fa073b43c 100644 --- a/Tethering/common/TetheringLib/Android.bp +++ b/Tethering/common/TetheringLib/Android.bp @@ -36,7 +36,7 @@ java_sdk_library { "//frameworks/base/core/tests/bandwidthtests", "//frameworks/base/core/tests/benchmarks", "//frameworks/base/core/tests/utillib", - "//frameworks/base/packages/Connectivity/tests:__subpackages__", + "//frameworks/base/services/tests/VpnTests", "//frameworks/base/tests/vcn", "//frameworks/opt/telephony/tests/telephonytests", "//packages/modules/CaptivePortalLogin/tests", diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp index f17396d439..07fa7339fe 100644 --- a/Tethering/tests/integration/Android.bp +++ b/Tethering/tests/integration/Android.bp @@ -46,7 +46,6 @@ java_defaults { android_library { name: "TetheringIntegrationTestsBaseLib", target_sdk_version: "current", - platform_apis: true, defaults: ["TetheringIntegrationTestsDefaults"], visibility: [ "//packages/modules/Connectivity/Tethering/tests/mts", @@ -59,7 +58,6 @@ android_library { android_library { name: "TetheringIntegrationTestsLatestSdkLib", target_sdk_version: "33", - platform_apis: true, defaults: ["TetheringIntegrationTestsDefaults"], srcs: [ "src/**/*.java", @@ -76,7 +74,6 @@ android_library { android_library { name: "TetheringIntegrationTestsLib", target_sdk_version: "current", - platform_apis: true, defaults: ["TetheringIntegrationTestsDefaults"], srcs: [ "src/**/*.java", diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c index c4b27b8200..5e401aa745 100644 --- a/bpf_progs/netd.c +++ b/bpf_progs/netd.c @@ -550,7 +550,7 @@ DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM, bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF, BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL, "fs_bpf_netd_readonly", "", - LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG) + IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG) (struct __sk_buff* skb) { return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8); } diff --git a/framework-t/Android.bp b/framework-t/Android.bp index 468cee4613..bc919ac872 100644 --- a/framework-t/Android.bp +++ b/framework-t/Android.bp @@ -182,6 +182,7 @@ java_sdk_library { "//frameworks/base/core/tests/bandwidthtests", "//frameworks/base/core/tests/benchmarks", "//frameworks/base/core/tests/utillib", + "//frameworks/base/services/tests/VpnTests", "//frameworks/base/tests/vcn", "//frameworks/opt/net/ethernet/tests:__subpackages__", "//frameworks/opt/telephony/tests/telephonytests", diff --git a/framework-t/src/android/net/nsd/AdvertisingRequest.java b/framework-t/src/android/net/nsd/AdvertisingRequest.java index b1ef98f73f..2895b0cdf9 100644 --- a/framework-t/src/android/net/nsd/AdvertisingRequest.java +++ b/framework-t/src/android/net/nsd/AdvertisingRequest.java @@ -17,11 +17,13 @@ package android.net.nsd; import android.annotation.LongDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.Duration; import java.util.Objects; /** @@ -34,7 +36,7 @@ public final class AdvertisingRequest implements Parcelable { /** * Only update the registration without sending exit and re-announcement. */ - public static final int NSD_ADVERTISING_UPDATE_ONLY = 1; + public static final long NSD_ADVERTISING_UPDATE_ONLY = 1; @NonNull @@ -46,7 +48,9 @@ public final class AdvertisingRequest implements Parcelable { NsdServiceInfo.class.getClassLoader(), NsdServiceInfo.class); final int protocolType = in.readInt(); final long advertiseConfig = in.readLong(); - return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig); + final long ttlSeconds = in.readLong(); + final Duration ttl = ttlSeconds < 0 ? null : Duration.ofSeconds(ttlSeconds); + return new AdvertisingRequest(serviceInfo, protocolType, advertiseConfig, ttl); } @Override @@ -60,6 +64,9 @@ public final class AdvertisingRequest implements Parcelable { // Bitmask of @AdvertisingConfig flags. Uses a long to allow 64 possible flags in the future. private final long mAdvertisingConfig; + @Nullable + private final Duration mTtl; + /** * @hide */ @@ -73,10 +80,11 @@ public final class AdvertisingRequest implements Parcelable { * The constructor for the advertiseRequest */ private AdvertisingRequest(@NonNull NsdServiceInfo serviceInfo, int protocolType, - long advertisingConfig) { + long advertisingConfig, @NonNull Duration ttl) { mServiceInfo = serviceInfo; mProtocolType = protocolType; mAdvertisingConfig = advertisingConfig; + mTtl = ttl; } /** @@ -101,12 +109,25 @@ public final class AdvertisingRequest implements Parcelable { return mAdvertisingConfig; } + /** + * Returns the time interval that the resource records may be cached on a DNS resolver or + * {@code null} if not specified. + * + * @hide + */ + // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED) + @Nullable + public Duration getTtl() { + return mTtl; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("serviceInfo: ").append(mServiceInfo) .append(", protocolType: ").append(mProtocolType) - .append(", advertisingConfig: ").append(mAdvertisingConfig); + .append(", advertisingConfig: ").append(mAdvertisingConfig) + .append(", ttl: ").append(mTtl); return sb.toString(); } @@ -120,13 +141,14 @@ public final class AdvertisingRequest implements Parcelable { final AdvertisingRequest otherRequest = (AdvertisingRequest) other; return mServiceInfo.equals(otherRequest.mServiceInfo) && mProtocolType == otherRequest.mProtocolType - && mAdvertisingConfig == otherRequest.mAdvertisingConfig; + && mAdvertisingConfig == otherRequest.mAdvertisingConfig + && Objects.equals(mTtl, otherRequest.mTtl); } } @Override public int hashCode() { - return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig); + return Objects.hash(mServiceInfo, mProtocolType, mAdvertisingConfig, mTtl); } @Override @@ -139,6 +161,7 @@ public final class AdvertisingRequest implements Parcelable { dest.writeParcelable(mServiceInfo, flags); dest.writeInt(mProtocolType); dest.writeLong(mAdvertisingConfig); + dest.writeLong(mTtl == null ? -1 : mTtl.getSeconds()); } // @FlaggedApi(NsdManager.Flags.ADVERTISE_REQUEST_API) @@ -151,6 +174,8 @@ public final class AdvertisingRequest implements Parcelable { private final NsdServiceInfo mServiceInfo; private final int mProtocolType; private long mAdvertisingConfig; + @Nullable + private Duration mTtl; /** * Creates a new {@link Builder} object. */ @@ -170,11 +195,44 @@ public final class AdvertisingRequest implements Parcelable { return this; } + /** + * Sets the time interval that the resource records may be cached on a DNS resolver. + * + * If this method is not called or {@code ttl} is {@code null}, default TTL values + * will be used for the service when it's registered. Otherwise, the {@code ttl} + * will be used for all resource records of this service. + * + * When registering a service, {@link NsdManager#FAILURE_BAD_PARAMETERS} will be returned + * if {@code ttl} is smaller than 30 seconds. + * + * Note: only number of seconds of {@code ttl} is used. + * + * @param ttl the maximum duration that the DNS resource records will be cached + * + * @see AdvertisingRequest#getTtl + * @hide + */ + // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED) + @NonNull + public Builder setTtl(@Nullable Duration ttl) { + if (ttl == null) { + mTtl = null; + return this; + } + final long ttlSeconds = ttl.getSeconds(); + if (ttlSeconds < 0 || ttlSeconds > 0xffffffffL) { + throw new IllegalArgumentException( + "ttlSeconds exceeds the allowed range (value = " + ttlSeconds + + ", allowedRanged = [0, 0xffffffffL])"); + } + mTtl = Duration.ofSeconds(ttlSeconds); + return this; + } /** Creates a new {@link AdvertisingRequest} object. */ @NonNull public AdvertisingRequest build() { - return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig); + return new AdvertisingRequest(mServiceInfo, mProtocolType, mAdvertisingConfig, mTtl); } } } diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java index f6e132497c..1001423732 100644 --- a/framework-t/src/android/net/nsd/NsdManager.java +++ b/framework-t/src/android/net/nsd/NsdManager.java @@ -160,6 +160,8 @@ public final class NsdManager { "com.android.net.flags.advertise_request_api"; static final String NSD_CUSTOM_HOSTNAME_ENABLED = "com.android.net.flags.nsd_custom_hostname_enabled"; + static final String NSD_CUSTOM_TTL_ENABLED = + "com.android.net.flags.nsd_custom_ttl_enabled"; } /** @@ -327,6 +329,20 @@ public final class NsdManager { /** Dns based service discovery protocol */ public static final int PROTOCOL_DNS_SD = 0x0001; + /** + * The minimum TTL seconds which is allowed for a service registration. + * + * @hide + */ + public static final long TTL_SECONDS_MIN = 30L; + + /** + * The maximum TTL seconds which is allowed for a service registration. + * + * @hide + */ + public static final long TTL_SECONDS_MAX = 10 * 3600L; + private static final SparseArray<String> EVENT_NAMES = new SparseArray<>(); static { EVENT_NAMES.put(DISCOVER_SERVICES, "DISCOVER_SERVICES"); diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java index 146d4cae30..f4cc2ac4f5 100644 --- a/framework-t/src/android/net/nsd/NsdServiceInfo.java +++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java @@ -16,8 +16,6 @@ package android.net.nsd; -import static java.nio.charset.StandardCharsets.UTF_8; - import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,6 +33,7 @@ import com.android.net.module.util.InetAddressUtils; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -71,6 +70,10 @@ public final class NsdServiceInfo implements Parcelable { private int mInterfaceIndex; + // The timestamp that all resource records associated with this service are considered invalid. + @Nullable + private Instant mExpirationTime; + public NsdServiceInfo() { mSubtypes = new ArraySet<>(); mTxtRecord = new ArrayMap<>(); @@ -99,6 +102,7 @@ public final class NsdServiceInfo implements Parcelable { mPort = other.getPort(); mNetwork = other.getNetwork(); mInterfaceIndex = other.getInterfaceIndex(); + mExpirationTime = other.getExpirationTime(); } /** Get the service name */ @@ -490,6 +494,38 @@ public final class NsdServiceInfo implements Parcelable { return Collections.unmodifiableSet(mSubtypes); } + /** + * Sets the timestamp after when this service is expired. + * + * Note: only number of seconds of {@code expirationTime} is used. + * + * @hide + */ + public void setExpirationTime(@Nullable Instant expirationTime) { + if (expirationTime == null) { + mExpirationTime = null; + } else { + mExpirationTime = Instant.ofEpochSecond(expirationTime.getEpochSecond()); + } + } + + /** + * Returns the timestamp after when this service is expired or {@code null} if it's unknown. + * + * A service is considered expired if any of its DNS record is expired. + * + * Clients that are depending on the refreshness of the service information should not continue + * use this service after the returned timestamp. Instead, clients may re-send queries for the + * service to get updated the service information. + * + * @hide + */ + // @FlaggedApi(NsdManager.Flags.NSD_CUSTOM_TTL_ENABLED) + @Nullable + public Instant getExpirationTime() { + return mExpirationTime; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -499,7 +535,8 @@ public final class NsdServiceInfo implements Parcelable { .append(", hostAddresses: ").append(TextUtils.join(", ", mHostAddresses)) .append(", hostname: ").append(mHostname) .append(", port: ").append(mPort) - .append(", network: ").append(mNetwork); + .append(", network: ").append(mNetwork) + .append(", expirationTime: ").append(mExpirationTime); byte[] txtRecord = getTxtRecord(); sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8)); @@ -539,6 +576,7 @@ public final class NsdServiceInfo implements Parcelable { InetAddressUtils.parcelInetAddress(dest, address, flags); } dest.writeString(mHostname); + dest.writeLong(mExpirationTime != null ? mExpirationTime.getEpochSecond() : -1); } /** Implement the Parcelable interface */ @@ -569,6 +607,8 @@ public final class NsdServiceInfo implements Parcelable { info.mHostAddresses.add(InetAddressUtils.unparcelInetAddress(in)); } info.mHostname = in.readString(); + final long seconds = in.readLong(); + info.setExpirationTime(seconds < 0 ? null : Instant.ofEpochSecond(seconds)); return info; } diff --git a/framework/Android.bp b/framework/Android.bp index 8fa336a771..52f2c7c268 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -96,7 +96,6 @@ java_defaults { ], impl_only_static_libs: [ "net-utils-device-common-bpf", - "net-utils-device-common-struct", ], libs: [ "androidx.annotation_annotation", @@ -125,7 +124,6 @@ java_library { // Even if the library is included in "impl_only_static_libs" of defaults. This is still // needed because java_library which doesn't understand "impl_only_static_libs". "net-utils-device-common-bpf", - "net-utils-device-common-struct", ], libs: [ // This cannot be in the defaults clause above because if it were, it would be used @@ -167,17 +165,16 @@ java_sdk_library { "//packages/modules/Connectivity/framework-t", "//packages/modules/Connectivity/service", "//packages/modules/Connectivity/service-t", - "//frameworks/base/packages/Connectivity/service", "//frameworks/base", // Tests using hidden APIs "//cts/tests/netlegacy22.api", "//cts/tests/tests/app.usage", // NetworkUsageStatsTest "//external/sl4a:__subpackages__", - "//frameworks/base/packages/Connectivity/tests:__subpackages__", "//frameworks/base/core/tests/bandwidthtests", "//frameworks/base/core/tests/benchmarks", "//frameworks/base/core/tests/utillib", + "//frameworks/base/services/tests/VpnTests", "//frameworks/base/tests/vcn", "//frameworks/opt/net/ethernet/tests:__subpackages__", "//frameworks/opt/telephony/tests/telephonytests", diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp index d34fd837ef..749113d933 100644 --- a/nearby/service/Android.bp +++ b/nearby/service/Android.bp @@ -43,6 +43,7 @@ java_library { ], static_libs: [ "androidx.core_core", + "android.hardware.bluetooth.finder-V1-java", "guava", "libprotobuf-java-lite", "modules-utils-build", diff --git a/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java b/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java index 63ff516ce5..365b099a25 100644 --- a/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java +++ b/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java @@ -16,26 +16,151 @@ package com.android.server.nearby.managers; +import static com.android.server.nearby.NearbyService.TAG; + +import android.annotation.TargetApi; +import android.hardware.bluetooth.finder.Eid; +import android.hardware.bluetooth.finder.IBluetoothFinder; import android.nearby.PoweredOffFindingEphemeralId; +import android.os.Build; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; import java.util.List; /** Connects to {@link IBluetoothFinder} HAL and invokes its API. */ -// A placeholder implementation until the HAL API can be used. +@TargetApi(Build.VERSION_CODES.TIRAMISU) public class BluetoothFinderManager { - private boolean mPoweredOffFindingModeEnabled = false; + private static final String HAL_INSTANCE_NAME = IBluetoothFinder.DESCRIPTOR + "/default"; + + private IBluetoothFinder mBluetoothFinder; + private IBinder.DeathRecipient mServiceDeathRecipient; + private final Object mLock = new Object(); + + private boolean initBluetoothFinderHal() { + final String methodStr = "initBluetoothFinderHal"; + if (!SdkLevel.isAtLeastV()) return false; + synchronized (mLock) { + if (mBluetoothFinder != null) { + Log.i(TAG, "Bluetooth Finder HAL is already initialized"); + return true; + } + try { + mBluetoothFinder = getServiceMockable(); + if (mBluetoothFinder == null) { + Log.e(TAG, "Unable to obtain IBluetoothFinder"); + return false; + } + Log.i(TAG, "Obtained IBluetoothFinder. Local ver: " + IBluetoothFinder.VERSION + + ", Remote ver: " + mBluetoothFinder.getInterfaceVersion()); + + IBinder serviceBinder = getServiceBinderMockable(); + if (serviceBinder == null) { + Log.e(TAG, "Unable to obtain the service binder for IBluetoothFinder"); + return false; + } + mServiceDeathRecipient = new BluetoothFinderDeathRecipient(); + serviceBinder.linkToDeath(mServiceDeathRecipient, /* flags= */ 0); + + Log.i(TAG, "Bluetooth Finder HAL initialization was successful"); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (Exception e) { + Log.e(TAG, methodStr + " encountered an exception: " + e); + } + return false; + } + } + + @VisibleForTesting + protected IBluetoothFinder getServiceMockable() { + return IBluetoothFinder.Stub.asInterface( + ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME)); + } + + @VisibleForTesting + protected IBinder getServiceBinderMockable() { + return mBluetoothFinder.asBinder(); + } - /** An empty implementation of the corresponding HAL API call. */ - public void sendEids(List<PoweredOffFindingEphemeralId> eids) {} + private class BluetoothFinderDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + Log.e(TAG, "BluetoothFinder service died."); + synchronized (mLock) { + mBluetoothFinder = null; + } + } + } - /** A placeholder implementation of the corresponding HAL API call. */ + /** See comments for {@link IBluetoothFinder#sendEids(Eid[])} */ + public void sendEids(List<PoweredOffFindingEphemeralId> eids) { + final String methodStr = "sendEids"; + if (!checkHalAndLogFailure(methodStr)) return; + Eid[] eidArray = eids.stream().map( + ephmeralId -> { + Eid eid = new Eid(); + eid.bytes = ephmeralId.bytes; + return eid; + }).toArray(Eid[]::new); + try { + mBluetoothFinder.sendEids(eidArray); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + } + + /** See comments for {@link IBluetoothFinder#setPoweredOffFinderMode(boolean)} */ public void setPoweredOffFinderMode(boolean enable) { - mPoweredOffFindingModeEnabled = enable; + final String methodStr = "setPoweredOffMode"; + if (!checkHalAndLogFailure(methodStr)) return; + try { + mBluetoothFinder.setPoweredOffFinderMode(enable); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } } - /** A placeholder implementation of the corresponding HAL API call. */ + /** See comments for {@link IBluetoothFinder#getPoweredOffFinderMode()} */ public boolean getPoweredOffFinderMode() { - return mPoweredOffFindingModeEnabled; + final String methodStr = "getPoweredOffMode"; + if (!checkHalAndLogFailure(methodStr)) return false; + try { + return mBluetoothFinder.getPoweredOffFinderMode(); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + + private boolean checkHalAndLogFailure(String methodStr) { + if ((mBluetoothFinder == null) && !initBluetoothFinderHal()) { + Log.e(TAG, "Unable to call " + methodStr + " because IBluetoothFinder is null."); + return false; + } + return true; + } + + private void handleRemoteException(RemoteException e, String methodStr) { + mBluetoothFinder = null; + Log.e(TAG, methodStr + " failed with remote exception: " + e); + } + + private void handleServiceSpecificException(ServiceSpecificException e, String methodStr) { + Log.e(TAG, methodStr + " failed with service-specific exception: " + e); } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java new file mode 100644 index 0000000000..671b5c5ac7 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2024 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.nearby.managers; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.bluetooth.finder.Eid; +import android.hardware.bluetooth.finder.IBluetoothFinder; +import android.nearby.PoweredOffFindingEphemeralId; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.os.ServiceSpecificException; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +public class BluetoothFinderManagerTest { + private BluetoothFinderManager mBluetoothFinderManager; + private boolean mGetServiceCalled = false; + + @Mock private IBluetoothFinder mIBluetoothFinderMock; + @Mock private IBinder mServiceBinderMock; + + private ArgumentCaptor<DeathRecipient> mDeathRecipientCaptor = + ArgumentCaptor.forClass(DeathRecipient.class); + + private ArgumentCaptor<Eid[]> mEidArrayCaptor = ArgumentCaptor.forClass(Eid[].class); + + private class BluetoothFinderManagerSpy extends BluetoothFinderManager { + @Override + protected IBluetoothFinder getServiceMockable() { + mGetServiceCalled = true; + return mIBluetoothFinderMock; + } + + @Override + protected IBinder getServiceBinderMockable() { + return mServiceBinderMock; + } + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mBluetoothFinderManager = new BluetoothFinderManagerSpy(); + } + + @Test + public void testSendEids() throws Exception { + byte[] eidBytes1 = { + (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d, + (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d, + (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d, + (byte) 0xe1, (byte) 0xde + }; + byte[] eidBytes2 = { + (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e, + (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e, + (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e, + (byte) 0xf2, (byte) 0xef + }; + PoweredOffFindingEphemeralId ephemeralId1 = new PoweredOffFindingEphemeralId(); + PoweredOffFindingEphemeralId ephemeralId2 = new PoweredOffFindingEphemeralId(); + ephemeralId1.bytes = eidBytes1; + ephemeralId2.bytes = eidBytes2; + + mBluetoothFinderManager.sendEids(List.of(ephemeralId1, ephemeralId2)); + + verify(mIBluetoothFinderMock).sendEids(mEidArrayCaptor.capture()); + assertThat(mEidArrayCaptor.getValue()[0].bytes).isEqualTo(eidBytes1); + assertThat(mEidArrayCaptor.getValue()[1].bytes).isEqualTo(eidBytes2); + } + + @Test + public void testSendEids_remoteException() throws Exception { + doThrow(new RemoteException()) + .when(mIBluetoothFinderMock).sendEids(any()); + mBluetoothFinderManager.sendEids(List.of()); + + // Verify that we get the service again following a RemoteException. + mGetServiceCalled = false; + mBluetoothFinderManager.sendEids(List.of()); + assertThat(mGetServiceCalled).isTrue(); + } + + @Test + public void testSendEids_serviceSpecificException() throws Exception { + doThrow(new ServiceSpecificException(1)) + .when(mIBluetoothFinderMock).sendEids(any()); + mBluetoothFinderManager.sendEids(List.of()); + } + + @Test + public void testSetPoweredOffFinderMode() throws Exception { + mBluetoothFinderManager.setPoweredOffFinderMode(true); + verify(mIBluetoothFinderMock).setPoweredOffFinderMode(true); + + mBluetoothFinderManager.setPoweredOffFinderMode(false); + verify(mIBluetoothFinderMock).setPoweredOffFinderMode(false); + } + + @Test + public void testSetPoweredOffFinderMode_remoteException() throws Exception { + doThrow(new RemoteException()) + .when(mIBluetoothFinderMock).setPoweredOffFinderMode(anyBoolean()); + mBluetoothFinderManager.setPoweredOffFinderMode(true); + + // Verify that we get the service again following a RemoteException. + mGetServiceCalled = false; + mBluetoothFinderManager.setPoweredOffFinderMode(true); + assertThat(mGetServiceCalled).isTrue(); + } + + @Test + public void testSetPoweredOffFinderMode_serviceSpecificException() throws Exception { + doThrow(new ServiceSpecificException(1)) + .when(mIBluetoothFinderMock).setPoweredOffFinderMode(anyBoolean()); + mBluetoothFinderManager.setPoweredOffFinderMode(true); + } + + @Test + public void testGetPoweredOffFinderMode() throws Exception { + when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenReturn(true); + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isTrue(); + + when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenReturn(false); + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse(); + } + + @Test + public void testGetPoweredOffFinderMode_remoteException() throws Exception { + when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenThrow(new RemoteException()); + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse(); + + // Verify that we get the service again following a RemoteException. + mGetServiceCalled = false; + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse(); + assertThat(mGetServiceCalled).isTrue(); + } + + @Test + public void testGetPoweredOffFinderMode_serviceSpecificException() throws Exception { + when(mIBluetoothFinderMock.getPoweredOffFinderMode()) + .thenThrow(new ServiceSpecificException(1)); + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse(); + } + + @Test + public void testDeathRecipient() throws Exception { + mBluetoothFinderManager.setPoweredOffFinderMode(true); + verify(mServiceBinderMock).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); + mDeathRecipientCaptor.getValue().binderDied(); + + // Verify that we get the service again following a binder death. + mGetServiceCalled = false; + mBluetoothFinderManager.setPoweredOffFinderMode(true); + assertThat(mGetServiceCalled).isTrue(); + } +} diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp index 9dc7cdc4e6..ed7d048279 100644 --- a/netbpfload/NetBpfLoad.cpp +++ b/netbpfload/NetBpfLoad.cpp @@ -369,6 +369,13 @@ int main(int argc, char** argv, char * const envp[]) { if (createSysFsBpfSubDir(location.prefix)) return 1; } + // Note: there's no actual src dir for fs_bpf_loader .o's, + // so it is not listed in 'locations[].prefix'. + // This is because this is primarily meant for triggering genfscon rules, + // and as such this will likely always be the case. + // Thus we need to manually create the /sys/fs/bpf/loader subdirectory. + if (createSysFsBpfSubDir("loader")) return 1; + // Load all ELF objects, create programs and maps, and pin them for (const auto& location : locations) { if (loadAllElfObjects(location) != 0) { diff --git a/service-t/Android.bp b/service-t/Android.bp index 19850fd19c..779f354fd1 100644 --- a/service-t/Android.bp +++ b/service-t/Android.bp @@ -75,6 +75,7 @@ java_library { "com.android.tethering", ], visibility: [ + "//frameworks/base/services/tests/VpnTests", "//frameworks/base/tests/vcn", "//packages/modules/Connectivity/service", "//packages/modules/Connectivity/tests:__subpackages__", diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java index 9ba49d2e52..cfb1a33390 100644 --- a/service-t/src/com/android/server/NsdService.java +++ b/service-t/src/com/android/server/NsdService.java @@ -28,6 +28,7 @@ import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT; import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED; import static android.net.nsd.NsdManager.SUBTYPE_LABEL_REGEX; import static android.net.nsd.NsdManager.TYPE_REGEX; +import static android.os.Process.SYSTEM_UID; import static android.provider.DeviceConfig.NAMESPACE_TETHERING; import static com.android.modules.utils.build.SdkLevel.isAtLeastU; @@ -92,6 +93,7 @@ import com.android.metrics.NetworkNsdReportedMetrics; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.DeviceConfigUtils; +import com.android.net.module.util.HandlerUtils; import com.android.net.module.util.InetAddressUtils; import com.android.net.module.util.PermissionUtils; import com.android.net.module.util.SharedLog; @@ -115,6 +117,7 @@ import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -738,6 +741,33 @@ public class NsdService extends INsdManager.Stub { return new ArraySet<>(subtypeMap.values()); } + private boolean checkTtl( + @Nullable Duration ttl, @NonNull ClientInfo clientInfo) { + if (ttl == null) { + return true; + } + + final long ttlSeconds = ttl.toSeconds(); + final int uid = clientInfo.getUid(); + + // Allows Thread module in the system_server to register TTL that is smaller than + // 30 seconds + final long minTtlSeconds = uid == SYSTEM_UID ? 0 : NsdManager.TTL_SECONDS_MIN; + + // Allows Thread module in the system_server to register TTL that is larger than + // 10 hours + final long maxTtlSeconds = + uid == SYSTEM_UID ? 0xffffffffL : NsdManager.TTL_SECONDS_MAX; + + if (ttlSeconds < minTtlSeconds || ttlSeconds > maxTtlSeconds) { + mServiceLogs.e("ttlSeconds exceeds allowed range (value = " + + ttlSeconds + ", allowedRange = [" + minTtlSeconds + + ", " + maxTtlSeconds + " ])"); + return false; + } + return true; + } + @Override public boolean processMessage(Message msg) { final ClientInfo clientInfo; @@ -964,11 +994,19 @@ public class NsdService extends INsdManager.Stub { break; } + if (!checkTtl(advertisingRequest.getTtl(), clientInfo)) { + clientInfo.onRegisterServiceFailedImmediately(clientRequestId, + NsdManager.FAILURE_BAD_PARAMETERS, false /* isLegacy */); + break; + } + serviceInfo.setSubtypes(subtypes); maybeStartMonitoringSockets(); final MdnsAdvertisingOptions mdnsAdvertisingOptions = - MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate( - isUpdateOnly).build(); + MdnsAdvertisingOptions.newBuilder() + .setIsOnlyUpdate(isUpdateOnly) + .setTtl(advertisingRequest.getTtl()) + .build(); mAdvertiser.addOrUpdateService(transactionId, serviceInfo, mdnsAdvertisingOptions, clientInfo.mUid); storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo, @@ -1511,6 +1549,7 @@ public class NsdService extends INsdManager.Stub { network == null ? INetd.LOCAL_NET_ID : network.netId, serviceInfo.getInterfaceIndex()); servInfo.setSubtypes(dedupSubtypeLabels(serviceInfo.getSubtypes())); + servInfo.setExpirationTime(serviceInfo.getExpirationTime()); return servInfo; } @@ -2510,6 +2549,14 @@ public class NsdService extends INsdManager.Stub { pw.increaseIndent(); mServiceLogs.reverseDump(pw); pw.decreaseIndent(); + + //Dump DiscoveryManager + pw.println(); + pw.println("DiscoveryManager:"); + pw.increaseIndent(); + HandlerUtils.runWithScissorsForDump( + mNsdStateMachine.getHandler(), () -> mMdnsDiscoveryManager.dump(pw), 10_000); + pw.decreaseIndent(); } private abstract static class ClientRequest { @@ -2671,6 +2718,10 @@ public class NsdService extends INsdManager.Stub { return sb.toString(); } + public int getUid() { + return mUid; + } + private boolean isPreSClient() { return mIsPreSClient; } diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java index 0b60572d5a..c162bcc4b1 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java @@ -449,7 +449,8 @@ public class MdnsAdvertiser { mPendingRegistrations.put(id, registration); for (int i = 0; i < mAdvertisers.size(); i++) { try { - mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo()); + mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo(), + registration.getAdvertisingOptions()); } catch (NameConflictException e) { mSharedLog.wtf("Name conflict adding services that should have unique names", e); @@ -515,7 +516,7 @@ public class MdnsAdvertiser { final Registration registration = mPendingRegistrations.valueAt(i); try { advertiser.addService(mPendingRegistrations.keyAt(i), - registration.getServiceInfo()); + registration.getServiceInfo(), registration.getAdvertisingOptions()); } catch (NameConflictException e) { mSharedLog.wtf("Name conflict adding services that should have unique names", e); @@ -587,15 +588,17 @@ public class MdnsAdvertiser { @NonNull private NsdServiceInfo mServiceInfo; final int mClientUid; + private final MdnsAdvertisingOptions mAdvertisingOptions; int mConflictDuringProbingCount; int mConflictAfterProbingCount; - - private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid) { + private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid, + @NonNull MdnsAdvertisingOptions advertisingOptions) { this.mOriginalServiceName = serviceInfo.getServiceName(); this.mOriginalHostname = serviceInfo.getHostname(); this.mServiceInfo = serviceInfo; this.mClientUid = clientUid; + this.mAdvertisingOptions = advertisingOptions; } /** Check if the new {@link NsdServiceInfo} doesn't update any data other than subtypes. */ @@ -697,6 +700,11 @@ public class MdnsAdvertiser { public NsdServiceInfo getServiceInfo() { return mServiceInfo; } + + @NonNull + public MdnsAdvertisingOptions getAdvertisingOptions() { + return mAdvertisingOptions; + } } /** @@ -855,7 +863,7 @@ public class MdnsAdvertiser { } mSharedLog.i("Adding service " + service + " with ID " + id + " and subtypes " + subtypes + " advertisingOptions " + advertisingOptions); - registration = new Registration(service, clientUid); + registration = new Registration(service, clientUid, advertisingOptions); final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter; if (network == null) { // If registering on all networks, no advertiser must have conflicts diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java index e7a6ca7345..a81d1e489b 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java @@ -16,6 +16,11 @@ package com.android.server.connectivity.mdns; +import android.annotation.Nullable; + +import java.time.Duration; +import java.util.Objects; + /** * API configuration parameters for advertising the mDNS service. * @@ -27,13 +32,15 @@ public class MdnsAdvertisingOptions { private static MdnsAdvertisingOptions sDefaultOptions; private final boolean mIsOnlyUpdate; + @Nullable + private final Duration mTtl; /** * Parcelable constructs for a {@link MdnsAdvertisingOptions}. */ - MdnsAdvertisingOptions( - boolean isOnlyUpdate) { + MdnsAdvertisingOptions(boolean isOnlyUpdate, @Nullable Duration ttl) { this.mIsOnlyUpdate = isOnlyUpdate; + this.mTtl = ttl; } /** @@ -60,9 +67,36 @@ public class MdnsAdvertisingOptions { return mIsOnlyUpdate; } + /** + * Returns the TTL for all records in a service. + */ + @Nullable + public Duration getTtl() { + return mTtl; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (!(other instanceof MdnsAdvertisingOptions)) { + return false; + } else { + final MdnsAdvertisingOptions otherOptions = (MdnsAdvertisingOptions) other; + return mIsOnlyUpdate == otherOptions.mIsOnlyUpdate + && Objects.equals(mTtl, otherOptions.mTtl); + } + } + + @Override + public int hashCode() { + return Objects.hash(mIsOnlyUpdate, mTtl); + } + @Override public String toString() { - return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + '}'; + return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + ", mTtl=" + mTtl + + '}'; } /** @@ -70,6 +104,8 @@ public class MdnsAdvertisingOptions { */ public static final class Builder { private boolean mIsOnlyUpdate = false; + @Nullable + private Duration mTtl; private Builder() { } @@ -83,10 +119,18 @@ public class MdnsAdvertisingOptions { } /** + * Sets the TTL duration for all records of the service. + */ + public Builder setTtl(@Nullable Duration ttl) { + this.mTtl = ttl; + return this; + } + + /** * Builds a {@link MdnsAdvertisingOptions} with the arguments supplied to this builder. */ public MdnsAdvertisingOptions build() { - return new MdnsAdvertisingOptions(mIsOnlyUpdate); + return new MdnsAdvertisingOptions(mIsOnlyUpdate, mTtl); } } } diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java index 1d6039c05a..21b706996c 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java @@ -34,6 +34,7 @@ import com.android.net.module.util.SharedLog; import com.android.server.connectivity.mdns.util.MdnsUtils; import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -363,4 +364,18 @@ public class MdnsDiscoveryManager implements MdnsSocketClientBase.Callback { executorProvider.newServiceTypeClientSchedulerExecutor(), socketKey, sharedLog.forSubComponent(tag), looper, serviceCache); } + + /** + * Dump DiscoveryManager state. + */ + public void dump(PrintWriter pw) { + discoveryExecutor.checkAndRunOnHandlerThread(() -> { + pw.println(); + // Dump ServiceTypeClients + for (MdnsServiceTypeClient serviceTypeClient + : perSocketServiceTypeClients.getAllMdnsServiceTypeClient()) { + serviceTypeClient.dump(pw); + } + }); + } }
\ No newline at end of file diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java index aa51c41773..c2363c085e 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java @@ -258,8 +258,10 @@ public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHand * * @throws NameConflictException There is already a service being advertised with that name. */ - public void addService(int id, NsdServiceInfo service) throws NameConflictException { - final int replacedExitingService = mRecordRepository.addService(id, service); + public void addService(int id, NsdServiceInfo service, + @NonNull MdnsAdvertisingOptions advertisingOptions) throws NameConflictException { + final int replacedExitingService = + mRecordRepository.addService(id, service, advertisingOptions.getTtl()); // Cancel announcements for the existing service. This only happens for exiting services // (so cancelling exiting announcements), as per RecordRepository.addService. if (replacedExitingService >= 0) { diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java index ed0bde2d93..ac64c3a9e8 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java @@ -45,6 +45,7 @@ import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -75,9 +76,9 @@ public class MdnsRecordRepository { // TTL for records with a host name as the resource record's name (e.g., A, AAAA, HINFO) or a // host name contained within the resource record's rdata (e.g., SRV, reverse mapping PTR // record) - private static final long NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120); + private static final long DEFAULT_NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120); // TTL for other records - private static final long NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75); + private static final long DEFAULT_NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75); // Top-level domain for link-local queries, as per RFC6762 3. private static final String LOCAL_TLD = "local"; @@ -193,6 +194,9 @@ public class MdnsRecordRepository { */ private boolean isProbing; + @Nullable + private Duration ttl; + /** * Create a ServiceRegistration with only update the subType. */ @@ -200,16 +204,32 @@ public class MdnsRecordRepository { NsdServiceInfo newServiceInfo = new NsdServiceInfo(serviceInfo); newServiceInfo.setSubtypes(newSubtypes); return new ServiceRegistration(srvRecord.record.getServiceHost(), newServiceInfo, - repliedServiceCount, sentPacketCount, exiting, isProbing); + repliedServiceCount, sentPacketCount, exiting, isProbing, ttl); } /** * Create a ServiceRegistration for dns-sd service registration (RFC6763). */ ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo, - int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing) { + int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing, + @Nullable Duration ttl) { this.serviceInfo = serviceInfo; + final long nonNameRecordsTtlMillis; + final long nameRecordsTtlMillis; + + // When custom TTL is specified, all records of the service will use the custom TTL. + // This is typically useful for SRP (Service Registration Protocol: + // https://datatracker.ietf.org/doc/html/draft-ietf-dnssd-srp-24) Advertising Proxy + // where all records in a single SRP are required the same TTL. + if (ttl != null) { + nonNameRecordsTtlMillis = ttl.toMillis(); + nameRecordsTtlMillis = ttl.toMillis(); + } else { + nonNameRecordsTtlMillis = DEFAULT_NON_NAME_RECORDS_TTL_MILLIS; + nameRecordsTtlMillis = DEFAULT_NAME_RECORDS_TTL_MILLIS; + } + final boolean hasService = !TextUtils.isEmpty(serviceInfo.getServiceType()); final boolean hasCustomHost = !TextUtils.isEmpty(serviceInfo.getHostname()); final String[] hostname = @@ -229,7 +249,7 @@ public class MdnsRecordRepository { serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */, - NON_NAME_RECORDS_TTL_MILLIS, + nonNameRecordsTtlMillis, serviceName), true /* sharedName */)); for (String subtype : serviceInfo.getSubtypes()) { @@ -239,7 +259,7 @@ public class MdnsRecordRepository { MdnsUtils.constructFullSubtype(serviceType, subtype), 0L /* receiptTimeMillis */, false /* cacheFlush */, - NON_NAME_RECORDS_TTL_MILLIS, + nonNameRecordsTtlMillis, serviceName), true /* sharedName */)); } @@ -249,7 +269,7 @@ public class MdnsRecordRepository { new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */, true /* cacheFlush */, - NAME_RECORDS_TTL_MILLIS, + nameRecordsTtlMillis, 0 /* servicePriority */, 0 /* serviceWeight */, serviceInfo.getPort(), hostname), @@ -261,7 +281,7 @@ public class MdnsRecordRepository { 0L /* receiptTimeMillis */, // Service name is verified unique after probing true /* cacheFlush */, - NON_NAME_RECORDS_TTL_MILLIS, + nonNameRecordsTtlMillis, attrsToTextEntries(serviceInfo.getAttributes())), false /* sharedName */); @@ -275,7 +295,7 @@ public class MdnsRecordRepository { DNS_SD_SERVICE_TYPE, 0L /* receiptTimeMillis */, false /* cacheFlush */, - NON_NAME_RECORDS_TTL_MILLIS, + nonNameRecordsTtlMillis, serviceType), true /* sharedName */)); } else { @@ -292,7 +312,7 @@ public class MdnsRecordRepository { new MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */, - NAME_RECORDS_TTL_MILLIS, + nameRecordsTtlMillis, address), false /* sharedName */)); } @@ -315,9 +335,9 @@ public class MdnsRecordRepository { * @param serviceInfo Service to advertise */ ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo, - int repliedServiceCount, int sentPacketCount) { + int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl) { this(deviceHostname, serviceInfo,repliedServiceCount, sentPacketCount, - false /* exiting */, true /* isProbing */); + false /* exiting */, true /* isProbing */, ttl); } void setProbing(boolean probing) { @@ -339,7 +359,7 @@ public class MdnsRecordRepository { revDnsAddr, 0L /* receiptTimeMillis */, true /* cacheFlush */, - NAME_RECORDS_TTL_MILLIS, + DEFAULT_NAME_RECORDS_TTL_MILLIS, mDeviceHostname), false /* sharedName */)); @@ -349,7 +369,7 @@ public class MdnsRecordRepository { mDeviceHostname, 0L /* receiptTimeMillis */, true /* cacheFlush */, - NAME_RECORDS_TTL_MILLIS, + DEFAULT_NAME_RECORDS_TTL_MILLIS, addr.getAddress()), false /* sharedName */)); } @@ -378,11 +398,13 @@ public class MdnsRecordRepository { * This may remove/replace any existing service that used the name added but is exiting. * @param serviceId A unique service ID. * @param serviceInfo Service info to add. + * @param ttl the TTL duration for all records of {@code serviceInfo} or {@code null} * @return If the added service replaced another with a matching name (which was exiting), the * ID of the replaced service. * @throws NameConflictException There is already a (non-exiting) service using the name. */ - public int addService(int serviceId, NsdServiceInfo serviceInfo) throws NameConflictException { + public int addService(int serviceId, NsdServiceInfo serviceInfo, @Nullable Duration ttl) + throws NameConflictException { if (mServices.contains(serviceId)) { throw new IllegalArgumentException( "Service ID must not be reused across registrations: " + serviceId); @@ -397,7 +419,7 @@ public class MdnsRecordRepository { final ServiceRegistration registration = new ServiceRegistration( mDeviceHostname, serviceInfo, NO_PACKET /* repliedServiceCount */, - NO_PACKET /* sentPacketCount */); + NO_PACKET /* sentPacketCount */, ttl); mServices.put(serviceId, registration); // Remove existing exiting service @@ -776,7 +798,7 @@ public class MdnsRecordRepository { true /* cacheFlush */, // TODO: RFC6762 6.1: "In general, the TTL given for an NSEC record SHOULD // be the same as the TTL that the record would have had, had it existed." - NAME_RECORDS_TTL_MILLIS, + DEFAULT_NAME_RECORDS_TTL_MILLIS, question.getName(), new int[] { question.getType() }); additionalAnswerInfo.add( @@ -1211,7 +1233,7 @@ public class MdnsRecordRepository { if (existing == null) return null; final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo, - existing.repliedServiceCount, existing.sentPacketCount); + existing.repliedServiceCount, existing.sentPacketCount, existing.ttl); mServices.put(serviceId, newService); return makeProbingInfo(serviceId, newService); } diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java index 78df6df33c..f60a95e7cf 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java @@ -28,6 +28,7 @@ import android.text.TextUtils; import com.android.net.module.util.ByteUtils; import java.nio.charset.Charset; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -62,7 +63,8 @@ public class MdnsServiceInfo implements Parcelable { source.createStringArrayList(), source.createTypedArrayList(TextEntry.CREATOR), source.readInt(), - source.readParcelable(null)); + source.readParcelable(Network.class.getClassLoader()), + Instant.ofEpochSecond(source.readLong())); } @Override @@ -89,6 +91,9 @@ public class MdnsServiceInfo implements Parcelable { @Nullable private final Network network; + @NonNull + private final Instant expirationTime; + /** Constructs a {@link MdnsServiceInfo} object with default values. */ public MdnsServiceInfo( String serviceInstanceName, @@ -110,7 +115,8 @@ public class MdnsServiceInfo implements Parcelable { textStrings, /* textEntries= */ null, /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED, - /* network= */ null); + /* network= */ null, + /* expirationTime= */ Instant.MAX); } /** Constructs a {@link MdnsServiceInfo} object with default values. */ @@ -135,7 +141,8 @@ public class MdnsServiceInfo implements Parcelable { textStrings, textEntries, /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED, - /* network= */ null); + /* network= */ null, + /* expirationTime= */ Instant.MAX); } /** @@ -165,7 +172,8 @@ public class MdnsServiceInfo implements Parcelable { textStrings, textEntries, interfaceIndex, - /* network= */ null); + /* network= */ null, + /* expirationTime= */ Instant.MAX); } /** @@ -184,7 +192,8 @@ public class MdnsServiceInfo implements Parcelable { @Nullable List<String> textStrings, @Nullable List<TextEntry> textEntries, int interfaceIndex, - @Nullable Network network) { + @Nullable Network network, + @NonNull Instant expirationTime) { this.serviceInstanceName = serviceInstanceName; this.serviceType = serviceType; this.subtypes = new ArrayList<>(); @@ -217,6 +226,7 @@ public class MdnsServiceInfo implements Parcelable { this.attributes = Collections.unmodifiableMap(attributes); this.interfaceIndex = interfaceIndex; this.network = network; + this.expirationTime = Instant.ofEpochSecond(expirationTime.getEpochSecond()); } private static List<TextEntry> parseTextStrings(List<String> textStrings) { @@ -314,6 +324,17 @@ public class MdnsServiceInfo implements Parcelable { } /** + * Returns the timestamp after when this service is expired or {@code null} if the expiration + * time is unknown. + * + * A service is considered expired if any of its DNS record is expired. + */ + @NonNull + public Instant getExpirationTime() { + return expirationTime; + } + + /** * Returns attribute value for {@code key} as a UTF-8 string. It's the caller who must make sure * that the value of {@code key} is indeed a UTF-8 string. {@code null} will be returned if no * attribute value exists for {@code key}. @@ -364,6 +385,7 @@ public class MdnsServiceInfo implements Parcelable { out.writeTypedList(textEntries); out.writeInt(interfaceIndex); out.writeParcelable(network, 0); + out.writeLong(expirationTime.getEpochSecond()); } @Override @@ -377,7 +399,8 @@ public class MdnsServiceInfo implements Parcelable { + ", interfaceIndex: " + interfaceIndex + ", network: " + network + ", textStrings: " + textStrings - + ", textEntries: " + textEntries; + + ", textEntries: " + textEntries + + ", expirationTime: " + expirationTime; } @@ -496,4 +519,4 @@ public class MdnsServiceInfo implements Parcelable { out.writeByteArray(value); } } -}
\ No newline at end of file +} diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java index e222fcfdb8..8f41b9479c 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java @@ -35,8 +35,10 @@ import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.SharedLog; import com.android.server.connectivity.mdns.util.MdnsUtils; +import java.io.PrintWriter; import java.net.Inet4Address; import java.net.Inet6Address; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -308,6 +310,7 @@ public class MdnsServiceTypeClient { textStrings = response.getTextRecord().getStrings(); textEntries = response.getTextRecord().getEntries(); } + Instant now = Instant.now(); // TODO: Throw an error message if response doesn't have Inet6 or Inet4 address. return new MdnsServiceInfo( serviceInstanceName, @@ -320,7 +323,8 @@ public class MdnsServiceTypeClient { textStrings, textEntries, response.getInterfaceIndex(), - response.getNetwork()); + response.getNetwork(), + now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli()))); } /** @@ -756,4 +760,13 @@ public class MdnsServiceTypeClient { args.sessionId, timeToNextTasksWithBackoffInMs)); return timeToNextTasksWithBackoffInMs; } + + /** + * Dump ServiceTypeClient state. + */ + public void dump(PrintWriter pw) { + ensureRunningOnHandlerThread(handler); + pw.println("ServiceTypeClient: Type{" + serviceType + "} " + socketKey + " with " + + listeners.size() + " listeners."); + } }
\ No newline at end of file diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java index 80c4033f8f..9684d1801c 100644 --- a/service-t/src/com/android/server/net/NetworkStatsService.java +++ b/service-t/src/com/android/server/net/NetworkStatsService.java @@ -2231,7 +2231,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { .setDefaultNetwork(true) .setOemManaged(ident.getOemManaged()) .setSubId(ident.getSubId()).build(); - final String ifaceVt = IFACE_VT + getSubIdForMobile(snapshot); + final String ifaceVt = IFACE_VT + getSubIdForCellularOrSatellite(snapshot); findOrCreateNetworkIdentitySet(mActiveIfaces, ifaceVt).add(vtIdent); findOrCreateNetworkIdentitySet(mActiveUidIfaces, ifaceVt).add(vtIdent); } @@ -2300,9 +2300,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mMobileIfaces = mobileIfaces.toArray(new String[0]); } - private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) { - if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - throw new IllegalArgumentException("Mobile state need capability TRANSPORT_CELLULAR"); + private static int getSubIdForCellularOrSatellite(@NonNull NetworkStateSnapshot state) { + if (!state.getNetworkCapabilities().hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + // Both cellular and satellite are 2 different network transport at Mobile using + // same telephony network specifier. So adding satellite transport to consider + // for, when satellite network is active at mobile. + && !state.getNetworkCapabilities().hasTransport( + NetworkCapabilities.TRANSPORT_SATELLITE)) { + throw new IllegalArgumentException( + "Mobile state need capability TRANSPORT_CELLULAR or TRANSPORT_SATELLITE"); } final NetworkSpecifier spec = state.getNetworkCapabilities().getNetworkSpecifier(); diff --git a/service/Android.bp b/service/Android.bp index 403ba7d33a..c35c4f853f 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -199,6 +199,7 @@ java_library { "PlatformProperties", "service-connectivity-protos", "service-connectivity-stats-protos", + "net-utils-multicast-forwarding-structs", ], apex_available: [ "com.android.tethering", diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp index 47e897d137..f7b42a6214 100644 --- a/staticlibs/Android.bp +++ b/staticlibs/Android.bp @@ -188,6 +188,33 @@ java_library { }, } +// The net-utils-multicast-forwarding-structs library requires the callers to +// contain net-utils-device-common-bpf. +java_library { + name: "net-utils-multicast-forwarding-structs", + srcs: [ + "device/com/android/net/module/util/structs/StructMf6cctl.java", + "device/com/android/net/module/util/structs/StructMif6ctl.java", + "device/com/android/net/module/util/structs/StructMrt6Msg.java", + ], + sdk_version: "module_current", + min_sdk_version: "30", + visibility: [ + "//packages/modules/Connectivity:__subpackages__", + ], + libs: [ + // Only Struct.java is needed from "net-utils-device-common-bpf" + "net-utils-device-common-bpf", + ], + apex_available: [ + "com.android.tethering", + ], + lint: { + strict_updatability_linting: true, + error_checks: ["NewApi"], + }, +} + // The net-utils-device-common-netlink library requires the callers to contain // net-utils-device-common-struct. java_library { @@ -272,13 +299,11 @@ java_library { "//cts/tests/tests/net", "//cts/tests/tests/wifi", "//packages/modules/Connectivity/tests/cts/net", - "//frameworks/base/packages/Tethering", "//packages/modules/Connectivity/Tethering", "//frameworks/base/tests:__subpackages__", "//frameworks/opt/net/ike", "//frameworks/opt/telephony", "//frameworks/base/wifi:__subpackages__", - "//frameworks/base/packages/Connectivity:__subpackages__", "//packages/modules/Connectivity:__subpackages__", "//packages/modules/NetworkStack:__subpackages__", "//packages/modules/CaptivePortalLogin", diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java index 3124b1b197..6eb56c7b45 100644 --- a/tests/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java @@ -61,7 +61,6 @@ import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.os.Process.INVALID_UID; - import static com.android.modules.utils.build.SdkLevel.isAtLeastS; import static com.android.modules.utils.build.SdkLevel.isAtLeastT; import static com.android.modules.utils.build.SdkLevel.isAtLeastV; @@ -69,7 +68,6 @@ import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; import static com.android.testutils.MiscAsserts.assertEmpty; import static com.android.testutils.MiscAsserts.assertThrows; import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -83,25 +81,22 @@ import android.net.wifi.aware.DiscoverySession; import android.net.wifi.aware.PeerHandle; import android.net.wifi.aware.WifiAwareNetworkSpecifier; import android.os.Build; -import android.test.suitebuilder.annotation.SmallTest; import android.util.ArraySet; import android.util.Range; - +import androidx.test.filters.SmallTest; import com.android.testutils.CompatUtil; import com.android.testutils.ConnectivityModuleTest; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRunner; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; @SmallTest @RunWith(DevSdkIgnoreRunner.class) diff --git a/tests/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml index cea83c76b6..182132989d 100644 --- a/tests/integration/AndroidManifest.xml +++ b/tests/integration/AndroidManifest.xml @@ -42,6 +42,9 @@ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> <!-- Register UidFrozenStateChangedCallback --> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/> + <!-- Permission required for CTS test - NetworkStatsIntegrationTest --> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/> <application android:debuggable="true"> <uses-library android:name="android.test.runner"/> diff --git a/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt new file mode 100644 index 0000000000..765e56e114 --- /dev/null +++ b/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2023 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.net.integrationtests + +import android.Manifest.permission.MANAGE_TEST_NETWORKS +import android.annotation.TargetApi +import android.app.usage.NetworkStats +import android.app.usage.NetworkStats.Bucket +import android.app.usage.NetworkStats.Bucket.TAG_NONE +import android.app.usage.NetworkStatsManager +import android.content.Context +import android.net.ConnectivityManager +import android.net.ConnectivityManager.TYPE_TEST +import android.net.InetAddresses +import android.net.IpPrefix +import android.net.LinkAddress +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.net.NetworkTemplate +import android.net.NetworkTemplate.MATCH_TEST +import android.net.TestNetworkSpecifier +import android.net.TrafficStats +import android.os.Build +import android.os.Process +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.net.integrationtests.NetworkStatsIntegrationTest.Direction.DOWNLOAD +import com.android.server.net.integrationtests.NetworkStatsIntegrationTest.Direction.UPLOAD +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.PacketBridge +import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged +import com.android.testutils.TestDnsServer +import com.android.testutils.TestHttpServer +import com.android.testutils.TestableNetworkCallback +import com.android.testutils.runAsShell +import fi.iki.elonen.NanoHTTPD +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.net.HttpURLConnection +import java.net.HttpURLConnection.HTTP_OK +import java.net.InetSocketAddress +import java.net.URL +import java.nio.charset.Charset +import kotlin.math.ceil +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.junit.After +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +private const val TEST_TAG = 0xF00D + +@RunWith(DevSdkIgnoreRunner::class) +@TargetApi(Build.VERSION_CODES.S) +@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) +class NetworkStatsIntegrationTest { + private val TAG = NetworkStatsIntegrationTest::class.java.simpleName + private val LOCAL_V6ADDR = + LinkAddress(InetAddresses.parseNumericAddress("2001:db8::1234"), 64) + + // Remote address, both the client and server will have a hallucination that + // they are talking to this address. + private val REMOTE_V6ADDR = + LinkAddress(InetAddresses.parseNumericAddress("dead:beef::808:808"), 64) + private val REMOTE_V4ADDR = + LinkAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 32) + private val DEFAULT_MTU = 1500 + private val DEFAULT_BUFFER_SIZE = 1500 // Any size greater than or equal to mtu + private val CONNECTION_TIMEOUT_MILLIS = 15000 + private val TEST_DOWNLOAD_SIZE = 10000L + private val TEST_UPLOAD_SIZE = 20000L + private val HTTP_SERVER_NAME = "test.com" + private val HTTP_SERVER_PORT = 8080 // Use port > 1024 to avoid restrictions on system ports + private val DNS_INTERNAL_SERVER_PORT = 53 + private val DNS_EXTERNAL_SERVER_PORT = 1053 + private val TCP_ACK_SIZE = 72 + + // Packet overheads that are not part of the actual data transmission, these + // include DNS packets, TCP handshake/termination packets, and HTTP header + // packets. These overheads were gathered from real samples and may not + // be perfectly accurate because of DNS caches and TCP retransmissions, etc. + private val CONSTANT_PACKET_OVERHEAD = 8 + + // 130 is an observed average. + private val CONSTANT_BYTES_OVERHEAD = 130 * CONSTANT_PACKET_OVERHEAD + private val TOLERANCE = 1.3 + + // Set up the packet bridge with two IPv6 address only test networks. + private val inst = InstrumentationRegistry.getInstrumentation() + private val context = inst.getContext() + private val packetBridge = runAsShell(MANAGE_TEST_NETWORKS) { + PacketBridge( + context, + listOf(LOCAL_V6ADDR), + REMOTE_V6ADDR.address, + listOf( + Pair(DNS_INTERNAL_SERVER_PORT, DNS_EXTERNAL_SERVER_PORT) + ) + ) + } + private val cm = context.getSystemService(ConnectivityManager::class.java)!! + + // Set up DNS server for testing server and DNS64. + private val fakeDns = TestDnsServer( + packetBridge.externalNetwork, + InetSocketAddress(LOCAL_V6ADDR.address, DNS_EXTERNAL_SERVER_PORT) + ).apply { + start() + setAnswer( + "ipv4only.arpa", + listOf(IpPrefix(REMOTE_V6ADDR.address, REMOTE_V6ADDR.prefixLength).address) + ) + setAnswer(HTTP_SERVER_NAME, listOf(REMOTE_V4ADDR.address)) + } + + // Start up test http server. + private val httpServer = TestHttpServer( + LOCAL_V6ADDR.address.hostAddress, + HTTP_SERVER_PORT + ).apply { + start() + } + + @Before + fun setUp() { + assumeTrue(shouldRunTests()) + packetBridge.start() + } + + // For networkstack tests, it is not guaranteed that the tethering module will be + // updated at the same time. If the tethering module is not new enough, it may not contain + // the necessary abilities to run these tests. For example, The tests depends on test + // network stats being counted, which can only be achieved when they are marked as TYPE_TEST. + // If the tethering module does not support TYPE_TEST stats, then these tests will need + // to be skipped. + fun shouldRunTests() = cm.getNetworkInfo(packetBridge.internalNetwork)!!.type == TYPE_TEST + + @After + fun tearDown() { + packetBridge.stop() + fakeDns.stop() + httpServer.stop() + } + + private fun waitFor464XlatReady(network: Network): String { + val iface = cm.getLinkProperties(network)!!.interfaceName!! + + // Make a network request to listen to the specific test network. + val nr = NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_TEST) + .setNetworkSpecifier(TestNetworkSpecifier(iface)) + .build() + val testCb = TestableNetworkCallback() + cm.registerNetworkCallback(nr, testCb) + + // Wait for the stacked address to be available. + testCb.eventuallyExpect<LinkPropertiesChanged> { + it.lp.stackedLinks.getOrNull(0)?.linkAddresses?.getOrNull(0) != null + } + + return iface + } + + private val Network.mtu: Int get() { + val lp = cm.getLinkProperties(this)!! + val mtuStacked = if (lp.stackedLinks[0]?.mtu != 0) lp.stackedLinks[0].mtu else DEFAULT_MTU + val mtuInterface = if (lp.mtu != 0) lp.mtu else DEFAULT_MTU + return mtuInterface.coerceAtMost(mtuStacked) + } + + /** + * Verify data usage download stats with test 464xlat networks. + * + * This test starts two test networks and binds them together, the internal one is for the + * client to make http traffic on the test network, and the external one is for the mocked + * http and dns server to bind to and provide responses. + * + * After Clat setup, the client will use clat v4 address to send packets to the mocked + * server v4 address, which will be translated into a v6 packet by the clat daemon with + * NAT64 prefix learned from the mocked DNS64 response. And send to the interface. + * + * While the packets are being forwarded to the external interface, the servers will see + * the packets originated from the mocked v6 address, and destined to a local v6 address. + */ + @Test + fun test464XlatTcpStats() { + // Wait for 464Xlat to be ready. + val internalInterfaceName = waitFor464XlatReady(packetBridge.internalNetwork) + val mtu = packetBridge.internalNetwork.mtu + + val snapshotBeforeTest = StatsSnapshot(context, internalInterfaceName) + + // Generate the download traffic. + genHttpTraffic(packetBridge.internalNetwork, uploadSize = 0L, TEST_DOWNLOAD_SIZE) + + // In practice, for one way 10k download payload, the download usage is about + // 11222~12880 bytes, with 14~17 packets. And the upload usage is about 1279~1626 bytes + // with 14~17 packets, which is majorly contributed by TCP ACK packets. + val snapshotAfterDownload = StatsSnapshot(context, internalInterfaceName) + val (expectedDownloadLower, expectedDownloadUpper) = getExpectedStatsBounds( + TEST_DOWNLOAD_SIZE, + mtu, + DOWNLOAD + ) + assertOnlyNonTaggedStatsIncreases( + snapshotBeforeTest, + snapshotAfterDownload, + expectedDownloadLower, + expectedDownloadUpper + ) + + // Generate upload traffic with tag to verify tagged data accounting as well. + genHttpTrafficWithTag( + packetBridge.internalNetwork, + TEST_UPLOAD_SIZE, + downloadSize = 0L, + TEST_TAG + ) + + // Verify upload data usage accounting. + val snapshotAfterUpload = StatsSnapshot(context, internalInterfaceName) + val (expectedUploadLower, expectedUploadUpper) = getExpectedStatsBounds( + TEST_UPLOAD_SIZE, + mtu, + UPLOAD + ) + assertAllStatsIncreases( + snapshotAfterDownload, + snapshotAfterUpload, + expectedUploadLower, + expectedUploadUpper + ) + } + + private enum class Direction { + DOWNLOAD, + UPLOAD + } + + private fun getExpectedStatsBounds( + transmittedSize: Long, + mtu: Int, + direction: Direction + ): Pair<BareStats, BareStats> { + // This is already an underestimated value since the input doesn't include TCP/IP + // layer overhead. + val txBytesLower = transmittedSize + // Include TCP/IP header overheads and retransmissions in the upper bound. + val txBytesUpper = (transmittedSize * TOLERANCE).toLong() + val txPacketsLower = txBytesLower / mtu + (CONSTANT_PACKET_OVERHEAD / TOLERANCE).toLong() + val estTransmissionPacketsUpper = ceil(txBytesUpper / mtu.toDouble()).toLong() + val txPacketsUpper = estTransmissionPacketsUpper + + (CONSTANT_PACKET_OVERHEAD * TOLERANCE).toLong() + // Assume ACK only sent once for the entire transmission. + val rxPacketsLower = 1L + (CONSTANT_PACKET_OVERHEAD / TOLERANCE).toLong() + // Assume ACK sent for every RX packet. + val rxPacketsUpper = txPacketsUpper + val rxBytesLower = 1L * TCP_ACK_SIZE + (CONSTANT_BYTES_OVERHEAD / TOLERANCE).toLong() + val rxBytesUpper = estTransmissionPacketsUpper * TCP_ACK_SIZE + + (CONSTANT_BYTES_OVERHEAD * TOLERANCE).toLong() + + return if (direction == UPLOAD) { + BareStats(rxBytesLower, rxPacketsLower, txBytesLower, txPacketsLower) to + BareStats(rxBytesUpper, rxPacketsUpper, txBytesUpper, txPacketsUpper) + } else { + BareStats(txBytesLower, txPacketsLower, rxBytesLower, rxPacketsLower) to + BareStats(txBytesUpper, txPacketsUpper, rxBytesUpper, rxPacketsUpper) + } + } + + private fun genHttpTraffic(network: Network, uploadSize: Long, downloadSize: Long) = + genHttpTrafficWithTag(network, uploadSize, downloadSize, NetworkStats.Bucket.TAG_NONE) + + private fun genHttpTrafficWithTag( + network: Network, + uploadSize: Long, + downloadSize: Long, + tag: Int + ) { + val path = "/test_upload_download" + val buf = ByteArray(DEFAULT_BUFFER_SIZE) + + httpServer.addResponse( + TestHttpServer.Request(path, NanoHTTPD.Method.POST), NanoHTTPD.Response.Status.OK, + content = getRandomString(downloadSize) + ) + var httpConnection: HttpURLConnection? = null + try { + TrafficStats.setThreadStatsTag(tag) + val spec = "http://$HTTP_SERVER_NAME:${httpServer.listeningPort}$path" + val url = URL(spec) + httpConnection = network.openConnection(url) as HttpURLConnection + httpConnection.connectTimeout = CONNECTION_TIMEOUT_MILLIS + httpConnection.requestMethod = "POST" + httpConnection.doOutput = true + // Tell the server that the response should not be compressed. Otherwise, the data usage + // accounted will be less than expected. + httpConnection.setRequestProperty("Accept-Encoding", "identity") + // Tell the server that to close connection after this request, this is needed to + // prevent from reusing the same socket that has different tagging requirement. + httpConnection.setRequestProperty("Connection", "close") + + // Send http body. + val outputStream = BufferedOutputStream(httpConnection.outputStream) + outputStream.write(getRandomString(uploadSize).toByteArray(Charset.forName("UTF-8"))) + outputStream.close() + assertEquals(HTTP_OK, httpConnection.responseCode) + + // Receive response from the server. + val inputStream = BufferedInputStream(httpConnection.getInputStream()) + var total = 0L + while (true) { + val count = inputStream.read(buf) + if (count == -1) break // End-of-Stream + total += count + } + assertEquals(downloadSize, total) + } finally { + httpConnection?.inputStream?.close() + TrafficStats.clearThreadStatsTag() + } + } + + // NetworkStats.Bucket cannot be written. So another class is needed to + // perform arithmetic operations. + data class BareStats( + val rxBytes: Long, + val rxPackets: Long, + val txBytes: Long, + val txPackets: Long + ) { + operator fun plus(other: BareStats): BareStats { + return BareStats( + this.rxBytes + other.rxBytes, this.rxPackets + other.rxPackets, + this.txBytes + other.txBytes, this.txPackets + other.txPackets + ) + } + + operator fun minus(other: BareStats): BareStats { + return BareStats( + this.rxBytes - other.rxBytes, this.rxPackets - other.rxPackets, + this.txBytes - other.txBytes, this.txPackets - other.txPackets + ) + } + + fun reverse(): BareStats = + BareStats( + rxBytes = txBytes, + rxPackets = txPackets, + txBytes = rxBytes, + txPackets = rxPackets + ) + + override fun toString(): String { + return "BareStats{rx/txBytes=$rxBytes/$txBytes, rx/txPackets=$rxPackets/$txPackets}" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BareStats) return false + + if (rxBytes != other.rxBytes) return false + if (rxPackets != other.rxPackets) return false + if (txBytes != other.txBytes) return false + if (txPackets != other.txPackets) return false + + return true + } + + override fun hashCode(): Int { + return (rxBytes * 11 + rxPackets * 13 + txBytes * 17 + txPackets * 19).toInt() + } + + companion object { + val EMPTY = BareStats(0L, 0L, 0L, 0L) + } + } + + data class StatsSnapshot(val context: Context, val iface: String) { + val statsSummary = getNetworkSummary(iface) + val statsUid = getUidDetail(iface, TAG_NONE) + val taggedSummary = getTaggedNetworkSummary(iface, TEST_TAG) + val taggedUid = getUidDetail(iface, TEST_TAG) + val trafficStatsIface = getTrafficStatsIface(iface) + val trafficStatsUid = getTrafficStatsUid(Process.myUid()) + + private fun getUidDetail(iface: String, tag: Int): BareStats { + return getNetworkStatsThat(iface, tag) { nsm, template -> + nsm.queryDetailsForUidTagState( + template, Long.MIN_VALUE, Long.MAX_VALUE, + Process.myUid(), tag, Bucket.STATE_ALL + ) + } + } + + private fun getNetworkSummary(iface: String): BareStats { + return getNetworkStatsThat(iface, TAG_NONE) { nsm, template -> + nsm.querySummary(template, Long.MIN_VALUE, Long.MAX_VALUE) + } + } + + private fun getTaggedNetworkSummary(iface: String, tag: Int): BareStats { + return getNetworkStatsThat(iface, tag) { nsm, template -> + nsm.queryTaggedSummary(template, Long.MIN_VALUE, Long.MAX_VALUE) + } + } + + private fun getNetworkStatsThat( + iface: String, + tag: Int, + queryApi: (nsm: NetworkStatsManager, template: NetworkTemplate) -> NetworkStats + ): BareStats { + val nsm = context.getSystemService(NetworkStatsManager::class.java)!! + nsm.forceUpdate() + val testTemplate = NetworkTemplate.Builder(MATCH_TEST) + .setWifiNetworkKeys(setOf(iface)).build() + val stats = queryApi.invoke(nsm, testTemplate) + val filteredBuckets = + stats.buckets().filter { it.uid == Process.myUid() && it.tag == tag } + return filteredBuckets.fold(BareStats.EMPTY) { acc, it -> + acc + BareStats( + it.rxBytes, + it.rxPackets, + it.txBytes, + it.txPackets + ) + } + } + + // Helper function to iterate buckets in app.usage.NetworkStats. + private fun NetworkStats.buckets() = object : Iterable<NetworkStats.Bucket> { + override fun iterator() = object : Iterator<NetworkStats.Bucket> { + override operator fun hasNext() = hasNextBucket() + override operator fun next() = + NetworkStats.Bucket().also { assertTrue(getNextBucket(it)) } + } + } + + private fun getTrafficStatsIface(iface: String): BareStats = BareStats( + TrafficStats.getRxBytes(iface), + TrafficStats.getRxPackets(iface), + TrafficStats.getTxBytes(iface), + TrafficStats.getTxPackets(iface) + ) + + private fun getTrafficStatsUid(uid: Int): BareStats = BareStats( + TrafficStats.getUidRxBytes(uid), + TrafficStats.getUidRxPackets(uid), + TrafficStats.getUidTxBytes(uid), + TrafficStats.getUidTxPackets(uid) + ) + } + + private fun assertAllStatsIncreases( + before: StatsSnapshot, + after: StatsSnapshot, + lower: BareStats, + upper: BareStats + ) { + assertNonTaggedStatsIncreases(before, after, lower, upper) + assertTaggedStatsIncreases(before, after, lower, upper) + } + + private fun assertOnlyNonTaggedStatsIncreases( + before: StatsSnapshot, + after: StatsSnapshot, + lower: BareStats, + upper: BareStats + ) { + assertNonTaggedStatsIncreases(before, after, lower, upper) + assertTaggedStatsEquals(before, after) + } + + private fun assertNonTaggedStatsIncreases( + before: StatsSnapshot, + after: StatsSnapshot, + lower: BareStats, + upper: BareStats + ) { + assertInRange( + "Unexpected iface traffic stats", + after.iface, + before.trafficStatsIface, after.trafficStatsIface, + lower, upper + ) + // Uid traffic stats are counted in both direction because the external network + // traffic is also attributed to the test uid. + assertInRange( + "Unexpected uid traffic stats", + after.iface, + before.trafficStatsUid, after.trafficStatsUid, + lower + lower.reverse(), upper + upper.reverse() + ) + assertInRange( + "Unexpected non-tagged summary stats", + after.iface, + before.statsSummary, after.statsSummary, + lower, upper + ) + assertInRange( + "Unexpected non-tagged uid stats", + after.iface, + before.statsUid, after.statsUid, + lower, upper + ) + } + + private fun assertTaggedStatsEquals(before: StatsSnapshot, after: StatsSnapshot) { + // Increment of tagged data should be zero since no tagged traffic was generated. + assertEquals( + before.taggedSummary, + after.taggedSummary, + "Unexpected tagged summary stats: ${after.iface}" + ) + assertEquals( + before.taggedUid, + after.taggedUid, + "Unexpected tagged uid stats: ${Process.myUid()} on ${after.iface}" + ) + } + + private fun assertTaggedStatsIncreases( + before: StatsSnapshot, + after: StatsSnapshot, + lower: BareStats, + upper: BareStats + ) { + assertInRange( + "Unexpected tagged summary stats", + after.iface, + before.taggedSummary, after.taggedSummary, + lower, + upper + ) + assertInRange( + "Unexpected tagged uid stats: ${Process.myUid()}", + after.iface, + before.taggedUid, after.taggedUid, + lower, + upper + ) + } + + /** Verify the given BareStats is in range [lower, upper] */ + private fun assertInRange( + tag: String, + iface: String, + before: BareStats, + after: BareStats, + lower: BareStats, + upper: BareStats + ) { + // Passing the value after operation and the value before operation to dump the actual + // numbers if it fails. + assertTrue(checkInRange(before, after, lower, upper), + "$tag on $iface: $after - $before is not within range [$lower, $upper]" + ) + } + + private fun checkInRange( + before: BareStats, + after: BareStats, + lower: BareStats, + upper: BareStats + ): Boolean { + val value = after - before + return value.rxBytes in lower.rxBytes..upper.rxBytes && + value.rxPackets in lower.rxPackets..upper.rxPackets && + value.txBytes in lower.txBytes..upper.txBytes && + value.txPackets in lower.txPackets..upper.txPackets + } + + fun getRandomString(length: Long): String { + val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') + return (1..length) + .map { allowedChars.random() } + .joinToString("") + } +} diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 4a1298f86f..20d457ff2c 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp @@ -66,13 +66,11 @@ filegroup { "java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt", "java/com/android/internal/net/NetworkUtilsInternalTest.java", "java/com/android/internal/net/VpnProfileTest.java", - "java/com/android/server/VpnManagerServiceTest.java", "java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java", "java/com/android/server/connectivity/IpConnectivityMetricsTest.java", "java/com/android/server/connectivity/MetricsTestUtil.java", "java/com/android/server/connectivity/MultipathPolicyTrackerTest.java", "java/com/android/server/connectivity/NetdEventListenerServiceTest.java", - "java/com/android/server/connectivity/VpnTest.java", "java/com/android/server/net/ipmemorystore/*.java", ], } diff --git a/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt new file mode 100644 index 0000000000..332f2a3384 --- /dev/null +++ b/tests/unit/java/android/net/nsd/AdvertisingRequestTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 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 android.net.nsd + +import android.net.nsd.AdvertisingRequest.NSD_ADVERTISING_UPDATE_ONLY +import android.net.nsd.NsdManager.PROTOCOL_DNS_SD +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.ConnectivityModuleTest +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.parcelingRoundTrip +import java.time.Duration +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNull +import org.junit.Test +import org.junit.runner.RunWith + +// TODO: move this class to CTS tests when AdvertisingRequest is made public +/** Unit tests for {@link AdvertisingRequest}. */ +@IgnoreUpTo(Build.VERSION_CODES.S_V2) +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +@ConnectivityModuleTest +class AdvertisingRequestTest { + @Test + fun testParcelingIsLossLess() { + val info = NsdServiceInfo().apply { + serviceType = "_ipp._tcp" + } + val beforeParcel = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD) + .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY) + .setTtl(Duration.ofSeconds(30L)) + .build() + + val afterParcel = parcelingRoundTrip(beforeParcel) + + assertEquals(beforeParcel.serviceInfo.serviceType, afterParcel.serviceInfo.serviceType) + assertEquals(beforeParcel.advertisingConfig, afterParcel.advertisingConfig) + } + +@Test +fun testBuilder_setNullTtl_success() { + val info = NsdServiceInfo().apply { + serviceType = "_ipp._tcp" + } + val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD) + .setTtl(null) + .build() + + assertNull(request.ttl) +} + + @Test + fun testBuilder_setPropertiesSuccess() { + val info = NsdServiceInfo().apply { + serviceType = "_ipp._tcp" + } + val request = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD) + .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY) + .setTtl(Duration.ofSeconds(100L)) + .build() + + assertEquals("_ipp._tcp", request.serviceInfo.serviceType) + assertEquals(PROTOCOL_DNS_SD, request.protocolType) + assertEquals(NSD_ADVERTISING_UPDATE_ONLY, request.advertisingConfig) + assertEquals(Duration.ofSeconds(100L), request.ttl) + } + + @Test + fun testEquality() { + val info = NsdServiceInfo().apply { + serviceType = "_ipp._tcp" + } + val request1 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD).build() + val request2 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD).build() + val request3 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD) + .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY) + .setTtl(Duration.ofSeconds(120L)) + .build() + val request4 = AdvertisingRequest.Builder(info, PROTOCOL_DNS_SD) + .setAdvertisingConfig(NSD_ADVERTISING_UPDATE_ONLY) + .setTtl(Duration.ofSeconds(120L)) + .build() + + assertEquals(request1, request2) + assertEquals(request3, request4) + assertNotEquals(request1, request3) + assertNotEquals(request2, request4) + } +} diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java index 951675ca79..76a649e58a 100644 --- a/tests/unit/java/android/net/nsd/NsdManagerTest.java +++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -54,6 +55,7 @@ import org.mockito.MockitoAnnotations; import java.net.InetAddress; import java.util.List; +import java.time.Duration; @DevSdkIgnoreRunner.MonitorThreadLeak @RunWith(DevSdkIgnoreRunner.class) @@ -224,6 +226,23 @@ public class NsdManagerTest { verify(listener, timeout(mTimeoutMs).times(1)).onServiceRegistered(request); } + @Test + public void testRegisterServiceWithCustomTtl() throws Exception { + final NsdManager manager = mManager; + final NsdServiceInfo info = new NsdServiceInfo("another_name2", "another_type2"); + info.setPort(2203); + final AdvertisingRequest request = new AdvertisingRequest.Builder(info, PROTOCOL) + .setTtl(Duration.ofSeconds(30)).build(); + final NsdManager.RegistrationListener listener = mock( + NsdManager.RegistrationListener.class); + + manager.registerService(request, Runnable::run, listener); + + AdvertisingRequest capturedRequest = getAdvertisingRequest( + req -> verify(mServiceConn).registerService(anyInt(), req.capture())); + assertEquals(request, capturedRequest); + } + private void doTestRegisterService() throws Exception { NsdManager manager = mManager; @@ -501,4 +520,12 @@ public class NsdManagerTest { verifier.accept(captor); return captor.getValue(); } + + AdvertisingRequest getAdvertisingRequest( + ThrowingConsumer<ArgumentCaptor<AdvertisingRequest>> verifier) throws Exception { + final ArgumentCaptor<AdvertisingRequest> captor = + ArgumentCaptor.forClass(AdvertisingRequest.class); + verifier.accept(captor); + return captor.getValue(); + } } diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java index 624855e218..881de56ede 100644 --- a/tests/unit/java/com/android/server/NsdServiceTest.java +++ b/tests/unit/java/com/android/server/NsdServiceTest.java @@ -83,6 +83,7 @@ import android.net.mdns.aidl.GetAddressInfo; import android.net.mdns.aidl.IMDnsEventListener; import android.net.mdns.aidl.RegistrationInfo; import android.net.mdns.aidl.ResolutionInfo; +import android.net.nsd.AdvertisingRequest; import android.net.nsd.INsdManagerCallback; import android.net.nsd.INsdServiceConnector; import android.net.nsd.MDnsManager; @@ -101,6 +102,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.util.Pair; @@ -110,6 +112,7 @@ import androidx.test.filters.SmallTest; import com.android.metrics.NetworkNsdReportedMetrics; import com.android.server.NsdService.Dependencies; import com.android.server.connectivity.mdns.MdnsAdvertiser; +import com.android.server.connectivity.mdns.MdnsAdvertisingOptions; import com.android.server.connectivity.mdns.MdnsDiscoveryManager; import com.android.server.connectivity.mdns.MdnsInterfaceSocket; import com.android.server.connectivity.mdns.MdnsSearchOptions; @@ -137,6 +140,8 @@ import org.mockito.MockitoAnnotations; import java.net.InetAddress; import java.net.UnknownHostException; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -971,7 +976,8 @@ public class NsdServiceTest { List.of() /* textStrings */, List.of() /* textEntries */, 1234, - network); + network, + Instant.MAX /* expirationTime */); // Callbacks for query sent. listener.onDiscoveryQuerySent(Collections.emptyList(), 1 /* transactionId */); @@ -1001,7 +1007,8 @@ public class NsdServiceTest { List.of() /* textStrings */, List.of() /* textEntries */, 1234, - network); + network, + Instant.MAX /* expirationTime */); // Verify onServiceUpdated callback. listener.onServiceUpdated(updatedServiceInfo); @@ -1133,7 +1140,8 @@ public class NsdServiceTest { List.of(), /* textStrings */ List.of(), /* textEntries */ 1234, /* interfaceIndex */ - network); + network, + Instant.MAX /* expirationTime */); // Verify onServiceNameDiscovered callback listener.onServiceNameDiscovered(foundInfo, false /* isServiceFromCache */); @@ -1154,7 +1162,8 @@ public class NsdServiceTest { null, /* textStrings */ null, /* textEntries */ 1234, /* interfaceIndex */ - network); + network, + Instant.MAX /* expirationTime */); // Verify onServiceNameRemoved callback listener.onServiceNameRemoved(removedInfo); verify(discListener, timeout(TIMEOUT_MS)).onServiceLost(argThat(info -> @@ -1276,7 +1285,8 @@ public class NsdServiceTest { List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{ 'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */, 1234, - network); + network, + Instant.ofEpochSecond(1000_000L) /* expirationTime */); // Verify onServiceFound callback doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime(); @@ -1301,6 +1311,7 @@ public class NsdServiceTest { assertTrue(info.getHostAddresses().stream().anyMatch( address -> address.equals(parseNumericAddress("2001:db8::2")))); assertEquals(network, info.getNetwork()); + assertEquals(Instant.ofEpochSecond(1000_000L), info.getExpirationTime()); // Verify the listener has been unregistered. verify(mDiscoveryManager, timeout(TIMEOUT_MS)) @@ -1518,6 +1529,82 @@ public class NsdServiceTest { } @Test + public void testAdvertiseCustomTtl_validTtl_success() { + runValidTtlAdvertisingTest(30L); + runValidTtlAdvertisingTest(10 * 3600L); + } + + @Test + public void testAdvertiseCustomTtl_ttlSmallerThan30SecondsButClientIsSystemServer_success() { + when(mDeps.getCallingUid()).thenReturn(Process.SYSTEM_UID); + + runValidTtlAdvertisingTest(29L); + } + + @Test + public void testAdvertiseCustomTtl_ttlLargerThan10HoursButClientIsSystemServer_success() { + when(mDeps.getCallingUid()).thenReturn(Process.SYSTEM_UID); + + runValidTtlAdvertisingTest(10 * 3600L + 1); + runValidTtlAdvertisingTest(0xffffffffL); + } + + private void runValidTtlAdvertisingTest(long validTtlSeconds) { + setMdnsAdvertiserEnabled(); + + final NsdManager client = connectClient(mService); + final RegistrationListener regListener = mock(RegistrationListener.class); + final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor = + ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class); + verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any()); + + final NsdServiceInfo regInfo = new NsdServiceInfo("Service custom TTL", SERVICE_TYPE); + regInfo.setPort(1234); + final AdvertisingRequest request = + new AdvertisingRequest.Builder(regInfo, NsdManager.PROTOCOL_DNS_SD) + .setTtl(Duration.ofSeconds(validTtlSeconds)).build(); + + client.registerService(request, Runnable::run, regListener); + waitForIdle(); + + final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class); + final MdnsAdvertisingOptions expectedAdverstingOptions = + MdnsAdvertisingOptions.newBuilder().setTtl(request.getTtl()).build(); + verify(mAdvertiser).addOrUpdateService(idCaptor.capture(), any(), + eq(expectedAdverstingOptions), anyInt()); + + // Verify onServiceRegistered callback + final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue(); + final int regId = idCaptor.getValue(); + cb.onRegisterServiceSucceeded(regId, regInfo); + + verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered( + argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null)))); + } + + @Test + public void testAdvertiseCustomTtl_invalidTtl_FailsWithBadParameters() { + setMdnsAdvertiserEnabled(); + final long invalidTtlSeconds = 29L; + final NsdManager client = connectClient(mService); + final RegistrationListener regListener = mock(RegistrationListener.class); + final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor = + ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class); + verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any(), any(), any()); + + final NsdServiceInfo regInfo = new NsdServiceInfo("Service custom TTL", SERVICE_TYPE); + regInfo.setPort(1234); + final AdvertisingRequest request = + new AdvertisingRequest.Builder(regInfo, NsdManager.PROTOCOL_DNS_SD) + .setTtl(Duration.ofSeconds(invalidTtlSeconds)).build(); + client.registerService(request, Runnable::run, regListener); + waitForIdle(); + + verify(regListener, timeout(TIMEOUT_MS)) + .onRegistrationFailed(any(), eq(FAILURE_BAD_PARAMETERS)); + } + + @Test public void testStopServiceResolutionWithMdnsDiscoveryManager() { setMdnsDiscoveryManagerEnabled(); diff --git a/tests/unit/java/com/android/server/VpnManagerServiceTest.java b/tests/unit/java/com/android/server/VpnManagerServiceTest.java deleted file mode 100644 index bf23cd192c..0000000000 --- a/tests/unit/java/com/android/server/VpnManagerServiceTest.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright (C) 2022 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; - -import static android.os.Build.VERSION_CODES.R; - -import static com.android.testutils.ContextUtils.mockService; -import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; -import static com.android.testutils.MiscAsserts.assertThrows; - -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.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.annotation.UserIdInt; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.ConnectivityManager; -import android.net.INetd; -import android.net.Uri; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.INetworkManagementService; -import android.os.Looper; -import android.os.UserHandle; -import android.os.UserManager; -import android.security.Credentials; - -import androidx.test.filters.SmallTest; - -import com.android.internal.net.VpnProfile; -import com.android.server.connectivity.Vpn; -import com.android.server.connectivity.VpnProfileStore; -import com.android.server.net.LockdownVpnTracker; -import com.android.testutils.DevSdkIgnoreRule; -import com.android.testutils.DevSdkIgnoreRunner; -import com.android.testutils.HandlerUtils; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -@RunWith(DevSdkIgnoreRunner.class) -@IgnoreUpTo(R) // VpnManagerService is not available before R -@SmallTest -public class VpnManagerServiceTest extends VpnTestBase { - private static final String CONTEXT_ATTRIBUTION_TAG = "VPN_MANAGER"; - - @Rule - public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); - - private static final int TIMEOUT_MS = 2_000; - - @Mock Context mContext; - @Mock Context mContextWithoutAttributionTag; - @Mock Context mSystemContext; - @Mock Context mUserAllContext; - private HandlerThread mHandlerThread; - @Mock private Vpn mVpn; - @Mock private INetworkManagementService mNms; - @Mock private ConnectivityManager mCm; - @Mock private UserManager mUserManager; - @Mock private INetd mNetd; - @Mock private PackageManager mPackageManager; - @Mock private VpnProfileStore mVpnProfileStore; - @Mock private LockdownVpnTracker mLockdownVpnTracker; - - private VpnManagerServiceDependencies mDeps; - private VpnManagerService mService; - private BroadcastReceiver mUserPresentReceiver; - private BroadcastReceiver mIntentReceiver; - private final String mNotMyVpnPkg = "com.not.my.vpn"; - - class VpnManagerServiceDependencies extends VpnManagerService.Dependencies { - @Override - public HandlerThread makeHandlerThread() { - return mHandlerThread; - } - - @Override - public INetworkManagementService getINetworkManagementService() { - return mNms; - } - - @Override - public INetd getNetd() { - return mNetd; - } - - @Override - public Vpn createVpn(Looper looper, Context context, INetworkManagementService nms, - INetd netd, @UserIdInt int userId) { - return mVpn; - } - - @Override - public VpnProfileStore getVpnProfileStore() { - return mVpnProfileStore; - } - - @Override - public LockdownVpnTracker createLockDownVpnTracker(Context context, Handler handler, - Vpn vpn, VpnProfile profile) { - return mLockdownVpnTracker; - } - - @Override - public @UserIdInt int getMainUserId() { - return UserHandle.USER_SYSTEM; - } - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mHandlerThread = new HandlerThread("TestVpnManagerService"); - mDeps = new VpnManagerServiceDependencies(); - - // The attribution tag is a dependency for IKE library to collect VPN metrics correctly - // and thus should not be changed without updating the IKE code. - doReturn(mContext) - .when(mContextWithoutAttributionTag) - .createAttributionContext(CONTEXT_ATTRIBUTION_TAG); - - doReturn(mUserAllContext).when(mContext).createContextAsUser(UserHandle.ALL, 0); - doReturn(mSystemContext).when(mContext).createContextAsUser(UserHandle.SYSTEM, 0); - doReturn(mPackageManager).when(mContext).getPackageManager(); - setMockedPackages(mPackageManager, sPackages); - - mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm); - mockService(mContext, UserManager.class, Context.USER_SERVICE, mUserManager); - doReturn(SYSTEM_USER).when(mUserManager).getUserInfo(eq(SYSTEM_USER_ID)); - - mService = new VpnManagerService(mContextWithoutAttributionTag, mDeps); - mService.systemReady(); - - final ArgumentCaptor<BroadcastReceiver> intentReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - final ArgumentCaptor<BroadcastReceiver> userPresentReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mSystemContext).registerReceiver( - userPresentReceiverCaptor.capture(), any(), any(), any()); - verify(mUserAllContext, times(2)).registerReceiver( - intentReceiverCaptor.capture(), any(), any(), any()); - mUserPresentReceiver = userPresentReceiverCaptor.getValue(); - mIntentReceiver = intentReceiverCaptor.getValue(); - - // Add user to create vpn in mVpn - onUserStarted(SYSTEM_USER_ID); - assertNotNull(mService.mVpns.get(SYSTEM_USER_ID)); - } - - @Test - public void testUpdateAppExclusionList() { - // Start vpn - mService.startVpnProfile(TEST_VPN_PKG); - verify(mVpn).startVpnProfile(eq(TEST_VPN_PKG)); - - // Remove package due to package replaced. - onPackageRemoved(PKGS[0], PKG_UIDS[0], true /* isReplacing */); - verify(mVpn, never()).refreshPlatformVpnAppExclusionList(); - - // Add package due to package replaced. - onPackageAdded(PKGS[0], PKG_UIDS[0], true /* isReplacing */); - verify(mVpn, never()).refreshPlatformVpnAppExclusionList(); - - // Remove package - onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */); - verify(mVpn).refreshPlatformVpnAppExclusionList(); - - // Add the package back - onPackageAdded(PKGS[0], PKG_UIDS[0], false /* isReplacing */); - verify(mVpn, times(2)).refreshPlatformVpnAppExclusionList(); - } - - @Test - public void testStartVpnProfileFromDiffPackage() { - assertThrows( - SecurityException.class, () -> mService.startVpnProfile(mNotMyVpnPkg)); - } - - @Test - public void testStopVpnProfileFromDiffPackage() { - assertThrows(SecurityException.class, () -> mService.stopVpnProfile(mNotMyVpnPkg)); - } - - @Test - public void testGetProvisionedVpnProfileStateFromDiffPackage() { - assertThrows(SecurityException.class, () -> - mService.getProvisionedVpnProfileState(mNotMyVpnPkg)); - } - - @Test - public void testGetProvisionedVpnProfileState() { - mService.getProvisionedVpnProfileState(TEST_VPN_PKG); - verify(mVpn).getProvisionedVpnProfileState(TEST_VPN_PKG); - } - - private Intent buildIntent(String action, String packageName, int userId, int uid, - boolean isReplacing) { - final Intent intent = new Intent(action); - intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - intent.putExtra(Intent.EXTRA_UID, uid); - intent.putExtra(Intent.EXTRA_REPLACING, isReplacing); - if (packageName != null) { - intent.setData(Uri.fromParts("package" /* scheme */, packageName, null /* fragment */)); - } - - return intent; - } - - private void sendIntent(Intent intent) { - sendIntent(mIntentReceiver, mContext, intent); - } - - private void sendIntent(BroadcastReceiver receiver, Context context, Intent intent) { - final Handler h = mHandlerThread.getThreadHandler(); - - // Send in handler thread. - h.post(() -> receiver.onReceive(context, intent)); - HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS); - } - - private void onUserStarted(int userId) { - sendIntent(buildIntent(Intent.ACTION_USER_STARTED, - null /* packageName */, userId, -1 /* uid */, false /* isReplacing */)); - } - - private void onUserUnlocked(int userId) { - sendIntent(buildIntent(Intent.ACTION_USER_UNLOCKED, - null /* packageName */, userId, -1 /* uid */, false /* isReplacing */)); - } - - private void onUserStopped(int userId) { - sendIntent(buildIntent(Intent.ACTION_USER_STOPPED, - null /* packageName */, userId, -1 /* uid */, false /* isReplacing */)); - } - - private void onLockDownReset() { - sendIntent(buildIntent(LockdownVpnTracker.ACTION_LOCKDOWN_RESET, null /* packageName */, - UserHandle.USER_SYSTEM, -1 /* uid */, false /* isReplacing */)); - } - - private void onPackageAdded(String packageName, int userId, int uid, boolean isReplacing) { - sendIntent(buildIntent(Intent.ACTION_PACKAGE_ADDED, packageName, userId, uid, isReplacing)); - } - - private void onPackageAdded(String packageName, int uid, boolean isReplacing) { - onPackageAdded(packageName, UserHandle.USER_SYSTEM, uid, isReplacing); - } - - private void onPackageRemoved(String packageName, int userId, int uid, boolean isReplacing) { - sendIntent(buildIntent(Intent.ACTION_PACKAGE_REMOVED, packageName, userId, uid, - isReplacing)); - } - - private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { - onPackageRemoved(packageName, UserHandle.USER_SYSTEM, uid, isReplacing); - } - - @Test - public void testReceiveIntentFromNonHandlerThread() { - assertThrows(IllegalStateException.class, () -> - mIntentReceiver.onReceive(mContext, buildIntent(Intent.ACTION_PACKAGE_REMOVED, - PKGS[0], UserHandle.USER_SYSTEM, PKG_UIDS[0], true /* isReplacing */))); - - assertThrows(IllegalStateException.class, () -> - mUserPresentReceiver.onReceive(mContext, new Intent(Intent.ACTION_USER_PRESENT))); - } - - private void setupLockdownVpn(String packageName) { - final byte[] profileTag = packageName.getBytes(StandardCharsets.UTF_8); - doReturn(profileTag).when(mVpnProfileStore).get(Credentials.LOCKDOWN_VPN); - } - - private void setupVpnProfile(String profileName) { - final VpnProfile profile = new VpnProfile(profileName); - profile.name = profileName; - profile.server = "192.0.2.1"; - profile.dnsServers = "8.8.8.8"; - profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK; - final byte[] encodedProfile = profile.encode(); - doReturn(encodedProfile).when(mVpnProfileStore).get(Credentials.VPN + profileName); - } - - @Test - public void testUserPresent() { - // Verify that LockDownVpnTracker is not created. - verify(mLockdownVpnTracker, never()).init(); - - setupLockdownVpn(TEST_VPN_PKG); - setupVpnProfile(TEST_VPN_PKG); - - // mUserPresentReceiver only registers ACTION_USER_PRESENT intent and does no verification - // on action, so an empty intent is enough. - sendIntent(mUserPresentReceiver, mSystemContext, new Intent()); - - verify(mLockdownVpnTracker).init(); - verify(mSystemContext).unregisterReceiver(mUserPresentReceiver); - verify(mUserAllContext, never()).unregisterReceiver(any()); - } - - @Test - public void testUpdateLockdownVpn() { - setupLockdownVpn(TEST_VPN_PKG); - onUserUnlocked(SYSTEM_USER_ID); - - // Will not create lockDownVpnTracker w/o valid profile configured in the keystore - verify(mLockdownVpnTracker, never()).init(); - - setupVpnProfile(TEST_VPN_PKG); - - // Remove the user from mVpns - onUserStopped(SYSTEM_USER_ID); - onUserUnlocked(SYSTEM_USER_ID); - verify(mLockdownVpnTracker, never()).init(); - - // Add user back - onUserStarted(SYSTEM_USER_ID); - verify(mLockdownVpnTracker).init(); - - // Trigger another update. The existing LockDownVpnTracker should be shut down and - // initialize another one. - onUserUnlocked(SYSTEM_USER_ID); - verify(mLockdownVpnTracker).shutdown(); - verify(mLockdownVpnTracker, times(2)).init(); - } - - @Test - public void testLockdownReset() { - // Init LockdownVpnTracker - setupLockdownVpn(TEST_VPN_PKG); - setupVpnProfile(TEST_VPN_PKG); - onUserUnlocked(SYSTEM_USER_ID); - verify(mLockdownVpnTracker).init(); - - onLockDownReset(); - verify(mLockdownVpnTracker).reset(); - } - - @Test - public void testLockdownResetWhenLockdownVpnTrackerIsNotInit() { - setupLockdownVpn(TEST_VPN_PKG); - setupVpnProfile(TEST_VPN_PKG); - - onLockDownReset(); - - // LockDownVpnTracker is not created. Lockdown reset will not take effect. - verify(mLockdownVpnTracker, never()).reset(); - } - - @Test - public void testIsVpnLockdownEnabled() { - // Vpn is created but the VPN lockdown is not enabled. - assertFalse(mService.isVpnLockdownEnabled(SYSTEM_USER_ID)); - - // Set lockdown for the SYSTEM_USER_ID VPN. - doReturn(true).when(mVpn).getLockdown(); - assertTrue(mService.isVpnLockdownEnabled(SYSTEM_USER_ID)); - - // Even lockdown is enabled but no Vpn is created for SECONDARY_USER. - assertFalse(mService.isVpnLockdownEnabled(SECONDARY_USER.id)); - } - - @Test - public void testGetVpnLockdownAllowlist() { - doReturn(null).when(mVpn).getLockdownAllowlist(); - assertNull(mService.getVpnLockdownAllowlist(SYSTEM_USER_ID)); - - final List<String> expected = List.of(PKGS); - doReturn(expected).when(mVpn).getLockdownAllowlist(); - assertEquals(expected, mService.getVpnLockdownAllowlist(SYSTEM_USER_ID)); - - // Even lockdown is enabled but no Vpn is created for SECONDARY_USER. - assertNull(mService.getVpnLockdownAllowlist(SECONDARY_USER.id)); - } -} diff --git a/tests/unit/java/com/android/server/VpnTestBase.java b/tests/unit/java/com/android/server/VpnTestBase.java deleted file mode 100644 index 6113872e21..0000000000 --- a/tests/unit/java/com/android/server/VpnTestBase.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2022 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; - -import static android.content.pm.UserInfo.FLAG_ADMIN; -import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; -import static android.content.pm.UserInfo.FLAG_PRIMARY; -import static android.content.pm.UserInfo.FLAG_RESTRICTED; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; - -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.os.Process; -import android.os.UserHandle; -import android.util.ArrayMap; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** Common variables or methods shared between VpnTest and VpnManagerServiceTest. */ -public class VpnTestBase { - protected static final String TEST_VPN_PKG = "com.testvpn.vpn"; - /** - * Names and UIDs for some fake packages. Important points: - * - UID is ordered increasing. - * - One pair of packages have consecutive UIDs. - */ - protected static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"}; - protected static final int[] PKG_UIDS = {10066, 10077, 10078, 10400}; - // Mock packages - protected static final Map<String, Integer> sPackages = new ArrayMap<>(); - static { - for (int i = 0; i < PKGS.length; i++) { - sPackages.put(PKGS[i], PKG_UIDS[i]); - } - sPackages.put(TEST_VPN_PKG, Process.myUid()); - } - - // Mock users - protected static final int SYSTEM_USER_ID = 0; - protected static final UserInfo SYSTEM_USER = new UserInfo(0, "system", UserInfo.FLAG_PRIMARY); - protected static final UserInfo PRIMARY_USER = new UserInfo(27, "Primary", - FLAG_ADMIN | FLAG_PRIMARY); - protected static final UserInfo SECONDARY_USER = new UserInfo(15, "Secondary", FLAG_ADMIN); - protected static final UserInfo RESTRICTED_PROFILE_A = new UserInfo(40, "RestrictedA", - FLAG_RESTRICTED); - protected static final UserInfo RESTRICTED_PROFILE_B = new UserInfo(42, "RestrictedB", - FLAG_RESTRICTED); - protected static final UserInfo MANAGED_PROFILE_A = new UserInfo(45, "ManagedA", - FLAG_MANAGED_PROFILE); - static { - RESTRICTED_PROFILE_A.restrictedProfileParentId = PRIMARY_USER.id; - RESTRICTED_PROFILE_B.restrictedProfileParentId = SECONDARY_USER.id; - MANAGED_PROFILE_A.profileGroupId = PRIMARY_USER.id; - } - - // Populate a fake packageName-to-UID mapping. - protected void setMockedPackages(PackageManager mockPm, final Map<String, Integer> packages) { - try { - doAnswer(invocation -> { - final String appName = (String) invocation.getArguments()[0]; - final int userId = (int) invocation.getArguments()[1]; - - final Integer appId = packages.get(appName); - if (appId == null) { - throw new PackageManager.NameNotFoundException(appName); - } - - return UserHandle.getUid(userId, appId); - }).when(mockPm).getPackageUidAsUser(anyString(), anyInt()); - } catch (Exception e) { - } - } - - protected List<Integer> toList(int[] arr) { - return Arrays.stream(arr).boxed().collect(Collectors.toList()); - } -} diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java index 6cc301d933..c53feee404 100644 --- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java +++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java @@ -20,10 +20,8 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; - import static com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.METRICS_COLLECTION_DURATION_MS; import static com.android.testutils.HandlerUtils.visibleOnHandlerThread; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -71,29 +69,16 @@ import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.telephony.SubscriptionManager; -import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import androidx.test.filters.SmallTest; import com.android.internal.util.IndentingPrintWriter; import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive; import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; import com.android.testutils.HandlerUtils; - -import libcore.util.HexEncoding; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import java.io.FileDescriptor; import java.io.StringWriter; import java.net.Inet4Address; @@ -103,6 +88,14 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; +import libcore.util.HexEncoding; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @RunWith(DevSdkIgnoreRunner.class) @SmallTest diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index 52b05aafbe..ab1e4671f2 100644 --- a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -26,7 +26,6 @@ import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClas import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE; import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -43,17 +42,14 @@ import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; import android.net.metrics.WakeupStats; import android.os.Build; -import android.test.suitebuilder.annotation.SmallTest; - +import androidx.test.filters.SmallTest; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; - -import org.junit.Test; -import org.junit.runner.RunWith; - import java.util.Arrays; import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. @RunWith(DevSdkIgnoreRunner.class) diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index 5881a8e760..91626d221e 100644 --- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -18,7 +18,6 @@ package com.android.server.connectivity; import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO; import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; @@ -50,14 +49,14 @@ import android.os.Build; import android.os.Parcelable; import android.os.SystemClock; import android.system.OsConstants; -import android.test.suitebuilder.annotation.SmallTest; import android.util.Base64; - +import androidx.test.filters.SmallTest; import com.android.internal.util.BitUtils; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; - +import java.io.PrintWriter; +import java.io.StringWriter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -65,9 +64,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.io.PrintWriter; -import java.io.StringWriter; - @RunWith(DevSdkIgnoreRunner.class) @SmallTest @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index d6676622b4..89e2a51bb3 100644 --- a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -18,9 +18,7 @@ package com.android.server.connectivity; import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO; import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME; - import static com.android.testutils.MiscAsserts.assertStringContains; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -34,27 +32,23 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.os.Build; import android.system.OsConstants; -import android.test.suitebuilder.annotation.SmallTest; import android.util.Base64; - +import androidx.test.filters.SmallTest; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRunner; - -import libcore.util.EmptyArray; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; - import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import libcore.util.EmptyArray; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; @RunWith(DevSdkIgnoreRunner.class) @SmallTest diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java deleted file mode 100644 index c9cece0249..0000000000 --- a/tests/unit/java/com/android/server/connectivity/VpnTest.java +++ /dev/null @@ -1,3298 +0,0 @@ -/* - * Copyright (C) 2016 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.connectivity; - -import static android.Manifest.permission.BIND_VPN_SERVICE; -import static android.Manifest.permission.CONTROL_VPN; -import static android.content.pm.PackageManager.PERMISSION_DENIED; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback; -import static android.net.ConnectivityDiagnosticsManager.DataStallReport; -import static android.net.ConnectivityManager.NetworkCallback; -import static android.net.INetd.IF_STATE_DOWN; -import static android.net.INetd.IF_STATE_UP; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static android.net.RouteInfo.RTN_UNREACHABLE; -import static android.net.VpnManager.TYPE_VPN_PLATFORM; -import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS; -import static android.net.cts.util.IkeSessionTestUtils.TEST_IDENTITY; -import static android.net.cts.util.IkeSessionTestUtils.TEST_KEEPALIVE_TIMEOUT_UNSET; -import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams; -import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE; -import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO; -import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_NONE; -import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_UDP; -import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO; -import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV4; -import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV6; -import static android.os.UserHandle.PER_USER_RANGE; -import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL; -import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT; -import static android.telephony.CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT; - -import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; -import static com.android.server.connectivity.Vpn.AUTOMATIC_KEEPALIVE_DELAY_SECONDS; -import static com.android.server.connectivity.Vpn.DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC; -import static com.android.server.connectivity.Vpn.DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT; -import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_AUTO; -import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV4_UDP; -import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_ESP; -import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_UDP; -import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor; -import static com.android.testutils.MiscAsserts.assertThrows; - -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; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.longThat; -import static org.mockito.Mockito.after; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.annotation.NonNull; -import android.annotation.UserIdInt; -import android.app.AppOpsManager; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.net.ConnectivityDiagnosticsManager; -import android.net.ConnectivityManager; -import android.net.INetd; -import android.net.Ikev2VpnProfile; -import android.net.InetAddresses; -import android.net.InterfaceConfigurationParcel; -import android.net.IpPrefix; -import android.net.IpSecConfig; -import android.net.IpSecManager; -import android.net.IpSecTransform; -import android.net.IpSecTunnelInterfaceResponse; -import android.net.LinkAddress; -import android.net.LinkProperties; -import android.net.Network; -import android.net.NetworkAgent; -import android.net.NetworkAgentConfig; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo.DetailedState; -import android.net.RouteInfo; -import android.net.TelephonyNetworkSpecifier; -import android.net.UidRangeParcel; -import android.net.VpnManager; -import android.net.VpnProfileState; -import android.net.VpnService; -import android.net.VpnTransportInfo; -import android.net.ipsec.ike.ChildSessionCallback; -import android.net.ipsec.ike.ChildSessionConfiguration; -import android.net.ipsec.ike.IkeFqdnIdentification; -import android.net.ipsec.ike.IkeSessionCallback; -import android.net.ipsec.ike.IkeSessionConfiguration; -import android.net.ipsec.ike.IkeSessionConnectionInfo; -import android.net.ipsec.ike.IkeSessionParams; -import android.net.ipsec.ike.IkeTrafficSelector; -import android.net.ipsec.ike.IkeTunnelConnectionParams; -import android.net.ipsec.ike.exceptions.IkeException; -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.net.vcn.VcnTransportInfo; -import android.net.wifi.WifiInfo; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import android.os.INetworkManagementService; -import android.os.ParcelFileDescriptor; -import android.os.PersistableBundle; -import android.os.PowerWhitelistManager; -import android.os.Process; -import android.os.UserHandle; -import android.os.UserManager; -import android.os.test.TestLooper; -import android.provider.Settings; -import android.security.Credentials; -import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Pair; -import android.util.Range; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.internal.R; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; -import com.android.internal.util.HexDump; -import com.android.internal.util.IndentingPrintWriter; -import com.android.server.DeviceIdleInternal; -import com.android.server.IpSecService; -import com.android.server.VpnTestBase; -import com.android.server.vcn.util.PersistableBundleUtils; -import com.android.testutils.DevSdkIgnoreRule; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.AdditionalAnswers; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.StringWriter; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Tests for {@link Vpn}. - * - * Build, install and run with: - * runtest frameworks-net -c com.android.server.connectivity.VpnTest - */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public class VpnTest extends VpnTestBase { - private static final String TAG = "VpnTest"; - - @Rule - public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); - - static final Network EGRESS_NETWORK = new Network(101); - static final String EGRESS_IFACE = "wlan0"; - private static final String TEST_VPN_CLIENT = "2.4.6.8"; - private static final String TEST_VPN_SERVER = "1.2.3.4"; - private static final String TEST_VPN_IDENTITY = "identity"; - private static final byte[] TEST_VPN_PSK = "psk".getBytes(); - - private static final int IP4_PREFIX_LEN = 32; - private static final int IP6_PREFIX_LEN = 64; - private static final int MIN_PORT = 0; - private static final int MAX_PORT = 65535; - - private static final InetAddress TEST_VPN_CLIENT_IP = - InetAddresses.parseNumericAddress(TEST_VPN_CLIENT); - private static final InetAddress TEST_VPN_SERVER_IP = - InetAddresses.parseNumericAddress(TEST_VPN_SERVER); - private static final InetAddress TEST_VPN_CLIENT_IP_2 = - InetAddresses.parseNumericAddress("192.0.2.200"); - private static final InetAddress TEST_VPN_SERVER_IP_2 = - InetAddresses.parseNumericAddress("192.0.2.201"); - private static final InetAddress TEST_VPN_INTERNAL_IP = - InetAddresses.parseNumericAddress("198.51.100.10"); - private static final InetAddress TEST_VPN_INTERNAL_IP6 = - InetAddresses.parseNumericAddress("2001:db8::1"); - private static final InetAddress TEST_VPN_INTERNAL_DNS = - InetAddresses.parseNumericAddress("8.8.8.8"); - private static final InetAddress TEST_VPN_INTERNAL_DNS6 = - InetAddresses.parseNumericAddress("2001:4860:4860::8888"); - - private static final IkeTrafficSelector IN_TS = - new IkeTrafficSelector(MIN_PORT, MAX_PORT, TEST_VPN_INTERNAL_IP, TEST_VPN_INTERNAL_IP); - private static final IkeTrafficSelector IN_TS6 = - new IkeTrafficSelector( - MIN_PORT, MAX_PORT, TEST_VPN_INTERNAL_IP6, TEST_VPN_INTERNAL_IP6); - private static final IkeTrafficSelector OUT_TS = - new IkeTrafficSelector(MIN_PORT, MAX_PORT, - InetAddresses.parseNumericAddress("0.0.0.0"), - InetAddresses.parseNumericAddress("255.255.255.255")); - private static final IkeTrafficSelector OUT_TS6 = - new IkeTrafficSelector( - MIN_PORT, - MAX_PORT, - InetAddresses.parseNumericAddress("::"), - InetAddresses.parseNumericAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); - - private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE); - private static final Network TEST_NETWORK_2 = new Network(Integer.MAX_VALUE - 1); - private static final String TEST_IFACE_NAME = "TEST_IFACE"; - private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345; - private static final long TEST_TIMEOUT_MS = 500L; - private static final long TIMEOUT_CROSSTHREAD_MS = 20_000L; - private static final String PRIMARY_USER_APP_EXCLUDE_KEY = - "VPNAPPEXCLUDED_27_com.testvpn.vpn"; - static final String PKGS_BYTES = getPackageByteString(List.of(PKGS)); - private static final Range<Integer> PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id); - private static final int TEST_KEEPALIVE_TIMER = 800; - private static final int TEST_SUB_ID = 1234; - private static final String TEST_MCCMNC = "12345"; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; - @Mock private UserManager mUserManager; - @Mock private PackageManager mPackageManager; - @Mock private INetworkManagementService mNetService; - @Mock private INetd mNetd; - @Mock private AppOpsManager mAppOps; - @Mock private NotificationManager mNotificationManager; - @Mock private Vpn.SystemServices mSystemServices; - @Mock private Vpn.IkeSessionWrapper mIkeSessionWrapper; - @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator; - @Mock private Vpn.VpnNetworkAgentWrapper mMockNetworkAgent; - @Mock private ConnectivityManager mConnectivityManager; - @Mock private ConnectivityDiagnosticsManager mCdm; - @Mock private TelephonyManager mTelephonyManager; - @Mock private TelephonyManager mTmPerSub; - @Mock private CarrierConfigManager mConfigManager; - @Mock private SubscriptionManager mSubscriptionManager; - @Mock private IpSecService mIpSecService; - @Mock private VpnProfileStore mVpnProfileStore; - private final TestExecutor mExecutor; - @Mock DeviceIdleInternal mDeviceIdleInternal; - private final VpnProfile mVpnProfile; - - @Captor private ArgumentCaptor<Collection<Range<Integer>>> mUidRangesCaptor; - - private IpSecManager mIpSecManager; - private TestDeps mTestDeps; - - public static class TestExecutor extends ScheduledThreadPoolExecutor { - public static final long REAL_DELAY = -1; - - // For the purposes of the test, run all scheduled tasks after 10ms to save - // execution time, unless overridden by the specific test. Set to REAL_DELAY - // to actually wait for the delay specified by the real call to schedule(). - public long delayMs = 10; - // If this is true, execute() will call the runnable inline. This is useful because - // super.execute() calls schedule(), which messes with checks that scheduled() is - // called a given number of times. - public boolean executeDirect = false; - - public TestExecutor() { - super(1); - } - - @Override - public void execute(final Runnable command) { - // See |executeDirect| for why this is necessary. - if (executeDirect) { - command.run(); - } else { - super.execute(command); - } - } - - @Override - public ScheduledFuture<?> schedule(final Runnable command, final long delay, - TimeUnit unit) { - if (0 == delay || delayMs == REAL_DELAY) { - // super.execute() calls schedule() with 0, so use the real delay if it's 0. - return super.schedule(command, delay, unit); - } else { - return super.schedule(command, delayMs, TimeUnit.MILLISECONDS); - } - } - } - - public VpnTest() throws Exception { - // Build an actual VPN profile that is capable of being converted to and from an - // Ikev2VpnProfile - final Ikev2VpnProfile.Builder builder = - new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY); - builder.setAuthPsk(TEST_VPN_PSK); - builder.setBypassable(true /* isBypassable */); - mExecutor = spy(new TestExecutor()); - mVpnProfile = builder.build().toVpnProfile(); - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mIpSecManager = new IpSecManager(mContext, mIpSecService); - mTestDeps = spy(new TestDeps()); - doReturn(IPV6_MIN_MTU) - .when(mTestDeps) - .calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean()); - doReturn(1500).when(mTestDeps).getJavaNetworkInterfaceMtu(any(), anyInt()); - - when(mContext.getPackageManager()).thenReturn(mPackageManager); - setMockedPackages(sPackages); - - when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG); - when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG); - mockService(UserManager.class, Context.USER_SERVICE, mUserManager); - mockService(AppOpsManager.class, Context.APP_OPS_SERVICE, mAppOps); - mockService(NotificationManager.class, Context.NOTIFICATION_SERVICE, mNotificationManager); - mockService(ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mConnectivityManager); - mockService(IpSecManager.class, Context.IPSEC_SERVICE, mIpSecManager); - mockService(ConnectivityDiagnosticsManager.class, Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, - mCdm); - mockService(TelephonyManager.class, Context.TELEPHONY_SERVICE, mTelephonyManager); - mockService(CarrierConfigManager.class, Context.CARRIER_CONFIG_SERVICE, mConfigManager); - mockService(SubscriptionManager.class, Context.TELEPHONY_SUBSCRIPTION_SERVICE, - mSubscriptionManager); - doReturn(mTmPerSub).when(mTelephonyManager).createForSubscriptionId(anyInt()); - when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)) - .thenReturn(Resources.getSystem().getString( - R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)); - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) - .thenReturn(true); - - // Used by {@link Notification.Builder} - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; - when(mContext.getApplicationInfo()).thenReturn(applicationInfo); - when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) - .thenReturn(applicationInfo); - - doNothing().when(mNetService).registerObserver(any()); - - // Deny all appops by default. - when(mAppOps.noteOpNoThrow(anyString(), anyInt(), anyString(), any(), any())) - .thenReturn(AppOpsManager.MODE_IGNORED); - - // Setup IpSecService - final IpSecTunnelInterfaceResponse tunnelResp = - new IpSecTunnelInterfaceResponse( - IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME); - when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any())) - .thenReturn(tunnelResp); - doReturn(new LinkProperties()).when(mConnectivityManager).getLinkProperties(any()); - - // The unit test should know what kind of permission it needs and set the permission by - // itself, so set the default value of Context#checkCallingOrSelfPermission to - // PERMISSION_DENIED. - doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any()); - - // Set up mIkev2SessionCreator and mExecutor - resetIkev2SessionCreator(mIkeSessionWrapper); - } - - private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) { - reset(mIkev2SessionCreator); - when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any())) - .thenReturn(ikeSession); - } - - private <T> void mockService(Class<T> clazz, String name, T service) { - doReturn(service).when(mContext).getSystemService(name); - doReturn(name).when(mContext).getSystemServiceName(clazz); - if (mContext.getSystemService(clazz).getClass().equals(Object.class)) { - // Test is using mockito-extended (mContext uses Answers.RETURNS_DEEP_STUBS and returned - // a mock object on a final method) - doCallRealMethod().when(mContext).getSystemService(clazz); - } - } - - private Set<Range<Integer>> rangeSet(Range<Integer> ... ranges) { - final Set<Range<Integer>> range = new ArraySet<>(); - for (Range<Integer> r : ranges) range.add(r); - - return range; - } - - private static Range<Integer> uidRangeForUser(int userId) { - return new Range<Integer>(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); - } - - private Range<Integer> uidRange(int start, int stop) { - return new Range<Integer>(start, stop); - } - - private static String getPackageByteString(List<String> packages) { - try { - return HexDump.toHexString( - PersistableBundleUtils.toDiskStableBytes(PersistableBundleUtils.fromList( - packages, PersistableBundleUtils.STRING_SERIALIZER)), - true /* upperCase */); - } catch (IOException e) { - return null; - } - } - - @Test - public void testRestrictedProfilesAreAddedToVpn() { - setMockedUsers(PRIMARY_USER, SECONDARY_USER, RESTRICTED_PROFILE_A, RESTRICTED_PROFILE_B); - - final Vpn vpn = createVpn(PRIMARY_USER.id); - - // Assume the user can have restricted profiles. - doReturn(true).when(mUserManager).canHaveRestrictedProfile(); - final Set<Range<Integer>> ranges = - vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, null, null); - - assertEquals(rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id)), - ranges); - } - - @Test - public void testManagedProfilesAreNotAddedToVpn() { - setMockedUsers(PRIMARY_USER, MANAGED_PROFILE_A); - - final Vpn vpn = createVpn(PRIMARY_USER.id); - final Set<Range<Integer>> ranges = vpn.createUserAndRestrictedProfilesRanges( - PRIMARY_USER.id, null, null); - - assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges); - } - - @Test - public void testAddUserToVpnOnlyAddsOneUser() { - setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A, MANAGED_PROFILE_A); - - final Vpn vpn = createVpn(PRIMARY_USER.id); - final Set<Range<Integer>> ranges = new ArraySet<>(); - vpn.addUserToRanges(ranges, PRIMARY_USER.id, null, null); - - assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges); - } - - @Test - public void testUidAllowAndDenylist() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - final Range<Integer> user = PRIMARY_USER_RANGE; - final int userStart = user.getLower(); - final int userStop = user.getUpper(); - final String[] packages = {PKGS[0], PKGS[1], PKGS[2]}; - - // Allowed list - final Set<Range<Integer>> allow = vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, - Arrays.asList(packages), null /* disallowedApplications */); - assertEquals(rangeSet( - uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]), - uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2]), - uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0]), - Process.toSdkSandboxUid(userStart + PKG_UIDS[0])), - uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[1]), - Process.toSdkSandboxUid(userStart + PKG_UIDS[2]))), - allow); - - // Denied list - final Set<Range<Integer>> disallow = - vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, - null /* allowedApplications */, Arrays.asList(packages)); - assertEquals(rangeSet( - uidRange(userStart, userStart + PKG_UIDS[0] - 1), - uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1), - /* Empty range between UIDS[1] and UIDS[2], should be excluded, */ - uidRange(userStart + PKG_UIDS[2] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)), - uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), - Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)), - uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)), - disallow); - } - - private void verifyPowerSaveTempWhitelistApp(String packageName) { - verify(mDeviceIdleInternal, timeout(TEST_TIMEOUT_MS)).addPowerSaveTempWhitelistApp( - anyInt(), eq(packageName), anyLong(), anyInt(), eq(false), - eq(PowerWhitelistManager.REASON_VPN), eq("VpnManager event")); - } - - @Test - public void testGetAlwaysAndOnGetLockDown() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - - // Default state. - assertFalse(vpn.getAlwaysOn()); - assertFalse(vpn.getLockdown()); - - // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList())); - assertTrue(vpn.getAlwaysOn()); - assertFalse(vpn.getLockdown()); - - // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList())); - assertTrue(vpn.getAlwaysOn()); - assertTrue(vpn.getLockdown()); - - // Remove always-on configuration. - assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList())); - assertFalse(vpn.getAlwaysOn()); - assertFalse(vpn.getLockdown()); - } - - @Test - public void testAlwaysOnWithoutLockdown() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], false /* lockdown */, null /* lockdownAllowlist */)); - verify(mConnectivityManager, never()).setRequireVpnForUids(anyBoolean(), any()); - - assertTrue(vpn.setAlwaysOnPackage( - null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */)); - verify(mConnectivityManager, never()).setRequireVpnForUids(anyBoolean(), any()); - } - - @Test - public void testLockdownChangingPackage() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - final Range<Integer> user = PRIMARY_USER_RANGE; - final int userStart = user.getLower(); - final int userStop = user.getUpper(); - // Set always-on without lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null)); - - // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null)); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1), - new UidRangeParcel(userStart + PKG_UIDS[1] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop) - })); - - // Switch to another app. - assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null)); - verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1), - new UidRangeParcel(userStart + PKG_UIDS[1] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop) - })); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1), - new UidRangeParcel(userStart + PKG_UIDS[3] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop) - })); - } - - @Test - public void testLockdownAllowlist() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - final Range<Integer> user = PRIMARY_USER_RANGE; - final int userStart = user.getLower(); - final int userStop = user.getUpper(); - // Set always-on with lockdown and allow app PKGS[2] from lockdown. - assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], true, Collections.singletonList(PKGS[2]))); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1), - new UidRangeParcel(userStart + PKG_UIDS[2] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[1]) - 1), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop) - })); - // Change allowed app list to PKGS[3]. - assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], true, Collections.singletonList(PKGS[3]))); - verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart + PKG_UIDS[2] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop) - })); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1), - new UidRangeParcel(userStart + PKG_UIDS[3] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), - Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop) - })); - - // Change the VPN app. - assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList(PKGS[3]))); - verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1), - new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1), - new UidRangeParcel(userStart + PKG_UIDS[3] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), - Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)) - })); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1), - new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1), - new UidRangeParcel(userStart + PKG_UIDS[3] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), - Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)) - })); - - // Remove the list of allowed packages. - assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null)); - verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1), - new UidRangeParcel(userStart + PKG_UIDS[3] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), - Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop) - })); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart + PKG_UIDS[0] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop), - })); - - // Add the list of allowed packages. - assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList(PKGS[1]))); - verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart + PKG_UIDS[0] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop), - })); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1), - new UidRangeParcel(userStart + PKG_UIDS[1] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), - Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop) - })); - - // Try allowing a package with a comma, should be rejected. - assertFalse(vpn.setAlwaysOnPackage( - PKGS[0], true, Collections.singletonList("a.b,c.d"))); - - // Pass a non-existent packages in the allowlist, they (and only they) should be ignored. - // allowed package should change from PGKS[1] to PKGS[2]. - assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"))); - verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1), - new UidRangeParcel(userStart + PKG_UIDS[1] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), - Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop) - })); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] { - new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1), - new UidRangeParcel(userStart + PKG_UIDS[2] + 1, - Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), - Process.toSdkSandboxUid(userStart + PKG_UIDS[2] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop) - })); - } - - @Test - public void testLockdownSystemUser() throws Exception { - final Vpn vpn = createVpn(SYSTEM_USER_ID); - - // Uid 0 is always excluded and PKG_UIDS[1] is the uid of the VPN. - final List<Integer> excludedUids = new ArrayList<>(List.of(0, PKG_UIDS[1])); - final List<Range<Integer>> ranges = makeVpnUidRange(SYSTEM_USER_ID, excludedUids); - - // Set always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], true /* lockdown */, null /* lockdownAllowlist */)); - verify(mConnectivityManager).setRequireVpnForUids(true, ranges); - - // Disable always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage( - null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */)); - verify(mConnectivityManager).setRequireVpnForUids(false, ranges); - - // Set always-on with lockdown and allow the app PKGS[2]. - excludedUids.add(PKG_UIDS[2]); - final List<Range<Integer>> ranges2 = makeVpnUidRange(SYSTEM_USER_ID, excludedUids); - assertTrue(vpn.setAlwaysOnPackage( - PKGS[1], true /* lockdown */, Collections.singletonList(PKGS[2]))); - verify(mConnectivityManager).setRequireVpnForUids(true, ranges2); - - // Disable always-on with lockdown. - assertTrue(vpn.setAlwaysOnPackage( - null /* packageName */, false /* lockdown */, null /* lockdownAllowlist */)); - verify(mConnectivityManager).setRequireVpnForUids(false, ranges2); - } - - @Test - public void testLockdownRuleRepeatability() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] { - new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())}; - // Given legacy lockdown is already enabled, - vpn.setLockdown(true); - verify(mConnectivityManager, times(1)).setRequireVpnForUids(true, - toRanges(primaryUserRangeParcel)); - - // Enabling legacy lockdown twice should do nothing. - vpn.setLockdown(true); - verify(mConnectivityManager, times(1)).setRequireVpnForUids(anyBoolean(), any()); - - // And disabling should remove the rules exactly once. - vpn.setLockdown(false); - verify(mConnectivityManager, times(1)).setRequireVpnForUids(false, - toRanges(primaryUserRangeParcel)); - - // Removing the lockdown again should have no effect. - vpn.setLockdown(false); - verify(mConnectivityManager, times(2)).setRequireVpnForUids(anyBoolean(), any()); - } - - private ArrayList<Range<Integer>> toRanges(UidRangeParcel[] ranges) { - ArrayList<Range<Integer>> rangesArray = new ArrayList<>(ranges.length); - for (int i = 0; i < ranges.length; i++) { - rangesArray.add(new Range<>(ranges[i].start, ranges[i].stop)); - } - return rangesArray; - } - - @Test - public void testLockdownRuleReversibility() throws Exception { - doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN); - final Vpn vpn = createVpn(PRIMARY_USER.id); - final UidRangeParcel[] entireUser = { - new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper()) - }; - final UidRangeParcel[] exceptPkg0 = { - new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1), - new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1, - Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] - 1)), - new UidRangeParcel(Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] + 1), - entireUser[0].stop), - }; - - final InOrder order = inOrder(mConnectivityManager); - - // Given lockdown is enabled with no package (legacy VPN), - vpn.setLockdown(true); - order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser)); - - // When a new VPN package is set the rules should change to cover that package. - vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE); - order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(entireUser)); - order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(exceptPkg0)); - - // When that VPN package is unset, everything should be undone again in reverse. - vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE); - order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(exceptPkg0)); - order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser)); - } - - @Test - public void testOnUserAddedAndRemoved_restrictedUser() throws Exception { - final InOrder order = inOrder(mMockNetworkAgent); - final Vpn vpn = createVpn(PRIMARY_USER.id); - final Set<Range<Integer>> initialRange = rangeSet(PRIMARY_USER_RANGE); - // Note since mVpnProfile is a Ikev2VpnProfile, this starts an IkeV2VpnRunner. - startLegacyVpn(vpn, mVpnProfile); - // Set an initial Uid range and mock the network agent - vpn.mNetworkCapabilities.setUids(initialRange); - vpn.mNetworkAgent = mMockNetworkAgent; - - // Add the restricted user - setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A); - vpn.onUserAdded(RESTRICTED_PROFILE_A.id); - // Expect restricted user range to be added to the NetworkCapabilities. - final Set<Range<Integer>> expectRestrictedRange = - rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id)); - assertEquals(expectRestrictedRange, vpn.mNetworkCapabilities.getUids()); - order.verify(mMockNetworkAgent).doSendNetworkCapabilities( - argThat(nc -> expectRestrictedRange.equals(nc.getUids()))); - - // Remove the restricted user - vpn.onUserRemoved(RESTRICTED_PROFILE_A.id); - // Expect restricted user range to be removed from the NetworkCapabilities. - assertEquals(initialRange, vpn.mNetworkCapabilities.getUids()); - order.verify(mMockNetworkAgent).doSendNetworkCapabilities( - argThat(nc -> initialRange.equals(nc.getUids()))); - } - - @Test - public void testOnUserAddedAndRemoved_restrictedUserLockdown() throws Exception { - final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] { - new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())}; - final Range<Integer> restrictedUserRange = uidRangeForUser(RESTRICTED_PROFILE_A.id); - final UidRangeParcel[] restrictedUserRangeParcel = new UidRangeParcel[] { - new UidRangeParcel(restrictedUserRange.getLower(), restrictedUserRange.getUpper())}; - final Vpn vpn = createVpn(PRIMARY_USER.id); - - // Set lockdown calls setRequireVpnForUids - vpn.setLockdown(true); - verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(primaryUserRangeParcel)); - - // Add the restricted user - doReturn(true).when(mUserManager).canHaveRestrictedProfile(); - setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A); - vpn.onUserAdded(RESTRICTED_PROFILE_A.id); - - // Expect restricted user range to be added. - verify(mConnectivityManager).setRequireVpnForUids(true, - toRanges(restrictedUserRangeParcel)); - - // Mark as partial indicates that the user is removed, mUserManager.getAliveUsers() does not - // return the restricted user but it is still returned in mUserManager.getUserInfo(). - RESTRICTED_PROFILE_A.partial = true; - // Remove the restricted user - vpn.onUserRemoved(RESTRICTED_PROFILE_A.id); - verify(mConnectivityManager).setRequireVpnForUids(false, - toRanges(restrictedUserRangeParcel)); - // reset to avoid affecting other tests since RESTRICTED_PROFILE_A is static. - RESTRICTED_PROFILE_A.partial = false; - } - - @Test - public void testOnUserAddedAndRemoved_restrictedUserAlwaysOn() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - - // setAlwaysOnPackage() calls setRequireVpnForUids() - assertTrue(vpn.setAlwaysOnPackage( - PKGS[0], true /* lockdown */, null /* lockdownAllowlist */)); - final List<Integer> excludedUids = List.of(PKG_UIDS[0]); - final List<Range<Integer>> primaryRanges = - makeVpnUidRange(PRIMARY_USER.id, excludedUids); - verify(mConnectivityManager).setRequireVpnForUids(true, primaryRanges); - - // Add the restricted user - doReturn(true).when(mUserManager).canHaveRestrictedProfile(); - setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A); - vpn.onUserAdded(RESTRICTED_PROFILE_A.id); - - final List<Range<Integer>> restrictedRanges = - makeVpnUidRange(RESTRICTED_PROFILE_A.id, excludedUids); - // Expect restricted user range to be added. - verify(mConnectivityManager).setRequireVpnForUids(true, restrictedRanges); - - // Mark as partial indicates that the user is removed, mUserManager.getAliveUsers() does not - // return the restricted user but it is still returned in mUserManager.getUserInfo(). - RESTRICTED_PROFILE_A.partial = true; - // Remove the restricted user - vpn.onUserRemoved(RESTRICTED_PROFILE_A.id); - verify(mConnectivityManager).setRequireVpnForUids(false, restrictedRanges); - - // reset to avoid affecting other tests since RESTRICTED_PROFILE_A is static. - RESTRICTED_PROFILE_A.partial = false; - } - - @Test - public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller() - throws Exception { - mTestDeps.mIgnoreCallingUidChecks = false; - final Vpn vpn = createVpn(); - assertThrows(SecurityException.class, - () -> vpn.prepare("com.not.vpn.owner", null, VpnManager.TYPE_VPN_SERVICE)); - assertThrows(SecurityException.class, - () -> vpn.prepare(null, "com.not.vpn.owner", VpnManager.TYPE_VPN_SERVICE)); - assertThrows(SecurityException.class, - () -> vpn.prepare("com.not.vpn.owner1", "com.not.vpn.owner2", - VpnManager.TYPE_VPN_SERVICE)); - } - - @Test - public void testPrepare_bothOldPackageAndNewPackageAreNull() throws Exception { - final Vpn vpn = createVpn(); - assertTrue(vpn.prepare(null, null, VpnManager.TYPE_VPN_SERVICE)); - - } - - @Test - public void testPrepare_legacyVpnWithoutControlVpn() - throws Exception { - doThrow(new SecurityException("no CONTROL_VPN")).when(mContext) - .enforceCallingOrSelfPermission(eq(CONTROL_VPN), any()); - final Vpn vpn = createVpn(); - assertThrows(SecurityException.class, - () -> vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE)); - - // CONTROL_VPN can be held by the caller or another system server process - both are - // allowed. Just checking for `enforceCallingPermission` may not be sufficient. - verify(mContext, never()).enforceCallingPermission(eq(CONTROL_VPN), any()); - } - - @Test - public void testPrepare_legacyVpnWithControlVpn() - throws Exception { - doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CONTROL_VPN), any()); - final Vpn vpn = createVpn(); - assertTrue(vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE)); - - // CONTROL_VPN can be held by the caller or another system server process - both are - // allowed. Just checking for `enforceCallingPermission` may not be sufficient. - verify(mContext, never()).enforceCallingPermission(eq(CONTROL_VPN), any()); - } - - @Test - public void testIsAlwaysOnPackageSupported() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - - ApplicationInfo appInfo = new ApplicationInfo(); - when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(PRIMARY_USER.id))) - .thenReturn(appInfo); - - ServiceInfo svcInfo = new ServiceInfo(); - ResolveInfo resInfo = new ResolveInfo(); - resInfo.serviceInfo = svcInfo; - when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA), - eq(PRIMARY_USER.id))) - .thenReturn(Collections.singletonList(resInfo)); - - // null package name should return false - assertFalse(vpn.isAlwaysOnPackageSupported(null)); - - // Pre-N apps are not supported - appInfo.targetSdkVersion = VERSION_CODES.M; - assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); - - // N+ apps are supported by default - appInfo.targetSdkVersion = VERSION_CODES.N; - assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0])); - - // Apps that opt out explicitly are not supported - appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; - Bundle metaData = new Bundle(); - metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false); - svcInfo.metaData = metaData; - assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); - } - - @Test - public void testNotificationShownForAlwaysOnApp() throws Exception { - final UserHandle userHandle = UserHandle.of(PRIMARY_USER.id); - final Vpn vpn = createVpn(PRIMARY_USER.id); - setMockedUsers(PRIMARY_USER); - - final InOrder order = inOrder(mNotificationManager); - - // Don't show a notification for regular disconnected states. - vpn.updateState(DetailedState.DISCONNECTED, TAG); - order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt()); - - // Start showing a notification for disconnected once always-on. - vpn.setAlwaysOnPackage(PKGS[0], false, null); - order.verify(mNotificationManager).notify(anyString(), anyInt(), any()); - - // Stop showing the notification once connected. - vpn.updateState(DetailedState.CONNECTED, TAG); - order.verify(mNotificationManager).cancel(anyString(), anyInt()); - - // Show the notification if we disconnect again. - vpn.updateState(DetailedState.DISCONNECTED, TAG); - order.verify(mNotificationManager).notify(anyString(), anyInt(), any()); - - // Notification should be cleared after unsetting always-on package. - vpn.setAlwaysOnPackage(null, false, null); - order.verify(mNotificationManager).cancel(anyString(), anyInt()); - } - - /** - * The profile name should NOT change between releases for backwards compatibility - * - * <p>If this is changed between releases, the {@link Vpn#getVpnProfilePrivileged()} method MUST - * be updated to ensure backward compatibility. - */ - @Test - public void testGetProfileNameForPackage() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - setMockedUsers(PRIMARY_USER); - - final String expected = Credentials.PLATFORM_VPN + PRIMARY_USER.id + "_" + TEST_VPN_PKG; - assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG)); - } - - private Vpn createVpn(String... grantedOps) throws Exception { - return createVpn(PRIMARY_USER, grantedOps); - } - - private Vpn createVpn(UserInfo user, String... grantedOps) throws Exception { - final Vpn vpn = createVpn(user.id); - setMockedUsers(user); - - for (final String opStr : grantedOps) { - when(mAppOps.noteOpNoThrow(opStr, Process.myUid(), TEST_VPN_PKG, - null /* attributionTag */, null /* message */)) - .thenReturn(AppOpsManager.MODE_ALLOWED); - } - - return vpn; - } - - private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) { - assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile)); - - // The profile should always be stored, whether or not consent has been previously granted. - verify(mVpnProfileStore) - .put( - eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), - eq(mVpnProfile.encode())); - - for (final String checkedOpStr : checkedOps) { - verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG, - null /* attributionTag */, null /* message */); - } - } - - @Test - public void testProvisionVpnProfileNoIpsecTunnels() throws Exception { - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) - .thenReturn(false); - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - try { - checkProvisionVpnProfile( - vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - fail("Expected exception due to missing feature"); - } catch (UnsupportedOperationException expected) { - } - } - - private String startVpnForVerifyAppExclusionList(Vpn vpn) throws Exception { - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(mVpnProfile.encode()); - when(mVpnProfileStore.get(PRIMARY_USER_APP_EXCLUDE_KEY)) - .thenReturn(HexDump.hexStringToByteArray(PKGS_BYTES)); - final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG); - final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges( - PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS)); - verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges)); - clearInvocations(mConnectivityManager); - verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); - vpn.mNetworkAgent = mMockNetworkAgent; - - return sessionKey; - } - - private Vpn prepareVpnForVerifyAppExclusionList() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - startVpnForVerifyAppExclusionList(vpn); - - return vpn; - } - - @Test - public void testSetAndGetAppExclusionList() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - final String sessionKey = startVpnForVerifyAppExclusionList(vpn); - verify(mVpnProfileStore, never()).put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), any()); - vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS)); - verify(mVpnProfileStore) - .put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), - eq(HexDump.hexStringToByteArray(PKGS_BYTES))); - final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges( - PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS)); - verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges)); - assertEquals(uidRanges, vpn.mNetworkCapabilities.getUids()); - assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); - } - - @Test - public void testRefreshPlatformVpnAppExclusionList_updatesExcludedUids() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - final String sessionKey = startVpnForVerifyAppExclusionList(vpn); - vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS)); - final Set<Range<Integer>> uidRanges = vpn.createUserAndRestrictedProfilesRanges( - PRIMARY_USER.id, null /* allowedApplications */, Arrays.asList(PKGS)); - verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges)); - verify(mMockNetworkAgent).doSendNetworkCapabilities(any()); - assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); - - reset(mMockNetworkAgent); - - // Remove one of the package - List<Integer> newExcludedUids = toList(PKG_UIDS); - newExcludedUids.remove((Integer) PKG_UIDS[0]); - Set<Range<Integer>> newUidRanges = makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids); - sPackages.remove(PKGS[0]); - vpn.refreshPlatformVpnAppExclusionList(); - - // List in keystore is not changed, but UID for the removed packages is no longer exempted. - assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); - assertEquals(newUidRanges, vpn.mNetworkCapabilities.getUids()); - ArgumentCaptor<NetworkCapabilities> ncCaptor = - ArgumentCaptor.forClass(NetworkCapabilities.class); - verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture()); - assertEquals(newUidRanges, ncCaptor.getValue().getUids()); - verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(newUidRanges)); - - reset(mMockNetworkAgent); - - // Add the package back - newExcludedUids.add(PKG_UIDS[0]); - newUidRanges = makeVpnUidRangeSet(PRIMARY_USER.id, newExcludedUids); - sPackages.put(PKGS[0], PKG_UIDS[0]); - vpn.refreshPlatformVpnAppExclusionList(); - - // List in keystore is not changed and the uid list should be updated in the net cap. - assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); - assertEquals(newUidRanges, vpn.mNetworkCapabilities.getUids()); - verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture()); - assertEquals(newUidRanges, ncCaptor.getValue().getUids()); - - // The uidRange is the same as the original setAppExclusionList so this is the second call - verify(mConnectivityManager, times(2)) - .setVpnDefaultForUids(eq(sessionKey), eq(newUidRanges)); - } - - private List<Range<Integer>> makeVpnUidRange(int userId, List<Integer> excludedAppIdList) { - final SortedSet<Integer> list = new TreeSet<>(); - - final int userBase = userId * UserHandle.PER_USER_RANGE; - for (int appId : excludedAppIdList) { - final int uid = UserHandle.getUid(userId, appId); - list.add(uid); - if (Process.isApplicationUid(uid)) { - list.add(Process.toSdkSandboxUid(uid)); // Add Sdk Sandbox UID - } - } - - final int minUid = userBase; - final int maxUid = userBase + UserHandle.PER_USER_RANGE - 1; - final List<Range<Integer>> ranges = new ArrayList<>(); - - // Iterate the list to create the ranges between each uid. - int start = minUid; - for (int uid : list) { - if (uid == start) { - start++; - } else { - ranges.add(new Range<>(start, uid - 1)); - start = uid + 1; - } - } - - // Create the range between last uid and max uid. - if (start <= maxUid) { - ranges.add(new Range<>(start, maxUid)); - } - - return ranges; - } - - private Set<Range<Integer>> makeVpnUidRangeSet(int userId, List<Integer> excludedAppIdList) { - return new ArraySet<>(makeVpnUidRange(userId, excludedAppIdList)); - } - - @Test - public void testSetAndGetAppExclusionListRestrictedUser() throws Exception { - final Vpn vpn = prepareVpnForVerifyAppExclusionList(); - - // Mock it to restricted profile - when(mUserManager.getUserInfo(anyInt())).thenReturn(RESTRICTED_PROFILE_A); - - // Restricted users cannot configure VPNs - assertThrows(SecurityException.class, - () -> vpn.setAppExclusionList(TEST_VPN_PKG, new ArrayList<>())); - - assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG)); - } - - @Test - public void testProvisionVpnProfilePreconsented() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - checkProvisionVpnProfile( - vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - } - - @Test - public void testProvisionVpnProfileNotPreconsented() throws Exception { - final Vpn vpn = createVpn(); - - // Expect that both the ACTIVATE_VPN and ACTIVATE_PLATFORM_VPN were tried, but the caller - // had neither. - checkProvisionVpnProfile(vpn, false /* expectedResult */, - AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, AppOpsManager.OPSTR_ACTIVATE_VPN); - } - - @Test - public void testProvisionVpnProfileVpnServicePreconsented() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_VPN); - - checkProvisionVpnProfile(vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_VPN); - } - - @Test - public void testProvisionVpnProfileTooLarge() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - final VpnProfile bigProfile = new VpnProfile(""); - bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]); - - try { - vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile); - fail("Expected IAE due to profile size"); - } catch (IllegalArgumentException expected) { - } - } - - @Test - public void testProvisionVpnProfileRestrictedUser() throws Exception { - final Vpn vpn = - createVpn( - RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - try { - vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile); - fail("Expected SecurityException due to restricted user"); - } catch (SecurityException expected) { - } - } - - @Test - public void testDeleteVpnProfile() throws Exception { - final Vpn vpn = createVpn(); - - vpn.deleteVpnProfile(TEST_VPN_PKG); - - verify(mVpnProfileStore) - .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); - } - - @Test - public void testDeleteVpnProfileRestrictedUser() throws Exception { - final Vpn vpn = - createVpn( - RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - try { - vpn.deleteVpnProfile(TEST_VPN_PKG); - fail("Expected SecurityException due to restricted user"); - } catch (SecurityException expected) { - } - } - - @Test - public void testGetVpnProfilePrivileged() throws Exception { - final Vpn vpn = createVpn(); - - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(new VpnProfile("").encode()); - - vpn.getVpnProfilePrivileged(TEST_VPN_PKG); - - verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); - } - - private void verifyPlatformVpnIsActivated(String packageName) { - verify(mAppOps).noteOpNoThrow( - eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), - eq(Process.myUid()), - eq(packageName), - eq(null) /* attributionTag */, - eq(null) /* message */); - verify(mAppOps).startOp( - eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER), - eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())), - eq(packageName), - eq(null) /* attributionTag */, - eq(null) /* message */); - } - - private void verifyPlatformVpnIsDeactivated(String packageName) { - // Add a small delay to double confirm that finishOp is only called once. - verify(mAppOps, after(100)).finishOp( - eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER), - eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())), - eq(packageName), - eq(null) /* attributionTag */); - } - - @Test - public void testStartVpnProfile() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(mVpnProfile.encode()); - - vpn.startVpnProfile(TEST_VPN_PKG); - - verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); - verifyPlatformVpnIsActivated(TEST_VPN_PKG); - } - - @Test - public void testStartVpnProfileVpnServicePreconsented() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_VPN); - - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(mVpnProfile.encode()); - - vpn.startVpnProfile(TEST_VPN_PKG); - - // Verify that the ACTIVATE_VPN appop was checked, but no error was thrown. - verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(), - TEST_VPN_PKG, null /* attributionTag */, null /* message */); - } - - @Test - public void testStartVpnProfileNotConsented() throws Exception { - final Vpn vpn = createVpn(); - - try { - vpn.startVpnProfile(TEST_VPN_PKG); - fail("Expected failure due to no user consent"); - } catch (SecurityException expected) { - } - - // Verify both appops were checked. - verify(mAppOps) - .noteOpNoThrow( - eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), - eq(Process.myUid()), - eq(TEST_VPN_PKG), - eq(null) /* attributionTag */, - eq(null) /* message */); - verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(), - TEST_VPN_PKG, null /* attributionTag */, null /* message */); - - // Keystore should never have been accessed. - verify(mVpnProfileStore, never()).get(any()); - } - - @Test - public void testStartVpnProfileMissingProfile() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null); - - try { - vpn.startVpnProfile(TEST_VPN_PKG); - fail("Expected failure due to missing profile"); - } catch (IllegalArgumentException expected) { - } - - verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); - verify(mAppOps) - .noteOpNoThrow( - eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), - eq(Process.myUid()), - eq(TEST_VPN_PKG), - eq(null) /* attributionTag */, - eq(null) /* message */); - } - - @Test - public void testStartVpnProfileRestrictedUser() throws Exception { - final Vpn vpn = createVpn(RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - try { - vpn.startVpnProfile(TEST_VPN_PKG); - fail("Expected SecurityException due to restricted user"); - } catch (SecurityException expected) { - } - } - - @Test - public void testStopVpnProfileRestrictedUser() throws Exception { - final Vpn vpn = createVpn(RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - - try { - vpn.stopVpnProfile(TEST_VPN_PKG); - fail("Expected SecurityException due to restricted user"); - } catch (SecurityException expected) { - } - } - - @Test - public void testStartOpAndFinishOpWillBeCalledWhenPlatformVpnIsOnAndOff() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(mVpnProfile.encode()); - vpn.startVpnProfile(TEST_VPN_PKG); - verifyPlatformVpnIsActivated(TEST_VPN_PKG); - // Add a small delay to make sure that startOp is only called once. - verify(mAppOps, after(100).times(1)).startOp( - eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER), - eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())), - eq(TEST_VPN_PKG), - eq(null) /* attributionTag */, - eq(null) /* message */); - // Check that the startOp is not called with OPSTR_ESTABLISH_VPN_SERVICE. - verify(mAppOps, never()).startOp( - eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE), - eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())), - eq(TEST_VPN_PKG), - eq(null) /* attributionTag */, - eq(null) /* message */); - vpn.stopVpnProfile(TEST_VPN_PKG); - verifyPlatformVpnIsDeactivated(TEST_VPN_PKG); - } - - @Test - public void testStartOpWithSeamlessHandover() throws Exception { - // Create with SYSTEM_USER so that establish() will match the user ID when checking - // against Binder.getCallerUid - final Vpn vpn = createVpn(SYSTEM_USER, AppOpsManager.OPSTR_ACTIVATE_VPN); - assertTrue(vpn.prepare(TEST_VPN_PKG, null, VpnManager.TYPE_VPN_SERVICE)); - final VpnConfig config = new VpnConfig(); - config.user = "VpnTest"; - config.addresses.add(new LinkAddress("192.0.2.2/32")); - config.mtu = 1450; - final ResolveInfo resolveInfo = new ResolveInfo(); - final ServiceInfo serviceInfo = new ServiceInfo(); - serviceInfo.permission = BIND_VPN_SERVICE; - resolveInfo.serviceInfo = serviceInfo; - when(mPackageManager.resolveService(any(), anyInt())).thenReturn(resolveInfo); - when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true); - vpn.establish(config); - verify(mAppOps, times(1)).startOp( - eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE), - eq(Process.myUid()), - eq(TEST_VPN_PKG), - eq(null) /* attributionTag */, - eq(null) /* message */); - // Call establish() twice with the same config, it should match seamless handover case and - // startOp() shouldn't be called again. - vpn.establish(config); - verify(mAppOps, times(1)).startOp( - eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE), - eq(Process.myUid()), - eq(TEST_VPN_PKG), - eq(null) /* attributionTag */, - eq(null) /* message */); - } - - private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass, - int errorCode, String[] packageName, @NonNull VpnProfileState... profileState) { - final Context userContext = - mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */); - final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); - - final int verifyTimes = profileState.length; - verify(userContext, timeout(TEST_TIMEOUT_MS).times(verifyTimes)) - .startService(intentArgumentCaptor.capture()); - - for (int i = 0; i < verifyTimes; i++) { - final Intent intent = intentArgumentCaptor.getAllValues().get(i); - assertEquals(packageName[i], intent.getPackage()); - assertEquals(sessionKey, intent.getStringExtra(VpnManager.EXTRA_SESSION_KEY)); - final Set<String> categories = intent.getCategories(); - assertTrue(categories.contains(category)); - assertEquals(1, categories.size()); - assertEquals(errorClass, - intent.getIntExtra(VpnManager.EXTRA_ERROR_CLASS, -1 /* defaultValue */)); - assertEquals(errorCode, - intent.getIntExtra(VpnManager.EXTRA_ERROR_CODE, -1 /* defaultValue */)); - // CATEGORY_EVENT_DEACTIVATED_BY_USER & CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED won't - // send NetworkCapabilities & LinkProperties to VPN app. - // For ERROR_CODE_NETWORK_LOST, the NetworkCapabilities & LinkProperties of underlying - // network will be cleared. So the VPN app will receive null for those 2 extra values. - if (category.equals(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER) - || category.equals(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED) - || errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) { - assertNull(intent.getParcelableExtra( - VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES)); - assertNull(intent.getParcelableExtra(VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES)); - } else { - assertNotNull(intent.getParcelableExtra( - VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES)); - assertNotNull(intent.getParcelableExtra( - VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES)); - } - - assertEquals(profileState[i], intent.getParcelableExtra( - VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class)); - } - reset(userContext); - } - - private void verifyDeactivatedByUser(String sessionKey, String[] packageName) { - // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and - // errorCode won't be set. - verifyVpnManagerEvent(sessionKey, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER, - -1 /* errorClass */, -1 /* errorCode */, packageName, - // VPN NetworkAgnet does not switch to CONNECTED in the test, and the state is not - // important here. Verify that the state as it is, i.e. CONNECTING state. - new VpnProfileState(VpnProfileState.STATE_CONNECTING, - sessionKey, false /* alwaysOn */, false /* lockdown */)); - } - - private void verifyAlwaysOnStateChanged(String[] packageName, VpnProfileState... profileState) { - verifyVpnManagerEvent(null /* sessionKey */, - VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */, - -1 /* errorCode */, packageName, profileState); - } - - @Test - public void testVpnManagerEventForUserDeactivated() throws Exception { - // For security reasons, Vpn#prepare() will check that oldPackage and newPackage are either - // null or the package of the caller. This test will call Vpn#prepare() to pretend the old - // VPN is replaced by a new one. But only Settings can change to some other packages, and - // this is checked with CONTROL_VPN so simulate holding CONTROL_VPN in order to pass the - // security checks. - doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN); - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(mVpnProfile.encode()); - - // Test the case that the user deactivates the vpn in vpn app. - final String sessionKey1 = vpn.startVpnProfile(TEST_VPN_PKG); - verifyPlatformVpnIsActivated(TEST_VPN_PKG); - vpn.stopVpnProfile(TEST_VPN_PKG); - verifyPlatformVpnIsDeactivated(TEST_VPN_PKG); - verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG); - reset(mDeviceIdleInternal); - verifyDeactivatedByUser(sessionKey1, new String[] {TEST_VPN_PKG}); - reset(mAppOps); - - // Test the case that the user chooses another vpn and the original one is replaced. - final String sessionKey2 = vpn.startVpnProfile(TEST_VPN_PKG); - verifyPlatformVpnIsActivated(TEST_VPN_PKG); - vpn.prepare(TEST_VPN_PKG, "com.new.vpn" /* newPackage */, TYPE_VPN_PLATFORM); - verifyPlatformVpnIsDeactivated(TEST_VPN_PKG); - verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG); - reset(mDeviceIdleInternal); - verifyDeactivatedByUser(sessionKey2, new String[] {TEST_VPN_PKG}); - } - - @Test - public void testVpnManagerEventForAlwaysOnChanged() throws Exception { - // Calling setAlwaysOnPackage() needs to hold CONTROL_VPN. - doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN); - final Vpn vpn = createVpn(PRIMARY_USER.id); - // Enable VPN always-on for PKGS[1]. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */, - null /* lockdownAllowlist */)); - verifyPowerSaveTempWhitelistApp(PKGS[1]); - reset(mDeviceIdleInternal); - verifyAlwaysOnStateChanged(new String[] {PKGS[1]}, - new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, - null /* sessionKey */, true /* alwaysOn */, false /* lockdown */)); - - // Enable VPN lockdown for PKGS[1]. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true /* lockdown */, - null /* lockdownAllowlist */)); - verifyPowerSaveTempWhitelistApp(PKGS[1]); - reset(mDeviceIdleInternal); - verifyAlwaysOnStateChanged(new String[] {PKGS[1]}, - new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, - null /* sessionKey */, true /* alwaysOn */, true /* lockdown */)); - - // Disable VPN lockdown for PKGS[1]. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */, - null /* lockdownAllowlist */)); - verifyPowerSaveTempWhitelistApp(PKGS[1]); - reset(mDeviceIdleInternal); - verifyAlwaysOnStateChanged(new String[] {PKGS[1]}, - new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, - null /* sessionKey */, true /* alwaysOn */, false /* lockdown */)); - - // Disable VPN always-on. - assertTrue(vpn.setAlwaysOnPackage(null, false /* lockdown */, - null /* lockdownAllowlist */)); - verifyPowerSaveTempWhitelistApp(PKGS[1]); - reset(mDeviceIdleInternal); - verifyAlwaysOnStateChanged(new String[] {PKGS[1]}, - new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, - null /* sessionKey */, false /* alwaysOn */, false /* lockdown */)); - - // Enable VPN always-on for PKGS[1] again. - assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */, - null /* lockdownAllowlist */)); - verifyPowerSaveTempWhitelistApp(PKGS[1]); - reset(mDeviceIdleInternal); - verifyAlwaysOnStateChanged(new String[] {PKGS[1]}, - new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, - null /* sessionKey */, true /* alwaysOn */, false /* lockdown */)); - - // Enable VPN always-on for PKGS[2]. - assertTrue(vpn.setAlwaysOnPackage(PKGS[2], false /* lockdown */, - null /* lockdownAllowlist */)); - verifyPowerSaveTempWhitelistApp(PKGS[2]); - reset(mDeviceIdleInternal); - // PKGS[1] is replaced with PKGS[2]. - // Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to - // PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to - // PKGS[2] to notify PKGS[2] that the VPN always-on is enabled. - verifyAlwaysOnStateChanged(new String[] {PKGS[1], PKGS[2]}, - new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, - null /* sessionKey */, false /* alwaysOn */, false /* lockdown */), - new VpnProfileState(VpnProfileState.STATE_DISCONNECTED, - null /* sessionKey */, true /* alwaysOn */, false /* lockdown */)); - } - - @Test - public void testReconnectVpnManagerVpnWithAlwaysOnEnabled() throws Exception { - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(mVpnProfile.encode()); - vpn.startVpnProfile(TEST_VPN_PKG); - verifyPlatformVpnIsActivated(TEST_VPN_PKG); - - // Enable VPN always-on for TEST_VPN_PKG. - assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */, - null /* lockdownAllowlist */)); - - // Reset to verify next startVpnProfile. - reset(mAppOps); - - vpn.stopVpnProfile(TEST_VPN_PKG); - - // Reconnect the vpn with different package will cause exception. - assertThrows(SecurityException.class, () -> vpn.startVpnProfile(PKGS[0])); - - // Reconnect the vpn again with the vpn always on package w/o exception. - vpn.startVpnProfile(TEST_VPN_PKG); - verifyPlatformVpnIsActivated(TEST_VPN_PKG); - } - - @Test - public void testLockdown_enableDisableWhileConnected() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - - final InOrder order = inOrder(mTestDeps); - order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS)) - .newNetworkAgent(any(), any(), any(), any(), any(), any(), - argThat(config -> config.allowBypass), any(), any()); - - // Make VPN lockdown. - assertTrue(vpnSnapShot.vpn.setAlwaysOnPackage(TEST_VPN_PKG, true /* lockdown */, - null /* lockdownAllowlist */)); - - order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS)) - .newNetworkAgent(any(), any(), any(), any(), any(), any(), - argThat(config -> !config.allowBypass), any(), any()); - - // Disable lockdown. - assertTrue(vpnSnapShot.vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */, - null /* lockdownAllowlist */)); - - order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS)) - .newNetworkAgent(any(), any(), any(), any(), any(), any(), - argThat(config -> config.allowBypass), any(), any()); - } - - @Test - public void testSetPackageAuthorizationVpnService() throws Exception { - final Vpn vpn = createVpn(); - - assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE)); - verify(mAppOps) - .setMode( - eq(AppOpsManager.OPSTR_ACTIVATE_VPN), - eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())), - eq(TEST_VPN_PKG), - eq(AppOpsManager.MODE_ALLOWED)); - } - - @Test - public void testSetPackageAuthorizationPlatformVpn() throws Exception { - final Vpn vpn = createVpn(); - - assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, TYPE_VPN_PLATFORM)); - verify(mAppOps) - .setMode( - eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), - eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())), - eq(TEST_VPN_PKG), - eq(AppOpsManager.MODE_ALLOWED)); - } - - @Test - public void testSetPackageAuthorizationRevokeAuthorization() throws Exception { - final Vpn vpn = createVpn(); - - assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE)); - verify(mAppOps) - .setMode( - eq(AppOpsManager.OPSTR_ACTIVATE_VPN), - eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())), - eq(TEST_VPN_PKG), - eq(AppOpsManager.MODE_IGNORED)); - verify(mAppOps) - .setMode( - eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), - eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())), - eq(TEST_VPN_PKG), - eq(AppOpsManager.MODE_IGNORED)); - } - - private NetworkCallback triggerOnAvailableAndGetCallback() throws Exception { - return triggerOnAvailableAndGetCallback(new NetworkCapabilities.Builder().build()); - } - - private NetworkCallback triggerOnAvailableAndGetCallback( - @NonNull final NetworkCapabilities caps) throws Exception { - final ArgumentCaptor<NetworkCallback> networkCallbackCaptor = - ArgumentCaptor.forClass(NetworkCallback.class); - verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)) - .registerSystemDefaultNetworkCallback(networkCallbackCaptor.capture(), any()); - - // onAvailable() will trigger onDefaultNetworkChanged(), so NetdUtils#setInterfaceUp will be - // invoked. Set the return value of INetd#interfaceGetCfg to prevent NullPointerException. - final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel(); - config.flags = new String[] {IF_STATE_DOWN}; - when(mNetd.interfaceGetCfg(anyString())).thenReturn(config); - final NetworkCallback cb = networkCallbackCaptor.getValue(); - cb.onAvailable(TEST_NETWORK); - // Trigger onCapabilitiesChanged() and onLinkPropertiesChanged() so the test can verify that - // if NetworkCapabilities and LinkProperties of underlying network will be sent/cleared or - // not. - // See verifyVpnManagerEvent(). - cb.onCapabilitiesChanged(TEST_NETWORK, caps); - cb.onLinkPropertiesChanged(TEST_NETWORK, new LinkProperties()); - return cb; - } - - private void verifyInterfaceSetCfgWithFlags(String flag) throws Exception { - // Add a timeout for waiting for interfaceSetCfg to be called. - verify(mNetd, timeout(TEST_TIMEOUT_MS)).interfaceSetCfg(argThat( - config -> Arrays.asList(config.flags).contains(flag))); - } - - private void doTestPlatformVpnWithException(IkeException exception, - String category, int errorType, int errorCode) throws Exception { - final ArgumentCaptor<IkeSessionCallback> captor = - ArgumentCaptor.forClass(IkeSessionCallback.class); - - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(mVpnProfile.encode()); - - doReturn(new NetworkCapabilities()).when(mConnectivityManager) - .getRedactedNetworkCapabilitiesForPackage(any(), anyInt(), anyString()); - doReturn(new LinkProperties()).when(mConnectivityManager) - .getRedactedLinkPropertiesForPackage(any(), anyInt(), anyString()); - - final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG); - final Set<Range<Integer>> uidRanges = rangeSet(PRIMARY_USER_RANGE); - // This is triggered by Ikev2VpnRunner constructor. - verify(mConnectivityManager, times(1)).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges)); - final NetworkCallback cb = triggerOnAvailableAndGetCallback(); - - verifyInterfaceSetCfgWithFlags(IF_STATE_UP); - - // Wait for createIkeSession() to be called before proceeding in order to ensure consistent - // state - verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)) - .createIkeSession(any(), any(), any(), any(), captor.capture(), any()); - // This is triggered by Vpn#startOrMigrateIkeSession(). - verify(mConnectivityManager, times(2)).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges)); - reset(mIkev2SessionCreator); - // For network lost case, the process should be triggered by calling onLost(), which is the - // same process with the real case. - if (errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) { - cb.onLost(TEST_NETWORK); - verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any()); - } else { - final IkeSessionCallback ikeCb = captor.getValue(); - mExecutor.execute(() -> ikeCb.onClosedWithException(exception)); - } - - verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG); - reset(mDeviceIdleInternal); - verifyVpnManagerEvent(sessionKey, category, errorType, errorCode, - // VPN NetworkAgnet does not switch to CONNECTED in the test, and the state is not - // important here. Verify that the state as it is, i.e. CONNECTING state. - new String[] {TEST_VPN_PKG}, new VpnProfileState(VpnProfileState.STATE_CONNECTING, - sessionKey, false /* alwaysOn */, false /* lockdown */)); - if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) { - verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), - eq(Collections.EMPTY_LIST)); - verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)) - .unregisterNetworkCallback(eq(cb)); - } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE - // Vpn won't retry when there is no usable underlying network. - && errorCode != VpnManager.ERROR_CODE_NETWORK_LOST) { - int retryIndex = 0; - // First failure occurred above. - final IkeSessionCallback retryCb = verifyRetryAndGetNewIkeCb(retryIndex++); - // Trigger 2 more failures to let the retry delay increase to 5s. - mExecutor.execute(() -> retryCb.onClosedWithException(exception)); - final IkeSessionCallback retryCb2 = verifyRetryAndGetNewIkeCb(retryIndex++); - mExecutor.execute(() -> retryCb2.onClosedWithException(exception)); - final IkeSessionCallback retryCb3 = verifyRetryAndGetNewIkeCb(retryIndex++); - - // setVpnDefaultForUids may be called again but the uidRanges should not change. - verify(mConnectivityManager, atLeast(2)).setVpnDefaultForUids(eq(sessionKey), - mUidRangesCaptor.capture()); - final List<Collection<Range<Integer>>> capturedUidRanges = - mUidRangesCaptor.getAllValues(); - for (int i = 2; i < capturedUidRanges.size(); i++) { - // Assert equals no order. - assertTrue( - "uid ranges should not be modified. Expected: " + uidRanges - + ", actual: " + capturedUidRanges.get(i), - capturedUidRanges.get(i).containsAll(uidRanges) - && capturedUidRanges.get(i).size() == uidRanges.size()); - } - - // A fourth failure will cause the retry delay to be greater than 5s. - mExecutor.execute(() -> retryCb3.onClosedWithException(exception)); - verifyRetryAndGetNewIkeCb(retryIndex++); - - // The VPN network preference will be cleared when the retry delay is greater than 5s. - verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), - eq(Collections.EMPTY_LIST)); - } - } - - private IkeSessionCallback verifyRetryAndGetNewIkeCb(int retryIndex) { - final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor = - ArgumentCaptor.forClass(IkeSessionCallback.class); - - // Verify retry is scheduled - final long expectedDelayMs = mTestDeps.getNextRetryDelayMs(retryIndex); - verify(mExecutor, timeout(TEST_TIMEOUT_MS)).schedule(any(Runnable.class), - eq(expectedDelayMs), eq(TimeUnit.MILLISECONDS)); - - verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelayMs)) - .createIkeSession(any(), any(), any(), any(), ikeCbCaptor.capture(), any()); - - // Forget the mIkev2SessionCreator#createIkeSession call and mExecutor#schedule call - // for the next retry verification - resetIkev2SessionCreator(mIkeSessionWrapper); - - return ikeCbCaptor.getValue(); - } - - @Test - public void testStartPlatformVpnAuthenticationFailed() throws Exception { - final IkeProtocolException exception = mock(IkeProtocolException.class); - final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED; - when(exception.getErrorType()).thenReturn(errorCode); - doTestPlatformVpnWithException(exception, - VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE, - errorCode); - } - - @Test - public void testStartPlatformVpnFailedWithRecoverableError() throws Exception { - final IkeProtocolException exception = mock(IkeProtocolException.class); - final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE; - when(exception.getErrorType()).thenReturn(errorCode); - doTestPlatformVpnWithException(exception, - VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode); - } - - @Test - public void testStartPlatformVpnFailedWithUnknownHostException() throws Exception { - final IkeNonProtocolException exception = mock(IkeNonProtocolException.class); - final UnknownHostException unknownHostException = new UnknownHostException(); - final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST; - when(exception.getCause()).thenReturn(unknownHostException); - doTestPlatformVpnWithException(exception, - VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, - errorCode); - } - - @Test - public void testStartPlatformVpnFailedWithIkeTimeoutException() throws Exception { - final IkeNonProtocolException exception = mock(IkeNonProtocolException.class); - final IkeTimeoutException ikeTimeoutException = - new IkeTimeoutException("IkeTimeoutException"); - final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT; - when(exception.getCause()).thenReturn(ikeTimeoutException); - doTestPlatformVpnWithException(exception, - VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, - errorCode); - } - - @Test - public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception { - final IkeNetworkLostException exception = new IkeNetworkLostException( - new Network(100)); - doTestPlatformVpnWithException(exception, - VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, - VpnManager.ERROR_CODE_NETWORK_LOST); - } - - @Test - public void testStartPlatformVpnFailedWithIOException() throws Exception { - final IkeNonProtocolException exception = mock(IkeNonProtocolException.class); - final IOException ioException = new IOException(); - final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO; - when(exception.getCause()).thenReturn(ioException); - doTestPlatformVpnWithException(exception, - VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, - errorCode); - } - - @Test - public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception { - when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any())) - .thenThrow(new IllegalArgumentException()); - final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile); - final NetworkCallback cb = triggerOnAvailableAndGetCallback(); - - verifyInterfaceSetCfgWithFlags(IF_STATE_UP); - - // Wait for createIkeSession() to be called before proceeding in order to ensure consistent - // state - verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb)); - assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state); - } - - @Test - public void testVpnManagerEventWillNotBeSentToSettingsVpn() throws Exception { - startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile); - triggerOnAvailableAndGetCallback(); - - verifyInterfaceSetCfgWithFlags(IF_STATE_UP); - - final IkeNonProtocolException exception = mock(IkeNonProtocolException.class); - final IkeTimeoutException ikeTimeoutException = - new IkeTimeoutException("IkeTimeoutException"); - when(exception.getCause()).thenReturn(ikeTimeoutException); - - final ArgumentCaptor<IkeSessionCallback> captor = - ArgumentCaptor.forClass(IkeSessionCallback.class); - verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)) - .createIkeSession(any(), any(), any(), any(), captor.capture(), any()); - final IkeSessionCallback ikeCb = captor.getValue(); - ikeCb.onClosedWithException(exception); - - final Context userContext = - mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */); - verify(userContext, never()).startService(any()); - } - - private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) { - assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null)); - - verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); - verify(mAppOps).setMode( - eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG), - eq(AppOpsManager.MODE_ALLOWED)); - - verify(mSystemServices).settingsSecurePutStringForUser( - eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(PRIMARY_USER.id)); - verify(mSystemServices).settingsSecurePutIntForUser( - eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0), - eq(PRIMARY_USER.id)); - verify(mSystemServices).settingsSecurePutStringForUser( - eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(PRIMARY_USER.id)); - } - - @Test - public void testSetAndStartAlwaysOnVpn() throws Exception { - final Vpn vpn = createVpn(PRIMARY_USER.id); - setMockedUsers(PRIMARY_USER); - - // UID checks must return a different UID; otherwise it'll be treated as already prepared. - final int uid = Process.myUid() + 1; - when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) - .thenReturn(uid); - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(mVpnProfile.encode()); - - setAndVerifyAlwaysOnPackage(vpn, uid, false); - assertTrue(vpn.startAlwaysOnVpn()); - - // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in - // a subsequent CL. - } - - private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception { - setMockedUsers(PRIMARY_USER); - vpn.startLegacyVpn(vpnProfile); - return vpn; - } - - private IkeSessionConnectionInfo createIkeConnectInfo() { - return new IkeSessionConnectionInfo(TEST_VPN_CLIENT_IP, TEST_VPN_SERVER_IP, TEST_NETWORK); - } - - private IkeSessionConnectionInfo createIkeConnectInfo_2() { - return new IkeSessionConnectionInfo( - TEST_VPN_CLIENT_IP_2, TEST_VPN_SERVER_IP_2, TEST_NETWORK_2); - } - - private IkeSessionConfiguration createIkeConfig( - IkeSessionConnectionInfo ikeConnectInfo, boolean isMobikeEnabled) { - final IkeSessionConfiguration.Builder builder = - new IkeSessionConfiguration.Builder(ikeConnectInfo); - - if (isMobikeEnabled) { - builder.addIkeExtension(EXTENSION_TYPE_MOBIKE); - } - - return builder.build(); - } - - private ChildSessionConfiguration createChildConfig() { - return new ChildSessionConfiguration.Builder( - Arrays.asList(IN_TS, IN_TS6), Arrays.asList(OUT_TS, OUT_TS6)) - .addInternalAddress(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN)) - .addInternalAddress(new LinkAddress(TEST_VPN_INTERNAL_IP6, IP6_PREFIX_LEN)) - .addInternalDnsServer(TEST_VPN_INTERNAL_DNS) - .addInternalDnsServer(TEST_VPN_INTERNAL_DNS6) - .build(); - } - - private IpSecTransform createIpSecTransform() { - return new IpSecTransform(mContext, new IpSecConfig()); - } - - private void verifyApplyTunnelModeTransforms(int expectedTimes) throws Exception { - verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform( - eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_IN), - anyInt(), anyString()); - verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform( - eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_OUT), - anyInt(), anyString()); - } - - private Pair<IkeSessionCallback, ChildSessionCallback> verifyCreateIkeAndCaptureCbs() - throws Exception { - final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor = - ArgumentCaptor.forClass(IkeSessionCallback.class); - final ArgumentCaptor<ChildSessionCallback> childCbCaptor = - ArgumentCaptor.forClass(ChildSessionCallback.class); - - verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)).createIkeSession( - any(), any(), any(), any(), ikeCbCaptor.capture(), childCbCaptor.capture()); - - return new Pair<>(ikeCbCaptor.getValue(), childCbCaptor.getValue()); - } - - private static class PlatformVpnSnapshot { - public final Vpn vpn; - public final NetworkCallback nwCb; - public final IkeSessionCallback ikeCb; - public final ChildSessionCallback childCb; - - PlatformVpnSnapshot(Vpn vpn, NetworkCallback nwCb, - IkeSessionCallback ikeCb, ChildSessionCallback childCb) { - this.vpn = vpn; - this.nwCb = nwCb; - this.ikeCb = ikeCb; - this.childCb = childCb; - } - } - - private PlatformVpnSnapshot verifySetupPlatformVpn(IkeSessionConfiguration ikeConfig) - throws Exception { - return verifySetupPlatformVpn(ikeConfig, true); - } - - private PlatformVpnSnapshot verifySetupPlatformVpn( - IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception { - return verifySetupPlatformVpn(mVpnProfile, ikeConfig, mtuSupportsIpv6); - } - - private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile, - IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception { - return verifySetupPlatformVpn(vpnProfile, ikeConfig, - new NetworkCapabilities.Builder().build() /* underlying network caps */, - mtuSupportsIpv6, false /* areLongLivedTcpConnectionsExpensive */); - } - - private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile, - IkeSessionConfiguration ikeConfig, - @NonNull final NetworkCapabilities underlyingNetworkCaps, - boolean mtuSupportsIpv6, - boolean areLongLivedTcpConnectionsExpensive) throws Exception { - if (!mtuSupportsIpv6) { - doReturn(IPV6_MIN_MTU - 1).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(), - anyBoolean()); - } - - doReturn(mMockNetworkAgent).when(mTestDeps) - .newNetworkAgent( - any(), any(), anyString(), any(), any(), any(), any(), any(), any()); - doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork(); - - final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); - when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) - .thenReturn(vpnProfile.encode()); - - final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG); - final Set<Range<Integer>> uidRanges = Collections.singleton(PRIMARY_USER_RANGE); - verify(mConnectivityManager).setVpnDefaultForUids(eq(sessionKey), eq(uidRanges)); - final NetworkCallback nwCb = triggerOnAvailableAndGetCallback(underlyingNetworkCaps); - // There are 4 interactions with the executor. - // - Network available - // - LP change - // - NC change - // - schedule() calls in scheduleStartIkeSession() - // The first 3 calls are triggered from Executor.execute(). The execute() will also call to - // schedule() with 0 delay. Verify the exact interaction here so that it won't cause flakes - // in the follow-up flow. - verify(mExecutor, timeout(TEST_TIMEOUT_MS).times(4)) - .schedule(any(Runnable.class), anyLong(), any()); - reset(mExecutor); - - // Mock the setup procedure by firing callbacks - final Pair<IkeSessionCallback, ChildSessionCallback> cbPair = - verifyCreateIkeAndCaptureCbs(); - final IkeSessionCallback ikeCb = cbPair.first; - final ChildSessionCallback childCb = cbPair.second; - - ikeCb.onOpened(ikeConfig); - childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN); - childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT); - childCb.onOpened(createChildConfig()); - - // Verification VPN setup - verifyApplyTunnelModeTransforms(1); - - ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class); - ArgumentCaptor<NetworkCapabilities> ncCaptor = - ArgumentCaptor.forClass(NetworkCapabilities.class); - ArgumentCaptor<NetworkAgentConfig> nacCaptor = - ArgumentCaptor.forClass(NetworkAgentConfig.class); - verify(mTestDeps).newNetworkAgent( - any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(), - any(), nacCaptor.capture(), any(), any()); - verify(mIkeSessionWrapper).setUnderpinnedNetwork(TEST_NETWORK); - // Check LinkProperties - final LinkProperties lp = lpCaptor.getValue(); - final List<RouteInfo> expectedRoutes = - new ArrayList<>( - Arrays.asList( - new RouteInfo( - new IpPrefix(Inet4Address.ANY, 0), - null /* gateway */, - TEST_IFACE_NAME, - RouteInfo.RTN_UNICAST))); - final List<LinkAddress> expectedAddresses = - new ArrayList<>( - Arrays.asList(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN))); - final List<InetAddress> expectedDns = new ArrayList<>(Arrays.asList(TEST_VPN_INTERNAL_DNS)); - - if (mtuSupportsIpv6) { - expectedRoutes.add( - new RouteInfo( - new IpPrefix(Inet6Address.ANY, 0), - null /* gateway */, - TEST_IFACE_NAME, - RouteInfo.RTN_UNICAST)); - expectedAddresses.add(new LinkAddress(TEST_VPN_INTERNAL_IP6, IP6_PREFIX_LEN)); - expectedDns.add(TEST_VPN_INTERNAL_DNS6); - } else { - expectedRoutes.add( - new RouteInfo( - new IpPrefix(Inet6Address.ANY, 0), - null /* gateway */, - TEST_IFACE_NAME, - RTN_UNREACHABLE)); - } - - assertEquals(expectedRoutes, lp.getRoutes()); - assertEquals(expectedAddresses, lp.getLinkAddresses()); - assertEquals(expectedDns, lp.getDnsServers()); - - // Check NetworkCapabilities - assertEquals(Arrays.asList(TEST_NETWORK), ncCaptor.getValue().getUnderlyingNetworks()); - - // Check if allowBypass is set or not. - assertTrue(nacCaptor.getValue().isBypassableVpn()); - // Check if extra info for VPN is set. - assertTrue(nacCaptor.getValue().getLegacyExtraInfo().contains(TEST_VPN_PKG)); - final VpnTransportInfo info = (VpnTransportInfo) ncCaptor.getValue().getTransportInfo(); - assertTrue(info.isBypassable()); - assertEquals(areLongLivedTcpConnectionsExpensive, - info.areLongLivedTcpConnectionsExpensive()); - return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb); - } - - @Test - public void testStartPlatformVpn() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); - verify(mConnectivityManager).setVpnDefaultForUids(anyString(), eq(Collections.EMPTY_LIST)); - } - - @Test - public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerNoTimer() throws Exception { - doTestMigrateIkeSession_FromIkeTunnConnParams( - false /* isAutomaticIpVersionSelectionEnabled */, - true /* isAutomaticNattKeepaliveTimerEnabled */, - TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */, - ESP_IP_VERSION_AUTO /* ipVersionInProfile */, - ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */); - } - - @Test - public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerTimerSet() throws Exception { - doTestMigrateIkeSession_FromIkeTunnConnParams( - false /* isAutomaticIpVersionSelectionEnabled */, - true /* isAutomaticNattKeepaliveTimerEnabled */, - TEST_KEEPALIVE_TIMER /* keepaliveInProfile */, - ESP_IP_VERSION_AUTO /* ipVersionInProfile */, - ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */); - } - - @Test - public void testMigrateIkeSession_FromIkeTunnConnParams_AutoIp() throws Exception { - doTestMigrateIkeSession_FromIkeTunnConnParams( - true /* isAutomaticIpVersionSelectionEnabled */, - false /* isAutomaticNattKeepaliveTimerEnabled */, - TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */, - ESP_IP_VERSION_AUTO /* ipVersionInProfile */, - ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */); - } - - @Test - public void testMigrateIkeSession_FromIkeTunnConnParams_AssignedIpProtocol() throws Exception { - doTestMigrateIkeSession_FromIkeTunnConnParams( - false /* isAutomaticIpVersionSelectionEnabled */, - false /* isAutomaticNattKeepaliveTimerEnabled */, - TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */, - ESP_IP_VERSION_IPV4 /* ipVersionInProfile */, - ESP_ENCAP_TYPE_UDP /* encapTypeInProfile */); - } - - @Test - public void testMigrateIkeSession_FromNotIkeTunnConnParams_AutoTimer() throws Exception { - doTestMigrateIkeSession_FromNotIkeTunnConnParams( - false /* isAutomaticIpVersionSelectionEnabled */, - true /* isAutomaticNattKeepaliveTimerEnabled */); - } - - @Test - public void testMigrateIkeSession_FromNotIkeTunnConnParams_AutoIp() throws Exception { - doTestMigrateIkeSession_FromNotIkeTunnConnParams( - true /* isAutomaticIpVersionSelectionEnabled */, - false /* isAutomaticNattKeepaliveTimerEnabled */); - } - - private void doTestMigrateIkeSession_FromNotIkeTunnConnParams( - boolean isAutomaticIpVersionSelectionEnabled, - boolean isAutomaticNattKeepaliveTimerEnabled) throws Exception { - final Ikev2VpnProfile ikeProfile = - new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY) - .setAuthPsk(TEST_VPN_PSK) - .setBypassable(true /* isBypassable */) - .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled) - .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled) - .build(); - - final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled - ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS - : DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT; - doTestMigrateIkeSession(ikeProfile.toVpnProfile(), - expectedKeepalive, - ESP_IP_VERSION_AUTO /* expectedIpVersion */, - ESP_ENCAP_TYPE_AUTO /* expectedEncapType */, - new NetworkCapabilities.Builder().build()); - } - - private Ikev2VpnProfile makeIkeV2VpnProfile( - boolean isAutomaticIpVersionSelectionEnabled, - boolean isAutomaticNattKeepaliveTimerEnabled, - int keepaliveInProfile, - int ipVersionInProfile, - int encapTypeInProfile) { - // TODO: Update helper function in IkeSessionTestUtils to support building IkeSessionParams - // with IP version and encap type when mainline-prod branch support these two APIs. - final IkeSessionParams params = getTestIkeSessionParams(true /* testIpv6 */, - new IkeFqdnIdentification(TEST_IDENTITY), keepaliveInProfile); - final IkeSessionParams ikeSessionParams = new IkeSessionParams.Builder(params) - .setIpVersion(ipVersionInProfile) - .setEncapType(encapTypeInProfile) - .build(); - - final IkeTunnelConnectionParams tunnelParams = - new IkeTunnelConnectionParams(ikeSessionParams, CHILD_PARAMS); - return new Ikev2VpnProfile.Builder(tunnelParams) - .setBypassable(true) - .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled) - .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled) - .build(); - } - - private void doTestMigrateIkeSession_FromIkeTunnConnParams( - boolean isAutomaticIpVersionSelectionEnabled, - boolean isAutomaticNattKeepaliveTimerEnabled, - int keepaliveInProfile, - int ipVersionInProfile, - int encapTypeInProfile) throws Exception { - doTestMigrateIkeSession_FromIkeTunnConnParams(isAutomaticIpVersionSelectionEnabled, - isAutomaticNattKeepaliveTimerEnabled, keepaliveInProfile, ipVersionInProfile, - encapTypeInProfile, new NetworkCapabilities.Builder().build()); - } - - private void doTestMigrateIkeSession_FromIkeTunnConnParams( - boolean isAutomaticIpVersionSelectionEnabled, - boolean isAutomaticNattKeepaliveTimerEnabled, - int keepaliveInProfile, - int ipVersionInProfile, - int encapTypeInProfile, - @NonNull final NetworkCapabilities nc) throws Exception { - final Ikev2VpnProfile ikeProfile = makeIkeV2VpnProfile( - isAutomaticIpVersionSelectionEnabled, - isAutomaticNattKeepaliveTimerEnabled, - keepaliveInProfile, - ipVersionInProfile, - encapTypeInProfile); - - final IkeSessionParams ikeSessionParams = - ikeProfile.getIkeTunnelConnectionParams().getIkeSessionParams(); - final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled - ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS - : ikeSessionParams.getNattKeepAliveDelaySeconds(); - final int expectedIpVersion = isAutomaticIpVersionSelectionEnabled - ? ESP_IP_VERSION_AUTO - : ikeSessionParams.getIpVersion(); - final int expectedEncapType = isAutomaticIpVersionSelectionEnabled - ? ESP_ENCAP_TYPE_AUTO - : ikeSessionParams.getEncapType(); - doTestMigrateIkeSession(ikeProfile.toVpnProfile(), expectedKeepalive, - expectedIpVersion, expectedEncapType, nc); - } - - @Test - public void doTestMigrateIkeSession_Vcn() throws Exception { - final int expectedKeepalive = 2097; // Any unlikely number will do - final NetworkCapabilities vcnNc = new NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_CELLULAR) - .setTransportInfo(new VcnTransportInfo(TEST_SUB_ID, expectedKeepalive)) - .build(); - final Ikev2VpnProfile ikev2VpnProfile = makeIkeV2VpnProfile( - true /* isAutomaticIpVersionSelectionEnabled */, - true /* isAutomaticNattKeepaliveTimerEnabled */, - 234 /* keepaliveInProfile */, // Should be ignored, any value will do - ESP_IP_VERSION_IPV4, // Should be ignored - ESP_ENCAP_TYPE_UDP // Should be ignored - ); - doTestMigrateIkeSession( - ikev2VpnProfile.toVpnProfile(), - expectedKeepalive, - ESP_IP_VERSION_AUTO /* expectedIpVersion */, - ESP_ENCAP_TYPE_AUTO /* expectedEncapType */, - vcnNc); - } - - private void doTestMigrateIkeSession( - @NonNull final VpnProfile profile, - final int expectedKeepalive, - final int expectedIpVersion, - final int expectedEncapType, - @NonNull final NetworkCapabilities caps) throws Exception { - final PlatformVpnSnapshot vpnSnapShot = - verifySetupPlatformVpn(profile, - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */), - caps /* underlying network capabilities */, - false /* mtuSupportsIpv6 */, - expectedKeepalive < DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC); - // Simulate a new network coming up - vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); - verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt()); - - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, caps); - // Verify MOBIKE is triggered - verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2, - expectedIpVersion, expectedEncapType, expectedKeepalive); - - vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); - } - - @Test - public void testLinkPropertiesUpdateTriggerReevaluation() throws Exception { - final boolean hasV6 = true; - - mockCarrierConfig(TEST_SUB_ID, TelephonyManager.SIM_STATE_LOADED, TEST_KEEPALIVE_TIMER, - PREFERRED_IKE_PROTOCOL_IPV6_ESP); - final IkeSessionParams params = getTestIkeSessionParams(hasV6, - new IkeFqdnIdentification(TEST_IDENTITY), TEST_KEEPALIVE_TIMER); - final IkeTunnelConnectionParams tunnelParams = - new IkeTunnelConnectionParams(params, CHILD_PARAMS); - final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams) - .setBypassable(true) - .setAutomaticNattKeepaliveTimerEnabled(false) - .setAutomaticIpVersionSelectionEnabled(true) - .build(); - final PlatformVpnSnapshot vpnSnapShot = - verifySetupPlatformVpn(ikeProfile.toVpnProfile(), - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */), - new NetworkCapabilities.Builder().build() /* underlying network caps */, - hasV6 /* mtuSupportsIpv6 */, - false /* areLongLivedTcpConnectionsExpensive */); - reset(mExecutor); - - // Simulate a new network coming up - final LinkProperties lp = new LinkProperties(); - lp.addLinkAddress(new LinkAddress("192.0.2.2/32")); - - // Have the executor use the real delay to make sure schedule() was called only - // once for all calls. Also, arrange for execute() not to call schedule() to avoid - // messing with the checks for schedule(). - mExecutor.delayMs = TestExecutor.REAL_DELAY; - mExecutor.executeDirect = true; - vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); - vpnSnapShot.nwCb.onCapabilitiesChanged( - TEST_NETWORK_2, new NetworkCapabilities.Builder().build()); - vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp)); - verify(mExecutor).schedule(any(Runnable.class), longThat(it -> it > 0), any()); - reset(mExecutor); - - final InOrder order = inOrder(mIkeSessionWrapper); - - // Verify the network is started - order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2, - ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER); - - // Send the same properties, check that no migration is scheduled - vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp)); - verify(mExecutor, never()).schedule(any(Runnable.class), anyLong(), any()); - - // Add v6 address, verify MOBIKE is triggered - lp.addLinkAddress(new LinkAddress("2001:db8::1/64")); - vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp)); - order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2, - ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER); - - // Add another v4 address, verify MOBIKE is triggered - final LinkProperties stacked = new LinkProperties(); - stacked.setInterfaceName("v4-" + lp.getInterfaceName()); - stacked.addLinkAddress(new LinkAddress("192.168.0.1/32")); - lp.addStackedLink(stacked); - vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp)); - order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2, - ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER); - - vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); - } - - private void mockCarrierConfig(int subId, int simStatus, int keepaliveTimer, int ikeProtocol) { - final SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class); - doReturn(subId).when(subscriptionInfo).getSubscriptionId(); - doReturn(List.of(subscriptionInfo)).when(mSubscriptionManager) - .getActiveSubscriptionInfoList(); - - doReturn(simStatus).when(mTmPerSub).getSimApplicationState(); - doReturn(TEST_MCCMNC).when(mTmPerSub).getSimOperator(subId); - - final PersistableBundle persistableBundle = new PersistableBundle(); - persistableBundle.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, keepaliveTimer); - persistableBundle.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, ikeProtocol); - // For CarrierConfigManager.isConfigForIdentifiedCarrier check - persistableBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true); - doReturn(persistableBundle).when(mConfigManager).getConfigForSubId(subId); - } - - private CarrierConfigManager.CarrierConfigChangeListener getCarrierConfigListener() { - final ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerCaptor = - ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class); - - verify(mConfigManager).registerCarrierConfigChangeListener(any(), listenerCaptor.capture()); - - return listenerCaptor.getValue(); - } - - @Test - public void testNattKeepaliveTimerFromCarrierConfig_noSubId() throws Exception { - doTestReadCarrierConfig(new NetworkCapabilities(), - TelephonyManager.SIM_STATE_LOADED, - PREFERRED_IKE_PROTOCOL_IPV4_UDP, - AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */, - ESP_IP_VERSION_AUTO /* expectedIpVersion */, - ESP_ENCAP_TYPE_AUTO /* expectedEncapType */, - false /* expectedReadFromCarrierConfig*/, - true /* areLongLivedTcpConnectionsExpensive */); - } - - @Test - public void testNattKeepaliveTimerFromCarrierConfig_simAbsent() throws Exception { - doTestReadCarrierConfig(new NetworkCapabilities.Builder().build(), - TelephonyManager.SIM_STATE_ABSENT, - PREFERRED_IKE_PROTOCOL_IPV4_UDP, - AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */, - ESP_IP_VERSION_AUTO /* expectedIpVersion */, - ESP_ENCAP_TYPE_AUTO /* expectedEncapType */, - false /* expectedReadFromCarrierConfig*/, - true /* areLongLivedTcpConnectionsExpensive */); - } - - @Test - public void testNattKeepaliveTimerFromCarrierConfig() throws Exception { - doTestReadCarrierConfig(createTestCellNc(), - TelephonyManager.SIM_STATE_LOADED, - PREFERRED_IKE_PROTOCOL_AUTO, - TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */, - ESP_IP_VERSION_AUTO /* expectedIpVersion */, - ESP_ENCAP_TYPE_AUTO /* expectedEncapType */, - true /* expectedReadFromCarrierConfig*/, - false /* areLongLivedTcpConnectionsExpensive */); - } - - @Test - public void testNattKeepaliveTimerFromCarrierConfig_NotCell() throws Exception { - final NetworkCapabilities nc = new NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_WIFI) - .setTransportInfo(new WifiInfo.Builder().build()) - .build(); - doTestReadCarrierConfig(nc, - TelephonyManager.SIM_STATE_LOADED, - PREFERRED_IKE_PROTOCOL_IPV4_UDP, - AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */, - ESP_IP_VERSION_AUTO /* expectedIpVersion */, - ESP_ENCAP_TYPE_AUTO /* expectedEncapType */, - false /* expectedReadFromCarrierConfig*/, - true /* areLongLivedTcpConnectionsExpensive */); - } - - @Test - public void testPreferredIpProtocolFromCarrierConfig_v4UDP() throws Exception { - doTestReadCarrierConfig(createTestCellNc(), - TelephonyManager.SIM_STATE_LOADED, - PREFERRED_IKE_PROTOCOL_IPV4_UDP, - TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */, - ESP_IP_VERSION_IPV4 /* expectedIpVersion */, - ESP_ENCAP_TYPE_UDP /* expectedEncapType */, - true /* expectedReadFromCarrierConfig*/, - false /* areLongLivedTcpConnectionsExpensive */); - } - - @Test - public void testPreferredIpProtocolFromCarrierConfig_v6ESP() throws Exception { - doTestReadCarrierConfig(createTestCellNc(), - TelephonyManager.SIM_STATE_LOADED, - PREFERRED_IKE_PROTOCOL_IPV6_ESP, - TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */, - ESP_IP_VERSION_IPV6 /* expectedIpVersion */, - ESP_ENCAP_TYPE_NONE /* expectedEncapType */, - true /* expectedReadFromCarrierConfig*/, - false /* areLongLivedTcpConnectionsExpensive */); - } - - @Test - public void testPreferredIpProtocolFromCarrierConfig_v6UDP() throws Exception { - doTestReadCarrierConfig(createTestCellNc(), - TelephonyManager.SIM_STATE_LOADED, - PREFERRED_IKE_PROTOCOL_IPV6_UDP, - TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */, - ESP_IP_VERSION_IPV6 /* expectedIpVersion */, - ESP_ENCAP_TYPE_UDP /* expectedEncapType */, - true /* expectedReadFromCarrierConfig*/, - false /* areLongLivedTcpConnectionsExpensive */); - } - - private NetworkCapabilities createTestCellNc() { - return new NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder() - .setSubscriptionId(TEST_SUB_ID) - .build()) - .build(); - } - - private void doTestReadCarrierConfig(NetworkCapabilities nc, int simState, int preferredIpProto, - int expectedKeepaliveTimer, int expectedIpVersion, int expectedEncapType, - boolean expectedReadFromCarrierConfig, - boolean areLongLivedTcpConnectionsExpensive) - throws Exception { - final Ikev2VpnProfile ikeProfile = - new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY) - .setAuthPsk(TEST_VPN_PSK) - .setBypassable(true /* isBypassable */) - .setAutomaticNattKeepaliveTimerEnabled(true) - .setAutomaticIpVersionSelectionEnabled(true) - .build(); - - final PlatformVpnSnapshot vpnSnapShot = - verifySetupPlatformVpn(ikeProfile.toVpnProfile(), - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */), - new NetworkCapabilities.Builder().build() /* underlying network caps */, - false /* mtuSupportsIpv6 */, - true /* areLongLivedTcpConnectionsExpensive */); - - final CarrierConfigManager.CarrierConfigChangeListener listener = - getCarrierConfigListener(); - - // Simulate a new network coming up - vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); - // Migration will not be started until receiving network capabilities change. - verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt()); - - reset(mIkeSessionWrapper); - mockCarrierConfig(TEST_SUB_ID, simState, TEST_KEEPALIVE_TIMER, preferredIpProto); - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, nc); - verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2, - expectedIpVersion, expectedEncapType, expectedKeepaliveTimer); - if (expectedReadFromCarrierConfig) { - final ArgumentCaptor<NetworkCapabilities> ncCaptor = - ArgumentCaptor.forClass(NetworkCapabilities.class); - verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS)) - .doSendNetworkCapabilities(ncCaptor.capture()); - - final VpnTransportInfo info = - (VpnTransportInfo) ncCaptor.getValue().getTransportInfo(); - assertEquals(areLongLivedTcpConnectionsExpensive, - info.areLongLivedTcpConnectionsExpensive()); - } else { - verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any()); - } - - reset(mExecutor); - reset(mIkeSessionWrapper); - reset(mMockNetworkAgent); - - // Trigger carrier config change - listener.onCarrierConfigChanged(1 /* logicalSlotIndex */, TEST_SUB_ID, - -1 /* carrierId */, -1 /* specificCarrierId */); - verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2, - expectedIpVersion, expectedEncapType, expectedKeepaliveTimer); - // Expect no NetworkCapabilities change. - // Call to doSendNetworkCapabilities() will not be triggered. - verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any()); - } - - @Test - public void testStartPlatformVpn_mtuDoesNotSupportIpv6() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = - verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */), - false /* mtuSupportsIpv6 */); - vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); - } - - @Test - public void testStartPlatformVpn_underlyingNetworkNotChange() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - // Trigger update on the same network should not cause underlying network change in NC of - // the VPN network - vpnSnapShot.nwCb.onAvailable(TEST_NETWORK); - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, - new NetworkCapabilities.Builder() - .setSubscriptionIds(Set.of(TEST_SUB_ID)) - .build()); - // Verify setNetwork() called but no underlying network update - verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK), - eq(ESP_IP_VERSION_AUTO) /* ipVersion */, - eq(ESP_ENCAP_TYPE_AUTO) /* encapType */, - eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */); - verify(mMockNetworkAgent, never()) - .doSetUnderlyingNetworks(any()); - - vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, - new NetworkCapabilities.Builder().build()); - - // A new network should trigger both setNetwork() and a underlying network update. - verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK_2), - eq(ESP_IP_VERSION_AUTO) /* ipVersion */, - eq(ESP_ENCAP_TYPE_AUTO) /* encapType */, - eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */); - verify(mMockNetworkAgent).doSetUnderlyingNetworks( - Collections.singletonList(TEST_NETWORK_2)); - - vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); - } - - @Test - public void testStartPlatformVpnMobility_mobikeEnabled() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - - // Set new MTU on a different network - final int newMtu = IPV6_MIN_MTU + 1; - doReturn(newMtu).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean()); - - // Mock network loss and verify a cleanup task is scheduled - vpnSnapShot.nwCb.onLost(TEST_NETWORK); - verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any()); - - // Mock new network comes up and the cleanup task is cancelled - vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); - verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt()); - - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, - new NetworkCapabilities.Builder().build()); - // Verify MOBIKE is triggered - verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK_2), - eq(ESP_IP_VERSION_AUTO) /* ipVersion */, - eq(ESP_ENCAP_TYPE_AUTO) /* encapType */, - eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */); - // Verify mNetworkCapabilities is updated - assertEquals( - Collections.singletonList(TEST_NETWORK_2), - vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks()); - verify(mMockNetworkAgent) - .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2)); - - // Mock the MOBIKE procedure - vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2()); - vpnSnapShot.childCb.onIpSecTransformsMigrated( - createIpSecTransform(), createIpSecTransform()); - - verify(mIpSecService).setNetworkForTunnelInterface( - eq(TEST_TUNNEL_RESOURCE_ID), eq(TEST_NETWORK_2), anyString()); - - // Expect 2 times: one for initial setup and one for MOBIKE - verifyApplyTunnelModeTransforms(2); - - // Verify mNetworkAgent is updated - verify(mMockNetworkAgent).doSendLinkProperties(argThat(lp -> lp.getMtu() == newMtu)); - verify(mMockNetworkAgent, never()).unregister(); - // No further doSetUnderlyingNetworks interaction. The interaction count should stay one. - verify(mMockNetworkAgent, times(1)).doSetUnderlyingNetworks(any()); - vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); - } - - @Test - public void testStartPlatformVpnMobility_mobikeEnabledMtuDoesNotSupportIpv6() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = - verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - - // Set MTU below 1280 - final int newMtu = IPV6_MIN_MTU - 1; - doReturn(newMtu).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(), anyBoolean()); - - // Mock new network available & MOBIKE procedures - vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, - new NetworkCapabilities.Builder().build()); - // Verify mNetworkCapabilities is updated - verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS)) - .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2)); - assertEquals( - Collections.singletonList(TEST_NETWORK_2), - vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks()); - - vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2()); - vpnSnapShot.childCb.onIpSecTransformsMigrated( - createIpSecTransform(), createIpSecTransform()); - - // Verify removal of IPv6 addresses and routes triggers a network agent restart - final ArgumentCaptor<LinkProperties> lpCaptor = - ArgumentCaptor.forClass(LinkProperties.class); - verify(mTestDeps, times(2)) - .newNetworkAgent(any(), any(), anyString(), any(), lpCaptor.capture(), any(), any(), - any(), any()); - verify(mMockNetworkAgent).unregister(); - // mMockNetworkAgent is an old NetworkAgent, so it won't update LinkProperties after - // unregistering. - verify(mMockNetworkAgent, never()).doSendLinkProperties(any()); - - final LinkProperties lp = lpCaptor.getValue(); - - for (LinkAddress addr : lp.getLinkAddresses()) { - if (addr.isIpv6()) { - fail("IPv6 address found on VPN with MTU < IPv6 minimum MTU"); - } - } - - for (InetAddress dnsAddr : lp.getDnsServers()) { - if (dnsAddr instanceof Inet6Address) { - fail("IPv6 DNS server found on VPN with MTU < IPv6 minimum MTU"); - } - } - - for (RouteInfo routeInfo : lp.getRoutes()) { - if (routeInfo.getDestinationLinkAddress().isIpv6() - && !routeInfo.isIPv6UnreachableDefault()) { - fail("IPv6 route found on VPN with MTU < IPv6 minimum MTU"); - } - } - - assertEquals(newMtu, lp.getMtu()); - - vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); - } - - @Test - public void testStartPlatformVpnReestablishes_mobikeDisabled() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */)); - - // Forget the first IKE creation to be prepared to capture callbacks of the second - // IKE session - resetIkev2SessionCreator(mock(Vpn.IkeSessionWrapper.class)); - - // Mock network switch - vpnSnapShot.nwCb.onLost(TEST_NETWORK); - vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); - // The old IKE Session will not be killed until receiving network capabilities change. - verify(mIkeSessionWrapper, never()).kill(); - - vpnSnapShot.nwCb.onCapabilitiesChanged( - TEST_NETWORK_2, new NetworkCapabilities.Builder().build()); - // Verify the old IKE Session is killed - verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).kill(); - - // Capture callbacks of the new IKE Session - final Pair<IkeSessionCallback, ChildSessionCallback> cbPair = - verifyCreateIkeAndCaptureCbs(); - final IkeSessionCallback ikeCb = cbPair.first; - final ChildSessionCallback childCb = cbPair.second; - - // Mock the IKE Session setup - ikeCb.onOpened(createIkeConfig(createIkeConnectInfo_2(), false /* isMobikeEnabled */)); - - childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN); - childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT); - childCb.onOpened(createChildConfig()); - - // Expect 2 times since there have been two Session setups - verifyApplyTunnelModeTransforms(2); - - // Verify mNetworkCapabilities and mNetworkAgent are updated - assertEquals( - Collections.singletonList(TEST_NETWORK_2), - vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks()); - verify(mMockNetworkAgent) - .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2)); - - vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); - } - - private String getDump(@NonNull final Vpn vpn) { - final StringWriter sw = new StringWriter(); - final IndentingPrintWriter writer = new IndentingPrintWriter(sw, ""); - vpn.dump(writer); - writer.flush(); - return sw.toString(); - } - - private int countMatches(@NonNull final Pattern regexp, @NonNull final String string) { - final Matcher m = regexp.matcher(string); - int i = 0; - while (m.find()) ++i; - return i; - } - - @Test - public void testNCEventChanges() throws Exception { - final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder() - .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_INTERNET) - .addCapability(NET_CAPABILITY_NOT_RESTRICTED) - .setLinkDownstreamBandwidthKbps(1000) - .setLinkUpstreamBandwidthKbps(500); - - final Ikev2VpnProfile ikeProfile = - new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY) - .setAuthPsk(TEST_VPN_PSK) - .setBypassable(true /* isBypassable */) - .setAutomaticNattKeepaliveTimerEnabled(true) - .setAutomaticIpVersionSelectionEnabled(true) - .build(); - - final PlatformVpnSnapshot vpnSnapShot = - verifySetupPlatformVpn(ikeProfile.toVpnProfile(), - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */), - ncBuilder.build(), false /* mtuSupportsIpv6 */, - true /* areLongLivedTcpConnectionsExpensive */); - - // Calls to onCapabilitiesChanged will be thrown to the executor for execution ; by - // default this will incur a 10ms delay before it's executed, messing with the timing - // of the log and having the checks for counts in equals() below flake. - mExecutor.executeDirect = true; - - // First nc changed triggered by verifySetupPlatformVpn - final Pattern pattern = Pattern.compile("Cap changed from", Pattern.MULTILINE); - final String stage1 = getDump(vpnSnapShot.vpn); - assertEquals(1, countMatches(pattern, stage1)); - - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build()); - final String stage2 = getDump(vpnSnapShot.vpn); - // Was the same caps, there should still be only 1 match - assertEquals(1, countMatches(pattern, stage2)); - - ncBuilder.setLinkDownstreamBandwidthKbps(1200) - .setLinkUpstreamBandwidthKbps(300); - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build()); - final String stage3 = getDump(vpnSnapShot.vpn); - // Was not an important change, should not be logged, still only 1 match - assertEquals(1, countMatches(pattern, stage3)); - - ncBuilder.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build()); - final String stage4 = getDump(vpnSnapShot.vpn); - // Change to caps is important, should cause a new match - assertEquals(2, countMatches(pattern, stage4)); - - ncBuilder.removeCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); - ncBuilder.setLinkDownstreamBandwidthKbps(600); - vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build()); - final String stage5 = getDump(vpnSnapShot.vpn); - // Change to caps is important, should cause a new match even with the unimportant change - assertEquals(3, countMatches(pattern, stage5)); - } - // TODO : beef up event logs tests - - private void verifyHandlingNetworkLoss(PlatformVpnSnapshot vpnSnapShot) throws Exception { - // Forget the #sendLinkProperties during first setup. - reset(mMockNetworkAgent); - - // Mock network loss - vpnSnapShot.nwCb.onLost(TEST_NETWORK); - - // Mock the grace period expires - verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any()); - - final ArgumentCaptor<LinkProperties> lpCaptor = - ArgumentCaptor.forClass(LinkProperties.class); - verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS)) - .doSendLinkProperties(lpCaptor.capture()); - final LinkProperties lp = lpCaptor.getValue(); - - assertNull(lp.getInterfaceName()); - final List<RouteInfo> expectedRoutes = Arrays.asList( - new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /* gateway */, - null /* iface */, RTN_UNREACHABLE), - new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /* gateway */, - null /* iface */, RTN_UNREACHABLE)); - assertEquals(expectedRoutes, lp.getRoutes()); - - verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS)).unregister(); - } - - @Test - public void testStartPlatformVpnHandlesNetworkLoss_mobikeEnabled() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - verifyHandlingNetworkLoss(vpnSnapShot); - } - - @Test - public void testStartPlatformVpnHandlesNetworkLoss_mobikeDisabled() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */)); - verifyHandlingNetworkLoss(vpnSnapShot); - } - - private ConnectivityDiagnosticsCallback getConnectivityDiagCallback() { - final ArgumentCaptor<ConnectivityDiagnosticsCallback> cdcCaptor = - ArgumentCaptor.forClass(ConnectivityDiagnosticsCallback.class); - verify(mCdm).registerConnectivityDiagnosticsCallback( - any(), any(), cdcCaptor.capture()); - return cdcCaptor.getValue(); - } - - private DataStallReport createDataStallReport() { - return new DataStallReport(TEST_NETWORK, 1234 /* reportTimestamp */, - 1 /* detectionMethod */, new LinkProperties(), new NetworkCapabilities(), - new PersistableBundle()); - } - - private void verifyMobikeTriggered(List<Network> expected, int retryIndex) { - // Verify retry is scheduled - final long expectedDelayMs = mTestDeps.getValidationFailRecoveryMs(retryIndex); - final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class); - verify(mExecutor, times(retryIndex + 1)).schedule( - any(Runnable.class), delayCaptor.capture(), eq(TimeUnit.MILLISECONDS)); - final List<Long> delays = delayCaptor.getAllValues(); - assertEquals(expectedDelayMs, (long) delays.get(delays.size() - 1)); - - final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class); - verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS + expectedDelayMs)) - .setNetwork(networkCaptor.capture(), anyInt() /* ipVersion */, - anyInt() /* encapType */, anyInt() /* keepaliveDelay */); - assertEquals(expected, Collections.singletonList(networkCaptor.getValue())); - } - - @Test - public void testDataStallInIkev2VpnMobikeDisabled() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */)); - - doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork(); - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus( - NetworkAgent.VALIDATION_STATUS_NOT_VALID); - - // Should not trigger MOBIKE if MOBIKE is not enabled - verify(mIkeSessionWrapper, never()).setNetwork(any() /* network */, - anyInt() /* ipVersion */, anyInt() /* encapType */, anyInt() /* keepaliveDelay */); - } - - @Test - public void testDataStallInIkev2VpnRecoveredByMobike() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - - doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork(); - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus( - NetworkAgent.VALIDATION_STATUS_NOT_VALID); - // Verify MOBIKE is triggered - verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(), - 0 /* retryIndex */); - // Validation failure on VPN network should trigger a re-evaluation request for the - // underlying network. - verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false); - - reset(mIkev2SessionCreator); - reset(mExecutor); - - // Send validation status update. - // Recovered and get network validated. It should not trigger the ike session reset. - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus( - NetworkAgent.VALIDATION_STATUS_VALID); - // Verify that the retry count is reset. The mValidationFailRetryCount will not be reset - // until the executor finishes the execute() call, so wait until the all tasks are executed. - waitForIdleSerialExecutor(mExecutor, TEST_TIMEOUT_MS); - assertEquals(0, - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).mValidationFailRetryCount); - verify(mIkev2SessionCreator, never()).createIkeSession( - any(), any(), any(), any(), any(), any()); - - reset(mIkeSessionWrapper); - reset(mExecutor); - - // Another validation fail should trigger another reportNetworkConnectivity - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus( - NetworkAgent.VALIDATION_STATUS_NOT_VALID); - verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(), - 0 /* retryIndex */); - verify(mConnectivityManager, times(2)).reportNetworkConnectivity(TEST_NETWORK, false); - } - - @Test - public void testDataStallInIkev2VpnNotRecoveredByMobike() throws Exception { - final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( - createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - - int retry = 0; - doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork(); - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus( - NetworkAgent.VALIDATION_STATUS_NOT_VALID); - verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(), - retry++); - // Validation failure on VPN network should trigger a re-evaluation request for the - // underlying network. - verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false); - reset(mIkev2SessionCreator); - - // Second validation status update. - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus( - NetworkAgent.VALIDATION_STATUS_NOT_VALID); - verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(), - retry++); - // Call to reportNetworkConnectivity should only happen once. No further interaction. - verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false); - - // Use real delay to verify reset session will not be performed if there is an existing - // recovery for resetting the session. - mExecutor.delayMs = TestExecutor.REAL_DELAY; - mExecutor.executeDirect = true; - // Send validation status update should result in ike session reset. - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus( - NetworkAgent.VALIDATION_STATUS_NOT_VALID); - - // Verify session reset is scheduled - long expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++); - final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class); - verify(mExecutor, times(retry)).schedule(any(Runnable.class), delayCaptor.capture(), - eq(TimeUnit.MILLISECONDS)); - final List<Long> delays = delayCaptor.getAllValues(); - assertEquals(expectedDelay, (long) delays.get(delays.size() - 1)); - // Call to reportNetworkConnectivity should only happen once. No further interaction. - verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false); - - // Another invalid status reported should not trigger other scheduled recovery. - expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++); - ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus( - NetworkAgent.VALIDATION_STATUS_NOT_VALID); - verify(mExecutor, never()).schedule( - any(Runnable.class), eq(expectedDelay), eq(TimeUnit.MILLISECONDS)); - - // Verify that session being reset - verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelay)) - .createIkeSession(any(), any(), any(), any(), any(), any()); - // Call to reportNetworkConnectivity should only happen once. No further interaction. - verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false); - } - - @Test - public void testStartLegacyVpnType() throws Exception { - setMockedUsers(PRIMARY_USER); - final Vpn vpn = createVpn(PRIMARY_USER.id); - final VpnProfile profile = new VpnProfile("testProfile" /* key */); - - profile.type = VpnProfile.TYPE_PPTP; - assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile)); - profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK; - assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile)); - } - - @Test - public void testStartLegacyVpnModifyProfile_TypePSK() throws Exception { - setMockedUsers(PRIMARY_USER); - final Vpn vpn = createVpn(PRIMARY_USER.id); - final Ikev2VpnProfile ikev2VpnProfile = - new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY) - .setAuthPsk(TEST_VPN_PSK) - .build(); - final VpnProfile profile = ikev2VpnProfile.toVpnProfile(); - - startLegacyVpn(vpn, profile); - assertEquals(profile, ikev2VpnProfile.toVpnProfile()); - } - - private void assertTransportInfoMatches(NetworkCapabilities nc, int type) { - assertNotNull(nc); - VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo(); - assertNotNull(ti); - assertEquals(type, ti.getType()); - } - - // Make it public and un-final so as to spy it - public class TestDeps extends Vpn.Dependencies { - TestDeps() {} - - @Override - public boolean isCallerSystem() { - return true; - } - - @Override - public PendingIntent getIntentForStatusPanel(Context context) { - return null; - } - - @Override - public ParcelFileDescriptor adoptFd(Vpn vpn, int mtu) { - return new ParcelFileDescriptor(new FileDescriptor()); - } - - @Override - public int jniCreate(Vpn vpn, int mtu) { - // Pick a random positive number as fd to return. - return 345; - } - - @Override - public String jniGetName(Vpn vpn, int fd) { - return TEST_IFACE_NAME; - } - - @Override - public int jniSetAddresses(Vpn vpn, String interfaze, String addresses) { - if (addresses == null) return 0; - // Return the number of addresses. - return addresses.split(" ").length; - } - - @Override - public void setBlocking(FileDescriptor fd, boolean blocking) {} - - @Override - public DeviceIdleInternal getDeviceIdleInternal() { - return mDeviceIdleInternal; - } - - @Override - public long getValidationFailRecoveryMs(int retryCount) { - // Simply return retryCount as the delay seconds for retrying. - return retryCount * 100L; - } - - @Override - public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() { - return mExecutor; - } - - public boolean mIgnoreCallingUidChecks = true; - @Override - public void verifyCallingUidAndPackage(Context context, String packageName, int userId) { - if (!mIgnoreCallingUidChecks) { - super.verifyCallingUidAndPackage(context, packageName, userId); - } - } - } - - /** - * Mock some methods of vpn object. - */ - private Vpn createVpn(@UserIdInt int userId) { - final Context asUserContext = mock(Context.class, AdditionalAnswers.delegatesTo(mContext)); - doReturn(UserHandle.of(userId)).when(asUserContext).getUser(); - when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt())) - .thenReturn(asUserContext); - final TestLooper testLooper = new TestLooper(); - final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, mTestDeps, mNetService, - mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator); - verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat( - provider -> provider.getName().contains("VpnNetworkProvider") - )); - return vpn; - } - - /** - * Populate {@link #mUserManager} with a list of fake users. - */ - private void setMockedUsers(UserInfo... users) { - final Map<Integer, UserInfo> userMap = new ArrayMap<>(); - for (UserInfo user : users) { - userMap.put(user.id, user); - } - - /** - * @see UserManagerService#getUsers(boolean) - */ - doAnswer(invocation -> { - final ArrayList<UserInfo> result = new ArrayList<>(users.length); - for (UserInfo ui : users) { - if (ui.isEnabled() && !ui.partial) { - result.add(ui); - } - } - return result; - }).when(mUserManager).getAliveUsers(); - - doAnswer(invocation -> { - final int id = (int) invocation.getArguments()[0]; - return userMap.get(id); - }).when(mUserManager).getUserInfo(anyInt()); - } - - /** - * Populate {@link #mPackageManager} with a fake packageName-to-UID mapping. - */ - private void setMockedPackages(final Map<String, Integer> packages) { - try { - doAnswer(invocation -> { - final String appName = (String) invocation.getArguments()[0]; - final int userId = (int) invocation.getArguments()[1]; - Integer appId = packages.get(appName); - if (appId == null) throw new PackageManager.NameNotFoundException(appName); - return UserHandle.getUid(userId, appId); - }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt()); - } catch (Exception e) { - } - } -} diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt index f753c93246..b8ebf0fe45 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt @@ -38,6 +38,7 @@ import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.DevSdkIgnoreRunner import com.android.testutils.waitForIdle import java.net.NetworkInterface +import java.time.Duration import java.util.Objects import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit @@ -313,9 +314,9 @@ class MdnsAdvertiserTest { eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any(), any() ) verify(mockInterfaceAdvertiser1).addService( - anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE)) + anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any()) verify(mockInterfaceAdvertiser2).addService( - anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE)) + anyInt(), eq(ALL_NETWORKS_SERVICE_SUBTYPE), any()) doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1) postSync { intAdvCbCaptor1.value.onServiceProbingSucceeded( @@ -489,15 +490,15 @@ class MdnsAdvertiserTest { eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any(), any() ) verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1), - argThat { it.matches(SERVICE_1) }) + argThat { it.matches(SERVICE_1) }, any()) verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2), - argThat { it.matches(expectedRenamed) }) + argThat { it.matches(expectedRenamed) }, any()) verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1), - argThat { it.matches(LONG_SERVICE_1) }) + argThat { it.matches(LONG_SERVICE_1) }, any()) verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2), - argThat { it.matches(expectedLongRenamed) }) + argThat { it.matches(expectedLongRenamed) }, any()) verify(mockInterfaceAdvertiser1).addService(eq(CASE_INSENSITIVE_TEST_SERVICE_ID), - argThat { it.matches(expectedCaseInsensitiveRenamed) }) + argThat { it.matches(expectedCaseInsensitiveRenamed) }, any()) doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1) postSync { intAdvCbCaptor.value.onServiceProbingSucceeded( @@ -532,7 +533,7 @@ class MdnsAdvertiserTest { postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) } verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1), - argThat { it.matches(ALL_NETWORKS_SERVICE) }) + argThat { it.matches(ALL_NETWORKS_SERVICE) }, any()) val updateOptions = MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(true).build() @@ -554,7 +555,24 @@ class MdnsAdvertiserTest { // Newly created MdnsInterfaceAdvertiser will get addService() call. postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_2, mockSocket2, listOf(TEST_LINKADDR2)) } verify(mockInterfaceAdvertiser2).addService(eq(SERVICE_ID_1), - argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) }) + argThat { it.matches(ALL_NETWORKS_SERVICE_SUBTYPE) }, any()) + } + + @Test + fun testAddOrUpdateService_customTtl_registeredSuccess() { + val advertiser = MdnsAdvertiser( + thread.looper, socketProvider, cb, mockDeps, sharedlog, flags, context) + val updateOptions = + MdnsAdvertisingOptions.newBuilder().setTtl(Duration.ofSeconds(30)).build() + + postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE, + updateOptions, TEST_CLIENT_UID_1) } + + val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java) + verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture()) + val socketCb = socketCbCaptor.value + postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) } + verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1), any(), eq(updateOptions)) } @Test diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt index 0637ad11c5..28608bbb3e 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt @@ -132,7 +132,7 @@ class MdnsInterfaceAdvertiserTest { knownServices.add(inv.getArgument(0)) -1 - }.`when`(repository).addService(anyInt(), any()) + }.`when`(repository).addService(anyInt(), any(), any()) doAnswer { inv -> knownServices.remove(inv.getArgument(0)) null @@ -403,9 +403,10 @@ class MdnsInterfaceAdvertiserTest { @Test fun testReplaceExitingService() { doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository) - .addService(eq(TEST_SERVICE_ID_DUPLICATE), any()) - advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1_SUBTYPE) - verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any()) + .addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any()) + advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1_SUBTYPE, + MdnsAdvertisingOptions.getDefaultOptions()) + verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any()) verify(announcer).stop(TEST_SERVICE_ID_DUPLICATE) verify(prober).startProbing(any()) } @@ -413,7 +414,7 @@ class MdnsInterfaceAdvertiserTest { @Test fun testUpdateExistingService() { doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository) - .addService(eq(TEST_SERVICE_ID_DUPLICATE), any()) + .addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any()) val subTypes = setOf("_sub") advertiser.updateService(TEST_SERVICE_ID_DUPLICATE, subTypes) verify(repository).updateService(eq(TEST_SERVICE_ID_DUPLICATE), any()) @@ -427,8 +428,8 @@ class MdnsInterfaceAdvertiserTest { doReturn(serviceId).`when`(testProbingInfo).serviceId doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId) - advertiser.addService(serviceId, serviceInfo) - verify(repository).addService(serviceId, serviceInfo) + advertiser.addService(serviceId, serviceInfo, MdnsAdvertisingOptions.getDefaultOptions()) + verify(repository).addService(serviceId, serviceInfo, null /* ttl */) verify(prober).startProbing(testProbingInfo) // Simulate probing success: continues to announcing diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt index fd8d98ba00..8d1dff60e7 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt @@ -145,7 +145,8 @@ class MdnsRecordRepositoryTest { fun testAddServiceAndProbe() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) assertEquals(0, repository.servicesCount) - assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)) + assertEquals(-1, + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)) assertEquals(1, repository.servicesCount) val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1) @@ -180,10 +181,10 @@ class MdnsRecordRepositoryTest { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1) assertFailsWith(NameConflictException::class) { - repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1) + repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* ttl */) } assertFailsWith(NameConflictException::class) { - repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3) + repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3, null /* ttl */) } } @@ -224,9 +225,9 @@ class MdnsRecordRepositoryTest { @Test fun testInvalidReuseOfServiceId() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1) + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */) assertFailsWith(IllegalArgumentException::class) { - repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2) + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2, null /* ttl */) } } @@ -235,7 +236,7 @@ class MdnsRecordRepositoryTest { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1)) - repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1) + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */) assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1)) val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1) @@ -327,7 +328,7 @@ class MdnsRecordRepositoryTest { repository.exitService(TEST_SERVICE_ID_1) assertEquals(TEST_SERVICE_ID_1, - repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1)) + repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* ttl */)) assertEquals(1, repository.servicesCount) repository.removeService(TEST_SERVICE_ID_2) @@ -824,7 +825,7 @@ class MdnsRecordRepositoryTest { repository.initWithService( TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(), listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24))) - repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2) + repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */) val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353) val query = makeQuery(TYPE_AAAA to TEST_CUSTOM_HOST_1_NAME) @@ -890,7 +891,8 @@ class MdnsRecordRepositoryTest { repository.initWithService( TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, subtypes = setOf(), listOf(LinkAddress(parseNumericAddress("192.0.2.111"), 24))) - repository.addService(TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1) + repository.addService( + TEST_SERVICE_CUSTOM_HOST_ID_1, TEST_SERVICE_CUSTOM_HOST_1, null /* ttl */) repository.removeService(TEST_CUSTOM_HOST_ID_1) repository.removeService(TEST_SERVICE_CUSTOM_HOST_ID_1) @@ -989,8 +991,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServices() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1) - repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2) + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */) + repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */) val packet = MdnsPacket( 0 /* flags */, @@ -1020,8 +1022,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServicesCaseInsensitive() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1) - repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2) + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */) + repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */) val packet = MdnsPacket( 0 /* flags */, @@ -1050,8 +1052,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServices_customHosts_differentAddresses() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1) - repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2) + repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */) + repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */) val packet = MdnsPacket( 0, /* flags */ @@ -1074,8 +1076,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServices_customHosts_moreAddressesThanUs_conflict() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1) - repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2) + repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */) + repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */) val packet = MdnsPacket( 0, /* flags */ @@ -1101,8 +1103,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServices_customHostsReplyHasFewerAddressesThanUs_noConflict() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1) - repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2) + repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */) + repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */) val packet = MdnsPacket( 0, /* flags */ @@ -1122,8 +1124,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServices_customHostsReplyHasIdenticalHosts_noConflict() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1) - repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2) + repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */) + repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */) val packet = MdnsPacket( 0, /* flags */ @@ -1147,8 +1149,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServices_customHostsCaseInsensitiveReplyHasIdenticalHosts_noConflict() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1) - repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2) + repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */) + repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */) val packet = MdnsPacket( 0, /* flags */ @@ -1171,8 +1173,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServices_IdenticalService() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1) - repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2) + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */) + repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */) val otherTtlMillis = 1234L val packet = MdnsPacket( @@ -1200,8 +1202,8 @@ class MdnsRecordRepositoryTest { @Test fun testGetConflictingServicesCaseInsensitive_IdenticalService() { val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags()) - repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1) - repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2) + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */) + repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* ttl */) val otherTtlMillis = 1234L val packet = MdnsPacket( @@ -1256,7 +1258,8 @@ class MdnsRecordRepositoryTest { makeFlags(includeInetAddressesInProbing = true)) repository.updateAddresses(TEST_ADDRESSES) assertEquals(0, repository.servicesCount) - assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)) + assertEquals(-1, + repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)) assertEquals(1, repository.servicesCount) val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1) @@ -1689,7 +1692,7 @@ private fun MdnsRecordRepository.addServiceAndFinishProbing( serviceId: Int, serviceInfo: NsdServiceInfo ): AnnouncementInfo { - addService(serviceId, serviceInfo) + addService(serviceId, serviceInfo, null /* ttl */) val probingInfo = setServiceProbing(serviceId) assertNotNull(probingInfo) return onProbingSucceeded(probingInfo) diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java index e7d7a983d3..8740e80879 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java @@ -35,6 +35,7 @@ import com.android.testutils.DevSdkIgnoreRunner; import org.junit.Test; import org.junit.runner.RunWith; +import java.time.Instant; import java.util.List; import java.util.Map; @@ -202,7 +203,8 @@ public class MdnsServiceInfoTest { List.of(), /* textEntries= */ null, /* interfaceIndex= */ 20, - network); + network, + Instant.MAX /* expirationTime */); assertEquals(network, info2.getNetwork()); } @@ -225,7 +227,8 @@ public class MdnsServiceInfoTest { MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max"), MdnsServiceInfo.TextEntry.fromString("test=")), 20 /* interfaceIndex */, - new Network(123)); + new Network(123), + Instant.MAX /* expirationTime */); beforeParcel.writeToParcel(parcel, 0); parcel.setDataPosition(0); diff --git a/tests/unit/vpn-jarjar-rules.txt b/tests/unit/vpn-jarjar-rules.txt index 1a6bddc89e..f74eab81bd 100644 --- a/tests/unit/vpn-jarjar-rules.txt +++ b/tests/unit/vpn-jarjar-rules.txt @@ -1,4 +1,2 @@ # Only keep classes imported by ConnectivityServiceTest -keep com.android.server.connectivity.Vpn keep com.android.server.connectivity.VpnProfileStore -keep com.android.server.net.LockdownVpnTracker diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java index 95496568ca..36ce4d5dae 100644 --- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java @@ -126,9 +126,6 @@ public class ThreadNetworkControllerTest { @Before public void setUp() throws Exception { - - mGrantedPermissions = new HashSet<String>(); - mExecutor = Executors.newSingleThreadExecutor(); ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class); if (manager != null) { mController = manager.getAllThreadNetworkControllers().get(0); @@ -138,22 +135,22 @@ public class ThreadNetworkControllerTest { // tests if a feature is not available. assumeNotNull(mController); - setEnabledAndWait(mController, true); - + mGrantedPermissions = new HashSet<String>(); + mExecutor = Executors.newSingleThreadExecutor(); mNsdManager = mContext.getSystemService(NsdManager.class); mHandlerThread = new HandlerThread(this.getClass().getSimpleName()); mHandlerThread.start(); + + setEnabledAndWait(mController, true); } @After public void tearDown() throws Exception { - if (mController != null) { - grantPermissions(THREAD_NETWORK_PRIVILEGED); - CompletableFuture<Void> future = new CompletableFuture<>(); - mController.leave(mExecutor, future::complete); - future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS); + if (mController == null) { + return; } dropAllPermissions(); + leaveAndWait(mController); tearDownTestNetwork(); } diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java index 7aaae869c2..e8ef3463ff 100644 --- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java +++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java @@ -374,9 +374,13 @@ public class BorderRoutingTest { subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_4); mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); - mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4); assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress())); + + // Verify ping reply from ftd1 and ftd2 separately as the order of replies can't be + // predicted. + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4); + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress())); } @@ -405,11 +409,13 @@ public class BorderRoutingTest { startFtdChild(ftd2); subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_5); - // Send the request twice as the order of replies from ftd1 and ftd2 are not guaranteed - mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress())); + + // Send the request twice as the order of replies from ftd1 and ftd2 are not guaranteed + mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); + assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress())); } diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java index 927b5aeda9..49b002a58b 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java @@ -17,9 +17,7 @@ package com.android.server.thread; import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; @@ -31,14 +29,14 @@ import static org.mockito.Mockito.when; import android.content.res.Resources; import android.os.PersistableBundle; -import android.test.suitebuilder.annotation.SmallTest; import android.util.AtomicFile; - +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; - import com.android.connectivity.resources.R; import com.android.server.connectivity.ConnectivityResources; - +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -46,10 +44,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; - /** Unit tests for {@link ThreadPersistentSettings}. */ @RunWith(AndroidJUnit4.class) @SmallTest |