DO NOT MERGE - Merge pie-platform-release (PPRL.190705.004) into master
Bug: 136196576
Change-Id: I481d824726ae0260b42cd7a4acc1c6fce593c324
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index ee05f28..25e111e 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -15,6 +15,9 @@
*/
package android.net;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,17 +30,41 @@
* {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity.
*/
public class CaptivePortal implements Parcelable {
- /** @hide */
+ /**
+ * Response code from the captive portal application, indicating that the portal was dismissed
+ * and the network should be re-validated.
+ * @see ICaptivePortal#appResponse(int)
+ * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int)
+ * @hide
+ */
+ @SystemApi
+ @TestApi
public static final int APP_RETURN_DISMISSED = 0;
- /** @hide */
+ /**
+ * Response code from the captive portal application, indicating that the user did not login and
+ * does not want to use the captive portal network.
+ * @see ICaptivePortal#appResponse(int)
+ * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int)
+ * @hide
+ */
+ @SystemApi
+ @TestApi
public static final int APP_RETURN_UNWANTED = 1;
- /** @hide */
+ /**
+ * Response code from the captive portal application, indicating that the user does not wish to
+ * login but wants to use the captive portal network as-is.
+ * @see ICaptivePortal#appResponse(int)
+ * @see android.net.INetworkMonitor#notifyCaptivePortalAppFinished(int)
+ * @hide
+ */
+ @SystemApi
+ @TestApi
public static final int APP_RETURN_WANTED_AS_IS = 2;
private final IBinder mBinder;
/** @hide */
- public CaptivePortal(IBinder binder) {
+ public CaptivePortal(@NonNull IBinder binder) {
mBinder = binder;
}
@@ -99,10 +126,27 @@
* connectivity for apps because the captive portal is still in place.
* @hide
*/
+ @SystemApi
+ @TestApi
public void useNetwork() {
try {
ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
} catch (RemoteException e) {
}
}
+
+ /**
+ * Log a captive portal login event.
+ * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto.
+ * @param packageName captive portal application package name.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void logEvent(int eventId, @NonNull String packageName) {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).logEvent(eventId, packageName);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/net/ConnectionInfo.java b/core/java/android/net/ConnectionInfo.java
new file mode 100644
index 0000000..58d0e05
--- /dev/null
+++ b/core/java/android/net/ConnectionInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Describe a network connection including local and remote address/port of a connection and the
+ * transport protocol.
+ *
+ * @hide
+ */
+public final class ConnectionInfo implements Parcelable {
+ public final int protocol;
+ public final InetSocketAddress local;
+ public final InetSocketAddress remote;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public ConnectionInfo(int protocol, InetSocketAddress local, InetSocketAddress remote) {
+ this.protocol = protocol;
+ this.local = local;
+ this.remote = remote;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(protocol);
+ out.writeByteArray(local.getAddress().getAddress());
+ out.writeInt(local.getPort());
+ out.writeByteArray(remote.getAddress().getAddress());
+ out.writeInt(remote.getPort());
+ }
+
+ public static final Creator<ConnectionInfo> CREATOR = new Creator<ConnectionInfo>() {
+ public ConnectionInfo createFromParcel(Parcel in) {
+ int protocol = in.readInt();
+ InetAddress localAddress;
+ try {
+ localAddress = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("Invalid InetAddress");
+ }
+ int localPort = in.readInt();
+ InetAddress remoteAddress;
+ try {
+ remoteAddress = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("Invalid InetAddress");
+ }
+ int remotePort = in.readInt();
+ InetSocketAddress local = new InetSocketAddress(localAddress, localPort);
+ InetSocketAddress remote = new InetSocketAddress(remoteAddress, remotePort);
+ return new ConnectionInfo(protocol, local, remote);
+ }
+
+ public ConnectionInfo[] newArray(int size) {
+ return new ConnectionInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c5cb1f5..111a8c4 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,28 +15,36 @@
*/
package android.net;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.SocketKeepalive.Callback;
import android.os.Binder;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkActivityListener;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -48,6 +56,7 @@
import android.util.Log;
import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.Preconditions;
@@ -55,13 +64,22 @@
import libcore.net.event.NetworkEventDispatcher;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.UncheckedIOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -82,6 +100,7 @@
@SystemService(Context.CONNECTIVITY_SERVICE)
public class ConnectivityManager {
private static final String TAG = "ConnectivityManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
* A change in network connectivity has occurred. A default connection has either
@@ -125,8 +144,8 @@
/**
* A temporary hack until SUPL system can get off the legacy APIS.
* They do too many network requests and the long list of apps listening
- * and waking due to the CONNECTIVITY_ACTION bcast makes it expensive.
- * Use this bcast intent instead for SUPL requests.
+ * and waking due to the CONNECTIVITY_ACTION broadcast makes it expensive.
+ * Use this broadcast intent instead for SUPL requests.
* @hide
*/
public static final String CONNECTIVITY_ACTION_SUPL =
@@ -152,7 +171,7 @@
* call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can
* reevaluate the network. If reevaluation finds the network no longer
* subject to a captive portal, the network may become the default active
- * data network. </li>
+ * data network.</li>
* <li> When the app handling this action believes the user explicitly wants
* to ignore the captive portal and the network, the app should call
* {@link CaptivePortal#ignoreNetwork}. </li>
@@ -165,10 +184,10 @@
* The lookup key for a {@link NetworkInfo} object. Retrieve with
* {@link android.content.Intent#getParcelableExtra(String)}.
*
- * @deprecated Since {@link NetworkInfo} can vary based on UID, applications
- * should always obtain network information through
- * {@link #getActiveNetworkInfo()}.
- * @see #EXTRA_NETWORK_TYPE
+ * @deprecated The {@link NetworkInfo} object is deprecated, as many of its properties
+ * can't accurately represent modern network characteristics.
+ * Please obtain information about networks from the {@link NetworkCapabilities}
+ * or {@link LinkProperties} objects instead.
*/
@Deprecated
public static final String EXTRA_NETWORK_INFO = "networkInfo";
@@ -177,7 +196,11 @@
* Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast.
*
* @see android.content.Intent#getIntExtra(String, int)
+ * @deprecated The network type is not rich enough to represent the characteristics
+ * of modern networks. Please use {@link NetworkCapabilities} instead,
+ * in particular the transports.
*/
+ @Deprecated
public static final String EXTRA_NETWORK_TYPE = "networkType";
/**
@@ -185,13 +208,19 @@
* is for a network to which the connectivity manager was failing over
* following a disconnect on another network.
* Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ *
+ * @deprecated See {@link NetworkInfo}.
*/
+ @Deprecated
public static final String EXTRA_IS_FAILOVER = "isFailover";
/**
* The lookup key for a {@link NetworkInfo} object. This is supplied when
* there is another network that it may be possible to connect to. Retrieve with
* {@link android.content.Intent#getParcelableExtra(String)}.
+ *
+ * @deprecated See {@link NetworkInfo}.
*/
+ @Deprecated
public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
/**
* The lookup key for a boolean that indicates whether there is a
@@ -212,7 +241,10 @@
* may be passed up from the lower networking layers, and its
* meaning may be specific to a particular network type. Retrieve
* it with {@link android.content.Intent#getStringExtra(String)}.
+ *
+ * @deprecated See {@link NetworkInfo#getExtraInfo()}.
*/
+ @Deprecated
public static final String EXTRA_EXTRA_INFO = "extraInfo";
/**
* The lookup key for an int that provides information about
@@ -242,6 +274,8 @@
* portal login activity.
* {@hide}
*/
+ @SystemApi
+ @TestApi
public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC =
"android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
@@ -249,6 +283,8 @@
* Key for passing a user agent string to the captive portal login activity.
* {@hide}
*/
+ @SystemApi
+ @TestApi
public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT =
"android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
@@ -260,7 +296,8 @@
* {@hide}
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_DATA_ACTIVITY_CHANGE = "android.net.conn.DATA_ACTIVITY_CHANGE";
+ public static final String ACTION_DATA_ACTIVITY_CHANGE =
+ "android.net.conn.DATA_ACTIVITY_CHANGE";
/**
* The lookup key for an enum that indicates the network device type on which this data activity
* change happens.
@@ -310,6 +347,7 @@
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @UnsupportedAppUsage
public static final String INET_CONDITION_ACTION =
"android.net.conn.INET_CONDITION_ACTION";
@@ -324,6 +362,7 @@
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @UnsupportedAppUsage
public static final String ACTION_TETHER_STATE_CHANGED =
"android.net.conn.TETHER_STATE_CHANGED";
@@ -332,6 +371,7 @@
* gives a String[] listing all the interfaces configured for
* tethering and currently available for tethering.
*/
+ @UnsupportedAppUsage
public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
/**
@@ -346,6 +386,7 @@
* gives a String[] listing all the interfaces currently tethered
* (ie, has DHCPv4 support and packets potentially forwarded/NATed)
*/
+ @UnsupportedAppUsage
public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
/**
@@ -354,6 +395,7 @@
* failed. Use {@link #getLastTetherError} to find the error code
* for any interfaces listed here.
*/
+ @UnsupportedAppUsage
public static final String EXTRA_ERRORED_TETHER = "erroredArray";
/**
@@ -390,15 +432,25 @@
"android.net.conn.PROMPT_LOST_VALIDATION";
/**
+ * Action used to display a dialog that asks the user whether to stay connected to a network
+ * that has not validated. This intent is used to start the dialog in settings via
+ * startActivity.
+ *
+ * @hide
+ */
+ public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY =
+ "android.net.conn.PROMPT_PARTIAL_CONNECTIVITY";
+
+ /**
* Invalid tethering type.
- * @see #startTethering(int, OnStartTetheringCallback, boolean)
+ * @see #startTethering(int, boolean, OnStartTetheringCallback)
* @hide
*/
public static final int TETHERING_INVALID = -1;
/**
* Wifi tethering type.
- * @see #startTethering(int, OnStartTetheringCallback, boolean)
+ * @see #startTethering(int, boolean, OnStartTetheringCallback)
* @hide
*/
@SystemApi
@@ -406,7 +458,7 @@
/**
* USB tethering type.
- * @see #startTethering(int, OnStartTetheringCallback, boolean)
+ * @see #startTethering(int, boolean, OnStartTetheringCallback)
* @hide
*/
@SystemApi
@@ -414,7 +466,7 @@
/**
* Bluetooth tethering type.
- * @see #startTethering(int, OnStartTetheringCallback, boolean)
+ * @see #startTethering(int, boolean, OnStartTetheringCallback)
* @hide
*/
@SystemApi
@@ -458,6 +510,7 @@
* The absence of a connection type.
* @hide
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_NONE = -1;
/**
@@ -574,6 +627,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_MOBILE_FOTA = 10;
/**
@@ -582,6 +636,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage
public static final int TYPE_MOBILE_IMS = 11;
/**
@@ -590,6 +645,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_MOBILE_CBS = 12;
/**
@@ -599,6 +655,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage
public static final int TYPE_WIFI_P2P = 13;
/**
@@ -607,6 +664,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage
public static final int TYPE_MOBILE_IA = 14;
/**
@@ -616,6 +674,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_MOBILE_EMERGENCY = 15;
/**
@@ -624,6 +683,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage
public static final int TYPE_PROXY = 16;
/**
@@ -634,11 +694,20 @@
@Deprecated
public static final int TYPE_VPN = 17;
- /** {@hide} */
- public static final int MAX_RADIO_TYPE = TYPE_VPN;
+ /**
+ * A network that is exclusively meant to be used for testing
+ *
+ * @deprecated Use {@link NetworkCapabilities} instead.
+ * @hide
+ */
+ @Deprecated
+ public static final int TYPE_TEST = 18; // TODO: Remove this once NetworkTypes are unused.
/** {@hide} */
- public static final int MAX_NETWORK_TYPE = TYPE_VPN;
+ public static final int MAX_RADIO_TYPE = TYPE_TEST;
+
+ /** {@hide} */
+ public static final int MAX_NETWORK_TYPE = TYPE_TEST;
private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
@@ -664,7 +733,7 @@
/**
* Static unique request used as a tombstone for NetworkCallbacks that have been unregistered.
* This allows to distinguish when unregistering NetworkCallbacks those that were never
- * registered and those that were already unregistered.
+ * registered from those that were already unregistered.
* @hide
*/
private static final NetworkRequest ALREADY_UNREGISTERED =
@@ -706,6 +775,7 @@
*/
public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
@@ -742,6 +812,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage
public static String getNetworkTypeName(int type) {
switch (type) {
case TYPE_NONE:
@@ -796,6 +867,7 @@
* {@hide}
*/
@Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static boolean isNetworkTypeMobile(int networkType) {
switch (networkType) {
case TYPE_MOBILE:
@@ -876,8 +948,11 @@
*
* @return a {@link NetworkInfo} object for the current default network
* or {@code null} if no default network is currently active
+ * @deprecated See {@link NetworkInfo}.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
public NetworkInfo getActiveNetworkInfo() {
try {
return mService.getActiveNetworkInfo();
@@ -897,6 +972,7 @@
* {@code null} if no default network is currently active
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
public Network getActiveNetwork() {
try {
return mService.getActiveNetwork();
@@ -918,6 +994,7 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ @Nullable
public Network getActiveNetworkForUid(int uid) {
return getActiveNetworkForUid(uid, false);
}
@@ -967,20 +1044,26 @@
* to remove an existing always-on VPN configuration.
* @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
* {@code false} otherwise.
+ * @param lockdownWhitelist The list of packages that are allowed to access network directly
+ * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
+ * this method must be called when a package that should be whitelisted is installed or
+ * uninstalled.
* @return {@code true} if the package is set as always-on VPN controller;
* {@code false} otherwise.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
- boolean lockdownEnabled) {
+ boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) {
try {
- return mService.setAlwaysOnVpnPackage(userId, vpnPackage, lockdownEnabled);
+ return mService.setAlwaysOnVpnPackage(
+ userId, vpnPackage, lockdownEnabled, lockdownWhitelist);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- /**
+ /**
* Returns the package name of the currently set always-on VPN application.
* If there is no always-on VPN set, or the VPN is provided by the system instead
* of by an app, {@code null} will be returned.
@@ -989,6 +1072,7 @@
* or {@code null} if none is set.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
public String getAlwaysOnVpnPackageForUser(int userId) {
try {
return mService.getAlwaysOnVpnPackage(userId);
@@ -998,6 +1082,36 @@
}
/**
+ * @return whether always-on VPN is in lockdown mode.
+ *
+ * @hide
+ **/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public boolean isVpnLockdownEnabled(int userId) {
+ try {
+ return mService.isVpnLockdownEnabled(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ }
+
+ /**
+ * @return the list of packages that are allowed to access network when always-on VPN is in
+ * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
+ *
+ * @hide
+ **/
+ @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+ public List<String> getVpnLockdownWhitelist(int userId) {
+ try {
+ return mService.getVpnLockdownWhitelist(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns details about the currently active default data network
* for a given uid. This is for internal use only to avoid spying
* other apps.
@@ -1009,6 +1123,7 @@
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ @UnsupportedAppUsage
public NetworkInfo getActiveNetworkInfoForUid(int uid) {
return getActiveNetworkInfoForUid(uid, false);
}
@@ -1042,6 +1157,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @Nullable
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
@@ -1059,9 +1175,12 @@
* @return a {@link NetworkInfo} object for the requested
* network or {@code null} if the {@code Network}
* is not valid.
+ * @deprecated See {@link NetworkInfo}.
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public NetworkInfo getNetworkInfo(Network network) {
+ @Nullable
+ public NetworkInfo getNetworkInfo(@Nullable Network network) {
return getNetworkInfoForUid(network, Process.myUid(), false);
}
@@ -1087,6 +1206,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @NonNull
public NetworkInfo[] getAllNetworkInfo() {
try {
return mService.getAllNetworkInfo();
@@ -1106,6 +1226,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
public Network getNetworkForType(int networkType) {
try {
return mService.getNetworkForType(networkType);
@@ -1121,6 +1242,7 @@
* @return an array of {@link Network} objects.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @NonNull
public Network[] getAllNetworks() {
try {
return mService.getAllNetworks();
@@ -1134,6 +1256,7 @@
* the Networks that applications run by the given user will use by default.
* @hide
*/
+ @UnsupportedAppUsage
public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
try {
return mService.getDefaultNetworkCapabilitiesForUser(userId);
@@ -1150,8 +1273,13 @@
* is no current default network.
*
* {@hide}
+ * @deprecated please use {@link #getLinkProperties(Network)} on the return
+ * value of {@link #getActiveNetwork()} instead. In particular,
+ * this method will return non-null LinkProperties even if the
+ * app is blocked by policy from using this network.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 109783091)
public LinkProperties getActiveLinkProperties() {
try {
return mService.getActiveLinkProperties();
@@ -1176,6 +1304,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkPropertiesForType(networkType);
@@ -1192,7 +1321,8 @@
* @return The {@link LinkProperties} for the network, or {@code null}.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public LinkProperties getLinkProperties(Network network) {
+ @Nullable
+ public LinkProperties getLinkProperties(@Nullable Network network) {
try {
return mService.getLinkProperties(network);
} catch (RemoteException e) {
@@ -1208,7 +1338,8 @@
* @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public NetworkCapabilities getNetworkCapabilities(Network network) {
+ @Nullable
+ public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
try {
return mService.getNetworkCapabilities(network);
} catch (RemoteException e) {
@@ -1217,12 +1348,15 @@
}
/**
- * Gets the URL that should be used for resolving whether a captive portal is present.
+ * Gets a URL that can be used for resolving whether a captive portal is present.
* 1. This URL should respond with a 204 response to a GET request to indicate no captive
* portal is present.
* 2. This URL must be HTTP as redirect responses are used to find captive portal
* sign-in pages. Captive portals cannot respond to HTTPS requests with redirects.
*
+ * The system network validation may be using different strategies to detect captive portals,
+ * so this method does not necessarily return a URL used by the system. It only returns a URL
+ * that may be relevant for other components trying to detect captive portals.
* @hide
*/
@SystemApi
@@ -1331,6 +1465,7 @@
return 1;
}
+ @UnsupportedAppUsage
private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
if (networkType == TYPE_MOBILE) {
switch (feature) {
@@ -1494,8 +1629,9 @@
};
}
- private static HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests =
- new HashMap<NetworkCapabilities, LegacyRequest>();
+ @UnsupportedAppUsage
+ private static final HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests =
+ new HashMap<>();
private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) {
synchronized (sLegacyRequests) {
@@ -1522,6 +1658,7 @@
Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum);
}
+ @UnsupportedAppUsage
private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) {
int delay = -1;
int type = legacyTypeForNetworkCapabilities(netCap);
@@ -1551,6 +1688,7 @@
}
}
+ @UnsupportedAppUsage
private boolean removeRequestForFeature(NetworkCapabilities netCap) {
final LegacyRequest l;
synchronized (sLegacyRequests) {
@@ -1618,10 +1756,13 @@
/** @hide */
public static class PacketKeepaliveCallback {
/** The requested keepalive was successfully started. */
+ @UnsupportedAppUsage
public void onStarted() {}
/** The keepalive was successfully stopped. */
+ @UnsupportedAppUsage
public void onStopped() {}
/** An error occurred. */
+ @UnsupportedAppUsage
public void onError(int error) {}
}
@@ -1635,8 +1776,11 @@
* {@code onStarted} method will be called. If an error occurs, {@code onError} will be called,
* specifying one of the {@code ERROR_*} constants in this class.
*
- * To stop an existing keepalive, call {@link stop}. The system will call {@code onStopped} if
- * the operation was successfull or {@code onError} if an error occurred.
+ * To stop an existing keepalive, call {@link PacketKeepalive#stop}. The system will call
+ * {@link PacketKeepaliveCallback#onStopped} if the operation was successful or
+ * {@link PacketKeepaliveCallback#onError} if an error occurred.
+ *
+ * @deprecated Use {@link SocketKeepalive} instead.
*
* @hide
*/
@@ -1677,22 +1821,26 @@
public static final int MIN_INTERVAL = 10;
private final Network mNetwork;
- private final PacketKeepaliveCallback mCallback;
- private final Looper mLooper;
- private final Messenger mMessenger;
+ private final ISocketKeepaliveCallback mCallback;
+ private final ExecutorService mExecutor;
private volatile Integer mSlot;
- void stopLooper() {
- mLooper.quit();
- }
-
+ @UnsupportedAppUsage
public void stop() {
try {
- mService.stopKeepalive(mNetwork, mSlot);
- } catch (RemoteException e) {
- Log.e(TAG, "Error stopping packet keepalive: ", e);
- stopLooper();
+ mExecutor.execute(() -> {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping packet keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ // The internal executor has already stopped due to previous event.
}
}
@@ -1700,64 +1848,183 @@
Preconditions.checkNotNull(network, "network cannot be null");
Preconditions.checkNotNull(callback, "callback cannot be null");
mNetwork = network;
- mCallback = callback;
- HandlerThread thread = new HandlerThread(TAG);
- thread.start();
- mLooper = thread.getLooper();
- mMessenger = new Messenger(new Handler(mLooper) {
+ mExecutor = Executors.newSingleThreadExecutor();
+ mCallback = new ISocketKeepaliveCallback.Stub() {
@Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case NetworkAgent.EVENT_PACKET_KEEPALIVE:
- int error = message.arg2;
- try {
- if (error == SUCCESS) {
- if (mSlot == null) {
- mSlot = message.arg1;
- mCallback.onStarted();
- } else {
- mSlot = null;
- stopLooper();
- mCallback.onStopped();
- }
- } else {
- stopLooper();
- mCallback.onError(error);
- }
- } catch (Exception e) {
- Log.e(TAG, "Exception in keepalive callback(" + error + ")", e);
- }
- break;
- default:
- Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
- break;
- }
+ public void onStarted(int slot) {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() -> {
+ mSlot = slot;
+ callback.onStarted();
+ }));
}
- });
+
+ @Override
+ public void onStopped() {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() -> {
+ mSlot = null;
+ callback.onStopped();
+ }));
+ mExecutor.shutdown();
+ }
+
+ @Override
+ public void onError(int error) {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() -> {
+ mSlot = null;
+ callback.onError(error);
+ }));
+ mExecutor.shutdown();
+ }
+
+ @Override
+ public void onDataReceived() {
+ // PacketKeepalive is only used for Nat-T keepalive and as such does not invoke
+ // this callback when data is received.
+ }
+ };
}
}
/**
* Starts an IPsec NAT-T keepalive packet with the specified parameters.
*
+ * @deprecated Use {@link #createSocketKeepalive} instead.
+ *
* @hide
*/
+ @UnsupportedAppUsage
public PacketKeepalive startNattKeepalive(
Network network, int intervalSeconds, PacketKeepaliveCallback callback,
InetAddress srcAddr, int srcPort, InetAddress dstAddr) {
final PacketKeepalive k = new PacketKeepalive(network, callback);
try {
- mService.startNattKeepalive(network, intervalSeconds, k.mMessenger, new Binder(),
+ mService.startNattKeepalive(network, intervalSeconds, k.mCallback,
srcAddr.getHostAddress(), srcPort, dstAddr.getHostAddress());
} catch (RemoteException e) {
Log.e(TAG, "Error starting packet keepalive: ", e);
- k.stopLooper();
- return null;
+ throw e.rethrowFromSystemServer();
}
return k;
}
/**
+ * Request that keepalives be started on a IPsec NAT-T socket.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param socket The socket that needs to be kept alive.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the
+ * given socket.
+ **/
+ public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ @NonNull UdpEncapsulationSocket socket,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ ParcelFileDescriptor dup;
+ try {
+ // Dup is needed here as the pfd inside the socket is owned by the IpSecService,
+ // which cannot be obtained by the app process.
+ dup = ParcelFileDescriptor.dup(socket.getFileDescriptor());
+ } catch (IOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new NattSocketKeepalive(mService, network, dup, socket.getResourceId(), source,
+ destination, executor, callback);
+ }
+
+ /**
+ * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called
+ * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param pfd The {@link ParcelFileDescriptor} that needs to be kept alive. The provided
+ * {@link ParcelFileDescriptor} must be bound to a port and the keepalives will be sent
+ * from that port.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}. The
+ * keepalive packets will always be sent to port 4500 of the given {@code destination}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the
+ * given socket.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+ public @NonNull SocketKeepalive createNattKeepalive(@NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ ParcelFileDescriptor dup;
+ try {
+ // TODO: Consider remove unnecessary dup.
+ dup = pfd.dup();
+ } catch (IOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new NattSocketKeepalive(mService, network, dup,
+ INVALID_RESOURCE_ID /* Unused */, source, destination, executor, callback);
+ }
+
+ /**
+ * Request that keepalives be started on a TCP socket.
+ * The socket must be established.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param socket The socket that needs to be kept alive.
+ * @param executor The executor on which callback will be invoked. This implementation assumes
+ * the provided {@link Executor} runs the callbacks in sequence with no
+ * concurrency. Failing this, no guarantee of correctness can be made. It is
+ * the responsibility of the caller to ensure the executor provides this
+ * guarantee. A simple way of creating such an executor is with the standard
+ * tool {@code Executors.newSingleThreadExecutor}.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the
+ * given socket.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+ public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ @NonNull Socket socket,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ ParcelFileDescriptor dup;
+ try {
+ dup = ParcelFileDescriptor.fromSocket(socket);
+ } catch (UncheckedIOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new TcpSocketKeepalive(mService, network, dup, executor, callback);
+ }
+
+ /**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
@@ -1803,6 +2070,7 @@
* {@link #bindProcessToNetwork} API.
*/
@Deprecated
+ @UnsupportedAppUsage
public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
checkLegacyRoutingApiAccess();
try {
@@ -1846,12 +2114,14 @@
* @hide
*/
@Deprecated
+ @UnsupportedAppUsage
public void setBackgroundDataSetting(boolean allowBackgroundData) {
// ignored
}
/** {@hide} */
@Deprecated
+ @UnsupportedAppUsage
public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
try {
return mService.getActiveNetworkQuotaInfo();
@@ -1865,6 +2135,7 @@
* @deprecated Talk to TelephonyManager directly
*/
@Deprecated
+ @UnsupportedAppUsage
public boolean getMobileDataEnabled() {
IBinder b = ServiceManager.getService(Context.TELEPHONY_SERVICE);
if (b != null) {
@@ -1897,7 +2168,7 @@
* to initiate network traffic), you can retrieve its instantaneous state with
* {@link ConnectivityManager#isDefaultNetworkActive}.
*/
- public void onNetworkActive();
+ void onNetworkActive();
}
private INetworkManagementService getNetworkManagementService() {
@@ -1912,8 +2183,7 @@
}
private final ArrayMap<OnNetworkActiveListener, INetworkActivityListener>
- mNetworkActivityListeners
- = new ArrayMap<OnNetworkActiveListener, INetworkActivityListener>();
+ mNetworkActivityListeners = new ArrayMap<>();
/**
* Start listening to reports when the system's default data network is active, meaning it is
@@ -1949,7 +2219,7 @@
*
* @param l Previously registered listener.
*/
- public void removeDefaultNetworkActiveListener(OnNetworkActiveListener l) {
+ public void removeDefaultNetworkActiveListener(@NonNull OnNetworkActiveListener l) {
INetworkActivityListener rl = mNetworkActivityListeners.get(l);
Preconditions.checkArgument(rl != null, "Listener was not registered.");
try {
@@ -1985,10 +2255,21 @@
}
/** {@hide} */
+ @UnsupportedAppUsage
public static ConnectivityManager from(Context context) {
return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
+ /** @hide */
+ public NetworkRequest getDefaultRequest() {
+ try {
+ // This is not racy as the default request is final in ConnectivityService.
+ return mService.getDefaultRequest();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/* TODO: These permissions checks don't belong in client-side code. Move them to
* services.jar, possibly in com.android.server.net. */
@@ -2035,6 +2316,7 @@
* @hide
*/
@Deprecated
+ @UnsupportedAppUsage
private static ConnectivityManager getInstance() {
if (getInstanceOrNull() == null) {
throw new IllegalStateException("No ConnectivityManager yet constructed");
@@ -2051,6 +2333,7 @@
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
public String[] getTetherableIfaces() {
try {
return mService.getTetherableIfaces();
@@ -2067,6 +2350,7 @@
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
public String[] getTetheredIfaces() {
try {
return mService.getTetheredIfaces();
@@ -2089,6 +2373,7 @@
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
public String[] getTetheringErroredIfaces() {
try {
return mService.getTetheringErroredIfaces();
@@ -2135,6 +2420,7 @@
*
* {@hide}
*/
+ @UnsupportedAppUsage
public int tether(String iface) {
try {
String pkgName = mContext.getOpPackageName();
@@ -2163,6 +2449,7 @@
*
* {@hide}
*/
+ @UnsupportedAppUsage
public int untether(String iface) {
try {
String pkgName = mContext.getOpPackageName();
@@ -2216,12 +2503,12 @@
/**
* Called when tethering has been successfully started.
*/
- public void onTetheringStarted() {};
+ public void onTetheringStarted() {}
/**
* Called when starting tethering failed.
*/
- public void onTetheringFailed() {};
+ public void onTetheringFailed() {}
}
/**
@@ -2306,6 +2593,94 @@
}
/**
+ * Callback for use with {@link registerTetheringEventCallback} to find out tethering
+ * upstream status.
+ *
+ *@hide
+ */
+ @SystemApi
+ public abstract static class OnTetheringEventCallback {
+
+ /**
+ * Called when tethering upstream changed. This can be called multiple times and can be
+ * called any time.
+ *
+ * @param network the {@link Network} of tethering upstream. Null means tethering doesn't
+ * have any upstream.
+ */
+ public void onUpstreamChanged(@Nullable Network network) {}
+ }
+
+ @GuardedBy("mTetheringEventCallbacks")
+ private final ArrayMap<OnTetheringEventCallback, ITetheringEventCallback>
+ mTetheringEventCallbacks = new ArrayMap<>();
+
+ /**
+ * Start listening to tethering change events. Any new added callback will receive the last
+ * tethering status right away. If callback is registered when tethering has no upstream or
+ * disabled, {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called
+ * with a null argument. The same callback object cannot be registered twice.
+ *
+ * @param executor the executor on which callback will be invoked.
+ * @param callback the callback to be called when tethering has change events.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void registerTetheringEventCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull final OnTetheringEventCallback callback) {
+ Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null.");
+
+ synchronized (mTetheringEventCallbacks) {
+ Preconditions.checkArgument(!mTetheringEventCallbacks.containsKey(callback),
+ "callback was already registered.");
+ ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() {
+ @Override
+ public void onUpstreamChanged(Network network) throws RemoteException {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ callback.onUpstreamChanged(network);
+ }));
+ }
+ };
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "registerTetheringUpstreamCallback:" + pkgName);
+ mService.registerTetheringEventCallback(remoteCallback, pkgName);
+ mTetheringEventCallbacks.put(callback, remoteCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Remove tethering event callback previously registered with
+ * {@link #registerTetheringEventCallback}.
+ *
+ * @param callback previously registered callback.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void unregisterTetheringEventCallback(
+ @NonNull final OnTetheringEventCallback callback) {
+ synchronized (mTetheringEventCallbacks) {
+ ITetheringEventCallback remoteCallback = mTetheringEventCallbacks.remove(callback);
+ Preconditions.checkNotNull(remoteCallback, "callback was not registered.");
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "unregisterTetheringEventCallback:" + pkgName);
+ mService.unregisterTetheringEventCallback(remoteCallback, pkgName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+
+ /**
* Get the list of regular expressions that define any tetherable
* USB network interfaces. If USB tethering is not supported by the
* device, this list should be empty.
@@ -2316,6 +2691,7 @@
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
public String[] getTetherableUsbRegexs() {
try {
return mService.getTetherableUsbRegexs();
@@ -2335,6 +2711,7 @@
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
public String[] getTetherableWifiRegexs() {
try {
return mService.getTetherableWifiRegexs();
@@ -2354,6 +2731,7 @@
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
public String[] getTetherableBluetoothRegexs() {
try {
return mService.getTetherableBluetoothRegexs();
@@ -2379,6 +2757,7 @@
*
* {@hide}
*/
+ @UnsupportedAppUsage
public int setUsbTethering(boolean enable) {
try {
String pkgName = mContext.getOpPackageName();
@@ -2390,6 +2769,7 @@
}
/** {@hide} */
+ @SystemApi
public static final int TETHER_ERROR_NO_ERROR = 0;
/** {@hide} */
public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
@@ -2412,7 +2792,13 @@
/** {@hide} */
public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
/** {@hide} */
+ @SystemApi
public static final int TETHER_ERROR_PROVISION_FAILED = 11;
+ /** {@hide} */
+ public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12;
+ /** {@hide} */
+ @SystemApi
+ public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
/**
* Get a more detailed error code after a Tethering or Untethering
@@ -2425,6 +2811,7 @@
* {@hide}
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage
public int getLastTetherError(String iface) {
try {
return mService.getLastTetherError(iface);
@@ -2433,6 +2820,79 @@
}
}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ TETHER_ERROR_NO_ERROR,
+ TETHER_ERROR_PROVISION_FAILED,
+ TETHER_ERROR_ENTITLEMENT_UNKONWN,
+ })
+ public @interface EntitlementResultCode {
+ }
+
+ /**
+ * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether
+ * entitlement succeeded.
+ * @hide
+ */
+ @SystemApi
+ public interface OnTetheringEntitlementResultListener {
+ /**
+ * Called to notify entitlement result.
+ *
+ * @param resultCode an int value of entitlement result. It may be one of
+ * {@link #TETHER_ERROR_NO_ERROR},
+ * {@link #TETHER_ERROR_PROVISION_FAILED}, or
+ * {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}.
+ */
+ void onTetheringEntitlementResult(@EntitlementResultCode int resultCode);
+ }
+
+ /**
+ * Get the last value of the entitlement check on this downstream. If the cached value is
+ * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, it just return the
+ * cached value. Otherwise, a UI-based entitlement check would be performed. It is not
+ * guaranteed that the UI-based entitlement check will complete in any specific time period
+ * and may in fact never complete. Any successful entitlement check the platform performs for
+ * any reason will update the cached value.
+ *
+ * @param type the downstream type of tethering. Must be one of
+ * {@link #TETHERING_WIFI},
+ * {@link #TETHERING_USB}, or
+ * {@link #TETHERING_BLUETOOTH}.
+ * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check.
+ * @param executor the executor on which callback will be invoked.
+ * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to
+ * notify the caller of the result of entitlement check. The listener may be called zero
+ * or one time.
+ * {@hide}
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ public void getLatestTetheringEntitlementResult(int type, boolean showEntitlementUi,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull final OnTetheringEntitlementResultListener listener) {
+ Preconditions.checkNotNull(listener, "TetheringEntitlementResultListener cannot be null.");
+ ResultReceiver wrappedListener = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ listener.onTetheringEntitlementResult(resultCode);
+ }));
+ }
+ };
+
+ try {
+ String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "getLatestTetheringEntitlementResult:" + pkgName);
+ mService.getLatestTetheringEntitlementResult(type, wrappedListener,
+ showEntitlementUi, pkgName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Report network connectivity status. This is currently used only
* to alter status bar UI.
@@ -2445,6 +2905,7 @@
* {@hide}
*/
public void reportInetCondition(int networkType, int percentage) {
+ printStackTrace();
try {
mService.reportInetCondition(networkType, percentage);
} catch (RemoteException e) {
@@ -2464,7 +2925,8 @@
* working and non-working connectivity.
*/
@Deprecated
- public void reportBadNetwork(Network network) {
+ public void reportBadNetwork(@Nullable Network network) {
+ printStackTrace();
try {
// One of these will be ignored because it matches system's current state.
// The other will trigger the necessary reevaluation.
@@ -2486,7 +2948,8 @@
* @param hasConnectivity {@code true} if the application was able to successfully access the
* Internet using {@code network} or {@code false} if not.
*/
- public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
+ public void reportNetworkConnectivity(@Nullable Network network, boolean hasConnectivity) {
+ printStackTrace();
try {
mService.reportNetworkConnectivity(network, hasConnectivity);
} catch (RemoteException e) {
@@ -2559,6 +3022,7 @@
* @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no
* HTTP proxy is active.
*/
+ @Nullable
public ProxyInfo getDefaultProxy() {
return getProxyForNetwork(getBoundNetworkForProcess());
}
@@ -2578,6 +3042,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public boolean isNetworkSupported(int networkType) {
try {
return mService.isNetworkSupported(networkType);
@@ -2658,9 +3123,6 @@
/**
* Set sign in error notification to visible or in visible
*
- * @param visible
- * @param networkType
- *
* {@hide}
* @deprecated Doesn't properly deal with multiple connected networks of the same type.
*/
@@ -2681,7 +3143,11 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK})
+ @SystemApi
public void setAirplaneMode(boolean enable) {
try {
mService.setAirplaneMode(enable);
@@ -2690,16 +3156,18 @@
}
}
- /** {@hide} */
- public void registerNetworkFactory(Messenger messenger, String name) {
+ /** {@hide} - returns the factory serial number */
+ @UnsupportedAppUsage
+ public int registerNetworkFactory(Messenger messenger, String name) {
try {
- mService.registerNetworkFactory(messenger, name);
+ return mService.registerNetworkFactory(messenger, name);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** {@hide} */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void unregisterNetworkFactory(Messenger messenger) {
try {
mService.unregisterNetworkFactory(messenger);
@@ -2708,6 +3176,10 @@
}
}
+ // TODO : remove this method. It is a stopgap measure to help sheperding a number
+ // of dependent changes that would conflict throughout the automerger graph. Having this
+ // temporarily helps with the process of going through with all these dependent changes across
+ // the entire tree.
/**
* @hide
* Register a NetworkAgent with ConnectivityService.
@@ -2715,8 +3187,20 @@
*/
public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
NetworkCapabilities nc, int score, NetworkMisc misc) {
+ return registerNetworkAgent(messenger, ni, lp, nc, score, misc,
+ NetworkFactory.SerialNumber.NONE);
+ }
+
+ /**
+ * @hide
+ * Register a NetworkAgent with ConnectivityService.
+ * @return NetID corresponding to NetworkAgent.
+ */
+ public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) {
try {
- return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc);
+ return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc,
+ factorySerialNumber);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2750,7 +3234,7 @@
*
* @hide
*/
- public void onPreCheck(Network network) {}
+ public void onPreCheck(@NonNull Network network) {}
/**
* Called when the framework connects and has declared a new network ready for use.
@@ -2760,10 +3244,12 @@
* @param network The {@link Network} of the satisfying network.
* @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network.
* @param linkProperties The {@link LinkProperties} of the satisfying network.
+ * @param blocked Whether access to the {@link Network} is blocked due to system policy.
* @hide
*/
- public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
- LinkProperties linkProperties) {
+ public void onAvailable(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties, boolean blocked) {
// Internally only this method is called when a new network is available, and
// it calls the callback in the same way and order that older versions used
// to call so as not to change the behavior.
@@ -2774,6 +3260,7 @@
}
onCapabilitiesChanged(network, networkCapabilities);
onLinkPropertiesChanged(network, linkProperties);
+ onBlockedStatusChanged(network, blocked);
}
/**
@@ -2781,11 +3268,12 @@
* This callback may be called more than once if the {@link Network} that is
* satisfying the request changes. This will always immediately be followed by a
* call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} then by a
- * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}.
+ * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call to
+ * {@link #onBlockedStatusChanged(Network, boolean)}.
*
* @param network The {@link Network} of the satisfying network.
*/
- public void onAvailable(Network network) {}
+ public void onAvailable(@NonNull Network network) {}
/**
* Called when the network is about to be disconnected. Often paired with an
@@ -2801,7 +3289,7 @@
* network connected. Note that the network may suffer a
* hard loss at any time.
*/
- public void onLosing(Network network, int maxMsToLive) {}
+ public void onLosing(@NonNull Network network, int maxMsToLive) {}
/**
* Called when the framework has a hard loss of the network or when the
@@ -2809,13 +3297,13 @@
*
* @param network The {@link Network} lost.
*/
- public void onLost(Network network) {}
+ public void onLost(@NonNull Network network) {}
/**
* Called if no network is found in the timeout time specified in
- * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call. This callback is not
- * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)}
- * without timeout. When this callback is invoked the associated
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the
+ * requested network request cannot be fulfilled (whether or not a timeout was
+ * specified). When this callback is invoked the associated
* {@link NetworkRequest} will have already been removed and released, as if
* {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
*/
@@ -2829,8 +3317,8 @@
* @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this
* network.
*/
- public void onCapabilitiesChanged(Network network,
- NetworkCapabilities networkCapabilities) {}
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities) {}
/**
* Called when the network the framework connected to for this request
@@ -2839,7 +3327,8 @@
* @param network The {@link Network} whose link properties have changed.
* @param linkProperties The new {@link LinkProperties} for this network.
*/
- public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {}
+ public void onLinkPropertiesChanged(@NonNull Network network,
+ @NonNull LinkProperties linkProperties) {}
/**
* Called when the network the framework connected to for this request
@@ -2850,7 +3339,7 @@
* a tunnel, etc.
* @hide
*/
- public void onNetworkSuspended(Network network) {}
+ public void onNetworkSuspended(@NonNull Network network) {}
/**
* Called when the network the framework connected to for this request
@@ -2858,7 +3347,15 @@
* preceded by a matching {@link NetworkCallback#onNetworkSuspended} call.
* @hide
*/
- public void onNetworkResumed(Network network) {}
+ public void onNetworkResumed(@NonNull Network network) {}
+
+ /**
+ * Called when access to the specified network is blocked or unblocked.
+ *
+ * @param network The {@link Network} whose blocked status has changed.
+ * @param blocked The blocked status of this {@link Network}.
+ */
+ public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
private NetworkRequest networkRequest;
}
@@ -2869,7 +3366,7 @@
* @hide
*/
public interface Errors {
- static int TOO_MANY_REQUESTS = 1;
+ int TOO_MANY_REQUESTS = 1;
}
/** @hide */
@@ -2906,6 +3403,8 @@
public static final int CALLBACK_SUSPENDED = BASE + 9;
/** @hide */
public static final int CALLBACK_RESUMED = BASE + 10;
+ /** @hide */
+ public static final int CALLBACK_BLK_CHANGED = BASE + 11;
/** @hide */
public static String getCallbackName(int whichCallback) {
@@ -2920,6 +3419,7 @@
case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST";
case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED";
case CALLBACK_RESUMED: return "CALLBACK_RESUMED";
+ case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED";
default:
return Integer.toString(whichCallback);
}
@@ -2949,14 +3449,19 @@
final NetworkCallback callback;
synchronized (sCallbacks) {
callback = sCallbacks.get(request);
+ if (callback == null) {
+ Log.w(TAG,
+ "callback not found for " + getCallbackName(message.what) + " message");
+ return;
+ }
+ if (message.what == CALLBACK_UNAVAIL) {
+ sCallbacks.remove(request);
+ callback.networkRequest = ALREADY_UNREGISTERED;
+ }
}
if (DBG) {
Log.d(TAG, getCallbackName(message.what) + " for network " + network);
}
- if (callback == null) {
- Log.w(TAG, "callback not found for " + getCallbackName(message.what) + " message");
- return;
- }
switch (message.what) {
case CALLBACK_PRECHECK: {
@@ -2966,7 +3471,7 @@
case CALLBACK_AVAILABLE: {
NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
LinkProperties lp = getObject(message, LinkProperties.class);
- callback.onAvailable(network, cap, lp);
+ callback.onAvailable(network, cap, lp, message.arg1 != 0);
break;
}
case CALLBACK_LOSING: {
@@ -2999,6 +3504,10 @@
callback.onNetworkResumed(network);
break;
}
+ case CALLBACK_BLK_CHANGED: {
+ boolean blocked = message.arg1 != 0;
+ callback.onBlockedStatusChanged(network, blocked);
+ }
}
}
@@ -3024,6 +3533,7 @@
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
int timeoutMs, int action, int legacyType, CallbackHandler handler) {
+ printStackTrace();
checkCallbackNotNull(callback);
Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities");
final NetworkRequest request;
@@ -3067,8 +3577,9 @@
*
* @hide
*/
- public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
- int timeoutMs, int legacyType, Handler handler) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
+ @NonNull Handler handler) {
CallbackHandler cbHandler = new CallbackHandler(handler);
NetworkCapabilities nc = request.networkCapabilities;
sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler);
@@ -3102,10 +3613,12 @@
* @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
* the callback must not be shared - it uniquely specifies this request.
* The callback is invoked on the default internal Handler.
- * @throws IllegalArgumentException if {@code request} specifies any mutable
- * {@code NetworkCapabilities}.
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
*/
- public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback) {
requestNetwork(request, networkCallback, getDefaultHandler());
}
@@ -3126,7 +3639,7 @@
* as these {@code NetworkCapabilities} represent states that a particular
* network may never attain, and whether a network will attain these states
* is unknown prior to bringing up the network so the framework does not
- * know how to go about satisfing a request with these capabilities.
+ * know how to go about satisfying a request with these capabilities.
*
* <p>This method requires the caller to hold either the
* {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
@@ -3137,11 +3650,12 @@
* @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
* the callback must not be shared - it uniquely specifies this request.
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
- * @throws IllegalArgumentException if {@code request} specifies any mutable
- * {@code NetworkCapabilities}.
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
*/
- public void requestNetwork(
- NetworkRequest request, NetworkCallback networkCallback, Handler handler) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
CallbackHandler cbHandler = new CallbackHandler(handler);
requestNetwork(request, networkCallback, 0, legacyType, cbHandler);
@@ -3174,9 +3688,12 @@
* @param timeoutMs The time in milliseconds to attempt looking for a suitable network
* before {@link NetworkCallback#onUnavailable()} is called. The timeout must
* be a positive value (i.e. >0).
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
*/
- public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
- int timeoutMs) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, int timeoutMs) {
checkTimeout(timeoutMs);
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
@@ -3186,7 +3703,7 @@
* Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
* by a timeout.
*
- * This function behaves identically to the non-timedout version, but if a suitable
+ * This function behaves identically to the version without timeout, but if a suitable
* network is not found within the given time (in milliseconds) the
* {@link NetworkCallback#onUnavailable} callback is called. The request can still be
* released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does
@@ -3208,9 +3725,12 @@
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
* @param timeoutMs The time in milliseconds to attempt looking for a suitable network
* before {@link NetworkCallback#onUnavailable} is called.
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
*/
- public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
- Handler handler, int timeoutMs) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) {
checkTimeout(timeoutMs);
int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
CallbackHandler cbHandler = new CallbackHandler(handler);
@@ -3267,7 +3787,7 @@
* as these {@code NetworkCapabilities} represent states that a particular
* network may never attain, and whether a network will attain these states
* is unknown prior to bringing up the network so the framework does not
- * know how to go about satisfing a request with these capabilities.
+ * know how to go about satisfying a request with these capabilities.
*
* <p>This method requires the caller to hold either the
* {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
@@ -3278,11 +3798,13 @@
* @param operation Action to perform when the network is available (corresponds
* to the {@link NetworkCallback#onAvailable} call. Typically
* comes from {@link PendingIntent#getBroadcast}. Cannot be null.
- * @throws IllegalArgumentException if {@code request} contains either
- * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
- * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
+ * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+ * @throws SecurityException if missing the appropriate permissions.
+ * @throws RuntimeException if request limit per UID is exceeded.
*/
- public void requestNetwork(NetworkRequest request, PendingIntent operation) {
+ public void requestNetwork(@NonNull NetworkRequest request,
+ @NonNull PendingIntent operation) {
+ printStackTrace();
checkPendingIntentNotNull(operation);
try {
mService.pendingRequestForNetwork(request.networkCapabilities, operation);
@@ -3305,7 +3827,8 @@
* {@link #requestNetwork(NetworkRequest, android.app.PendingIntent)} with the
* corresponding NetworkRequest you'd like to remove. Cannot be null.
*/
- public void releaseNetworkRequest(PendingIntent operation) {
+ public void releaseNetworkRequest(@NonNull PendingIntent operation) {
+ printStackTrace();
checkPendingIntentNotNull(operation);
try {
mService.releasePendingNetworkRequest(operation);
@@ -3329,7 +3852,8 @@
/**
* Registers to receive notifications about all networks which satisfy the given
* {@link NetworkRequest}. The callbacks will continue to be called until
- * either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called.
+ * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is
+ * called.
*
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The {@link NetworkCallback} that the system will call as suitable
@@ -3337,14 +3861,16 @@
* The callback is invoked on the default internal Handler.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback) {
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback) {
registerNetworkCallback(request, networkCallback, getDefaultHandler());
}
/**
* Registers to receive notifications about all networks which satisfy the given
* {@link NetworkRequest}. The callbacks will continue to be called until
- * either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called.
+ * either the application exits or {@link #unregisterNetworkCallback(NetworkCallback)} is
+ * called.
*
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The {@link NetworkCallback} that the system will call as suitable
@@ -3352,8 +3878,8 @@
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerNetworkCallback(
- NetworkRequest request, NetworkCallback networkCallback, Handler handler) {
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
CallbackHandler cbHandler = new CallbackHandler(handler);
NetworkCapabilities nc = request.networkCapabilities;
sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler);
@@ -3389,7 +3915,9 @@
* comes from {@link PendingIntent#getBroadcast}. Cannot be null.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
+ public void registerNetworkCallback(@NonNull NetworkRequest request,
+ @NonNull PendingIntent operation) {
+ printStackTrace();
checkPendingIntentNotNull(operation);
try {
mService.pendingListenForNetwork(request.networkCapabilities, operation);
@@ -3410,7 +3938,7 @@
* The callback is invoked on the default internal Handler.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerDefaultNetworkCallback(NetworkCallback networkCallback) {
+ public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback) {
registerDefaultNetworkCallback(networkCallback, getDefaultHandler());
}
@@ -3424,7 +3952,8 @@
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public void registerDefaultNetworkCallback(NetworkCallback networkCallback, Handler handler) {
+ public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
+ @NonNull Handler handler) {
// This works because if the NetworkCapabilities are null,
// ConnectivityService takes them from the default request.
//
@@ -3432,9 +3961,9 @@
// capabilities, this request is guaranteed, at all times, to be
// satisfied by the same network, if any, that satisfies the default
// request, i.e., the system default network.
- NetworkCapabilities nullCapabilities = null;
CallbackHandler cbHandler = new CallbackHandler(handler);
- sendRequestForNetwork(nullCapabilities, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
+ sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0,
+ REQUEST, TYPE_NONE, cbHandler);
}
/**
@@ -3449,7 +3978,7 @@
* @param network {@link Network} specifying which network you're interested.
* @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
*/
- public boolean requestBandwidthUpdate(Network network) {
+ public boolean requestBandwidthUpdate(@NonNull Network network) {
try {
return mService.requestBandwidthUpdate(network);
} catch (RemoteException e) {
@@ -3470,7 +3999,8 @@
*
* @param networkCallback The {@link NetworkCallback} used when making the request.
*/
- public void unregisterNetworkCallback(NetworkCallback networkCallback) {
+ public void unregisterNetworkCallback(@NonNull NetworkCallback networkCallback) {
+ printStackTrace();
checkCallbackNotNull(networkCallback);
final List<NetworkRequest> reqs = new ArrayList<>();
// Find all requests associated to this callback and stop callback triggers immediately.
@@ -3478,8 +4008,10 @@
synchronized (sCallbacks) {
Preconditions.checkArgument(networkCallback.networkRequest != null,
"NetworkCallback was not registered");
- Preconditions.checkArgument(networkCallback.networkRequest != ALREADY_UNREGISTERED,
- "NetworkCallback was already unregistered");
+ if (networkCallback.networkRequest == ALREADY_UNREGISTERED) {
+ Log.d(TAG, "NetworkCallback was already unregistered");
+ return;
+ }
for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
if (e.getValue() == networkCallback) {
reqs.add(e.getKey());
@@ -3508,7 +4040,7 @@
* {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}.
* Cannot be null.
*/
- public void unregisterNetworkCallback(PendingIntent operation) {
+ public void unregisterNetworkCallback(@NonNull PendingIntent operation) {
checkPendingIntentNotNull(operation);
releaseNetworkRequest(operation);
}
@@ -3527,7 +4059,7 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
try {
mService.setAcceptUnvalidated(network, accept, always);
@@ -3537,6 +4069,29 @@
}
/**
+ * Informs the system whether it should consider the network as validated even if it only has
+ * partial connectivity. If {@code accept} is true, then the network will be considered as
+ * validated even if connectivity is only partial. If {@code always} is true, then the choice
+ * is remembered, so that the next time the user connects to this network, the system will
+ * switch to it.
+ *
+ * @param network The network to accept.
+ * @param accept Whether to consider the network as validated even if it has partial
+ * connectivity.
+ * @param always Whether to remember this choice in the future.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) {
+ try {
+ mService.setAcceptPartialConnectivity(network, accept, always);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Informs the system to penalize {@code network}'s score when it becomes unvalidated. This is
* only meaningful if the system is configured not to penalize such networks, e.g., if the
* {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code
@@ -3546,7 +4101,7 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL)
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void setAvoidUnvalidated(Network network) {
try {
mService.setAvoidUnvalidated(network);
@@ -3572,6 +4127,42 @@
}
/**
+ * Requests that the system open the captive portal app with the specified extras.
+ *
+ * <p>This endpoint is exclusively for use by the NetworkStack and is protected by the
+ * corresponding permission.
+ * @param network Network on which the captive portal was detected.
+ * @param appExtras Extras to include in the app start intent.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+ public void startCaptivePortalApp(@NonNull Network network, @NonNull Bundle appExtras) {
+ try {
+ mService.startCaptivePortalAppInternal(network, appExtras);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Determine whether the device is configured to avoid bad wifi.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public boolean shouldAvoidBadWifi() {
+ try {
+ return mService.shouldAvoidBadWifi();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* It is acceptable to briefly use multipath data to provide seamless connectivity for
* time-sensitive user-facing operations when the system default network is temporarily
* unresponsive. The amount of data should be limited (less than one megabyte for every call to
@@ -3630,7 +4221,7 @@
* @return a bitwise OR of zero or more of the {@code MULTIPATH_PREFERENCE_*} constants.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public @MultipathPreference int getMultipathPreference(Network network) {
+ public @MultipathPreference int getMultipathPreference(@Nullable Network network) {
try {
return mService.getMultipathPreference(network);
} catch (RemoteException e) {
@@ -3668,8 +4259,8 @@
* the current binding.
* @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
*/
- public boolean bindProcessToNetwork(Network network) {
- // Forcing callers to call thru non-static function ensures ConnectivityManager
+ public boolean bindProcessToNetwork(@Nullable Network network) {
+ // Forcing callers to call through non-static function ensures ConnectivityManager
// instantiated.
return setProcessDefaultNetwork(network);
}
@@ -3696,12 +4287,19 @@
* is a direct replacement.
*/
@Deprecated
- public static boolean setProcessDefaultNetwork(Network network) {
+ public static boolean setProcessDefaultNetwork(@Nullable Network network) {
int netId = (network == null) ? NETID_UNSET : network.netId;
- if (netId == NetworkUtils.getBoundNetworkForProcess()) {
- return true;
+ boolean isSameNetId = (netId == NetworkUtils.getBoundNetworkForProcess());
+
+ if (netId != NETID_UNSET) {
+ netId = network.getNetIdForResolv();
}
- if (NetworkUtils.bindProcessToNetwork(netId)) {
+
+ if (!NetworkUtils.bindProcessToNetwork(netId)) {
+ return false;
+ }
+
+ if (!isSameNetId) {
// Set HTTP proxy system properties to match network.
// TODO: Deprecate this static method and replace it with a non-static version.
try {
@@ -3715,10 +4313,9 @@
// Must flush socket pool as idle sockets will be bound to previous network and may
// cause subsequent fetches to be performed on old network.
NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
- return true;
- } else {
- return false;
}
+
+ return true;
}
/**
@@ -3727,6 +4324,7 @@
*
* @return {@code Network} to which this process is bound, or {@code null}.
*/
+ @Nullable
public Network getBoundNetworkForProcess() {
// Forcing callers to call thru non-static function ensures ConnectivityManager
// instantiated.
@@ -3743,6 +4341,7 @@
* {@code getBoundNetworkForProcess} is a direct replacement.
*/
@Deprecated
+ @Nullable
public static Network getProcessDefaultNetwork() {
int netId = NetworkUtils.getBoundNetworkForProcess();
if (netId == NETID_UNSET) return null;
@@ -3751,8 +4350,9 @@
private void unsupportedStartingFrom(int version) {
if (Process.myUid() == Process.SYSTEM_UID) {
- // The getApplicationInfo() call we make below is not supported in system context, and
- // we want to allow the system to use these APIs anyway.
+ // The getApplicationInfo() call we make below is not supported in system context. Let
+ // the call through here, and rely on the fact that ConnectivityService will refuse to
+ // allow the system to use these APIs anyway.
return;
}
@@ -3769,11 +4369,6 @@
// functions by accessing ConnectivityService directly. However, it should be clear that doing
// so is unsupported and may break in the future. http://b/22728205
private void checkLegacyRoutingApiAccess() {
- if (mContext.checkCallingOrSelfPermission("com.android.permission.INJECT_OMADM_SETTINGS")
- == PackageManager.PERMISSION_GRANTED) {
- return;
- }
-
unsupportedStartingFrom(VERSION_CODES.M);
}
@@ -3788,9 +4383,10 @@
* @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}.
*/
@Deprecated
+ @UnsupportedAppUsage
public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
return NetworkUtils.bindProcessToNetworkForHostResolution(
- network == null ? NETID_UNSET : network.netId);
+ (network == null) ? NETID_UNSET : network.getNetIdForResolv());
}
/**
@@ -3872,6 +4468,7 @@
*
* @return Hash of network watchlist config file. Null if config does not exist.
*/
+ @Nullable
public byte[] getNetworkWatchlistConfigHash() {
try {
return mService.getNetworkWatchlistConfigHash();
@@ -3880,4 +4477,43 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Returns the {@code uid} of the owner of a network connection.
+ *
+ * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and
+ * {@code IPPROTO_UDP} currently supported.
+ * @param local The local {@link InetSocketAddress} of a connection.
+ * @param remote The remote {@link InetSocketAddress} of a connection.
+ *
+ * @return {@code uid} if the connection is found and the app has permission to observe it
+ * (e.g., if it is associated with the calling VPN app's tunnel) or
+ * {@link android.os.Process#INVALID_UID} if the connection is not found.
+ * Throws {@link SecurityException} if the caller is not the active VPN for the current user.
+ * Throws {@link IllegalArgumentException} if an unsupported protocol is requested.
+ */
+ public int getConnectionOwnerUid(int protocol, @NonNull InetSocketAddress local,
+ @NonNull InetSocketAddress remote) {
+ ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote);
+ try {
+ return mService.getConnectionOwnerUid(connectionInfo);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void printStackTrace() {
+ if (DEBUG) {
+ final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
+ final StringBuffer sb = new StringBuffer();
+ for (int i = 3; i < callStack.length; i++) {
+ final String stackTrace = callStack[i].toString();
+ if (stackTrace == null || stackTrace.contains("android.os")) {
+ break;
+ }
+ sb.append(" [").append(stackTrace).append("]");
+ }
+ Log.d(TAG, "StackLog:" + sb.toString());
+ }
+ }
}
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
new file mode 100644
index 0000000..0b1a845
--- /dev/null
+++ b/core/java/android/net/DnsResolver.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.net.NetworkUtils.getDnsNetwork;
+import static android.net.NetworkUtils.resNetworkCancel;
+import static android.net.NetworkUtils.resNetworkQuery;
+import static android.net.NetworkUtils.resNetworkResult;
+import static android.net.NetworkUtils.resNetworkSend;
+import static android.net.util.DnsUtils.haveIpv4;
+import static android.net.util.DnsUtils.haveIpv6;
+import static android.net.util.DnsUtils.rfc6724Sort;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.system.OsConstants.ENONET;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Dns resolver class for asynchronous dns querying
+ *
+ * Note that if a client sends a query with more than 1 record in the question section but
+ * the remote dns server does not support this, it may not respond at all, leading to a timeout.
+ *
+ */
+public final class DnsResolver {
+ private static final String TAG = "DnsResolver";
+ private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+ private static final int MAXPACKET = 8 * 1024;
+ private static final int SLEEP_TIME_MS = 2;
+
+ @IntDef(prefix = { "CLASS_" }, value = {
+ CLASS_IN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryClass {}
+ public static final int CLASS_IN = 1;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_A,
+ TYPE_AAAA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryType {}
+ public static final int TYPE_A = 1;
+ public static final int TYPE_AAAA = 28;
+
+ @IntDef(prefix = { "FLAG_" }, value = {
+ FLAG_EMPTY,
+ FLAG_NO_RETRY,
+ FLAG_NO_CACHE_STORE,
+ FLAG_NO_CACHE_LOOKUP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface QueryFlag {}
+ public static final int FLAG_EMPTY = 0;
+ public static final int FLAG_NO_RETRY = 1 << 0;
+ public static final int FLAG_NO_CACHE_STORE = 1 << 1;
+ public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
+
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_PARSE,
+ ERROR_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DnsError {}
+ /**
+ * Indicates that there was an error parsing the response the query.
+ * The cause of this error is available via getCause() and is a ParseException.
+ */
+ public static final int ERROR_PARSE = 0;
+ /**
+ * Indicates that there was an error sending the query.
+ * The cause of this error is available via getCause() and is an ErrnoException.
+ */
+ public static final int ERROR_SYSTEM = 1;
+
+ private static final int NETID_UNSET = 0;
+
+ private static final DnsResolver sInstance = new DnsResolver();
+
+ /**
+ * Get instance for DnsResolver
+ */
+ public static @NonNull DnsResolver getInstance() {
+ return sInstance;
+ }
+
+ private DnsResolver() {}
+
+ /**
+ * Base interface for answer callbacks
+ *
+ * @param <T> The type of the answer
+ */
+ public interface Callback<T> {
+ /**
+ * Success response to
+ * {@link android.net.DnsResolver#query query()} or
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
+ *
+ * Invoked when the answer to a query was successfully parsed.
+ *
+ * @param answer <T> answer to the query.
+ * @param rcode The response code in the DNS response.
+ *
+ * {@see android.net.DnsResolver#query query()}
+ */
+ void onAnswer(@NonNull T answer, int rcode);
+ /**
+ * Error response to
+ * {@link android.net.DnsResolver#query query()} or
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
+ *
+ * Invoked when there is no valid answer to
+ * {@link android.net.DnsResolver#query query()}
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
+ *
+ * @param error a {@link DnsException} object with additional
+ * detail regarding the failure
+ */
+ void onError(@NonNull DnsException error);
+ }
+
+ /**
+ * Class to represent DNS error
+ */
+ public static class DnsException extends Exception {
+ /**
+ * DNS error code as one of the ERROR_* constants
+ */
+ @DnsError public final int code;
+
+ DnsException(@DnsError int code, @Nullable Throwable cause) {
+ super(cause);
+ this.code = code;
+ }
+ }
+
+ /**
+ * Send a raw DNS query.
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param query blob message to query
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void rawQuery(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super byte[]> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final FileDescriptor queryfd;
+ try {
+ queryfd = resNetworkSend((network != null)
+ ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+
+ synchronized (lock) {
+ registerFDListener(executor, queryfd, callback, cancellationSignal, lock);
+ if (cancellationSignal == null) return;
+ addCancellationSignal(cancellationSignal, queryfd, lock);
+ }
+ }
+
+ /**
+ * Send a DNS query with the specified name, class and query type.
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param domain domain name to query
+ * @param nsClass dns class as one of the CLASS_* constants
+ * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void rawQuery(@Nullable Network network, @NonNull String domain,
+ @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super byte[]> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final FileDescriptor queryfd;
+ try {
+ queryfd = resNetworkQuery((network != null)
+ ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ synchronized (lock) {
+ registerFDListener(executor, queryfd, callback, cancellationSignal, lock);
+ if (cancellationSignal == null) return;
+ addCancellationSignal(cancellationSignal, queryfd, lock);
+ }
+ }
+
+ private class InetAddressAnswerAccumulator implements Callback<byte[]> {
+ private final List<InetAddress> mAllAnswers;
+ private final Network mNetwork;
+ private int mRcode;
+ private DnsException mDnsException;
+ private final Callback<? super List<InetAddress>> mUserCallback;
+ private final int mTargetAnswerCount;
+ private int mReceivedAnswerCount = 0;
+
+ InetAddressAnswerAccumulator(@NonNull Network network, int size,
+ @NonNull Callback<? super List<InetAddress>> callback) {
+ mNetwork = network;
+ mTargetAnswerCount = size;
+ mAllAnswers = new ArrayList<>();
+ mUserCallback = callback;
+ }
+
+ private boolean maybeReportError() {
+ if (mRcode != 0) {
+ mUserCallback.onAnswer(mAllAnswers, mRcode);
+ return true;
+ }
+ if (mDnsException != null) {
+ mUserCallback.onError(mDnsException);
+ return true;
+ }
+ return false;
+ }
+
+ private void maybeReportAnswer() {
+ if (++mReceivedAnswerCount != mTargetAnswerCount) return;
+ if (mAllAnswers.isEmpty() && maybeReportError()) return;
+ mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode);
+ }
+
+ @Override
+ public void onAnswer(@NonNull byte[] answer, int rcode) {
+ // If at least one query succeeded, return an rcode of 0.
+ // Otherwise, arbitrarily return the first rcode received.
+ if (mReceivedAnswerCount == 0 || rcode == 0) {
+ mRcode = rcode;
+ }
+ try {
+ mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses());
+ } catch (ParseException e) {
+ mDnsException = new DnsException(ERROR_PARSE, e);
+ }
+ maybeReportAnswer();
+ }
+
+ @Override
+ public void onError(@NonNull DnsException error) {
+ mDnsException = error;
+ maybeReportAnswer();
+ }
+ }
+
+ /**
+ * Send a DNS query with the specified name on a network with both IPv4 and IPv6,
+ * get back a set of InetAddresses with rfc6724 sorting style asynchronously.
+ *
+ * This method will examine the connection ability on given network, and query IPv4
+ * and IPv6 if connection is available.
+ *
+ * If at least one query succeeded with valid answer, rcode will be 0
+ *
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param domain domain name to query
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the
+ * caller of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super List<InetAddress>> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final Network queryNetwork;
+ try {
+ queryNetwork = (network != null) ? network : getDnsNetwork();
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ final boolean queryIpv6 = haveIpv6(queryNetwork);
+ final boolean queryIpv4 = haveIpv4(queryNetwork);
+
+ // This can only happen if queryIpv4 and queryIpv6 are both false.
+ // This almost certainly means that queryNetwork does not exist or no longer exists.
+ if (!queryIpv6 && !queryIpv4) {
+ executor.execute(() -> callback.onError(
+ new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET))));
+ return;
+ }
+
+ final FileDescriptor v4fd;
+ final FileDescriptor v6fd;
+
+ int queryCount = 0;
+
+ if (queryIpv6) {
+ try {
+ v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN,
+ TYPE_AAAA, flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ queryCount++;
+ } else v6fd = null;
+
+ // Avoiding gateways drop packets if queries are sent too close together
+ try {
+ Thread.sleep(SLEEP_TIME_MS);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+
+ if (queryIpv4) {
+ try {
+ v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A,
+ flags);
+ } catch (ErrnoException e) {
+ if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid.
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ queryCount++;
+ } else v4fd = null;
+
+ final InetAddressAnswerAccumulator accumulator =
+ new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback);
+
+ synchronized (lock) {
+ if (queryIpv6) {
+ registerFDListener(executor, v6fd, accumulator, cancellationSignal, lock);
+ }
+ if (queryIpv4) {
+ registerFDListener(executor, v4fd, accumulator, cancellationSignal, lock);
+ }
+ if (cancellationSignal == null) return;
+ cancellationSignal.setOnCancelListener(() -> {
+ synchronized (lock) {
+ if (queryIpv4) cancelQuery(v4fd);
+ if (queryIpv6) cancelQuery(v6fd);
+ }
+ });
+ }
+ }
+
+ /**
+ * Send a DNS query with the specified name and query type, get back a set of
+ * InetAddresses with rfc6724 sorting style asynchronously.
+ *
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param domain domain name to query
+ * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull String domain,
+ @QueryType int nsType, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super List<InetAddress>> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final FileDescriptor queryfd;
+ final Network queryNetwork;
+ try {
+ queryNetwork = (network != null) ? network : getDnsNetwork();
+ queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType,
+ flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ final InetAddressAnswerAccumulator accumulator =
+ new InetAddressAnswerAccumulator(queryNetwork, 1, callback);
+ synchronized (lock) {
+ registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
+ if (cancellationSignal == null) return;
+ addCancellationSignal(cancellationSignal, queryfd, lock);
+ }
+ }
+
+ /**
+ * Class to retrieve DNS response
+ *
+ * @hide
+ */
+ public static final class DnsResponse {
+ public final @NonNull byte[] answerbuf;
+ public final int rcode;
+ public DnsResponse(@NonNull byte[] answerbuf, int rcode) {
+ this.answerbuf = answerbuf;
+ this.rcode = rcode;
+ }
+ }
+
+ private void registerFDListener(@NonNull Executor executor,
+ @NonNull FileDescriptor queryfd, @NonNull Callback<? super byte[]> answerCallback,
+ @Nullable CancellationSignal cancellationSignal, @NonNull Object lock) {
+ final MessageQueue mainThreadMessageQueue = Looper.getMainLooper().getQueue();
+ mainThreadMessageQueue.addOnFileDescriptorEventListener(
+ queryfd,
+ FD_EVENTS,
+ (fd, events) -> {
+ // b/134310704
+ // Unregister fd event listener before resNetworkResult is called to prevent
+ // race condition caused by fd reused.
+ // For example when querying v4 and v6, it's possible that the first query ends
+ // and the fd is closed before the second request starts, which might return
+ // the same fd for the second request. By that time, the looper must have
+ // unregistered the fd, otherwise another event listener can't be registered.
+ mainThreadMessageQueue.removeOnFileDescriptorEventListener(fd);
+
+ executor.execute(() -> {
+ DnsResponse resp = null;
+ ErrnoException exception = null;
+ synchronized (lock) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ try {
+ resp = resNetworkResult(fd); // Closes fd, marks it invalid.
+ } catch (ErrnoException e) {
+ Log.e(TAG, "resNetworkResult:" + e.toString());
+ exception = e;
+ }
+ }
+ if (exception != null) {
+ answerCallback.onError(new DnsException(ERROR_SYSTEM, exception));
+ return;
+ }
+ answerCallback.onAnswer(resp.answerbuf, resp.rcode);
+ });
+
+ // The file descriptor has already been unregistered, so it does not really
+ // matter what is returned here. In spirit 0 (meaning "unregister this FD")
+ // is still the closest to what the looper needs to do. When returning 0,
+ // Looper knows to ignore the fd if it has already been unregistered.
+ return 0;
+ });
+ }
+
+ private void cancelQuery(@NonNull FileDescriptor queryfd) {
+ if (!queryfd.valid()) return;
+ Looper.getMainLooper().getQueue().removeOnFileDescriptorEventListener(queryfd);
+ resNetworkCancel(queryfd); // Closes fd, marks it invalid.
+ }
+
+ private void addCancellationSignal(@NonNull CancellationSignal cancellationSignal,
+ @NonNull FileDescriptor queryfd, @NonNull Object lock) {
+ cancellationSignal.setOnCancelListener(() -> {
+ synchronized (lock) {
+ cancelQuery(queryfd);
+ }
+ });
+ }
+
+ private static class DnsAddressAnswer extends DnsPacket {
+ private static final String TAG = "DnsResolver.DnsAddressAnswer";
+ private static final boolean DBG = false;
+
+ private final int mQueryType;
+
+ DnsAddressAnswer(@NonNull byte[] data) throws ParseException {
+ super(data);
+ if ((mHeader.flags & (1 << 15)) == 0) {
+ throw new ParseException("Not an answer packet");
+ }
+ if (mHeader.getRecordCount(QDSECTION) == 0) {
+ throw new ParseException("No question found");
+ }
+ // Expect only one question in question section.
+ mQueryType = mRecords[QDSECTION].get(0).nsType;
+ }
+
+ public @NonNull List<InetAddress> getAddresses() {
+ final List<InetAddress> results = new ArrayList<InetAddress>();
+ if (mHeader.getRecordCount(ANSECTION) == 0) return results;
+
+ for (final DnsRecord ansSec : mRecords[ANSECTION]) {
+ // Only support A and AAAA, also ignore answers if query type != answer type.
+ int nsType = ansSec.nsType;
+ if (nsType != mQueryType || (nsType != TYPE_A && nsType != TYPE_AAAA)) {
+ continue;
+ }
+ try {
+ results.add(InetAddress.getByAddress(ansSec.getRR()));
+ } catch (UnknownHostException e) {
+ if (DBG) {
+ Log.w(TAG, "rr to address fail");
+ }
+ }
+ }
+ return results;
+ }
+ }
+
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 83c862f..61648dc 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -17,7 +17,9 @@
package android.net;
import android.app.PendingIntent;
+import android.net.ConnectionInfo;
import android.net.LinkProperties;
+import android.net.ITetheringEventCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -25,7 +27,9 @@
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkState;
+import android.net.ISocketKeepaliveCallback;
import android.net.ProxyInfo;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Messenger;
import android.os.ParcelFileDescriptor;
@@ -45,10 +49,12 @@
{
Network getActiveNetwork();
Network getActiveNetworkForUid(int uid, boolean ignoreBlocked);
+ @UnsupportedAppUsage
NetworkInfo getActiveNetworkInfo();
NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked);
NetworkInfo getNetworkInfo(int networkType);
NetworkInfo getNetworkInfoForUid(in Network network, int uid, boolean ignoreBlocked);
+ @UnsupportedAppUsage
NetworkInfo[] getAllNetworkInfo();
Network getNetworkForType(int networkType);
Network[] getAllNetworks();
@@ -56,12 +62,14 @@
boolean isNetworkSupported(int networkType);
+ @UnsupportedAppUsage
LinkProperties getActiveLinkProperties();
LinkProperties getLinkPropertiesForType(int networkType);
LinkProperties getLinkProperties(in Network network);
NetworkCapabilities getNetworkCapabilities(in Network network);
+ @UnsupportedAppUsage
NetworkState[] getAllNetworkState();
NetworkQuotaInfo getActiveNetworkQuotaInfo();
@@ -73,6 +81,7 @@
int untether(String iface, String callerPkg);
+ @UnsupportedAppUsage
int getLastTetherError(String iface);
boolean isTetheringSupported(String callerPkg);
@@ -82,16 +91,21 @@
void stopTethering(int type, String callerPkg);
+ @UnsupportedAppUsage
String[] getTetherableIfaces();
+ @UnsupportedAppUsage
String[] getTetheredIfaces();
+ @UnsupportedAppUsage
String[] getTetheringErroredIfaces();
String[] getTetheredDhcpRanges();
+ @UnsupportedAppUsage
String[] getTetherableUsbRegexs();
+ @UnsupportedAppUsage
String[] getTetherableWifiRegexs();
String[] getTetherableBluetoothRegexs();
@@ -116,14 +130,18 @@
VpnConfig getVpnConfig(int userId);
+ @UnsupportedAppUsage
void startLegacyVpn(in VpnProfile profile);
LegacyVpnInfo getLegacyVpnInfo(int userId);
boolean updateLockdownVpn();
boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
- boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
+ boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
+ in List<String> lockdownWhitelist);
String getAlwaysOnVpnPackage(int userId);
+ boolean isVpnLockdownEnabled(int userId);
+ List<String> getVpnLockdownWhitelist(int userId);
int checkMobileProvisioning(int suggestedTimeOutMs);
@@ -133,14 +151,14 @@
void setAirplaneMode(boolean enable);
- void registerNetworkFactory(in Messenger messenger, in String name);
+ int registerNetworkFactory(in Messenger messenger, in String name);
boolean requestBandwidthUpdate(in Network network);
void unregisterNetworkFactory(in Messenger messenger);
int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
- in NetworkCapabilities nc, int score, in NetworkMisc misc);
+ in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
@@ -159,11 +177,16 @@
void releaseNetworkRequest(in NetworkRequest networkRequest);
void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
+ void setAcceptPartialConnectivity(in Network network, boolean accept, boolean always);
void setAvoidUnvalidated(in Network network);
void startCaptivePortalApp(in Network network);
+ void startCaptivePortalAppInternal(in Network network, in Bundle appExtras);
+ boolean shouldAvoidBadWifi();
int getMultipathPreference(in Network Network);
+ NetworkRequest getDefaultRequest();
+
int getRestoreDefaultNetworkDelay(int networkType);
boolean addVpnAddress(String address, int prefixLength);
@@ -172,12 +195,31 @@
void factoryReset();
- void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
- in IBinder binder, String srcAddr, int srcPort, String dstAddr);
+ void startNattKeepalive(in Network network, int intervalSeconds,
+ in ISocketKeepaliveCallback cb, String srcAddr, int srcPort, String dstAddr);
+
+ void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId,
+ int intervalSeconds, in ISocketKeepaliveCallback cb, String srcAddr,
+ String dstAddr);
+
+ void startTcpKeepalive(in Network network, in FileDescriptor fd, int intervalSeconds,
+ in ISocketKeepaliveCallback cb);
void stopKeepalive(in Network network, int slot);
String getCaptivePortalServerUrl();
byte[] getNetworkWatchlistConfigHash();
+
+ int getConnectionOwnerUid(in ConnectionInfo connectionInfo);
+ boolean isCallerCurrentAlwaysOnVpnApp();
+ boolean isCallerCurrentAlwaysOnVpnLockdownApp();
+
+ void getLatestTetheringEntitlementResult(int type, in ResultReceiver receiver,
+ boolean showEntitlementUi, String callerPkg);
+
+ void registerTetheringEventCallback(ITetheringEventCallback callback, String callerPkg);
+ void unregisterTetheringEventCallback(ITetheringEventCallback callback, String callerPkg);
+
+ IBinder startOrGetTestNetworkService();
}
diff --git a/core/java/android/net/InetAddresses.java b/core/java/android/net/InetAddresses.java
new file mode 100644
index 0000000..01b795e
--- /dev/null
+++ b/core/java/android/net/InetAddresses.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.annotation.NonNull;
+
+import libcore.net.InetAddressUtils;
+
+import java.net.InetAddress;
+
+/**
+ * Utility methods for {@link InetAddress} implementations.
+ */
+public class InetAddresses {
+
+ private InetAddresses() {}
+
+ /**
+ * Checks to see if the {@code address} is a numeric address (such as {@code "192.0.2.1"} or
+ * {@code "2001:db8::1:2"}).
+ *
+ * <p>A numeric address is either an IPv4 address containing exactly 4 decimal numbers or an
+ * IPv6 numeric address. IPv4 addresses that consist of either hexadecimal or octal digits or
+ * do not have exactly 4 numbers are not treated as numeric.
+ *
+ * <p>This method will never do a DNS lookup.
+ *
+ * @param address the address to parse.
+ * @return true if the supplied address is numeric, false otherwise.
+ */
+ public static boolean isNumericAddress(@NonNull String address) {
+ return InetAddressUtils.isNumericAddress(address);
+ }
+
+ /**
+ * Returns an InetAddress corresponding to the given numeric address (such
+ * as {@code "192.168.0.1"} or {@code "2001:4860:800d::68"}).
+ *
+ * <p>See {@link #isNumericAddress(String)} (String)} for a definition as to what constitutes a
+ * numeric address.
+ *
+ * <p>This method will never do a DNS lookup.
+ *
+ * @param address the address to parse, must be numeric.
+ * @return an {@link InetAddress} instance corresponding to the address.
+ * @throws IllegalArgumentException if {@code address} is not a numeric address.
+ */
+ public static @NonNull InetAddress parseNumericAddress(@NonNull String address) {
+ return InetAddressUtils.parseNumericAddress(address);
+ }
+}
diff --git a/core/java/android/net/IpConfiguration.java b/core/java/android/net/IpConfiguration.java
index fe69f29..3319f33 100644
--- a/core/java/android/net/IpConfiguration.java
+++ b/core/java/android/net/IpConfiguration.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.UnsupportedAppUsage;
import android.net.StaticIpConfiguration;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,8 +33,9 @@
public enum IpAssignment {
/* Use statically configured IP settings. Configuration can be accessed
* with staticIpConfiguration */
+ @UnsupportedAppUsage
STATIC,
- /* Use dynamically configured IP settigns */
+ /* Use dynamically configured IP settings */
DHCP,
/* no IP details are assigned, this is used to indicate
* that any existing IP settings should be retained */
@@ -47,6 +49,7 @@
public enum ProxySettings {
/* No proxy is to be used. Any existing proxy settings
* should be cleared. */
+ @UnsupportedAppUsage
NONE,
/* Use statically configured proxy. Configuration can be accessed
* with httpProxy. */
@@ -61,6 +64,7 @@
public ProxySettings proxySettings;
+ @UnsupportedAppUsage
public ProxyInfo httpProxy;
private void init(IpAssignment ipAssignment,
@@ -79,6 +83,7 @@
init(IpAssignment.UNASSIGNED, ProxySettings.UNASSIGNED, null, null);
}
+ @UnsupportedAppUsage
public IpConfiguration(IpAssignment ipAssignment,
ProxySettings proxySettings,
StaticIpConfiguration staticIpConfiguration,
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index 4631c56..416157c 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -16,6 +16,10 @@
package android.net;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pair;
@@ -68,7 +72,7 @@
*
* @hide
*/
- public IpPrefix(byte[] address, int prefixLength) {
+ public IpPrefix(@NonNull byte[] address, @IntRange(from = 0, to = 128) int prefixLength) {
this.address = address.clone();
this.prefixLength = prefixLength;
checkAndMaskAddressAndPrefixLength();
@@ -83,7 +87,9 @@
* @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
* @hide
*/
- public IpPrefix(InetAddress address, int prefixLength) {
+ @SystemApi
+ @TestApi
+ public IpPrefix(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength) {
// We don't reuse the (byte[], int) constructor because it calls clone() on the byte array,
// which is unnecessary because getAddress() already returns a clone.
this.address = address.getAddress();
@@ -100,7 +106,9 @@
*
* @hide
*/
- public IpPrefix(String prefix) {
+ @SystemApi
+ @TestApi
+ public IpPrefix(@NonNull String prefix) {
// We don't reuse the (InetAddress, int) constructor because "error: call to this must be
// first statement in constructor". We could factor out setting the member variables to an
// init() method, but if we did, then we'd have to make the members non-final, or "error:
@@ -143,13 +151,13 @@
*
* @return the address in the form of a byte array.
*/
- public InetAddress getAddress() {
+ public @NonNull InetAddress getAddress() {
try {
return InetAddress.getByAddress(address);
} catch (UnknownHostException e) {
// Cannot happen. InetAddress.getByAddress can only throw an exception if the byte
// array is the wrong length, but we check that in the constructor.
- return null;
+ throw new IllegalArgumentException("Address is invalid");
}
}
@@ -159,7 +167,7 @@
*
* @return the address in the form of a byte array.
*/
- public byte[] getRawAddress() {
+ public @NonNull byte[] getRawAddress() {
return address.clone();
}
@@ -168,6 +176,7 @@
*
* @return the prefix length.
*/
+ @IntRange(from = 0, to = 128)
public int getPrefixLength() {
return prefixLength;
}
@@ -176,10 +185,10 @@
* Determines whether the prefix contains the specified address.
*
* @param address An {@link InetAddress} to test.
- * @return {@code true} if the prefix covers the given address.
+ * @return {@code true} if the prefix covers the given address. {@code false} otherwise.
*/
- public boolean contains(InetAddress address) {
- byte[] addrBytes = (address == null) ? null : address.getAddress();
+ public boolean contains(@NonNull InetAddress address) {
+ byte[] addrBytes = address.getAddress();
if (addrBytes == null || addrBytes.length != this.address.length) {
return false;
}
@@ -194,7 +203,7 @@
* @param otherPrefix the prefix to test
* @hide
*/
- public boolean containsPrefix(IpPrefix otherPrefix) {
+ public boolean containsPrefix(@NonNull IpPrefix otherPrefix) {
if (otherPrefix.getPrefixLength() < prefixLength) return false;
final byte[] otherAddress = otherPrefix.getRawAddress();
NetworkUtils.maskRawAddress(otherAddress, prefixLength);
diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java
index 7436ad0..18726f7 100644
--- a/core/java/android/net/KeepalivePacketData.java
+++ b/core/java/android/net/KeepalivePacketData.java
@@ -16,22 +16,20 @@
package android.net;
-import static android.net.ConnectivityManager.PacketKeepalive.*;
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+import android.net.SocketKeepalive.InvalidPacketException;
import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
-import android.system.OsConstants;
import android.util.Log;
-import java.net.Inet4Address;
import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
/**
* Represents the actual packets that are sent by the
- * {@link android.net.ConnectivityManager.PacketKeepalive} API.
+ * {@link android.net.SocketKeepalive} API.
*
* @hide
*/
@@ -53,8 +51,8 @@
/** Packet data. A raw byte string of packet data, not including the link-layer header. */
private final byte[] mPacket;
- private static final int IPV4_HEADER_LENGTH = 20;
- private static final int UDP_HEADER_LENGTH = 8;
+ protected static final int IPV4_HEADER_LENGTH = 20;
+ protected static final int UDP_HEADER_LENGTH = 8;
// This should only be constructed via static factory methods, such as
// nattKeepalivePacket
@@ -80,53 +78,10 @@
}
}
- public static class InvalidPacketException extends Exception {
- public final int error;
- public InvalidPacketException(int error) {
- this.error = error;
- }
- }
-
public byte[] getPacket() {
return mPacket.clone();
}
- public static KeepalivePacketData nattKeepalivePacket(
- InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
- throws InvalidPacketException {
-
- if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
- throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
- }
-
- if (dstPort != NATT_PORT) {
- throw new InvalidPacketException(ERROR_INVALID_PORT);
- }
-
- int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
- ByteBuffer buf = ByteBuffer.allocate(length);
- buf.order(ByteOrder.BIG_ENDIAN);
- buf.putShort((short) 0x4500); // IP version and TOS
- buf.putShort((short) length);
- buf.putInt(0); // ID, flags, offset
- buf.put((byte) 64); // TTL
- buf.put((byte) OsConstants.IPPROTO_UDP);
- int ipChecksumOffset = buf.position();
- buf.putShort((short) 0); // IP checksum
- buf.put(srcAddress.getAddress());
- buf.put(dstAddress.getAddress());
- buf.putShort((short) srcPort);
- buf.putShort((short) dstPort);
- buf.putShort((short) (length - 20)); // UDP length
- int udpChecksumOffset = buf.position();
- buf.putShort((short) 0); // UDP checksum
- buf.put((byte) 0xff); // NAT-T keepalive
- buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
- buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH));
-
- return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
- }
-
/* Parcelable Implementation */
public int describeContents() {
return 0;
@@ -141,7 +96,7 @@
out.writeByteArray(mPacket);
}
- private KeepalivePacketData(Parcel in) {
+ protected KeepalivePacketData(Parcel in) {
srcAddress = NetworkUtils.numericToInetAddress(in.readString());
dstAddress = NetworkUtils.numericToInetAddress(in.readString());
srcPort = in.readInt();
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index bcfe938..f17adea 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -25,6 +25,13 @@
import static android.system.OsConstants.RT_SCOPE_SITE;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pair;
@@ -54,11 +61,13 @@
/**
* IPv4 or IPv6 address.
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private InetAddress address;
/**
* Prefix length.
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int prefixLength;
/**
@@ -100,8 +109,8 @@
*
* Per RFC 4193 section 8, fc00::/7 identifies these addresses.
*/
- private boolean isIPv6ULA() {
- if (isIPv6()) {
+ private boolean isIpv6ULA() {
+ if (isIpv6()) {
byte[] bytes = address.getAddress();
return ((bytes[0] & (byte)0xfe) == (byte)0xfc);
}
@@ -112,15 +121,31 @@
* @return true if the address is IPv6.
* @hide
*/
- public boolean isIPv6() {
+ @TestApi
+ @SystemApi
+ public boolean isIpv6() {
return address instanceof Inet6Address;
}
/**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return true if the address is IPv6.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean isIPv6() {
+ return isIpv6();
+ }
+
+ /**
* @return true if the address is IPv4 or is a mapped IPv4 address.
* @hide
*/
- public boolean isIPv4() {
+ @TestApi
+ @SystemApi
+ public boolean isIpv4() {
return address instanceof Inet4Address;
}
@@ -146,13 +171,16 @@
* Constructs a new {@code LinkAddress} from an {@code InetAddress} and prefix length, with
* the specified flags and scope. Flags and scope are not checked for validity.
* @param address The IP address.
- * @param prefixLength The prefix length.
+ * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
* @param flags A bitmask of {@code IFA_F_*} values representing properties of the address.
* @param scope An integer defining the scope in which the address is unique (e.g.,
* {@link OsConstants#RT_SCOPE_LINK} or {@link OsConstants#RT_SCOPE_SITE}).
* @hide
*/
- public LinkAddress(InetAddress address, int prefixLength, int flags, int scope) {
+ @SystemApi
+ @TestApi
+ public LinkAddress(@NonNull InetAddress address, @IntRange(from = 0, to = 128) int prefixLength,
+ int flags, int scope) {
init(address, prefixLength, flags, scope);
}
@@ -160,10 +188,13 @@
* Constructs a new {@code LinkAddress} from an {@code InetAddress} and a prefix length.
* The flags are set to zero and the scope is determined from the address.
* @param address The IP address.
- * @param prefixLength The prefix length.
+ * @param prefixLength The prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6).
* @hide
*/
- public LinkAddress(InetAddress address, int prefixLength) {
+ @SystemApi
+ @TestApi
+ public LinkAddress(@NonNull InetAddress address,
+ @IntRange(from = 0, to = 128) int prefixLength) {
this(address, prefixLength, 0, 0);
this.scope = scopeForUnicastAddress(address);
}
@@ -174,7 +205,7 @@
* @param interfaceAddress The interface address.
* @hide
*/
- public LinkAddress(InterfaceAddress interfaceAddress) {
+ public LinkAddress(@NonNull InterfaceAddress interfaceAddress) {
this(interfaceAddress.getAddress(),
interfaceAddress.getNetworkPrefixLength());
}
@@ -182,10 +213,12 @@
/**
* Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or
* "2001:db8::1/64". The flags are set to zero and the scope is determined from the address.
- * @param string The string to parse.
+ * @param address The string to parse.
* @hide
*/
- public LinkAddress(String address) {
+ @SystemApi
+ @TestApi
+ public LinkAddress(@NonNull String address) {
this(address, 0, 0);
this.scope = scopeForUnicastAddress(this.address);
}
@@ -193,12 +226,14 @@
/**
* Constructs a new {@code LinkAddress} from a string such as "192.0.2.5/24" or
* "2001:db8::1/64", with the specified flags and scope.
- * @param string The string to parse.
+ * @param address The string to parse.
* @param flags The address flags.
* @param scope The address scope.
* @hide
*/
- public LinkAddress(String address, int flags, int scope) {
+ @SystemApi
+ @TestApi
+ public LinkAddress(@NonNull String address, int flags, int scope) {
// This may throw an IllegalArgumentException; catching it is the caller's responsibility.
// TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24".
Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address);
@@ -255,7 +290,12 @@
* otherwise.
* @hide
*/
- public boolean isSameAddressAs(LinkAddress other) {
+ @TestApi
+ @SystemApi
+ public boolean isSameAddressAs(@Nullable LinkAddress other) {
+ if (other == null) {
+ return false;
+ }
return address.equals(other.address) && prefixLength == other.prefixLength;
}
@@ -269,6 +309,7 @@
/**
* Returns the prefix length of this {@code LinkAddress}.
*/
+ @IntRange(from = 0, to = 128)
public int getPrefixLength() {
return prefixLength;
}
@@ -278,6 +319,8 @@
* TODO: Delete all callers and remove in favour of getPrefixLength().
* @hide
*/
+ @UnsupportedAppUsage
+ @IntRange(from = 0, to = 128)
public int getNetworkPrefixLength() {
return getPrefixLength();
}
@@ -300,6 +343,8 @@
* Returns true if this {@code LinkAddress} is global scope and preferred.
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean isGlobalPreferred() {
/**
* Note that addresses flagged as IFA_F_OPTIMISTIC are
@@ -307,10 +352,10 @@
* state has cleared either DAD has succeeded or failed, and both
* flags are cleared regardless).
*/
- return (scope == RT_SCOPE_UNIVERSE &&
- !isIPv6ULA() &&
- (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED)) == 0L &&
- ((flags & IFA_F_TENTATIVE) == 0L || (flags & IFA_F_OPTIMISTIC) != 0L));
+ return (scope == RT_SCOPE_UNIVERSE
+ && !isIpv6ULA()
+ && (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED)) == 0L
+ && ((flags & IFA_F_TENTATIVE) == 0L || (flags & IFA_F_OPTIMISTIC) != 0L));
}
/**
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 300a78b..ad67763 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -18,6 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -32,6 +36,7 @@
import java.util.Hashtable;
import java.util.List;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* Describes the properties of a network link.
@@ -47,18 +52,22 @@
*/
public final class LinkProperties implements Parcelable {
// The interface described by the network link.
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mIfaceName;
- private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
- private ArrayList<InetAddress> mDnses = new ArrayList<InetAddress>();
- private ArrayList<InetAddress> mValidatedPrivateDnses = new ArrayList<InetAddress>();
+ private final ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
+ private final ArrayList<InetAddress> mDnses = new ArrayList<>();
+ // PCSCF addresses are addresses of SIP proxies that only exist for the IMS core service.
+ private final ArrayList<InetAddress> mPcscfs = new ArrayList<InetAddress>();
+ private final ArrayList<InetAddress> mValidatedPrivateDnses = new ArrayList<>();
private boolean mUsePrivateDns;
private String mPrivateDnsServerName;
private String mDomains;
- private ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
+ private ArrayList<RouteInfo> mRoutes = new ArrayList<>();
private ProxyInfo mHttpProxy;
private int mMtu;
// in the format "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max"
private String mTcpBufferSizes;
+ private IpPrefix mNat64Prefix;
private static final int MIN_MTU = 68;
private static final int MIN_MTU_V6 = 1280;
@@ -66,15 +75,14 @@
// Stores the properties of links that are "stacked" above this link.
// Indexed by interface name to allow modification and to prevent duplicates being added.
- private Hashtable<String, LinkProperties> mStackedLinks =
- new Hashtable<String, LinkProperties>();
+ private Hashtable<String, LinkProperties> mStackedLinks = new Hashtable<>();
/**
* @hide
*/
public static class CompareResult<T> {
- public final List<T> removed = new ArrayList<T>();
- public final List<T> added = new ArrayList<T>();
+ public final List<T> removed = new ArrayList<>();
+ public final List<T> added = new ArrayList<>();
public CompareResult() {}
@@ -93,12 +101,9 @@
@Override
public String toString() {
- String retVal = "removed=[";
- for (T addr : removed) retVal += addr.toString() + ",";
- retVal += "] added=[";
- for (T addr : added) retVal += addr.toString() + ",";
- retVal += "]";
- return retVal;
+ return "removed=[" + TextUtils.join(",", removed)
+ + "] added=[" + TextUtils.join(",", added)
+ + "]";
}
}
@@ -106,9 +111,13 @@
* @hide
*/
public enum ProvisioningChange {
+ @UnsupportedAppUsage
STILL_NOT_PROVISIONED,
+ @UnsupportedAppUsage
LOST_PROVISIONING,
+ @UnsupportedAppUsage
GAINED_PROVISIONING,
+ @UnsupportedAppUsage
STILL_PROVISIONED,
}
@@ -117,10 +126,11 @@
*
* @hide
*/
+ @UnsupportedAppUsage
public static ProvisioningChange compareProvisioning(
LinkProperties before, LinkProperties after) {
if (before.isProvisioned() && after.isProvisioned()) {
- // On dualstack networks, DHCPv4 renewals can occasionally fail.
+ // On dual-stack networks, DHCPv4 renewals can occasionally fail.
// When this happens, IPv6-reachable services continue to function
// normally but IPv4-only services (naturally) fail.
//
@@ -131,7 +141,7 @@
//
// For users, this is confusing and unexpected behaviour, and is
// not necessarily easy to diagnose. Therefore, we treat changing
- // from a dualstack network to an IPv6-only network equivalent to
+ // from a dual-stack network to an IPv6-only network equivalent to
// a total loss of provisioning.
//
// For one such example of this, see b/18867306.
@@ -139,9 +149,9 @@
// Additionally, losing IPv6 provisioning can result in TCP
// connections getting stuck until timeouts fire and other
// baffling failures. Therefore, loss of either IPv4 or IPv6 on a
- // previously dualstack network is deemed a lost of provisioning.
- if ((before.isIPv4Provisioned() && !after.isIPv4Provisioned()) ||
- (before.isIPv6Provisioned() && !after.isIPv6Provisioned())) {
+ // previously dual-stack network is deemed a lost of provisioning.
+ if ((before.isIpv4Provisioned() && !after.isIpv4Provisioned())
+ || (before.isIpv6Provisioned() && !after.isIpv6Provisioned())) {
return ProvisioningChange.LOST_PROVISIONING;
}
return ProvisioningChange.STILL_PROVISIONED;
@@ -155,7 +165,7 @@
}
/**
- * @hide
+ * Constructs a new {@code LinkProperties} with default values.
*/
public LinkProperties() {
}
@@ -163,25 +173,26 @@
/**
* @hide
*/
- public LinkProperties(LinkProperties source) {
+ @SystemApi
+ @TestApi
+ public LinkProperties(@Nullable LinkProperties source) {
if (source != null) {
- mIfaceName = source.getInterfaceName();
- for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l);
- for (InetAddress i : source.getDnsServers()) mDnses.add(i);
- for (InetAddress i : source.getValidatedPrivateDnsServers()) {
- mValidatedPrivateDnses.add(i);
- }
+ mIfaceName = source.mIfaceName;
+ mLinkAddresses.addAll(source.mLinkAddresses);
+ mDnses.addAll(source.mDnses);
+ mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
mUsePrivateDns = source.mUsePrivateDns;
mPrivateDnsServerName = source.mPrivateDnsServerName;
- mDomains = source.getDomains();
- for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
- mHttpProxy = (source.getHttpProxy() == null) ?
- null : new ProxyInfo(source.getHttpProxy());
+ mPcscfs.addAll(source.mPcscfs);
+ mDomains = source.mDomains;
+ mRoutes.addAll(source.mRoutes);
+ mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
for (LinkProperties l: source.mStackedLinks.values()) {
addStackedLink(l);
}
- setMtu(source.getMtu());
+ setMtu(source.mMtu);
mTcpBufferSizes = source.mTcpBufferSizes;
+ mNat64Prefix = source.mNat64Prefix;
}
}
@@ -190,11 +201,10 @@
* will have their interface changed to match this new value.
*
* @param iface The name of the network interface used for this link.
- * @hide
*/
- public void setInterfaceName(String iface) {
+ public void setInterfaceName(@Nullable String iface) {
mIfaceName = iface;
- ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
+ ArrayList<RouteInfo> newRoutes = new ArrayList<>(mRoutes.size());
for (RouteInfo route : mRoutes) {
newRoutes.add(routeWithInterface(route));
}
@@ -213,9 +223,10 @@
/**
* @hide
*/
- public List<String> getAllInterfaceNames() {
- List<String> interfaceNames = new ArrayList<String>(mStackedLinks.size() + 1);
- if (mIfaceName != null) interfaceNames.add(new String(mIfaceName));
+ @UnsupportedAppUsage
+ public @NonNull List<String> getAllInterfaceNames() {
+ List<String> interfaceNames = new ArrayList<>(mStackedLinks.size() + 1);
+ if (mIfaceName != null) interfaceNames.add(mIfaceName);
for (LinkProperties stacked: mStackedLinks.values()) {
interfaceNames.addAll(stacked.getAllInterfaceNames());
}
@@ -229,11 +240,12 @@
* prefix lengths for each address. This is a simplified utility alternative to
* {@link LinkProperties#getLinkAddresses}.
*
- * @return An umodifiable {@link List} of {@link InetAddress} for this link.
+ * @return An unmodifiable {@link List} of {@link InetAddress} for this link.
* @hide
*/
- public List<InetAddress> getAddresses() {
- List<InetAddress> addresses = new ArrayList<InetAddress>();
+ @UnsupportedAppUsage
+ public @NonNull List<InetAddress> getAddresses() {
+ final List<InetAddress> addresses = new ArrayList<>();
for (LinkAddress linkAddress : mLinkAddresses) {
addresses.add(linkAddress.getAddress());
}
@@ -244,8 +256,9 @@
* Returns all the addresses on this link and all the links stacked above it.
* @hide
*/
- public List<InetAddress> getAllAddresses() {
- List<InetAddress> addresses = new ArrayList<InetAddress>();
+ @UnsupportedAppUsage
+ public @NonNull List<InetAddress> getAllAddresses() {
+ List<InetAddress> addresses = new ArrayList<>();
for (LinkAddress linkAddress : mLinkAddresses) {
addresses.add(linkAddress.getAddress());
}
@@ -271,7 +284,9 @@
* @return true if {@code address} was added or updated, false otherwise.
* @hide
*/
- public boolean addLinkAddress(LinkAddress address) {
+ @SystemApi
+ @TestApi
+ public boolean addLinkAddress(@NonNull LinkAddress address) {
if (address == null) {
return false;
}
@@ -298,7 +313,9 @@
* @return true if the address was removed, false if it did not exist.
* @hide
*/
- public boolean removeLinkAddress(LinkAddress toRemove) {
+ @SystemApi
+ @TestApi
+ public boolean removeLinkAddress(@NonNull LinkAddress toRemove) {
int i = findLinkAddressIndex(toRemove);
if (i >= 0) {
mLinkAddresses.remove(i);
@@ -313,7 +330,7 @@
*
* @return An unmodifiable {@link List} of {@link LinkAddress} for this link.
*/
- public List<LinkAddress> getLinkAddresses() {
+ public @NonNull List<LinkAddress> getLinkAddresses() {
return Collections.unmodifiableList(mLinkAddresses);
}
@@ -321,9 +338,9 @@
* Returns all the addresses on this link and all the links stacked above it.
* @hide
*/
+ @UnsupportedAppUsage
public List<LinkAddress> getAllLinkAddresses() {
- List<LinkAddress> addresses = new ArrayList<LinkAddress>();
- addresses.addAll(mLinkAddresses);
+ List<LinkAddress> addresses = new ArrayList<>(mLinkAddresses);
for (LinkProperties stacked: mStackedLinks.values()) {
addresses.addAll(stacked.getAllLinkAddresses());
}
@@ -336,9 +353,8 @@
*
* @param addresses The {@link Collection} of {@link LinkAddress} to set in this
* object.
- * @hide
*/
- public void setLinkAddresses(Collection<LinkAddress> addresses) {
+ public void setLinkAddresses(@NonNull Collection<LinkAddress> addresses) {
mLinkAddresses.clear();
for (LinkAddress address: addresses) {
addLinkAddress(address);
@@ -352,7 +368,9 @@
* @return true if the DNS server was added, false if it was already present.
* @hide
*/
- public boolean addDnsServer(InetAddress dnsServer) {
+ @TestApi
+ @SystemApi
+ public boolean addDnsServer(@NonNull InetAddress dnsServer) {
if (dnsServer != null && !mDnses.contains(dnsServer)) {
mDnses.add(dnsServer);
return true;
@@ -367,11 +385,10 @@
* @return true if the DNS server was removed, false if it did not exist.
* @hide
*/
- public boolean removeDnsServer(InetAddress dnsServer) {
- if (dnsServer != null) {
- return mDnses.remove(dnsServer);
- }
- return false;
+ @TestApi
+ @SystemApi
+ public boolean removeDnsServer(@NonNull InetAddress dnsServer) {
+ return mDnses.remove(dnsServer);
}
/**
@@ -379,9 +396,8 @@
* the given {@link Collection} of {@link InetAddress} objects.
*
* @param dnsServers The {@link Collection} of DNS servers to set in this object.
- * @hide
*/
- public void setDnsServers(Collection<InetAddress> dnsServers) {
+ public void setDnsServers(@NonNull Collection<InetAddress> dnsServers) {
mDnses.clear();
for (InetAddress dnsServer: dnsServers) {
addDnsServer(dnsServer);
@@ -391,10 +407,10 @@
/**
* Returns all the {@link InetAddress} for DNS servers on this link.
*
- * @return An umodifiable {@link List} of {@link InetAddress} for DNS servers on
+ * @return An unmodifiable {@link List} of {@link InetAddress} for DNS servers on
* this link.
*/
- public List<InetAddress> getDnsServers() {
+ public @NonNull List<InetAddress> getDnsServers() {
return Collections.unmodifiableList(mDnses);
}
@@ -404,6 +420,8 @@
* @param usePrivateDns The private DNS state.
* @hide
*/
+ @TestApi
+ @SystemApi
public void setUsePrivateDns(boolean usePrivateDns) {
mUsePrivateDns = usePrivateDns;
}
@@ -429,6 +447,8 @@
* @param privateDnsServerName The private DNS server name.
* @hide
*/
+ @TestApi
+ @SystemApi
public void setPrivateDnsServerName(@Nullable String privateDnsServerName) {
mPrivateDnsServerName = privateDnsServerName;
}
@@ -460,7 +480,7 @@
* @return true if the DNS server was added, false if it was already present.
* @hide
*/
- public boolean addValidatedPrivateDnsServer(InetAddress dnsServer) {
+ public boolean addValidatedPrivateDnsServer(@NonNull InetAddress dnsServer) {
if (dnsServer != null && !mValidatedPrivateDnses.contains(dnsServer)) {
mValidatedPrivateDnses.add(dnsServer);
return true;
@@ -476,11 +496,8 @@
* @return true if the DNS server was removed, false if it did not exist.
* @hide
*/
- public boolean removeValidatedPrivateDnsServer(InetAddress dnsServer) {
- if (dnsServer != null) {
- return mValidatedPrivateDnses.remove(dnsServer);
- }
- return false;
+ public boolean removeValidatedPrivateDnsServer(@NonNull InetAddress dnsServer) {
+ return mValidatedPrivateDnses.remove(dnsServer);
}
/**
@@ -491,7 +508,9 @@
* object.
* @hide
*/
- public void setValidatedPrivateDnsServers(Collection<InetAddress> dnsServers) {
+ @TestApi
+ @SystemApi
+ public void setValidatedPrivateDnsServers(@NonNull Collection<InetAddress> dnsServers) {
mValidatedPrivateDnses.clear();
for (InetAddress dnsServer: dnsServers) {
addValidatedPrivateDnsServer(dnsServer);
@@ -502,32 +521,88 @@
* Returns all the {@link InetAddress} for validated private DNS servers on this link.
* These are resolved from the private DNS server name.
*
- * @return An umodifiable {@link List} of {@link InetAddress} for validated private
+ * @return An unmodifiable {@link List} of {@link InetAddress} for validated private
* DNS servers on this link.
* @hide
*/
- public List<InetAddress> getValidatedPrivateDnsServers() {
+ @TestApi
+ @SystemApi
+ public @NonNull List<InetAddress> getValidatedPrivateDnsServers() {
return Collections.unmodifiableList(mValidatedPrivateDnses);
}
/**
+ * Adds the given {@link InetAddress} to the list of PCSCF servers, if not present.
+ *
+ * @param pcscfServer The {@link InetAddress} to add to the list of PCSCF servers.
+ * @return true if the PCSCF server was added, false otherwise.
+ * @hide
+ */
+ public boolean addPcscfServer(@NonNull InetAddress pcscfServer) {
+ if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) {
+ mPcscfs.add(pcscfServer);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Removes the given {@link InetAddress} from the list of PCSCF servers.
+ *
+ * @param pcscfServer The {@link InetAddress} to remove from the list of PCSCF servers.
+ * @return true if the PCSCF server was removed, false otherwise.
+ * @hide
+ */
+ public boolean removePcscfServer(@NonNull InetAddress pcscfServer) {
+ return mPcscfs.remove(pcscfServer);
+ }
+
+ /**
+ * Replaces the PCSCF servers in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link InetAddress} objects.
+ *
+ * @param pcscfServers The {@link Collection} of PCSCF servers to set in this object.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void setPcscfServers(@NonNull Collection<InetAddress> pcscfServers) {
+ mPcscfs.clear();
+ for (InetAddress pcscfServer: pcscfServers) {
+ addPcscfServer(pcscfServer);
+ }
+ }
+
+ /**
+ * Returns all the {@link InetAddress} for PCSCF servers on this link.
+ *
+ * @return An unmodifiable {@link List} of {@link InetAddress} for PCSCF servers on
+ * this link.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public @NonNull List<InetAddress> getPcscfServers() {
+ return Collections.unmodifiableList(mPcscfs);
+ }
+
+ /**
* Sets the DNS domain search path used on this link.
*
* @param domains A {@link String} listing in priority order the comma separated
* domains to search when resolving host names on this link.
- * @hide
*/
- public void setDomains(String domains) {
+ public void setDomains(@Nullable String domains) {
mDomains = domains;
}
/**
- * Get the DNS domains search path set for this link.
+ * Get the DNS domains search path set for this link. May be {@code null} if not set.
*
- * @return A {@link String} containing the comma separated domains to search when resolving
- * host names on this link.
+ * @return A {@link String} containing the comma separated domains to search when resolving host
+ * names on this link or {@code null}.
*/
- public String getDomains() {
+ public @Nullable String getDomains() {
return mDomains;
}
@@ -537,7 +612,6 @@
* 10000 will be ignored.
*
* @param mtu The MTU to use for this link.
- * @hide
*/
public void setMtu(int mtu) {
mMtu = mtu;
@@ -548,7 +622,6 @@
* this will return 0.
*
* @return The mtu value set for this link.
- * @hide
*/
public int getMtu() {
return mMtu;
@@ -562,18 +635,22 @@
*
* @hide
*/
- public void setTcpBufferSizes(String tcpBufferSizes) {
+ @TestApi
+ @SystemApi
+ public void setTcpBufferSizes(@Nullable String tcpBufferSizes) {
mTcpBufferSizes = tcpBufferSizes;
}
/**
- * Gets the tcp buffer sizes.
+ * Gets the tcp buffer sizes. May be {@code null} if not set.
*
- * @return the tcp buffer sizes to use when this link is the system default.
+ * @return the tcp buffer sizes to use when this link is the system default or {@code null}.
*
* @hide
*/
- public String getTcpBufferSizes() {
+ @TestApi
+ @SystemApi
+ public @Nullable String getTcpBufferSizes() {
return mTcpBufferSizes;
}
@@ -593,22 +670,18 @@
*
* @param route A {@link RouteInfo} to add to this object.
* @return {@code false} if the route was already present, {@code true} if it was added.
- *
- * @hide
*/
- public boolean addRoute(RouteInfo route) {
- if (route != null) {
- String routeIface = route.getInterface();
- if (routeIface != null && !routeIface.equals(mIfaceName)) {
- throw new IllegalArgumentException(
- "Route added with non-matching interface: " + routeIface +
- " vs. " + mIfaceName);
- }
- route = routeWithInterface(route);
- if (!mRoutes.contains(route)) {
- mRoutes.add(route);
- return true;
- }
+ public boolean addRoute(@NonNull RouteInfo route) {
+ String routeIface = route.getInterface();
+ if (routeIface != null && !routeIface.equals(mIfaceName)) {
+ throw new IllegalArgumentException(
+ "Route added with non-matching interface: " + routeIface
+ + " vs. " + mIfaceName);
+ }
+ route = routeWithInterface(route);
+ if (!mRoutes.contains(route)) {
+ mRoutes.add(route);
+ return true;
}
return false;
}
@@ -622,10 +695,10 @@
*
* @hide
*/
- public boolean removeRoute(RouteInfo route) {
- return route != null &&
- Objects.equals(mIfaceName, route.getInterface()) &&
- mRoutes.remove(route);
+ @TestApi
+ @SystemApi
+ public boolean removeRoute(@NonNull RouteInfo route) {
+ return Objects.equals(mIfaceName, route.getInterface()) && mRoutes.remove(route);
}
/**
@@ -633,7 +706,7 @@
*
* @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
*/
- public List<RouteInfo> getRoutes() {
+ public @NonNull List<RouteInfo> getRoutes() {
return Collections.unmodifiableList(mRoutes);
}
@@ -643,7 +716,7 @@
* @hide
*/
public void ensureDirectlyConnectedRoutes() {
- for (LinkAddress addr: mLinkAddresses) {
+ for (LinkAddress addr : mLinkAddresses) {
addRoute(new RouteInfo(addr, null, mIfaceName));
}
}
@@ -652,9 +725,9 @@
* Returns all the routes on this link and all the links stacked above it.
* @hide
*/
- public List<RouteInfo> getAllRoutes() {
- List<RouteInfo> routes = new ArrayList<>();
- routes.addAll(mRoutes);
+ @UnsupportedAppUsage
+ public @NonNull List<RouteInfo> getAllRoutes() {
+ List<RouteInfo> routes = new ArrayList<>(mRoutes);
for (LinkProperties stacked: mStackedLinks.values()) {
routes.addAll(stacked.getAllRoutes());
}
@@ -667,34 +740,64 @@
* not enforce it and applications may ignore them.
*
* @param proxy A {@link ProxyInfo} defining the HTTP Proxy to use on this link.
- * @hide
*/
- public void setHttpProxy(ProxyInfo proxy) {
+ public void setHttpProxy(@Nullable ProxyInfo proxy) {
mHttpProxy = proxy;
}
/**
* Gets the recommended {@link ProxyInfo} (or {@code null}) set on this link.
*
- * @return The {@link ProxyInfo} set on this link
+ * @return The {@link ProxyInfo} set on this link or {@code null}.
*/
- public ProxyInfo getHttpProxy() {
+ public @Nullable ProxyInfo getHttpProxy() {
return mHttpProxy;
}
/**
+ * Returns the NAT64 prefix in use on this link, if any.
+ *
+ * @return the NAT64 prefix or {@code null}.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public @Nullable IpPrefix getNat64Prefix() {
+ return mNat64Prefix;
+ }
+
+ /**
+ * Sets the NAT64 prefix in use on this link.
+ *
+ * Currently, only 96-bit prefixes (i.e., where the 32-bit IPv4 address is at the end of the
+ * 128-bit IPv6 address) are supported or {@code null} for no prefix.
+ *
+ * @param prefix the NAT64 prefix.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void setNat64Prefix(@Nullable IpPrefix prefix) {
+ if (prefix != null && prefix.getPrefixLength() != 96) {
+ throw new IllegalArgumentException("Only 96-bit prefixes are supported: " + prefix);
+ }
+ mNat64Prefix = prefix; // IpPrefix objects are immutable.
+ }
+
+ /**
* Adds a stacked link.
*
- * If there is already a stacked link with the same interfacename as link,
+ * If there is already a stacked link with the same interface name as link,
* that link is replaced with link. Otherwise, link is added to the list
- * of stacked links. If link is null, nothing changes.
+ * of stacked links.
*
* @param link The link to add.
* @return true if the link was stacked, false otherwise.
* @hide
*/
- public boolean addStackedLink(LinkProperties link) {
- if (link != null && link.getInterfaceName() != null) {
+ @UnsupportedAppUsage
+ public boolean addStackedLink(@NonNull LinkProperties link) {
+ if (link.getInterfaceName() != null) {
mStackedLinks.put(link.getInterfaceName(), link);
return true;
}
@@ -711,23 +814,21 @@
* @return true if the link was removed, false otherwise.
* @hide
*/
- public boolean removeStackedLink(String iface) {
- if (iface != null) {
- LinkProperties removed = mStackedLinks.remove(iface);
- return removed != null;
- }
- return false;
+ public boolean removeStackedLink(@NonNull String iface) {
+ LinkProperties removed = mStackedLinks.remove(iface);
+ return removed != null;
}
/**
* Returns all the links stacked on top of this link.
* @hide
*/
+ @UnsupportedAppUsage
public @NonNull List<LinkProperties> getStackedLinks() {
if (mStackedLinks.isEmpty()) {
- return Collections.EMPTY_LIST;
+ return Collections.emptyList();
}
- List<LinkProperties> stacked = new ArrayList<LinkProperties>();
+ final List<LinkProperties> stacked = new ArrayList<>();
for (LinkProperties link : mStackedLinks.values()) {
stacked.add(new LinkProperties(link));
}
@@ -736,7 +837,6 @@
/**
* Clears this object to its initial state.
- * @hide
*/
public void clear() {
mIfaceName = null;
@@ -744,12 +844,14 @@
mDnses.clear();
mUsePrivateDns = false;
mPrivateDnsServerName = null;
+ mPcscfs.clear();
mDomains = null;
mRoutes.clear();
mHttpProxy = null;
mStackedLinks.clear();
mMtu = 0;
mTcpBufferSizes = null;
+ mNat64Prefix = null;
}
/**
@@ -761,57 +863,87 @@
@Override
public String toString() {
- String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
+ // Space as a separator, so no need for spaces at start/end of the individual fragments.
+ final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
- String linkAddresses = "LinkAddresses: [";
- for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString() + ",";
- linkAddresses += "] ";
-
- String dns = "DnsAddresses: [";
- for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
- dns += "] ";
-
- String usePrivateDns = "UsePrivateDns: " + mUsePrivateDns + " ";
-
- String privateDnsServerName = "";
- if (privateDnsServerName != null) {
- privateDnsServerName = "PrivateDnsServerName: " + mPrivateDnsServerName + " ";
+ if (mIfaceName != null) {
+ resultJoiner.add("InterfaceName:");
+ resultJoiner.add(mIfaceName);
}
- String validatedPrivateDns = "";
+ resultJoiner.add("LinkAddresses: [");
+ if (!mLinkAddresses.isEmpty()) {
+ resultJoiner.add(TextUtils.join(",", mLinkAddresses));
+ }
+ resultJoiner.add("]");
+
+ resultJoiner.add("DnsAddresses: [");
+ if (!mDnses.isEmpty()) {
+ resultJoiner.add(TextUtils.join(",", mDnses));
+ }
+ resultJoiner.add("]");
+
+ if (mUsePrivateDns) {
+ resultJoiner.add("UsePrivateDns: true");
+ }
+
+ if (mPrivateDnsServerName != null) {
+ resultJoiner.add("PrivateDnsServerName:");
+ resultJoiner.add(mPrivateDnsServerName);
+ }
+
+ if (!mPcscfs.isEmpty()) {
+ resultJoiner.add("PcscfAddresses: [");
+ resultJoiner.add(TextUtils.join(",", mPcscfs));
+ resultJoiner.add("]");
+ }
+
if (!mValidatedPrivateDnses.isEmpty()) {
- validatedPrivateDns = "ValidatedPrivateDnsAddresses: [";
- for (InetAddress addr : mValidatedPrivateDnses) {
- validatedPrivateDns += addr.getHostAddress() + ",";
+ final StringJoiner validatedPrivateDnsesJoiner =
+ new StringJoiner(",", "ValidatedPrivateDnsAddresses: [", "]");
+ for (final InetAddress addr : mValidatedPrivateDnses) {
+ validatedPrivateDnsesJoiner.add(addr.getHostAddress());
}
- validatedPrivateDns += "] ";
+ resultJoiner.add(validatedPrivateDnsesJoiner.toString());
}
- String domainName = "Domains: " + mDomains;
+ resultJoiner.add("Domains:");
+ resultJoiner.add(mDomains);
- String mtu = " MTU: " + mMtu;
+ resultJoiner.add("MTU:");
+ resultJoiner.add(Integer.toString(mMtu));
- String tcpBuffSizes = "";
if (mTcpBufferSizes != null) {
- tcpBuffSizes = " TcpBufferSizes: " + mTcpBufferSizes;
+ resultJoiner.add("TcpBufferSizes:");
+ resultJoiner.add(mTcpBufferSizes);
}
- String routes = " Routes: [";
- for (RouteInfo route : mRoutes) routes += route.toString() + ",";
- routes += "] ";
- String proxy = (mHttpProxy == null ? "" : " HttpProxy: " + mHttpProxy.toString() + " ");
+ resultJoiner.add("Routes: [");
+ if (!mRoutes.isEmpty()) {
+ resultJoiner.add(TextUtils.join(",", mRoutes));
+ }
+ resultJoiner.add("]");
- String stacked = "";
- if (mStackedLinks.values().size() > 0) {
- stacked += " Stacked: [";
- for (LinkProperties link: mStackedLinks.values()) {
- stacked += " [" + link.toString() + " ],";
+ if (mHttpProxy != null) {
+ resultJoiner.add("HttpProxy:");
+ resultJoiner.add(mHttpProxy.toString());
+ }
+
+ if (mNat64Prefix != null) {
+ resultJoiner.add("Nat64Prefix:");
+ resultJoiner.add(mNat64Prefix.toString());
+ }
+
+ final Collection<LinkProperties> stackedLinksValues = mStackedLinks.values();
+ if (!stackedLinksValues.isEmpty()) {
+ final StringJoiner stackedLinksJoiner = new StringJoiner(",", "Stacked: [", "]");
+ for (final LinkProperties lp : stackedLinksValues) {
+ stackedLinksJoiner.add("[ " + lp + " ]");
}
- stacked += "] ";
+ resultJoiner.add(stackedLinksJoiner.toString());
}
- return "{" + ifaceName + linkAddresses + routes + dns + usePrivateDns
- + privateDnsServerName + domainName + mtu + tcpBuffSizes + proxy
- + stacked + "}";
+
+ return resultJoiner.toString();
}
/**
@@ -820,7 +952,9 @@
* @return {@code true} if there is an IPv4 address, {@code false} otherwise.
* @hide
*/
- public boolean hasIPv4Address() {
+ @TestApi
+ @SystemApi
+ public boolean hasIpv4Address() {
for (LinkAddress address : mLinkAddresses) {
if (address.getAddress() instanceof Inet4Address) {
return true;
@@ -830,15 +964,27 @@
}
/**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv4Address() {
+ return hasIpv4Address();
+ }
+
+ /**
* Returns true if this link or any of its stacked interfaces has an IPv4 address.
*
* @return {@code true} if there is an IPv4 address, {@code false} otherwise.
*/
- private boolean hasIPv4AddressOnInterface(String iface) {
+ private boolean hasIpv4AddressOnInterface(String iface) {
// mIfaceName can be null.
- return (Objects.equals(iface, mIfaceName) && hasIPv4Address()) ||
- (iface != null && mStackedLinks.containsKey(iface) &&
- mStackedLinks.get(iface).hasIPv4Address());
+ return (Objects.equals(iface, mIfaceName) && hasIpv4Address())
+ || (iface != null && mStackedLinks.containsKey(iface)
+ && mStackedLinks.get(iface).hasIpv4Address());
}
/**
@@ -847,7 +993,9 @@
* @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
* @hide
*/
- public boolean hasGlobalIPv6Address() {
+ @TestApi
+ @SystemApi
+ public boolean hasGlobalIpv6Address() {
for (LinkAddress address : mLinkAddresses) {
if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
return true;
@@ -857,12 +1005,25 @@
}
/**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasGlobalIPv6Address() {
+ return hasGlobalIpv6Address();
+ }
+
+ /**
* Returns true if this link has an IPv4 default route.
*
* @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
* @hide
*/
- public boolean hasIPv4DefaultRoute() {
+ @UnsupportedAppUsage
+ public boolean hasIpv4DefaultRoute() {
for (RouteInfo r : mRoutes) {
if (r.isIPv4Default()) {
return true;
@@ -872,12 +1033,26 @@
}
/**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv4DefaultRoute() {
+ return hasIpv4DefaultRoute();
+ }
+
+ /**
* Returns true if this link has an IPv6 default route.
*
* @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
* @hide
*/
- public boolean hasIPv6DefaultRoute() {
+ @TestApi
+ @SystemApi
+ public boolean hasIpv6DefaultRoute() {
for (RouteInfo r : mRoutes) {
if (r.isIPv6Default()) {
return true;
@@ -887,12 +1062,25 @@
}
/**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv6DefaultRoute() {
+ return hasIpv6DefaultRoute();
+ }
+
+ /**
* Returns true if this link has an IPv4 DNS server.
*
* @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
* @hide
*/
- public boolean hasIPv4DnsServer() {
+ @UnsupportedAppUsage
+ public boolean hasIpv4DnsServer() {
for (InetAddress ia : mDnses) {
if (ia instanceof Inet4Address) {
return true;
@@ -902,12 +1090,25 @@
}
/**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv4DnsServer() {
+ return hasIpv4DnsServer();
+ }
+
+ /**
* Returns true if this link has an IPv6 DNS server.
*
* @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
* @hide
*/
- public boolean hasIPv6DnsServer() {
+ @UnsupportedAppUsage
+ public boolean hasIpv6DnsServer() {
for (InetAddress ia : mDnses) {
if (ia instanceof Inet6Address) {
return true;
@@ -917,16 +1118,60 @@
}
/**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean hasIPv6DnsServer() {
+ return hasIpv6DnsServer();
+ }
+
+ /**
+ * Returns true if this link has an IPv4 PCSCF server.
+ *
+ * @return {@code true} if there is an IPv4 PCSCF server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIpv4PcscfServer() {
+ for (InetAddress ia : mPcscfs) {
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link has an IPv6 PCSCF server.
+ *
+ * @return {@code true} if there is an IPv6 PCSCF server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIpv6PcscfServer() {
+ for (InetAddress ia : mPcscfs) {
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns true if this link is provisioned for global IPv4 connectivity.
* This requires an IP address, default route, and DNS server.
*
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
- public boolean isIPv4Provisioned() {
- return (hasIPv4Address() &&
- hasIPv4DefaultRoute() &&
- hasIPv4DnsServer());
+ @TestApi
+ @SystemApi
+ public boolean isIpv4Provisioned() {
+ return (hasIpv4Address()
+ && hasIpv4DefaultRoute()
+ && hasIpv4DnsServer());
}
/**
@@ -936,21 +1181,38 @@
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
- public boolean isIPv6Provisioned() {
- return (hasGlobalIPv6Address() &&
- hasIPv6DefaultRoute() &&
- hasIPv6DnsServer());
+ @TestApi
+ @SystemApi
+ public boolean isIpv6Provisioned() {
+ return (hasGlobalIpv6Address()
+ && hasIpv6DefaultRoute()
+ && hasIpv6DnsServer());
}
/**
+ * For backward compatibility.
+ * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
+ * just yet.
+ * @return {@code true} if the link is provisioned, {@code false} otherwise.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public boolean isIPv6Provisioned() {
+ return isIpv6Provisioned();
+ }
+
+
+ /**
* Returns true if this link is provisioned for global connectivity,
* for at least one Internet Protocol family.
*
* @return {@code true} if the link is provisioned, {@code false} otherwise.
* @hide
*/
+ @TestApi
+ @SystemApi
public boolean isProvisioned() {
- return (isIPv4Provisioned() || isIPv6Provisioned());
+ return (isIpv4Provisioned() || isIpv6Provisioned());
}
/**
@@ -960,7 +1222,9 @@
* {@code false} otherwise.
* @hide
*/
- public boolean isReachable(InetAddress ip) {
+ @TestApi
+ @SystemApi
+ public boolean isReachable(@NonNull InetAddress ip) {
final List<RouteInfo> allRoutes = getAllRoutes();
// If we don't have a route to this IP address, it's not reachable.
final RouteInfo bestRoute = RouteInfo.selectBestRoute(allRoutes, ip);
@@ -972,7 +1236,7 @@
if (ip instanceof Inet4Address) {
// For IPv4, it suffices for now to simply have any address.
- return hasIPv4AddressOnInterface(bestRoute.getInterface());
+ return hasIpv4AddressOnInterface(bestRoute.getInterface());
} else if (ip instanceof Inet6Address) {
if (ip.isLinkLocalAddress()) {
// For now, just make sure link-local destinations have
@@ -983,7 +1247,7 @@
// For non-link-local destinations check that either the best route
// is directly connected or that some global preferred address exists.
// TODO: reconsider all cases (disconnected ULA networks, ...).
- return (!bestRoute.hasGateway() || hasGlobalIPv6Address());
+ return (!bestRoute.hasGateway() || hasGlobalIpv6Address());
}
}
@@ -997,7 +1261,8 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalInterfaceName(LinkProperties target) {
+ @UnsupportedAppUsage
+ public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) {
return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
}
@@ -1008,7 +1273,8 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalAddresses(LinkProperties target) {
+ @UnsupportedAppUsage
+ public boolean isIdenticalAddresses(@NonNull LinkProperties target) {
Collection<InetAddress> targetAddresses = target.getAddresses();
Collection<InetAddress> sourceAddresses = getAddresses();
return (sourceAddresses.size() == targetAddresses.size()) ?
@@ -1022,13 +1288,14 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalDnses(LinkProperties target) {
+ @UnsupportedAppUsage
+ public boolean isIdenticalDnses(@NonNull LinkProperties target) {
Collection<InetAddress> targetDnses = target.getDnsServers();
String targetDomains = target.getDomains();
if (mDomains == null) {
if (targetDomains != null) return false;
} else {
- if (mDomains.equals(targetDomains) == false) return false;
+ if (!mDomains.equals(targetDomains)) return false;
}
return (mDnses.size() == targetDnses.size()) ?
mDnses.containsAll(targetDnses) : false;
@@ -1042,7 +1309,7 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalPrivateDns(LinkProperties target) {
+ public boolean isIdenticalPrivateDns(@NonNull LinkProperties target) {
return (isPrivateDnsActive() == target.isPrivateDnsActive()
&& TextUtils.equals(getPrivateDnsServerName(),
target.getPrivateDnsServerName()));
@@ -1056,20 +1323,34 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalValidatedPrivateDnses(LinkProperties target) {
+ public boolean isIdenticalValidatedPrivateDnses(@NonNull LinkProperties target) {
Collection<InetAddress> targetDnses = target.getValidatedPrivateDnsServers();
return (mValidatedPrivateDnses.size() == targetDnses.size())
? mValidatedPrivateDnses.containsAll(targetDnses) : false;
}
/**
+ * Compares this {@code LinkProperties} PCSCF addresses against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalPcscfs(@NonNull LinkProperties target) {
+ Collection<InetAddress> targetPcscfs = target.getPcscfServers();
+ return (mPcscfs.size() == targetPcscfs.size()) ?
+ mPcscfs.containsAll(targetPcscfs) : false;
+ }
+
+ /**
* Compares this {@code LinkProperties} Routes against the target
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalRoutes(LinkProperties target) {
+ @UnsupportedAppUsage
+ public boolean isIdenticalRoutes(@NonNull LinkProperties target) {
Collection<RouteInfo> targetRoutes = target.getRoutes();
return (mRoutes.size() == targetRoutes.size()) ?
mRoutes.containsAll(targetRoutes) : false;
@@ -1082,7 +1363,8 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalHttpProxy(LinkProperties target) {
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) {
return getHttpProxy() == null ? target.getHttpProxy() == null :
getHttpProxy().equals(target.getHttpProxy());
}
@@ -1094,7 +1376,8 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalStackedLinks(LinkProperties target) {
+ @UnsupportedAppUsage
+ public boolean isIdenticalStackedLinks(@NonNull LinkProperties target) {
if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) {
return false;
}
@@ -1115,7 +1398,7 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalMtu(LinkProperties target) {
+ public boolean isIdenticalMtu(@NonNull LinkProperties target) {
return getMtu() == target.getMtu();
}
@@ -1126,11 +1409,21 @@
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
- public boolean isIdenticalTcpBufferSizes(LinkProperties target) {
+ public boolean isIdenticalTcpBufferSizes(@NonNull LinkProperties target) {
return Objects.equals(mTcpBufferSizes, target.mTcpBufferSizes);
}
- @Override
+ /**
+ * Compares this {@code LinkProperties} NAT64 prefix against the target.
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalNat64Prefix(@NonNull LinkProperties target) {
+ return Objects.equals(mNat64Prefix, target.mNat64Prefix);
+ }
+
/**
* Compares this {@code LinkProperties} instance against the target
* LinkProperties in {@code obj}. Two LinkPropertieses are equal if
@@ -1145,13 +1438,14 @@
* @param obj the object to be tested for equality.
* @return {@code true} if both objects are equal, {@code false} otherwise.
*/
+ @Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof LinkProperties)) return false;
LinkProperties target = (LinkProperties) obj;
- /**
+ /*
* This method does not check that stacked interfaces are equal, because
* stacked interfaces are not so much a property of the link as a
* description of connections between links.
@@ -1161,11 +1455,13 @@
&& isIdenticalDnses(target)
&& isIdenticalPrivateDns(target)
&& isIdenticalValidatedPrivateDnses(target)
+ && isIdenticalPcscfs(target)
&& isIdenticalRoutes(target)
&& isIdenticalHttpProxy(target)
&& isIdenticalStackedLinks(target)
&& isIdenticalMtu(target)
- && isIdenticalTcpBufferSizes(target);
+ && isIdenticalTcpBufferSizes(target)
+ && isIdenticalNat64Prefix(target);
}
/**
@@ -1176,7 +1472,7 @@
* @return the differences between the addresses.
* @hide
*/
- public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
+ public @NonNull CompareResult<LinkAddress> compareAddresses(@Nullable LinkProperties target) {
/*
* Duplicate the LinkAddresses into removed, we will be removing
* address which are common between mLinkAddresses and target
@@ -1196,7 +1492,7 @@
* @return the differences between the DNS addresses.
* @hide
*/
- public CompareResult<InetAddress> compareDnses(LinkProperties target) {
+ public @NonNull CompareResult<InetAddress> compareDnses(@Nullable LinkProperties target) {
/*
* Duplicate the InetAddresses into removed, we will be removing
* dns address which are common between mDnses and target
@@ -1215,7 +1511,8 @@
* @return the differences between the DNS addresses.
* @hide
*/
- public CompareResult<InetAddress> compareValidatedPrivateDnses(LinkProperties target) {
+ public @NonNull CompareResult<InetAddress> compareValidatedPrivateDnses(
+ @Nullable LinkProperties target) {
return new CompareResult<>(mValidatedPrivateDnses,
target != null ? target.getValidatedPrivateDnsServers() : null);
}
@@ -1228,7 +1525,7 @@
* @return the differences between the routes.
* @hide
*/
- public CompareResult<RouteInfo> compareAllRoutes(LinkProperties target) {
+ public @NonNull CompareResult<RouteInfo> compareAllRoutes(@Nullable LinkProperties target) {
/*
* Duplicate the RouteInfos into removed, we will be removing
* routes which are common between mRoutes and target
@@ -1246,7 +1543,8 @@
* @return the differences between the interface names.
* @hide
*/
- public CompareResult<String> compareAllInterfaceNames(LinkProperties target) {
+ public @NonNull CompareResult<String> compareAllInterfaceNames(
+ @Nullable LinkProperties target) {
/*
* Duplicate the interface names into removed, we will be removing
* interface names which are common between this and target
@@ -1258,12 +1556,13 @@
}
- @Override
/**
- * generate hashcode based on significant fields
+ * Generate hashcode based on significant fields
+ *
* Equal objects must produce the same hash code, while unequal objects
* may have the same hash codes.
*/
+ @Override
public int hashCode() {
return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
+ mLinkAddresses.size() * 31
@@ -1276,7 +1575,9 @@
+ mMtu * 51
+ ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode())
+ (mUsePrivateDns ? 57 : 0)
- + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode());
+ + mPcscfs.size() * 67
+ + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode())
+ + Objects.hash(mNat64Prefix);
}
/**
@@ -1299,6 +1600,10 @@
}
dest.writeBoolean(mUsePrivateDns);
dest.writeString(mPrivateDnsServerName);
+ dest.writeInt(mPcscfs.size());
+ for (InetAddress d : mPcscfs) {
+ dest.writeByteArray(d.getAddress());
+ }
dest.writeString(mDomains);
dest.writeInt(mMtu);
dest.writeString(mTcpBufferSizes);
@@ -1313,7 +1618,9 @@
} else {
dest.writeByte((byte)0);
}
- ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
+ dest.writeParcelable(mNat64Prefix, 0);
+
+ ArrayList<LinkProperties> stackedLinks = new ArrayList<>(mStackedLinks.values());
dest.writeList(stackedLinks);
}
@@ -1331,7 +1638,7 @@
}
int addressCount = in.readInt();
for (int i = 0; i < addressCount; i++) {
- netProp.addLinkAddress((LinkAddress) in.readParcelable(null));
+ netProp.addLinkAddress(in.readParcelable(null));
}
addressCount = in.readInt();
for (int i = 0; i < addressCount; i++) {
@@ -1348,16 +1655,23 @@
}
netProp.setUsePrivateDns(in.readBoolean());
netProp.setPrivateDnsServerName(in.readString());
+ addressCount = in.readInt();
+ for (int i = 0; i < addressCount; i++) {
+ try {
+ netProp.addPcscfServer(InetAddress.getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) { }
+ }
netProp.setDomains(in.readString());
netProp.setMtu(in.readInt());
netProp.setTcpBufferSizes(in.readString());
addressCount = in.readInt();
for (int i = 0; i < addressCount; i++) {
- netProp.addRoute((RouteInfo) in.readParcelable(null));
+ netProp.addRoute(in.readParcelable(null));
}
if (in.readByte() == 1) {
- netProp.setHttpProxy((ProxyInfo) in.readParcelable(null));
+ netProp.setHttpProxy(in.readParcelable(null));
}
+ netProp.setNat64Prefix(in.readParcelable(null));
ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
in.readList(stackedLinks, LinkProperties.class.getClassLoader());
for (LinkProperties stackedLink: stackedLinks) {
@@ -1377,10 +1691,9 @@
*/
public static boolean isValidMtu(int mtu, boolean ipv6) {
if (ipv6) {
- if (mtu >= MIN_MTU_V6 && mtu <= MAX_MTU) return true;
+ return mtu >= MIN_MTU_V6 && mtu <= MAX_MTU;
} else {
- if (mtu >= MIN_MTU && mtu <= MAX_MTU) return true;
+ return mtu >= MIN_MTU && mtu <= MAX_MTU;
}
- return false;
}
}
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 74d6470..c2b7d2c 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -18,6 +18,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,6 +28,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
@@ -48,8 +52,11 @@
/**
* The MacAddress zero MAC address.
+ *
+ * <p>Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
* @hide
*/
+ @UnsupportedAppUsage
public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);
/** @hide */
@@ -391,4 +398,34 @@
}
return out;
}
+
+ /**
+ * Create a link-local Inet6Address from the MAC address. The EUI-48 MAC address is converted
+ * to an EUI-64 MAC address per RFC 4291. The resulting EUI-64 is used to construct a link-local
+ * IPv6 address per RFC 4862.
+ *
+ * @return A link-local Inet6Address constructed from the MAC address.
+ * @hide
+ */
+ public @Nullable Inet6Address getLinkLocalIpv6FromEui48Mac() {
+ byte[] macEui48Bytes = toByteArray();
+ byte[] addr = new byte[16];
+
+ addr[0] = (byte) 0xfe;
+ addr[1] = (byte) 0x80;
+ addr[8] = (byte) (macEui48Bytes[0] ^ (byte) 0x02); // flip the link-local bit
+ addr[9] = macEui48Bytes[1];
+ addr[10] = macEui48Bytes[2];
+ addr[11] = (byte) 0xff;
+ addr[12] = (byte) 0xfe;
+ addr[13] = macEui48Bytes[3];
+ addr[14] = macEui48Bytes[4];
+ addr[15] = macEui48Bytes[5];
+
+ try {
+ return Inet6Address.getByAddress(null, addr, 0);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+ }
}
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
new file mode 100644
index 0000000..a77c244
--- /dev/null
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+
+import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.util.IpUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.system.OsConstants;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/** @hide */
+public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
+ // This should only be constructed via static factory methods, such as
+ // nattKeepalivePacket
+ private NattKeepalivePacketData(InetAddress srcAddress, int srcPort,
+ InetAddress dstAddress, int dstPort, byte[] data) throws
+ InvalidPacketException {
+ super(srcAddress, srcPort, dstAddress, dstPort, data);
+ }
+
+ /**
+ * Factory method to create Nat-T keepalive packet structure.
+ */
+ public static NattKeepalivePacketData nattKeepalivePacket(
+ InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
+ throws InvalidPacketException {
+
+ if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ if (dstPort != NattSocketKeepalive.NATT_PORT) {
+ throw new InvalidPacketException(ERROR_INVALID_PORT);
+ }
+
+ int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
+ ByteBuffer buf = ByteBuffer.allocate(length);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ buf.putShort((short) 0x4500); // IP version and TOS
+ buf.putShort((short) length);
+ buf.putInt(0); // ID, flags, offset
+ buf.put((byte) 64); // TTL
+ buf.put((byte) OsConstants.IPPROTO_UDP);
+ int ipChecksumOffset = buf.position();
+ buf.putShort((short) 0); // IP checksum
+ buf.put(srcAddress.getAddress());
+ buf.put(dstAddress.getAddress());
+ buf.putShort((short) srcPort);
+ buf.putShort((short) dstPort);
+ buf.putShort((short) (length - 20)); // UDP length
+ int udpChecksumOffset = buf.position();
+ buf.putShort((short) 0); // UDP checksum
+ buf.put((byte) 0xff); // NAT-T keepalive
+ buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
+ buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH));
+
+ return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
+ }
+
+ /** Parcelable Implementation */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(srcAddress.getHostAddress());
+ out.writeString(dstAddress.getHostAddress());
+ out.writeInt(srcPort);
+ out.writeInt(dstPort);
+ }
+
+ /** Parcelable Creator */
+ public static final Parcelable.Creator<NattKeepalivePacketData> CREATOR =
+ new Parcelable.Creator<NattKeepalivePacketData>() {
+ public NattKeepalivePacketData createFromParcel(Parcel in) {
+ final InetAddress srcAddress =
+ InetAddresses.parseNumericAddress(in.readString());
+ final InetAddress dstAddress =
+ InetAddresses.parseNumericAddress(in.readString());
+ final int srcPort = in.readInt();
+ final int dstPort = in.readInt();
+ try {
+ return NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort,
+ dstAddress, dstPort);
+ } catch (InvalidPacketException e) {
+ throw new IllegalArgumentException(
+ "Invalid NAT-T keepalive data: " + e.error);
+ }
+ }
+
+ public NattKeepalivePacketData[] newArray(int size) {
+ return new NattKeepalivePacketData[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java
new file mode 100644
index 0000000..b0ce0c7
--- /dev/null
+++ b/core/java/android/net/NattSocketKeepalive.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.util.concurrent.Executor;
+
+/** @hide */
+public final class NattSocketKeepalive extends SocketKeepalive {
+ /** The NAT-T destination port for IPsec */
+ public static final int NATT_PORT = 4500;
+
+ @NonNull private final InetAddress mSource;
+ @NonNull private final InetAddress mDestination;
+ private final int mResourceId;
+
+ NattSocketKeepalive(@NonNull IConnectivityManager service,
+ @NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
+ int resourceId,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ super(service, network, pfd, executor, callback);
+ mSource = source;
+ mDestination = destination;
+ mResourceId = resourceId;
+ }
+
+ @Override
+ void startImpl(int intervalSec) {
+ mExecutor.execute(() -> {
+ try {
+ mService.startNattKeepaliveWithFd(mNetwork, mPfd.getFileDescriptor(), mResourceId,
+ intervalSec, mCallback,
+ mSource.getHostAddress(), mDestination.getHostAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting socket keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ @Override
+ void stopImpl() {
+ mExecutor.execute(() -> {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping socket keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+}
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index d75d439..09a86fc 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,6 +16,10 @@
package android.net;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.system.ErrnoException;
@@ -59,6 +63,7 @@
/**
* @hide
*/
+ @UnsupportedAppUsage
public final int netId;
// Objects used to perform per-network operations such as getSocketFactory
@@ -98,20 +103,31 @@
// anytime and (b) receivers should be explicit about attempts to bypass
// Private DNS so that the intent of the code is easily determined and
// code search audits are possible.
- private boolean mPrivateDnsBypass = false;
+ private final transient boolean mPrivateDnsBypass;
/**
* @hide
*/
+ @UnsupportedAppUsage
public Network(int netId) {
- this.netId = netId;
+ this(netId, false);
}
/**
* @hide
*/
- public Network(Network that) {
- this.netId = that.netId;
+ public Network(int netId, boolean privateDnsBypass) {
+ this.netId = netId;
+ this.mPrivateDnsBypass = privateDnsBypass;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public Network(@NonNull Network that) {
+ this(that.netId, that.mPrivateDnsBypass);
}
/**
@@ -130,8 +146,7 @@
* Operates the same as {@code InetAddress.getByName} except that host
* resolution is done on this network.
*
- * @param host
- * the hostName to be resolved to an address or {@code null}.
+ * @param host the hostname to be resolved to an address or {@code null}.
* @return the {@code InetAddress} instance representing the host.
* @throws UnknownHostException
* if the address lookup fails.
@@ -141,14 +156,16 @@
}
/**
- * Specify whether or not Private DNS should be bypassed when attempting
- * to use {@link getAllByName()}/{@link getByName()} methods on the given
+ * Obtain a Network object for which Private DNS is to be bypassed when attempting
+ * to use {@link #getAllByName(String)}/{@link #getByName(String)} methods on the given
* instance for hostname resolution.
*
* @hide
*/
- public void setPrivateDnsBypass(boolean bypass) {
- mPrivateDnsBypass = bypass;
+ @TestApi
+ @SystemApi
+ public @NonNull Network getPrivateDnsBypassingCopy() {
+ return new Network(netId, true);
}
/**
@@ -169,13 +186,6 @@
* A {@code SocketFactory} that produces {@code Socket}'s bound to this network.
*/
private class NetworkBoundSocketFactory extends SocketFactory {
- private final int mNetId;
-
- public NetworkBoundSocketFactory(int netId) {
- super();
- mNetId = netId;
- }
-
private Socket connectToHost(String host, int port, SocketAddress localAddress)
throws IOException {
// Lookup addresses only on this Network.
@@ -201,7 +211,8 @@
}
@Override
- public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
+ throws IOException {
return connectToHost(host, port, new InetSocketAddress(localHost, localPort));
}
@@ -265,7 +276,7 @@
if (mNetworkBoundSocketFactory == null) {
synchronized (mLock) {
if (mNetworkBoundSocketFactory == null) {
- mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId);
+ mNetworkBoundSocketFactory = new NetworkBoundSocketFactory();
}
}
}
@@ -475,7 +486,7 @@
@Override
public boolean equals(Object obj) {
- if (obj instanceof Network == false) return false;
+ if (!(obj instanceof Network)) return false;
Network other = (Network)obj;
return this.netId == other.netId;
}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 52a2354..b3f829a 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -16,8 +16,9 @@
package android.net;
+import android.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.net.ConnectivityManager.PacketKeepalive;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -56,6 +57,7 @@
private static final long BW_REFRESH_MIN_WIN_MS = 500;
private boolean mPollLceScheduled = false;
private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
+ public final int mFactorySerialNumber;
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
@@ -152,7 +154,7 @@
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
*/
- public static final int CMD_START_PACKET_KEEPALIVE = BASE + 11;
+ public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
/**
* Requests that the specified keepalive packet be stopped.
@@ -161,20 +163,20 @@
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
*/
- public static final int CMD_STOP_PACKET_KEEPALIVE = BASE + 12;
+ public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
/**
- * Sent by the NetworkAgent to ConnectivityService to provide status on a packet keepalive
- * request. This may either be the reply to a CMD_START_PACKET_KEEPALIVE, or an asynchronous
+ * Sent by the NetworkAgent to ConnectivityService to provide status on a socket keepalive
+ * request. This may either be the reply to a CMD_START_SOCKET_KEEPALIVE, or an asynchronous
* error notification.
*
- * This is also sent by KeepaliveTracker to the app's ConnectivityManager.PacketKeepalive to
- * so that the app's PacketKeepaliveCallback methods can be called.
+ * This is also sent by KeepaliveTracker to the app's {@link SocketKeepalive},
+ * so that the app's {@link SocketKeepalive.Callback} methods can be called.
*
* arg1 = slot number of the keepalive
* arg2 = error code
*/
- public static final int EVENT_PACKET_KEEPALIVE = BASE + 13;
+ public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
/**
* Sent by ConnectivityService to inform this network transport of signal strength thresholds
@@ -191,16 +193,50 @@
*/
public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
+ /**
+ * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter.
+ *
+ * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the
+ * remote site will send ACK packets in response to the keepalive packets, the firmware also
+ * needs to be configured to properly filter the ACKs to prevent the system from waking up.
+ * This does not happen with UDP, so this message is TCP-specific.
+ * arg1 = slot number of the keepalive to filter for.
+ * obj = the keepalive packet to send repeatedly.
+ */
+ public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
+
+ /**
+ * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
+ * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
+ * arg1 = slot number of the keepalive packet filter to remove.
+ */
+ public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
+
+ // TODO : remove these two constructors. They are a stopgap measure to help sheperding a number
+ // of dependent changes that would conflict throughout the automerger graph. Having these
+ // temporarily helps with the process of going through with all these dependent changes across
+ // the entire tree.
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
- this(looper, context, logTag, ni, nc, lp, score, null);
+ this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE);
+ }
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
+ this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE);
}
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
+ NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) {
+ this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber);
+ }
+
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc,
+ int factorySerialNumber) {
super(looper);
LOG_TAG = logTag;
mContext = context;
+ mFactorySerialNumber = factorySerialNumber;
if (ni == null || nc == null || lp == null) {
throw new IllegalArgumentException();
}
@@ -209,7 +245,8 @@
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
- new LinkProperties(lp), new NetworkCapabilities(nc), score, misc);
+ new LinkProperties(lp), new NetworkCapabilities(nc), score, misc,
+ factorySerialNumber);
}
@Override
@@ -286,12 +323,12 @@
saveAcceptUnvalidated(msg.arg1 != 0);
break;
}
- case CMD_START_PACKET_KEEPALIVE: {
- startPacketKeepalive(msg);
+ case CMD_START_SOCKET_KEEPALIVE: {
+ startSocketKeepalive(msg);
break;
}
- case CMD_STOP_PACKET_KEEPALIVE: {
- stopPacketKeepalive(msg);
+ case CMD_STOP_SOCKET_KEEPALIVE: {
+ stopSocketKeepalive(msg);
break;
}
@@ -311,6 +348,14 @@
preventAutomaticReconnect();
break;
}
+ case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
+ addKeepalivePacketFilter(msg);
+ break;
+ }
+ case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
+ removeKeepalivePacketFilter(msg);
+ break;
+ }
}
}
@@ -351,6 +396,7 @@
/**
* Called by the bearer code when it has new NetworkInfo data.
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void sendNetworkInfo(NetworkInfo networkInfo) {
queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
}
@@ -372,7 +418,7 @@
if (score < 0) {
throw new IllegalArgumentException("Score must be >= 0");
}
- queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score));
+ queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, score, 0);
}
/**
@@ -387,7 +433,32 @@
* {@link #saveAcceptUnvalidated} to respect the user's choice.
*/
public void explicitlySelected(boolean acceptUnvalidated) {
- queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, acceptUnvalidated);
+ explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
+ }
+
+ /**
+ * Called by the bearer to indicate whether the network was manually selected by the user.
+ * This should be called before the NetworkInfo is marked CONNECTED so that this
+ * Network can be given special treatment at that time.
+ *
+ * If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true},
+ * then the system will switch to this network. If {@code explicitlySelected} is {@code true}
+ * and {@code acceptUnvalidated} is {@code false}, and the network cannot be validated, the
+ * system will ask the user whether to switch to this network. If the user confirms and selects
+ * "don't ask again", then the system will call {@link #saveAcceptUnvalidated} to persist the
+ * user's choice. Thus, if the transport ever calls this method with {@code explicitlySelected}
+ * set to {@code true} and {@code acceptUnvalidated} set to {@code false}, it must also
+ * implement {@link #saveAcceptUnvalidated} to respect the user's choice.
+ *
+ * If {@code explicitlySelected} is {@code false} and {@code acceptUnvalidated} is
+ * {@code true}, the system will interpret this as the user having accepted partial connectivity
+ * on this network. Thus, the system will switch to the network and consider it validated even
+ * if it only provides partial connectivity, but the network is not otherwise treated specially.
+ */
+ public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
+ queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED,
+ explicitlySelected ? 1 : 0,
+ acceptUnvalidated ? 1 : 0);
}
/**
@@ -440,22 +511,38 @@
/**
* Requests that the network hardware send the specified packet at the specified interval.
*/
- protected void startPacketKeepalive(Message msg) {
- onPacketKeepaliveEvent(msg.arg1, PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ protected void startSocketKeepalive(Message msg) {
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
- * Requests that the network hardware send the specified packet at the specified interval.
+ * Requests that the network hardware stops sending keepalive packets.
*/
- protected void stopPacketKeepalive(Message msg) {
- onPacketKeepaliveEvent(msg.arg1, PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ protected void stopSocketKeepalive(Message msg) {
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
- * Called by the network when a packet keepalive event occurs.
+ * Called by the network when a socket keepalive event occurs.
*/
- public void onPacketKeepaliveEvent(int slot, int reason) {
- queueOrSendMessage(EVENT_PACKET_KEEPALIVE, slot, reason);
+ public void onSocketKeepaliveEvent(int slot, int reason) {
+ queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason);
+ }
+
+ /**
+ * Called by ConnectivityService to add specific packet filter to network hardware to block
+ * ACKs matching the sent keepalive packets. Implementations that support this feature must
+ * override this method.
+ */
+ protected void addKeepalivePacketFilter(Message msg) {
+ }
+
+ /**
+ * Called by ConnectivityService to remove a packet filter installed with
+ * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
+ * must override this method.
+ */
+ protected void removeKeepalivePacketFilter(Message msg) {
}
/**
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index e3a1107..dfd7089 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -17,9 +17,13 @@
package android.net;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
import android.net.ConnectivityManager.NetworkCallback;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
@@ -56,6 +60,7 @@
/**
* @hide
*/
+ @UnsupportedAppUsage
public NetworkCapabilities() {
clearAll();
mNetworkCapabilities = DEFAULT_CAPABILITIES;
@@ -76,6 +81,7 @@
mNetworkCapabilities = mTransportTypes = mUnwantedNetworkCapabilities = 0;
mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
mNetworkSpecifier = null;
+ mTransportInfo = null;
mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
mUids = null;
mEstablishingVpnAppUid = INVALID_UID;
@@ -86,12 +92,13 @@
* Set all contents of this object to the contents of a NetworkCapabilities.
* @hide
*/
- public void set(NetworkCapabilities nc) {
+ public void set(@NonNull NetworkCapabilities nc) {
mNetworkCapabilities = nc.mNetworkCapabilities;
mTransportTypes = nc.mTransportTypes;
mLinkUpBandwidthKbps = nc.mLinkUpBandwidthKbps;
mLinkDownBandwidthKbps = nc.mLinkDownBandwidthKbps;
mNetworkSpecifier = nc.mNetworkSpecifier;
+ mTransportInfo = nc.mTransportInfo;
mSignalStrength = nc.mSignalStrength;
setUids(nc.mUids); // Will make the defensive copy
mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
@@ -103,6 +110,7 @@
* Represents the network's capabilities. If any are specified they will be satisfied
* by any Network that matches all of them.
*/
+ @UnsupportedAppUsage
private long mNetworkCapabilities;
/**
@@ -136,6 +144,8 @@
NET_CAPABILITY_NOT_CONGESTED,
NET_CAPABILITY_NOT_SUSPENDED,
NET_CAPABILITY_OEM_PAID,
+ NET_CAPABILITY_MCX,
+ NET_CAPABILITY_PARTIAL_CONNECTIVITY,
})
public @interface NetCapability { }
@@ -290,8 +300,21 @@
@SystemApi
public static final int NET_CAPABILITY_OEM_PAID = 22;
+ /**
+ * Indicates this is a network that has the ability to reach a carrier's Mission Critical
+ * servers.
+ */
+ public static final int NET_CAPABILITY_MCX = 23;
+
+ /**
+ * Indicates that this network was tested to only provide partial connectivity.
+ * @hide
+ */
+ @SystemApi
+ public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_OEM_PAID;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -306,7 +329,8 @@
| (1 << NET_CAPABILITY_NOT_ROAMING)
| (1 << NET_CAPABILITY_FOREGROUND)
| (1 << NET_CAPABILITY_NOT_CONGESTED)
- | (1 << NET_CAPABILITY_NOT_SUSPENDED);
+ | (1 << NET_CAPABILITY_NOT_SUSPENDED)
+ | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
/**
* Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -339,7 +363,8 @@
(1 << NET_CAPABILITY_IA) |
(1 << NET_CAPABILITY_IMS) |
(1 << NET_CAPABILITY_RCS) |
- (1 << NET_CAPABILITY_XCAP);
+ (1 << NET_CAPABILITY_XCAP) |
+ (1 << NET_CAPABILITY_MCX);
/**
* Capabilities that force network to be restricted.
@@ -360,6 +385,15 @@
(1 << NET_CAPABILITY_WIFI_P2P);
/**
+ * Capabilities that are managed by ConnectivityService.
+ */
+ private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
+ (1 << NET_CAPABILITY_VALIDATED)
+ | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
+ | (1 << NET_CAPABILITY_FOREGROUND)
+ | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+
+ /**
* Adds the given capability to this {@code NetworkCapability} instance.
* Multiple capabilities may be applied sequentially. Note that when searching
* for a network to satisfy a request, all capabilities requested must be satisfied.
@@ -371,7 +405,8 @@
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities addCapability(@NetCapability int capability) {
+ @UnsupportedAppUsage
+ public @NonNull NetworkCapabilities addCapability(@NetCapability int capability) {
checkValidCapability(capability);
mNetworkCapabilities |= 1 << capability;
mUnwantedNetworkCapabilities &= ~(1 << capability); // remove from unwanted capability list
@@ -400,14 +435,15 @@
/**
* Removes (if found) the given capability from this {@code NetworkCapability} instance.
* <p>
- * Note that this method removes capabilities that was added via {@link #addCapability(int)},
+ * Note that this method removes capabilities that were added via {@link #addCapability(int)},
* {@link #addUnwantedCapability(int)} or {@link #setCapabilities(int[], int[])} .
*
* @param capability the capability to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities removeCapability(@NetCapability int capability) {
+ @UnsupportedAppUsage
+ public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
checkValidCapability(capability);
final long mask = ~(1 << capability);
mNetworkCapabilities &= mask;
@@ -421,7 +457,8 @@
*
* @hide
*/
- public NetworkCapabilities setCapability(@NetCapability int capability, boolean value) {
+ public @NonNull NetworkCapabilities setCapability(@NetCapability int capability,
+ boolean value) {
if (value) {
addCapability(capability);
} else {
@@ -490,7 +527,16 @@
&& ((mUnwantedNetworkCapabilities & (1 << capability)) != 0);
}
- private void combineNetCapabilities(NetworkCapabilities nc) {
+ /**
+ * Check if this NetworkCapabilities has system managed capabilities or not.
+ * @hide
+ */
+ public boolean hasConnectivityManagedCapability() {
+ return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
+ }
+
+ /** Note this method may result in having the same capability in wanted and unwanted lists. */
+ private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
this.mNetworkCapabilities |= nc.mNetworkCapabilities;
this.mUnwantedNetworkCapabilities |= nc.mUnwantedNetworkCapabilities;
}
@@ -502,7 +548,7 @@
*
* @hide
*/
- public String describeFirstNonRequestableCapability() {
+ public @Nullable String describeFirstNonRequestableCapability() {
final long nonRequestable = (mNetworkCapabilities | mUnwantedNetworkCapabilities)
& NON_REQUESTABLE_CAPABILITIES;
@@ -514,7 +560,8 @@
return null;
}
- private boolean satisfiedByNetCapabilities(NetworkCapabilities nc, boolean onlyImmutable) {
+ private boolean satisfiedByNetCapabilities(@NonNull NetworkCapabilities nc,
+ boolean onlyImmutable) {
long requestedCapabilities = mNetworkCapabilities;
long requestedUnwantedCapabilities = mUnwantedNetworkCapabilities;
long providedCapabilities = nc.mNetworkCapabilities;
@@ -528,12 +575,12 @@
}
/** @hide */
- public boolean equalsNetCapabilities(NetworkCapabilities nc) {
+ public boolean equalsNetCapabilities(@NonNull NetworkCapabilities nc) {
return (nc.mNetworkCapabilities == this.mNetworkCapabilities)
&& (nc.mUnwantedNetworkCapabilities == this.mUnwantedNetworkCapabilities);
}
- private boolean equalsNetCapabilitiesRequestable(NetworkCapabilities that) {
+ private boolean equalsNetCapabilitiesRequestable(@NonNull NetworkCapabilities that) {
return ((this.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
(that.mNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES))
&& ((this.mUnwantedNetworkCapabilities & ~NON_REQUESTABLE_CAPABILITIES) ==
@@ -587,6 +634,7 @@
TRANSPORT_VPN,
TRANSPORT_WIFI_AWARE,
TRANSPORT_LOWPAN,
+ TRANSPORT_TEST,
})
public @interface Transport { }
@@ -625,10 +673,18 @@
*/
public static final int TRANSPORT_LOWPAN = 6;
+ /**
+ * Indicates this network uses a Test-only virtual interface as a transport.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int TRANSPORT_TEST = 7;
+
/** @hide */
public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
/** @hide */
- public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
+ public static final int MAX_TRANSPORT = TRANSPORT_TEST;
/** @hide */
public static boolean isValidTransport(@Transport int transportType) {
@@ -642,7 +698,8 @@
"ETHERNET",
"VPN",
"WIFI_AWARE",
- "LOWPAN"
+ "LOWPAN",
+ "TEST"
};
/**
@@ -658,7 +715,8 @@
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities addTransportType(@Transport int transportType) {
+ @UnsupportedAppUsage
+ public @NonNull NetworkCapabilities addTransportType(@Transport int transportType) {
checkValidTransportType(transportType);
mTransportTypes |= 1 << transportType;
setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -672,7 +730,7 @@
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities removeTransportType(@Transport int transportType) {
+ public @NonNull NetworkCapabilities removeTransportType(@Transport int transportType) {
checkValidTransportType(transportType);
mTransportTypes &= ~(1 << transportType);
setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -685,7 +743,8 @@
*
* @hide
*/
- public NetworkCapabilities setTransportType(@Transport int transportType, boolean value) {
+ public @NonNull NetworkCapabilities setTransportType(@Transport int transportType,
+ boolean value) {
if (value) {
addTransportType(transportType);
} else {
@@ -701,7 +760,8 @@
* @hide
*/
@TestApi
- public @Transport int[] getTransportTypes() {
+ @SystemApi
+ @NonNull public @Transport int[] getTransportTypes() {
return BitUtils.unpackBits(mTransportTypes);
}
@@ -762,6 +822,11 @@
mEstablishingVpnAppUid = uid;
}
+ /** @hide */
+ public int getEstablishingVpnAppUid() {
+ return mEstablishingVpnAppUid;
+ }
+
/**
* Value indicating that link bandwidth is unspecified.
* @hide
@@ -791,7 +856,7 @@
* @param upKbps the estimated first hop upstream (device to network) bandwidth.
* @hide
*/
- public NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) {
+ public @NonNull NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) {
mLinkUpBandwidthKbps = upKbps;
return this;
}
@@ -821,7 +886,7 @@
* @param downKbps the estimated first hop downstream (network to device) bandwidth.
* @hide
*/
- public NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) {
+ public @NonNull NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) {
mLinkDownBandwidthKbps = downKbps;
return this;
}
@@ -866,6 +931,7 @@
}
private NetworkSpecifier mNetworkSpecifier = null;
+ private TransportInfo mTransportInfo = null;
/**
* Sets the optional bearer specific network specifier.
@@ -879,7 +945,7 @@
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
+ public @NonNull NetworkCapabilities setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
if (networkSpecifier != null && Long.bitCount(mTransportTypes) != 1) {
throw new IllegalStateException("Must have a single transport specified to use " +
"setNetworkSpecifier");
@@ -891,16 +957,43 @@
}
/**
- * Gets the optional bearer specific network specifier.
+ * Sets the optional transport specific information.
*
- * @return The optional {@link NetworkSpecifier} specifying the bearer specific network
- * specifier. See {@link #setNetworkSpecifier}.
+ * @param transportInfo A concrete, parcelable framework class that extends
+ * {@link TransportInfo}.
+ * @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkSpecifier getNetworkSpecifier() {
+ public @NonNull NetworkCapabilities setTransportInfo(TransportInfo transportInfo) {
+ mTransportInfo = transportInfo;
+ return this;
+ }
+
+ /**
+ * Gets the optional bearer specific network specifier. May be {@code null} if not set.
+ *
+ * @return The optional {@link NetworkSpecifier} specifying the bearer specific network
+ * specifier or {@code null}. See {@link #setNetworkSpecifier}.
+ * @hide
+ */
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ public @Nullable NetworkSpecifier getNetworkSpecifier() {
return mNetworkSpecifier;
}
+ /**
+ * Returns a transport-specific information container. The application may cast this
+ * container to a concrete sub-class based on its knowledge of the network request. The
+ * application should be able to deal with a {@code null} return value or an invalid case,
+ * e.g. use {@code instanceof} operator to verify expected type.
+ *
+ * @return A concrete implementation of the {@link TransportInfo} class or null if not
+ * available for the network.
+ */
+ @Nullable public TransportInfo getTransportInfo() {
+ return mTransportInfo;
+ }
+
private void combineSpecifiers(NetworkCapabilities nc) {
if (mNetworkSpecifier != null && !mNetworkSpecifier.equals(nc.mNetworkSpecifier)) {
throw new IllegalStateException("Can't combine two networkSpecifiers");
@@ -917,11 +1010,20 @@
return Objects.equals(mNetworkSpecifier, nc.mNetworkSpecifier);
}
+ private void combineTransportInfos(NetworkCapabilities nc) {
+ if (mTransportInfo != null && !mTransportInfo.equals(nc.mTransportInfo)) {
+ throw new IllegalStateException("Can't combine two TransportInfos");
+ }
+ setTransportInfo(nc.mTransportInfo);
+ }
+
+ private boolean equalsTransportInfo(NetworkCapabilities nc) {
+ return Objects.equals(mTransportInfo, nc.mTransportInfo);
+ }
+
/**
* Magic value that indicates no signal strength provided. A request specifying this value is
* always satisfied.
- *
- * @hide
*/
public static final int SIGNAL_STRENGTH_UNSPECIFIED = Integer.MIN_VALUE;
@@ -929,6 +1031,7 @@
* Signal strength. This is a signed integer, and higher values indicate better signal.
* The exact units are bearer-dependent. For example, Wi-Fi uses RSSI.
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private int mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
/**
@@ -944,7 +1047,8 @@
* @param signalStrength the bearer-specific signal strength.
* @hide
*/
- public NetworkCapabilities setSignalStrength(int signalStrength) {
+ @UnsupportedAppUsage
+ public @NonNull NetworkCapabilities setSignalStrength(int signalStrength) {
mSignalStrength = signalStrength;
return this;
}
@@ -954,6 +1058,7 @@
*
* @hide
*/
+ @UnsupportedAppUsage
public boolean hasSignalStrength() {
return mSignalStrength > SIGNAL_STRENGTH_UNSPECIFIED;
}
@@ -962,7 +1067,6 @@
* Retrieves the signal strength.
*
* @return The bearer-specific signal strength.
- * @hide
*/
public int getSignalStrength() {
return mSignalStrength;
@@ -1021,7 +1125,7 @@
* Convenience method to set the UIDs this network applies to to a single UID.
* @hide
*/
- public NetworkCapabilities setSingleUid(int uid) {
+ public @NonNull NetworkCapabilities setSingleUid(int uid) {
final ArraySet<UidRange> identity = new ArraySet<>(1);
identity.add(new UidRange(uid, uid));
setUids(identity);
@@ -1033,7 +1137,7 @@
* This makes a copy of the set so that callers can't modify it after the call.
* @hide
*/
- public NetworkCapabilities setUids(Set<UidRange> uids) {
+ public @NonNull NetworkCapabilities setUids(Set<UidRange> uids) {
if (null == uids) {
mUids = null;
} else {
@@ -1047,7 +1151,7 @@
* This returns a copy of the set so that callers can't modify the original object.
* @hide
*/
- public Set<UidRange> getUids() {
+ public @Nullable Set<UidRange> getUids() {
return null == mUids ? null : new ArraySet<>(mUids);
}
@@ -1080,7 +1184,7 @@
* @hide
*/
@VisibleForTesting
- public boolean equalsUids(NetworkCapabilities nc) {
+ public boolean equalsUids(@NonNull NetworkCapabilities nc) {
Set<UidRange> comparedUids = nc.mUids;
if (null == comparedUids) return null == mUids;
if (null == mUids) return false;
@@ -1113,7 +1217,7 @@
* @see #appliesToUid
* @hide
*/
- public boolean satisfiedByUids(NetworkCapabilities nc) {
+ public boolean satisfiedByUids(@NonNull NetworkCapabilities nc) {
if (null == nc.mUids || null == mUids) return true; // The network satisfies everything.
for (UidRange requiredRange : mUids) {
if (requiredRange.contains(nc.mEstablishingVpnAppUid)) return true;
@@ -1133,7 +1237,7 @@
* @hide
*/
@VisibleForTesting
- public boolean appliesToUidRange(UidRange requiredRange) {
+ public boolean appliesToUidRange(@Nullable UidRange requiredRange) {
if (null == mUids) return true;
for (UidRange uidRange : mUids) {
if (uidRange.containsRange(requiredRange)) {
@@ -1148,7 +1252,7 @@
* NetworkCapabilities apply to.
* nc is assumed nonnull.
*/
- private void combineUids(NetworkCapabilities nc) {
+ private void combineUids(@NonNull NetworkCapabilities nc) {
if (null == nc.mUids || null == mUids) {
mUids = null;
return;
@@ -1169,7 +1273,7 @@
* Sets the SSID of this network.
* @hide
*/
- public NetworkCapabilities setSSID(String ssid) {
+ public @NonNull NetworkCapabilities setSSID(@Nullable String ssid) {
mSSID = ssid;
return this;
}
@@ -1178,7 +1282,7 @@
* Gets the SSID of this network, or null if none or unknown.
* @hide
*/
- public String getSSID() {
+ public @Nullable String getSSID() {
return mSSID;
}
@@ -1186,7 +1290,7 @@
* Tests if the SSID of this network is the same as the SSID of the passed network.
* @hide
*/
- public boolean equalsSSID(NetworkCapabilities nc) {
+ public boolean equalsSSID(@NonNull NetworkCapabilities nc) {
return Objects.equals(mSSID, nc.mSSID);
}
@@ -1194,7 +1298,7 @@
* Check if the SSID requirements of this object are matched by the passed object.
* @hide
*/
- public boolean satisfiedBySSID(NetworkCapabilities nc) {
+ public boolean satisfiedBySSID(@NonNull NetworkCapabilities nc) {
return mSSID == null || mSSID.equals(nc.mSSID);
}
@@ -1205,7 +1309,7 @@
* equal.
* @hide
*/
- private void combineSSIDs(NetworkCapabilities nc) {
+ private void combineSSIDs(@NonNull NetworkCapabilities nc) {
if (mSSID != null && !mSSID.equals(nc.mSSID)) {
throw new IllegalStateException("Can't combine two SSIDs");
}
@@ -1213,14 +1317,19 @@
}
/**
- * Combine a set of Capabilities to this one. Useful for coming up with the complete set
+ * Combine a set of Capabilities to this one. Useful for coming up with the complete set.
+ * <p>
+ * Note that this method may break an invariant of having a particular capability in either
+ * wanted or unwanted lists but never in both. Requests that have the same capability in
+ * both lists will never be satisfied.
* @hide
*/
- public void combineCapabilities(NetworkCapabilities nc) {
+ public void combineCapabilities(@NonNull NetworkCapabilities nc) {
combineNetCapabilities(nc);
combineTransportTypes(nc);
combineLinkBandwidths(nc);
combineSpecifiers(nc);
+ combineTransportInfos(nc);
combineSignalStrength(nc);
combineUids(nc);
combineSSIDs(nc);
@@ -1253,7 +1362,9 @@
*
* @hide
*/
- public boolean satisfiedByNetworkCapabilities(NetworkCapabilities nc) {
+ @TestApi
+ @SystemApi
+ public boolean satisfiedByNetworkCapabilities(@Nullable NetworkCapabilities nc) {
return satisfiedByNetworkCapabilities(nc, false);
}
@@ -1264,7 +1375,7 @@
*
* @hide
*/
- public boolean satisfiedByImmutableNetworkCapabilities(NetworkCapabilities nc) {
+ public boolean satisfiedByImmutableNetworkCapabilities(@Nullable NetworkCapabilities nc) {
return satisfiedByNetworkCapabilities(nc, true);
}
@@ -1275,7 +1386,7 @@
*
* @hide
*/
- public String describeImmutableDifferences(NetworkCapabilities that) {
+ public String describeImmutableDifferences(@Nullable NetworkCapabilities that) {
if (that == null) {
return "other NetworkCapabilities was null";
}
@@ -1314,7 +1425,7 @@
*
* @hide
*/
- public boolean equalRequestableCapabilities(NetworkCapabilities nc) {
+ public boolean equalRequestableCapabilities(@Nullable NetworkCapabilities nc) {
if (nc == null) return false;
return (equalsNetCapabilitiesRequestable(nc) &&
equalsTransportTypes(nc) &&
@@ -1322,7 +1433,7 @@
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(@Nullable Object obj) {
if (obj == null || (obj instanceof NetworkCapabilities == false)) return false;
NetworkCapabilities that = (NetworkCapabilities) obj;
return (equalsNetCapabilities(that)
@@ -1330,6 +1441,7 @@
&& equalsLinkBandwidths(that)
&& equalsSignalStrength(that)
&& equalsSpecifier(that)
+ && equalsTransportInfo(that)
&& equalsUids(that)
&& equalsSSID(that));
}
@@ -1347,7 +1459,8 @@
+ Objects.hashCode(mNetworkSpecifier) * 23
+ (mSignalStrength * 29)
+ Objects.hashCode(mUids) * 31
- + Objects.hashCode(mSSID) * 37;
+ + Objects.hashCode(mSSID) * 37
+ + Objects.hashCode(mTransportInfo) * 41;
}
@Override
@@ -1362,6 +1475,7 @@
dest.writeInt(mLinkUpBandwidthKbps);
dest.writeInt(mLinkDownBandwidthKbps);
dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
+ dest.writeParcelable((Parcelable) mTransportInfo, flags);
dest.writeInt(mSignalStrength);
dest.writeArraySet(mUids);
dest.writeString(mSSID);
@@ -1379,6 +1493,7 @@
netCap.mLinkUpBandwidthKbps = in.readInt();
netCap.mLinkDownBandwidthKbps = in.readInt();
netCap.mNetworkSpecifier = in.readParcelable(null);
+ netCap.mTransportInfo = in.readParcelable(null);
netCap.mSignalStrength = in.readInt();
netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
null /* ClassLoader, null for default */);
@@ -1392,7 +1507,7 @@
};
@Override
- public String toString() {
+ public @NonNull String toString() {
final StringBuilder sb = new StringBuilder("[");
if (0 != mTransportTypes) {
sb.append(" Transports: ");
@@ -1404,7 +1519,7 @@
appendStringRepresentationOfBitMaskToStringBuilder(sb, mNetworkCapabilities,
NetworkCapabilities::capabilityNameOf, "&");
}
- if (0 != mNetworkCapabilities) {
+ if (0 != mUnwantedNetworkCapabilities) {
sb.append(" Unwanted: ");
appendStringRepresentationOfBitMaskToStringBuilder(sb, mUnwantedNetworkCapabilities,
NetworkCapabilities::capabilityNameOf, "&");
@@ -1418,6 +1533,9 @@
if (mNetworkSpecifier != null) {
sb.append(" Specifier: <").append(mNetworkSpecifier).append(">");
}
+ if (mTransportInfo != null) {
+ sb.append(" TransportInfo: <").append(mTransportInfo).append(">");
+ }
if (hasSignalStrength()) {
sb.append(" SignalStrength: ").append(mSignalStrength);
}
@@ -1448,8 +1566,8 @@
/**
* @hide
*/
- public static void appendStringRepresentationOfBitMaskToStringBuilder(StringBuilder sb,
- long bitMask, NameOf nameFetcher, String separator) {
+ public static void appendStringRepresentationOfBitMaskToStringBuilder(@NonNull StringBuilder sb,
+ long bitMask, @NonNull NameOf nameFetcher, @NonNull String separator) {
int bitPos = 0;
boolean firstElementAdded = false;
while (bitMask != 0) {
@@ -1467,7 +1585,7 @@
}
/** @hide */
- public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(@NonNull ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
for (int transport : getTransportTypes()) {
@@ -1484,6 +1602,9 @@
if (mNetworkSpecifier != null) {
proto.write(NetworkCapabilitiesProto.NETWORK_SPECIFIER, mNetworkSpecifier.toString());
}
+ if (mTransportInfo != null) {
+ // TODO b/120653863: write transport-specific info to proto?
+ }
proto.write(NetworkCapabilitiesProto.CAN_REPORT_SIGNAL_STRENGTH, hasSignalStrength());
proto.write(NetworkCapabilitiesProto.SIGNAL_STRENGTH, mSignalStrength);
@@ -1494,7 +1615,7 @@
/**
* @hide
*/
- public static String capabilityNamesOf(@NetCapability int[] capabilities) {
+ public static @NonNull String capabilityNamesOf(@Nullable @NetCapability int[] capabilities) {
StringJoiner joiner = new StringJoiner("|");
if (capabilities != null) {
for (int c : capabilities) {
@@ -1507,39 +1628,42 @@
/**
* @hide
*/
- public static String capabilityNameOf(@NetCapability int capability) {
+ public static @NonNull String capabilityNameOf(@NetCapability int capability) {
switch (capability) {
- case NET_CAPABILITY_MMS: return "MMS";
- case NET_CAPABILITY_SUPL: return "SUPL";
- case NET_CAPABILITY_DUN: return "DUN";
- case NET_CAPABILITY_FOTA: return "FOTA";
- case NET_CAPABILITY_IMS: return "IMS";
- case NET_CAPABILITY_CBS: return "CBS";
- case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P";
- case NET_CAPABILITY_IA: return "IA";
- case NET_CAPABILITY_RCS: return "RCS";
- case NET_CAPABILITY_XCAP: return "XCAP";
- case NET_CAPABILITY_EIMS: return "EIMS";
- case NET_CAPABILITY_NOT_METERED: return "NOT_METERED";
- case NET_CAPABILITY_INTERNET: return "INTERNET";
- case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
- case NET_CAPABILITY_TRUSTED: return "TRUSTED";
- case NET_CAPABILITY_NOT_VPN: return "NOT_VPN";
- case NET_CAPABILITY_VALIDATED: return "VALIDATED";
- case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
- case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING";
- case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
- case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED";
- case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED";
- case NET_CAPABILITY_OEM_PAID: return "OEM_PAID";
- default: return Integer.toString(capability);
+ case NET_CAPABILITY_MMS: return "MMS";
+ case NET_CAPABILITY_SUPL: return "SUPL";
+ case NET_CAPABILITY_DUN: return "DUN";
+ case NET_CAPABILITY_FOTA: return "FOTA";
+ case NET_CAPABILITY_IMS: return "IMS";
+ case NET_CAPABILITY_CBS: return "CBS";
+ case NET_CAPABILITY_WIFI_P2P: return "WIFI_P2P";
+ case NET_CAPABILITY_IA: return "IA";
+ case NET_CAPABILITY_RCS: return "RCS";
+ case NET_CAPABILITY_XCAP: return "XCAP";
+ case NET_CAPABILITY_EIMS: return "EIMS";
+ case NET_CAPABILITY_NOT_METERED: return "NOT_METERED";
+ case NET_CAPABILITY_INTERNET: return "INTERNET";
+ case NET_CAPABILITY_NOT_RESTRICTED: return "NOT_RESTRICTED";
+ case NET_CAPABILITY_TRUSTED: return "TRUSTED";
+ case NET_CAPABILITY_NOT_VPN: return "NOT_VPN";
+ case NET_CAPABILITY_VALIDATED: return "VALIDATED";
+ case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+ case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING";
+ case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
+ case NET_CAPABILITY_NOT_CONGESTED: return "NOT_CONGESTED";
+ case NET_CAPABILITY_NOT_SUSPENDED: return "NOT_SUSPENDED";
+ case NET_CAPABILITY_OEM_PAID: return "OEM_PAID";
+ case NET_CAPABILITY_MCX: return "MCX";
+ case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
+ default: return Integer.toString(capability);
}
}
/**
* @hide
*/
- public static String transportNamesOf(@Transport int[] types) {
+ @UnsupportedAppUsage
+ public static @NonNull String transportNamesOf(@Nullable @Transport int[] types) {
StringJoiner joiner = new StringJoiner("|");
if (types != null) {
for (int t : types) {
@@ -1552,7 +1676,7 @@
/**
* @hide
*/
- public static String transportNameOf(@Transport int transport) {
+ public static @NonNull String transportNameOf(@Transport int transport) {
if (!isValidTransport(transport)) {
return "UNKNOWN";
}
@@ -1572,4 +1696,14 @@
Preconditions.checkArgument(isValidCapability(capability),
"NetworkCapability " + capability + "out of range");
}
+
+ /**
+ * Check if this {@code NetworkCapability} instance is metered.
+ *
+ * @return {@code true} if {@code NET_CAPABILITY_NOT_METERED} is not set on this instance.
+ * @hide
+ */
+ public boolean isMetered() {
+ return !hasCapability(NET_CAPABILITY_NOT_METERED);
+ }
}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 999771a..8fb5a20 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.NonNull;
+import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,7 +29,20 @@
* Describes the status of a network interface.
* <p>Use {@link ConnectivityManager#getActiveNetworkInfo()} to get an instance that represents
* the current network connection.
+ *
+ * @deprecated Callers should instead use the {@link ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes, or switch to use
+ * {@link ConnectivityManager#getNetworkCapabilities} or
+ * {@link ConnectivityManager#getLinkProperties} to get information synchronously. Keep
+ * in mind that while callbacks are guaranteed to be called for every event in order,
+ * synchronous calls have no such constraints, and as such it is unadvisable to use the
+ * synchronous methods inside the callbacks as they will often not offer a view of
+ * networking that is consistent (that is: they may return a past or a future state with
+ * respect to the event being processed by the callback). Instead, callers are advised
+ * to only use the arguments of the callbacks, possibly memorizing the specific bits of
+ * information they need to keep from one callback to another.
*/
+@Deprecated
public class NetworkInfo implements Parcelable {
/**
@@ -51,7 +66,10 @@
* <tr><td><code>FAILED</code></td><td><code>DISCONNECTED</code></td></tr>
* <tr><td><code>BLOCKED</code></td><td><code>DISCONNECTED</code></td></tr>
* </table>
+ *
+ * @deprecated See {@link NetworkInfo}.
*/
+ @Deprecated
public enum State {
CONNECTING, CONNECTED, SUSPENDED, DISCONNECTING, DISCONNECTED, UNKNOWN
}
@@ -60,7 +78,10 @@
* The fine-grained state of a network connection. This level of detail
* is probably of interest to few applications. Most should use
* {@link android.net.NetworkInfo.State State} instead.
+ *
+ * @deprecated See {@link NetworkInfo}.
*/
+ @Deprecated
public enum DetailedState {
/** Ready to start data connection setup. */
IDLE,
@@ -118,7 +139,9 @@
private int mSubtype;
private String mTypeName;
private String mSubtypeName;
+ @NonNull
private State mState;
+ @NonNull
private DetailedState mDetailedState;
private String mReason;
private String mExtraInfo;
@@ -129,6 +152,7 @@
/**
* @hide
*/
+ @UnsupportedAppUsage
public NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
if (!ConnectivityManager.isNetworkTypeValid(type)
&& type != ConnectivityManager.TYPE_NONE) {
@@ -143,6 +167,7 @@
}
/** {@hide} */
+ @UnsupportedAppUsage
public NetworkInfo(NetworkInfo source) {
if (source != null) {
synchronized (source) {
@@ -199,7 +224,9 @@
* Return a network-type-specific integer describing the subtype
* of the network.
* @return the network subtype
+ * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead.
*/
+ @Deprecated
public int getSubtype() {
synchronized (this) {
return mSubtype;
@@ -209,6 +236,7 @@
/**
* @hide
*/
+ @UnsupportedAppUsage
public void setSubtype(int subtype, String subtypeName) {
synchronized (this) {
mSubtype = subtype;
@@ -239,7 +267,9 @@
/**
* Return a human-readable name describing the subtype of the network.
* @return the name of the network subtype
+ * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead.
*/
+ @Deprecated
public String getSubtypeName() {
synchronized (this) {
return mSubtypeName;
@@ -274,7 +304,15 @@
* connections and pass data.
* <p>Always call this before attempting to perform data transactions.
* @return {@code true} if network connectivity exists, {@code false} otherwise.
+ * @deprecated Apps should instead use the
+ * {@link android.net.ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes. See
+ * {@link ConnectivityManager#registerDefaultNetworkCallback} and
+ * {@link ConnectivityManager#registerNetworkCallback}. These will
+ * give a more accurate picture of the connectivity state of
+ * the device and let apps react more easily and quickly to changes.
*/
+ @Deprecated
public boolean isConnected() {
synchronized (this) {
return mState == State.CONNECTED;
@@ -317,6 +355,7 @@
* @hide
*/
@Deprecated
+ @UnsupportedAppUsage
public void setIsAvailable(boolean isAvailable) {
synchronized (this) {
mIsAvailable = isAvailable;
@@ -347,6 +386,7 @@
* @hide
*/
@Deprecated
+ @UnsupportedAppUsage
public void setFailover(boolean isFailover) {
synchronized (this) {
mIsFailover = isFailover;
@@ -377,6 +417,7 @@
*/
@VisibleForTesting
@Deprecated
+ @UnsupportedAppUsage
public void setRoaming(boolean isRoaming) {
synchronized (this) {
mIsRoaming = isRoaming;
@@ -404,8 +445,16 @@
/**
* Reports the current fine-grained state of the network.
* @return the fine-grained state
+ * @deprecated Apps should instead use the
+ * {@link android.net.ConnectivityManager.NetworkCallback} API to
+ * learn about connectivity changes. See
+ * {@link ConnectivityManager#registerDefaultNetworkCallback} and
+ * {@link ConnectivityManager#registerNetworkCallback}. These will
+ * give a more accurate picture of the connectivity state of
+ * the device and let apps react more easily and quickly to changes.
*/
- public DetailedState getDetailedState() {
+ @Deprecated
+ public @NonNull DetailedState getDetailedState() {
synchronized (this) {
return mDetailedState;
}
@@ -422,6 +471,7 @@
* @hide
*/
@Deprecated
+ @UnsupportedAppUsage
public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
synchronized (this) {
this.mDetailedState = detailedState;
@@ -435,8 +485,10 @@
* Set the extraInfo field.
* @param extraInfo an optional {@code String} providing addditional network state
* information passed up from the lower networking layers.
+ * @deprecated See {@link NetworkInfo#getExtraInfo}.
* @hide
*/
+ @Deprecated
public void setExtraInfo(String extraInfo) {
synchronized (this) {
this.mExtraInfo = extraInfo;
@@ -460,7 +512,10 @@
* Report the extra information about the network state, if any was
* provided by the lower networking layers.
* @return the extra information, or null if not available
+ * @deprecated Use other services e.g. WifiManager to get additional information passed up from
+ * the lower networking layers.
*/
+ @Deprecated
public String getExtraInfo() {
synchronized (this) {
return mExtraInfo;
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index a2da6ea..6fb2390 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -52,6 +52,12 @@
public boolean acceptUnvalidated;
/**
+ * Whether the user explicitly set that this network should be validated even if presence of
+ * only partial internet connectivity.
+ */
+ public boolean acceptPartialConnectivity;
+
+ /**
* Set to avoid surfacing the "Sign in to network" notification.
* if carrier receivers/apps are registered to handle the carrier-specific provisioning
* procedure, a carrier specific provisioning notification will be placed.
@@ -65,6 +71,12 @@
*/
public String subscriberId;
+ /**
+ * Set to skip 464xlat. This means the device will treat the network as IPv6-only and
+ * will not attempt to detect a NAT64 via RFC 7050 DNS lookups.
+ */
+ public boolean skip464xlat;
+
public NetworkMisc() {
}
@@ -75,6 +87,7 @@
acceptUnvalidated = nm.acceptUnvalidated;
subscriberId = nm.subscriberId;
provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
+ skip464xlat = nm.skip464xlat;
}
}
@@ -90,6 +103,7 @@
out.writeInt(acceptUnvalidated ? 1 : 0);
out.writeString(subscriberId);
out.writeInt(provisioningNotificationDisabled ? 1 : 0);
+ out.writeInt(skip464xlat ? 1 : 0);
}
public static final Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
@@ -101,6 +115,7 @@
networkMisc.acceptUnvalidated = in.readInt() != 0;
networkMisc.subscriberId = in.readString();
networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
+ networkMisc.skip464xlat = in.readInt() != 0;
return networkMisc;
}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 16c2342..acafa13 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -17,8 +17,12 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
import android.net.NetworkCapabilities.NetCapability;
import android.net.NetworkCapabilities.Transport;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
@@ -38,6 +42,7 @@
* The {@link NetworkCapabilities} that define this request.
* @hide
*/
+ @UnsupportedAppUsage
public final @NonNull NetworkCapabilities networkCapabilities;
/**
@@ -46,6 +51,7 @@
* the request.
* @hide
*/
+ @UnsupportedAppUsage
public final int requestId;
/**
@@ -53,6 +59,7 @@
* Causes CONNECTIVITY_ACTION broadcasts to be sent.
* @hide
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public final int legacyType;
/**
@@ -241,6 +248,7 @@
* @return The builder to facilitate chaining.
* @hide
*/
+ @UnsupportedAppUsage
public Builder clearCapabilities() {
mNetworkCapabilities.clearAll();
return this;
@@ -336,10 +344,15 @@
* current value. A value of {@code SIGNAL_STRENGTH_UNSPECIFIED} means no value when
* received and has no effect when requesting a callback.
*
+ * <p>This method requires the caller to hold the
+ * {@link android.Manifest.permission#NETWORK_SIGNAL_STRENGTH_WAKEUP} permission
+ *
* @param signalStrength the bearer-specific signal strength.
* @hide
*/
- public Builder setSignalStrength(int signalStrength) {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP)
+ public @NonNull Builder setSignalStrength(int signalStrength) {
mNetworkCapabilities.setSignalStrength(signalStrength);
return this;
}
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 321f971..97fb3fb 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -16,6 +16,8 @@
package android.net;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Slog;
@@ -33,6 +35,7 @@
public final NetworkInfo networkInfo;
public final LinkProperties linkProperties;
public final NetworkCapabilities networkCapabilities;
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public final Network network;
public final String subscriberId;
public final String networkId;
@@ -58,6 +61,7 @@
}
}
+ @UnsupportedAppUsage
public NetworkState(Parcel in) {
networkInfo = in.readParcelable(null);
linkProperties = in.readParcelable(null);
@@ -82,6 +86,7 @@
out.writeString(networkId);
}
+ @UnsupportedAppUsage
public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() {
@Override
public NetworkState createFromParcel(Parcel in) {
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 9a5d502..228e62d 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -16,7 +16,10 @@
package android.net;
-import android.os.Parcel;
+import android.annotation.UnsupportedAppUsage;
+import android.net.shared.Inet4AddressUtils;
+import android.os.Build;
+import android.system.ErrnoException;
import android.util.Log;
import android.util.Pair;
@@ -41,27 +44,16 @@
private static final String TAG = "NetworkUtils";
/**
- * Attaches a socket filter that accepts DHCP packets to the given socket.
+ * Attaches a socket filter that drops all of incoming packets.
+ * @param fd the socket's {@link FileDescriptor}.
*/
- public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
+ public static native void attachDropAllBPFFilter(FileDescriptor fd) throws SocketException;
/**
- * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
+ * Detaches a socket filter.
* @param fd the socket's {@link FileDescriptor}.
- * @param packetType the hardware address type, one of ARPHRD_*.
*/
- public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
-
- /**
- * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
- *
- * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
- *
- * @param fd the socket's {@link FileDescriptor}.
- * @param packetType the hardware address type, one of ARPHRD_*.
- */
- public native static void attachControlPacketFilter(FileDescriptor fd, int packetType)
- throws SocketException;
+ public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
/**
* Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
@@ -108,6 +100,7 @@
* this socket will go directly to the underlying network, so its traffic will not be
* forwarded through the VPN.
*/
+ @UnsupportedAppUsage
public static boolean protectFromVpn(FileDescriptor fd) {
return protectFromVpn(fd.getInt$());
}
@@ -126,51 +119,90 @@
public native static boolean queryUserAccess(int uid, int netId);
/**
- * Convert a IPv4 address from an integer to an InetAddress.
- * @param hostAddress an int corresponding to the IPv4 address in network byte order
+ * DNS resolver series jni method.
+ * Issue the query {@code msg} on the network designated by {@code netId}.
+ * {@code flags} is an additional config to control actual querying behavior.
+ * @return a file descriptor to watch for read events
*/
- public static InetAddress intToInetAddress(int hostAddress) {
- byte[] addressBytes = { (byte)(0xff & hostAddress),
- (byte)(0xff & (hostAddress >> 8)),
- (byte)(0xff & (hostAddress >> 16)),
- (byte)(0xff & (hostAddress >> 24)) };
+ public static native FileDescriptor resNetworkSend(
+ int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
- try {
- return InetAddress.getByAddress(addressBytes);
- } catch (UnknownHostException e) {
- throw new AssertionError();
- }
+ /**
+ * DNS resolver series jni method.
+ * Look up the {@code nsClass} {@code nsType} Resource Record (RR) associated
+ * with Domain Name {@code dname} on the network designated by {@code netId}.
+ * {@code flags} is an additional config to control actual querying behavior.
+ * @return a file descriptor to watch for read events
+ */
+ public static native FileDescriptor resNetworkQuery(
+ int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Read a result for the query associated with the {@code fd}.
+ * @return DnsResponse containing blob answer and rcode
+ */
+ public static native DnsResolver.DnsResponse resNetworkResult(FileDescriptor fd)
+ throws ErrnoException;
+
+ /**
+ * DNS resolver series jni method.
+ * Attempts to cancel the in-progress query associated with the {@code fd}.
+ */
+ public static native void resNetworkCancel(FileDescriptor fd);
+
+ /**
+ * DNS resolver series jni method.
+ * Attempts to get network which resolver will use if no network is explicitly selected.
+ */
+ public static native Network getDnsNetwork() throws ErrnoException;
+
+ /**
+ * Get the tcp repair window associated with the {@code fd}.
+ *
+ * @param fd the tcp socket's {@link FileDescriptor}.
+ * @return a {@link TcpRepairWindow} object indicates tcp window size.
+ */
+ public static native TcpRepairWindow getTcpRepairWindow(FileDescriptor fd)
+ throws ErrnoException;
+
+ /**
+ * @see Inet4AddressUtils#intToInet4AddressHTL(int)
+ * @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)}
+ * or {@link Inet4AddressUtils#intToInet4AddressHTL(int)}
+ */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static InetAddress intToInetAddress(int hostAddress) {
+ return Inet4AddressUtils.intToInet4AddressHTL(hostAddress);
}
/**
- * Convert a IPv4 address from an InetAddress to an integer
- * @param inetAddr is an InetAddress corresponding to the IPv4 address
- * @return the IP address as an integer in network byte order
+ * @see Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)
+ * @deprecated Use either {@link Inet4AddressUtils#inet4AddressToIntHTH(Inet4Address)}
+ * or {@link Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)}
*/
+ @Deprecated
public static int inetAddressToInt(Inet4Address inetAddr)
throws IllegalArgumentException {
- byte [] addr = inetAddr.getAddress();
- return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) |
- ((addr[1] & 0xff) << 8) | (addr[0] & 0xff);
+ return Inet4AddressUtils.inet4AddressToIntHTL(inetAddr);
}
/**
- * Convert a network prefix length to an IPv4 netmask integer
- * @param prefixLength
- * @return the IPv4 netmask as an integer in network byte order
+ * @see Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)
+ * @deprecated Use either {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTH(int)}
+ * or {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)}
*/
+ @Deprecated
+ @UnsupportedAppUsage
public static int prefixLengthToNetmaskInt(int prefixLength)
throws IllegalArgumentException {
- if (prefixLength < 0 || prefixLength > 32) {
- throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
- }
- int value = 0xffffffff << (32 - prefixLength);
- return Integer.reverseBytes(value);
+ return Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL(prefixLength);
}
/**
* Convert a IPv4 netmask integer to a prefix length
- * @param netmask as an integer in network byte order
+ * @param netmask as an integer (0xff000000 for a /8 subnet)
* @return the network prefix length
*/
public static int netmaskIntToPrefixLength(int netmask) {
@@ -183,16 +215,13 @@
* @return the network prefix length
* @throws IllegalArgumentException the specified netmask was not contiguous.
* @hide
+ * @deprecated use {@link Inet4AddressUtils#netmaskToPrefixLength(Inet4Address)}
*/
+ @UnsupportedAppUsage
+ @Deprecated
public static int netmaskToPrefixLength(Inet4Address netmask) {
- // inetAddressToInt returns an int in *network* byte order.
- int i = Integer.reverseBytes(inetAddressToInt(netmask));
- int prefixLength = Integer.bitCount(i);
- int trailingZeros = Integer.numberOfTrailingZeros(i);
- if (trailingZeros != 32 - prefixLength) {
- throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i));
- }
- return prefixLength;
+ // This is only here because some apps seem to be using it (@UnsupportedAppUsage).
+ return Inet4AddressUtils.netmaskToPrefixLength(netmask);
}
@@ -203,39 +232,16 @@
* @param addrString
* @return the InetAddress
* @hide
+ * @deprecated Use {@link InetAddresses#parseNumericAddress(String)}, if possible.
*/
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ @Deprecated
public static InetAddress numericToInetAddress(String addrString)
throws IllegalArgumentException {
return InetAddress.parseNumericAddress(addrString);
}
/**
- * Writes an InetAddress to a parcel. The address may be null. This is likely faster than
- * calling writeSerializable.
- */
- protected static void parcelInetAddress(Parcel parcel, InetAddress address, int flags) {
- byte[] addressArray = (address != null) ? address.getAddress() : null;
- parcel.writeByteArray(addressArray);
- }
-
- /**
- * Reads an InetAddress from a parcel. Returns null if the address that was written was null
- * or if the data is invalid.
- */
- protected static InetAddress unparcelInetAddress(Parcel in) {
- byte[] addressArray = in.createByteArray();
- if (addressArray == null) {
- return null;
- }
- try {
- return InetAddress.getByAddress(addressArray);
- } catch (UnknownHostException e) {
- return null;
- }
- }
-
-
- /**
* Masks a raw IP address byte array with the specified prefix length.
*/
public static void maskRawAddress(byte[] array, int prefixLength) {
@@ -278,17 +284,10 @@
/**
* Returns the implicit netmask of an IPv4 address, as was the custom before 1993.
*/
+ @UnsupportedAppUsage
public static int getImplicitNetmask(Inet4Address address) {
- int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value.
- if (firstByte < 128) {
- return 8;
- } else if (firstByte < 192) {
- return 16;
- } else if (firstByte < 224) {
- return 24;
- } else {
- return 32; // Will likely not end well for other reasons.
- }
+ // Only here because it seems to be used by apps
+ return Inet4AddressUtils.getImplicitNetmask(address);
}
/**
@@ -368,6 +367,7 @@
* @param addr a string representing an ip addr
* @return a string propertly trimmed
*/
+ @UnsupportedAppUsage
public static String trimV4AddrZeros(String addr) {
if (addr == null) return null;
String[] octets = addr.split("\\.");
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index 5f5e623..ef2269a 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -17,6 +17,7 @@
package android.net;
+import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -38,12 +39,12 @@
*/
public class ProxyInfo implements Parcelable {
- private String mHost;
- private int mPort;
- private String mExclusionList;
- private String[] mParsedExclusionList;
+ private final String mHost;
+ private final int mPort;
+ private final String mExclusionList;
+ private final String[] mParsedExclusionList;
+ private final Uri mPacFileUrl;
- private Uri mPacFileUrl;
/**
*@hide
*/
@@ -91,10 +92,12 @@
* Create a ProxyProperties that points at a HTTP Proxy.
* @hide
*/
+ @UnsupportedAppUsage
public ProxyInfo(String host, int port, String exclList) {
mHost = host;
mPort = port;
- setExclusionList(exclList);
+ mExclusionList = exclList;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.EMPTY;
}
@@ -105,7 +108,8 @@
public ProxyInfo(Uri pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) {
throw new NullPointerException();
}
@@ -119,7 +123,8 @@
public ProxyInfo(String pacFileUrl) {
mHost = LOCAL_HOST;
mPort = LOCAL_PORT;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
mPacFileUrl = Uri.parse(pacFileUrl);
}
@@ -130,13 +135,22 @@
public ProxyInfo(Uri pacFileUrl, int localProxyPort) {
mHost = LOCAL_HOST;
mPort = localProxyPort;
- setExclusionList(LOCAL_EXCL_LIST);
+ mExclusionList = LOCAL_EXCL_LIST;
+ mParsedExclusionList = parseExclusionList(mExclusionList);
if (pacFileUrl == null) {
throw new NullPointerException();
}
mPacFileUrl = pacFileUrl;
}
+ private static String[] parseExclusionList(String exclusionList) {
+ if (exclusionList == null) {
+ return new String[0];
+ } else {
+ return exclusionList.toLowerCase(Locale.ROOT).split(",");
+ }
+ }
+
private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) {
mHost = host;
mPort = port;
@@ -157,6 +171,10 @@
mExclusionList = source.getExclusionListAsString();
mParsedExclusionList = source.mParsedExclusionList;
} else {
+ mHost = null;
+ mPort = 0;
+ mExclusionList = null;
+ mParsedExclusionList = null;
mPacFileUrl = Uri.EMPTY;
}
}
@@ -212,24 +230,14 @@
return mExclusionList;
}
- // comma separated
- private void setExclusionList(String exclusionList) {
- mExclusionList = exclusionList;
- if (mExclusionList == null) {
- mParsedExclusionList = new String[0];
- } else {
- mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(",");
- }
- }
-
/**
* @hide
*/
public boolean isValid() {
if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
- mPort == 0 ? "" : Integer.toString(mPort),
- mExclusionList == null ? "" : mExclusionList);
+ mPort == 0 ? "" : Integer.toString(mPort),
+ mExclusionList == null ? "" : mExclusionList);
}
/**
@@ -260,7 +268,7 @@
sb.append("] ");
sb.append(Integer.toString(mPort));
if (mExclusionList != null) {
- sb.append(" xl=").append(mExclusionList);
+ sb.append(" xl=").append(mExclusionList);
}
} else {
sb.append("[ProxyProperties.mHost == null]");
@@ -306,8 +314,8 @@
*/
public int hashCode() {
return ((null == mHost) ? 0 : mHost.hashCode())
- + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
- + mPort;
+ + ((null == mExclusionList) ? 0 : mExclusionList.hashCode())
+ + mPort;
}
/**
@@ -350,8 +358,7 @@
}
String exclList = in.readString();
String[] parsedExclList = in.readStringArray();
- ProxyInfo proxyProperties =
- new ProxyInfo(host, port, exclList, parsedExclList);
+ ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList);
return proxyProperties;
}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 90a2460..fdd904a 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -16,14 +16,22 @@
package android.net;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import java.net.UnknownHostException;
-import java.net.InetAddress;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.Inet4Address;
import java.net.Inet6Address;
-
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Objects;
@@ -47,29 +55,48 @@
* (IPv4 or IPv6).
*/
public final class RouteInfo implements Parcelable {
+ /** @hide */
+ @IntDef(value = {
+ RTN_UNICAST,
+ RTN_UNREACHABLE,
+ RTN_THROW,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RouteType {}
+
/**
* The IP destination address for this route.
*/
+ @NonNull
private final IpPrefix mDestination;
/**
* The gateway address for this route.
*/
+ @UnsupportedAppUsage
+ @Nullable
private final InetAddress mGateway;
/**
* The interface for this route.
*/
+ @Nullable
private final String mInterface;
/** Unicast route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_UNICAST = 1;
/** Unreachable route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_UNREACHABLE = 7;
/** Throw route. @hide */
+ @SystemApi
+ @TestApi
public static final int RTN_THROW = 9;
/**
@@ -79,6 +106,7 @@
// Derived data members.
// TODO: remove these.
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final boolean mIsHost;
private final boolean mHasGateway;
@@ -96,10 +124,14 @@
* @param destination the destination prefix
* @param gateway the IP address to route packets through
* @param iface the interface name to send packets on
+ * @param type the type of this route
*
* @hide
*/
- public RouteInfo(IpPrefix destination, InetAddress gateway, String iface, int type) {
+ @SystemApi
+ @TestApi
+ public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway,
+ @Nullable String iface, @RouteType int type) {
switch (type) {
case RTN_UNICAST:
case RTN_UNREACHABLE:
@@ -158,16 +190,33 @@
}
/**
- * @hide
+ * Constructs a {@code RouteInfo} object.
+ *
+ * If destination is null, then gateway must be specified and the
+ * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+ * if the gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * route <code>::/0</code> if gateway is an instance of {@link Inet6Address}.
+ * <p>
+ * Destination and gateway may not both be null.
+ *
+ * @param destination the destination address and prefix in an {@link IpPrefix}
+ * @param gateway the {@link InetAddress} to route packets through
+ * @param iface the interface name to send packets on
+ *
+ * @hide
*/
- public RouteInfo(IpPrefix destination, InetAddress gateway, String iface) {
+ @UnsupportedAppUsage
+ public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway,
+ @Nullable String iface) {
this(destination, gateway, iface, RTN_UNICAST);
}
/**
* @hide
*/
- public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) {
+ @UnsupportedAppUsage
+ public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway,
+ @Nullable String iface) {
this(destination == null ? null :
new IpPrefix(destination.getAddress(), destination.getPrefixLength()),
gateway, iface);
@@ -188,7 +237,7 @@
*
* @hide
*/
- public RouteInfo(IpPrefix destination, InetAddress gateway) {
+ public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway) {
this(destination, gateway, null);
}
@@ -197,7 +246,8 @@
*
* TODO: Remove this.
*/
- public RouteInfo(LinkAddress destination, InetAddress gateway) {
+ @UnsupportedAppUsage
+ public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway) {
this(destination, gateway, null);
}
@@ -208,7 +258,8 @@
*
* @hide
*/
- public RouteInfo(InetAddress gateway) {
+ @UnsupportedAppUsage
+ public RouteInfo(@NonNull InetAddress gateway) {
this((IpPrefix) null, gateway, null);
}
@@ -220,35 +271,36 @@
*
* @hide
*/
- public RouteInfo(IpPrefix destination) {
+ public RouteInfo(@NonNull IpPrefix destination) {
this(destination, null, null);
}
/**
* @hide
*/
- public RouteInfo(LinkAddress destination) {
+ public RouteInfo(@NonNull LinkAddress destination) {
this(destination, null, null);
}
/**
* @hide
*/
- public RouteInfo(IpPrefix destination, int type) {
+ public RouteInfo(@NonNull IpPrefix destination, @RouteType int type) {
this(destination, null, null, type);
}
/**
* @hide
*/
- public static RouteInfo makeHostRoute(InetAddress host, String iface) {
+ public static RouteInfo makeHostRoute(@NonNull InetAddress host, @Nullable String iface) {
return makeHostRoute(host, null, iface);
}
/**
* @hide
*/
- public static RouteInfo makeHostRoute(InetAddress host, InetAddress gateway, String iface) {
+ public static RouteInfo makeHostRoute(@Nullable InetAddress host, @Nullable InetAddress gateway,
+ @Nullable String iface) {
if (host == null) return null;
if (host instanceof Inet4Address) {
@@ -258,6 +310,7 @@
}
}
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private boolean isHost() {
return (mDestination.getAddress() instanceof Inet4Address &&
mDestination.getPrefixLength() == 32) ||
@@ -270,6 +323,7 @@
*
* @return {@link IpPrefix} specifying the destination. This is never {@code null}.
*/
+ @NonNull
public IpPrefix getDestination() {
return mDestination;
}
@@ -278,6 +332,7 @@
* TODO: Convert callers to use IpPrefix and then remove.
* @hide
*/
+ @NonNull
public LinkAddress getDestinationLinkAddress() {
return new LinkAddress(mDestination.getAddress(), mDestination.getPrefixLength());
}
@@ -288,6 +343,7 @@
* @return {@link InetAddress} specifying the gateway or next hop. This may be
* {@code null} for a directly-connected route."
*/
+ @Nullable
public InetAddress getGateway() {
return mGateway;
}
@@ -297,6 +353,7 @@
*
* @return The name of the interface used for this route.
*/
+ @Nullable
public String getInterface() {
return mInterface;
}
@@ -308,6 +365,9 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
+ @RouteType
public int getType() {
return mType;
}
@@ -353,7 +413,6 @@
* ({@code false}).
*
* @return {@code true} if a gateway is specified
- * @hide
*/
public boolean hasGateway() {
return mHasGateway;
@@ -379,6 +438,8 @@
*
* @hide
*/
+ @UnsupportedAppUsage
+ @Nullable
public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
if ((routes == null) || (dest == null)) return null;
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
new file mode 100644
index 0000000..ec73866
--- /dev/null
+++ b/core/java/android/net/SocketKeepalive.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
+ * passing in a non-null callback. If the {@link SocketKeepalive} is successfully
+ * started, the callback's {@code onStarted} method will be called. If an error occurs,
+ * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
+ * class.
+ *
+ * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
+ * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
+ * {@link SocketKeepalive.Callback#onError} if an error occurred.
+ *
+ * For cellular, the device MUST support at least 1 keepalive slot.
+ *
+ * For WiFi, the device SHOULD support keepalive offload. If it does not, it MUST reply with
+ * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload
+ * request. If it does, it MUST support at least 3 concurrent keepalive slots.
+ */
+public abstract class SocketKeepalive implements AutoCloseable {
+ static final String TAG = "SocketKeepalive";
+
+ /** @hide */
+ public static final int SUCCESS = 0;
+
+ /** @hide */
+ public static final int NO_KEEPALIVE = -1;
+
+ /** @hide */
+ public static final int DATA_RECEIVED = -2;
+
+ /** @hide */
+ public static final int BINDER_DIED = -10;
+
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = -20;
+ /** The specified IP addresses are invalid. For example, the specified source IP address is
+ * not configured on the specified {@code Network}. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+ /** The requested port is invalid. */
+ public static final int ERROR_INVALID_PORT = -22;
+ /** The packet length is invalid (e.g., too long). */
+ public static final int ERROR_INVALID_LENGTH = -23;
+ /** The packet transmission interval is invalid (e.g., too short). */
+ public static final int ERROR_INVALID_INTERVAL = -24;
+ /** The target socket is invalid. */
+ public static final int ERROR_INVALID_SOCKET = -25;
+ /** The target socket is not idle. */
+ public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
+ /** The device does not support this request. */
+ public static final int ERROR_UNSUPPORTED = -30;
+ /** @hide TODO: delete when telephony code has been updated. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = -31;
+ /** The limitation of resource is reached. */
+ public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_NETWORK,
+ ERROR_INVALID_IP_ADDRESS,
+ ERROR_INVALID_PORT,
+ ERROR_INVALID_LENGTH,
+ ERROR_INVALID_INTERVAL,
+ ERROR_INVALID_SOCKET,
+ ERROR_SOCKET_NOT_IDLE
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * The minimum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MIN_INTERVAL_SEC = 10;
+
+ /**
+ * The maximum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MAX_INTERVAL_SEC = 3600;
+
+ /**
+ * An exception that embarks an error code.
+ * @hide
+ */
+ public static class ErrorCodeException extends Exception {
+ public final int error;
+ public ErrorCodeException(final int error, final Throwable e) {
+ super(e);
+ this.error = error;
+ }
+ public ErrorCodeException(final int error) {
+ this.error = error;
+ }
+ }
+
+ /**
+ * This socket is invalid.
+ * See the error code for details, and the optional cause.
+ * @hide
+ */
+ public static class InvalidSocketException extends ErrorCodeException {
+ public InvalidSocketException(final int error, final Throwable e) {
+ super(error, e);
+ }
+ public InvalidSocketException(final int error) {
+ super(error);
+ }
+ }
+
+ /**
+ * This packet is invalid.
+ * See the error code for details.
+ * @hide
+ */
+ public static class InvalidPacketException extends ErrorCodeException {
+ public InvalidPacketException(final int error) {
+ super(error);
+ }
+ }
+
+ @NonNull final IConnectivityManager mService;
+ @NonNull final Network mNetwork;
+ @NonNull final ParcelFileDescriptor mPfd;
+ @NonNull final Executor mExecutor;
+ @NonNull final ISocketKeepaliveCallback mCallback;
+ // TODO: remove slot since mCallback could be used to identify which keepalive to stop.
+ @Nullable Integer mSlot;
+
+ SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
+ @NonNull Executor executor, @NonNull Callback callback) {
+ mService = service;
+ mNetwork = network;
+ mPfd = pfd;
+ mExecutor = executor;
+ mCallback = new ISocketKeepaliveCallback.Stub() {
+ @Override
+ public void onStarted(int slot) {
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() -> {
+ mSlot = slot;
+ callback.onStarted();
+ }));
+ }
+
+ @Override
+ public void onStopped() {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ mSlot = null;
+ callback.onStopped();
+ }));
+ }
+
+ @Override
+ public void onError(int error) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ mSlot = null;
+ callback.onError(error);
+ }));
+ }
+
+ @Override
+ public void onDataReceived() {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> {
+ mSlot = null;
+ callback.onDataReceived();
+ }));
+ }
+ };
+ }
+
+ /**
+ * Request that keepalive be started with the given {@code intervalSec}. See
+ * {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an exception
+ * when invoking start or stop of the {@link SocketKeepalive}, a {@link RemoteException} will be
+ * thrown into the {@code executor}. This is typically not important to catch because the remote
+ * party is the system, so if it is not in shape to communicate through binder the system is
+ * probably going down anyway. If the caller cares regardless, it can use a custom
+ * {@link Executor} to catch the {@link RemoteException}.
+ *
+ * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+ * The interval should be between 10 seconds and 3600 seconds, otherwise
+ * {@link #ERROR_INVALID_INTERVAL} will be returned.
+ */
+ public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+ int intervalSec) {
+ startImpl(intervalSec);
+ }
+
+ abstract void startImpl(int intervalSec);
+
+ /**
+ * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
+ * before using the object. See {@link SocketKeepalive}.
+ */
+ public final void stop() {
+ stopImpl();
+ }
+
+ abstract void stopImpl();
+
+ /**
+ * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
+ * usable again if {@code close()} is called.
+ */
+ @Override
+ public final void close() {
+ stop();
+ try {
+ mPfd.close();
+ } catch (IOException e) {
+ // Nothing much can be done.
+ }
+ }
+
+ /**
+ * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
+ * {@link SocketKeepalive}.
+ */
+ public static class Callback {
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(@ErrorCode int error) {}
+ /** The keepalive on a TCP socket was stopped because the socket received data. This is
+ * never called for UDP sockets. */
+ public void onDataReceived() {}
+ }
+}
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 58b1b88..0600036 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -16,9 +16,14 @@
package android.net;
-import android.net.LinkAddress;
-import android.os.Parcelable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.net.shared.InetAddressUtils;
import android.os.Parcel;
+import android.os.Parcelable;
import java.net.InetAddress;
import java.util.ArrayList;
@@ -28,34 +33,47 @@
/**
* Class that describes static IP configuration.
*
- * This class is different from LinkProperties because it represents
+ * <p>This class is different from {@link LinkProperties} because it represents
* configuration intent. The general contract is that if we can represent
* a configuration here, then we should be able to configure it on a network.
* The intent is that it closely match the UI we have for configuring networks.
*
- * In contrast, LinkProperties represents current state. It is much more
+ * <p>In contrast, {@link LinkProperties} represents current state. It is much more
* expressive. For example, it supports multiple IP addresses, multiple routes,
* stacked interfaces, and so on. Because LinkProperties is so expressive,
* using it to represent configuration intent as well as current state causes
* problems. For example, we could unknowingly save a configuration that we are
* not in fact capable of applying, or we could save a configuration that the
* UI cannot display, which has the potential for malicious code to hide
- * hostile or unexpected configuration from the user: see, for example,
- * http://b/12663469 and http://b/16893413 .
+ * hostile or unexpected configuration from the user.
*
* @hide
*/
-public class StaticIpConfiguration implements Parcelable {
+@SystemApi
+@TestApi
+public final class StaticIpConfiguration implements Parcelable {
+ /** @hide */
+ @UnsupportedAppUsage
+ @Nullable
public LinkAddress ipAddress;
+ /** @hide */
+ @UnsupportedAppUsage
+ @Nullable
public InetAddress gateway;
+ /** @hide */
+ @UnsupportedAppUsage
+ @NonNull
public final ArrayList<InetAddress> dnsServers;
+ /** @hide */
+ @UnsupportedAppUsage
+ @Nullable
public String domains;
public StaticIpConfiguration() {
- dnsServers = new ArrayList<InetAddress>();
+ dnsServers = new ArrayList<>();
}
- public StaticIpConfiguration(StaticIpConfiguration source) {
+ public StaticIpConfiguration(@Nullable StaticIpConfiguration source) {
this();
if (source != null) {
// All of these except dnsServers are immutable, so no need to make copies.
@@ -74,17 +92,117 @@
}
/**
- * Returns the network routes specified by this object. Will typically include a
- * directly-connected route for the IP address's local subnet and a default route. If the
- * default gateway is not covered by the directly-connected route, it will also contain a host
- * route to the gateway as well. This configuration is arguably invalid, but it used to work
- * in K and earlier, and other OSes appear to accept it.
+ * Get the static IP address included in the configuration.
*/
- public List<RouteInfo> getRoutes(String iface) {
+ public @Nullable LinkAddress getIpAddress() {
+ return ipAddress;
+ }
+
+ /**
+ * Get the gateway included in the configuration.
+ */
+ public @Nullable InetAddress getGateway() {
+ return gateway;
+ }
+
+ /**
+ * Get the DNS servers included in the configuration.
+ */
+ public @NonNull List<InetAddress> getDnsServers() {
+ return dnsServers;
+ }
+
+ /**
+ * Get a {@link String} containing the comma separated domains to search when resolving host
+ * names on this link, in priority order.
+ */
+ public @Nullable String getDomains() {
+ return domains;
+ }
+
+ /**
+ * Helper class to build a new instance of {@link StaticIpConfiguration}.
+ */
+ public static final class Builder {
+ private LinkAddress mIpAddress;
+ private InetAddress mGateway;
+ private Iterable<InetAddress> mDnsServers;
+ private String mDomains;
+
+ /**
+ * Set the IP address to be included in the configuration; null by default.
+ * @return The {@link Builder} for chaining.
+ */
+ public @NonNull Builder setIpAddress(@Nullable LinkAddress ipAddress) {
+ mIpAddress = ipAddress;
+ return this;
+ }
+
+ /**
+ * Set the address of the gateway to be included in the configuration; null by default.
+ * @return The {@link Builder} for chaining.
+ */
+ public @NonNull Builder setGateway(@Nullable InetAddress gateway) {
+ mGateway = gateway;
+ return this;
+ }
+
+ /**
+ * Set the addresses of the DNS servers included in the configuration; empty by default.
+ * @return The {@link Builder} for chaining.
+ */
+ public @NonNull Builder setDnsServers(@NonNull Iterable<InetAddress> dnsServers) {
+ mDnsServers = dnsServers;
+ return this;
+ }
+
+ /**
+ * Sets the DNS domain search path to be used on the link; null by default.
+ * @param newDomains A {@link String} containing the comma separated domains to search when
+ * resolving host names on this link, in priority order.
+ * @return The {@link Builder} for chaining.
+ */
+ public @NonNull Builder setDomains(@Nullable String newDomains) {
+ mDomains = newDomains;
+ return this;
+ }
+
+ /**
+ * Create a {@link StaticIpConfiguration} from the parameters in this {@link Builder}.
+ * @return The newly created StaticIpConfiguration.
+ */
+ public @NonNull StaticIpConfiguration build() {
+ final StaticIpConfiguration config = new StaticIpConfiguration();
+ config.ipAddress = mIpAddress;
+ config.gateway = mGateway;
+ for (InetAddress server : mDnsServers) {
+ config.dnsServers.add(server);
+ }
+ config.domains = mDomains;
+ return config;
+ }
+ }
+
+ /**
+ * Add a DNS server to this configuration.
+ */
+ public void addDnsServer(@NonNull InetAddress server) {
+ dnsServers.add(server);
+ }
+
+ /**
+ * Returns the network routes specified by this object. Will typically include a
+ * directly-connected route for the IP address's local subnet and a default route.
+ * @param iface Interface to include in the routes.
+ */
+ public @NonNull List<RouteInfo> getRoutes(@Nullable String iface) {
List<RouteInfo> routes = new ArrayList<RouteInfo>(3);
if (ipAddress != null) {
RouteInfo connectedRoute = new RouteInfo(ipAddress, null, iface);
routes.add(connectedRoute);
+ // If the default gateway is not covered by the directly-connected route, also add a
+ // host route to the gateway as well. This configuration is arguably invalid, but it
+ // used to work in K and earlier, and other OSes appear to accept it.
if (gateway != null && !connectedRoute.matches(gateway)) {
routes.add(RouteInfo.makeHostRoute(gateway, iface));
}
@@ -100,8 +218,9 @@
* contained in the LinkProperties will not be a complete picture of the link's configuration,
* because any configuration information that is obtained dynamically by the network (e.g.,
* IPv6 configuration) will not be included.
+ * @hide
*/
- public LinkProperties toLinkProperties(String iface) {
+ public @NonNull LinkProperties toLinkProperties(String iface) {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(iface);
if (ipAddress != null) {
@@ -117,6 +236,7 @@
return lp;
}
+ @Override
public String toString() {
StringBuffer str = new StringBuffer();
@@ -136,6 +256,7 @@
return str.toString();
}
+ @Override
public int hashCode() {
int result = 13;
result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode());
@@ -161,12 +282,10 @@
}
/** Implement the Parcelable interface */
- public static Creator<StaticIpConfiguration> CREATOR =
+ public static final Creator<StaticIpConfiguration> CREATOR =
new Creator<StaticIpConfiguration>() {
public StaticIpConfiguration createFromParcel(Parcel in) {
- StaticIpConfiguration s = new StaticIpConfiguration();
- readFromParcel(s, in);
- return s;
+ return readFromParcel(in);
}
public StaticIpConfiguration[] newArray(int size) {
@@ -175,29 +294,34 @@
};
/** Implement the Parcelable interface */
+ @Override
public int describeContents() {
return 0;
}
/** Implement the Parcelable interface */
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(ipAddress, flags);
- NetworkUtils.parcelInetAddress(dest, gateway, flags);
+ InetAddressUtils.parcelInetAddress(dest, gateway, flags);
dest.writeInt(dnsServers.size());
for (InetAddress dnsServer : dnsServers) {
- NetworkUtils.parcelInetAddress(dest, dnsServer, flags);
+ InetAddressUtils.parcelInetAddress(dest, dnsServer, flags);
}
dest.writeString(domains);
}
- protected static void readFromParcel(StaticIpConfiguration s, Parcel in) {
+ /** @hide */
+ public static StaticIpConfiguration readFromParcel(Parcel in) {
+ final StaticIpConfiguration s = new StaticIpConfiguration();
s.ipAddress = in.readParcelable(null);
- s.gateway = NetworkUtils.unparcelInetAddress(in);
+ s.gateway = InetAddressUtils.unparcelInetAddress(in);
s.dnsServers.clear();
int size = in.readInt();
for (int i = 0; i < size; i++) {
- s.dnsServers.add(NetworkUtils.unparcelInetAddress(in));
+ s.dnsServers.add(InetAddressUtils.unparcelInetAddress(in));
}
s.domains = in.readString();
+ return s;
}
}
diff --git a/core/java/android/net/TcpRepairWindow.java b/core/java/android/net/TcpRepairWindow.java
new file mode 100644
index 0000000..86034f0
--- /dev/null
+++ b/core/java/android/net/TcpRepairWindow.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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;
+
+/**
+ * Corresponds to C's {@code struct tcp_repair_window} from
+ * include/uapi/linux/tcp.h
+ *
+ * @hide
+ */
+public final class TcpRepairWindow {
+ public final int sndWl1;
+ public final int sndWnd;
+ public final int maxWindow;
+ public final int rcvWnd;
+ public final int rcvWup;
+ public final int rcvWndScale;
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public TcpRepairWindow(final int sndWl1, final int sndWnd, final int maxWindow,
+ final int rcvWnd, final int rcvWup, final int rcvWndScale) {
+ this.sndWl1 = sndWl1;
+ this.sndWnd = sndWnd;
+ this.maxWindow = maxWindow;
+ this.rcvWnd = rcvWnd;
+ this.rcvWup = rcvWup;
+ this.rcvWndScale = rcvWndScale;
+ }
+}
diff --git a/core/java/android/net/TcpSocketKeepalive.java b/core/java/android/net/TcpSocketKeepalive.java
new file mode 100644
index 0000000..436397e
--- /dev/null
+++ b/core/java/android/net/TcpSocketKeepalive.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.util.concurrent.Executor;
+
+/** @hide */
+final class TcpSocketKeepalive extends SocketKeepalive {
+
+ TcpSocketKeepalive(@NonNull IConnectivityManager service,
+ @NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ super(service, network, pfd, executor, callback);
+ }
+
+ /**
+ * Starts keepalives. {@code mSocket} must be a connected TCP socket.
+ *
+ * - The application must not write to or read from the socket after calling this method, until
+ * onDataReceived, onStopped, or onError are called. If it does, the keepalive will fail
+ * with {@link #ERROR_SOCKET_NOT_IDLE}, or {@code #ERROR_INVALID_SOCKET} if the socket
+ * experienced an error (as in poll(2) returned POLLERR or POLLHUP); if this happens, the data
+ * received from the socket may be invalid, and the socket can't be recovered.
+ * - If the socket has data in the send or receive buffer, then this call will fail with
+ * {@link #ERROR_SOCKET_NOT_IDLE} and can be retried after the data has been processed.
+ * An app could ensure this by using an application-layer protocol to receive acknowledgement
+ * that indicates all data has been delivered to server, e.g. HTTP 200 OK.
+ * Then the app could go into keepalive mode after reading all remaining data within the
+ * acknowledgement.
+ */
+ @Override
+ void startImpl(int intervalSec) {
+ mExecutor.execute(() -> {
+ try {
+ final FileDescriptor fd = mPfd.getFileDescriptor();
+ mService.startTcpKeepalive(mNetwork, fd, intervalSec, mCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting packet keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ @Override
+ void stopImpl() {
+ mExecutor.execute(() -> {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping packet keepalive: ", e);
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+}
diff --git a/core/java/android/net/TestNetworkInterface.java b/core/java/android/net/TestNetworkInterface.java
new file mode 100644
index 0000000..8455083
--- /dev/null
+++ b/core/java/android/net/TestNetworkInterface.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return the interface name and fd of the test interface
+ *
+ * @hide
+ */
+@TestApi
+public final class TestNetworkInterface implements Parcelable {
+ private final ParcelFileDescriptor mFileDescriptor;
+ private final String mInterfaceName;
+
+ @Override
+ public int describeContents() {
+ return (mFileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mFileDescriptor, PARCELABLE_WRITE_RETURN_VALUE);
+ out.writeString(mInterfaceName);
+ }
+
+ public TestNetworkInterface(ParcelFileDescriptor pfd, String intf) {
+ mFileDescriptor = pfd;
+ mInterfaceName = intf;
+ }
+
+ private TestNetworkInterface(Parcel in) {
+ mFileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+ mInterfaceName = in.readString();
+ }
+
+ public ParcelFileDescriptor getFileDescriptor() {
+ return mFileDescriptor;
+ }
+
+ public String getInterfaceName() {
+ return mInterfaceName;
+ }
+
+ public static final Parcelable.Creator<TestNetworkInterface> CREATOR =
+ new Parcelable.Creator<TestNetworkInterface>() {
+ public TestNetworkInterface createFromParcel(Parcel in) {
+ return new TestNetworkInterface(in);
+ }
+
+ public TestNetworkInterface[] newArray(int size) {
+ return new TestNetworkInterface[size];
+ }
+ };
+}
diff --git a/core/java/android/net/TestNetworkManager.java b/core/java/android/net/TestNetworkManager.java
new file mode 100644
index 0000000..4ac4a69
--- /dev/null
+++ b/core/java/android/net/TestNetworkManager.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Class that allows creation and management of per-app, test-only networks
+ *
+ * @hide
+ */
+@TestApi
+public class TestNetworkManager {
+ @NonNull private static final String TAG = TestNetworkManager.class.getSimpleName();
+
+ @NonNull private final ITestNetworkManager mService;
+
+ /** @hide */
+ public TestNetworkManager(@NonNull ITestNetworkManager service) {
+ mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager");
+ }
+
+ /**
+ * Teardown the capability-limited, testing-only network for a given interface
+ *
+ * @param network The test network that should be torn down
+ * @hide
+ */
+ @TestApi
+ public void teardownTestNetwork(@NonNull Network network) {
+ try {
+ mService.teardownTestNetwork(network.netId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets up a capability-limited, testing-only network for a given interface
+ *
+ * @param lp The LinkProperties for the TestNetworkService to use for this test network. Note
+ * that the interface name and link addresses will be overwritten, and the passed-in values
+ * discarded.
+ * @param isMetered Whether or not the network should be considered metered.
+ * @param binder A binder object guarding the lifecycle of this test network.
+ * @hide
+ */
+ public void setupTestNetwork(
+ @NonNull LinkProperties lp, boolean isMetered, @NonNull IBinder binder) {
+ Preconditions.checkNotNull(lp, "Invalid LinkProperties");
+ try {
+ mService.setupTestNetwork(lp.getInterfaceName(), lp, isMetered, binder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets up a capability-limited, testing-only network for a given interface
+ *
+ * @param iface the name of the interface to be used for the Network LinkProperties.
+ * @param binder A binder object guarding the lifecycle of this test network.
+ * @hide
+ */
+ @TestApi
+ public void setupTestNetwork(@NonNull String iface, @NonNull IBinder binder) {
+ try {
+ mService.setupTestNetwork(iface, null, true, binder);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Create a tun interface for testing purposes
+ *
+ * @param linkAddrs an array of LinkAddresses to assign to the TUN interface
+ * @return A ParcelFileDescriptor of the underlying TUN interface. Close this to tear down the
+ * TUN interface.
+ * @hide
+ */
+ @TestApi
+ public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) {
+ try {
+ return mService.createTunInterface(linkAddrs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Create a tap interface for testing purposes
+ *
+ * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
+ * TAP interface.
+ * @hide
+ */
+ @TestApi
+ public TestNetworkInterface createTapInterface() {
+ try {
+ return mService.createTapInterface();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+}
diff --git a/core/java/android/net/TransportInfo.java b/core/java/android/net/TransportInfo.java
new file mode 100644
index 0000000..b78d3fe
--- /dev/null
+++ b/core/java/android/net/TransportInfo.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2018 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;
+
+/**
+ * A container for transport-specific capabilities which is returned by
+ * {@link NetworkCapabilities#getTransportInfo()}. Specific networks
+ * may provide concrete implementations of this interface.
+ */
+public interface TransportInfo {
+}
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
index 3164929..a1ac960 100644
--- a/core/java/android/net/UidRange.java
+++ b/core/java/android/net/UidRange.java
@@ -21,6 +21,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Collection;
+
/**
* An inclusive range of UIDs.
*
@@ -42,10 +44,16 @@
return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
}
+ /** Returns the smallest user Id which is contained in this UidRange */
public int getStartUser() {
return start / PER_USER_RANGE;
}
+ /** Returns the largest user Id which is contained in this UidRange */
+ public int getEndUser() {
+ return stop / PER_USER_RANGE;
+ }
+
public boolean contains(int uid) {
return start <= uid && uid <= stop;
}
@@ -89,7 +97,9 @@
return start + "-" + stop;
}
- // implement the Parcelable interface
+ // Implement the Parcelable interface
+ // TODO: Consider making this class no longer parcelable, since all users are likely in the
+ // system server.
@Override
public int describeContents() {
return 0;
@@ -115,4 +125,23 @@
return new UidRange[size];
}
};
+
+ /**
+ * Returns whether any of the UidRange in the collection contains the specified uid
+ *
+ * @param ranges The collection of UidRange to check
+ * @param uid the uid in question
+ * @return {@code true} if the uid is contained within the ranges, {@code false} otherwise
+ *
+ * @see UidRange#contains(int)
+ */
+ public static boolean containsUid(Collection<UidRange> ranges, int uid) {
+ if (ranges == null) return false;
+ for (UidRange range : ranges) {
+ if (range.contains(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
new file mode 100644
index 0000000..4dd2ace
--- /dev/null
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -0,0 +1,133 @@
+/*
+ * 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 android.net.apf;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.res.Resources;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.R;
+
+/**
+ * APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible
+ * way to drop unwanted network packets to save power.
+ *
+ * See documentation at hardware/google/apf/apf.h
+ *
+ * This class is immutable.
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class ApfCapabilities implements Parcelable {
+ /**
+ * Version of APF instruction set supported for packet filtering. 0 indicates no support for
+ * packet filtering using APF programs.
+ */
+ public final int apfVersionSupported;
+
+ /**
+ * Maximum size of APF program allowed.
+ */
+ public final int maximumApfProgramSize;
+
+ /**
+ * Format of packets passed to APF filter. Should be one of ARPHRD_*
+ */
+ public final int apfPacketFormat;
+
+ public ApfCapabilities(
+ int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+ this.apfVersionSupported = apfVersionSupported;
+ this.maximumApfProgramSize = maximumApfProgramSize;
+ this.apfPacketFormat = apfPacketFormat;
+ }
+
+ private ApfCapabilities(Parcel in) {
+ apfVersionSupported = in.readInt();
+ maximumApfProgramSize = in.readInt();
+ apfPacketFormat = in.readInt();
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(apfVersionSupported);
+ dest.writeInt(maximumApfProgramSize);
+ dest.writeInt(apfPacketFormat);
+ }
+
+ public static final Creator<ApfCapabilities> CREATOR = new Creator<ApfCapabilities>() {
+ @Override
+ public ApfCapabilities createFromParcel(Parcel in) {
+ return new ApfCapabilities(in);
+ }
+
+ @Override
+ public ApfCapabilities[] newArray(int size) {
+ return new ApfCapabilities[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
+ apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ApfCapabilities)) return false;
+ final ApfCapabilities other = (ApfCapabilities) obj;
+ return apfVersionSupported == other.apfVersionSupported
+ && maximumApfProgramSize == other.maximumApfProgramSize
+ && apfPacketFormat == other.apfPacketFormat;
+ }
+
+ /**
+ * Determines whether the APF interpreter advertises support for the data buffer access opcodes
+ * LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and
+ * STDW (STore Data Word) support is present from APFv4 on.
+ *
+ * @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported.
+ */
+ public boolean hasDataAccess() {
+ return apfVersionSupported >= 4;
+ }
+
+ /**
+ * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames.
+ */
+ public static boolean getApfDrop8023Frames() {
+ return Resources.getSystem().getBoolean(R.bool.config_apfDrop802_3Frames);
+ }
+
+ /**
+ * @return An array of blacklisted EtherType, packets with EtherTypes within it will be dropped.
+ */
+ public static @NonNull int[] getApfEtherTypeBlackList() {
+ return Resources.getSystem().getIntArray(R.array.config_apfEthTypeBlackList);
+ }
+}
diff --git a/core/java/android/net/util/DnsUtils.java b/core/java/android/net/util/DnsUtils.java
new file mode 100644
index 0000000..7908353
--- /dev/null
+++ b/core/java/android/net/util/DnsUtils.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.Network;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.util.BitUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class DnsUtils {
+ private static final String TAG = "DnsUtils";
+ private static final int CHAR_BIT = 8;
+ public static final int IPV6_ADDR_SCOPE_NODELOCAL = 0x01;
+ public static final int IPV6_ADDR_SCOPE_LINKLOCAL = 0x02;
+ public static final int IPV6_ADDR_SCOPE_SITELOCAL = 0x05;
+ public static final int IPV6_ADDR_SCOPE_GLOBAL = 0x0e;
+ private static final Comparator<SortableAddress> sRfc6724Comparator = new Rfc6724Comparator();
+
+ /**
+ * Comparator to sort SortableAddress in Rfc6724 style.
+ */
+ public static class Rfc6724Comparator implements Comparator<SortableAddress> {
+ // This function matches the behaviour of _rfc6724_compare in the native resolver.
+ @Override
+ public int compare(SortableAddress span1, SortableAddress span2) {
+ // Rule 1: Avoid unusable destinations.
+ if (span1.hasSrcAddr != span2.hasSrcAddr) {
+ return span2.hasSrcAddr - span1.hasSrcAddr;
+ }
+
+ // Rule 2: Prefer matching scope.
+ if (span1.scopeMatch != span2.scopeMatch) {
+ return span2.scopeMatch - span1.scopeMatch;
+ }
+
+ // TODO: Implement rule 3: Avoid deprecated addresses.
+ // TODO: Implement rule 4: Prefer home addresses.
+
+ // Rule 5: Prefer matching label.
+ if (span1.labelMatch != span2.labelMatch) {
+ return span2.labelMatch - span1.labelMatch;
+ }
+
+ // Rule 6: Prefer higher precedence.
+ if (span1.precedence != span2.precedence) {
+ return span2.precedence - span1.precedence;
+ }
+
+ // TODO: Implement rule 7: Prefer native transport.
+
+ // Rule 8: Prefer smaller scope.
+ if (span1.scope != span2.scope) {
+ return span1.scope - span2.scope;
+ }
+
+ // Rule 9: Use longest matching prefix. IPv6 only.
+ if (span1.prefixMatchLen != span2.prefixMatchLen) {
+ return span2.prefixMatchLen - span1.prefixMatchLen;
+ }
+
+ // Rule 10: Leave the order unchanged. Collections.sort is a stable sort.
+ return 0;
+ }
+ }
+
+ /**
+ * Class used to sort with RFC 6724
+ */
+ public static class SortableAddress {
+ public final int label;
+ public final int labelMatch;
+ public final int scope;
+ public final int scopeMatch;
+ public final int precedence;
+ public final int prefixMatchLen;
+ public final int hasSrcAddr;
+ public final InetAddress address;
+
+ public SortableAddress(@NonNull InetAddress addr, @Nullable InetAddress srcAddr) {
+ address = addr;
+ hasSrcAddr = (srcAddr != null) ? 1 : 0;
+ label = findLabel(addr);
+ scope = findScope(addr);
+ precedence = findPrecedence(addr);
+ labelMatch = ((srcAddr != null) && (label == findLabel(srcAddr))) ? 1 : 0;
+ scopeMatch = ((srcAddr != null) && (scope == findScope(srcAddr))) ? 1 : 0;
+ if (isIpv6Address(addr) && isIpv6Address(srcAddr)) {
+ prefixMatchLen = compareIpv6PrefixMatchLen(srcAddr, addr);
+ } else {
+ prefixMatchLen = 0;
+ }
+ }
+ }
+
+ /**
+ * Sort the given address list in RFC6724 order.
+ * Will leave the list unchanged if an error occurs.
+ *
+ * This function matches the behaviour of _rfc6724_sort in the native resolver.
+ */
+ public static @NonNull List<InetAddress> rfc6724Sort(@Nullable Network network,
+ @NonNull List<InetAddress> answers) {
+ final ArrayList<SortableAddress> sortableAnswerList = new ArrayList<>();
+ for (InetAddress addr : answers) {
+ sortableAnswerList.add(new SortableAddress(addr, findSrcAddress(network, addr)));
+ }
+
+ Collections.sort(sortableAnswerList, sRfc6724Comparator);
+
+ final List<InetAddress> sortedAnswers = new ArrayList<>();
+ for (SortableAddress ans : sortableAnswerList) {
+ sortedAnswers.add(ans.address);
+ }
+
+ return sortedAnswers;
+ }
+
+ private static @Nullable InetAddress findSrcAddress(@Nullable Network network,
+ @NonNull InetAddress addr) {
+ final int domain;
+ if (isIpv4Address(addr)) {
+ domain = AF_INET;
+ } else if (isIpv6Address(addr)) {
+ domain = AF_INET6;
+ } else {
+ return null;
+ }
+ final FileDescriptor socket;
+ try {
+ socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "findSrcAddress:" + e.toString());
+ return null;
+ }
+ try {
+ if (network != null) network.bindSocket(socket);
+ Os.connect(socket, new InetSocketAddress(addr, 0));
+ return ((InetSocketAddress) Os.getsockname(socket)).getAddress();
+ } catch (IOException | ErrnoException e) {
+ return null;
+ } finally {
+ IoUtils.closeQuietly(socket);
+ }
+ }
+
+ /**
+ * Get the label for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ *
+ * Note that Java will return an IPv4-mapped address as an IPv4 address.
+ */
+ private static int findLabel(@NonNull InetAddress addr) {
+ if (isIpv4Address(addr)) {
+ return 4;
+ } else if (isIpv6Address(addr)) {
+ if (addr.isLoopbackAddress()) {
+ return 0;
+ } else if (isIpv6Address6To4(addr)) {
+ return 2;
+ } else if (isIpv6AddressTeredo(addr)) {
+ return 5;
+ } else if (isIpv6AddressULA(addr)) {
+ return 13;
+ } else if (((Inet6Address) addr).isIPv4CompatibleAddress()) {
+ return 3;
+ } else if (addr.isSiteLocalAddress()) {
+ return 11;
+ } else if (isIpv6Address6Bone(addr)) {
+ return 12;
+ } else {
+ // All other IPv6 addresses, including global unicast addresses.
+ return 1;
+ }
+ } else {
+ // This should never happen.
+ return 1;
+ }
+ }
+
+ private static boolean isIpv6Address(@Nullable InetAddress addr) {
+ return addr instanceof Inet6Address;
+ }
+
+ private static boolean isIpv4Address(@Nullable InetAddress addr) {
+ return addr instanceof Inet4Address;
+ }
+
+ private static boolean isIpv6Address6To4(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x20 && byteAddr[1] == 0x02;
+ }
+
+ private static boolean isIpv6AddressTeredo(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x20 && byteAddr[1] == 0x01 && byteAddr[2] == 0x00
+ && byteAddr[3] == 0x00;
+ }
+
+ private static boolean isIpv6AddressULA(@NonNull InetAddress addr) {
+ return isIpv6Address(addr) && (addr.getAddress()[0] & 0xfe) == 0xfc;
+ }
+
+ private static boolean isIpv6Address6Bone(@NonNull InetAddress addr) {
+ if (!isIpv6Address(addr)) return false;
+ final byte[] byteAddr = addr.getAddress();
+ return byteAddr[0] == 0x3f && byteAddr[1] == (byte) 0xfe;
+ }
+
+ private static int getIpv6MulticastScope(@NonNull InetAddress addr) {
+ return !isIpv6Address(addr) ? 0 : (addr.getAddress()[1] & 0x0f);
+ }
+
+ private static int findScope(@NonNull InetAddress addr) {
+ if (isIpv6Address(addr)) {
+ if (addr.isMulticastAddress()) {
+ return getIpv6MulticastScope(addr);
+ } else if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+ /**
+ * RFC 4291 section 2.5.3 says loopback is to be treated as having
+ * link-local scope.
+ */
+ return IPV6_ADDR_SCOPE_LINKLOCAL;
+ } else if (addr.isSiteLocalAddress()) {
+ return IPV6_ADDR_SCOPE_SITELOCAL;
+ } else {
+ return IPV6_ADDR_SCOPE_GLOBAL;
+ }
+ } else if (isIpv4Address(addr)) {
+ if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+ return IPV6_ADDR_SCOPE_LINKLOCAL;
+ } else {
+ /**
+ * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses
+ * and shared addresses (100.64.0.0/10), are assigned global scope.
+ */
+ return IPV6_ADDR_SCOPE_GLOBAL;
+ }
+ } else {
+ /**
+ * This should never happen.
+ * Return a scope with low priority as a last resort.
+ */
+ return IPV6_ADDR_SCOPE_NODELOCAL;
+ }
+ }
+
+ /**
+ * Get the precedence for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ *
+ * Note that Java will return an IPv4-mapped address as an IPv4 address.
+ */
+ private static int findPrecedence(@NonNull InetAddress addr) {
+ if (isIpv4Address(addr)) {
+ return 35;
+ } else if (isIpv6Address(addr)) {
+ if (addr.isLoopbackAddress()) {
+ return 50;
+ } else if (isIpv6Address6To4(addr)) {
+ return 30;
+ } else if (isIpv6AddressTeredo(addr)) {
+ return 5;
+ } else if (isIpv6AddressULA(addr)) {
+ return 3;
+ } else if (((Inet6Address) addr).isIPv4CompatibleAddress() || addr.isSiteLocalAddress()
+ || isIpv6Address6Bone(addr)) {
+ return 1;
+ } else {
+ // All other IPv6 addresses, including global unicast addresses.
+ return 40;
+ }
+ } else {
+ return 1;
+ }
+ }
+
+ /**
+ * Find number of matching initial bits between the two addresses.
+ */
+ private static int compareIpv6PrefixMatchLen(@NonNull InetAddress srcAddr,
+ @NonNull InetAddress dstAddr) {
+ final byte[] srcByte = srcAddr.getAddress();
+ final byte[] dstByte = dstAddr.getAddress();
+
+ // This should never happen.
+ if (srcByte.length != dstByte.length) return 0;
+
+ for (int i = 0; i < dstByte.length; ++i) {
+ if (srcByte[i] == dstByte[i]) {
+ continue;
+ }
+ int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]);
+ return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24); // Java ints are 32 bits
+ }
+ return dstByte.length * CHAR_BIT;
+ }
+
+ /**
+ * Check if given network has Ipv4 capability
+ * This function matches the behaviour of have_ipv4 in the native resolver.
+ */
+ public static boolean haveIpv4(@Nullable Network network) {
+ final SocketAddress addrIpv4 =
+ new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
+ return checkConnectivity(network, AF_INET, addrIpv4);
+ }
+
+ /**
+ * Check if given network has Ipv6 capability
+ * This function matches the behaviour of have_ipv6 in the native resolver.
+ */
+ public static boolean haveIpv6(@Nullable Network network) {
+ final SocketAddress addrIpv6 =
+ new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
+ return checkConnectivity(network, AF_INET6, addrIpv6);
+ }
+
+ private static boolean checkConnectivity(@Nullable Network network,
+ int domain, @NonNull SocketAddress addr) {
+ final FileDescriptor socket;
+ try {
+ socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+ } catch (ErrnoException e) {
+ return false;
+ }
+ try {
+ if (network != null) network.bindSocket(socket);
+ Os.connect(socket, addr);
+ } catch (IOException | ErrnoException e) {
+ return false;
+ } finally {
+ IoUtils.closeQuietly(socket);
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/net/util/KeepaliveUtils.java b/core/java/android/net/util/KeepaliveUtils.java
new file mode 100644
index 0000000..bfc4563
--- /dev/null
+++ b/core/java/android/net/util/KeepaliveUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.NetworkCapabilities;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
+
+import com.android.internal.R;
+
+/**
+ * Collection of utilities for socket keepalive offload.
+ *
+ * @hide
+ */
+public final class KeepaliveUtils {
+
+ public static final String TAG = "KeepaliveUtils";
+
+ public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
+ public KeepaliveDeviceConfigurationException(final String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Read supported keepalive count for each transport type from overlay resource. This should be
+ * used to create a local variable store of resource customization, and use it as the input for
+ * {@link getSupportedKeepalivesForNetworkCapabilities}.
+ *
+ * @param context The context to read resource from.
+ * @return An array of supported keepalive count for each transport type.
+ */
+ @NonNull
+ public static int[] getSupportedKeepalives(@NonNull Context context) {
+ String[] res = null;
+ try {
+ res = context.getResources().getStringArray(
+ R.array.config_networkSupportedKeepaliveCount);
+ } catch (Resources.NotFoundException unused) {
+ }
+ if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");
+
+ final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1];
+ for (final String row : res) {
+ if (TextUtils.isEmpty(row)) {
+ throw new KeepaliveDeviceConfigurationException("Empty string");
+ }
+ final String[] arr = row.split(",");
+ if (arr.length != 2) {
+ throw new KeepaliveDeviceConfigurationException("Invalid parameter length");
+ }
+
+ int transport;
+ int supported;
+ try {
+ transport = Integer.parseInt(arr[0]);
+ supported = Integer.parseInt(arr[1]);
+ } catch (NumberFormatException e) {
+ throw new KeepaliveDeviceConfigurationException("Invalid number format");
+ }
+
+ if (!NetworkCapabilities.isValidTransport(transport)) {
+ throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
+ }
+
+ if (supported < 0) {
+ throw new KeepaliveDeviceConfigurationException(
+ "Invalid supported count " + supported + " for "
+ + NetworkCapabilities.transportNameOf(transport));
+ }
+ ret[transport] = supported;
+ }
+ return ret;
+ }
+
+ /**
+ * Get supported keepalive count for the given {@link NetworkCapabilities}.
+ *
+ * @param supportedKeepalives An array of supported keepalive count for each transport type.
+ * @param nc The {@link NetworkCapabilities} of the network the socket keepalive is on.
+ *
+ * @return Supported keepalive count for the given {@link NetworkCapabilities}.
+ */
+ public static int getSupportedKeepalivesForNetworkCapabilities(
+ @NonNull int[] supportedKeepalives, @NonNull NetworkCapabilities nc) {
+ final int[] transports = nc.getTransportTypes();
+ if (transports.length == 0) return 0;
+ int supportedCount = supportedKeepalives[transports[0]];
+ // Iterate through transports and return minimum supported value.
+ for (final int transport : transports) {
+ if (supportedCount > supportedKeepalives[transport]) {
+ supportedCount = supportedKeepalives[transport];
+ }
+ }
+ return supportedCount;
+ }
+}
diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java
similarity index 100%
rename from services/net/java/android/net/util/MultinetworkPolicyTracker.java
rename to core/java/android/net/util/MultinetworkPolicyTracker.java
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 823f1cc..08aa1d9 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -16,24 +16,29 @@
#define LOG_TAG "NetUtils"
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "NetdClient.h"
-#include <utils/misc.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/Log.h>
+#include <vector>
+
#include <arpa/inet.h>
-#include <net/if.h>
#include <linux/filter.h>
#include <linux/if_arp.h>
+#include <linux/tcp.h>
+#include <net/if.h>
#include <netinet/ether.h>
#include <netinet/icmp6.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
-#include <cutils/properties.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <cutils/properties.h>
+#include <utils/misc.h>
+#include <utils/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "NetdClient.h"
#include "core_jni_helpers.h"
+#include "jni.h"
extern "C" {
int ifc_enable(const char *ifname);
@@ -44,38 +49,36 @@
namespace android {
-static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type);
-static const uint32_t kEtherHeaderLen = sizeof(ether_header);
-static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol);
-static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off);
-static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt);
-static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr);
-static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
-static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source);
-static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
-static const uint16_t kDhcpClientPort = 68;
+constexpr int MAXPACKETSIZE = 8 * 1024;
+// FrameworkListener limits the size of commands to 4096 bytes.
+constexpr int MAXCMDSIZE = 4096;
-static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
+static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
+ ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
+ if (detailMessage.get() == NULL) {
+ // Not really much we can do here. We're probably dead in the water,
+ // but let's try to stumble on...
+ env->ExceptionClear();
+ }
+ static jclass errnoExceptionClass =
+ MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
+
+ static jmethodID errnoExceptionCtor =
+ GetMethodIDOrDie(env, errnoExceptionClass,
+ "<init>", "(Ljava/lang/String;I)V");
+
+ jobject exception = env->NewObject(errnoExceptionClass,
+ errnoExceptionCtor,
+ detailMessage.get(),
+ error);
+ env->Throw(reinterpret_cast<jthrowable>(exception));
+}
+
+static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
struct sock_filter filter_code[] = {
- // Check the protocol is UDP.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 6),
-
- // Check this is not a fragment.
- BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
- BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 4, 0),
-
- // Get the IP header length.
- BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen),
-
- // Check the destination port.
- BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1),
-
- // Accept or reject.
- BPF_STMT(BPF_RET | BPF_K, 0xffff),
- BPF_STMT(BPF_RET | BPF_K, 0)
+ // Reject all.
+ BPF_STMT(BPF_RET | BPF_K, 0)
};
struct sock_fprog filter = {
sizeof(filter_code) / sizeof(filter_code[0]),
@@ -89,115 +92,16 @@
}
}
-static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd,
- jint hardwareAddressType)
+static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
- if (hardwareAddressType != ARPHRD_ETHER) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "attachRaFilter only supports ARPHRD_ETHER");
- return;
- }
-
- struct sock_filter filter_code[] = {
- // Check IPv6 Next Header is ICMPv6.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
-
- // Check ICMPv6 type is Router Advertisement.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1),
-
- // Accept or reject.
- BPF_STMT(BPF_RET | BPF_K, 0xffff),
- BPF_STMT(BPF_RET | BPF_K, 0)
- };
- struct sock_fprog filter = {
- sizeof(filter_code) / sizeof(filter_code[0]),
- filter_code,
- };
-
+ int dummy = 0;
int fd = jniGetFDFromFileDescriptor(env, javaFd);
- if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+ if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &dummy, sizeof(dummy)) != 0) {
jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+ "setsockopt(SO_DETACH_FILTER): %s", strerror(errno));
}
+
}
-
-// TODO: Move all this filter code into libnetutils.
-static void android_net_utils_attachControlPacketFilter(
- JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) {
- if (hardwareAddressType != ARPHRD_ETHER) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "attachControlPacketFilter only supports ARPHRD_ETHER");
- return;
- }
-
- // Capture all:
- // - ARPs
- // - DHCPv4 packets
- // - Router Advertisements & Solicitations
- // - Neighbor Advertisements & Solicitations
- //
- // tcpdump:
- // arp or
- // '(ip and udp port 68)' or
- // '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
- struct sock_filter filter_code[] = {
- // Load the link layer next payload field.
- BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kEtherTypeOffset),
-
- // Accept all ARP.
- // TODO: Figure out how to better filter ARPs on noisy networks.
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_ARP, 16, 0),
-
- // If IPv4:
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IP, 0, 9),
-
- // Check the protocol is UDP.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 14),
-
- // Check this is not a fragment.
- BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
- BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 12, 0),
-
- // Get the IP header length.
- BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen),
-
- // Check the source port.
- BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPSrcPortIndirectOffset),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 8, 0),
-
- // Check the destination port.
- BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 6, 7),
-
- // IPv6 ...
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 6),
- // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 4),
- // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
- BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, ND_ROUTER_SOLICIT, 0, 2),
- BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0),
-
- // Accept or reject.
- BPF_STMT(BPF_RET | BPF_K, 0xffff),
- BPF_STMT(BPF_RET | BPF_K, 0)
- };
- struct sock_fprog filter = {
- sizeof(filter_code) / sizeof(filter_code[0]),
- filter_code,
- };
-
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
- }
-}
-
static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
jint ifIndex)
{
@@ -323,6 +227,132 @@
return (jboolean) !queryUserAccess(uid, netId);
}
+static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
+{
+ if (env->GetArrayLength(addr) != len) {
+ return false;
+ }
+ env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst));
+ return true;
+}
+
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+ jstring dname, jint ns_class, jint ns_type, jint flags) {
+ const jsize javaCharsCount = env->GetStringLength(dname);
+ const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
+
+ // Only allow dname which could be simply formatted to UTF8.
+ // In native layer, res_mkquery would re-format the input char array to packet.
+ std::vector<char> queryname(byteCountUTF8 + 1, 0);
+
+ env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
+ int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+ if (fd < 0) {
+ throwErrnoException(env, "resNetworkQuery", -fd);
+ return nullptr;
+ }
+
+ return jniCreateFileDescriptor(env, fd);
+}
+
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+ jbyteArray msg, jint msgLen, jint flags) {
+ uint8_t data[MAXCMDSIZE];
+
+ checkLenAndCopy(env, msg, msgLen, data);
+ int fd = resNetworkSend(netId, data, msgLen, flags);
+
+ if (fd < 0) {
+ throwErrnoException(env, "resNetworkSend", -fd);
+ return nullptr;
+ }
+
+ return jniCreateFileDescriptor(env, fd);
+}
+
+static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ int rcode;
+ std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
+
+ int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+ jniSetFileDescriptorOfFD(env, javaFd, -1);
+ if (res < 0) {
+ throwErrnoException(env, "resNetworkResult", -res);
+ return nullptr;
+ }
+
+ jbyteArray answer = env->NewByteArray(res);
+ if (answer == nullptr) {
+ throwErrnoException(env, "resNetworkResult", ENOMEM);
+ return nullptr;
+ } else {
+ env->SetByteArrayRegion(answer, 0, res,
+ reinterpret_cast<jbyte*>(buf.data()));
+ }
+
+ jclass class_DnsResponse = env->FindClass("android/net/DnsResolver$DnsResponse");
+ jmethodID ctor = env->GetMethodID(class_DnsResponse, "<init>", "([BI)V");
+
+ return env->NewObject(class_DnsResponse, ctor, answer, rcode);
+}
+
+static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ resNetworkCancel(fd);
+ jniSetFileDescriptorOfFD(env, javaFd, -1);
+}
+
+static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
+ unsigned dnsNetId = 0;
+ if (int res = getNetworkForDns(&dnsNetId) < 0) {
+ throwErrnoException(env, "getDnsNetId", -res);
+ return nullptr;
+ }
+ bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
+
+ static jclass class_Network = MakeGlobalRefOrDie(
+ env, FindClassOrDie(env, "android/net/Network"));
+ static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V");
+ return env->NewObject(
+ class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass);
+}
+
+static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
+ if (javaFd == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return NULL;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ struct tcp_repair_window trw = {};
+ socklen_t size = sizeof(trw);
+
+ // Obtain the parameters of the TCP repair window.
+ int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size);
+ if (rc == -1) {
+ throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
+ return NULL;
+ }
+
+ struct tcp_info tcpinfo = {};
+ socklen_t tcpinfo_size = sizeof(tcp_info);
+
+ // Obtain the window scale from the tcp info structure. This contains a scale factor that
+ // should be applied to the window size.
+ rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size);
+ if (rc == -1) {
+ throwErrnoException(env, "getsockopt : TCP_INFO", errno);
+ return NULL;
+ }
+
+ jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
+ jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "<init>", "(IIIIII)V");
+
+ return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window,
+ trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale);
+}
// ----------------------------------------------------------------------------
@@ -337,10 +367,15 @@
{ "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
{ "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
- { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
- { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
- { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
+ { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
+ { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
+ { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
+ { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+ { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+ { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
+ { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
+ { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 17cece1..231c015 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -25,6 +25,8 @@
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -33,11 +35,19 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkPolicyManager.RULE_NONE;
+import static android.net.NetworkPolicyManager.uidRulesToString;
+import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired;
+import static android.os.Process.INVALID_UID;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.internal.util.Preconditions.checkNotNull;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.BroadcastOptions;
import android.app.NotificationManager;
@@ -49,45 +59,64 @@
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.net.CaptivePortal;
+import android.net.ConnectionInfo;
import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.PacketKeepalive;
+import android.net.ICaptivePortal;
import android.net.IConnectivityManager;
+import android.net.IDnsResolver;
import android.net.IIpConnectivityMetrics;
+import android.net.INetd;
import android.net.INetdEventCallback;
import android.net.INetworkManagementEventObserver;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
+import android.net.ISocketKeepaliveCallback;
+import android.net.ITetheringEventCallback;
+import android.net.InetAddresses;
+import android.net.IpMemoryStore;
+import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.MatchAllNetworkSpecifier;
+import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
+import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMisc;
+import android.net.NetworkMonitorManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
+import android.net.NetworkStackClient;
import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.NetworkWatchlistManager;
-import android.net.Proxy;
+import android.net.PrivateDnsConfigParcel;
import android.net.ProxyInfo;
import android.net.RouteInfo;
+import android.net.SocketKeepalive;
import android.net.UidRange;
import android.net.Uri;
import android.net.VpnService;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
+import android.net.netlink.InetDiagMessage;
+import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetdService;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -106,6 +135,7 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -115,8 +145,8 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -127,8 +157,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.net.LegacyVpnInfo;
-import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnInfo;
import com.android.internal.net.VpnProfile;
@@ -140,9 +170,9 @@
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.XmlUtils;
import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.AutodestructReference;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.DnsManager;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.KeepaliveTracker;
@@ -151,11 +181,10 @@
import com.android.server.connectivity.MultipathPolicyTracker;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
-import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
-import com.android.server.connectivity.PacManager;
import com.android.server.connectivity.PermissionMonitor;
+import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.connectivity.tethering.TetheringDependencies;
@@ -179,10 +208,11 @@
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -199,16 +229,28 @@
implements PendingIntent.OnFinished {
private static final String TAG = ConnectivityService.class.getSimpleName();
- public static final String DIAG_ARG = "--diag";
+ private static final String DIAG_ARG = "--diag";
public static final String SHORT_ARG = "--short";
- public static final String TETHERING_ARG = "tethering";
+ private static final String TETHERING_ARG = "tethering";
+ private static final String NETWORK_ARG = "networks";
+ private static final String REQUEST_ARG = "requests";
private static final boolean DBG = true;
- private static final boolean VDBG = false;
+ private static final boolean DDBG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
- private static final boolean LOGD_RULES = false;
private static final boolean LOGD_BLOCKED_NETWORKINFO = true;
+ /**
+ * Default URL to use for {@link #getCaptivePortalServerUrl()}. This should not be changed
+ * by OEMs for configuration purposes, as this value is overridden by
+ * Settings.Global.CAPTIVE_PORTAL_HTTP_URL.
+ * R.string.config_networkCaptivePortalServerUrl should be overridden instead for this purpose
+ * (preferably via runtime resource overlays).
+ */
+ private static final String DEFAULT_CAPTIVE_PORTAL_HTTP_URL =
+ "http://connectivitycheck.gstatic.com/generate_204";
+
// TODO: create better separation between radio types and network types
// how long to wait before switching back to a radio's default network
@@ -221,6 +263,9 @@
// connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+ // How long to dismiss network notification.
+ private static final int TIMEOUT_NOTIFICATION_DELAY_MS = 20 * 1000;
+
// Default to 30s linger time-out. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -235,13 +280,14 @@
private Tethering mTethering;
- private final PermissionMonitor mPermissionMonitor;
+ @VisibleForTesting
+ protected final PermissionMonitor mPermissionMonitor;
private KeyStore mKeyStore;
@VisibleForTesting
@GuardedBy("mVpns")
- protected final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
+ protected final SparseArray<Vpn> mVpns = new SparseArray<>();
// TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
// a direct call to LockdownVpnTracker.isEnabled().
@@ -250,25 +296,38 @@
@GuardedBy("mVpns")
private LockdownVpnTracker mLockdownTracker;
+ /**
+ * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal
+ * handler thread, they don't need a lock.
+ */
+ private SparseIntArray mUidRules = new SparseIntArray();
+ /** Flag indicating if background data is restricted. */
+ private boolean mRestrictBackground;
+
final private Context mContext;
- private int mNetworkPreference;
// 0 is full bad, 100 is full good
private int mDefaultInetConditionPublished = 0;
- private boolean mTestMode;
- private static ConnectivityService sServiceInstance;
-
- private INetworkManagementService mNetd;
+ private INetworkManagementService mNMS;
+ @VisibleForTesting
+ protected IDnsResolver mDnsResolver;
+ @VisibleForTesting
+ protected INetd mNetd;
private INetworkStatsService mStatsService;
private INetworkPolicyManager mPolicyManager;
private NetworkPolicyManagerInternal mPolicyManagerInternal;
- private IIpConnectivityMetrics mIpConnectivityMetrics;
+
+ /**
+ * TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple
+ * instances.
+ */
+ @GuardedBy("mTNSLock")
+ private TestNetworkService mTNS;
+
+ private final Object mTNSLock = new Object();
private String mCurrentTcpBufferSizes;
- private static final int ENABLED = 1;
- private static final int DISABLED = 0;
-
private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
new Class[] { AsyncChannel.class, ConnectivityService.class, NetworkAgent.class,
NetworkAgentInfo.class });
@@ -281,7 +340,7 @@
// Don't reap networks. This should be passed when some networks have not yet been
// rematched against all NetworkRequests.
DONT_REAP
- };
+ }
private enum UnneededFor {
LINGER, // Determine whether this network is unneeded and should be lingered.
@@ -289,11 +348,6 @@
}
/**
- * used internally to change our mobile data enabled flag
- */
- private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
-
- /**
* used internally to clear a wakelock when transitioning
* from one net to another. Clear happens when we get a new
* network - EVENT_EXPIRE_NET_TRANSITION_WAKELOCK happens
@@ -398,9 +452,9 @@
private static final int EVENT_PROMPT_UNVALIDATED = 29;
/**
- * used internally to (re)configure mobile data always-on settings.
+ * used internally to (re)configure always-on networks.
*/
- private static final int EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON = 30;
+ private static final int EVENT_CONFIGURE_ALWAYS_ON_NETWORKS = 30;
/**
* used to add a network listener with a pending intent
@@ -424,10 +478,76 @@
// Handle private DNS validation status updates.
private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
+ /**
+ * Used to handle onUidRulesChanged event from NetworkPolicyManagerService.
+ */
+ private static final int EVENT_UID_RULES_CHANGED = 39;
+
+ /**
+ * Used to handle onRestrictBackgroundChanged event from NetworkPolicyManagerService.
+ */
+ private static final int EVENT_DATA_SAVER_CHANGED = 40;
+
+ /**
+ * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
+ * been tested.
+ * obj = String representing URL that Internet probe was redirect to, if it was redirected.
+ * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
+ * arg2 = NetID.
+ */
+ public static final int EVENT_NETWORK_TESTED = 41;
+
+ /**
+ * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the private DNS
+ * config was resolved.
+ * obj = PrivateDnsConfig
+ * arg2 = netid
+ */
+ public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = 42;
+
+ /**
+ * Request ConnectivityService display provisioning notification.
+ * arg1 = Whether to make the notification visible.
+ * arg2 = NetID.
+ * obj = Intent to be launched when notification selected by user, null if !arg1.
+ */
+ public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
+
+ /**
+ * This event can handle dismissing notification by given network id.
+ */
+ public static final int EVENT_TIMEOUT_NOTIFICATION = 44;
+
+ /**
+ * Used to specify whether a network should be used even if connectivity is partial.
+ * arg1 = whether to accept the network if its connectivity is partial (1 for true or 0 for
+ * false)
+ * arg2 = whether to remember this choice in the future (1 for true or 0 for false)
+ * obj = network
+ */
+ private static final int EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY = 45;
+
+ /**
+ * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+ * should be shown.
+ */
+ public static final int PROVISIONING_NOTIFICATION_SHOW = 1;
+
+ /**
+ * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+ * should be hidden.
+ */
+ public static final int PROVISIONING_NOTIFICATION_HIDE = 0;
+
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
}
+ private static IDnsResolver getDnsResolver() {
+ return IDnsResolver.Stub
+ .asInterface(ServiceManager.getService("dnsresolver"));
+ }
+
/** Handler thread used for both of the handlers below. */
@VisibleForTesting
protected final HandlerThread mHandlerThread;
@@ -444,29 +564,22 @@
private int mNetTransitionWakeLockTimeout;
private final PowerManager.WakeLock mPendingIntentWakeLock;
- // track the current default http proxy - tell the world if we get a new one (real change)
- private volatile ProxyInfo mDefaultProxy = null;
- private Object mProxyLock = new Object();
- private boolean mDefaultProxyDisabled = false;
-
- // track the global proxy.
- private ProxyInfo mGlobalProxy = null;
-
- private PacManager mPacManager = null;
+ // A helper object to track the current default HTTP proxy. ConnectivityService needs to tell
+ // the world when it changes.
+ @VisibleForTesting
+ protected final ProxyTracker mProxyTracker;
final private SettingsObserver mSettingsObserver;
private UserManager mUserManager;
- NetworkConfig[] mNetConfigs;
- int mNetworksDefined;
+ private NetworkConfig[] mNetConfigs;
+ private int mNetworksDefined;
// the set of network types that can only be enabled by system/sig apps
- List mProtectedNetworks;
+ private List mProtectedNetworks;
- private DataConnectionStats mDataConnectionStats;
-
- TelephonyManager mTelephonyManager;
+ private TelephonyManager mTelephonyManager;
private KeepaliveTracker mKeepaliveTracker;
private NetworkNotificationManager mNotifier;
@@ -496,33 +609,11 @@
private long mMaxWakelockDurationMs = 0;
private long mLastWakeLockAcquireTimestamp = 0;
- // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
- private static final int MAX_VALIDATION_LOGS = 10;
- private static class ValidationLog {
- final Network mNetwork;
- final String mName;
- final ReadOnlyLocalLog mLog;
-
- ValidationLog(Network network, String name, ReadOnlyLocalLog log) {
- mNetwork = network;
- mName = name;
- mLog = log;
- }
- }
- private final ArrayDeque<ValidationLog> mValidationLogs =
- new ArrayDeque<ValidationLog>(MAX_VALIDATION_LOGS);
-
- private void addValidationLogs(ReadOnlyLocalLog log, Network network, String name) {
- synchronized (mValidationLogs) {
- while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
- mValidationLogs.removeLast();
- }
- mValidationLogs.addFirst(new ValidationLog(network, name, log));
- }
- }
-
private final IpConnectivityLog mMetricsLog;
+ @GuardedBy("mBandwidthRequests")
+ private final SparseArray<Integer> mBandwidthRequests = new SparseArray(10);
+
@VisibleForTesting
final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
@@ -546,7 +637,8 @@
* the first network for a given type changes, or if the default network
* changes.
*/
- private class LegacyTypeTracker {
+ @VisibleForTesting
+ static class LegacyTypeTracker {
private static final boolean DBG = true;
private static final boolean VDBG = false;
@@ -572,10 +664,12 @@
* - dump is thread-safe with respect to concurrent add and remove calls.
*/
private final ArrayList<NetworkAgentInfo> mTypeLists[];
+ @NonNull
+ private final ConnectivityService mService;
- public LegacyTypeTracker() {
- mTypeLists = (ArrayList<NetworkAgentInfo>[])
- new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
+ LegacyTypeTracker(@NonNull ConnectivityService service) {
+ mService = service;
+ mTypeLists = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE + 1];
}
public void addSupportedType(int type) {
@@ -583,7 +677,7 @@
throw new IllegalStateException(
"legacy list for type " + type + "already initialized");
}
- mTypeLists[type] = new ArrayList<NetworkAgentInfo>();
+ mTypeLists[type] = new ArrayList<>();
}
public boolean isTypeSupported(int type) {
@@ -624,10 +718,10 @@
}
// Send a broadcast if this is the first network of its type or if it's the default.
- final boolean isDefaultNetwork = isDefaultNetwork(nai);
+ final boolean isDefaultNetwork = mService.isDefaultNetwork(nai);
if ((list.size() == 1) || isDefaultNetwork) {
maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
- sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
+ mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
}
}
@@ -645,19 +739,18 @@
}
}
- final DetailedState state = DetailedState.DISCONNECTED;
-
if (wasFirstNetwork || wasDefault) {
- maybeLogBroadcast(nai, state, type, wasDefault);
- sendLegacyNetworkBroadcast(nai, state, type);
+ maybeLogBroadcast(nai, DetailedState.DISCONNECTED, type, wasDefault);
+ mService.sendLegacyNetworkBroadcast(nai, DetailedState.DISCONNECTED, type);
}
if (!list.isEmpty() && wasFirstNetwork) {
if (DBG) log("Other network available for type " + type +
", sending connected broadcast");
final NetworkAgentInfo replacement = list.get(0);
- maybeLogBroadcast(replacement, state, type, isDefaultNetwork(replacement));
- sendLegacyNetworkBroadcast(replacement, state, type);
+ maybeLogBroadcast(replacement, DetailedState.CONNECTED, type,
+ mService.isDefaultNetwork(replacement));
+ mService.sendLegacyNetworkBroadcast(replacement, DetailedState.CONNECTED, type);
}
}
@@ -672,7 +765,7 @@
// send out another legacy broadcast - currently only used for suspend/unsuspend
// toggle
public void update(NetworkAgentInfo nai) {
- final boolean isDefault = isDefaultNetwork(nai);
+ final boolean isDefault = mService.isDefaultNetwork(nai);
final DetailedState state = nai.networkInfo.getDetailedState();
for (int type = 0; type < mTypeLists.length; type++) {
final ArrayList<NetworkAgentInfo> list = mTypeLists[type];
@@ -680,13 +773,13 @@
final boolean isFirst = contains && (nai == list.get(0));
if (isFirst || contains && isDefault) {
maybeLogBroadcast(nai, state, type, isDefault);
- sendLegacyNetworkBroadcast(nai, state, type);
+ mService.sendLegacyNetworkBroadcast(nai, state, type);
}
}
}
private String naiToString(NetworkAgentInfo nai) {
- String name = (nai != null) ? nai.name() : "null";
+ String name = nai.name();
String state = (nai.networkInfo != null) ?
nai.networkInfo.getState() + "/" + nai.networkInfo.getDetailedState() :
"???/???";
@@ -716,7 +809,7 @@
pw.println();
}
}
- private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
+ private final LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(this);
/**
* Helper class which parses out priority arguments and dumps sections according to their
@@ -742,13 +835,14 @@
public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
- this(context, netManager, statsService, policyManager, new IpConnectivityLog());
+ this(context, netManager, statsService, policyManager,
+ getDnsResolver(), new IpConnectivityLog(), NetdService.getInstance());
}
@VisibleForTesting
protected ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
- IpConnectivityLog logger) {
+ IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd) {
if (DBG) log("ConnectivityService starting up");
mSystemProperties = getSystemProperties();
@@ -762,6 +856,12 @@
mDefaultMobileDataRequest = createDefaultInternetRequestForTransport(
NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
+ // The default WiFi request is a background request so that apps using WiFi are
+ // migrated to a better network (typically ethernet) when one comes up, instead
+ // of staying on WiFi forever.
+ mDefaultWifiRequest = createDefaultInternetRequestForTransport(
+ NetworkCapabilities.TRANSPORT_WIFI, NetworkRequest.Type.BACKGROUND_REQUEST);
+
mHandlerThread = new HandlerThread("ConnectivityServiceThread");
mHandlerThread.start();
mHandler = new InternalHandler(mHandlerThread.getLooper());
@@ -773,16 +873,22 @@
mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
mContext = checkNotNull(context, "missing Context");
- mNetd = checkNotNull(netManager, "missing INetworkManagementService");
+ mNMS = checkNotNull(netManager, "missing INetworkManagementService");
mStatsService = checkNotNull(statsService, "missing INetworkStatsService");
mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
mPolicyManagerInternal = checkNotNull(
LocalServices.getService(NetworkPolicyManagerInternal.class),
"missing NetworkPolicyManagerInternal");
+ mDnsResolver = checkNotNull(dnsresolver, "missing IDnsResolver");
+ mProxyTracker = makeProxyTracker();
+ mNetd = netd;
mKeyStore = KeyStore.getInstance();
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ // To ensure uid rules are synchronized with Network Policy, register for
+ // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
+ // reading existing policy from disk.
try {
mPolicyManager.registerListener(mPolicyListener);
} catch (RemoteException e) {
@@ -860,8 +966,7 @@
}
}
- mTestMode = mSystemProperties.get("cm.test.mode").equals("true")
- && mSystemProperties.get("ro.build.type").equals("eng");
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mTethering = makeTethering();
@@ -876,7 +981,7 @@
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(
- mUserIntentReceiver,
+ mIntentReceiver,
UserHandle.ALL,
intentFilter,
null /* broadcastPermission */,
@@ -884,9 +989,22 @@
mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.SYSTEM,
new IntentFilter(Intent.ACTION_USER_PRESENT), null, null);
+ // Listen to package add and removal events for all users.
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiverAsUser(
+ mIntentReceiver,
+ UserHandle.ALL,
+ intentFilter,
+ null /* broadcastPermission */,
+ mHandler);
+
try {
- mNetd.registerObserver(mTethering);
- mNetd.registerObserver(mDataActivityObserver);
+ mNMS.registerObserver(mTethering);
+ mNMS.registerObserver(mDataActivityObserver);
} catch (RemoteException e) {
loge("Error registering observer :" + e);
}
@@ -894,14 +1012,10 @@
mSettingsObserver = new SettingsObserver(mContext, mHandler);
registerSettingsCallbacks();
- mDataConnectionStats = new DataConnectionStats(mContext);
- mDataConnectionStats.startMonitoring();
+ final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext);
+ dataConnectionStats.startMonitoring();
- mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
-
- mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-
- mKeepaliveTracker = new KeepaliveTracker(mHandler);
+ mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager,
mContext.getSystemService(NotificationManager.class));
@@ -919,11 +1033,12 @@
mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler);
- mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties);
+ mDnsManager = new DnsManager(mContext, mDnsResolver, mSystemProperties);
registerPrivateDnsSettingsCallbacks();
}
- private Tethering makeTethering() {
+ @VisibleForTesting
+ protected Tethering makeTethering() {
// TODO: Move other elements into @Overridden getters.
final TetheringDependencies deps = new TetheringDependencies() {
@Override
@@ -935,11 +1050,16 @@
return mDefaultRequest;
}
};
- return new Tethering(mContext, mNetd, mStatsService, mPolicyManager,
+ return new Tethering(mContext, mNMS, mStatsService, mPolicyManager,
IoThread.get().getLooper(), new MockableSystemProperties(),
deps);
}
+ @VisibleForTesting
+ protected ProxyTracker makeProxyTracker() {
+ return new ProxyTracker(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
+ }
+
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
@@ -967,8 +1087,8 @@
// 2. Give FakeSettingsProvider an alternative notification mechanism and have the test use it
// by subclassing SettingsObserver.
@VisibleForTesting
- void updateMobileDataAlwaysOn() {
- mHandler.sendEmptyMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
+ void updateAlwaysOnNetworks() {
+ mHandler.sendEmptyMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
}
// See FakeSettingsProvider comment above.
@@ -977,22 +1097,31 @@
mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
- private void handleMobileDataAlwaysOn() {
+ private void handleAlwaysOnNetworkRequest(
+ NetworkRequest networkRequest, String settingName, boolean defaultValue) {
final boolean enable = toBool(Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1));
- final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
+ mContext.getContentResolver(), settingName, encodeBool(defaultValue)));
+ final boolean isEnabled = (mNetworkRequests.get(networkRequest) != null);
if (enable == isEnabled) {
return; // Nothing to do.
}
if (enable) {
handleRegisterNetworkRequest(new NetworkRequestInfo(
- null, mDefaultMobileDataRequest, new Binder()));
+ null, networkRequest, new Binder()));
} else {
- handleReleaseNetworkRequest(mDefaultMobileDataRequest, Process.SYSTEM_UID);
+ handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID,
+ /* callOnUnavailable */ false);
}
}
+ private void handleConfigureAlwaysOnNetworks() {
+ handleAlwaysOnNetworkRequest(
+ mDefaultMobileDataRequest,Settings.Global.MOBILE_DATA_ALWAYS_ON, true);
+ handleAlwaysOnNetworkRequest(mDefaultWifiRequest, Settings.Global.WIFI_ALWAYS_REQUESTED,
+ false);
+ }
+
private void registerSettingsCallbacks() {
// Watch for global HTTP proxy changes.
mSettingsObserver.observe(
@@ -1002,7 +1131,12 @@
// Watch for whether or not to keep mobile data always on.
mSettingsObserver.observe(
Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON),
- EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
+ EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
+
+ // Watch for whether or not to keep wifi always on.
+ mSettingsObserver.observe(
+ Settings.Global.getUriFor(Settings.Global.WIFI_ALWAYS_REQUESTED),
+ EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
}
private void registerPrivateDnsSettingsCallbacks() {
@@ -1031,7 +1165,7 @@
throw new IllegalStateException("No free netIds");
}
- private NetworkState getFilteredNetworkState(int networkType, int uid, boolean ignoreBlocked) {
+ private NetworkState getFilteredNetworkState(int networkType, int uid) {
if (mLegacyTypeTracker.isTypeSupported(networkType)) {
final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
final NetworkState state;
@@ -1049,14 +1183,15 @@
state = new NetworkState(info, new LinkProperties(), capabilities,
null, null, null);
}
- filterNetworkStateForUid(state, uid, ignoreBlocked);
+ filterNetworkStateForUid(state, uid, false);
return state;
} else {
return NetworkState.EMPTY;
}
}
- private NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
+ @VisibleForTesting
+ protected NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
if (network == null) {
return null;
}
@@ -1115,14 +1250,9 @@
if (ignoreBlocked) {
return false;
}
- // Networks are never blocked for system services
- // TODO: consider moving this check to NetworkPolicyManagerInternal.isUidNetworkingBlocked.
- if (isSystem(uid)) {
- return false;
- }
synchronized (mVpns) {
final Vpn vpn = mVpns.get(UserHandle.getUserId(uid));
- if (vpn != null && vpn.isBlockingUid(uid)) {
+ if (vpn != null && vpn.getLockdown() && vpn.isBlockingUid(uid)) {
return true;
}
}
@@ -1149,6 +1279,17 @@
mNetworkInfoBlockingLogs.log(action + " " + uid);
}
+ private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net,
+ boolean blocked) {
+ if (nri == null || net == null || !LOGD_BLOCKED_NETWORKINFO) {
+ return;
+ }
+ String action = blocked ? "BLOCKED" : "UNBLOCKED";
+ log(String.format("Blocked status changed to %s for %d(%d) on netId %d", blocked,
+ nri.mUid, nri.request.requestId, net.netId));
+ mNetworkInfoBlockingLogs.log(action + " " + nri.mUid);
+ }
+
/**
* Apply any relevant filters to {@link NetworkState} for the given UID. For
* example, this may mark the network as {@link DetailedState#BLOCKED} based
@@ -1254,7 +1395,7 @@
return state.networkInfo;
}
}
- final NetworkState state = getFilteredNetworkState(networkType, uid, false);
+ final NetworkState state = getFilteredNetworkState(networkType, uid);
return state.networkInfo;
}
@@ -1289,7 +1430,7 @@
public Network getNetworkForType(int networkType) {
enforceAccessPermission();
final int uid = Binder.getCallingUid();
- NetworkState state = getFilteredNetworkState(networkType, uid, false);
+ NetworkState state = getFilteredNetworkState(networkType, uid);
if (!isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, false)) {
return state.network;
}
@@ -1326,7 +1467,7 @@
// default.
enforceAccessPermission();
- HashMap<Network, NetworkCapabilities> result = new HashMap<Network, NetworkCapabilities>();
+ HashMap<Network, NetworkCapabilities> result = new HashMap<>();
NetworkAgentInfo nai = getDefaultNetwork();
NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
@@ -1433,6 +1574,9 @@
newNc.setUids(null);
newNc.setSSID(null);
}
+ if (newNc.getNetworkSpecifier() != null) {
+ newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact());
+ }
return newNc;
}
@@ -1479,8 +1623,7 @@
public boolean isActiveNetworkMetered() {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
- final NetworkCapabilities caps = getUnfilteredActiveNetworkState(uid).networkCapabilities;
+ final NetworkCapabilities caps = getNetworkCapabilities(getActiveNetwork());
if (caps != null) {
return !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
} else {
@@ -1498,6 +1641,23 @@
};
/**
+ * Ensures that the system cannot call a particular method.
+ */
+ private boolean disallowedBecauseSystemCaller() {
+ // TODO: start throwing a SecurityException when GnssLocationProvider stops calling
+ // requestRouteToHost. In Q, GnssLocationProvider is changed to not call requestRouteToHost
+ // for devices launched with Q and above. However, existing devices upgrading to Q and
+ // above must continued to be supported for few more releases.
+ if (isSystem(Binder.getCallingUid()) && SystemProperties.getInt(
+ "ro.product.first_api_level", 0) > Build.VERSION_CODES.P) {
+ log("This method exists only for app backwards compatibility"
+ + " and must not be called by system services.");
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface.
* @param networkType the type of the network over which traffic to the
@@ -1508,6 +1668,9 @@
*/
@Override
public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
+ if (disallowedBecauseSystemCaller()) {
+ return false;
+ }
enforceChangePermission();
if (mProtectedNetworks.contains(networkType)) {
enforceConnectivityInternalPermission();
@@ -1585,7 +1748,7 @@
if (DBG) log("Adding legacy route " + bestRoute +
" for UID/PID " + uid + "/" + Binder.getCallingPid());
try {
- mNetd.addLegacyRouteForNetId(netId, bestRoute, uid);
+ mNMS.addLegacyRouteForNetId(netId, bestRoute, uid);
} catch (Exception e) {
// never crash - catch them all
if (DBG) loge("Exception trying to add a route: " + e);
@@ -1609,19 +1772,42 @@
loge("Error parsing ip address in validation event");
}
}
+
+ @Override
+ public void onDnsEvent(int netId, int eventType, int returnCode, String hostname,
+ String[] ipAddresses, int ipAddressesCount, long timestamp, int uid) {
+ NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
+ // Netd event only allow registrants from system. Each NetworkMonitor thread is under
+ // the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd
+ // event callback for certain nai. e.g. cellular. Register here to pass to
+ // NetworkMonitor instead.
+ // TODO: Move the Dns Event to NetworkMonitor. NetdEventListenerService only allow one
+ // callback from each caller type. Need to re-factor NetdEventListenerService to allow
+ // multiple NetworkMonitor registrants.
+ if (nai != null && nai.satisfies(mDefaultRequest)) {
+ nai.networkMonitor().notifyDnsResponse(returnCode);
+ }
+ }
+
+ @Override
+ public void onNat64PrefixEvent(int netId, boolean added,
+ String prefixString, int prefixLength) {
+ mHandler.post(() -> handleNat64PrefixEvent(netId, added, prefixString, prefixLength));
+ }
};
@VisibleForTesting
protected void registerNetdEventCallback() {
- mIpConnectivityMetrics =
- (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
- ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
- if (mIpConnectivityMetrics == null) {
+ final IIpConnectivityMetrics ipConnectivityMetrics =
+ IIpConnectivityMetrics.Stub.asInterface(
+ ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+ if (ipConnectivityMetrics == null) {
Slog.wtf(TAG, "Missing IIpConnectivityMetrics");
+ return;
}
try {
- mIpConnectivityMetrics.addNetdEventCallback(
+ ipConnectivityMetrics.addNetdEventCallback(
INetdEventCallback.CALLBACK_CALLER_CONNECTIVITY_SERVICE,
mNetdEventCallback);
} catch (Exception e) {
@@ -1632,10 +1818,17 @@
private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() {
@Override
public void onUidRulesChanged(int uid, int uidRules) {
- // TODO: notify UID when it has requested targeted updates
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_RULES_CHANGED, uid, uidRules));
}
@Override
public void onRestrictBackgroundChanged(boolean restrictBackground) {
+ // caller is NPMS, since we only register with them
+ if (LOGD_BLOCKED_NETWORKINFO) {
+ log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")");
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_DATA_SAVER_CHANGED, restrictBackground ? 1 : 0, 0));
+
// TODO: relocate this specific callback in Tethering.
if (restrictBackground) {
log("onRestrictBackgroundChanged(true): disabling tethering");
@@ -1644,6 +1837,50 @@
}
};
+ void handleUidRulesChanged(int uid, int newRules) {
+ // skip update when we've already applied rules
+ final int oldRules = mUidRules.get(uid, RULE_NONE);
+ if (oldRules == newRules) return;
+
+ maybeNotifyNetworkBlockedForNewUidRules(uid, newRules);
+
+ if (newRules == RULE_NONE) {
+ mUidRules.delete(uid);
+ } else {
+ mUidRules.put(uid, newRules);
+ }
+ }
+
+ void handleRestrictBackgroundChanged(boolean restrictBackground) {
+ if (mRestrictBackground == restrictBackground) return;
+
+ for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ final boolean curMetered = nai.networkCapabilities.isMetered();
+ maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground,
+ restrictBackground);
+ }
+
+ mRestrictBackground = restrictBackground;
+ }
+
+ private boolean isUidNetworkingWithVpnBlocked(int uid, int uidRules, boolean isNetworkMetered,
+ boolean isBackgroundRestricted) {
+ synchronized (mVpns) {
+ final Vpn vpn = mVpns.get(UserHandle.getUserId(uid));
+ // Because the return value of this function depends on the list of UIDs the
+ // always-on VPN blocks when in lockdown mode, when the always-on VPN changes that
+ // list all state depending on the return value of this function has to be recomputed.
+ // TODO: add a trigger when the always-on VPN sets its blocked UIDs to reevaluate and
+ // send the necessary onBlockedStatusChanged callbacks.
+ if (vpn != null && vpn.getLockdown() && vpn.isBlockingUid(uid)) {
+ return true;
+ }
+ }
+
+ return mPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules,
+ isNetworkMetered, isBackgroundRestricted);
+ }
+
/**
* Require that the caller is either in the same user or has appropriate permission to interact
* across users.
@@ -1660,6 +1897,31 @@
"ConnectivityService");
}
+ private boolean checkAnyPermissionOf(String... permissions) {
+ for (String permission : permissions) {
+ if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkAnyPermissionOf(int pid, int uid, String... permissions) {
+ for (String permission : permissions) {
+ if (mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void enforceAnyPermissionOf(String... permissions) {
+ if (!checkAnyPermissionOf(permissions)) {
+ throw new SecurityException("Requires one of the following permissions: "
+ + String.join(", ", permissions) + ".");
+ }
+ }
+
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
@@ -1677,19 +1939,22 @@
}
private void enforceSettingsPermission() {
- mContext.enforceCallingOrSelfPermission(
+ enforceAnyPermissionOf(
android.Manifest.permission.NETWORK_SETTINGS,
- "ConnectivityService");
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private boolean checkSettingsPermission() {
- return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.NETWORK_SETTINGS);
+ return checkAnyPermissionOf(
+ android.Manifest.permission.NETWORK_SETTINGS,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
}
private boolean checkSettingsPermission(int pid, int uid) {
return PERMISSION_GRANTED == mContext.checkPermission(
- android.Manifest.permission.NETWORK_SETTINGS, pid, uid);
+ android.Manifest.permission.NETWORK_SETTINGS, pid, uid)
+ || PERMISSION_GRANTED == mContext.checkPermission(
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid);
}
private void enforceTetherAccessPermission() {
@@ -1699,11 +1964,37 @@
}
private void enforceConnectivityInternalPermission() {
- mContext.enforceCallingOrSelfPermission(
+ enforceAnyPermissionOf(
android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ }
+
+ private void enforceControlAlwaysOnVpnPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
"ConnectivityService");
}
+ private void enforceNetworkStackSettingsOrSetup() {
+ enforceAnyPermissionOf(
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD,
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ }
+
+ private boolean checkNetworkStackPermission() {
+ return checkAnyPermissionOf(
+ android.Manifest.permission.NETWORK_STACK,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ }
+
+ private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) {
+ return checkAnyPermissionOf(pid, uid,
+ android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+ }
+
private void enforceConnectivityRestrictedNetworksPermission() {
try {
mContext.enforceCallingOrSelfPermission(
@@ -1774,7 +2065,8 @@
private void sendStickyBroadcast(Intent intent) {
synchronized (this) {
- if (!mSystemReady) {
+ if (!mSystemReady
+ && intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
mInitialBroadcast = new Intent(intent);
}
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -1799,7 +2091,7 @@
try {
bs.noteConnectivityChanged(intent.getIntExtra(
ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE),
- ni != null ? ni.getState().toString() : "?");
+ ni.getState().toString());
} catch (RemoteException e) {
}
intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
@@ -1813,8 +2105,9 @@
}
void systemReady() {
- loadGlobalProxy();
+ mProxyTracker.loadGlobalProxy();
registerNetdEventCallback();
+ mTethering.systemReady();
synchronized (this) {
mSystemReady = true;
@@ -1823,15 +2116,13 @@
mInitialBroadcast = null;
}
}
- // load the global proxy at startup
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
// Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
// for user to unlock device too.
updateLockdownVpn();
- // Configure whether mobile data is always on.
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON));
+ // Create network requests for always-on networks.
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS));
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SYSTEM_READY));
@@ -1869,7 +2160,7 @@
if (timeout > 0 && iface != null && type != ConnectivityManager.TYPE_NONE) {
try {
- mNetd.addIdleTimer(iface, timeout, type);
+ mNMS.addIdleTimer(iface, timeout, type);
} catch (Exception e) {
// You shall not crash!
loge("Exception in setupDataActivityTracking " + e);
@@ -1887,8 +2178,8 @@
if (iface != null && (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))) {
try {
- // the call fails silently if no idletimer setup for this interface
- mNetd.removeIdleTimer(iface);
+ // the call fails silently if no idle timer setup for this interface
+ mNMS.removeIdleTimer(iface);
} catch (Exception e) {
loge("Exception in removeDataActivityTracking " + e);
}
@@ -1896,7 +2187,19 @@
}
/**
- * Reads the network specific MTU size from reources.
+ * Update data activity tracking when network state is updated.
+ */
+ private void updateDataActivityTracking(NetworkAgentInfo newNetwork,
+ NetworkAgentInfo oldNetwork) {
+ if (newNetwork != null) {
+ setupDataActivityTracking(newNetwork);
+ }
+ if (oldNetwork != null) {
+ removeDataActivityTracking(oldNetwork);
+ }
+ }
+ /**
+ * Reads the network specific MTU size from resources.
* and set it on it's iface.
*/
private void updateMtu(LinkProperties newLp, LinkProperties oldLp) {
@@ -1910,7 +2213,7 @@
if (VDBG) log("identical MTU - not setting");
return;
}
- if (LinkProperties.isValidMtu(mtu, newLp.hasGlobalIPv6Address()) == false) {
+ if (!LinkProperties.isValidMtu(mtu, newLp.hasGlobalIpv6Address())) {
if (mtu != 0) loge("Unexpected mtu value: " + mtu + ", " + iface);
return;
}
@@ -1922,14 +2225,15 @@
}
try {
- if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
- mNetd.setMtu(iface, mtu);
+ if (VDBG || DDBG) log("Setting MTU size: " + iface + ", " + mtu);
+ mNMS.setMtu(iface, mtu);
} catch (Exception e) {
Slog.e(TAG, "exception in setMtu()" + e);
}
}
- private static final String DEFAULT_TCP_BUFFER_SIZES = "4096,87380,110208,4096,16384,110208";
+ @VisibleForTesting
+ protected static final String DEFAULT_TCP_BUFFER_SIZES = "4096,87380,110208,4096,16384,110208";
private static final String DEFAULT_TCP_RWND_KEY = "net.tcp.default_init_rwnd";
// Overridden for testing purposes to avoid writing to SystemProperties.
@@ -1938,12 +2242,7 @@
return new MockableSystemProperties();
}
- private void updateTcpBufferSizes(NetworkAgentInfo nai) {
- if (isDefaultNetwork(nai) == false) {
- return;
- }
-
- String tcpBufferSizes = nai.linkProperties.getTcpBufferSizes();
+ private void updateTcpBufferSizes(String tcpBufferSizes) {
String[] values = null;
if (tcpBufferSizes != null) {
values = tcpBufferSizes.split(",");
@@ -1958,17 +2257,13 @@
if (tcpBufferSizes.equals(mCurrentTcpBufferSizes)) return;
try {
- if (VDBG) Slog.d(TAG, "Setting tx/rx TCP buffers to " + tcpBufferSizes);
+ if (VDBG || DDBG) Slog.d(TAG, "Setting tx/rx TCP buffers to " + tcpBufferSizes);
- final String prefix = "/sys/kernel/ipv4/tcp_";
- FileUtils.stringToFile(prefix + "rmem_min", values[0]);
- FileUtils.stringToFile(prefix + "rmem_def", values[1]);
- FileUtils.stringToFile(prefix + "rmem_max", values[2]);
- FileUtils.stringToFile(prefix + "wmem_min", values[3]);
- FileUtils.stringToFile(prefix + "wmem_def", values[4]);
- FileUtils.stringToFile(prefix + "wmem_max", values[5]);
+ String rmemValues = String.join(" ", values[0], values[1], values[2]);
+ String wmemValues = String.join(" ", values[3], values[4], values[5]);
+ mNetd.setTcpRWmemorySize(rmemValues, wmemValues);
mCurrentTcpBufferSizes = tcpBufferSizes;
- } catch (IOException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
loge("Can't set TCP buffer sizes:" + e);
}
@@ -2005,7 +2300,7 @@
private void dumpNetworkDiagnostics(IndentingPrintWriter pw) {
final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
final long DIAG_TIME_MS = 5000;
- for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ for (NetworkAgentInfo nai : networksSortedById()) {
// Start gathering diagnostic information.
netDiags.add(new NetworkDiagnostics(
nai.network,
@@ -2036,6 +2331,12 @@
} else if (ArrayUtils.contains(args, TETHERING_ARG)) {
mTethering.dump(fd, pw, args);
return;
+ } else if (ArrayUtils.contains(args, NETWORK_ARG)) {
+ dumpNetworks(pw);
+ return;
+ } else if (ArrayUtils.contains(args, REQUEST_ARG)) {
+ dumpNetworkRequests(pw);
+ return;
}
pw.print("NetworkFactories for:");
@@ -2056,37 +2357,38 @@
pw.println("Current Networks:");
pw.increaseIndent();
- for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- pw.println(nai.toString());
- pw.increaseIndent();
- pw.println(String.format(
- "Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d",
- nai.numForegroundNetworkRequests(),
- nai.numNetworkRequests() - nai.numRequestNetworkRequests(),
- nai.numBackgroundNetworkRequests(),
- nai.numNetworkRequests()));
- pw.increaseIndent();
- for (int i = 0; i < nai.numNetworkRequests(); i++) {
- pw.println(nai.requestAt(i).toString());
- }
- pw.decreaseIndent();
- pw.println("Lingered:");
- pw.increaseIndent();
- nai.dumpLingerTimers(pw);
- pw.decreaseIndent();
- pw.decreaseIndent();
- }
+ dumpNetworks(pw);
pw.decreaseIndent();
pw.println();
- pw.println("Network Requests:");
+ pw.print("Restrict background: ");
+ pw.println(mRestrictBackground);
+ pw.println();
+
+ pw.println("Status for known UIDs:");
pw.increaseIndent();
- for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- pw.println(nri.toString());
+ final int size = mUidRules.size();
+ for (int i = 0; i < size; i++) {
+ // Don't crash if the array is modified while dumping in bugreports.
+ try {
+ final int uid = mUidRules.keyAt(i);
+ final int uidRules = mUidRules.get(uid, RULE_NONE);
+ pw.println("UID=" + uid + " rules=" + uidRulesToString(uidRules));
+ } catch (ArrayIndexOutOfBoundsException e) {
+ pw.println(" ArrayIndexOutOfBoundsException");
+ } catch (ConcurrentModificationException e) {
+ pw.println(" ConcurrentModificationException");
+ }
}
pw.println();
pw.decreaseIndent();
+ pw.println("Network Requests:");
+ pw.increaseIndent();
+ dumpNetworkRequests(pw);
+ pw.decreaseIndent();
+ pw.println();
+
mLegacyTypeTracker.dump(pw);
pw.println();
@@ -2103,17 +2405,6 @@
if (ArrayUtils.contains(args, SHORT_ARG) == false) {
pw.println();
- synchronized (mValidationLogs) {
- pw.println("mValidationLogs (most recent first):");
- for (ValidationLog p : mValidationLogs) {
- pw.println(p.mNetwork + " - " + p.mName);
- pw.increaseIndent();
- p.mLog.dump(fd, pw, args);
- pw.decreaseIndent();
- }
- }
-
- pw.println();
pw.println("mNetworkRequestInfoLogs (most recent first):");
pw.increaseIndent();
mNetworkRequestInfoLogs.reverseDump(fd, pw, args);
@@ -2137,8 +2428,81 @@
pw.println("currently holding WakeLock for: " + (duration / 1000) + "s");
}
mWakelockLogs.reverseDump(fd, pw, args);
+
+ pw.println();
+ pw.println("bandwidth update requests (by uid):");
+ pw.increaseIndent();
+ synchronized (mBandwidthRequests) {
+ for (int i = 0; i < mBandwidthRequests.size(); i++) {
+ pw.println("[" + mBandwidthRequests.keyAt(i)
+ + "]: " + mBandwidthRequests.valueAt(i));
+ }
+ }
+ pw.decreaseIndent();
+
pw.decreaseIndent();
}
+
+ pw.println();
+ pw.println("NetworkStackClient logs:");
+ pw.increaseIndent();
+ NetworkStackClient.getInstance().dump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Permission Monitor:");
+ pw.increaseIndent();
+ mPermissionMonitor.dump(pw);
+ pw.decreaseIndent();
+ }
+
+ private void dumpNetworks(IndentingPrintWriter pw) {
+ for (NetworkAgentInfo nai : networksSortedById()) {
+ pw.println(nai.toString());
+ pw.increaseIndent();
+ pw.println(String.format(
+ "Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d",
+ nai.numForegroundNetworkRequests(),
+ nai.numNetworkRequests() - nai.numRequestNetworkRequests(),
+ nai.numBackgroundNetworkRequests(),
+ nai.numNetworkRequests()));
+ pw.increaseIndent();
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ pw.println(nai.requestAt(i).toString());
+ }
+ pw.decreaseIndent();
+ pw.println("Lingered:");
+ pw.increaseIndent();
+ nai.dumpLingerTimers(pw);
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+ }
+
+ private void dumpNetworkRequests(IndentingPrintWriter pw) {
+ for (NetworkRequestInfo nri : requestsSortedById()) {
+ pw.println(nri.toString());
+ }
+ }
+
+ /**
+ * Return an array of all current NetworkAgentInfos sorted by network id.
+ */
+ private NetworkAgentInfo[] networksSortedById() {
+ NetworkAgentInfo[] networks = new NetworkAgentInfo[0];
+ networks = mNetworkAgentInfos.values().toArray(networks);
+ Arrays.sort(networks, Comparator.comparingInt(nai -> nai.network.netId));
+ return networks;
+ }
+
+ /**
+ * Return an array of all current NetworkRequest sorted by request id.
+ */
+ private NetworkRequestInfo[] requestsSortedById() {
+ NetworkRequestInfo[] requests = new NetworkRequestInfo[0];
+ requests = mNetworkRequests.values().toArray(requests);
+ Arrays.sort(requests, Comparator.comparingInt(nri -> nri.request.requestId));
+ return requests;
}
private boolean isLiveNetworkAgent(NetworkAgentInfo nai, int what) {
@@ -2191,9 +2555,7 @@
switch (msg.what) {
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
final NetworkCapabilities networkCapabilities = (NetworkCapabilities) msg.obj;
- if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ||
- networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) ||
- networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) {
+ if (networkCapabilities.hasConnectivityManagedCapability()) {
Slog.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
}
updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
@@ -2209,20 +2571,27 @@
break;
}
case NetworkAgent.EVENT_NETWORK_SCORE_CHANGED: {
- Integer score = (Integer) msg.obj;
- if (score != null) updateNetworkScore(nai, score.intValue());
+ updateNetworkScore(nai, msg.arg1);
break;
}
case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
- if (nai.everConnected && !nai.networkMisc.explicitlySelected) {
- loge("ERROR: already-connected network explicitly selected.");
+ if (nai.everConnected) {
+ loge("ERROR: cannot call explicitlySelected on already-connected network");
}
- nai.networkMisc.explicitlySelected = true;
- nai.networkMisc.acceptUnvalidated = (boolean) msg.obj;
+ nai.networkMisc.explicitlySelected = toBool(msg.arg1);
+ nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
+ // Mark the network as temporarily accepting partial connectivity so that it
+ // will be validated (and possibly become default) even if it only provides
+ // partial internet access. Note that if user connects to partial connectivity
+ // and choose "don't ask again", then wifi disconnected by some reasons(maybe
+ // out of wifi coverage) and if the same wifi is available again, the device
+ // will auto connect to this wifi even though the wifi has "no internet".
+ // TODO: Evaluate using a separate setting in IpMemoryStore.
+ nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2);
break;
}
- case NetworkAgent.EVENT_PACKET_KEEPALIVE: {
- mKeepaliveTracker.handleEventPacketKeepalive(nai, msg);
+ case NetworkAgent.EVENT_SOCKET_KEEPALIVE: {
+ mKeepaliveTracker.handleEventSocketKeepalive(nai, msg);
break;
}
}
@@ -2232,13 +2601,25 @@
switch (msg.what) {
default:
return false;
- case NetworkMonitor.EVENT_NETWORK_TESTED: {
+ case EVENT_NETWORK_TESTED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
- final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+ final boolean wasPartial = nai.partialConnectivity;
+ nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
+ final boolean partialConnectivityChanged =
+ (wasPartial != nai.partialConnectivity);
+
+ final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0);
final boolean wasValidated = nai.lastValidated;
final boolean wasDefault = isDefaultNetwork(nai);
+ // Only show a connected notification if the network is pending validation
+ // after the captive portal app was open, and it has now validated.
+ if (nai.captivePortalValidationPending && valid) {
+ // User is now logged in, network validated.
+ nai.captivePortalValidationPending = false;
+ showNetworkNotification(nai, NotificationType.LOGGED_IN);
+ }
final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
@@ -2259,22 +2640,46 @@
updateCapabilities(oldScore, nai, nai.networkCapabilities);
// If score has changed, rebroadcast to NetworkFactories. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
- if (valid) handleFreshlyValidatedNetwork(nai);
+ if (valid) {
+ handleFreshlyValidatedNetwork(nai);
+ // Clear NO_INTERNET, PARTIAL_CONNECTIVITY and LOST_INTERNET
+ // notifications if network becomes valid.
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.NO_INTERNET);
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.LOST_INTERNET);
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.PARTIAL_CONNECTIVITY);
+ }
+ } else if (partialConnectivityChanged) {
+ updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
}
updateInetCondition(nai);
// Let the NetworkAgent know the state of its network
Bundle redirectUrlBundle = new Bundle();
redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
+ // TODO: Evaluate to update partial connectivity to status to NetworkAgent.
nai.asyncChannel.sendMessage(
NetworkAgent.CMD_REPORT_NETWORK_STATUS,
(valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
0, redirectUrlBundle);
+
+ // If NetworkMonitor detects partial connectivity before
+ // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
+ // immediately. Re-notify partial connectivity silently if no internet
+ // notification already there.
+ if (!wasPartial && nai.partialConnectivity) {
+ // Remove delayed message if there is a pending message.
+ mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
+ handlePromptUnvalidated(nai.network);
+ }
+
if (wasValidated && !nai.lastValidated) {
handleNetworkUnvalidated(nai);
}
break;
}
- case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
+ case EVENT_PROVISIONING_NOTIFICATION: {
final int netId = msg.arg2;
final boolean visible = toBool(msg.arg1);
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
@@ -2294,7 +2699,10 @@
updateCapabilities(oldScore, nai, nai.networkCapabilities);
}
if (!visible) {
- mNotifier.clearNotification(netId);
+ // Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
+ // notifications belong to the same network may be cleared unexpectedly.
+ mNotifier.clearNotification(netId, NotificationType.SIGN_IN);
+ mNotifier.clearNotification(netId, NotificationType.NETWORK_SWITCH);
} else {
if (nai == null) {
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
@@ -2307,7 +2715,7 @@
}
break;
}
- case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
+ case EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
@@ -2339,19 +2747,90 @@
return true;
}
+ private boolean maybeHandleNetworkFactoryMessage(Message msg) {
+ switch (msg.what) {
+ default:
+ return false;
+ case android.net.NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: {
+ handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid,
+ /* callOnUnavailable */ true);
+ break;
+ }
+ }
+ return true;
+ }
+
@Override
public void handleMessage(Message msg) {
- if (!maybeHandleAsyncChannelMessage(msg) &&
- !maybeHandleNetworkMonitorMessage(msg) &&
- !maybeHandleNetworkAgentInfoMessage(msg)) {
+ if (!maybeHandleAsyncChannelMessage(msg)
+ && !maybeHandleNetworkMonitorMessage(msg)
+ && !maybeHandleNetworkAgentInfoMessage(msg)
+ && !maybeHandleNetworkFactoryMessage(msg)) {
maybeHandleNetworkAgentMessage(msg);
}
}
}
- private boolean networkRequiresValidation(NetworkAgentInfo nai) {
- return NetworkMonitor.isValidationRequired(
- mDefaultRequest.networkCapabilities, nai.networkCapabilities);
+ private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub {
+ private final int mNetId;
+ private final AutodestructReference<NetworkAgentInfo> mNai;
+
+ private NetworkMonitorCallbacks(NetworkAgentInfo nai) {
+ mNetId = nai.network.netId;
+ mNai = new AutodestructReference(nai);
+ }
+
+ @Override
+ public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT,
+ new Pair<>(mNai.getAndDestroy(), networkMonitor)));
+ }
+
+ @Override
+ public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
+ testResult, mNetId, redirectUrl));
+ }
+
+ @Override
+ public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PRIVATE_DNS_CONFIG_RESOLVED,
+ 0, mNetId, PrivateDnsConfig.fromParcel(config)));
+ }
+
+ @Override
+ public void showProvisioningNotification(String action, String packageName) {
+ final Intent intent = new Intent(action);
+ intent.setPackage(packageName);
+
+ final PendingIntent pendingIntent;
+ // Only the system server can register notifications with package "android"
+ final long token = Binder.clearCallingIdentity();
+ try {
+ pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW,
+ mNetId, pendingIntent));
+ }
+
+ @Override
+ public void hideProvisioningNotification() {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE, mNetId));
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return this.VERSION;
+ }
+ }
+
+ private boolean networkRequiresPrivateDnsValidation(NetworkAgentInfo nai) {
+ return isPrivateDnsValidationRequired(nai.networkCapabilities);
}
private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
@@ -2369,7 +2848,7 @@
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
handlePerNetworkPrivateDnsConfig(nai, cfg);
- if (networkRequiresValidation(nai)) {
+ if (networkRequiresPrivateDnsValidation(nai)) {
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
}
@@ -2378,12 +2857,12 @@
private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
// Private DNS only ever applies to networks that might provide
// Internet access and therefore also require validation.
- if (!networkRequiresValidation(nai)) return;
+ if (!networkRequiresPrivateDnsValidation(nai)) return;
- // Notify the NetworkMonitor thread in case it needs to cancel or
+ // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
// schedule DNS resolutions. If a DNS resolution is required the
// result will be sent back to us.
- nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg);
+ nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
// With Private DNS bypass support, we can proceed to update the
// Private DNS config immediately, even if we're in strict mode
@@ -2405,6 +2884,29 @@
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
+ private void handleNat64PrefixEvent(int netId, boolean added, String prefixString,
+ int prefixLength) {
+ NetworkAgentInfo nai = mNetworkForNetId.get(netId);
+ if (nai == null) return;
+
+ log(String.format("NAT64 prefix %s on netId %d: %s/%d",
+ (added ? "added" : "removed"), netId, prefixString, prefixLength));
+
+ IpPrefix prefix = null;
+ if (added) {
+ try {
+ prefix = new IpPrefix(InetAddresses.parseNumericAddress(prefixString),
+ prefixLength);
+ } catch (IllegalArgumentException e) {
+ loge("Invalid NAT64 prefix " + prefixString + "/" + prefixLength);
+ return;
+ }
+ }
+
+ nai.clatd.setNat64Prefix(prefix);
+ handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
+ }
+
private void updateLingerState(NetworkAgentInfo nai, long now) {
// 1. Update the linger timer. If it's changed, reschedule or cancel the alarm.
// 2. If the network was lingering and there are now requests, unlinger it.
@@ -2429,12 +2931,24 @@
if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (VDBG) log("NetworkFactory connected");
+ // Finish setting up the full connection
+ mNetworkFactoryInfos.get(msg.replyTo).asyncChannel.sendMessage(
+ AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
// A network factory has connected. Send it all current NetworkRequests.
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.request.isListen()) continue;
NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
- ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
- (nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
+ final int score;
+ final int serial;
+ if (nai != null) {
+ score = nai.getCurrentScore();
+ serial = nai.factorySerialNumber;
+ } else {
+ score = 0;
+ serial = NetworkFactory.SerialNumber.NONE;
+ }
+ ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, serial,
+ nri.request);
}
} else {
loge("Error connecting NetworkFactory");
@@ -2482,6 +2996,8 @@
if (DBG) {
log(nai.name() + " got DISCONNECTED, was satisfying " + nai.numNetworkRequests());
}
+ // Clear all notifications of this network.
+ mNotifier.clearNotification(nai.network.netId);
// A network agent has disconnected.
// TODO - if we move the logic to the network agent (have them disconnect
// because they lost all their requests or because their score isn't good)
@@ -2507,15 +3023,14 @@
// sending all CALLBACK_LOST messages (for requests, not listens) at the end
// of rematchAllNetworksAndRequests
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
- mKeepaliveTracker.handleStopAllKeepalives(nai,
- ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
+ mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK);
for (String iface : nai.linkProperties.getAllInterfaceNames()) {
// Disable wakeup packet monitoring for each interface.
wakeupModifyInterface(iface, nai.networkCapabilities, false);
}
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
+ nai.networkMonitor().notifyNetworkDisconnected();
mNetworkAgentInfos.remove(nai.messenger);
- nai.maybeStopClat();
+ nai.clatd.update();
synchronized (mNetworkForNetId) {
// Remove the NetworkAgent, but don't mark the netId as
// available until we've told netd to delete it below.
@@ -2527,12 +3042,12 @@
NetworkAgentInfo currentNetwork = getNetworkForRequest(request.requestId);
if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
clearNetworkForRequest(request.requestId);
- sendUpdatedScoreToFactories(request, 0);
+ sendUpdatedScoreToFactories(request, null);
}
}
nai.clearLingerState();
if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
- removeDataActivityTracking(nai);
+ updateDataActivityTracking(null /* newNetwork */, nai);
notifyLockdownVpn(nai);
ensureNetworkTransitionWakelock(nai.name());
}
@@ -2551,11 +3066,7 @@
// fallback network the default or requested a new network from the
// NetworkFactories, so network traffic isn't interrupted for an unnecessarily
// long time.
- try {
- mNetd.removeNetwork(nai.network.netId);
- } catch (Exception e) {
- loge("Exception removing network: " + e);
- }
+ destroyNativeNetwork(nai);
mDnsManager.removeNetwork(nai.network);
}
synchronized (mNetworkForNetId) {
@@ -2563,6 +3074,35 @@
}
}
+ private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
+ try {
+ // This should never fail. Specifying an already in use NetID will cause failure.
+ if (networkAgent.isVPN()) {
+ mNetd.networkCreateVpn(networkAgent.network.netId,
+ (networkAgent.networkMisc == null
+ || !networkAgent.networkMisc.allowBypass));
+ } else {
+ mNetd.networkCreatePhysical(networkAgent.network.netId,
+ getNetworkPermission(networkAgent.networkCapabilities));
+ }
+ mDnsResolver.createNetworkCache(networkAgent.network.netId);
+ return true;
+ } catch (RemoteException | ServiceSpecificException e) {
+ loge("Error creating network " + networkAgent.network.netId + ": "
+ + e.getMessage());
+ return false;
+ }
+ }
+
+ private void destroyNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
+ try {
+ mNetd.networkDestroy(networkAgent.network.netId);
+ mDnsResolver.destroyNetworkCache(networkAgent.network.netId);
+ } catch (RemoteException | ServiceSpecificException e) {
+ loge("Exception destroying network: " + e);
+ }
+ }
+
// If this method proves to be too slow then we can maintain a separate
// pendingIntent => NetworkRequestInfo map.
// This method assumes that every non-null PendingIntent maps to exactly 1 NetworkRequestInfo.
@@ -2585,7 +3125,8 @@
if (existingRequest != null) { // remove the existing request.
if (DBG) log("Replacing " + existingRequest.request + " with "
+ nri.request + " because their intents matched.");
- handleReleaseNetworkRequest(existingRequest.request, getCallingUid());
+ handleReleaseNetworkRequest(existingRequest.request, getCallingUid(),
+ /* callOnUnavailable */ false);
}
handleRegisterNetworkRequest(nri);
}
@@ -2603,7 +3144,7 @@
}
rematchAllNetworksAndRequests(null, 0);
if (nri.request.isRequest() && getNetworkForRequest(nri.request.requestId) == null) {
- sendUpdatedScoreToFactories(nri.request, 0);
+ sendUpdatedScoreToFactories(nri.request, null);
}
}
@@ -2611,7 +3152,7 @@
int callingUid) {
NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
if (nri != null) {
- handleReleaseNetworkRequest(nri.request, callingUid);
+ handleReleaseNetworkRequest(nri.request, callingUid, /* callOnUnavailable */ false);
}
}
@@ -2694,7 +3235,8 @@
callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
}
- private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
+ private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid,
+ boolean callOnUnavailable) {
final NetworkRequestInfo nri =
getNriForAppRequest(request, callingUid, "release NetworkRequest");
if (nri == null) {
@@ -2704,6 +3246,9 @@
log("releasing " + nri.request + " (release request)");
}
handleRemoveNetworkRequest(nri);
+ if (callOnUnavailable) {
+ callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+ }
}
private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
@@ -2730,7 +3275,7 @@
if (nai != null) {
boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
nai.removeRequest(nri.request.requestId);
- if (VDBG) {
+ if (VDBG || DDBG) {
log(" Removing from current network " + nai.name() +
", leaving " + nai.numNetworkRequests() + " requests.");
}
@@ -2750,20 +3295,6 @@
}
}
- // TODO: remove this code once we know that the Slog.wtf is never hit.
- //
- // Find all networks that are satisfying this request and remove the request
- // from their request lists.
- // TODO - it's my understanding that for a request there is only a single
- // network satisfying it, so this loop is wasteful
- for (NetworkAgentInfo otherNai : mNetworkAgentInfos.values()) {
- if (otherNai.isSatisfyingRequest(nri.request.requestId) && otherNai != nai) {
- Slog.wtf(TAG, "Request " + nri.request + " satisfied by " +
- otherNai.name() + ", but mNetworkAgentInfos says " +
- (nai != null ? nai.name() : "null"));
- }
- }
-
// Maintain the illusion. When this request arrived, we might have pretended
// that a network connected to serve it, even though the network was already
// connected. Now that this request has gone away, we might have to pretend
@@ -2808,14 +3339,21 @@
@Override
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
- enforceConnectivityInternalPermission();
+ enforceNetworkStackSettingsOrSetup();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
encodeBool(accept), encodeBool(always), network));
}
@Override
+ public void setAcceptPartialConnectivity(Network network, boolean accept, boolean always) {
+ enforceNetworkStackSettingsOrSetup();
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY,
+ encodeBool(accept), encodeBool(always), network));
+ }
+
+ @Override
public void setAvoidUnvalidated(Network network) {
- enforceConnectivityInternalPermission();
+ enforceNetworkStackSettingsOrSetup();
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_AVOID_UNVALIDATED, network));
}
@@ -2841,6 +3379,10 @@
if (accept != nai.networkMisc.acceptUnvalidated) {
int oldScore = nai.getCurrentScore();
nai.networkMisc.acceptUnvalidated = accept;
+ // If network becomes partial connectivity and user already accepted to use this
+ // network, we should respect the user's option and don't need to popup the
+ // PARTIAL_CONNECTIVITY notification to user again.
+ nai.networkMisc.acceptPartialConnectivity = accept;
rematchAllNetworksAndRequests(nai, oldScore);
sendUpdatedScoreToFactories(nai);
}
@@ -2853,12 +3395,56 @@
if (!accept) {
// Tell the NetworkAgent to not automatically reconnect to the network.
nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
- // Teardown the nework.
+ // Teardown the network.
teardownUnneededNetwork(nai);
}
}
+ private void handleSetAcceptPartialConnectivity(Network network, boolean accept,
+ boolean always) {
+ if (DBG) {
+ log("handleSetAcceptPartialConnectivity network=" + network + " accept=" + accept
+ + " always=" + always);
+ }
+
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) {
+ // Nothing to do.
+ return;
+ }
+
+ if (nai.lastValidated) {
+ // The network validated while the dialog box was up. Take no action.
+ return;
+ }
+
+ if (accept != nai.networkMisc.acceptPartialConnectivity) {
+ nai.networkMisc.acceptPartialConnectivity = accept;
+ }
+
+ // TODO: Use the current design or save the user choice into IpMemoryStore.
+ if (always) {
+ nai.asyncChannel.sendMessage(
+ NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, encodeBool(accept));
+ }
+
+ if (!accept) {
+ // Tell the NetworkAgent to not automatically reconnect to the network.
+ nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
+ // Tear down the network.
+ teardownUnneededNetwork(nai);
+ } else {
+ // Inform NetworkMonitor that partial connectivity is acceptable. This will likely
+ // result in a partial connectivity result which will be processed by
+ // maybeHandleNetworkMonitorMessage.
+ //
+ // TODO: NetworkMonitor does not refer to the "never ask again" bit. The bit is stored
+ // per network. Therefore, NetworkMonitor may still do https probe.
+ nai.networkMonitor().setAcceptPartialConnectivity();
+ }
+ }
+
private void handleSetAvoidUnvalidated(Network network) {
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null || nai.lastValidated) {
@@ -2887,14 +3473,87 @@
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
if (nai == null) return;
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+ nai.networkMonitor().launchCaptivePortalApp();
});
}
+ /**
+ * NetworkStack endpoint to start the captive portal app. The NetworkStack needs to use this
+ * endpoint as it does not have INTERACT_ACROSS_USERS_FULL itself.
+ * @param network Network on which the captive portal was detected.
+ * @param appExtras Bundle to use as intent extras for the captive portal application.
+ * Must be treated as opaque to avoid preventing the captive portal app to
+ * update its arguments.
+ */
+ @Override
+ public void startCaptivePortalAppInternal(Network network, Bundle appExtras) {
+ mContext.enforceCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ "ConnectivityService");
+
+ final Intent appIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
+ appIntent.putExtras(appExtras);
+ appIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
+ new CaptivePortal(new CaptivePortalImpl(network).asBinder()));
+ appIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // This runs on a random binder thread, but getNetworkAgentInfoForNetwork is thread-safe,
+ // and captivePortalValidationPending is volatile.
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai != null) {
+ nai.captivePortalValidationPending = true;
+ }
+ Binder.withCleanCallingIdentity(() ->
+ mContext.startActivityAsUser(appIntent, UserHandle.CURRENT));
+ }
+
+ private class CaptivePortalImpl extends ICaptivePortal.Stub {
+ private final Network mNetwork;
+
+ private CaptivePortalImpl(Network network) {
+ mNetwork = network;
+ }
+
+ @Override
+ public void appResponse(final int response) {
+ if (response == CaptivePortal.APP_RETURN_WANTED_AS_IS) {
+ enforceSettingsPermission();
+ }
+
+ // getNetworkAgentInfoForNetwork is thread-safe
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork);
+ if (nai == null) return;
+
+ // nai.networkMonitor() is thread-safe
+ final NetworkMonitorManager nm = nai.networkMonitor();
+ if (nm == null) return;
+ nm.notifyCaptivePortalAppFinished(response);
+ }
+
+ @Override
+ public void logEvent(int eventId, String packageName) {
+ enforceSettingsPermission();
+
+ new MetricsLogger().action(eventId, packageName);
+ }
+ }
+
public boolean avoidBadWifi() {
return mMultinetworkPolicyTracker.getAvoidBadWifi();
}
+ /**
+ * Return whether the device should maintain continuous, working connectivity by switching away
+ * from WiFi networks having no connectivity.
+ * @see MultinetworkPolicyTracker#getAvoidBadWifi()
+ */
+ public boolean shouldAvoidBadWifi() {
+ if (!checkNetworkStackPermission()) {
+ throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
+ }
+ return avoidBadWifi();
+ }
+
+
private void rematchForAvoidBadWifiUpdate() {
rematchAllNetworksAndRequests(null, 0);
for (NetworkAgentInfo nai: mNetworkAgentInfos.values()) {
@@ -2932,7 +3591,7 @@
pw.println("User setting: " + description);
pw.println("Network overrides:");
pw.increaseIndent();
- for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ for (NetworkAgentInfo nai : networksSortedById()) {
if (nai.avoidUnvalidated) {
pw.println(nai.name());
}
@@ -2941,14 +3600,33 @@
pw.decreaseIndent();
}
- private void showValidationNotification(NetworkAgentInfo nai, NotificationType type) {
+ private void showNetworkNotification(NetworkAgentInfo nai, NotificationType type) {
final String action;
+ final boolean highPriority;
switch (type) {
+ case LOGGED_IN:
+ action = Settings.ACTION_WIFI_SETTINGS;
+ mHandler.removeMessages(EVENT_TIMEOUT_NOTIFICATION);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NOTIFICATION,
+ nai.network.netId, 0), TIMEOUT_NOTIFICATION_DELAY_MS);
+ // High priority because it is a direct result of the user logging in to a portal.
+ highPriority = true;
+ break;
case NO_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
+ // High priority because it is only displayed for explicitly selected networks.
+ highPriority = true;
break;
case LOST_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION;
+ // High priority because it could help the user avoid unexpected data usage.
+ highPriority = true;
+ break;
+ case PARTIAL_CONNECTIVITY:
+ action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
+ // Don't bother the user with a high-priority notification if the network was not
+ // explicitly selected by the user.
+ highPriority = nai.networkMisc.explicitlySelected;
break;
default:
Slog.wtf(TAG, "Unknown notification type " + type);
@@ -2956,37 +3634,79 @@
}
Intent intent = new Intent(action);
- intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName("com.android.settings",
- "com.android.settings.wifi.WifiNoInternetDialog");
+ if (type != NotificationType.LOGGED_IN) {
+ intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.android.settings",
+ "com.android.settings.wifi.WifiNoInternetDialog");
+ }
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
- mNotifier.showNotification(nai.network.netId, type, nai, null, pendingIntent, true);
+
+ mNotifier.showNotification(nai.network.netId, type, nai, null, pendingIntent, highPriority);
+ }
+
+ private boolean shouldPromptUnvalidated(NetworkAgentInfo nai) {
+ // Don't prompt if the network is validated, and don't prompt on captive portals
+ // because we're already prompting the user to sign in.
+ if (nai.everValidated || nai.everCaptivePortalDetected) {
+ return false;
+ }
+
+ // If a network has partial connectivity, always prompt unless the user has already accepted
+ // partial connectivity and selected don't ask again. This ensures that if the device
+ // automatically connects to a network that has partial Internet access, the user will
+ // always be able to use it, either because they've already chosen "don't ask again" or
+ // because we have prompt them.
+ if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) {
+ return true;
+ }
+
+ // If a network has no Internet access, only prompt if the network was explicitly selected
+ // and if the user has not already told us to use the network regardless of whether it
+ // validated or not.
+ if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) {
+ return true;
+ }
+
+ return false;
}
private void handlePromptUnvalidated(Network network) {
- if (VDBG) log("handlePromptUnvalidated " + network);
+ if (VDBG || DDBG) log("handlePromptUnvalidated " + network);
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
- // we haven't already been told to switch to it regardless of whether it validated or not.
- // Also don't prompt on captive portals because we're already prompting the user to sign in.
- if (nai == null || nai.everValidated || nai.everCaptivePortalDetected ||
- !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
+ if (nai == null || !shouldPromptUnvalidated(nai)) {
return;
}
- showValidationNotification(nai, NotificationType.NO_INTERNET);
+
+ // Stop automatically reconnecting to this network in the future. Automatically connecting
+ // to a network that provides no or limited connectivity is not useful, because the user
+ // cannot use that network except through the notification shown by this method, and the
+ // notification is only shown if the network is explicitly selected by the user.
+ nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
+
+ // TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when
+ // NetworkMonitor detects the network is partial connectivity. Need to change the design to
+ // popup the notification immediately when the network is partial connectivity.
+ if (nai.partialConnectivity) {
+ showNetworkNotification(nai, NotificationType.PARTIAL_CONNECTIVITY);
+ } else {
+ showNetworkNotification(nai, NotificationType.NO_INTERNET);
+ }
}
private void handleNetworkUnvalidated(NetworkAgentInfo nai) {
NetworkCapabilities nc = nai.networkCapabilities;
if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc);
- if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
- mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
- showValidationNotification(nai, NotificationType.LOST_INTERNET);
+ if (!nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ return;
+ }
+
+ if (mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
+ showNetworkNotification(nai, NotificationType.LOST_INTERNET);
}
}
@@ -3008,6 +3728,11 @@
return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
}
+ @Override
+ public NetworkRequest getDefaultRequest() {
+ return mDefaultRequest;
+ }
+
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
@@ -3022,7 +3747,7 @@
break;
}
case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
- handleDeprecatedGlobalHttpProxy();
+ mProxyTracker.loadDeprecatedGlobalHttpProxy();
break;
}
case EVENT_PROXY_HAS_CHANGED: {
@@ -3038,7 +3763,9 @@
break;
}
case EVENT_REGISTER_NETWORK_AGENT: {
- handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj);
+ final Pair<NetworkAgentInfo, INetworkMonitor> arg =
+ (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj;
+ handleRegisterNetworkAgent(arg.first, arg.second);
break;
}
case EVENT_REGISTER_NETWORK_REQUEST:
@@ -3061,7 +3788,8 @@
break;
}
case EVENT_RELEASE_NETWORK_REQUEST: {
- handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
+ handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
+ /* callOnUnavailable */ false);
break;
}
case EVENT_SET_ACCEPT_UNVALIDATED: {
@@ -3069,6 +3797,12 @@
handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
break;
}
+ case EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY: {
+ Network network = (Network) msg.obj;
+ handleSetAcceptPartialConnectivity(network, toBool(msg.arg1),
+ toBool(msg.arg2));
+ break;
+ }
case EVENT_SET_AVOID_UNVALIDATED: {
handleSetAvoidUnvalidated((Network) msg.obj);
break;
@@ -3077,17 +3811,17 @@
handlePromptUnvalidated((Network) msg.obj);
break;
}
- case EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON: {
- handleMobileDataAlwaysOn();
+ case EVENT_CONFIGURE_ALWAYS_ON_NETWORKS: {
+ handleConfigureAlwaysOnNetworks();
break;
}
// Sent by KeepaliveTracker to process an app request on the state machine thread.
- case NetworkAgent.CMD_START_PACKET_KEEPALIVE: {
+ case NetworkAgent.CMD_START_SOCKET_KEEPALIVE: {
mKeepaliveTracker.handleStartKeepalive(msg);
break;
}
// Sent by KeepaliveTracker to process an app request on the state machine thread.
- case NetworkAgent.CMD_STOP_PACKET_KEEPALIVE: {
+ case NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE: {
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj);
int slot = msg.arg1;
int reason = msg.arg2;
@@ -3095,9 +3829,6 @@
break;
}
case EVENT_SYSTEM_READY: {
- for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- nai.networkMonitor.systemReady = true;
- }
mMultipathPolicyTracker.start();
break;
}
@@ -3112,6 +3843,15 @@
handlePrivateDnsValidationUpdate(
(PrivateDnsValidationUpdate) msg.obj);
break;
+ case EVENT_UID_RULES_CHANGED:
+ handleUidRulesChanged(msg.arg1, msg.arg2);
+ break;
+ case EVENT_DATA_SAVER_CHANGED:
+ handleRestrictBackgroundChanged(toBool(msg.arg1));
+ break;
+ case EVENT_TIMEOUT_NOTIFICATION:
+ mNotifier.clearNotification(msg.arg1, NotificationType.LOGGED_IN);
+ break;
}
}
}
@@ -3121,8 +3861,7 @@
public int tether(String iface, String callerPkg) {
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
- final int status = mTethering.tether(iface);
- return status;
+ return mTethering.tether(iface);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
@@ -3134,8 +3873,7 @@
ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
if (isTetheringSupported()) {
- final int status = mTethering.untether(iface);
- return status;
+ return mTethering.untether(iface);
} else {
return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
}
@@ -3265,6 +4003,36 @@
mTethering.stopTethering(type);
}
+ /**
+ * Get the latest value of the tethering entitlement check.
+ *
+ * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns
+ * out some such apps are observed to abuse this API, change to per-UID limits on this API
+ * if it's really needed.
+ */
+ @Override
+ public void getLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
+ boolean showEntitlementUi, String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
+ mTethering.getLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+ }
+
+ /** Register tethering event callback. */
+ @Override
+ public void registerTetheringEventCallback(ITetheringEventCallback callback,
+ String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
+ mTethering.registerTetheringEventCallback(callback);
+ }
+
+ /** Unregister tethering event callback. */
+ @Override
+ public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
+ String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
+ mTethering.unregisterTetheringEventCallback(callback);
+ }
+
// Called when we lose the default network and have no replacement yet.
// This will automatically be cleared after X seconds or a new default network
// becomes CONNECTED, whichever happens first. The timer is started by the
@@ -3364,147 +4132,62 @@
if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
return;
}
- nai.networkMonitor.forceReevaluation(uid);
+ nai.networkMonitor().forceReevaluation(uid);
}
- private ProxyInfo getDefaultProxy() {
- // this information is already available as a world read/writable jvm property
- // so this API change wouldn't have a benifit. It also breaks the passing
- // of proxy info to all the JVMs.
- // enforceAccessPermission();
- synchronized (mProxyLock) {
- ProxyInfo ret = mGlobalProxy;
- if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy;
- return ret;
+ /**
+ * Returns information about the proxy a certain network is using. If given a null network, it
+ * it will return the proxy for the bound network for the caller app or the default proxy if
+ * none.
+ *
+ * @param network the network we want to get the proxy information for.
+ * @return Proxy information if a network has a proxy configured, or otherwise null.
+ */
+ @Override
+ public ProxyInfo getProxyForNetwork(Network network) {
+ final ProxyInfo globalProxy = mProxyTracker.getGlobalProxy();
+ if (globalProxy != null) return globalProxy;
+ if (network == null) {
+ // Get the network associated with the calling UID.
+ final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(),
+ true);
+ if (activeNetwork == null) {
+ return null;
+ }
+ return getLinkPropertiesProxyInfo(activeNetwork);
+ } else if (queryUserAccess(Binder.getCallingUid(), network.netId)) {
+ // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
+ // caller may not have.
+ return getLinkPropertiesProxyInfo(network);
+ }
+ // No proxy info available if the calling UID does not have network access.
+ return null;
+ }
+
+ @VisibleForTesting
+ protected boolean queryUserAccess(int uid, int netId) {
+ return NetworkUtils.queryUserAccess(uid, netId);
+ }
+
+ private ProxyInfo getLinkPropertiesProxyInfo(Network network) {
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) return null;
+ synchronized (nai) {
+ final ProxyInfo linkHttpProxy = nai.linkProperties.getHttpProxy();
+ return linkHttpProxy == null ? null : new ProxyInfo(linkHttpProxy);
}
}
@Override
- public ProxyInfo getProxyForNetwork(Network network) {
- if (network == null) return getDefaultProxy();
- final ProxyInfo globalProxy = getGlobalProxy();
- if (globalProxy != null) return globalProxy;
- if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null;
- // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
- // caller may not have.
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- if (nai == null) return null;
- synchronized (nai) {
- final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy();
- if (proxyInfo == null) return null;
- return new ProxyInfo(proxyInfo);
- }
- }
-
- // Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
- // (e.g. if mGlobalProxy==null fall back to network-specific proxy, if network-specific
- // proxy is null then there is no proxy in place).
- private ProxyInfo canonicalizeProxyInfo(ProxyInfo proxy) {
- if (proxy != null && TextUtils.isEmpty(proxy.getHost())
- && (proxy.getPacFileUrl() == null || Uri.EMPTY.equals(proxy.getPacFileUrl()))) {
- proxy = null;
- }
- return proxy;
- }
-
- // ProxyInfo equality function with a couple modifications over ProxyInfo.equals() to make it
- // better for determining if a new proxy broadcast is necessary:
- // 1. Canonicalize empty ProxyInfos to null so an empty proxy compares equal to null so as to
- // avoid unnecessary broadcasts.
- // 2. Make sure all parts of the ProxyInfo's compare true, including the host when a PAC URL
- // is in place. This is important so legacy PAC resolver (see com.android.proxyhandler)
- // changes aren't missed. The legacy PAC resolver pretends to be a simple HTTP proxy but
- // actually uses the PAC to resolve; this results in ProxyInfo's with PAC URL, host and port
- // all set.
- private boolean proxyInfoEqual(ProxyInfo a, ProxyInfo b) {
- a = canonicalizeProxyInfo(a);
- b = canonicalizeProxyInfo(b);
- // ProxyInfo.equals() doesn't check hosts when PAC URLs are present, but we need to check
- // hosts even when PAC URLs are present to account for the legacy PAC resolver.
- return Objects.equals(a, b) && (a == null || Objects.equals(a.getHost(), b.getHost()));
- }
-
- public void setGlobalProxy(ProxyInfo proxyProperties) {
+ public void setGlobalProxy(final ProxyInfo proxyProperties) {
enforceConnectivityInternalPermission();
-
- synchronized (mProxyLock) {
- if (proxyProperties == mGlobalProxy) return;
- if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
- if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
-
- String host = "";
- int port = 0;
- String exclList = "";
- String pacFileUrl = "";
- if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
- !Uri.EMPTY.equals(proxyProperties.getPacFileUrl()))) {
- if (!proxyProperties.isValid()) {
- if (DBG)
- log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
- return;
- }
- mGlobalProxy = new ProxyInfo(proxyProperties);
- host = mGlobalProxy.getHost();
- port = mGlobalProxy.getPort();
- exclList = mGlobalProxy.getExclusionListAsString();
- if (!Uri.EMPTY.equals(proxyProperties.getPacFileUrl())) {
- pacFileUrl = proxyProperties.getPacFileUrl().toString();
- }
- } else {
- mGlobalProxy = null;
- }
- ContentResolver res = mContext.getContentResolver();
- final long token = Binder.clearCallingIdentity();
- try {
- Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, host);
- Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port);
- Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
- exclList);
- Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- if (mGlobalProxy == null) {
- proxyProperties = mDefaultProxy;
- }
- sendProxyBroadcast(proxyProperties);
- }
+ mProxyTracker.setGlobalProxy(proxyProperties);
}
- private void loadGlobalProxy() {
- ContentResolver res = mContext.getContentResolver();
- String host = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST);
- int port = Settings.Global.getInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, 0);
- String exclList = Settings.Global.getString(res,
- Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
- String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC);
- if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) {
- ProxyInfo proxyProperties;
- if (!TextUtils.isEmpty(pacFileUrl)) {
- proxyProperties = new ProxyInfo(pacFileUrl);
- } else {
- proxyProperties = new ProxyInfo(host, port, exclList);
- }
- if (!proxyProperties.isValid()) {
- if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
- return;
- }
-
- synchronized (mProxyLock) {
- mGlobalProxy = proxyProperties;
- }
- }
- }
-
+ @Override
+ @Nullable
public ProxyInfo getGlobalProxy() {
- // this information is already available as a world read/writable jvm property
- // so this API change wouldn't have a benifit. It also breaks the passing
- // of proxy info to all the JVMs.
- // enforceAccessPermission();
- synchronized (mProxyLock) {
- return mGlobalProxy;
- }
+ return mProxyTracker.getGlobalProxy();
}
private void handleApplyDefaultProxy(ProxyInfo proxy) {
@@ -3512,85 +4195,19 @@
&& Uri.EMPTY.equals(proxy.getPacFileUrl())) {
proxy = null;
}
- synchronized (mProxyLock) {
- if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
- if (mDefaultProxy == proxy) return; // catches repeated nulls
- if (proxy != null && !proxy.isValid()) {
- if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString());
- return;
- }
-
- // This call could be coming from the PacManager, containing the port of the local
- // proxy. If this new proxy matches the global proxy then copy this proxy to the
- // global (to get the correct local port), and send a broadcast.
- // TODO: Switch PacManager to have its own message to send back rather than
- // reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
- if ((mGlobalProxy != null) && (proxy != null)
- && (!Uri.EMPTY.equals(proxy.getPacFileUrl()))
- && proxy.getPacFileUrl().equals(mGlobalProxy.getPacFileUrl())) {
- mGlobalProxy = proxy;
- sendProxyBroadcast(mGlobalProxy);
- return;
- }
- mDefaultProxy = proxy;
-
- if (mGlobalProxy != null) return;
- if (!mDefaultProxyDisabled) {
- sendProxyBroadcast(proxy);
- }
- }
+ mProxyTracker.setDefaultProxy(proxy);
}
- // If the proxy has changed from oldLp to newLp, resend proxy broadcast with default proxy.
- // This method gets called when any network changes proxy, but the broadcast only ever contains
- // the default proxy (even if it hasn't changed).
- // TODO: Deprecate the broadcast extras as they aren't necessarily applicable in a multi-network
- // world where an app might be bound to a non-default network.
- private void updateProxy(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo nai) {
+ // If the proxy has changed from oldLp to newLp, resend proxy broadcast. This method gets called
+ // when any network changes proxy.
+ // TODO: Remove usage of broadcast extras as they are deprecated and not applicable in a
+ // multi-network world where an app might be bound to a non-default network.
+ private void updateProxy(LinkProperties newLp, LinkProperties oldLp) {
ProxyInfo newProxyInfo = newLp == null ? null : newLp.getHttpProxy();
ProxyInfo oldProxyInfo = oldLp == null ? null : oldLp.getHttpProxy();
- if (!proxyInfoEqual(newProxyInfo, oldProxyInfo)) {
- sendProxyBroadcast(getDefaultProxy());
- }
- }
-
- private void handleDeprecatedGlobalHttpProxy() {
- String proxy = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.HTTP_PROXY);
- if (!TextUtils.isEmpty(proxy)) {
- String data[] = proxy.split(":");
- if (data.length == 0) {
- return;
- }
-
- String proxyHost = data[0];
- int proxyPort = 8080;
- if (data.length > 1) {
- try {
- proxyPort = Integer.parseInt(data[1]);
- } catch (NumberFormatException e) {
- return;
- }
- }
- ProxyInfo p = new ProxyInfo(data[0], proxyPort, "");
- setGlobalProxy(p);
- }
- }
-
- private void sendProxyBroadcast(ProxyInfo proxy) {
- if (proxy == null) proxy = new ProxyInfo("", 0, "");
- if (mPacManager.setCurrentProxyScriptUrl(proxy)) return;
- if (DBG) log("sending Proxy Broadcast for " + proxy);
- Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
- Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ if (!ProxyTracker.proxyInfoEqual(newProxyInfo, oldProxyInfo)) {
+ mProxyTracker.sendProxyBroadcast();
}
}
@@ -3601,7 +4218,7 @@
SettingsObserver(Context context, Handler handler) {
super(null);
- mUriEventMap = new HashMap<Uri, Integer>();
+ mUriEventMap = new HashMap<>();
mContext = context;
mHandler = handler;
}
@@ -3769,7 +4386,7 @@
/**
* @return VPN information for accounting, or null if we can't retrieve all required
- * information, e.g primary underlying iface.
+ * information, e.g underlying ifaces.
*/
@Nullable
private VpnInfo createVpnInfo(Vpn vpn) {
@@ -3781,17 +4398,28 @@
// see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
// the underlyingNetworks list.
if (underlyingNetworks == null) {
- NetworkAgentInfo defaultNetwork = getDefaultNetwork();
- if (defaultNetwork != null && defaultNetwork.linkProperties != null) {
- info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName();
- }
- } else if (underlyingNetworks.length > 0) {
- LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]);
- if (linkProperties != null) {
- info.primaryUnderlyingIface = linkProperties.getInterfaceName();
+ NetworkAgentInfo defaultNai = getDefaultNetwork();
+ if (defaultNai != null) {
+ underlyingNetworks = new Network[] { defaultNai.network };
}
}
- return info.primaryUnderlyingIface == null ? null : info;
+ if (underlyingNetworks != null && underlyingNetworks.length > 0) {
+ List<String> interfaces = new ArrayList<>();
+ for (Network network : underlyingNetworks) {
+ LinkProperties lp = getLinkProperties(network);
+ if (lp != null) {
+ for (String iface : lp.getAllInterfaceNames()) {
+ if (!TextUtils.isEmpty(iface)) {
+ interfaces.add(iface);
+ }
+ }
+ }
+ }
+ if (!interfaces.isEmpty()) {
+ info.underlyingIfaces = interfaces.toArray(new String[interfaces.size()]);
+ }
+ }
+ return info.underlyingIfaces == null ? null : info;
}
/**
@@ -3873,7 +4501,7 @@
Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
return false;
}
- setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile));
+ setLockdownTracker(new LockdownVpnTracker(mContext, mNMS, this, vpn, profile));
} else {
setLockdownTracker(null);
}
@@ -3890,6 +4518,8 @@
private void setLockdownTracker(LockdownVpnTracker tracker) {
// Shutdown any existing tracker
final LockdownVpnTracker existing = mLockdownTracker;
+ // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the
+ // necessary onBlockedStatusChanged callbacks.
mLockdownTracker = null;
if (existing != null) {
existing.shutdown();
@@ -3919,7 +4549,7 @@
synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn == null) {
- // Shouldn't happen as all codepaths that point here should have checked the Vpn
+ // Shouldn't happen as all code paths that point here should have checked the Vpn
// exists already.
Slog.wtf(TAG, "User " + userId + " has no Vpn configuration");
return false;
@@ -3945,8 +4575,9 @@
}
@Override
- public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
- enforceConnectivityInternalPermission();
+ public boolean setAlwaysOnVpnPackage(
+ int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+ enforceControlAlwaysOnVpnPermission();
enforceCrossUserPermission(userId);
synchronized (mVpns) {
@@ -3960,11 +4591,11 @@
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
- if (!vpn.setAlwaysOnPackage(packageName, lockdown)) {
+ if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
return false;
}
if (!startAlwaysOnVpn(userId)) {
- vpn.setAlwaysOnPackage(null, false);
+ vpn.setAlwaysOnPackage(null, false, null);
return false;
}
}
@@ -3973,7 +4604,7 @@
@Override
public String getAlwaysOnVpnPackage(int userId) {
- enforceConnectivityInternalPermission();
+ enforceControlAlwaysOnVpnPermission();
enforceCrossUserPermission(userId);
synchronized (mVpns) {
@@ -3987,6 +4618,36 @@
}
@Override
+ public boolean isVpnLockdownEnabled(int userId) {
+ enforceControlAlwaysOnVpnPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+ return false;
+ }
+ return vpn.getLockdown();
+ }
+ }
+
+ @Override
+ public List<String> getVpnLockdownWhitelist(int userId) {
+ enforceControlAlwaysOnVpnPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+ return null;
+ }
+ return vpn.getLockdownWhitelist();
+ }
+ }
+
+ @Override
public int checkMobileProvisioning(int suggestedTimeOutMs) {
// TODO: Remove? Any reason to trigger a provisioning check?
return -1;
@@ -4083,7 +4744,7 @@
url = String.format(url,
mTelephonyManager.getSimSerialNumber() /* ICCID */,
mTelephonyManager.getDeviceId() /* IMEI */,
- phoneNumber /* Phone numer */);
+ phoneNumber /* Phone number */);
}
return url;
@@ -4108,7 +4769,7 @@
@Override
public void setAirplaneMode(boolean enable) {
- enforceConnectivityInternalPermission();
+ enforceNetworkStackSettingsOrSetup();
final long ident = Binder.clearCallingIdentity();
try {
final ContentResolver cr = mContext.getContentResolver();
@@ -4128,7 +4789,7 @@
loge("Starting user already has a VPN");
return;
}
- userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
+ userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId);
mVpns.put(userId, userVpn);
if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
updateLockdownVpn();
@@ -4149,6 +4810,7 @@
}
private void onUserAdded(int userId) {
+ mPermissionMonitor.onUserAdded(userId);
Network defaultNetwork = getNetwork(getDefaultNetwork());
synchronized (mVpns) {
final int vpnsSize = mVpns.size();
@@ -4162,6 +4824,7 @@
}
private void onUserRemoved(int userId) {
+ mPermissionMonitor.onUserRemoved(userId);
Network defaultNetwork = getNetwork(getDefaultNetwork());
synchronized (mVpns) {
final int vpnsSize = mVpns.size();
@@ -4174,6 +4837,56 @@
}
}
+ private void onPackageAdded(String packageName, int uid) {
+ if (TextUtils.isEmpty(packageName) || uid < 0) {
+ Slog.wtf(TAG, "Invalid package in onPackageAdded: " + packageName + " | " + uid);
+ return;
+ }
+ mPermissionMonitor.onPackageAdded(packageName, uid);
+ }
+
+ private void onPackageReplaced(String packageName, int uid) {
+ if (TextUtils.isEmpty(packageName) || uid < 0) {
+ Slog.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid);
+ return;
+ }
+ final int userId = UserHandle.getUserId(uid);
+ synchronized (mVpns) {
+ final Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ return;
+ }
+ // Legacy always-on VPN won't be affected since the package name is not set.
+ if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
+ Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
+ + userId);
+ vpn.startAlwaysOnVpn();
+ }
+ }
+ }
+
+ private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+ if (TextUtils.isEmpty(packageName) || uid < 0) {
+ Slog.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
+ return;
+ }
+ mPermissionMonitor.onPackageRemoved(uid);
+
+ final int userId = UserHandle.getUserId(uid);
+ synchronized (mVpns) {
+ final Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ return;
+ }
+ // Legacy always-on VPN won't be affected since the package name is not set.
+ if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
+ Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
+ + userId);
+ vpn.setAlwaysOnPackage(null, false, null);
+ }
+ }
+ }
+
private void onUserUnlocked(int userId) {
synchronized (mVpns) {
// User present may be sent because of an unlock, which might mean an unlocked keystore.
@@ -4185,12 +4898,16 @@
}
}
- private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
ensureRunningOnConnectivityServiceThread();
final String action = intent.getAction();
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ final Uri packageData = intent.getData();
+ final String packageName =
+ packageData != null ? packageData.getSchemeSpecificPart() : null;
if (userId == UserHandle.USER_NULL) return;
if (Intent.ACTION_USER_STARTED.equals(action)) {
@@ -4203,6 +4920,14 @@
onUserRemoved(userId);
} else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
onUserUnlocked(userId);
+ } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ onPackageAdded(packageName, uid);
+ } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+ onPackageReplaced(packageName, uid);
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ final boolean isReplacing = intent.getBooleanExtra(
+ Intent.EXTRA_REPLACING, false);
+ onPackageRemoved(packageName, uid, isReplacing);
}
}
};
@@ -4217,10 +4942,8 @@
}
};
- private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
- new HashMap<Messenger, NetworkFactoryInfo>();
- private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
- new HashMap<NetworkRequest, NetworkRequestInfo>();
+ private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = new HashMap<>();
+ private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
// Map from UID to number of NetworkRequests that UID has filed.
@@ -4231,11 +4954,14 @@
public final String name;
public final Messenger messenger;
public final AsyncChannel asyncChannel;
+ public final int factorySerialNumber;
- public NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel) {
+ NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
+ int factorySerialNumber) {
this.name = name;
this.messenger = messenger;
this.asyncChannel = asyncChannel;
+ this.factorySerialNumber = factorySerialNumber;
}
}
@@ -4324,17 +5050,23 @@
}
}
- // This checks that the passed capabilities either do not request a specific SSID, or the
- // calling app has permission to do so.
+ // This checks that the passed capabilities either do not request a specific SSID/SignalStrength
+ // , or the calling app has permission to do so.
private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
int callerPid, int callerUid) {
if (null != nc.getSSID() && !checkSettingsPermission(callerPid, callerUid)) {
throw new SecurityException("Insufficient permissions to request a specific SSID");
}
+
+ if (nc.hasSignalStrength()
+ && !checkNetworkSignalStrengthWakeupPermission(callerPid, callerUid)) {
+ throw new SecurityException(
+ "Insufficient permissions to request a specific signal strength");
+ }
}
private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) {
- final SortedSet<Integer> thresholds = new TreeSet();
+ final SortedSet<Integer> thresholds = new TreeSet<>();
synchronized (nai) {
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.request.networkCapabilities.hasSignalStrength() &&
@@ -4343,7 +5075,7 @@
}
}
}
- return new ArrayList<Integer>(thresholds);
+ return new ArrayList<>(thresholds);
}
private void updateSignalStrengthThresholds(
@@ -4448,6 +5180,14 @@
}
if (nai != null) {
nai.asyncChannel.sendMessage(android.net.NetworkAgent.CMD_REQUEST_BANDWIDTH_UPDATE);
+ synchronized (mBandwidthRequests) {
+ final int uid = Binder.getCallingUid();
+ Integer uidReqs = mBandwidthRequests.get(uid);
+ if (uidReqs == null) {
+ uidReqs = new Integer(0);
+ }
+ mBandwidthRequests.put(uid, ++uidReqs);
+ }
return true;
}
return false;
@@ -4588,10 +5328,12 @@
}
@Override
- public void registerNetworkFactory(Messenger messenger, String name) {
+ public int registerNetworkFactory(Messenger messenger, String name) {
enforceConnectivityInternalPermission();
- NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel());
+ NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(),
+ NetworkFactory.SerialNumber.nextSerialNumber());
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
+ return nfi.factorySerialNumber;
}
private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
@@ -4623,13 +5365,11 @@
*/
// NOTE: Accessed on multiple threads, must be synchronized on itself.
@GuardedBy("mNetworkForRequestId")
- private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
- new SparseArray<NetworkAgentInfo>();
+ private final SparseArray<NetworkAgentInfo> mNetworkForRequestId = new SparseArray<>();
// NOTE: Accessed on multiple threads, must be synchronized on itself.
@GuardedBy("mNetworkForNetId")
- private final SparseArray<NetworkAgentInfo> mNetworkForNetId =
- new SparseArray<NetworkAgentInfo>();
+ private final SparseArray<NetworkAgentInfo> mNetworkForNetId = new SparseArray<>();
// NOTE: Accessed on multiple threads, synchronized with mNetworkForNetId.
// An entry is first added to mNetIdInUse, prior to mNetworkForNetId, so
// there may not be a strict 1:1 correlation between the two.
@@ -4639,11 +5379,10 @@
// NetworkAgentInfo keyed off its connecting messenger
// TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays
// NOTE: Only should be accessed on ConnectivityServiceThread, except dump().
- private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
- new HashMap<Messenger, NetworkAgentInfo>();
+ private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos = new HashMap<>();
@GuardedBy("mBlockedAppUids")
- private final HashSet<Integer> mBlockedAppUids = new HashSet();
+ private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
// Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
private final NetworkRequest mDefaultRequest;
@@ -4652,6 +5391,10 @@
// priority networks like Wi-Fi are active.
private final NetworkRequest mDefaultMobileDataRequest;
+ // Request used to optionally keep wifi data active even when higher
+ // priority networks like ethernet are active.
+ private final NetworkRequest mDefaultWifiRequest;
+
private NetworkAgentInfo getNetworkForRequest(int requestId) {
synchronized (mNetworkForRequestId) {
return mNetworkForRequestId.get(requestId);
@@ -4687,7 +5430,8 @@
}
}
- private boolean isDefaultNetwork(NetworkAgentInfo nai) {
+ @VisibleForTesting
+ protected boolean isDefaultNetwork(NetworkAgentInfo nai) {
return nai == getDefaultNetwork();
}
@@ -4695,9 +5439,35 @@
return nri.request.requestId == mDefaultRequest.requestId;
}
+ // TODO : remove this method. It's a stopgap measure to help sheperding a number of dependent
+ // changes that would conflict throughout the automerger graph. Having this method temporarily
+ // helps with the process of going through with all these dependent changes across the entire
+ // tree.
public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
int currentScore, NetworkMisc networkMisc) {
+ return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities,
+ currentScore, networkMisc, NetworkFactory.SerialNumber.NONE);
+ }
+
+ /**
+ * Register a new agent with ConnectivityService to handle a network.
+ *
+ * @param messenger a messenger for ConnectivityService to contact the agent asynchronously.
+ * @param networkInfo the initial info associated with this network. It can be updated later :
+ * see {@link #updateNetworkInfo}.
+ * @param linkProperties the initial link properties of this network. They can be updated
+ * later : see {@link #updateLinkProperties}.
+ * @param networkCapabilities the initial capabilites of this network. They can be updated
+ * later : see {@link #updateNetworkCapabilities}.
+ * @param currentScore the initial score of the network. See
+ * {@link NetworkAgentInfo#getCurrentScore}.
+ * @param networkMisc metadata about the network. This is never updated.
+ * @param factorySerialNumber the serial number of the factory owning this NetworkAgent.
+ */
+ public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
+ int currentScore, NetworkMisc networkMisc, int factorySerialNumber) {
enforceConnectivityInternalPermission();
LinkProperties lp = new LinkProperties(linkProperties);
@@ -4707,27 +5477,46 @@
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
- mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
+ mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mDnsResolver,
+ mNMS, factorySerialNumber);
// Make sure the network capabilities reflect what the agent info says.
- nai.networkCapabilities = mixInCapabilities(nai, nc);
- synchronized (this) {
- nai.networkMonitor.systemReady = mSystemReady;
- }
+ nai.setNetworkCapabilities(mixInCapabilities(nai, nc));
final String extraInfo = networkInfo.getExtraInfo();
final String name = TextUtils.isEmpty(extraInfo)
? nai.networkCapabilities.getSSID() : extraInfo;
- addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network, name);
if (DBG) log("registerNetworkAgent " + nai);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ getNetworkStack().makeNetworkMonitor(
+ nai.network, name, new NetworkMonitorCallbacks(nai));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ // NetworkAgentInfo registration will finish when the NetworkMonitor is created.
+ // If the network disconnects or sends any other event before that, messages are deferred by
+ // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the
+ // registration.
return nai.network.netId;
}
- private void handleRegisterNetworkAgent(NetworkAgentInfo nai) {
+ @VisibleForTesting
+ protected NetworkStackClient getNetworkStack() {
+ return NetworkStackClient.getInstance();
+ }
+
+ private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+ nai.onNetworkMonitorCreated(networkMonitor);
if (VDBG) log("Got NetworkAgent Messenger");
mNetworkAgentInfos.put(nai.messenger, nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.put(nai.network.netId, nai);
}
+
+ try {
+ networkMonitor.start();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
NetworkInfo networkInfo = nai.networkInfo;
nai.networkInfo = null;
@@ -4735,23 +5524,29 @@
updateUids(nai, null, nai.networkCapabilities);
}
- private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties oldLp) {
- LinkProperties newLp = new LinkProperties(networkAgent.linkProperties);
+ private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp,
+ LinkProperties oldLp) {
int netId = networkAgent.network.netId;
- // The NetworkAgentInfo does not know whether clatd is running on its network or not. Before
- // we do anything else, make sure its LinkProperties are accurate.
- if (networkAgent.clatd != null) {
- networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
- }
+ // The NetworkAgentInfo does not know whether clatd is running on its network or not, or
+ // whether there is a NAT64 prefix. Before we do anything else, make sure its LinkProperties
+ // are accurate.
+ networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
+
+ // update filtering rules, need to happen after the interface update so netd knows about the
+ // new interface (the interface name -> index map becomes initialized)
+ updateVpnFiltering(newLp, oldLp, networkAgent);
+
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
// updateMtu(lp, null);
// }
- updateTcpBufferSizes(networkAgent);
+ if (isDefaultNetwork(networkAgent)) {
+ updateTcpBufferSizes(newLp.getTcpBufferSizes());
+ }
updateRoutes(newLp, oldLp, netId);
updateDnses(newLp, oldLp, netId);
@@ -4761,28 +5556,30 @@
// updateDnses will fetch the private DNS configuration from DnsManager.
mDnsManager.updatePrivateDnsStatus(netId, newLp);
- // Start or stop clat accordingly to network state.
- networkAgent.updateClat(mNetd);
if (isDefaultNetwork(networkAgent)) {
handleApplyDefaultProxy(newLp.getHttpProxy());
} else {
- updateProxy(newLp, oldLp, networkAgent);
- }
-
- synchronized (networkAgent) {
- networkAgent.linkProperties = newLp;
+ updateProxy(newLp, oldLp);
}
// TODO - move this check to cover the whole function
if (!Objects.equals(newLp, oldLp)) {
+ synchronized (networkAgent) {
+ networkAgent.linkProperties = newLp;
+ }
+ // Start or stop DNS64 detection and 464xlat according to network state.
+ networkAgent.clatd.update();
notifyIfacesChangedForNetworkStats();
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
+ networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp);
+ if (networkAgent.everConnected) {
+ notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
+ }
}
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
}
private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
- // Marks are only available on WiFi interaces. Checking for
+ // Marks are only available on WiFi interfaces. Checking for
// marks on unsupported interfaces is harmless.
if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return;
@@ -4802,9 +5599,9 @@
final String prefix = "iface:" + iface;
try {
if (add) {
- mNetd.getNetdService().wakeupAddInterface(iface, prefix, mark, mask);
+ mNetd.wakeupAddInterface(iface, prefix, mark, mask);
} else {
- mNetd.getNetdService().wakeupDelInterface(iface, prefix, mark, mask);
+ mNetd.wakeupDelInterface(iface, prefix, mark, mask);
}
} catch (Exception e) {
loge("Exception modifying wakeup packet monitoring: " + e);
@@ -4814,13 +5611,13 @@
private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId,
NetworkCapabilities caps) {
- CompareResult<String> interfaceDiff = new CompareResult<String>(
+ CompareResult<String> interfaceDiff = new CompareResult<>(
oldLp != null ? oldLp.getAllInterfaceNames() : null,
newLp != null ? newLp.getAllInterfaceNames() : null);
for (String iface : interfaceDiff.added) {
try {
if (DBG) log("Adding iface " + iface + " to network " + netId);
- mNetd.addInterfaceToNetwork(iface, netId);
+ mNMS.addInterfaceToNetwork(iface, netId);
wakeupModifyInterface(iface, caps, true);
} catch (Exception e) {
loge("Exception adding interface: " + e);
@@ -4830,7 +5627,7 @@
try {
if (DBG) log("Removing iface " + iface + " from network " + netId);
wakeupModifyInterface(iface, caps, false);
- mNetd.removeInterfaceFromNetwork(iface, netId);
+ mNMS.removeInterfaceFromNetwork(iface, netId);
} catch (Exception e) {
loge("Exception removing interface: " + e);
}
@@ -4849,12 +5646,12 @@
// add routes before removing old in case it helps with continuous connectivity
- // do this twice, adding non-nexthop routes first, then routes they are dependent on
+ // do this twice, adding non-next-hop routes first, then routes they are dependent on
for (RouteInfo route : routeDiff.added) {
if (route.hasGateway()) continue;
- if (VDBG) log("Adding Route [" + route + "] to network " + netId);
+ if (VDBG || DDBG) log("Adding Route [" + route + "] to network " + netId);
try {
- mNetd.addRoute(netId, route);
+ mNMS.addRoute(netId, route);
} catch (Exception e) {
if ((route.getDestination().getAddress() instanceof Inet4Address) || VDBG) {
loge("Exception in addRoute for non-gateway: " + e);
@@ -4863,9 +5660,9 @@
}
for (RouteInfo route : routeDiff.added) {
if (route.hasGateway() == false) continue;
- if (VDBG) log("Adding Route [" + route + "] to network " + netId);
+ if (VDBG || DDBG) log("Adding Route [" + route + "] to network " + netId);
try {
- mNetd.addRoute(netId, route);
+ mNMS.addRoute(netId, route);
} catch (Exception e) {
if ((route.getGateway() instanceof Inet4Address) || VDBG) {
loge("Exception in addRoute for gateway: " + e);
@@ -4874,9 +5671,9 @@
}
for (RouteInfo route : routeDiff.removed) {
- if (VDBG) log("Removing Route [" + route + "] from network " + netId);
+ if (VDBG || DDBG) log("Removing Route [" + route + "] from network " + netId);
try {
- mNetd.removeRoute(netId, route);
+ mNMS.removeRoute(netId, route);
} catch (Exception e) {
loge("Exception in removeRoute: " + e);
}
@@ -4903,15 +5700,45 @@
}
}
- private String getNetworkPermission(NetworkCapabilities nc) {
- // TODO: make these permission strings AIDL constants instead.
+ private void updateVpnFiltering(LinkProperties newLp, LinkProperties oldLp,
+ NetworkAgentInfo nai) {
+ final String oldIface = oldLp != null ? oldLp.getInterfaceName() : null;
+ final String newIface = newLp != null ? newLp.getInterfaceName() : null;
+ final boolean wasFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, oldLp);
+ final boolean needsFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, newLp);
+
+ if (!wasFiltering && !needsFiltering) {
+ // Nothing to do.
+ return;
+ }
+
+ if (Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) {
+ // Nothing changed.
+ return;
+ }
+
+ final Set<UidRange> ranges = nai.networkCapabilities.getUids();
+ final int vpnAppUid = nai.networkCapabilities.getEstablishingVpnAppUid();
+ // TODO: this create a window of opportunity for apps to receive traffic between the time
+ // when the old rules are removed and the time when new rules are added. To fix this,
+ // make eBPF support two whitelisted interfaces so here new rules can be added before the
+ // old rules are being removed.
+ if (wasFiltering) {
+ mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid);
+ }
+ if (needsFiltering) {
+ mPermissionMonitor.onVpnUidRangesAdded(newIface, ranges, vpnAppUid);
+ }
+ }
+
+ private int getNetworkPermission(NetworkCapabilities nc) {
if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
- return NetworkManagementService.PERMISSION_SYSTEM;
+ return INetd.PERMISSION_SYSTEM;
}
if (!nc.hasCapability(NET_CAPABILITY_FOREGROUND)) {
- return NetworkManagementService.PERMISSION_NETWORK;
+ return INetd.PERMISSION_NETWORK;
}
- return null;
+ return INetd.PERMISSION_NONE;
}
/**
@@ -4959,6 +5786,11 @@
} else {
newNc.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
}
+ if (nai.partialConnectivity) {
+ newNc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ } else {
+ newNc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ }
return newNc;
}
@@ -4984,11 +5816,11 @@
if (Objects.equals(nai.networkCapabilities, newNc)) return;
- final String oldPermission = getNetworkPermission(nai.networkCapabilities);
- final String newPermission = getNetworkPermission(newNc);
- if (!Objects.equals(oldPermission, newPermission) && nai.created && !nai.isVPN()) {
+ final int oldPermission = getNetworkPermission(nai.networkCapabilities);
+ final int newPermission = getNetworkPermission(newNc);
+ if (oldPermission != newPermission && nai.created && !nai.isVPN()) {
try {
- mNetd.setNetworkPermission(nai.network.netId, newPermission);
+ mNMS.setNetworkPermission(nai.network.netId, newPermission);
} catch (RemoteException e) {
loge("Exception in setNetworkPermission: " + e);
}
@@ -4997,7 +5829,7 @@
final NetworkCapabilities prevNc;
synchronized (nai) {
prevNc = nai.networkCapabilities;
- nai.networkCapabilities = newNc;
+ nai.setNetworkCapabilities(newNc);
}
updateUids(nai, prevNc, newNc);
@@ -5015,12 +5847,20 @@
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
- // Report changes that are interesting for network statistics tracking.
if (prevNc != null) {
- final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) !=
- newNc.hasCapability(NET_CAPABILITY_NOT_METERED);
+ final boolean oldMetered = prevNc.isMetered();
+ final boolean newMetered = newNc.isMetered();
+ final boolean meteredChanged = oldMetered != newMetered;
+
+ if (meteredChanged) {
+ maybeNotifyNetworkBlocked(nai, oldMetered, newMetered, mRestrictBackground,
+ mRestrictBackground);
+ }
+
final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) !=
newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+
+ // Report changes that are interesting for network statistics tracking.
if (meteredChanged || roamingChanged) {
notifyIfacesChangedForNetworkStats();
}
@@ -5033,6 +5873,34 @@
}
}
+ /**
+ * Returns whether VPN isolation (ingress interface filtering) should be applied on the given
+ * network.
+ *
+ * Ingress interface filtering enforces that all apps under the given network can only receive
+ * packets from the network's interface (and loopback). This is important for VPNs because
+ * apps that cannot bypass a fully-routed VPN shouldn't be able to receive packets from any
+ * non-VPN interfaces.
+ *
+ * As a result, this method should return true iff
+ * 1. the network is an app VPN (not legacy VPN)
+ * 2. the VPN does not allow bypass
+ * 3. the VPN is fully-routed
+ * 4. the VPN interface is non-null
+ *
+ * @See INetd#firewallAddUidInterfaceRules
+ * @See INetd#firewallRemoveUidInterfaceRules
+ */
+ private boolean requiresVpnIsolation(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc,
+ LinkProperties lp) {
+ if (nc == null || lp == null) return false;
+ return nai.isVPN()
+ && !nai.networkMisc.allowBypass
+ && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID
+ && lp.getInterfaceName() != null
+ && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
+ }
+
private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
NetworkCapabilities newNc) {
Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUids();
@@ -5045,41 +5913,65 @@
newRanges.removeAll(prevRangesCopy);
try {
+ // When updating the VPN uid routing rules, add the new range first then remove the old
+ // range. If old range were removed first, there would be a window between the old
+ // range being removed and the new range being added, during which UIDs contained
+ // in both ranges are not subject to any VPN routing rules. Adding new range before
+ // removing old range works because, unlike the filtering rules below, it's possible to
+ // add duplicate UID routing rules.
if (!newRanges.isEmpty()) {
final UidRange[] addedRangesArray = new UidRange[newRanges.size()];
newRanges.toArray(addedRangesArray);
- mNetd.addVpnUidRanges(nai.network.netId, addedRangesArray);
+ mNMS.addVpnUidRanges(nai.network.netId, addedRangesArray);
}
if (!prevRanges.isEmpty()) {
final UidRange[] removedRangesArray = new UidRange[prevRanges.size()];
prevRanges.toArray(removedRangesArray);
- mNetd.removeVpnUidRanges(nai.network.netId, removedRangesArray);
+ mNMS.removeVpnUidRanges(nai.network.netId, removedRangesArray);
+ }
+ final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties);
+ final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties);
+ final String iface = nai.linkProperties.getInterfaceName();
+ // For VPN uid interface filtering, old ranges need to be removed before new ranges can
+ // be added, due to the range being expanded and stored as invidiual UIDs. For example
+ // the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means
+ // prevRanges = [0, 99999] while newRanges = [0, 10012], [10014, 99999]. If prevRanges
+ // were added first and then newRanges got removed later, there would be only one uid
+ // 10013 left. A consequence of removing old ranges before adding new ranges is that
+ // there is now a window of opportunity when the UIDs are not subject to any filtering.
+ // Note that this is in contrast with the (more robust) update of VPN routing rules
+ // above, where the addition of new ranges happens before the removal of old ranges.
+ // TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range
+ // to be removed will never overlap with the new range to be added.
+ if (wasFiltering && !prevRanges.isEmpty()) {
+ mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges,
+ prevNc.getEstablishingVpnAppUid());
+ }
+ if (shouldFilter && !newRanges.isEmpty()) {
+ mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges,
+ newNc.getEstablishingVpnAppUid());
}
} catch (Exception e) {
// Never crash!
- loge("Exception in updateUids: " + e);
+ loge("Exception in updateUids: ", e);
}
}
public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
+ ensureRunningOnConnectivityServiceThread();
+
if (getNetworkAgentInfoForNetId(nai.network.netId) != nai) {
// Ignore updates for disconnected networks
return;
}
// newLp is already a defensive copy.
newLp.ensureDirectlyConnectedRoutes();
- if (VDBG) {
+ if (VDBG || DDBG) {
log("Update of LinkProperties for " + nai.name() +
"; created=" + nai.created +
"; everConnected=" + nai.everConnected);
}
- LinkProperties oldLp = nai.linkProperties;
- synchronized (nai) {
- nai.linkProperties = newLp;
- }
- if (nai.everConnected) {
- updateLinkProperties(nai, oldLp);
- }
+ updateLinkProperties(nai, newLp, new LinkProperties(nai.linkProperties));
}
private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
@@ -5087,15 +5979,23 @@
NetworkRequest nr = nai.requestAt(i);
// Don't send listening requests to factories. b/17393458
if (nr.isListen()) continue;
- sendUpdatedScoreToFactories(nr, nai.getCurrentScore());
+ sendUpdatedScoreToFactories(nr, nai);
}
}
- private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, int score) {
- if (VDBG) log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
+ private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, NetworkAgentInfo nai) {
+ int score = 0;
+ int serial = 0;
+ if (nai != null) {
+ score = nai.getCurrentScore();
+ serial = nai.factorySerialNumber;
+ }
+ if (VDBG || DDBG){
+ log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
+ }
for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, 0,
- networkRequest);
+ nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
+ serial, networkRequest);
}
}
@@ -5148,8 +6048,11 @@
}
switch (notificationType) {
case ConnectivityManager.CALLBACK_AVAILABLE: {
- putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities));
+ putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions(
+ networkAgent.networkCapabilities, nri.mPid, nri.mUid));
putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
+ // For this notification, arg1 contains the blocked status.
+ msg.arg1 = arg1;
break;
}
case ConnectivityManager.CALLBACK_LOSING: {
@@ -5167,6 +6070,11 @@
putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
break;
}
+ case ConnectivityManager.CALLBACK_BLK_CHANGED: {
+ maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1 != 0);
+ msg.arg1 = arg1;
+ break;
+ }
}
msg.what = notificationType;
msg.setData(bundle);
@@ -5222,16 +6130,16 @@
private void makeDefault(NetworkAgentInfo newNetwork) {
if (DBG) log("Switching to new default network: " + newNetwork);
- setupDataActivityTracking(newNetwork);
+
try {
- mNetd.setDefaultNetId(newNetwork.network.netId);
+ mNMS.setDefaultNetId(newNetwork.network.netId);
} catch (Exception e) {
loge("Exception setting default network :" + e);
}
notifyLockdownVpn(newNetwork);
handleApplyDefaultProxy(newNetwork.linkProperties.getHttpProxy());
- updateTcpBufferSizes(newNetwork);
+ updateTcpBufferSizes(newNetwork.linkProperties.getTcpBufferSizes());
mDnsManager.setDefaultDnsSystemProperties(newNetwork.linkProperties.getDnsServers());
notifyIfacesChangedForNetworkStats();
// Fix up the NetworkCapabilities of any VPNs that don't specify underlying networks.
@@ -5300,12 +6208,12 @@
final boolean wasBackgroundNetwork = newNetwork.isBackgroundNetwork();
final int score = newNetwork.getCurrentScore();
- if (VDBG) log("rematching " + newNetwork.name());
+ if (VDBG || DDBG) log("rematching " + newNetwork.name());
// Find and migrate to this Network any NetworkRequests for
// which this network is now the best.
- ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();
- ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<NetworkRequestInfo>();
+ ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<>();
+ ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<>();
NetworkCapabilities nc = newNetwork.networkCapabilities;
if (VDBG) log(" network has: " + nc);
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
@@ -5331,7 +6239,7 @@
if (satisfies) {
// next check if it's better than any current network we're using for
// this request
- if (VDBG) {
+ if (VDBG || DDBG) {
log("currentScore = " +
(currentNetwork != null ? currentNetwork.getCurrentScore() : 0) +
", newScore = " + score);
@@ -5339,12 +6247,14 @@
if (currentNetwork == null || currentNetwork.getCurrentScore() < score) {
if (VDBG) log("rematch for " + newNetwork.name());
if (currentNetwork != null) {
- if (VDBG) log(" accepting network in place of " + currentNetwork.name());
+ if (VDBG || DDBG){
+ log(" accepting network in place of " + currentNetwork.name());
+ }
currentNetwork.removeRequest(nri.request.requestId);
currentNetwork.lingerRequest(nri.request, now, mLingerDelayMs);
affectedNetworks.add(currentNetwork);
} else {
- if (VDBG) log(" accepting network in place of null");
+ if (VDBG || DDBG) log(" accepting network in place of null");
}
newNetwork.unlingerRequest(nri.request);
setNetworkForRequest(nri.request.requestId, newNetwork);
@@ -5355,10 +6265,10 @@
keep = true;
// Tell NetworkFactories about the new score, so they can stop
// trying to connect if they know they cannot match it.
- // TODO - this could get expensive if we have alot of requests for this
+ // TODO - this could get expensive if we have a lot of requests for this
// network. Think about if there is a way to reduce this. Push
// netid->request mapping to each factory?
- sendUpdatedScoreToFactories(nri.request, score);
+ sendUpdatedScoreToFactories(nri.request, newNetwork);
if (isDefaultRequest(nri)) {
isNewDefault = true;
oldDefaultNetwork = currentNetwork;
@@ -5382,7 +6292,7 @@
newNetwork.removeRequest(nri.request.requestId);
if (currentNetwork == newNetwork) {
clearNetworkForRequest(nri.request.requestId);
- sendUpdatedScoreToFactories(nri.request, 0);
+ sendUpdatedScoreToFactories(nri.request, null);
} else {
Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
newNetwork.name() +
@@ -5399,6 +6309,7 @@
}
}
if (isNewDefault) {
+ updateDataActivityTracking(newNetwork, oldDefaultNetwork);
// Notify system services that this network is up.
makeDefault(newNetwork);
// Log 0 -> X and Y -> X default network transitions, where X is the new default.
@@ -5476,7 +6387,7 @@
// This has to happen after the notifyNetworkCallbacks as that tickles each
// ConnectivityManager instance so that legacy requests correctly bind dns
- // requests to this network. The legacy users are listening for this bcast
+ // requests to this network. The legacy users are listening for this broadcast
// and will generally do a dns request so they can ensureRouteToHost and if
// they do that before the callbacks happen they'll use the default network.
//
@@ -5541,7 +6452,7 @@
// TODO: This may get slow. The "changed" parameter is provided for future optimization
// to avoid the slowness. It is not simply enough to process just "changed", for
// example in the case where "changed"'s score decreases and another network should begin
- // satifying a NetworkRequest that "changed" currently satisfies.
+ // satisfying a NetworkRequest that "changed" currently satisfies.
// Optimization: Only reprocess "changed" if its score improved. This is safe because it
// can only add more NetworkRequests satisfied by "changed", and this is exactly what
@@ -5618,48 +6529,37 @@
// A network that has just connected has zero requests and is thus a foreground network.
networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
- try {
- // This should never fail. Specifying an already in use NetID will cause failure.
- if (networkAgent.isVPN()) {
- mNetd.createVirtualNetwork(networkAgent.network.netId,
- !networkAgent.linkProperties.getDnsServers().isEmpty(),
- (networkAgent.networkMisc == null ||
- !networkAgent.networkMisc.allowBypass));
- } else {
- mNetd.createPhysicalNetwork(networkAgent.network.netId,
- getNetworkPermission(networkAgent.networkCapabilities));
- }
- } catch (Exception e) {
- loge("Error creating network " + networkAgent.network.netId + ": "
- + e.getMessage());
- return;
- }
+ if (!createNativeNetwork(networkAgent)) return;
networkAgent.created = true;
}
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
networkAgent.everConnected = true;
- handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
- updateLinkProperties(networkAgent, null);
- notifyIfacesChangedForNetworkStats();
-
- networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
- scheduleUnvalidatedPrompt(networkAgent);
-
- if (networkAgent.isVPN()) {
- // Temporarily disable the default proxy (not global).
- synchronized (mProxyLock) {
- if (!mDefaultProxyDisabled) {
- mDefaultProxyDisabled = true;
- if (mGlobalProxy == null && mDefaultProxy != null) {
- sendProxyBroadcast(null);
- }
- }
- }
- // TODO: support proxy per network.
+ if (networkAgent.linkProperties == null) {
+ Slog.wtf(TAG, networkAgent.name() + " connected with null LinkProperties");
}
+ // NetworkCapabilities need to be set before sending the private DNS config to
+ // NetworkMonitor, otherwise NetworkMonitor cannot determine if validation is required.
+ synchronized (networkAgent) {
+ networkAgent.setNetworkCapabilities(networkAgent.networkCapabilities);
+ }
+ handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
+ updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
+ null);
+
+ // Until parceled LinkProperties are sent directly to NetworkMonitor, the connect
+ // command must be sent after updating LinkProperties to maximize chances of
+ // NetworkMonitor seeing the correct LinkProperties when starting.
+ // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
+ if (networkAgent.networkMisc.acceptPartialConnectivity) {
+ networkAgent.networkMonitor().setAcceptPartialConnectivity();
+ }
+ networkAgent.networkMonitor().notifyNetworkConnected(
+ networkAgent.linkProperties, networkAgent.networkCapabilities);
+ scheduleUnvalidatedPrompt(networkAgent);
+
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
// capabilities, so it only needs to be done once on initial connect, not every time the
@@ -5682,20 +6582,19 @@
} else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.asyncChannel.disconnect();
if (networkAgent.isVPN()) {
- synchronized (mProxyLock) {
- if (mDefaultProxyDisabled) {
- mDefaultProxyDisabled = false;
- if (mGlobalProxy == null && mDefaultProxy != null) {
- sendProxyBroadcast(mDefaultProxy);
- }
- }
- }
updateUids(networkAgent, networkAgent.networkCapabilities, null);
}
disconnectAndDestroyNetwork(networkAgent);
+ if (networkAgent.isVPN()) {
+ // As the active or bound network changes for apps, broadcast the default proxy, as
+ // apps may need to update their proxy data. This is called after disconnecting from
+ // VPN to make sure we do not broadcast the old proxy data.
+ // TODO(b/122649188): send the broadcast only to VPN users.
+ mProxyTracker.sendProxyBroadcast();
+ }
} else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
state == NetworkInfo.State.SUSPENDED) {
- // going into or coming out of SUSPEND: rescore and notify
+ // going into or coming out of SUSPEND: re-score and notify
if (networkAgent.getCurrentScore() != oldScore) {
rematchAllNetworksAndRequests(networkAgent, oldScore);
}
@@ -5711,7 +6610,7 @@
}
private void updateNetworkScore(NetworkAgentInfo nai, int score) {
- if (VDBG) log("updateNetworkScore for " + nai.name() + " to " + score);
+ if (VDBG || DDBG) log("updateNetworkScore for " + nai.name() + " to " + score);
if (score < 0) {
loge("updateNetworkScore for " + nai.name() + " got a negative score (" + score +
"). Bumping score to min of 0");
@@ -5738,10 +6637,80 @@
return;
}
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, 0);
+ final boolean metered = nai.networkCapabilities.isMetered();
+ final boolean blocked = isUidNetworkingWithVpnBlocked(nri.mUid, mUidRules.get(nri.mUid),
+ metered, mRestrictBackground);
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0);
}
- private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
+ /**
+ * Notify of the blocked state apps with a registered callback matching a given NAI.
+ *
+ * Unlike other callbacks, blocked status is different between each individual uid. So for
+ * any given nai, all requests need to be considered according to the uid who filed it.
+ *
+ * @param nai The target NetworkAgentInfo.
+ * @param oldMetered True if the previous network capabilities is metered.
+ * @param newRestrictBackground True if data saver is enabled.
+ */
+ private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered,
+ boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground) {
+
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest nr = nai.requestAt(i);
+ NetworkRequestInfo nri = mNetworkRequests.get(nr);
+ final int uidRules = mUidRules.get(nri.mUid);
+ final boolean oldBlocked, newBlocked;
+ // mVpns lock needs to be hold here to ensure that the active VPN cannot be changed
+ // between these two calls.
+ synchronized (mVpns) {
+ oldBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, oldMetered,
+ oldRestrictBackground);
+ newBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, newMetered,
+ newRestrictBackground);
+ }
+ if (oldBlocked != newBlocked) {
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
+ encodeBool(newBlocked));
+ }
+ }
+ }
+
+ /**
+ * Notify apps with a given UID of the new blocked state according to new uid rules.
+ * @param uid The uid for which the rules changed.
+ * @param newRules The new rules to apply.
+ */
+ private void maybeNotifyNetworkBlockedForNewUidRules(int uid, int newRules) {
+ for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ final boolean metered = nai.networkCapabilities.isMetered();
+ final boolean oldBlocked, newBlocked;
+ // TODO: Consider that doze mode or turn on/off battery saver would deliver lots of uid
+ // rules changed event. And this function actually loop through all connected nai and
+ // its requests. It seems that mVpns lock will be grabbed frequently in this case.
+ // Reduce the number of locking or optimize the use of lock are likely needed in future.
+ synchronized (mVpns) {
+ oldBlocked = isUidNetworkingWithVpnBlocked(
+ uid, mUidRules.get(uid), metered, mRestrictBackground);
+ newBlocked = isUidNetworkingWithVpnBlocked(
+ uid, newRules, metered, mRestrictBackground);
+ }
+ if (oldBlocked == newBlocked) {
+ continue;
+ }
+ final int arg = encodeBool(newBlocked);
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest nr = nai.requestAt(i);
+ NetworkRequestInfo nri = mNetworkRequests.get(nr);
+ if (nri != null && nri.mUid == uid) {
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, arg);
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ protected void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
// The NetworkInfo we actually send out has no bearing on the real
// state of affairs. For example, if the default connection is mobile,
// and a request for HIPRI has just gone away, we need to pretend that
@@ -5788,7 +6757,7 @@
}
protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) {
- if (VDBG) {
+ if (VDBG || DDBG) {
String notification = ConnectivityManager.getCallbackName(notifyType);
log("notifyType " + notification + " for " + networkAgent.name());
}
@@ -5831,7 +6800,7 @@
/**
* Notify NetworkStatsService that the set of active ifaces has changed, or that one of the
- * properties tracked by NetworkStatsService on an active iface has changed.
+ * active iface's tracked properties has changed.
*/
private void notifyIfacesChangedForNetworkStats() {
ensureRunningOnConnectivityServiceThread();
@@ -5840,9 +6809,11 @@
if (activeLinkProperties != null) {
activeIface = activeLinkProperties.getInterfaceName();
}
+
+ final VpnInfo[] vpnInfos = getAllVpnInfo();
try {
mStatsService.forceUpdateIfaces(
- getDefaultNetworks(), getAllVpnInfo(), getAllNetworkState(), activeIface);
+ getDefaultNetworks(), getAllNetworkState(), activeIface, vpnInfos);
} catch (Exception ignored) {
}
}
@@ -5886,23 +6857,54 @@
@Override
public String getCaptivePortalServerUrl() {
enforceConnectivityInternalPermission();
- return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext);
+ String settingUrl = mContext.getResources().getString(
+ R.string.config_networkCaptivePortalServerUrl);
+
+ if (!TextUtils.isEmpty(settingUrl)) {
+ return settingUrl;
+ }
+
+ settingUrl = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+ if (!TextUtils.isEmpty(settingUrl)) {
+ return settingUrl;
+ }
+
+ return DEFAULT_CAPTIVE_PORTAL_HTTP_URL;
}
@Override
- public void startNattKeepalive(Network network, int intervalSeconds, Messenger messenger,
- IBinder binder, String srcAddr, int srcPort, String dstAddr) {
+ public void startNattKeepalive(Network network, int intervalSeconds,
+ ISocketKeepaliveCallback cb, String srcAddr, int srcPort, String dstAddr) {
enforceKeepalivePermission();
mKeepaliveTracker.startNattKeepalive(
- getNetworkAgentInfoForNetwork(network),
- intervalSeconds, messenger, binder,
- srcAddr, srcPort, dstAddr, ConnectivityManager.PacketKeepalive.NATT_PORT);
+ getNetworkAgentInfoForNetwork(network), null /* fd */,
+ intervalSeconds, cb,
+ srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT);
+ }
+
+ @Override
+ public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
+ int intervalSeconds, ISocketKeepaliveCallback cb, String srcAddr,
+ String dstAddr) {
+ mKeepaliveTracker.startNattKeepalive(
+ getNetworkAgentInfoForNetwork(network), fd, resourceId,
+ intervalSeconds, cb,
+ srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+ }
+
+ @Override
+ public void startTcpKeepalive(Network network, FileDescriptor fd, int intervalSeconds,
+ ISocketKeepaliveCallback cb) {
+ enforceKeepalivePermission();
+ mKeepaliveTracker.startTcpKeepalive(
+ getNetworkAgentInfoForNetwork(network), fd, intervalSeconds, cb);
}
@Override
public void stopKeepalive(Network network, int slot) {
mHandler.sendMessage(mHandler.obtainMessage(
- NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network));
+ NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE, slot, SocketKeepalive.SUCCESS, network));
}
@Override
@@ -5915,6 +6917,11 @@
final int userId = UserHandle.getCallingUserId();
+ Binder.withCleanCallingIdentity(() -> {
+ final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
+ ipMemoryStore.factoryReset();
+ });
+
// Turn airplane mode off
setAirplaneMode(false);
@@ -5931,7 +6938,7 @@
synchronized (mVpns) {
final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
if (alwaysOnPackage != null) {
- setAlwaysOnVpnPackage(userId, null, false);
+ setAlwaysOnVpnPackage(userId, null, false, null);
setVpnPackageAuthorization(alwaysOnPackage, userId, false);
}
@@ -5979,12 +6986,6 @@
}
@VisibleForTesting
- public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo nai, NetworkRequest defaultRequest) {
- return new NetworkMonitor(context, handler, nai, defaultRequest);
- }
-
- @VisibleForTesting
MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
return new MultinetworkPolicyTracker(c, h, r);
}
@@ -6074,4 +7075,93 @@
pw.println(" Get airplane mode.");
}
}
+
+ @GuardedBy("mVpns")
+ private Vpn getVpnIfOwner() {
+ final int uid = Binder.getCallingUid();
+ final int user = UserHandle.getUserId(uid);
+
+ final Vpn vpn = mVpns.get(user);
+ if (vpn == null) {
+ return null;
+ } else {
+ final VpnInfo info = vpn.getVpnInfo();
+ return (info == null || info.ownerUid != uid) ? null : vpn;
+ }
+ }
+
+ /**
+ * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission
+ * for testing.
+ */
+ private Vpn enforceActiveVpnOrNetworkStackPermission() {
+ if (checkNetworkStackPermission()) {
+ return null;
+ }
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ if (vpn != null) {
+ return vpn;
+ }
+ }
+ throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK "
+ + "permission");
+ }
+
+ /**
+ * @param connectionInfo the connection to resolve.
+ * @return {@code uid} if the connection is found and the app has permission to observe it
+ * (e.g., if it is associated with the calling VPN app's tunnel) or {@code INVALID_UID} if the
+ * connection is not found.
+ */
+ public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
+ final Vpn vpn = enforceActiveVpnOrNetworkStackPermission();
+ if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) {
+ throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
+ }
+
+ final int uid = InetDiagMessage.getConnectionOwnerUid(connectionInfo.protocol,
+ connectionInfo.local, connectionInfo.remote);
+
+ /* Filter out Uids not associated with the VPN. */
+ if (vpn != null && !vpn.appliesToUid(uid)) {
+ return INVALID_UID;
+ }
+
+ return uid;
+ }
+
+ @Override
+ public boolean isCallerCurrentAlwaysOnVpnApp() {
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ return vpn != null && vpn.getAlwaysOn();
+ }
+ }
+
+ @Override
+ public boolean isCallerCurrentAlwaysOnVpnLockdownApp() {
+ synchronized (mVpns) {
+ Vpn vpn = getVpnIfOwner();
+ return vpn != null && vpn.getLockdown();
+ }
+ }
+
+ /**
+ * Returns a IBinder to a TestNetworkService. Will be lazily created as needed.
+ *
+ * <p>The TestNetworkService must be run in the system server due to TUN creation.
+ */
+ @Override
+ public IBinder startOrGetTestNetworkService() {
+ synchronized (mTNSLock) {
+ TestNetworkService.enforceTestNetworkPermissions(mContext);
+
+ if (mTNS == null) {
+ mTNS = new TestNetworkService(mContext, mNMS);
+ }
+
+ return mTNS;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
new file mode 100644
index 0000000..d19d2dd
--- /dev/null
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2018 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 com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.ITestNetworkManager;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.RouteInfo;
+import android.net.StringNetworkSpecifier;
+import android.net.TestNetworkInterface;
+import android.net.util.NetdService;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.UncheckedIOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** @hide */
+class TestNetworkService extends ITestNetworkManager.Stub {
+ @NonNull private static final String TAG = TestNetworkService.class.getSimpleName();
+ @NonNull private static final String TEST_NETWORK_TYPE = "TEST_NETWORK";
+ @NonNull private static final String TEST_TUN_PREFIX = "testtun";
+ @NonNull private static final String TEST_TAP_PREFIX = "testtap";
+ @NonNull private static final AtomicInteger sTestTunIndex = new AtomicInteger();
+
+ @NonNull private final Context mContext;
+ @NonNull private final INetworkManagementService mNMS;
+ @NonNull private final INetd mNetd;
+
+ @NonNull private final HandlerThread mHandlerThread;
+ @NonNull private final Handler mHandler;
+
+ // Native method stubs
+ private static native int jniCreateTunTap(boolean isTun, @NonNull String iface);
+
+ @VisibleForTesting
+ protected TestNetworkService(
+ @NonNull Context context, @NonNull INetworkManagementService netManager) {
+ mHandlerThread = new HandlerThread("TestNetworkServiceThread");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ mContext = checkNotNull(context, "missing Context");
+ mNMS = checkNotNull(netManager, "missing INetworkManagementService");
+ mNetd = checkNotNull(NetdService.getInstance(), "could not get netd instance");
+ }
+
+ /**
+ * Create a TUN or TAP interface with the given interface name and link addresses
+ *
+ * <p>This method will return the FileDescriptor to the interface. Close it to tear down the
+ * interface.
+ */
+ private TestNetworkInterface createInterface(boolean isTun, LinkAddress[] linkAddrs) {
+ enforceTestNetworkPermissions(mContext);
+
+ checkNotNull(linkAddrs, "missing linkAddrs");
+
+ String ifacePrefix = isTun ? TEST_TUN_PREFIX : TEST_TAP_PREFIX;
+ String iface = ifacePrefix + sTestTunIndex.getAndIncrement();
+ return Binder.withCleanCallingIdentity(
+ () -> {
+ try {
+ ParcelFileDescriptor tunIntf =
+ ParcelFileDescriptor.adoptFd(jniCreateTunTap(isTun, iface));
+ for (LinkAddress addr : linkAddrs) {
+ mNetd.interfaceAddAddress(
+ iface,
+ addr.getAddress().getHostAddress(),
+ addr.getPrefixLength());
+ }
+
+ return new TestNetworkInterface(tunIntf, iface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ /**
+ * Create a TUN interface with the given interface name and link addresses
+ *
+ * <p>This method will return the FileDescriptor to the TUN interface. Close it to tear down the
+ * TUN interface.
+ */
+ @Override
+ public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) {
+ return createInterface(true, linkAddrs);
+ }
+
+ /**
+ * Create a TAP interface with the given interface name
+ *
+ * <p>This method will return the FileDescriptor to the TAP interface. Close it to tear down the
+ * TAP interface.
+ */
+ @Override
+ public TestNetworkInterface createTapInterface() {
+ return createInterface(false, new LinkAddress[0]);
+ }
+
+ // Tracker for TestNetworkAgents
+ @GuardedBy("mTestNetworkTracker")
+ @NonNull
+ private final SparseArray<TestNetworkAgent> mTestNetworkTracker = new SparseArray<>();
+
+ public class TestNetworkAgent extends NetworkAgent implements IBinder.DeathRecipient {
+ private static final int NETWORK_SCORE = 1; // Use a low, non-zero score.
+
+ private final int mUid;
+ @NonNull private final NetworkInfo mNi;
+ @NonNull private final NetworkCapabilities mNc;
+ @NonNull private final LinkProperties mLp;
+
+ @GuardedBy("mBinderLock")
+ @NonNull
+ private IBinder mBinder;
+
+ @NonNull private final Object mBinderLock = new Object();
+
+ private TestNetworkAgent(
+ @NonNull Looper looper,
+ @NonNull Context context,
+ @NonNull NetworkInfo ni,
+ @NonNull NetworkCapabilities nc,
+ @NonNull LinkProperties lp,
+ int uid,
+ @NonNull IBinder binder)
+ throws RemoteException {
+ super(looper, context, TEST_NETWORK_TYPE, ni, nc, lp, NETWORK_SCORE);
+
+ mUid = uid;
+ mNi = ni;
+ mNc = nc;
+ mLp = lp;
+
+ synchronized (mBinderLock) {
+ mBinder = binder; // Binder null-checks in create()
+
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ throw e; // Abort, signal failure up the stack.
+ }
+ }
+ }
+
+ /**
+ * If the Binder object dies, this function is called to free the resources of this
+ * TestNetworkAgent
+ */
+ @Override
+ public void binderDied() {
+ teardown();
+ }
+
+ @Override
+ protected void unwanted() {
+ teardown();
+ }
+
+ private void teardown() {
+ mNi.setDetailedState(DetailedState.DISCONNECTED, null, null);
+ mNi.setIsAvailable(false);
+ sendNetworkInfo(mNi);
+
+ // Synchronize on mBinderLock to ensure that unlinkToDeath is never called more than
+ // once (otherwise it could throw an exception)
+ synchronized (mBinderLock) {
+ // If mBinder is null, this Test Network has already been cleaned up.
+ if (mBinder == null) return;
+ mBinder.unlinkToDeath(this, 0);
+ mBinder = null;
+ }
+
+ // Has to be in TestNetworkAgent to ensure all teardown codepaths properly clean up
+ // resources, even for binder death or unwanted calls.
+ synchronized (mTestNetworkTracker) {
+ mTestNetworkTracker.remove(netId);
+ }
+ }
+ }
+
+ private TestNetworkAgent registerTestNetworkAgent(
+ @NonNull Looper looper,
+ @NonNull Context context,
+ @NonNull String iface,
+ @Nullable LinkProperties lp,
+ boolean isMetered,
+ int callingUid,
+ @NonNull IBinder binder)
+ throws RemoteException, SocketException {
+ checkNotNull(looper, "missing Looper");
+ checkNotNull(context, "missing Context");
+ // iface and binder validity checked by caller
+
+ // Build network info with special testing type
+ NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_TEST, 0, TEST_NETWORK_TYPE, "");
+ ni.setDetailedState(DetailedState.CONNECTED, null, null);
+ ni.setIsAvailable(true);
+
+ // Build narrow set of NetworkCapabilities, useful only for testing
+ NetworkCapabilities nc = new NetworkCapabilities();
+ nc.clearAll(); // Remove default capabilities.
+ nc.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ nc.setNetworkSpecifier(new StringNetworkSpecifier(iface));
+ if (!isMetered) {
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ }
+
+ // Build LinkProperties
+ if (lp == null) {
+ lp = new LinkProperties();
+ } else {
+ lp = new LinkProperties(lp);
+ // Use LinkAddress(es) from the interface itself to minimize how much the caller
+ // is trusted.
+ lp.setLinkAddresses(new ArrayList<>());
+ }
+ lp.setInterfaceName(iface);
+
+ // Find the currently assigned addresses, and add them to LinkProperties
+ boolean allowIPv4 = false, allowIPv6 = false;
+ NetworkInterface netIntf = NetworkInterface.getByName(iface);
+ checkNotNull(netIntf, "No such network interface found: " + netIntf);
+
+ for (InterfaceAddress intfAddr : netIntf.getInterfaceAddresses()) {
+ lp.addLinkAddress(
+ new LinkAddress(intfAddr.getAddress(), intfAddr.getNetworkPrefixLength()));
+
+ if (intfAddr.getAddress() instanceof Inet6Address) {
+ allowIPv6 |= !intfAddr.getAddress().isLinkLocalAddress();
+ } else if (intfAddr.getAddress() instanceof Inet4Address) {
+ allowIPv4 = true;
+ }
+ }
+
+ // Add global routes (but as non-default, non-internet providing network)
+ if (allowIPv4) {
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null, iface));
+ }
+ if (allowIPv6) {
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null, iface));
+ }
+
+ return new TestNetworkAgent(looper, context, ni, nc, lp, callingUid, binder);
+ }
+
+ /**
+ * Sets up a Network with extremely limited privileges, guarded by the MANAGE_TEST_NETWORKS
+ * permission.
+ *
+ * <p>This method provides a Network that is useful only for testing.
+ */
+ @Override
+ public void setupTestNetwork(
+ @NonNull String iface,
+ @Nullable LinkProperties lp,
+ boolean isMetered,
+ @NonNull IBinder binder) {
+ enforceTestNetworkPermissions(mContext);
+
+ checkNotNull(iface, "missing Iface");
+ checkNotNull(binder, "missing IBinder");
+
+ if (!(iface.startsWith(INetd.IPSEC_INTERFACE_PREFIX)
+ || iface.startsWith(TEST_TUN_PREFIX))) {
+ throw new IllegalArgumentException(
+ "Cannot create network for non ipsec, non-testtun interface");
+ }
+
+ // Setup needs to be done with NETWORK_STACK privileges.
+ int callingUid = Binder.getCallingUid();
+ Binder.withCleanCallingIdentity(
+ () -> {
+ try {
+ mNMS.setInterfaceUp(iface);
+
+ // Synchronize all accesses to mTestNetworkTracker to prevent the case
+ // where:
+ // 1. TestNetworkAgent successfully binds to death of binder
+ // 2. Before it is added to the mTestNetworkTracker, binder dies,
+ // binderDied() is called (on a different thread)
+ // 3. This thread is pre-empted, put() is called after remove()
+ synchronized (mTestNetworkTracker) {
+ TestNetworkAgent agent =
+ registerTestNetworkAgent(
+ mHandler.getLooper(),
+ mContext,
+ iface,
+ lp,
+ isMetered,
+ callingUid,
+ binder);
+
+ mTestNetworkTracker.put(agent.netId, agent);
+ }
+ } catch (SocketException e) {
+ throw new UncheckedIOException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ }
+
+ /** Teardown a test network */
+ @Override
+ public void teardownTestNetwork(int netId) {
+ enforceTestNetworkPermissions(mContext);
+
+ final TestNetworkAgent agent;
+ synchronized (mTestNetworkTracker) {
+ agent = mTestNetworkTracker.get(netId);
+ }
+
+ if (agent == null) {
+ return; // Already torn down
+ } else if (agent.mUid != Binder.getCallingUid()) {
+ throw new SecurityException("Attempted to modify other user's test networks");
+ }
+
+ // Safe to be called multiple times.
+ agent.teardown();
+ }
+
+ private static final String PERMISSION_NAME =
+ android.Manifest.permission.MANAGE_TEST_NETWORKS;
+
+ public static void enforceTestNetworkPermissions(@NonNull Context context) {
+ context.enforceCallingOrSelfPermission(PERMISSION_NAME, "TestNetworkService");
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/AutodestructReference.java b/services/core/java/com/android/server/connectivity/AutodestructReference.java
new file mode 100644
index 0000000..009a43e
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/AutodestructReference.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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 android.annotation.NonNull;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A ref that autodestructs at the first usage of it.
+ * @param <T> The type of the held object
+ * @hide
+ */
+public class AutodestructReference<T> {
+ private final AtomicReference<T> mHeld;
+ public AutodestructReference(@NonNull T obj) {
+ if (null == obj) throw new NullPointerException("Autodestruct reference to null");
+ mHeld = new AtomicReference<>(obj);
+ }
+
+ /** Get the ref and destruct it. NPE if already destructed. */
+ @NonNull
+ public T getAndDestroy() {
+ final T obj = mHeld.getAndSet(null);
+ if (null == obj) throw new NullPointerException("Already autodestructed");
+ return obj;
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index c0beb37..2321afb 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -18,10 +18,9 @@
import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
-import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
+import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
@@ -31,23 +30,23 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.net.IDnsResolver;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkUtils;
+import android.net.ResolverParamsParcel;
import android.net.Uri;
-import android.net.dns.ResolvUtil;
+import android.net.shared.PrivateDnsConfig;
import android.os.Binder;
-import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
-import com.android.server.connectivity.MockableSystemProperties;
-
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -55,10 +54,8 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
import java.util.Set;
-import java.util.StringJoiner;
+import java.util.stream.Collectors;
/**
@@ -124,43 +121,6 @@
private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
- public static class PrivateDnsConfig {
- public final boolean useTls;
- public final String hostname;
- public final InetAddress[] ips;
-
- public PrivateDnsConfig() {
- this(false);
- }
-
- public PrivateDnsConfig(boolean useTls) {
- this.useTls = useTls;
- this.hostname = "";
- this.ips = new InetAddress[0];
- }
-
- public PrivateDnsConfig(String hostname, InetAddress[] ips) {
- this.useTls = !TextUtils.isEmpty(hostname);
- this.hostname = useTls ? hostname : "";
- this.ips = (ips != null) ? ips : new InetAddress[0];
- }
-
- public PrivateDnsConfig(PrivateDnsConfig cfg) {
- useTls = cfg.useTls;
- hostname = cfg.hostname;
- ips = cfg.ips;
- }
-
- public boolean inStrictMode() {
- return useTls && !TextUtils.isEmpty(hostname);
- }
-
- public String toString() {
- return PrivateDnsConfig.class.getSimpleName() +
- "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
- }
- }
-
public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
final String mode = getPrivateDnsMode(cr);
@@ -174,15 +134,6 @@
return new PrivateDnsConfig(useTls);
}
- public static PrivateDnsConfig tryBlockingResolveOf(Network network, String name) {
- try {
- final InetAddress[] ips = ResolvUtil.blockingResolveAllLocally(network, name);
- return new PrivateDnsConfig(name, ips);
- } catch (UnknownHostException uhe) {
- return new PrivateDnsConfig(name, null);
- }
- }
-
public static Uri[] getPrivateDnsSettingsUris() {
return new Uri[]{
Settings.Global.getUriFor(PRIVATE_DNS_DEFAULT_MODE),
@@ -281,7 +232,7 @@
private final Context mContext;
private final ContentResolver mContentResolver;
- private final INetworkManagementService mNMS;
+ private final IDnsResolver mDnsResolver;
private final MockableSystemProperties mSystemProperties;
// TODO: Replace these Maps with SparseArrays.
private final Map<Integer, PrivateDnsConfig> mPrivateDnsMap;
@@ -295,10 +246,10 @@
private String mPrivateDnsMode;
private String mPrivateDnsSpecifier;
- public DnsManager(Context ctx, INetworkManagementService nms, MockableSystemProperties sp) {
+ public DnsManager(Context ctx, IDnsResolver dnsResolver, MockableSystemProperties sp) {
mContext = ctx;
mContentResolver = mContext.getContentResolver();
- mNMS = nms;
+ mDnsResolver = dnsResolver;
mSystemProperties = sp;
mPrivateDnsMap = new HashMap<>();
mPrivateDnsValidationMap = new HashMap<>();
@@ -355,11 +306,9 @@
public void setDnsConfigurationForNetwork(
int netId, LinkProperties lp, boolean isDefaultNetwork) {
- final String[] assignedServers = NetworkUtils.makeStrings(lp.getDnsServers());
- final String[] domainStrs = getDomainStrings(lp.getDomains());
updateParametersSettings();
- final int[] params = { mSampleValidity, mSuccessThreshold, mMinSamples, mMaxSamples };
+ final ResolverParamsParcel paramsParcel = new ResolverParamsParcel();
// We only use the PrivateDnsConfig data pushed to this class instance
// from ConnectivityService because it works in coordination with
@@ -373,33 +322,45 @@
final boolean useTls = privateDnsCfg.useTls;
final boolean strictMode = privateDnsCfg.inStrictMode();
- final String tlsHostname = strictMode ? privateDnsCfg.hostname : "";
- final String[] tlsServers =
+ paramsParcel.netId = netId;
+ paramsParcel.sampleValiditySeconds = mSampleValidity;
+ paramsParcel.successThreshold = mSuccessThreshold;
+ paramsParcel.minSamples = mMinSamples;
+ paramsParcel.maxSamples = mMaxSamples;
+ paramsParcel.servers = NetworkUtils.makeStrings(lp.getDnsServers());
+ paramsParcel.domains = getDomainStrings(lp.getDomains());
+ paramsParcel.tlsName = strictMode ? privateDnsCfg.hostname : "";
+ paramsParcel.tlsServers =
strictMode ? NetworkUtils.makeStrings(
Arrays.stream(privateDnsCfg.ips)
.filter((ip) -> lp.isReachable(ip))
.collect(Collectors.toList()))
- : useTls ? assignedServers // Opportunistic
+ : useTls ? paramsParcel.servers // Opportunistic
: new String[0]; // Off
-
+ paramsParcel.tlsFingerprints = new String[0];
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
if (!mPrivateDnsValidationMap.containsKey(netId)) {
mPrivateDnsValidationMap.put(netId, new PrivateDnsValidationStatuses());
}
- mPrivateDnsValidationMap.get(netId).updateTrackedDnses(tlsServers, tlsHostname);
+ mPrivateDnsValidationMap.get(netId).updateTrackedDnses(paramsParcel.tlsServers,
+ paramsParcel.tlsName);
} else {
mPrivateDnsValidationMap.remove(netId);
}
- Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %s, %s, %s)",
- netId, Arrays.toString(assignedServers), Arrays.toString(domainStrs),
- Arrays.toString(params), tlsHostname, Arrays.toString(tlsServers)));
+ Slog.d(TAG, String.format("setDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
+ + "%d, %d, %s, %s)", paramsParcel.netId, Arrays.toString(paramsParcel.servers),
+ Arrays.toString(paramsParcel.domains), paramsParcel.sampleValiditySeconds,
+ paramsParcel.successThreshold, paramsParcel.minSamples,
+ paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
+ paramsParcel.retryCount, paramsParcel.tlsName,
+ Arrays.toString(paramsParcel.tlsServers)));
+
try {
- mNMS.setDnsConfigurationForNetwork(
- netId, assignedServers, domainStrs, params, tlsHostname, tlsServers);
- } catch (Exception e) {
+ mDnsResolver.setResolverConfiguration(paramsParcel);
+ } catch (RemoteException | ServiceSpecificException e) {
Slog.e(TAG, "Error setting DNS configuration: " + e);
return;
}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 0f8fc17..9bae902 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -16,46 +16,67 @@
package com.android.server.connectivity;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.connectivity.NetworkAgentInfo;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.PacketKeepalive;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NattSocketKeepalive.NATT_PORT;
+import static android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER;
+import static android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER;
+import static android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE;
+import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE;
+import static android.net.SocketKeepalive.BINDER_DIED;
+import static android.net.SocketKeepalive.DATA_RECEIVED;
+import static android.net.SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
+import static android.net.SocketKeepalive.ERROR_INVALID_INTERVAL;
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
+import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
+import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
+import static android.net.SocketKeepalive.NO_KEEPALIVE;
+import static android.net.SocketKeepalive.SUCCESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ISocketKeepaliveCallback;
import android.net.KeepalivePacketData;
-import android.net.LinkAddress;
+import android.net.NattKeepalivePacketData;
import android.net.NetworkAgent;
import android.net.NetworkUtils;
+import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.SocketKeepalive.InvalidSocketException;
+import android.net.TcpKeepalivePacketData;
import android.net.util.IpUtils;
+import android.net.util.KeepaliveUtils;
import android.os.Binder;
-import android.os.IBinder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
-import android.os.Messenger;
import android.os.Process;
import android.os.RemoteException;
-import android.system.OsConstants;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import android.util.Pair;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import com.android.internal.R;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
-import static android.net.ConnectivityManager.PacketKeepalive.*;
-import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
-
/**
- * Manages packet keepalive requests.
+ * Manages socket keepalive requests.
*
* Provides methods to stop and start keepalive requests, and keeps track of keepalives across all
* networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its
- * methods must be called only from the ConnectivityService handler thread.
+ * handle* methods must be called only from the ConnectivityService handler thread.
*/
public class KeepaliveTracker {
@@ -68,49 +89,112 @@
private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
new HashMap<> ();
private final Handler mConnectivityServiceHandler;
+ @NonNull
+ private final TcpKeepaliveController mTcpController;
+ @NonNull
+ private final Context mContext;
- public KeepaliveTracker(Handler handler) {
+ // Supported keepalive count for each transport type, can be configured through
+ // config_networkSupportedKeepaliveCount. For better error handling, use
+ // {@link getSupportedKeepalivesForNetworkCapabilities} instead of direct access.
+ @NonNull
+ private final int[] mSupportedKeepalives;
+
+ // Reserved privileged keepalive slots per transport. Caller's permission will be enforced if
+ // the number of remaining keepalive slots is less than or equal to the threshold.
+ private final int mReservedPrivilegedSlots;
+
+ // Allowed unprivileged keepalive slots per uid. Caller's permission will be enforced if
+ // the number of remaining keepalive slots is less than or equal to the threshold.
+ private final int mAllowedUnprivilegedSlotsForUid;
+
+ public KeepaliveTracker(Context context, Handler handler) {
mConnectivityServiceHandler = handler;
+ mTcpController = new TcpKeepaliveController(handler);
+ mContext = context;
+ mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
+ mReservedPrivilegedSlots = mContext.getResources().getInteger(
+ R.integer.config_reservedPrivilegedKeepaliveSlots);
+ mAllowedUnprivilegedSlotsForUid = mContext.getResources().getInteger(
+ R.integer.config_allowedUnprivilegedKeepalivePerUid);
}
/**
- * Tracks information about a packet keepalive.
+ * Tracks information about a socket keepalive.
*
* All information about this keepalive is known at construction time except the slot number,
* which is only returned when the hardware has successfully started the keepalive.
*/
class KeepaliveInfo implements IBinder.DeathRecipient {
- // Bookkeping data.
- private final Messenger mMessenger;
- private final IBinder mBinder;
+ // Bookkeeping data.
+ private final ISocketKeepaliveCallback mCallback;
private final int mUid;
private final int mPid;
+ private final boolean mPrivileged;
private final NetworkAgentInfo mNai;
+ private final int mType;
+ private final FileDescriptor mFd;
- /** Keepalive slot. A small integer that identifies this keepalive among the ones handled
- * by this network. */
- private int mSlot = PacketKeepalive.NO_KEEPALIVE;
+ public static final int TYPE_NATT = 1;
+ public static final int TYPE_TCP = 2;
+
+ // Keepalive slot. A small integer that identifies this keepalive among the ones handled
+ // by this network.
+ private int mSlot = NO_KEEPALIVE;
// Packet data.
private final KeepalivePacketData mPacket;
private final int mInterval;
- // Whether the keepalive is started or not.
- public boolean isStarted;
+ // Whether the keepalive is started or not. The initial state is NOT_STARTED.
+ private static final int NOT_STARTED = 1;
+ private static final int STARTING = 2;
+ private static final int STARTED = 3;
+ private static final int STOPPING = 4;
+ private int mStartedState = NOT_STARTED;
- public KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai,
- KeepalivePacketData packet, int interval) {
- mMessenger = messenger;
- mBinder = binder;
+ KeepaliveInfo(@NonNull ISocketKeepaliveCallback callback,
+ @NonNull NetworkAgentInfo nai,
+ @NonNull KeepalivePacketData packet,
+ int interval,
+ int type,
+ @Nullable FileDescriptor fd) throws InvalidSocketException {
+ mCallback = callback;
mPid = Binder.getCallingPid();
mUid = Binder.getCallingUid();
+ mPrivileged = (PERMISSION_GRANTED == mContext.checkPermission(PERMISSION, mPid, mUid));
mNai = nai;
mPacket = packet;
mInterval = interval;
+ mType = type;
+
+ // For SocketKeepalive, a dup of fd is kept in mFd so the source port from which the
+ // keepalives are sent cannot be reused by another app even if the fd gets closed by
+ // the user. A null is acceptable here for backward compatibility of PacketKeepalive
+ // API.
+ try {
+ if (fd != null) {
+ mFd = Os.dup(fd);
+ } else {
+ Log.d(TAG, toString() + " calls with null fd");
+ if (!mPrivileged) {
+ throw new SecurityException(
+ "null fd is not allowed for unprivileged access.");
+ }
+ if (mType == TYPE_TCP) {
+ throw new IllegalArgumentException(
+ "null fd is not allowed for tcp socket keepalives.");
+ }
+ mFd = null;
+ }
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot dup fd: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
try {
- mBinder.linkToDeath(this, 0);
+ mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
binderDied();
}
@@ -120,37 +204,39 @@
return mNai;
}
- public String toString() {
- return new StringBuffer("KeepaliveInfo [")
- .append(" network=").append(mNai.network)
- .append(" isStarted=").append(isStarted)
- .append(" ")
- .append(IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort))
- .append("->")
- .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort))
- .append(" interval=" + mInterval)
- .append(" packetData=" + HexDump.toHexString(mPacket.getPacket()))
- .append(" uid=").append(mUid).append(" pid=").append(mPid)
- .append(" ]")
- .toString();
+ private String startedStateString(final int state) {
+ switch (state) {
+ case NOT_STARTED : return "NOT_STARTED";
+ case STARTING : return "STARTING";
+ case STARTED : return "STARTED";
+ case STOPPING : return "STOPPING";
+ }
+ throw new IllegalArgumentException("Unknown state");
}
- /** Sends a message back to the application via its PacketKeepalive.Callback. */
- void notifyMessenger(int slot, int err) {
- KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err);
+ public String toString() {
+ return "KeepaliveInfo ["
+ + " type=" + mType
+ + " network=" + mNai.network
+ + " startedState=" + startedStateString(mStartedState)
+ + " "
+ + IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort)
+ + "->"
+ + IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)
+ + " interval=" + mInterval
+ + " uid=" + mUid + " pid=" + mPid + " privileged=" + mPrivileged
+ + " packetData=" + HexDump.toHexString(mPacket.getPacket())
+ + " ]";
}
/** Called when the application process is killed. */
public void binderDied() {
- // Not called from ConnectivityService handler thread, so send it a message.
- mConnectivityServiceHandler.obtainMessage(
- NetworkAgent.CMD_STOP_PACKET_KEEPALIVE,
- mSlot, PacketKeepalive.BINDER_DIED, mNai.network).sendToTarget();
+ stop(BINDER_DIED);
}
void unlinkDeathRecipient() {
- if (mBinder != null) {
- mBinder.unlinkToDeath(this, 0);
+ if (mCallback != null) {
+ mCallback.asBinder().unlinkToDeath(this, 0);
}
}
@@ -172,12 +258,63 @@
}
private int checkInterval() {
- return mInterval >= MIN_INTERVAL ? SUCCESS : ERROR_INVALID_INTERVAL;
+ if (mInterval < MIN_INTERVAL_SEC || mInterval > MAX_INTERVAL_SEC) {
+ return ERROR_INVALID_INTERVAL;
+ }
+ return SUCCESS;
+ }
+
+ private int checkPermission() {
+ final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
+ if (networkKeepalives == null) {
+ return ERROR_INVALID_NETWORK;
+ }
+
+ if (mPrivileged) return SUCCESS;
+
+ final int supported = KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
+ mSupportedKeepalives, mNai.networkCapabilities);
+
+ int takenUnprivilegedSlots = 0;
+ for (final KeepaliveInfo ki : networkKeepalives.values()) {
+ if (!ki.mPrivileged) ++takenUnprivilegedSlots;
+ }
+ if (takenUnprivilegedSlots > supported - mReservedPrivilegedSlots) {
+ return ERROR_INSUFFICIENT_RESOURCES;
+ }
+
+ // Count unprivileged keepalives for the same uid across networks.
+ int unprivilegedCountSameUid = 0;
+ for (final HashMap<Integer, KeepaliveInfo> kaForNetwork : mKeepalives.values()) {
+ for (final KeepaliveInfo ki : kaForNetwork.values()) {
+ if (ki.mUid == mUid) {
+ unprivilegedCountSameUid++;
+ }
+ }
+ }
+ if (unprivilegedCountSameUid > mAllowedUnprivilegedSlotsForUid) {
+ return ERROR_INSUFFICIENT_RESOURCES;
+ }
+ return SUCCESS;
+ }
+
+ private int checkLimit() {
+ final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
+ if (networkKeepalives == null) {
+ return ERROR_INVALID_NETWORK;
+ }
+ final int supported = KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
+ mSupportedKeepalives, mNai.networkCapabilities);
+ if (supported == 0) return ERROR_UNSUPPORTED;
+ if (networkKeepalives.size() > supported) return ERROR_INSUFFICIENT_RESOURCES;
+ return SUCCESS;
}
private int isValid() {
synchronized (mNai) {
int error = checkInterval();
+ if (error == SUCCESS) error = checkLimit();
+ if (error == SUCCESS) error = checkPermission();
if (error == SUCCESS) error = checkNetworkConnected();
if (error == SUCCESS) error = checkSourceAddress();
return error;
@@ -185,13 +322,38 @@
}
void start(int slot) {
+ mSlot = slot;
int error = isValid();
if (error == SUCCESS) {
- mSlot = slot;
Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name());
- mNai.asyncChannel.sendMessage(CMD_START_PACKET_KEEPALIVE, slot, mInterval, mPacket);
+ switch (mType) {
+ case TYPE_NATT:
+ mNai.asyncChannel.sendMessage(
+ CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */, mPacket);
+ mNai.asyncChannel
+ .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket);
+ break;
+ case TYPE_TCP:
+ try {
+ mTcpController.startSocketMonitor(mFd, this, mSlot);
+ } catch (InvalidSocketException e) {
+ handleStopKeepalive(mNai, mSlot, ERROR_INVALID_SOCKET);
+ return;
+ }
+ mNai.asyncChannel.sendMessage(
+ CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */, mPacket);
+ // TODO: check result from apf and notify of failure as needed.
+ mNai.asyncChannel
+ .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket);
+ break;
+ default:
+ Log.wtf(TAG, "Starting keepalive with unknown type: " + mType);
+ handleStopKeepalive(mNai, mSlot, error);
+ return;
+ }
+ mStartedState = STARTING;
} else {
- notifyMessenger(NO_KEEPALIVE, error);
+ handleStopKeepalive(mNai, mSlot, error);
return;
}
}
@@ -203,27 +365,74 @@
Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
}
}
- if (isStarted) {
- Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name());
- mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot);
+ Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name() + ": " + reason);
+ switch (mStartedState) {
+ case NOT_STARTED:
+ // Remove the reference of the keepalive that meet error before starting,
+ // e.g. invalid parameter.
+ cleanupStoppedKeepalive(mNai, mSlot);
+ break;
+ case STOPPING:
+ // Keepalive is already in stopping state, ignore.
+ return;
+ default:
+ mStartedState = STOPPING;
+ switch (mType) {
+ case TYPE_TCP:
+ mTcpController.stopSocketMonitor(mSlot);
+ // fall through
+ case TYPE_NATT:
+ mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
+ mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER,
+ mSlot);
+ break;
+ default:
+ Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType);
+ }
}
- // TODO: at the moment we unconditionally return failure here. In cases where the
- // NetworkAgent is alive, should we ask it to reply, so it can return failure?
- notifyMessenger(mSlot, reason);
+
+ // Close the duplicated fd that maintains the lifecycle of socket whenever
+ // keepalive is running.
+ if (mFd != null) {
+ try {
+ Os.close(mFd);
+ } catch (ErrnoException e) {
+ // This should not happen since system server controls the lifecycle of fd when
+ // keepalive offload is running.
+ Log.wtf(TAG, "Error closing fd for keepalive " + mSlot + ": " + e);
+ }
+ }
+
+ if (reason == SUCCESS) {
+ try {
+ mCallback.onStopped();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Discarded onStop callback: " + reason);
+ }
+ } else if (reason == DATA_RECEIVED) {
+ try {
+ mCallback.onDataReceived();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Discarded onDataReceived callback: " + reason);
+ }
+ } else {
+ notifyErrorCallback(mCallback, reason);
+ }
+
unlinkDeathRecipient();
}
+
+ void onFileDescriptorInitiatedStop(final int socketKeepaliveReason) {
+ handleStopKeepalive(mNai, mSlot, socketKeepaliveReason);
+ }
}
- void notifyMessenger(Messenger messenger, int slot, int err) {
- Message message = Message.obtain();
- message.what = EVENT_PACKET_KEEPALIVE;
- message.arg1 = slot;
- message.arg2 = err;
- message.obj = null;
+ void notifyErrorCallback(ISocketKeepaliveCallback cb, int error) {
+ if (DBG) Log.w(TAG, "Sending onError(" + error + ") callback");
try {
- messenger.send(message);
+ cb.onError(error);
} catch (RemoteException e) {
- // Process died?
+ Log.w(TAG, "Discarded onError(" + error + ") callback");
}
}
@@ -254,13 +463,15 @@
}
public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
- HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
+ final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
if (networkKeepalives != null) {
- for (KeepaliveInfo ki : networkKeepalives.values()) {
+ final ArrayList<KeepaliveInfo> kalist = new ArrayList(networkKeepalives.values());
+ for (KeepaliveInfo ki : kalist) {
ki.stop(reason);
+ // Clean up keepalives since the network agent is disconnected and unable to pass
+ // back asynchronous result of stop().
+ cleanupStoppedKeepalive(nai, ki.mSlot);
}
- networkKeepalives.clear();
- mKeepalives.remove(nai);
}
}
@@ -277,7 +488,25 @@
return;
}
ki.stop(reason);
+ // Clean up keepalives will be done as a result of calling ki.stop() after the slots are
+ // freed.
+ }
+
+ private void cleanupStoppedKeepalive(NetworkAgentInfo nai, int slot) {
+ String networkName = (nai == null) ? "(null)" : nai.name();
+ HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
+ if (networkKeepalives == null) {
+ Log.e(TAG, "Attempt to remove keepalive on nonexistent network " + networkName);
+ return;
+ }
+ KeepaliveInfo ki = networkKeepalives.get(slot);
+ if (ki == null) {
+ Log.e(TAG, "Attempt to remove nonexistent keepalive " + slot + " on " + networkName);
+ return;
+ }
networkKeepalives.remove(slot);
+ Log.d(TAG, "Remove keepalive " + slot + " on " + networkName + ", "
+ + networkKeepalives.size() + " remains.");
if (networkKeepalives.isEmpty()) {
mKeepalives.remove(nai);
}
@@ -299,7 +528,9 @@
}
}
- public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) {
+ /** Handle keepalive events from lower layer. */
+ public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai,
+ @NonNull Message message) {
int slot = message.arg1;
int reason = message.arg2;
@@ -308,31 +539,68 @@
ki = mKeepalives.get(nai).get(slot);
} catch(NullPointerException e) {}
if (ki == null) {
- Log.e(TAG, "Event for unknown keepalive " + slot + " on " + nai.name());
+ Log.e(TAG, "Event " + message.what + "," + slot + "," + reason
+ + " for unknown keepalive " + slot + " on " + nai.name());
return;
}
- if (reason == SUCCESS && !ki.isStarted) {
- // Keepalive successfully started.
- if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name());
- ki.isStarted = true;
- ki.notifyMessenger(slot, reason);
- } else {
- // Keepalive successfully stopped, or error.
- ki.isStarted = false;
- if (reason == SUCCESS) {
- if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name());
+ // This can be called in a number of situations :
+ // - startedState is STARTING.
+ // - reason is SUCCESS => go to STARTED.
+ // - reason isn't SUCCESS => it's an error starting. Go to NOT_STARTED and stop keepalive.
+ // - startedState is STARTED.
+ // - reason is SUCCESS => it's a success stopping. Go to NOT_STARTED and stop keepalive.
+ // - reason isn't SUCCESS => it's an error in exec. Go to NOT_STARTED and stop keepalive.
+ // The control is not supposed to ever come here if the state is NOT_STARTED. This is
+ // because in NOT_STARTED state, the code will switch to STARTING before sending messages
+ // to start, and the only way to NOT_STARTED is this function, through the edges outlined
+ // above : in all cases, keepalive gets stopped and can't restart without going into
+ // STARTING as messages are ordered. This also depends on the hardware processing the
+ // messages in order.
+ // TODO : clarify this code and get rid of mStartedState. Using a StateMachine is an
+ // option.
+ if (KeepaliveInfo.STARTING == ki.mStartedState) {
+ if (SUCCESS == reason) {
+ // Keepalive successfully started.
+ Log.d(TAG, "Started keepalive " + slot + " on " + nai.name());
+ ki.mStartedState = KeepaliveInfo.STARTED;
+ try {
+ ki.mCallback.onStarted(slot);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Discarded onStarted(" + slot + ") callback");
+ }
} else {
- if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason);
+ Log.d(TAG, "Failed to start keepalive " + slot + " on " + nai.name()
+ + ": " + reason);
+ // The message indicated some error trying to start: do call handleStopKeepalive.
+ handleStopKeepalive(nai, slot, reason);
}
- handleStopKeepalive(nai, slot, reason);
+ } else if (KeepaliveInfo.STOPPING == ki.mStartedState) {
+ // The message indicated result of stopping : clean up keepalive slots.
+ Log.d(TAG, "Stopped keepalive " + slot + " on " + nai.name()
+ + " stopped: " + reason);
+ ki.mStartedState = KeepaliveInfo.NOT_STARTED;
+ cleanupStoppedKeepalive(nai, slot);
+ } else {
+ Log.wtf(TAG, "Event " + message.what + "," + slot + "," + reason
+ + " for keepalive in wrong state: " + ki.toString());
}
}
- public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
- IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
+ /**
+ * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+ * {@link android.net.SocketKeepalive}.
+ **/
+ public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ @Nullable FileDescriptor fd,
+ int intervalSeconds,
+ @NonNull ISocketKeepaliveCallback cb,
+ @NonNull String srcAddrString,
+ int srcPort,
+ @NonNull String dstAddrString,
+ int dstPort) {
if (nai == null) {
- notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
+ notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
return;
}
@@ -341,26 +609,125 @@
srcAddress = NetworkUtils.numericToInetAddress(srcAddrString);
dstAddress = NetworkUtils.numericToInetAddress(dstAddrString);
} catch (IllegalArgumentException e) {
- notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_IP_ADDRESS);
+ notifyErrorCallback(cb, ERROR_INVALID_IP_ADDRESS);
return;
}
KeepalivePacketData packet;
try {
- packet = KeepalivePacketData.nattKeepalivePacket(
+ packet = NattKeepalivePacketData.nattKeepalivePacket(
srcAddress, srcPort, dstAddress, NATT_PORT);
- } catch (KeepalivePacketData.InvalidPacketException e) {
- notifyMessenger(messenger, NO_KEEPALIVE, e.error);
+ } catch (InvalidPacketException e) {
+ notifyErrorCallback(cb, e.error);
return;
}
- KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds);
+ KeepaliveInfo ki = null;
+ try {
+ ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
+ KeepaliveInfo.TYPE_NATT, fd);
+ } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
+ Log.e(TAG, "Fail to construct keepalive", e);
+ notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+ return;
+ }
Log.d(TAG, "Created keepalive: " + ki.toString());
mConnectivityServiceHandler.obtainMessage(
- NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget();
+ NetworkAgent.CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
+ }
+
+ /**
+ * Called by ConnectivityService to start TCP keepalive on a file descriptor.
+ *
+ * In order to offload keepalive for application correctly, sequence number, ack number and
+ * other fields are needed to form the keepalive packet. Thus, this function synchronously
+ * puts the socket into repair mode to get the necessary information. After the socket has been
+ * put into repair mode, the application cannot access the socket until reverted to normal.
+ *
+ * See {@link android.net.SocketKeepalive}.
+ **/
+ public void startTcpKeepalive(@Nullable NetworkAgentInfo nai,
+ @NonNull FileDescriptor fd,
+ int intervalSeconds,
+ @NonNull ISocketKeepaliveCallback cb) {
+ if (nai == null) {
+ notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
+ return;
+ }
+
+ final TcpKeepalivePacketData packet;
+ try {
+ packet = TcpKeepaliveController.getTcpKeepalivePacket(fd);
+ } catch (InvalidPacketException | InvalidSocketException e) {
+ notifyErrorCallback(cb, e.error);
+ return;
+ }
+ KeepaliveInfo ki = null;
+ try {
+ ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
+ KeepaliveInfo.TYPE_TCP, fd);
+ } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
+ Log.e(TAG, "Fail to construct keepalive e=" + e);
+ notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+ return;
+ }
+ Log.d(TAG, "Created keepalive: " + ki.toString());
+ mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
+ }
+
+ /**
+ * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is
+ * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the
+ * resource index bound to the {@link UdpEncapsulationSocket} when creating by
+ * {@link com.android.server.IpSecService} to verify whether the given
+ * {@link UdpEncapsulationSocket} is legitimate.
+ **/
+ public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ @Nullable FileDescriptor fd,
+ int resourceId,
+ int intervalSeconds,
+ @NonNull ISocketKeepaliveCallback cb,
+ @NonNull String srcAddrString,
+ @NonNull String dstAddrString,
+ int dstPort) {
+ // Ensure that the socket is created by IpSecService.
+ if (!isNattKeepaliveSocketValid(fd, resourceId)) {
+ notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+ }
+
+ // Get src port to adopt old API.
+ int srcPort = 0;
+ try {
+ final SocketAddress srcSockAddr = Os.getsockname(fd);
+ srcPort = ((InetSocketAddress) srcSockAddr).getPort();
+ } catch (ErrnoException e) {
+ notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+ }
+
+ // Forward request to old API.
+ startNattKeepalive(nai, fd, intervalSeconds, cb, srcAddrString, srcPort,
+ dstAddrString, dstPort);
+ }
+
+ /**
+ * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid.
+ **/
+ public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) {
+ // TODO: 1. confirm whether the fd is called from system api or created by IpSecService.
+ // 2. If the fd is created from the system api, check that it's bounded. And
+ // call dup to keep the fd open.
+ // 3. If the fd is created from IpSecService, check if the resource ID is valid. And
+ // hold the resource needed in IpSecService.
+ if (null == fd) {
+ return false;
+ }
+ return true;
}
public void dump(IndentingPrintWriter pw) {
- pw.println("Packet keepalives:");
+ pw.println("Supported Socket keepalives: " + Arrays.toString(mSupportedKeepalives));
+ pw.println("Reserved Privileged keepalives: " + mReservedPrivilegedSlots);
+ pw.println("Allowed Unprivileged keepalives per uid: " + mAllowedUnprivilegedSlotsForUid);
+ pw.println("Socket keepalives:");
pw.increaseIndent();
for (NetworkAgentInfo nai : mKeepalives.keySet()) {
pw.println(nai.name());
diff --git a/services/core/java/com/android/server/connectivity/LingerMonitor.java b/services/core/java/com/android/server/connectivity/LingerMonitor.java
index 635db19..929dfc4 100644
--- a/services/core/java/com/android/server/connectivity/LingerMonitor.java
+++ b/services/core/java/com/android/server/connectivity/LingerMonitor.java
@@ -90,6 +90,8 @@
mNotifier = notifier;
mDailyLimit = dailyLimit;
mRateLimitMillis = rateLimitMillis;
+ // Ensure that (now - mLastNotificationMillis) >= rateLimitMillis at first
+ mLastNotificationMillis = -rateLimitMillis;
}
private static HashMap<String, Integer> makeTransportToNameMap() {
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index f523d59..66bd27c 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -16,20 +16,27 @@
package com.android.server.connectivity;
-import android.net.InterfaceConfiguration;
import android.net.ConnectivityManager;
+import android.net.IDnsResolver;
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.RouteInfo;
import android.os.INetworkManagementService;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.net.BaseNetworkObserver;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.util.Objects;
/**
@@ -59,41 +66,80 @@
NetworkInfo.State.SUSPENDED,
};
+ private final IDnsResolver mDnsResolver;
+ private final INetd mNetd;
private final INetworkManagementService mNMService;
// The network we're running on, and its type.
private final NetworkAgentInfo mNetwork;
private enum State {
- IDLE, // start() not called. Base iface and stacked iface names are null.
- STARTING, // start() called. Base iface and stacked iface names are known.
- RUNNING, // start() called, and the stacked iface is known to be up.
- STOPPING; // stop() called, this Nat464Xlat is still registered as a network observer for
- // the stacked interface.
+ IDLE, // start() not called. Base iface and stacked iface names are null.
+ DISCOVERING, // same as IDLE, except prefix discovery in progress.
+ STARTING, // start() called. Base iface and stacked iface names are known.
+ RUNNING, // start() called, and the stacked iface is known to be up.
}
+ private IpPrefix mNat64Prefix;
private String mBaseIface;
private String mIface;
+ private Inet6Address mIPv6Address;
private State mState = State.IDLE;
- public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) {
+ public Nat464Xlat(NetworkAgentInfo nai, INetd netd, IDnsResolver dnsResolver,
+ INetworkManagementService nmService) {
+ mDnsResolver = dnsResolver;
+ mNetd = netd;
mNMService = nmService;
mNetwork = nai;
}
/**
- * Determines whether a network requires clat.
+ * Whether to attempt 464xlat on this network. This is true for an IPv6-only network that is
+ * currently connected and where the NetworkAgent has not disabled 464xlat. It is the signal to
+ * enable NAT64 prefix discovery.
+ *
* @param network the NetworkAgentInfo corresponding to the network.
* @return true if the network requires clat, false otherwise.
*/
- public static boolean requiresClat(NetworkAgentInfo nai) {
+ @VisibleForTesting
+ protected static boolean requiresClat(NetworkAgentInfo nai) {
// TODO: migrate to NetworkCapabilities.TRANSPORT_*.
final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState());
- // We only run clat on networks that don't have a native IPv4 address.
- final boolean hasIPv4Address =
- (nai.linkProperties != null) && nai.linkProperties.hasIPv4Address();
- return supported && connected && !hasIPv4Address;
+
+ // Only run clat on networks that have a global IPv6 address and don't have a native IPv4
+ // address.
+ LinkProperties lp = nai.linkProperties;
+ final boolean isIpv6OnlyNetwork = (lp != null) && lp.hasGlobalIpv6Address()
+ && !lp.hasIpv4Address();
+
+ // If the network tells us it doesn't use clat, respect that.
+ final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat;
+
+ return supported && connected && isIpv6OnlyNetwork && !skip464xlat;
+ }
+
+ /**
+ * Whether the clat demon should be started on this network now. This is true if requiresClat is
+ * true and a NAT64 prefix has been discovered.
+ *
+ * @param nai the NetworkAgentInfo corresponding to the network.
+ * @return true if the network should start clat, false otherwise.
+ */
+ @VisibleForTesting
+ protected static boolean shouldStartClat(NetworkAgentInfo nai) {
+ LinkProperties lp = nai.linkProperties;
+ return requiresClat(nai) && lp != null && lp.getNat64Prefix() != null;
+ }
+
+ /**
+ * @return true if we have started prefix discovery and not yet stopped it (regardless of
+ * whether it is still running or has succeeded).
+ * A true result corresponds to internal states DISCOVERING, STARTING and RUNNING.
+ */
+ public boolean isPrefixDiscoveryStarted() {
+ return mState == State.DISCOVERING || isStarted();
}
/**
@@ -101,7 +147,7 @@
* A true result corresponds to internal states STARTING and RUNNING.
*/
public boolean isStarted() {
- return mState != State.IDLE;
+ return (mState == State.STARTING || mState == State.RUNNING);
}
/**
@@ -119,32 +165,31 @@
}
/**
- * @return true if clatd has been stopped.
- */
- public boolean isStopping() {
- return mState == State.STOPPING;
- }
-
- /**
* Start clatd, register this Nat464Xlat as a network observer for the stacked interface,
* and set internal state.
*/
private void enterStartingState(String baseIface) {
try {
mNMService.registerObserver(this);
- } catch(RemoteException e) {
- Slog.e(TAG,
- "startClat: Can't register interface observer for clat on " + mNetwork.name());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Can't register interface observer for clat on " + mNetwork.name());
return;
}
+
+ String addrStr = null;
try {
- mNMService.startClatd(baseIface);
- } catch(RemoteException|IllegalStateException e) {
- Slog.e(TAG, "Error starting clatd on " + baseIface, e);
+ addrStr = mNetd.clatdStart(baseIface, mNat64Prefix.toString());
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
}
mIface = CLAT_PREFIX + baseIface;
mBaseIface = baseIface;
mState = State.STARTING;
+ try {
+ mIPv6Address = (Inet6Address) InetAddresses.parseNumericAddress(addrStr);
+ } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
+ Slog.e(TAG, "Invalid IPv6 address " + addrStr);
+ }
}
/**
@@ -156,37 +201,27 @@
}
/**
- * Stop clatd, and turn ND offload on if it had been turned off.
- */
- private void enterStoppingState() {
- try {
- mNMService.stopClatd(mBaseIface);
- } catch(RemoteException|IllegalStateException e) {
- Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
- }
-
- mState = State.STOPPING;
- }
-
- /**
* Unregister as a base observer for the stacked interface, and clear internal state.
*/
- private void enterIdleState() {
+ private void leaveStartedState() {
try {
mNMService.unregisterObserver(this);
- } catch(RemoteException|IllegalStateException e) {
- Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface, e);
+ } catch (RemoteException | IllegalStateException e) {
+ Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface + ": " + e);
}
-
mIface = null;
mBaseIface = null;
mState = State.IDLE;
+ if (requiresClat(mNetwork)) {
+ mState = State.DISCOVERING;
+ } else {
+ stopPrefixDiscovery();
+ mState = State.IDLE;
+ }
}
- /**
- * Starts the clat daemon.
- */
- public void start() {
+ @VisibleForTesting
+ protected void start() {
if (isStarted()) {
Slog.e(TAG, "startClat: already started");
return;
@@ -202,25 +237,87 @@
Slog.e(TAG, "startClat: Can't start clat on null interface");
return;
}
- // TODO: should we only do this if mNMService.startClatd() succeeds?
+ // TODO: should we only do this if mNetd.clatdStart() succeeds?
Slog.i(TAG, "Starting clatd on " + baseIface);
enterStartingState(baseIface);
}
- /**
- * Stops the clat daemon.
- */
- public void stop() {
+ @VisibleForTesting
+ protected void stop() {
if (!isStarted()) {
+ Slog.e(TAG, "stopClat: already stopped");
return;
}
- Slog.i(TAG, "Stopping clatd on " + mBaseIface);
- boolean wasStarting = isStarting();
- enterStoppingState();
- if (wasStarting) {
- enterIdleState();
+ Slog.i(TAG, "Stopping clatd on " + mBaseIface);
+ try {
+ mNetd.clatdStop(mBaseIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Error stopping clatd on " + mBaseIface + ": " + e);
}
+
+ String iface = mIface;
+ boolean wasRunning = isRunning();
+
+ // Change state before updating LinkProperties. handleUpdateLinkProperties ends up calling
+ // fixupLinkProperties, and if at that time the state is still RUNNING, fixupLinkProperties
+ // would wrongly inform ConnectivityService that there is still a stacked interface.
+ leaveStartedState();
+
+ if (wasRunning) {
+ LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
+ lp.removeStackedLink(iface);
+ mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
+ }
+ }
+
+ private void startPrefixDiscovery() {
+ try {
+ mDnsResolver.startPrefix64Discovery(getNetId());
+ mState = State.DISCOVERING;
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Error starting prefix discovery on netId " + getNetId() + ": " + e);
+ }
+ }
+
+ private void stopPrefixDiscovery() {
+ try {
+ mDnsResolver.stopPrefix64Discovery(getNetId());
+ } catch (RemoteException | ServiceSpecificException e) {
+ Slog.e(TAG, "Error stopping prefix discovery on netId " + getNetId() + ": " + e);
+ }
+ }
+
+ /**
+ * Starts/stops NAT64 prefix discovery and clatd as necessary.
+ */
+ public void update() {
+ // TODO: turn this class into a proper StateMachine. // http://b/126113090
+ if (requiresClat(mNetwork)) {
+ if (!isPrefixDiscoveryStarted()) {
+ startPrefixDiscovery();
+ } else if (shouldStartClat(mNetwork)) {
+ // NAT64 prefix detected. Start clatd.
+ // TODO: support the NAT64 prefix changing after it's been discovered. There is no
+ // need to support this at the moment because it cannot happen without changes to
+ // the Dns64Configuration code in netd.
+ start();
+ } else {
+ // NAT64 prefix removed. Stop clatd and go back into DISCOVERING state.
+ stop();
+ }
+ } else {
+ // Network no longer requires clat. Stop clat and prefix discovery.
+ if (isStarted()) {
+ stop();
+ } else if (isPrefixDiscoveryStarted()) {
+ leaveStartedState();
+ }
+ }
+ }
+
+ public void setNat64Prefix(IpPrefix nat64Prefix) {
+ mNat64Prefix = nat64Prefix;
}
/**
@@ -229,6 +326,8 @@
* has no idea that 464xlat is running on top of it.
*/
public void fixupLinkProperties(LinkProperties oldLp, LinkProperties lp) {
+ lp.setNat64Prefix(mNat64Prefix);
+
if (!isRunning()) {
return;
}
@@ -267,7 +366,7 @@
try {
InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
return config.getLinkAddress();
- } catch(RemoteException|IllegalStateException e) {
+ } catch (RemoteException | IllegalStateException e) {
Slog.e(TAG, "Error getting link properties: " + e);
return null;
}
@@ -277,6 +376,20 @@
* Adds stacked link on base link and transitions to RUNNING state.
*/
private void handleInterfaceLinkStateChanged(String iface, boolean up) {
+ // TODO: if we call start(), then stop(), then start() again, and the
+ // interfaceLinkStateChanged notification for the first start is delayed past the first
+ // stop, then the code becomes out of sync with system state and will behave incorrectly.
+ //
+ // This is not trivial to fix because:
+ // 1. It is not guaranteed that start() will eventually result in the interface coming up,
+ // because there could be an error starting clat (e.g., if the interface goes down before
+ // the packet socket can be bound).
+ // 2. If start is called multiple times, there is nothing in the interfaceLinkStateChanged
+ // notification that says which start() call the interface was created by.
+ //
+ // Once this code is converted to StateMachine, it will be possible to use deferMessage to
+ // ensure it stays in STARTING state until the interfaceLinkStateChanged notification fires,
+ // and possibly use a timeout (or provide some guarantees at the lower layer) to address #1.
if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
return;
}
@@ -302,20 +415,16 @@
if (!Objects.equals(mIface, iface)) {
return;
}
- if (!isRunning() && !isStopping()) {
+ if (!isRunning()) {
return;
}
Slog.i(TAG, "interface " + iface + " removed");
- if (!isStopping()) {
- // Ensure clatd is stopped if stop() has not been called: this likely means that clatd
- // has crashed.
- enterStoppingState();
- }
- enterIdleState();
- LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
- lp.removeStackedLink(iface);
- mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
+ // If we're running, and the interface was removed, then we didn't call stop(), and it's
+ // likely that clatd crashed. Ensure we call stop() so we can start clatd again. Calling
+ // stop() will also update LinkProperties, and if clatd crashed, the LinkProperties update
+ // will cause ConnectivityService to call start() again.
+ stop();
}
@Override
@@ -332,4 +441,9 @@
public String toString() {
return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
}
+
+ @VisibleForTesting
+ protected int getNetId() {
+ return mNetwork.network.netId;
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 505480e..5b04379 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,20 +16,21 @@
package com.android.server.connectivity;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
import android.content.Context;
+import android.net.IDnsResolver;
+import android.net.INetd;
+import android.net.INetworkMonitor;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkMisc;
+import android.net.NetworkMonitorManager;
import android.net.NetworkRequest;
import android.net.NetworkState;
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.Messenger;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
@@ -37,11 +38,8 @@
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.WakeupMessage;
import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkMonitor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Comparator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -124,9 +122,9 @@
// This Network object is always valid.
public final Network network;
public LinkProperties linkProperties;
- // This should only be modified via ConnectivityService.updateCapabilities().
+ // This should only be modified by ConnectivityService, via setNetworkCapabilities().
+ // TODO: make this private with a getter.
public NetworkCapabilities networkCapabilities;
- public final NetworkMonitor networkMonitor;
public final NetworkMisc networkMisc;
// Indicates if netd has been told to create this Network. From this point on the appropriate
// routing rules are setup and routes are added so packets can begin flowing over the Network.
@@ -157,6 +155,13 @@
// Whether a captive portal was found during the last network validation attempt.
public boolean lastCaptivePortalDetected;
+ // Indicates the captive portal app was opened to show a login UI to the user, but the network
+ // has not validated yet.
+ public volatile boolean captivePortalValidationPending;
+
+ // Set to true when partial connectivity was detected.
+ public boolean partialConnectivity;
+
// Networks are lingered when they become unneeded as a result of their NetworkRequests being
// satisfied by a higher-scoring network. so as to allow communication to wrap up before the
// network is taken down. This usually only happens to the default network. Lingering ends with
@@ -236,8 +241,13 @@
public final Messenger messenger;
public final AsyncChannel asyncChannel;
+ public final int factorySerialNumber;
+
// Used by ConnectivityService to keep track of 464xlat.
- public Nat464Xlat clatd;
+ public final Nat464Xlat clatd;
+
+ // Set after asynchronous creation of the NetworkMonitor.
+ private volatile NetworkMonitorManager mNetworkMonitor;
private static final String TAG = ConnectivityService.class.getSimpleName();
private static final boolean VDBG = false;
@@ -247,7 +257,8 @@
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
- NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
+ NetworkMisc misc, ConnectivityService connService, INetd netd,
+ IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) {
this.messenger = messenger;
asyncChannel = ac;
network = net;
@@ -255,17 +266,44 @@
linkProperties = lp;
networkCapabilities = nc;
currentScore = score;
+ clatd = new Nat464Xlat(this, netd, dnsResolver, nms);
mConnService = connService;
mContext = context;
mHandler = handler;
- networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
networkMisc = misc;
+ this.factorySerialNumber = factorySerialNumber;
+ }
+
+ /**
+ * Inform NetworkAgentInfo that a new NetworkMonitor was created.
+ */
+ public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+ mNetworkMonitor = new NetworkMonitorManager(networkMonitor);
+ }
+
+ /**
+ * Set the NetworkCapabilities on this NetworkAgentInfo. Also attempts to notify NetworkMonitor
+ * of the new capabilities, if NetworkMonitor has been created.
+ *
+ * <p>If {@link NetworkMonitor#notifyNetworkCapabilitiesChanged(NetworkCapabilities)} fails,
+ * the exception is logged but not reported to callers.
+ */
+ public void setNetworkCapabilities(NetworkCapabilities nc) {
+ networkCapabilities = nc;
+ final NetworkMonitorManager nm = mNetworkMonitor;
+ if (nm != null) {
+ nm.notifyNetworkCapabilitiesChanged(nc);
+ }
}
public ConnectivityService connService() {
return mConnService;
}
+ public NetworkMisc netMisc() {
+ return networkMisc;
+ }
+
public Handler handler() {
return mHandler;
}
@@ -274,6 +312,15 @@
return network;
}
+ /**
+ * Get the NetworkMonitorManager in this NetworkAgentInfo.
+ *
+ * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called.
+ */
+ public NetworkMonitorManager networkMonitor() {
+ return mNetworkMonitor;
+ }
+
// Functions for manipulating the requests satisfied by this network.
//
// These functions must only called on ConnectivityService's main thread.
@@ -432,11 +479,11 @@
// down an explicitly selected network before the user gets a chance to prefer it when
// a higher-scoring network (e.g., Ethernet) is available.
if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
- return ConnectivityConstants.MAXIMUM_NETWORK_SCORE;
+ return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
}
int score = currentScore;
- if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty()) {
+ if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty() && !isVPN()) {
score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
}
if (score < 0) score = 0;
@@ -569,45 +616,24 @@
for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
}
- public void updateClat(INetworkManagementService netd) {
- if (Nat464Xlat.requiresClat(this)) {
- maybeStartClat(netd);
- } else {
- maybeStopClat();
- }
- }
-
- /** Ensure clat has started for this network. */
- public void maybeStartClat(INetworkManagementService netd) {
- if (clatd != null && clatd.isStarted()) {
- return;
- }
- clatd = new Nat464Xlat(netd, this);
- clatd.start();
- }
-
- /** Ensure clat has stopped for this network. */
- public void maybeStopClat() {
- if (clatd == null) {
- return;
- }
- clatd.stop();
- clatd = null;
- }
-
+ // TODO: Print shorter members first and only print the boolean variable which value is true
+ // to improve readability.
public String toString() {
- return "NetworkAgentInfo{ ni{" + networkInfo + "} " +
- "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " +
- "lp{" + linkProperties + "} " +
- "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " +
- "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " +
- "created{" + created + "} lingering{" + isLingering() + "} " +
- "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
- "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
- "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
- "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
- "clat{" + clatd + "} " +
- "}";
+ return "NetworkAgentInfo{ ni{" + networkInfo + "} "
+ + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} "
+ + "lp{" + linkProperties + "} "
+ + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} "
+ + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} "
+ + "created{" + created + "} lingering{" + isLingering() + "} "
+ + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
+ + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+ + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+ + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+ + "captivePortalValidationPending{" + captivePortalValidationPending + "} "
+ + "partialConnectivity{" + partialConnectivity + "} "
+ + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
+ + "clat{" + clatd + "} "
+ + "}";
}
public String name() {
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index c471f0c..a1a8e35 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -33,11 +33,14 @@
import android.util.Pair;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.TrafficStatsConstants;
+
+import libcore.io.IoUtils;
import java.io.Closeable;
import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -48,17 +51,13 @@
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
-
-import libcore.io.IoUtils;
-
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* NetworkDiagnostics
@@ -186,7 +185,7 @@
// TODO: we could use mLinkProperties.isReachable(TEST_DNS6) here, because we won't set any
// DNS servers for which isReachable() is false, but since this is diagnostic code, be extra
// careful.
- if (mLinkProperties.hasGlobalIPv6Address() || mLinkProperties.hasIPv6DefaultRoute()) {
+ if (mLinkProperties.hasGlobalIpv6Address() || mLinkProperties.hasIpv6DefaultRoute()) {
mLinkProperties.addDnsServer(TEST_DNS6);
}
@@ -383,7 +382,8 @@
protected void setupSocket(
int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
throws ErrnoException, IOException {
- final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(
+ TrafficStatsConstants.TAG_SYSTEM_PROBE);
try {
mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
} finally {
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 36a2476..077c405 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -16,30 +16,34 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.net.NetworkCapabilities;
+import android.net.NetworkSpecifier;
+import android.net.StringNetworkSpecifier;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
+import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.widget.Toast;
+
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-
public class NetworkNotificationManager {
@@ -47,6 +51,8 @@
LOST_INTERNET(SystemMessage.NOTE_NETWORK_LOST_INTERNET),
NETWORK_SWITCH(SystemMessage.NOTE_NETWORK_SWITCH),
NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
+ LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN),
+ PARTIAL_CONNECTIVITY(SystemMessage.NOTE_NETWORK_PARTIAL_CONNECTIVITY),
SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
public final int eventId;
@@ -90,7 +96,7 @@
return -1;
}
- private static String getTransportName(int transportType) {
+ private static String getTransportName(@TransportType int transportType) {
Resources r = Resources.getSystem();
String[] networkTypes = r.getStringArray(R.array.network_switch_type_name);
try {
@@ -100,10 +106,14 @@
}
}
- private static int getIcon(int transportType) {
- return (transportType == TRANSPORT_WIFI) ?
- R.drawable.stat_notify_wifi_in_range : // TODO: Distinguish ! from ?.
- R.drawable.stat_notify_rssi_in_range;
+ private static int getIcon(int transportType, NotificationType notifyType) {
+ if (transportType != TRANSPORT_WIFI) {
+ return R.drawable.stat_notify_rssi_in_range;
+ }
+
+ return notifyType == NotificationType.LOGGED_IN
+ ? R.drawable.ic_wifi_signal_4
+ : R.drawable.stat_notify_wifi_in_range; // TODO: Distinguish ! from ?.
}
/**
@@ -120,6 +130,7 @@
* @param id an identifier that uniquely identifies this notification. This must match
* between show and hide calls. We use the NetID value but for legacy callers
* we concatenate the range of types with the range of NetIDs.
+ * @param notifyType the type of the notification.
* @param nai the network with which the notification is associated. For a SIGN_IN, NO_INTERNET,
* or LOST_INTERNET notification, this is the network we're connecting to. For a
* NETWORK_SWITCH notification it's the network that we switched from. When this network
@@ -166,13 +177,20 @@
Resources r = Resources.getSystem();
CharSequence title;
CharSequence details;
- int icon = getIcon(transportType);
+ int icon = getIcon(transportType, notifyType);
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
- title = r.getString(R.string.wifi_no_internet, 0);
+ title = r.getString(R.string.wifi_no_internet,
+ WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
details = r.getString(R.string.wifi_no_internet_detailed);
+ } else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY
+ && transportType == TRANSPORT_WIFI) {
+ title = r.getString(R.string.network_partial_connectivity,
+ WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
+ details = r.getString(R.string.network_partial_connectivity_detailed);
} else if (notifyType == NotificationType.LOST_INTERNET &&
transportType == TRANSPORT_WIFI) {
- title = r.getString(R.string.wifi_no_internet, 0);
+ title = r.getString(R.string.wifi_no_internet,
+ WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
details = r.getString(R.string.wifi_no_internet_detailed);
} else if (notifyType == NotificationType.SIGN_IN) {
switch (transportType) {
@@ -185,27 +203,54 @@
title = r.getString(R.string.network_available_sign_in, 0);
// TODO: Change this to pull from NetworkInfo once a printable
// name has been added to it
- details = mTelephonyManager.getNetworkOperatorName();
+ NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
+ int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ if (specifier instanceof StringNetworkSpecifier) {
+ try {
+ subId = Integer.parseInt(
+ ((StringNetworkSpecifier) specifier).specifier);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "NumberFormatException on "
+ + ((StringNetworkSpecifier) specifier).specifier);
+ }
+ }
+
+ details = mTelephonyManager.createForSubscriptionId(subId)
+ .getNetworkOperatorName();
break;
default:
title = r.getString(R.string.network_available_sign_in, 0);
details = r.getString(R.string.network_available_sign_in_detailed, name);
break;
}
+ } else if (notifyType == NotificationType.LOGGED_IN) {
+ title = WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID());
+ details = r.getString(R.string.captive_portal_logged_in_detailed);
} else if (notifyType == NotificationType.NETWORK_SWITCH) {
String fromTransport = getTransportName(transportType);
String toTransport = getTransportName(getFirstTransportType(switchToNai));
title = r.getString(R.string.network_switch_metered, toTransport);
details = r.getString(R.string.network_switch_metered_detail, toTransport,
fromTransport);
+ } else if (notifyType == NotificationType.NO_INTERNET
+ || notifyType == NotificationType.PARTIAL_CONNECTIVITY) {
+ // NO_INTERNET and PARTIAL_CONNECTIVITY notification for non-WiFi networks
+ // are sent, but they are not implemented yet.
+ return;
} else {
Slog.wtf(TAG, "Unknown notification type " + notifyType + " on network transport "
+ getTransportName(transportType));
return;
}
-
- final String channelId = highPriority ? SystemNotificationChannels.NETWORK_ALERTS :
- SystemNotificationChannels.NETWORK_STATUS;
+ // When replacing an existing notification for a given network, don't alert, just silently
+ // update the existing notification. Note that setOnlyAlertOnce() will only work for the
+ // same id, and the id used here is the NotificationType which is different in every type of
+ // notification. This is required because the notification metrics only track the ID but not
+ // the tag.
+ final boolean hasPreviousNotification = previousNotifyType != null;
+ final String channelId = (highPriority && !hasPreviousNotification)
+ ? SystemNotificationChannels.NETWORK_ALERTS
+ : SystemNotificationChannels.NETWORK_STATUS;
Notification.Builder builder = new Notification.Builder(mContext, channelId)
.setWhen(System.currentTimeMillis())
.setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
@@ -239,6 +284,18 @@
}
}
+ /**
+ * Clear the notification with the given id, only if it matches the given type.
+ */
+ public void clearNotification(int id, NotificationType notifyType) {
+ final int previousEventId = mNotificationTypeMap.get(id);
+ final NotificationType previousNotifyType = NotificationType.getFromId(previousEventId);
+ if (notifyType != previousNotifyType) {
+ return;
+ }
+ clearNotification(id);
+ }
+
public void clearNotification(int id) {
if (mNotificationTypeMap.indexOfKey(id) < 0) {
return;
@@ -290,18 +347,25 @@
return (t != null) ? t.name() : "UNKNOWN";
}
+ /**
+ * A notification with a higher number will take priority over a notification with a lower
+ * number.
+ */
private static int priority(NotificationType t) {
if (t == null) {
return 0;
}
switch (t) {
case SIGN_IN:
+ return 5;
+ case PARTIAL_CONNECTIVITY:
return 4;
case NO_INTERNET:
return 3;
case NETWORK_SWITCH:
return 2;
case LOST_INTERNET:
+ case LOGGED_IN:
return 1;
default:
return 0;
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index e471c7d..fbe2589 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -19,38 +19,53 @@
import static android.Manifest.permission.CHANGE_NETWORK_STATE;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.NETWORK_STACK;
-import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
-import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
-import android.content.BroadcastReceiver;
+import android.annotation.NonNull;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.INetworkManagementService;
+import android.net.INetd;
+import android.net.UidRange;
+import android.os.Build;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
-import android.text.TextUtils;
+import android.system.OsConstants;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map.Entry;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
+
/**
* A utility class to inform Netd of UID permisisons.
* Does a mass update at boot and then monitors for app install/remove.
@@ -60,46 +75,70 @@
public class PermissionMonitor {
private static final String TAG = "PermissionMonitor";
private static final boolean DBG = true;
- private static final Boolean SYSTEM = Boolean.TRUE;
- private static final Boolean NETWORK = Boolean.FALSE;
+ protected static final Boolean SYSTEM = Boolean.TRUE;
+ protected static final Boolean NETWORK = Boolean.FALSE;
+ private static final int VERSION_Q = Build.VERSION_CODES.Q;
- private final Context mContext;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
- private final INetworkManagementService mNetd;
- private final BroadcastReceiver mIntentReceiver;
+ private final INetd mNetd;
// Values are User IDs.
+ @GuardedBy("this")
private final Set<Integer> mUsers = new HashSet<>();
- // Keys are App IDs. Values are true for SYSTEM permission and false for NETWORK permission.
+ // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission.
+ @GuardedBy("this")
private final Map<Integer, Boolean> mApps = new HashMap<>();
- public PermissionMonitor(Context context, INetworkManagementService netd) {
- mContext = context;
- mPackageManager = context.getPackageManager();
- mUserManager = UserManager.get(context);
- mNetd = netd;
- mIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- int appUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
- Uri appData = intent.getData();
- String appName = appData != null ? appData.getSchemeSpecificPart() : null;
+ // Keys are active non-bypassable and fully-routed VPN's interface name, Values are uid ranges
+ // for apps under the VPN
+ @GuardedBy("this")
+ private final Map<String, Set<UidRange>> mVpnUidRanges = new HashMap<>();
- if (Intent.ACTION_USER_ADDED.equals(action)) {
- onUserAdded(user);
- } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
- onUserRemoved(user);
- } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
- onAppAdded(appName, appUid);
- } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
- onAppRemoved(appUid);
+ // A set of appIds for apps across all users on the device. We track appIds instead of uids
+ // directly to reduce its size and also eliminate the need to update this set when user is
+ // added/removed.
+ @GuardedBy("this")
+ private final Set<Integer> mAllApps = new HashSet<>();
+
+ private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
+
+ private int getPermissionForUid(int uid) {
+ int permission = 0;
+ // Check all the packages for this UID. The UID has the permission if any of the
+ // packages in it has the permission.
+ String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ for (String name : packages) {
+ final PackageInfo app = getPackageInfo(name);
+ if (app != null && app.requestedPermissions != null) {
+ permission |= getNetdPermissionMask(app.requestedPermissions,
+ app.requestedPermissionsFlags);
+ }
}
+ } else {
+ // The last package of this uid is removed from device. Clean the package up.
+ permission = INetd.PERMISSION_UNINSTALLED;
}
- };
+ return permission;
+ }
+
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
+ }
+ }
+
+ public PermissionMonitor(Context context, INetd netd) {
+ mPackageManager = context.getPackageManager();
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mNetd = netd;
}
// Intended to be called only once at startup, after the system is ready. Installs a broadcast
@@ -107,28 +146,27 @@
public synchronized void startMonitoring() {
log("Monitoring");
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_ADDED);
- intentFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null);
-
- intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- intentFilter.addDataScheme("package");
- mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null);
-
- List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS);
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ if (pmi != null) {
+ pmi.getPackageList(new PackageListObserver());
+ } else {
+ loge("failed to get the PackageManagerInternal service");
+ }
+ List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
+ | MATCH_ANY_USER);
if (apps == null) {
loge("No apps");
return;
}
+ SparseIntArray netdPermsUids = new SparseIntArray();
+
for (PackageInfo app : apps) {
- int uid = app.applicationInfo != null ? app.applicationInfo.uid : -1;
+ int uid = app.applicationInfo != null ? app.applicationInfo.uid : INVALID_UID;
if (uid < 0) {
continue;
}
+ mAllApps.add(UserHandle.getAppId(uid));
boolean isNetwork = hasNetworkPermission(app);
boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
@@ -141,6 +179,11 @@
mApps.put(uid, hasRestrictedPermission);
}
}
+
+ //TODO: unify the management of the permissions into one codepath.
+ int otherNetdPerms = getNetdPermissionMask(app.requestedPermissions,
+ app.requestedPermissionsFlags);
+ netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
}
List<UserInfo> users = mUserManager.getUsers(true); // exclude dying users
@@ -150,14 +193,34 @@
}
}
+ final SparseArray<ArraySet<String>> systemPermission =
+ SystemConfig.getInstance().getSystemPermissions();
+ for (int i = 0; i < systemPermission.size(); i++) {
+ ArraySet<String> perms = systemPermission.valueAt(i);
+ int uid = systemPermission.keyAt(i);
+ int netdPermission = 0;
+ // Get the uids of native services that have UPDATE_DEVICE_STATS or INTERNET permission.
+ if (perms != null) {
+ netdPermission |= perms.contains(UPDATE_DEVICE_STATS)
+ ? INetd.PERMISSION_UPDATE_DEVICE_STATS : 0;
+ netdPermission |= perms.contains(INTERNET)
+ ? INetd.PERMISSION_INTERNET : 0;
+ }
+ netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
+ }
log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
update(mUsers, mApps, true);
+ sendPackagePermissionsToNetd(netdPermsUids);
}
@VisibleForTesting
- boolean isPreinstalledSystemApp(PackageInfo app) {
- int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0;
- return (flags & (FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP)) != 0;
+ static boolean isVendorApp(@NonNull ApplicationInfo appInfo) {
+ return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct();
+ }
+
+ @VisibleForTesting
+ protected int getDeviceFirstSdkInt() {
+ return Build.VERSION.FIRST_SDK_INT;
}
@VisibleForTesting
@@ -177,7 +240,20 @@
}
private boolean hasRestrictedNetworkPermission(PackageInfo app) {
- if (isPreinstalledSystemApp(app)) return true;
+ // TODO : remove this check in the future(b/31479477). All apps should just
+ // request the appropriate permission for their use case since android Q.
+ if (app.applicationInfo != null) {
+ // Backward compatibility for b/114245686, on devices that launched before Q daemons
+ // and apps running as the system UID are exempted from this check.
+ if (app.applicationInfo.uid == SYSTEM_UID && getDeviceFirstSdkInt() < VERSION_Q) {
+ return true;
+ }
+
+ if (app.applicationInfo.targetSdkVersion < VERSION_Q
+ && isVendorApp(app.applicationInfo)) {
+ return true;
+ }
+ }
return hasPermission(app, CONNECTIVITY_INTERNAL)
|| hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
}
@@ -186,13 +262,8 @@
// This function defines what it means to hold the permission to use
// background networks.
return hasPermission(app, CHANGE_NETWORK_STATE)
- || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)
- || hasPermission(app, CONNECTIVITY_INTERNAL)
|| hasPermission(app, NETWORK_STACK)
- // TODO : remove this check (b/31479477). Not all preinstalled apps should
- // have access to background networks, they should just request the appropriate
- // permission for their use case from the list above.
- || isPreinstalledSystemApp(app);
+ || hasRestrictedNetworkPermission(app);
}
public boolean hasUseBackgroundNetworksPermission(int uid) {
@@ -213,10 +284,11 @@
}
}
- private int[] toIntArray(List<Integer> list) {
+ private int[] toIntArray(Collection<Integer> list) {
int[] array = new int[list.size()];
- for (int i = 0; i < list.size(); i++) {
- array[i] = list.get(i);
+ int i = 0;
+ for (Integer item : list) {
+ array[i++] = item;
}
return array;
}
@@ -232,18 +304,25 @@
}
try {
if (add) {
- mNetd.setPermission("NETWORK", toIntArray(network));
- mNetd.setPermission("SYSTEM", toIntArray(system));
+ mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network));
+ mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system));
} else {
- mNetd.clearPermission(toIntArray(network));
- mNetd.clearPermission(toIntArray(system));
+ mNetd.networkClearPermissionForUser(toIntArray(network));
+ mNetd.networkClearPermissionForUser(toIntArray(system));
}
} catch (RemoteException e) {
loge("Exception when updating permissions: " + e);
}
}
- private synchronized void onUserAdded(int user) {
+ /**
+ * Called when a user is added. See {link #ACTION_USER_ADDED}.
+ *
+ * @param user The integer userHandle of the added user. See {@link #EXTRA_USER_HANDLE}.
+ *
+ * @hide
+ */
+ public synchronized void onUserAdded(int user) {
if (user < 0) {
loge("Invalid user in onUserAdded: " + user);
return;
@@ -255,7 +334,14 @@
update(users, mApps, true);
}
- private synchronized void onUserRemoved(int user) {
+ /**
+ * Called when an user is removed. See {link #ACTION_USER_REMOVED}.
+ *
+ * @param user The integer userHandle of the removed user. See {@link #EXTRA_USER_HANDLE}.
+ *
+ * @hide
+ */
+ public synchronized void onUserRemoved(int user) {
if (user < 0) {
loge("Invalid user in onUserRemoved: " + user);
return;
@@ -267,8 +353,8 @@
update(users, mApps, false);
}
-
- private Boolean highestPermissionForUid(Boolean currentPermission, String name) {
+ @VisibleForTesting
+ protected Boolean highestPermissionForUid(Boolean currentPermission, String name) {
if (currentPermission == SYSTEM) {
return currentPermission;
}
@@ -286,33 +372,67 @@
return currentPermission;
}
- private synchronized void onAppAdded(String appName, int appUid) {
- if (TextUtils.isEmpty(appName) || appUid < 0) {
- loge("Invalid app in onAppAdded: " + appName + " | " + appUid);
- return;
- }
-
+ /**
+ * Called when a package is added. See {link #ACTION_PACKAGE_ADDED}.
+ *
+ * @param packageName The name of the new package.
+ * @param uid The uid of the new package.
+ *
+ * @hide
+ */
+ public synchronized void onPackageAdded(String packageName, int uid) {
// If multiple packages share a UID (cf: android:sharedUserId) and ask for different
// permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
- final Boolean permission = highestPermissionForUid(mApps.get(appUid), appName);
- if (permission != mApps.get(appUid)) {
- mApps.put(appUid, permission);
+ final Boolean permission = highestPermissionForUid(mApps.get(uid), packageName);
+ if (permission != mApps.get(uid)) {
+ mApps.put(uid, permission);
Map<Integer, Boolean> apps = new HashMap<>();
- apps.put(appUid, permission);
+ apps.put(uid, permission);
update(mUsers, apps, true);
}
+
+ // If the newly-installed package falls within some VPN's uid range, update Netd with it.
+ // This needs to happen after the mApps update above, since removeBypassingUids() depends
+ // on mApps to check if the package can bypass VPN.
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ if (UidRange.containsUid(vpn.getValue(), uid)) {
+ final Set<Integer> changedUids = new HashSet<>();
+ changedUids.add(uid);
+ removeBypassingUids(changedUids, /* vpnAppUid */ -1);
+ updateVpnUids(vpn.getKey(), changedUids, true);
+ }
+ }
+ mAllApps.add(UserHandle.getAppId(uid));
}
- private synchronized void onAppRemoved(int appUid) {
- if (appUid < 0) {
- loge("Invalid app in onAppRemoved: " + appUid);
- return;
+ /**
+ * Called when a package is removed. See {link #ACTION_PACKAGE_REMOVED}.
+ *
+ * @param uid containing the integer uid previously assigned to the package.
+ *
+ * @hide
+ */
+ public synchronized void onPackageRemoved(int uid) {
+ // If the newly-removed package falls within some VPN's uid range, update Netd with it.
+ // This needs to happen before the mApps update below, since removeBypassingUids() depends
+ // on mApps to check if the package can bypass VPN.
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ if (UidRange.containsUid(vpn.getValue(), uid)) {
+ final Set<Integer> changedUids = new HashSet<>();
+ changedUids.add(uid);
+ removeBypassingUids(changedUids, /* vpnAppUid */ -1);
+ updateVpnUids(vpn.getKey(), changedUids, false);
+ }
}
- Map<Integer, Boolean> apps = new HashMap<>();
+ // If the package has been removed from all users on the device, clear it form mAllApps.
+ if (mPackageManager.getNameForUid(uid) == null) {
+ mAllApps.remove(UserHandle.getAppId(uid));
+ }
+ Map<Integer, Boolean> apps = new HashMap<>();
Boolean permission = null;
- String[] packages = mPackageManager.getPackagesForUid(appUid);
+ String[] packages = mPackageManager.getPackagesForUid(uid);
if (packages != null && packages.length > 0) {
for (String name : packages) {
permission = highestPermissionForUid(permission, name);
@@ -324,20 +444,266 @@
}
}
}
- if (permission == mApps.get(appUid)) {
+ if (permission == mApps.get(uid)) {
// The permissions of this UID have not changed. Nothing to do.
return;
} else if (permission != null) {
- mApps.put(appUid, permission);
- apps.put(appUid, permission);
+ mApps.put(uid, permission);
+ apps.put(uid, permission);
update(mUsers, apps, true);
} else {
- mApps.remove(appUid);
- apps.put(appUid, NETWORK); // doesn't matter which permission we pick here
+ mApps.remove(uid);
+ apps.put(uid, NETWORK); // doesn't matter which permission we pick here
update(mUsers, apps, false);
}
}
+ private static int getNetdPermissionMask(String[] requestedPermissions,
+ int[] requestedPermissionsFlags) {
+ int permissions = 0;
+ if (requestedPermissions == null || requestedPermissionsFlags == null) return permissions;
+ for (int i = 0; i < requestedPermissions.length; i++) {
+ if (requestedPermissions[i].equals(INTERNET)
+ && ((requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) != 0)) {
+ permissions |= INetd.PERMISSION_INTERNET;
+ }
+ if (requestedPermissions[i].equals(UPDATE_DEVICE_STATS)
+ && ((requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) != 0)) {
+ permissions |= INetd.PERMISSION_UPDATE_DEVICE_STATS;
+ }
+ }
+ return permissions;
+ }
+
+ private PackageInfo getPackageInfo(String packageName) {
+ try {
+ PackageInfo app = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS
+ | MATCH_ANY_USER);
+ return app;
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Called when a new set of UID ranges are added to an active VPN network
+ *
+ * @param iface The active VPN network's interface name
+ * @param rangesToAdd The new UID ranges to be added to the network
+ * @param vpnAppUid The uid of the VPN app
+ */
+ public synchronized void onVpnUidRangesAdded(@NonNull String iface, Set<UidRange> rangesToAdd,
+ int vpnAppUid) {
+ // Calculate the list of new app uids under the VPN due to the new UID ranges and update
+ // Netd about them. Because mAllApps only contains appIds instead of uids, the result might
+ // be an overestimation if an app is not installed on the user on which the VPN is running,
+ // but that's safe.
+ final Set<Integer> changedUids = intersectUids(rangesToAdd, mAllApps);
+ removeBypassingUids(changedUids, vpnAppUid);
+ updateVpnUids(iface, changedUids, true);
+ if (mVpnUidRanges.containsKey(iface)) {
+ mVpnUidRanges.get(iface).addAll(rangesToAdd);
+ } else {
+ mVpnUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
+ }
+ }
+
+ /**
+ * Called when a set of UID ranges are removed from an active VPN network
+ *
+ * @param iface The VPN network's interface name
+ * @param rangesToRemove Existing UID ranges to be removed from the VPN network
+ * @param vpnAppUid The uid of the VPN app
+ */
+ public synchronized void onVpnUidRangesRemoved(@NonNull String iface,
+ Set<UidRange> rangesToRemove, int vpnAppUid) {
+ // Calculate the list of app uids that are no longer under the VPN due to the removed UID
+ // ranges and update Netd about them.
+ final Set<Integer> changedUids = intersectUids(rangesToRemove, mAllApps);
+ removeBypassingUids(changedUids, vpnAppUid);
+ updateVpnUids(iface, changedUids, false);
+ Set<UidRange> existingRanges = mVpnUidRanges.getOrDefault(iface, null);
+ if (existingRanges == null) {
+ loge("Attempt to remove unknown vpn uid Range iface = " + iface);
+ return;
+ }
+ existingRanges.removeAll(rangesToRemove);
+ if (existingRanges.size() == 0) {
+ mVpnUidRanges.remove(iface);
+ }
+ }
+
+ /**
+ * Compute the intersection of a set of UidRanges and appIds. Returns a set of uids
+ * that satisfies:
+ * 1. falls into one of the UidRange
+ * 2. matches one of the appIds
+ */
+ private Set<Integer> intersectUids(Set<UidRange> ranges, Set<Integer> appIds) {
+ Set<Integer> result = new HashSet<>();
+ for (UidRange range : ranges) {
+ for (int userId = range.getStartUser(); userId <= range.getEndUser(); userId++) {
+ for (int appId : appIds) {
+ final int uid = UserHandle.getUid(userId, appId);
+ if (range.contains(uid)) {
+ result.add(uid);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Remove all apps which can elect to bypass the VPN from the list of uids
+ *
+ * An app can elect to bypass the VPN if it hold SYSTEM permission, or if its the active VPN
+ * app itself.
+ *
+ * @param uids The list of uids to operate on
+ * @param vpnAppUid The uid of the VPN app
+ */
+ private void removeBypassingUids(Set<Integer> uids, int vpnAppUid) {
+ uids.remove(vpnAppUid);
+ uids.removeIf(uid -> mApps.getOrDefault(uid, NETWORK) == SYSTEM);
+ }
+
+ /**
+ * Update netd about the list of uids that are under an active VPN connection which they cannot
+ * bypass.
+ *
+ * This is to instruct netd to set up appropriate filtering rules for these uids, such that they
+ * can only receive ingress packets from the VPN's tunnel interface (and loopback).
+ *
+ * @param iface the interface name of the active VPN connection
+ * @param add {@code true} if the uids are to be added to the interface, {@code false} if they
+ * are to be removed from the interface.
+ */
+ private void updateVpnUids(String iface, Set<Integer> uids, boolean add) {
+ if (uids.size() == 0) {
+ return;
+ }
+ try {
+ if (add) {
+ mNetd.firewallAddUidInterfaceRules(iface, toIntArray(uids));
+ } else {
+ mNetd.firewallRemoveUidInterfaceRules(toIntArray(uids));
+ }
+ } catch (ServiceSpecificException e) {
+ // Silently ignore exception when device does not support eBPF, otherwise just log
+ // the exception and do not crash
+ if (e.errorCode != OsConstants.EOPNOTSUPP) {
+ loge("Exception when updating permissions: ", e);
+ }
+ } catch (RemoteException e) {
+ loge("Exception when updating permissions: ", e);
+ }
+ }
+
+ /**
+ * Called by PackageListObserver when a package is installed/uninstalled. Send the updated
+ * permission information to netd.
+ *
+ * @param uid the app uid of the package installed
+ * @param permissions the permissions the app requested and netd cares about.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ void sendPackagePermissionsForUid(int uid, int permissions) {
+ SparseIntArray netdPermissionsAppIds = new SparseIntArray();
+ netdPermissionsAppIds.put(uid, permissions);
+ sendPackagePermissionsToNetd(netdPermissionsAppIds);
+ }
+
+ /**
+ * Called by packageManagerService to send IPC to netd. Grant or revoke the INTERNET
+ * and/or UPDATE_DEVICE_STATS permission of the uids in array.
+ *
+ * @param netdPermissionsAppIds integer pairs of uids and the permission granted to it. If the
+ * permission is 0, revoke all permissions of that uid.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ void sendPackagePermissionsToNetd(SparseIntArray netdPermissionsAppIds) {
+ if (mNetd == null) {
+ Log.e(TAG, "Failed to get the netd service");
+ return;
+ }
+ ArrayList<Integer> allPermissionAppIds = new ArrayList<>();
+ ArrayList<Integer> internetPermissionAppIds = new ArrayList<>();
+ ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>();
+ ArrayList<Integer> noPermissionAppIds = new ArrayList<>();
+ ArrayList<Integer> uninstalledAppIds = new ArrayList<>();
+ for (int i = 0; i < netdPermissionsAppIds.size(); i++) {
+ int permissions = netdPermissionsAppIds.valueAt(i);
+ switch(permissions) {
+ case (INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS):
+ allPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+ break;
+ case INetd.PERMISSION_INTERNET:
+ internetPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+ break;
+ case INetd.PERMISSION_UPDATE_DEVICE_STATS:
+ updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+ break;
+ case INetd.PERMISSION_NONE:
+ noPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+ break;
+ case INetd.PERMISSION_UNINSTALLED:
+ uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
+ default:
+ Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
+ + netdPermissionsAppIds.keyAt(i));
+ }
+ }
+ try {
+ // TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
+ if (allPermissionAppIds.size() != 0) {
+ mNetd.trafficSetNetPermForUids(
+ INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
+ ArrayUtils.convertToIntArray(allPermissionAppIds));
+ }
+ if (internetPermissionAppIds.size() != 0) {
+ mNetd.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET,
+ ArrayUtils.convertToIntArray(internetPermissionAppIds));
+ }
+ if (updateStatsPermissionAppIds.size() != 0) {
+ mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS,
+ ArrayUtils.convertToIntArray(updateStatsPermissionAppIds));
+ }
+ if (noPermissionAppIds.size() != 0) {
+ mNetd.trafficSetNetPermForUids(INetd.PERMISSION_NONE,
+ ArrayUtils.convertToIntArray(noPermissionAppIds));
+ }
+ if (uninstalledAppIds.size() != 0) {
+ mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED,
+ ArrayUtils.convertToIntArray(uninstalledAppIds));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Pass appId list of special permission failed." + e);
+ }
+ }
+
+ /** Should only be used by unit tests */
+ @VisibleForTesting
+ public Set<UidRange> getVpnUidRanges(String iface) {
+ return mVpnUidRanges.get(iface);
+ }
+
+ /** Dump info to dumpsys */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("Interface filtering rules:");
+ pw.increaseIndent();
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ pw.println("Interface: " + vpn.getKey());
+ pw.println("UIDs: " + vpn.getValue().toString());
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
private static void log(String s) {
if (DBG) {
Log.d(TAG, s);
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
new file mode 100644
index 0000000..e715890
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -0,0 +1,311 @@
+/**
+ * Copyright (c) 2018, 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.provider.Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST;
+import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_HOST;
+import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_PAC;
+import static android.provider.Settings.Global.GLOBAL_HTTP_PROXY_PORT;
+import static android.provider.Settings.Global.HTTP_PROXY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Proxy;
+import android.net.ProxyInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * A class to handle proxy for ConnectivityService.
+ *
+ * @hide
+ */
+public class ProxyTracker {
+ private static final String TAG = ProxyTracker.class.getSimpleName();
+ private static final boolean DBG = true;
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final Object mProxyLock = new Object();
+ // The global proxy is the proxy that is set device-wide, overriding any network-specific
+ // proxy. Note however that proxies are hints ; the system does not enforce their use. Hence
+ // this value is only for querying.
+ @Nullable
+ @GuardedBy("mProxyLock")
+ private ProxyInfo mGlobalProxy = null;
+ // The default proxy is the proxy that applies to no particular network if the global proxy
+ // is not set. Individual networks have their own settings that override this. This member
+ // is set through setDefaultProxy, which is called when the default network changes proxies
+ // in its LinkProperties, or when ConnectivityService switches to a new default network, or
+ // when PacManager resolves the proxy.
+ @Nullable
+ @GuardedBy("mProxyLock")
+ private volatile ProxyInfo mDefaultProxy = null;
+ // Whether the default proxy is enabled.
+ @GuardedBy("mProxyLock")
+ private boolean mDefaultProxyEnabled = true;
+
+ // The object responsible for Proxy Auto Configuration (PAC).
+ @NonNull
+ private final PacManager mPacManager;
+
+ public ProxyTracker(@NonNull final Context context,
+ @NonNull final Handler connectivityServiceInternalHandler, final int pacChangedEvent) {
+ mContext = context;
+ mPacManager = new PacManager(context, connectivityServiceInternalHandler, pacChangedEvent);
+ }
+
+ // Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
+ // (e.g. if mGlobalProxy==null fall back to network-specific proxy, if network-specific
+ // proxy is null then there is no proxy in place).
+ @Nullable
+ private static ProxyInfo canonicalizeProxyInfo(@Nullable final ProxyInfo proxy) {
+ if (proxy != null && TextUtils.isEmpty(proxy.getHost())
+ && Uri.EMPTY.equals(proxy.getPacFileUrl())) {
+ return null;
+ }
+ return proxy;
+ }
+
+ // ProxyInfo equality functions with a couple modifications over ProxyInfo.equals() to make it
+ // better for determining if a new proxy broadcast is necessary:
+ // 1. Canonicalize empty ProxyInfos to null so an empty proxy compares equal to null so as to
+ // avoid unnecessary broadcasts.
+ // 2. Make sure all parts of the ProxyInfo's compare true, including the host when a PAC URL
+ // is in place. This is important so legacy PAC resolver (see com.android.proxyhandler)
+ // changes aren't missed. The legacy PAC resolver pretends to be a simple HTTP proxy but
+ // actually uses the PAC to resolve; this results in ProxyInfo's with PAC URL, host and port
+ // all set.
+ public static boolean proxyInfoEqual(@Nullable final ProxyInfo a, @Nullable final ProxyInfo b) {
+ final ProxyInfo pa = canonicalizeProxyInfo(a);
+ final ProxyInfo pb = canonicalizeProxyInfo(b);
+ // ProxyInfo.equals() doesn't check hosts when PAC URLs are present, but we need to check
+ // hosts even when PAC URLs are present to account for the legacy PAC resolver.
+ return Objects.equals(pa, pb) && (pa == null || Objects.equals(pa.getHost(), pb.getHost()));
+ }
+
+ /**
+ * Gets the default system-wide proxy.
+ *
+ * This will return the global proxy if set, otherwise the default proxy if in use. Note
+ * that this is not necessarily the proxy that any given process should use, as the right
+ * proxy for a process is the proxy for the network this process will use, which may be
+ * different from this value. This value is simply the default in case there is no proxy set
+ * in the network that will be used by a specific process.
+ * @return The default system-wide proxy or null if none.
+ */
+ @Nullable
+ public ProxyInfo getDefaultProxy() {
+ // This information is already available as a world read/writable jvm property.
+ synchronized (mProxyLock) {
+ if (mGlobalProxy != null) return mGlobalProxy;
+ if (mDefaultProxyEnabled) return mDefaultProxy;
+ return null;
+ }
+ }
+
+ /**
+ * Gets the global proxy.
+ *
+ * @return The global proxy or null if none.
+ */
+ @Nullable
+ public ProxyInfo getGlobalProxy() {
+ // This information is already available as a world read/writable jvm property.
+ synchronized (mProxyLock) {
+ return mGlobalProxy;
+ }
+ }
+
+ /**
+ * Read the global proxy settings and cache them in memory.
+ */
+ public void loadGlobalProxy() {
+ ContentResolver res = mContext.getContentResolver();
+ String host = Settings.Global.getString(res, GLOBAL_HTTP_PROXY_HOST);
+ int port = Settings.Global.getInt(res, GLOBAL_HTTP_PROXY_PORT, 0);
+ String exclList = Settings.Global.getString(res, GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ String pacFileUrl = Settings.Global.getString(res, GLOBAL_HTTP_PROXY_PAC);
+ if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) {
+ ProxyInfo proxyProperties;
+ if (!TextUtils.isEmpty(pacFileUrl)) {
+ proxyProperties = new ProxyInfo(pacFileUrl);
+ } else {
+ proxyProperties = new ProxyInfo(host, port, exclList);
+ }
+ if (!proxyProperties.isValid()) {
+ if (DBG) Slog.d(TAG, "Invalid proxy properties, ignoring: " + proxyProperties);
+ return;
+ }
+
+ synchronized (mProxyLock) {
+ mGlobalProxy = proxyProperties;
+ }
+ }
+ loadDeprecatedGlobalHttpProxy();
+ // TODO : shouldn't this function call mPacManager.setCurrentProxyScriptUrl ?
+ }
+
+ /**
+ * Read the global proxy from the deprecated Settings.Global.HTTP_PROXY setting and apply it.
+ */
+ public void loadDeprecatedGlobalHttpProxy() {
+ final String proxy = Settings.Global.getString(mContext.getContentResolver(), HTTP_PROXY);
+ if (!TextUtils.isEmpty(proxy)) {
+ String data[] = proxy.split(":");
+ if (data.length == 0) {
+ return;
+ }
+
+ final String proxyHost = data[0];
+ int proxyPort = 8080;
+ if (data.length > 1) {
+ try {
+ proxyPort = Integer.parseInt(data[1]);
+ } catch (NumberFormatException e) {
+ return;
+ }
+ }
+ final ProxyInfo p = new ProxyInfo(proxyHost, proxyPort, "");
+ setGlobalProxy(p);
+ }
+ }
+
+ /**
+ * Sends the system broadcast informing apps about a new proxy configuration.
+ *
+ * Confusingly this method also sets the PAC file URL. TODO : separate this, it has nothing
+ * to do in a "sendProxyBroadcast" method.
+ */
+ public void sendProxyBroadcast() {
+ final ProxyInfo defaultProxy = getDefaultProxy();
+ final ProxyInfo proxyInfo = null != defaultProxy ? defaultProxy : new ProxyInfo("", 0, "");
+ if (mPacManager.setCurrentProxyScriptUrl(proxyInfo) == PacManager.DONT_SEND_BROADCAST) {
+ return;
+ }
+ if (DBG) Slog.d(TAG, "sending Proxy Broadcast for " + proxyInfo);
+ Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxyInfo);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Sets the global proxy in memory. Also writes the values to the global settings of the device.
+ *
+ * @param proxyInfo the proxy spec, or null for no proxy.
+ */
+ public void setGlobalProxy(@Nullable ProxyInfo proxyInfo) {
+ synchronized (mProxyLock) {
+ // ProxyInfo#equals is not commutative :( and is public API, so it can't be fixed.
+ if (proxyInfo == mGlobalProxy) return;
+ if (proxyInfo != null && proxyInfo.equals(mGlobalProxy)) return;
+ if (mGlobalProxy != null && mGlobalProxy.equals(proxyInfo)) return;
+
+ final String host;
+ final int port;
+ final String exclList;
+ final String pacFileUrl;
+ if (proxyInfo != null && (!TextUtils.isEmpty(proxyInfo.getHost()) ||
+ !Uri.EMPTY.equals(proxyInfo.getPacFileUrl()))) {
+ if (!proxyInfo.isValid()) {
+ if (DBG) Slog.d(TAG, "Invalid proxy properties, ignoring: " + proxyInfo);
+ return;
+ }
+ mGlobalProxy = new ProxyInfo(proxyInfo);
+ host = mGlobalProxy.getHost();
+ port = mGlobalProxy.getPort();
+ exclList = mGlobalProxy.getExclusionListAsString();
+ pacFileUrl = Uri.EMPTY.equals(proxyInfo.getPacFileUrl())
+ ? "" : proxyInfo.getPacFileUrl().toString();
+ } else {
+ host = "";
+ port = 0;
+ exclList = "";
+ pacFileUrl = "";
+ mGlobalProxy = null;
+ }
+ final ContentResolver res = mContext.getContentResolver();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Settings.Global.putString(res, GLOBAL_HTTP_PROXY_HOST, host);
+ Settings.Global.putInt(res, GLOBAL_HTTP_PROXY_PORT, port);
+ Settings.Global.putString(res, GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclList);
+ Settings.Global.putString(res, GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ sendProxyBroadcast();
+ }
+ }
+
+ /**
+ * Sets the default proxy for the device.
+ *
+ * The default proxy is the proxy used for networks that do not have a specific proxy.
+ * @param proxyInfo the proxy spec, or null for no proxy.
+ */
+ public void setDefaultProxy(@Nullable ProxyInfo proxyInfo) {
+ synchronized (mProxyLock) {
+ if (Objects.equals(mDefaultProxy, proxyInfo)) return;
+ if (proxyInfo != null && !proxyInfo.isValid()) {
+ if (DBG) Slog.d(TAG, "Invalid proxy properties, ignoring: " + proxyInfo);
+ return;
+ }
+
+ // This call could be coming from the PacManager, containing the port of the local
+ // proxy. If this new proxy matches the global proxy then copy this proxy to the
+ // global (to get the correct local port), and send a broadcast.
+ // TODO: Switch PacManager to have its own message to send back rather than
+ // reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
+ if ((mGlobalProxy != null) && (proxyInfo != null)
+ && (!Uri.EMPTY.equals(proxyInfo.getPacFileUrl()))
+ && proxyInfo.getPacFileUrl().equals(mGlobalProxy.getPacFileUrl())) {
+ mGlobalProxy = proxyInfo;
+ sendProxyBroadcast();
+ return;
+ }
+ mDefaultProxy = proxyInfo;
+
+ if (mGlobalProxy != null) return;
+ if (mDefaultProxyEnabled) {
+ sendProxyBroadcast();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
new file mode 100644
index 0000000..e570ef1
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2019 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.net.SocketKeepalive.DATA_RECEIVED;
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE;
+import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.system.OsConstants.ENOPROTOOPT;
+import static android.system.OsConstants.FIONREAD;
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IP_TOS;
+import static android.system.OsConstants.IP_TTL;
+import static android.system.OsConstants.TIOCOUTQ;
+
+import android.annotation.NonNull;
+import android.net.NetworkUtils;
+import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.SocketKeepalive.InvalidSocketException;
+import android.net.TcpKeepalivePacketData;
+import android.net.TcpKeepalivePacketDataParcelable;
+import android.net.TcpRepairWindow;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.os.Messenger;
+import android.system.ErrnoException;
+import android.system.Int32Ref;
+import android.system.Os;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
+
+import java.io.FileDescriptor;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+
+/**
+ * Manage tcp socket which offloads tcp keepalive.
+ *
+ * The input socket will be changed to repair mode and the application
+ * will not have permission to read/write data. If the application wants
+ * to write data, it must stop tcp keepalive offload to leave repair mode
+ * first. If a remote packet arrives, repair mode will be turned off and
+ * offload will be stopped. The application will receive a callback to know
+ * it can start reading data.
+ *
+ * {start,stop}SocketMonitor are thread-safe, but care must be taken in the
+ * order in which they are called. Please note that while calling
+ * {@link #startSocketMonitor(FileDescriptor, Messenger, int)} multiple times
+ * with either the same slot or the same FileDescriptor without stopping it in
+ * between will result in an exception, calling {@link #stopSocketMonitor(int)}
+ * multiple times with the same int is explicitly a no-op.
+ * Please also note that switching the socket to repair mode is not synchronized
+ * with either of these operations and has to be done in an orderly fashion
+ * with stopSocketMonitor. Take care in calling these in the right order.
+ * @hide
+ */
+public class TcpKeepaliveController {
+ private static final String TAG = "TcpKeepaliveController";
+ private static final boolean DBG = false;
+
+ private final MessageQueue mFdHandlerQueue;
+
+ private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+
+ // Reference include/uapi/linux/tcp.h
+ private static final int TCP_REPAIR = 19;
+ private static final int TCP_REPAIR_QUEUE = 20;
+ private static final int TCP_QUEUE_SEQ = 21;
+ private static final int TCP_NO_QUEUE = 0;
+ private static final int TCP_RECV_QUEUE = 1;
+ private static final int TCP_SEND_QUEUE = 2;
+ private static final int TCP_REPAIR_OFF = 0;
+ private static final int TCP_REPAIR_ON = 1;
+ // Reference include/uapi/linux/sockios.h
+ private static final int SIOCINQ = FIONREAD;
+ private static final int SIOCOUTQ = TIOCOUTQ;
+
+ /**
+ * Keeps track of packet listeners.
+ * Key: slot number of keepalive offload.
+ * Value: {@link FileDescriptor} being listened to.
+ */
+ @GuardedBy("mListeners")
+ private final SparseArray<FileDescriptor> mListeners = new SparseArray<>();
+
+ public TcpKeepaliveController(final Handler connectivityServiceHandler) {
+ mFdHandlerQueue = connectivityServiceHandler.getLooper().getQueue();
+ }
+
+ /** Build tcp keepalive packet. */
+ public static TcpKeepalivePacketData getTcpKeepalivePacket(@NonNull FileDescriptor fd)
+ throws InvalidPacketException, InvalidSocketException {
+ try {
+ final TcpKeepalivePacketDataParcelable tcpDetails = switchToRepairMode(fd);
+ return TcpKeepalivePacketData.tcpKeepalivePacket(tcpDetails);
+ } catch (InvalidPacketException | InvalidSocketException e) {
+ switchOutOfRepairMode(fd);
+ throw e;
+ }
+ }
+ /**
+ * Switch the tcp socket to repair mode and query detail tcp information.
+ *
+ * @param fd the fd of socket on which to use keepalive offload.
+ * @return a {@link TcpKeepalivePacketData#TcpKeepalivePacketDataParcelable} object for current
+ * tcp/ip information.
+ */
+ private static TcpKeepalivePacketDataParcelable switchToRepairMode(FileDescriptor fd)
+ throws InvalidSocketException {
+ if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd);
+ final TcpKeepalivePacketDataParcelable tcpDetails = new TcpKeepalivePacketDataParcelable();
+ final SocketAddress srcSockAddr;
+ final SocketAddress dstSockAddr;
+ final TcpRepairWindow trw;
+
+ // Query source address and port.
+ try {
+ srcSockAddr = Os.getsockname(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Get sockname fail: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
+ if (srcSockAddr instanceof InetSocketAddress) {
+ tcpDetails.srcAddress = getAddress((InetSocketAddress) srcSockAddr);
+ tcpDetails.srcPort = getPort((InetSocketAddress) srcSockAddr);
+ } else {
+ Log.e(TAG, "Invalid or mismatched SocketAddress");
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET);
+ }
+ // Query destination address and port.
+ try {
+ dstSockAddr = Os.getpeername(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Get peername fail: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
+ if (dstSockAddr instanceof InetSocketAddress) {
+ tcpDetails.dstAddress = getAddress((InetSocketAddress) dstSockAddr);
+ tcpDetails.dstPort = getPort((InetSocketAddress) dstSockAddr);
+ } else {
+ Log.e(TAG, "Invalid or mismatched peer SocketAddress");
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET);
+ }
+
+ // Query sequence and ack number
+ dropAllIncomingPackets(fd, true);
+ try {
+ // Switch to tcp repair mode.
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_ON);
+
+ // Check if socket is idle.
+ if (!isSocketIdle(fd)) {
+ Log.e(TAG, "Socket is not idle");
+ throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
+ }
+ // Query write sequence number from SEND_QUEUE.
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
+ tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ // Query read sequence number from RECV_QUEUE.
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
+ tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
+ // Finally, check if socket is still idle. TODO : this check needs to move to
+ // after starting polling to prevent a race.
+ if (!isReceiveQueueEmpty(fd)) {
+ Log.e(TAG, "Fatal: receive queue of this socket is not empty");
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET);
+ }
+ if (!isSendQueueEmpty(fd)) {
+ Log.e(TAG, "Socket is not idle");
+ throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
+ }
+
+ // Query tcp window size.
+ trw = NetworkUtils.getTcpRepairWindow(fd);
+ tcpDetails.rcvWnd = trw.rcvWnd;
+ tcpDetails.rcvWndScale = trw.rcvWndScale;
+ if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
+ // Query TOS.
+ tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
+ // Query TTL.
+ tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
+ }
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Exception reading TCP state from socket", e);
+ if (e.errno == ENOPROTOOPT) {
+ // ENOPROTOOPT may happen in kernel version lower than 4.8.
+ // Treat it as ERROR_UNSUPPORTED.
+ throw new InvalidSocketException(ERROR_UNSUPPORTED, e);
+ } else {
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
+ } finally {
+ dropAllIncomingPackets(fd, false);
+ }
+
+ // Keepalive sequence number is last sequence number - 1. If it couldn't be retrieved,
+ // then it must be set to -1, so decrement in all cases.
+ tcpDetails.seq = tcpDetails.seq - 1;
+
+ return tcpDetails;
+ }
+
+ /**
+ * Switch the tcp socket out of repair mode.
+ *
+ * @param fd the fd of socket to switch back to normal.
+ */
+ private static void switchOutOfRepairMode(@NonNull final FileDescriptor fd) {
+ try {
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot switch socket out of repair mode", e);
+ // Well, there is not much to do here to recover
+ }
+ }
+
+ /**
+ * Start monitoring incoming packets.
+ *
+ * @param fd socket fd to monitor.
+ * @param ki a {@link KeepaliveInfo} that tracks information about a socket keepalive.
+ * @param slot keepalive slot.
+ */
+ public void startSocketMonitor(@NonNull final FileDescriptor fd,
+ @NonNull final KeepaliveInfo ki, final int slot)
+ throws IllegalArgumentException, InvalidSocketException {
+ synchronized (mListeners) {
+ if (null != mListeners.get(slot)) {
+ throw new IllegalArgumentException("This slot is already taken");
+ }
+ for (int i = 0; i < mListeners.size(); ++i) {
+ if (fd.equals(mListeners.valueAt(i))) {
+ Log.e(TAG, "This fd is already registered.");
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET);
+ }
+ }
+ mFdHandlerQueue.addOnFileDescriptorEventListener(fd, FD_EVENTS, (readyFd, events) -> {
+ // This can't be called twice because the queue guarantees that once the listener
+ // is unregistered it can't be called again, even for a message that arrived
+ // before it was unregistered.
+ final int reason;
+ if (0 != (events & EVENT_ERROR)) {
+ reason = ERROR_INVALID_SOCKET;
+ } else {
+ reason = DATA_RECEIVED;
+ }
+ ki.onFileDescriptorInitiatedStop(reason);
+ // The listener returns the new set of events to listen to. Because 0 means no
+ // event, the listener gets unregistered.
+ return 0;
+ });
+ mListeners.put(slot, fd);
+ }
+ }
+
+ /** Stop socket monitor */
+ // This slot may have been stopped automatically already because the socket received data,
+ // was closed on the other end or otherwise suffered some error. In this case, this function
+ // is a no-op.
+ public void stopSocketMonitor(final int slot) {
+ final FileDescriptor fd;
+ synchronized (mListeners) {
+ fd = mListeners.get(slot);
+ if (null == fd) return;
+ mListeners.remove(slot);
+ }
+ mFdHandlerQueue.removeOnFileDescriptorEventListener(fd);
+ if (DBG) Log.d(TAG, "Moving socket out of repair mode for stop : " + fd);
+ switchOutOfRepairMode(fd);
+ }
+
+ private static byte [] getAddress(InetSocketAddress inetAddr) {
+ return inetAddr.getAddress().getAddress();
+ }
+
+ private static int getPort(InetSocketAddress inetAddr) {
+ return inetAddr.getPort();
+ }
+
+ private static boolean isSocketIdle(FileDescriptor fd) throws ErrnoException {
+ return isReceiveQueueEmpty(fd) && isSendQueueEmpty(fd);
+ }
+
+ private static boolean isReceiveQueueEmpty(FileDescriptor fd)
+ throws ErrnoException {
+ Int32Ref result = new Int32Ref(-1);
+ Os.ioctlInt(fd, SIOCINQ, result);
+ if (result.value != 0) {
+ Log.e(TAG, "Read queue has data");
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isSendQueueEmpty(FileDescriptor fd)
+ throws ErrnoException {
+ Int32Ref result = new Int32Ref(-1);
+ Os.ioctlInt(fd, SIOCOUTQ, result);
+ if (result.value != 0) {
+ Log.e(TAG, "Write queue has data");
+ return false;
+ }
+ return true;
+ }
+
+ private static void dropAllIncomingPackets(FileDescriptor fd, boolean enable)
+ throws InvalidSocketException {
+ try {
+ if (enable) {
+ NetworkUtils.attachDropAllBPFFilter(fd);
+ } else {
+ NetworkUtils.detachBPFFilter(fd);
+ }
+ } catch (SocketException e) {
+ Log.e(TAG, "Socket Exception: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
+ }
+}
diff --git a/services/core/jni/com_android_server_TestNetworkService.cpp b/services/core/jni/com_android_server_TestNetworkService.cpp
new file mode 100644
index 0000000..36a6fde
--- /dev/null
+++ b/services/core/jni/com_android_server_TestNetworkService.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#define LOG_NDEBUG 0
+
+#define LOG_TAG "TestNetworkServiceJni"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <linux/ipv6_route.h>
+#include <linux/route.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <log/log.h>
+
+#include "netutils/ifc.h"
+
+#include "jni.h"
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+namespace android {
+
+//------------------------------------------------------------------------------
+
+static void throwException(JNIEnv* env, int error, const char* action, const char* iface) {
+ const std::string& msg =
+ android::base::StringPrintf("Error %s %s: %s", action, iface, strerror(error));
+
+ jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
+}
+
+static int createTunTapInterface(JNIEnv* env, bool isTun, const char* iface) {
+ base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK));
+ ifreq ifr{};
+
+ // Allocate interface.
+ ifr.ifr_flags = (isTun ? IFF_TUN : IFF_TAP) | IFF_NO_PI;
+ strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
+ if (ioctl(tun.get(), TUNSETIFF, &ifr)) {
+ throwException(env, errno, "allocating", ifr.ifr_name);
+ return -1;
+ }
+
+ // Activate interface using an unconnected datagram socket.
+ base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
+ ifr.ifr_flags = IFF_UP;
+
+ if (ioctl(inet6CtrlSock.get(), SIOCSIFFLAGS, &ifr)) {
+ throwException(env, errno, "activating", ifr.ifr_name);
+ return -1;
+ }
+
+ return tun.release();
+}
+
+//------------------------------------------------------------------------------
+
+static jint create(JNIEnv* env, jobject /* thiz */, jboolean isTun, jstring jIface) {
+ ScopedUtfChars iface(env, jIface);
+ if (!iface.c_str()) {
+ jniThrowNullPointerException(env, "iface");
+ return -1;
+ }
+
+ int tun = createTunTapInterface(env, isTun, iface.c_str());
+
+ // Any exceptions will be thrown from the createTunTapInterface call
+ return tun;
+}
+
+//------------------------------------------------------------------------------
+
+static const JNINativeMethod gMethods[] = {
+ {"jniCreateTunTap", "(ZLjava/lang/String;)I", (void*)create},
+};
+
+int register_android_server_TestNetworkService(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/TestNetworkService", gMethods,
+ NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
deleted file mode 100644
index dec8ca2..0000000
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ /dev/null
@@ -1,62 +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 android.net.apf;
-
-/**
- * APF program support capabilities.
- *
- * @hide
- */
-public class ApfCapabilities {
- /**
- * Version of APF instruction set supported for packet filtering. 0 indicates no support for
- * packet filtering using APF programs.
- */
- public final int apfVersionSupported;
-
- /**
- * Maximum size of APF program allowed.
- */
- public final int maximumApfProgramSize;
-
- /**
- * Format of packets passed to APF filter. Should be one of ARPHRD_*
- */
- public final int apfPacketFormat;
-
- public ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat)
- {
- this.apfVersionSupported = apfVersionSupported;
- this.maximumApfProgramSize = maximumApfProgramSize;
- this.apfPacketFormat = apfPacketFormat;
- }
-
- public String toString() {
- return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
- apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
- }
-
- /**
- * Returns true if the APF interpreter advertises support for the data buffer access opcodes
- * LDDW and STDW.
- *
- * Full LDDW and STDW support is present from APFv4 on.
- */
- public boolean hasDataAccess() {
- return apfVersionSupported >= 4;
- }
-}
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
new file mode 100644
index 0000000..5260b30
--- /dev/null
+++ b/tests/net/Android.bp
@@ -0,0 +1,71 @@
+//########################################################################
+// Build FrameworksNetTests package
+//########################################################################
+java_defaults {
+ name: "FrameworksNetTests-jni-defaults",
+ jni_libs: [
+ "ld-android",
+ "libartbase",
+ "libbacktrace",
+ "libbase",
+ "libbinder",
+ "libbinderthreadstate",
+ "libbpf",
+ "libbpf_android",
+ "libc++",
+ "libcgrouprc",
+ "libcrypto",
+ "libcutils",
+ "libdexfile",
+ "libdl_android",
+ "libhidl-gen-utils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "libjsoncpp",
+ "liblog",
+ "liblzma",
+ "libnativehelper",
+ "libnetdbpf",
+ "libnetdutils",
+ "libnetworkstatsfactorytestjni",
+ "libpackagelistparser",
+ "libpcre2",
+ "libprocessgroup",
+ "libselinux",
+ "libtinyxml2",
+ "libui",
+ "libunwindstack",
+ "libutils",
+ "libutilscallstack",
+ "libvndksupport",
+ "libziparchive",
+ "libz",
+ "netd_aidl_interface-V2-cpp",
+ ],
+}
+
+android_test {
+ name: "FrameworksNetTests",
+ defaults: ["FrameworksNetTests-jni-defaults"],
+ srcs: ["java/**/*.java", "java/**/*.kt"],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ static_libs: [
+ "androidx.test.rules",
+ "FrameworksNetCommonTests",
+ "frameworks-base-testutils",
+ "framework-protos",
+ "mockito-target-minus-junit4",
+ "net-tests-utils",
+ "platform-test-annotations",
+ "services.core",
+ "services.net",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ ],
+}
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
new file mode 100644
index 0000000..e44d460
--- /dev/null
+++ b/tests/net/common/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2019 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.
+//
+
+// Tests in this folder are included both in unit tests and CTS.
+// They must be fast and stable, and exercise public or test APIs.
+java_library {
+ name: "FrameworksNetCommonTests",
+ srcs: ["java/**/*.java", "java/**/*.kt"],
+ static_libs: [
+ "androidx.test.rules",
+ "junit",
+ "mockito-target-minus-junit4",
+ "net-tests-utils",
+ "platform-test-annotations",
+ ],
+ libs: [
+ "android.test.base.stubs",
+ ],
+}
diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java
new file mode 100644
index 0000000..eed7159
--- /dev/null
+++ b/tests/net/common/java/android/net/CaptivePortalTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CaptivePortalTest {
+ private static final int DEFAULT_TIMEOUT_MS = 5000;
+ private static final String TEST_PACKAGE_NAME = "com.google.android.test";
+
+ private final class MyCaptivePortalImpl extends ICaptivePortal.Stub {
+ int mCode = -1;
+ String mPackageName = null;
+
+ @Override
+ public void appResponse(final int response) throws RemoteException {
+ mCode = response;
+ }
+
+ @Override
+ public void logEvent(int eventId, String packageName) throws RemoteException {
+ mCode = eventId;
+ mPackageName = packageName;
+ }
+ }
+
+ private interface TestFunctor {
+ void useCaptivePortal(CaptivePortal o);
+ }
+
+ private MyCaptivePortalImpl runCaptivePortalTest(TestFunctor f) {
+ final MyCaptivePortalImpl cp = new MyCaptivePortalImpl();
+ f.useCaptivePortal(new CaptivePortal(cp.asBinder()));
+ return cp;
+ }
+
+ @Test
+ public void testReportCaptivePortalDismissed() {
+ final MyCaptivePortalImpl result =
+ runCaptivePortalTest(c -> c.reportCaptivePortalDismissed());
+ assertEquals(result.mCode, CaptivePortal.APP_RETURN_DISMISSED);
+ }
+
+ @Test
+ public void testIgnoreNetwork() {
+ final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.ignoreNetwork());
+ assertEquals(result.mCode, CaptivePortal.APP_RETURN_UNWANTED);
+ }
+
+ @Test
+ public void testUseNetwork() {
+ final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.useNetwork());
+ assertEquals(result.mCode, CaptivePortal.APP_RETURN_WANTED_AS_IS);
+ }
+
+ @Test
+ public void testLogEvent() {
+ final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent(
+ MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
+ TEST_PACKAGE_NAME));
+ assertEquals(result.mCode, MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY);
+ assertEquals(result.mPackageName, TEST_PACKAGE_NAME);
+ }
+}
diff --git a/tests/net/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java
similarity index 76%
rename from tests/net/java/android/net/IpPrefixTest.java
rename to tests/net/common/java/android/net/IpPrefixTest.java
index 1f1ba2e..985e10d 100644
--- a/tests/net/java/android/net/IpPrefixTest.java
+++ b/tests/net/common/java/android/net/IpPrefixTest.java
@@ -16,29 +16,32 @@
package android.net;
+import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.os.Parcel;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.net.InetAddress;
import java.util.Random;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class IpPrefixTest {
- private static InetAddress Address(String addr) {
+ private static InetAddress address(String addr) {
return InetAddress.parseNumericAddress(addr);
}
@@ -57,59 +60,59 @@
try {
p = new IpPrefix((byte[]) null, 9);
fail("Expected NullPointerException: null byte array");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix((InetAddress) null, 10);
fail("Expected NullPointerException: null InetAddress");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix((String) null);
fail("Expected NullPointerException: null String");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
byte[] b2 = {1, 2, 3, 4, 5};
p = new IpPrefix(b2, 29);
fail("Expected IllegalArgumentException: invalid array length");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("1.2.3.4");
fail("Expected IllegalArgumentException: no prefix length");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("1.2.3.4/");
fail("Expected IllegalArgumentException: empty prefix length");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("foo/32");
fail("Expected IllegalArgumentException: invalid address");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("1/32");
fail("Expected IllegalArgumentException: deprecated IPv4 format");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("1.2.3.256/32");
fail("Expected IllegalArgumentException: invalid IPv4 address");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("foo/32");
fail("Expected IllegalArgumentException: non-address");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("f00:::/32");
fail("Expected IllegalArgumentException: invalid IPv6 address");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
}
@Test
@@ -131,17 +134,17 @@
try {
p = new IpPrefix(IPV4_BYTES, 33);
fail("Expected IllegalArgumentException: invalid prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix(IPV4_BYTES, 128);
fail("Expected IllegalArgumentException: invalid prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix(IPV4_BYTES, -1);
fail("Expected IllegalArgumentException: negative prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
p = new IpPrefix(IPV6_BYTES, 128);
assertEquals("2001:db8:dead:beef:f00::a0/128", p.toString());
@@ -161,93 +164,82 @@
try {
p = new IpPrefix(IPV6_BYTES, -1);
fail("Expected IllegalArgumentException: negative prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix(IPV6_BYTES, 129);
fail("Expected IllegalArgumentException: negative prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
}
- private void assertAreEqual(Object o1, Object o2) {
- assertTrue(o1.equals(o2));
- assertTrue(o2.equals(o1));
- }
-
- private void assertAreNotEqual(Object o1, Object o2) {
- assertFalse(o1.equals(o2));
- assertFalse(o2.equals(o1));
- }
-
@Test
public void testEquals() {
IpPrefix p1, p2;
p1 = new IpPrefix("192.0.2.251/23");
p2 = new IpPrefix(new byte[]{(byte) 192, (byte) 0, (byte) 2, (byte) 251}, 23);
- assertAreEqual(p1, p2);
+ assertEqualBothWays(p1, p2);
p1 = new IpPrefix("192.0.2.5/23");
- assertAreEqual(p1, p2);
+ assertEqualBothWays(p1, p2);
p1 = new IpPrefix("192.0.2.5/24");
- assertAreNotEqual(p1, p2);
+ assertNotEqualEitherWay(p1, p2);
p1 = new IpPrefix("192.0.4.5/23");
- assertAreNotEqual(p1, p2);
+ assertNotEqualEitherWay(p1, p2);
p1 = new IpPrefix("2001:db8:dead:beef:f00::80/122");
p2 = new IpPrefix(IPV6_BYTES, 122);
assertEquals("2001:db8:dead:beef:f00::80/122", p2.toString());
- assertAreEqual(p1, p2);
+ assertEqualBothWays(p1, p2);
p1 = new IpPrefix("2001:db8:dead:beef:f00::bf/122");
- assertAreEqual(p1, p2);
+ assertEqualBothWays(p1, p2);
p1 = new IpPrefix("2001:db8:dead:beef:f00::8:0/123");
- assertAreNotEqual(p1, p2);
+ assertNotEqualEitherWay(p1, p2);
p1 = new IpPrefix("2001:db8:dead:beef::/122");
- assertAreNotEqual(p1, p2);
+ assertNotEqualEitherWay(p1, p2);
// 192.0.2.4/32 != c000:0204::/32.
byte[] ipv6bytes = new byte[16];
System.arraycopy(IPV4_BYTES, 0, ipv6bytes, 0, IPV4_BYTES.length);
p1 = new IpPrefix(ipv6bytes, 32);
- assertAreEqual(p1, new IpPrefix("c000:0204::/32"));
+ assertEqualBothWays(p1, new IpPrefix("c000:0204::/32"));
p2 = new IpPrefix(IPV4_BYTES, 32);
- assertAreNotEqual(p1, p2);
+ assertNotEqualEitherWay(p1, p2);
}
@Test
public void testContainsInetAddress() {
IpPrefix p = new IpPrefix("2001:db8:f00::ace:d00d/127");
- assertTrue(p.contains(Address("2001:db8:f00::ace:d00c")));
- assertTrue(p.contains(Address("2001:db8:f00::ace:d00d")));
- assertFalse(p.contains(Address("2001:db8:f00::ace:d00e")));
- assertFalse(p.contains(Address("2001:db8:f00::bad:d00d")));
- assertFalse(p.contains(Address("2001:4868:4860::8888")));
- assertFalse(p.contains((InetAddress)null));
- assertFalse(p.contains(Address("8.8.8.8")));
+ assertTrue(p.contains(address("2001:db8:f00::ace:d00c")));
+ assertTrue(p.contains(address("2001:db8:f00::ace:d00d")));
+ assertFalse(p.contains(address("2001:db8:f00::ace:d00e")));
+ assertFalse(p.contains(address("2001:db8:f00::bad:d00d")));
+ assertFalse(p.contains(address("2001:4868:4860::8888")));
+ assertFalse(p.contains(address("8.8.8.8")));
p = new IpPrefix("192.0.2.0/23");
- assertTrue(p.contains(Address("192.0.2.43")));
- assertTrue(p.contains(Address("192.0.3.21")));
- assertFalse(p.contains(Address("192.0.0.21")));
- assertFalse(p.contains(Address("8.8.8.8")));
- assertFalse(p.contains(Address("2001:4868:4860::8888")));
+ assertTrue(p.contains(address("192.0.2.43")));
+ assertTrue(p.contains(address("192.0.3.21")));
+ assertFalse(p.contains(address("192.0.0.21")));
+ assertFalse(p.contains(address("8.8.8.8")));
+ assertFalse(p.contains(address("2001:4868:4860::8888")));
IpPrefix ipv6Default = new IpPrefix("::/0");
- assertTrue(ipv6Default.contains(Address("2001:db8::f00")));
- assertFalse(ipv6Default.contains(Address("192.0.2.1")));
+ assertTrue(ipv6Default.contains(address("2001:db8::f00")));
+ assertFalse(ipv6Default.contains(address("192.0.2.1")));
IpPrefix ipv4Default = new IpPrefix("0.0.0.0/0");
- assertTrue(ipv4Default.contains(Address("255.255.255.255")));
- assertTrue(ipv4Default.contains(Address("192.0.2.1")));
- assertFalse(ipv4Default.contains(Address("2001:db8::f00")));
+ assertTrue(ipv4Default.contains(address("255.255.255.255")));
+ assertTrue(ipv4Default.contains(address("192.0.2.1")));
+ assertFalse(ipv4Default.contains(address("2001:db8::f00")));
}
@Test
@@ -315,10 +307,10 @@
p = new IpPrefix(b, random.nextInt(129));
}
if (p.equals(oldP)) {
- assertEquals(p.hashCode(), oldP.hashCode());
+ assertEquals(p.hashCode(), oldP.hashCode());
}
if (p.hashCode() != oldP.hashCode()) {
- assertNotEquals(p, oldP);
+ assertNotEquals(p, oldP);
}
}
}
@@ -332,9 +324,9 @@
new IpPrefix("0.0.0.0/0"),
};
for (int i = 0; i < prefixes.length; i++) {
- for (int j = i + 1; j < prefixes.length; j++) {
- assertNotEquals(prefixes[i].hashCode(), prefixes[j].hashCode());
- }
+ for (int j = i + 1; j < prefixes.length; j++) {
+ assertNotEquals(prefixes[i].hashCode(), prefixes[j].hashCode());
+ }
}
}
@@ -356,25 +348,6 @@
assertEquals(InetAddress.parseNumericAddress("192.0.2.0"), p.getAddress());
}
- public IpPrefix passThroughParcel(IpPrefix p) {
- Parcel parcel = Parcel.obtain();
- IpPrefix p2 = null;
- try {
- p.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- p2 = IpPrefix.CREATOR.createFromParcel(parcel);
- } finally {
- parcel.recycle();
- }
- assertNotNull(p2);
- return p2;
- }
-
- public void assertParcelingIsLossless(IpPrefix p) {
- IpPrefix p2 = passThroughParcel(p);
- assertEquals(p, p2);
- }
-
@Test
public void testParceling() {
IpPrefix p;
@@ -386,5 +359,7 @@
p = new IpPrefix("192.0.2.0/25");
assertParcelingIsLossless(p);
assertTrue(p.isIPv4());
+
+ assertFieldCountEquals(2, IpPrefix.class);
}
}
diff --git a/tests/net/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
similarity index 87%
rename from tests/net/java/android/net/LinkAddressTest.java
rename to tests/net/common/java/android/net/LinkAddressTest.java
index c1ad946..b2e573b 100644
--- a/tests/net/java/android/net/LinkAddressTest.java
+++ b/tests/net/common/java/android/net/LinkAddressTest.java
@@ -26,31 +26,32 @@
import static android.system.OsConstants.RT_SCOPE_LINK;
import static android.system.OsConstants.RT_SCOPE_SITE;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
+
+import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
+import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
-import android.os.Parcel;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class LinkAddressTest {
@@ -82,14 +83,14 @@
assertEquals(25, address.getPrefixLength());
assertEquals(0, address.getFlags());
assertEquals(RT_SCOPE_UNIVERSE, address.getScope());
- assertTrue(address.isIPv4());
+ assertTrue(address.isIpv4());
address = new LinkAddress(V6_ADDRESS, 127);
assertEquals(V6_ADDRESS, address.getAddress());
assertEquals(127, address.getPrefixLength());
assertEquals(0, address.getFlags());
assertEquals(RT_SCOPE_UNIVERSE, address.getScope());
- assertTrue(address.isIPv6());
+ assertTrue(address.isIpv6());
// Nonsensical flags/scopes or combinations thereof are acceptable.
address = new LinkAddress(V6 + "/64", IFA_F_DEPRECATED | IFA_F_PERMANENT, RT_SCOPE_LINK);
@@ -97,14 +98,14 @@
assertEquals(64, address.getPrefixLength());
assertEquals(IFA_F_DEPRECATED | IFA_F_PERMANENT, address.getFlags());
assertEquals(RT_SCOPE_LINK, address.getScope());
- assertTrue(address.isIPv6());
+ assertTrue(address.isIpv6());
address = new LinkAddress(V4 + "/23", 123, 456);
assertEquals(V4_ADDRESS, address.getAddress());
assertEquals(23, address.getPrefixLength());
assertEquals(123, address.getFlags());
assertEquals(456, address.getScope());
- assertTrue(address.isIPv4());
+ assertTrue(address.isIpv4());
// InterfaceAddress doesn't have a constructor. Fetch some from an interface.
List<InterfaceAddress> addrs = NetworkInterface.getByName("lo").getInterfaceAddresses();
@@ -218,67 +219,56 @@
l1.isSameAddressAs(l2));
}
- private void assertLinkAddressesEqual(LinkAddress l1, LinkAddress l2) {
- assertTrue(l1 + " unexpectedly not equal to " + l2, l1.equals(l2));
- assertTrue(l2 + " unexpectedly not equal to " + l1, l2.equals(l1));
- assertEquals(l1.hashCode(), l2.hashCode());
- }
-
- private void assertLinkAddressesNotEqual(LinkAddress l1, LinkAddress l2) {
- assertFalse(l1 + " unexpectedly equal to " + l2, l1.equals(l2));
- assertFalse(l2 + " unexpectedly equal to " + l1, l2.equals(l1));
- }
-
@Test
public void testEqualsAndSameAddressAs() {
LinkAddress l1, l2, l3;
l1 = new LinkAddress("2001:db8::1/64");
l2 = new LinkAddress("2001:db8::1/64");
- assertLinkAddressesEqual(l1, l2);
+ assertEqualBothWays(l1, l2);
assertIsSameAddressAs(l1, l2);
l2 = new LinkAddress("2001:db8::1/65");
- assertLinkAddressesNotEqual(l1, l2);
+ assertNotEqualEitherWay(l1, l2);
assertIsNotSameAddressAs(l1, l2);
l2 = new LinkAddress("2001:db8::2/64");
- assertLinkAddressesNotEqual(l1, l2);
+ assertNotEqualEitherWay(l1, l2);
assertIsNotSameAddressAs(l1, l2);
l1 = new LinkAddress("192.0.2.1/24");
l2 = new LinkAddress("192.0.2.1/24");
- assertLinkAddressesEqual(l1, l2);
+ assertEqualBothWays(l1, l2);
assertIsSameAddressAs(l1, l2);
l2 = new LinkAddress("192.0.2.1/23");
- assertLinkAddressesNotEqual(l1, l2);
+ assertNotEqualEitherWay(l1, l2);
assertIsNotSameAddressAs(l1, l2);
l2 = new LinkAddress("192.0.2.2/24");
- assertLinkAddressesNotEqual(l1, l2);
+ assertNotEqualEitherWay(l1, l2);
assertIsNotSameAddressAs(l1, l2);
// Check equals() and isSameAddressAs() on identical addresses with different flags.
l1 = new LinkAddress(V6_ADDRESS, 64);
l2 = new LinkAddress(V6_ADDRESS, 64, 0, RT_SCOPE_UNIVERSE);
- assertLinkAddressesEqual(l1, l2);
+ assertEqualBothWays(l1, l2);
assertIsSameAddressAs(l1, l2);
l2 = new LinkAddress(V6_ADDRESS, 64, IFA_F_DEPRECATED, RT_SCOPE_UNIVERSE);
- assertLinkAddressesNotEqual(l1, l2);
+ assertNotEqualEitherWay(l1, l2);
assertIsSameAddressAs(l1, l2);
// Check equals() and isSameAddressAs() on identical addresses with different scope.
l1 = new LinkAddress(V4_ADDRESS, 24);
l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_UNIVERSE);
- assertLinkAddressesEqual(l1, l2);
+ assertEqualBothWays(l1, l2);
assertIsSameAddressAs(l1, l2);
l2 = new LinkAddress(V4_ADDRESS, 24, 0, RT_SCOPE_HOST);
- assertLinkAddressesNotEqual(l1, l2);
+ assertNotEqualEitherWay(l1, l2);
assertIsSameAddressAs(l1, l2);
// Addresses with the same start or end bytes aren't equal between families.
@@ -292,10 +282,10 @@
assertTrue(Arrays.equals(ipv4Bytes, l2FirstIPv6Bytes));
assertTrue(Arrays.equals(ipv4Bytes, l3LastIPv6Bytes));
- assertLinkAddressesNotEqual(l1, l2);
+ assertNotEqualEitherWay(l1, l2);
assertIsNotSameAddressAs(l1, l2);
- assertLinkAddressesNotEqual(l1, l3);
+ assertNotEqualEitherWay(l1, l3);
assertIsNotSameAddressAs(l1, l3);
// Because we use InetAddress, an IPv4 address is equal to its IPv4-mapped address.
@@ -303,7 +293,7 @@
String addressString = V4 + "/24";
l1 = new LinkAddress(addressString);
l2 = new LinkAddress("::ffff:" + addressString);
- assertLinkAddressesEqual(l1, l2);
+ assertEqualBothWays(l1, l2);
assertIsSameAddressAs(l1, l2);
}
@@ -320,25 +310,6 @@
assertNotEquals(l1.hashCode(), l2.hashCode());
}
- private LinkAddress passThroughParcel(LinkAddress l) {
- Parcel p = Parcel.obtain();
- LinkAddress l2 = null;
- try {
- l.writeToParcel(p, 0);
- p.setDataPosition(0);
- l2 = LinkAddress.CREATOR.createFromParcel(p);
- } finally {
- p.recycle();
- }
- assertNotNull(l2);
- return l2;
- }
-
- private void assertParcelingIsLossless(LinkAddress l) {
- LinkAddress l2 = passThroughParcel(l);
- assertEquals(l, l2);
- }
-
@Test
public void testParceling() {
LinkAddress l;
@@ -347,7 +318,7 @@
assertParcelingIsLossless(l);
l = new LinkAddress(V4 + "/28", IFA_F_PERMANENT, RT_SCOPE_LINK);
- assertParcelingIsLossless(l);
+ assertParcelSane(l, 4);
}
private void assertGlobalPreferred(LinkAddress l, String msg) {
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
similarity index 68%
rename from tests/net/java/android/net/LinkPropertiesTest.java
rename to tests/net/common/java/android/net/LinkPropertiesTest.java
index 9695e9a..b0464d9 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -16,23 +16,26 @@
package android.net;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.LinkProperties.ProvisioningChange;
-import android.net.RouteInfo;
-import android.os.Parcel;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.system.OsConstants;
import android.util.ArraySet;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
@@ -41,32 +44,83 @@
import java.util.List;
import java.util.Set;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class LinkPropertiesTest {
- private static InetAddress ADDRV4 = NetworkUtils.numericToInetAddress("75.208.6.1");
- private static InetAddress ADDRV6 = NetworkUtils.numericToInetAddress(
+ private static final InetAddress ADDRV4 = InetAddresses.parseNumericAddress("75.208.6.1");
+ private static final InetAddress ADDRV6 = InetAddresses.parseNumericAddress(
"2001:0db8:85a3:0000:0000:8a2e:0370:7334");
- private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
- private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
- private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
- private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
- private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
- private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
- private static InetAddress GATEWAY62 = NetworkUtils.numericToInetAddress("fe80::6:2222");
- private static String NAME = "qmi0";
- private static int MTU = 1500;
-
- private static LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
- private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
- private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
+ private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("75.208.7.1");
+ private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("69.78.7.1");
+ private static final InetAddress DNS6 = InetAddresses.parseNumericAddress(
+ "2001:4860:4860::8888");
+ private static final InetAddress PRIVDNS1 = InetAddresses.parseNumericAddress("1.1.1.1");
+ private static final InetAddress PRIVDNS2 = InetAddresses.parseNumericAddress("1.0.0.1");
+ private static final InetAddress PRIVDNS6 = InetAddresses.parseNumericAddress(
+ "2606:4700:4700::1111");
+ private static final InetAddress PCSCFV4 = InetAddresses.parseNumericAddress("10.77.25.37");
+ private static final InetAddress PCSCFV6 = InetAddresses.parseNumericAddress(
+ "2001:0db8:85a3:0000:0000:8a2e:0370:1");
+ private static final InetAddress GATEWAY1 = InetAddresses.parseNumericAddress("75.208.8.1");
+ private static final InetAddress GATEWAY2 = InetAddresses.parseNumericAddress("69.78.8.1");
+ private static final InetAddress GATEWAY61 = InetAddresses.parseNumericAddress(
+ "fe80::6:0000:613");
+ private static final InetAddress GATEWAY62 = InetAddresses.parseNumericAddress("fe80::6:2222");
+ private static final String NAME = "qmi0";
+ private static final String DOMAINS = "google.com";
+ private static final String PRIV_DNS_SERVER_NAME = "private.dns.com";
+ private static final String TCP_BUFFER_SIZES = "524288,1048576,2097152,262144,524288,1048576";
+ private static final int MTU = 1500;
+ private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
+ private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
+ private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
// TODO: replace all calls to NetworkUtils.numericToInetAddress with calls to this method.
private InetAddress Address(String addrString) {
- return NetworkUtils.numericToInetAddress(addrString);
+ return InetAddresses.parseNumericAddress(addrString);
+ }
+
+ private void checkEmpty(final LinkProperties lp) {
+ assertEquals(0, lp.getAllInterfaceNames().size());
+ assertEquals(0, lp.getAllAddresses().size());
+ assertEquals(0, lp.getDnsServers().size());
+ assertEquals(0, lp.getValidatedPrivateDnsServers().size());
+ assertEquals(0, lp.getPcscfServers().size());
+ assertEquals(0, lp.getAllRoutes().size());
+ assertEquals(0, lp.getAllLinkAddresses().size());
+ assertEquals(0, lp.getStackedLinks().size());
+ assertEquals(0, lp.getMtu());
+ assertNull(lp.getPrivateDnsServerName());
+ assertNull(lp.getDomains());
+ assertNull(lp.getHttpProxy());
+ assertNull(lp.getTcpBufferSizes());
+ assertNull(lp.getNat64Prefix());
+ assertFalse(lp.isProvisioned());
+ assertFalse(lp.isIpv4Provisioned());
+ assertFalse(lp.isIpv6Provisioned());
+ assertFalse(lp.isPrivateDnsActive());
+ }
+
+ private LinkProperties makeTestObject() {
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(NAME);
+ lp.addLinkAddress(LINKADDRV4);
+ lp.addLinkAddress(LINKADDRV6);
+ lp.addDnsServer(DNS1);
+ lp.addDnsServer(DNS2);
+ lp.addValidatedPrivateDnsServer(PRIVDNS1);
+ lp.addValidatedPrivateDnsServer(PRIVDNS2);
+ lp.setUsePrivateDns(true);
+ lp.setPrivateDnsServerName(PRIV_DNS_SERVER_NAME);
+ lp.addPcscfServer(PCSCFV6);
+ lp.setDomains(DOMAINS);
+ lp.addRoute(new RouteInfo(GATEWAY1));
+ lp.addRoute(new RouteInfo(GATEWAY2));
+ lp.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888));
+ lp.setMtu(MTU);
+ lp.setTcpBufferSizes(TCP_BUFFER_SIZES);
+ lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
+ return lp;
}
public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
@@ -86,6 +140,9 @@
assertTrue(source.isIdenticalValidatedPrivateDnses(target));
assertTrue(target.isIdenticalValidatedPrivateDnses(source));
+ assertTrue(source.isIdenticalPcscfs(target));
+ assertTrue(target.isIdenticalPcscfs(source));
+
assertTrue(source.isIdenticalRoutes(target));
assertTrue(target.isIdenticalRoutes(source));
@@ -128,6 +185,8 @@
// set 2 dnses
source.addDnsServer(DNS1);
source.addDnsServer(DNS2);
+ // set 1 pcscf
+ source.addPcscfServer(PCSCFV6);
// set 2 gateways
source.addRoute(new RouteInfo(GATEWAY1));
source.addRoute(new RouteInfo(GATEWAY2));
@@ -141,6 +200,7 @@
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -154,6 +214,7 @@
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -162,11 +223,11 @@
target.clear();
target.setInterfaceName(NAME);
// change link addresses
- target.addLinkAddress(new LinkAddress(
- NetworkUtils.numericToInetAddress("75.208.6.2"), 32));
+ target.addLinkAddress(new LinkAddress(Address("75.208.6.2"), 32));
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -177,8 +238,22 @@
target.addLinkAddress(LINKADDRV4);
target.addLinkAddress(LINKADDRV6);
// change dnses
- target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+ target.addDnsServer(Address("75.208.7.2"));
target.addDnsServer(DNS2);
+ target.addPcscfServer(PCSCFV6);
+ target.addRoute(new RouteInfo(GATEWAY1));
+ target.addRoute(new RouteInfo(GATEWAY2));
+ target.setMtu(MTU);
+ assertFalse(source.equals(target));
+
+ target.clear();
+ target.setInterfaceName(NAME);
+ target.addLinkAddress(LINKADDRV4);
+ target.addLinkAddress(LINKADDRV6);
+ target.addDnsServer(Address("75.208.7.2"));
+ target.addDnsServer(DNS2);
+ // change pcscf
+ target.addPcscfServer(Address("2001::1"));
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -191,7 +266,7 @@
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
// change gateway
- target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress("75.208.8.2")));
+ target.addRoute(new RouteInfo(Address("75.208.8.2")));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
assertFalse(source.equals(target));
@@ -261,10 +336,15 @@
}
}
+ private void assertAllRoutesNotHaveInterface(String iface, LinkProperties lp) {
+ for (RouteInfo r : lp.getRoutes()) {
+ assertNotEquals(iface, r.getInterface());
+ }
+ }
+
@Test
public void testRouteInterfaces() {
- LinkAddress prefix = new LinkAddress(
- NetworkUtils.numericToInetAddress("2001:db8::"), 32);
+ LinkAddress prefix = new LinkAddress(Address("2001:db8::"), 32);
InetAddress address = ADDRV6;
// Add a route with no interface to a LinkProperties with no interface. No errors.
@@ -288,6 +368,8 @@
// Change the interface name. All the routes should change their interface name too.
lp.setInterfaceName("rmnet0");
assertAllRoutesHaveInterface("rmnet0", lp);
+ assertAllRoutesNotHaveInterface(null, lp);
+ assertAllRoutesNotHaveInterface("wlan0", lp);
// Now add a route with the wrong interface. This causes an exception too.
try {
@@ -301,6 +383,7 @@
lp.addRoute(r);
assertEquals(2, lp.getRoutes().size());
assertAllRoutesHaveInterface("wlan0", lp);
+ assertAllRoutesNotHaveInterface("rmnet0", lp);
// Routes with null interfaces are converted to wlan0.
r = RouteInfo.makeHostRoute(ADDRV6, null);
@@ -310,14 +393,23 @@
// Check comparisons work.
LinkProperties lp2 = new LinkProperties(lp);
- assertAllRoutesHaveInterface("wlan0", lp);
+ assertAllRoutesHaveInterface("wlan0", lp2);
assertEquals(0, lp.compareAllRoutes(lp2).added.size());
assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
lp2.setInterfaceName("p2p0");
assertAllRoutesHaveInterface("p2p0", lp2);
+ assertAllRoutesNotHaveInterface("wlan0", lp2);
assertEquals(3, lp.compareAllRoutes(lp2).added.size());
assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+
+ // Check remove works
+ lp.removeRoute(new RouteInfo(prefix, address, null));
+ assertEquals(3, lp.getRoutes().size());
+ lp.removeRoute(new RouteInfo(prefix, address, "wlan0"));
+ assertEquals(2, lp.getRoutes().size());
+ assertAllRoutesHaveInterface("wlan0", lp);
+ assertAllRoutesNotHaveInterface("p2p0", lp);
}
@Test
@@ -381,8 +473,8 @@
LinkProperties lp = new LinkProperties();
// No addresses.
- assertFalse(lp.hasIPv4Address());
- assertFalse(lp.hasGlobalIPv6Address());
+ assertFalse(lp.hasIpv4Address());
+ assertFalse(lp.hasGlobalIpv6Address());
// Addresses on stacked links don't count.
LinkProperties stacked = new LinkProperties();
@@ -390,53 +482,53 @@
lp.addStackedLink(stacked);
stacked.addLinkAddress(LINKADDRV4);
stacked.addLinkAddress(LINKADDRV6);
- assertTrue(stacked.hasIPv4Address());
- assertTrue(stacked.hasGlobalIPv6Address());
- assertFalse(lp.hasIPv4Address());
- assertFalse(lp.hasGlobalIPv6Address());
+ assertTrue(stacked.hasIpv4Address());
+ assertTrue(stacked.hasGlobalIpv6Address());
+ assertFalse(lp.hasIpv4Address());
+ assertFalse(lp.hasGlobalIpv6Address());
lp.removeStackedLink("stacked");
- assertFalse(lp.hasIPv4Address());
- assertFalse(lp.hasGlobalIPv6Address());
+ assertFalse(lp.hasIpv4Address());
+ assertFalse(lp.hasGlobalIpv6Address());
// Addresses on the base link.
- // Check the return values of hasIPvXAddress and ensure the add/remove methods return true
+ // Check the return values of hasIpvXAddress and ensure the add/remove methods return true
// iff something changes.
assertEquals(0, lp.getLinkAddresses().size());
assertTrue(lp.addLinkAddress(LINKADDRV6));
assertEquals(1, lp.getLinkAddresses().size());
- assertFalse(lp.hasIPv4Address());
- assertTrue(lp.hasGlobalIPv6Address());
+ assertFalse(lp.hasIpv4Address());
+ assertTrue(lp.hasGlobalIpv6Address());
assertTrue(lp.removeLinkAddress(LINKADDRV6));
assertEquals(0, lp.getLinkAddresses().size());
assertTrue(lp.addLinkAddress(LINKADDRV6LINKLOCAL));
assertEquals(1, lp.getLinkAddresses().size());
- assertFalse(lp.hasGlobalIPv6Address());
+ assertFalse(lp.hasGlobalIpv6Address());
assertTrue(lp.addLinkAddress(LINKADDRV4));
assertEquals(2, lp.getLinkAddresses().size());
- assertTrue(lp.hasIPv4Address());
- assertFalse(lp.hasGlobalIPv6Address());
+ assertTrue(lp.hasIpv4Address());
+ assertFalse(lp.hasGlobalIpv6Address());
assertTrue(lp.addLinkAddress(LINKADDRV6));
assertEquals(3, lp.getLinkAddresses().size());
- assertTrue(lp.hasIPv4Address());
- assertTrue(lp.hasGlobalIPv6Address());
+ assertTrue(lp.hasIpv4Address());
+ assertTrue(lp.hasGlobalIpv6Address());
assertTrue(lp.removeLinkAddress(LINKADDRV6LINKLOCAL));
assertEquals(2, lp.getLinkAddresses().size());
- assertTrue(lp.hasIPv4Address());
- assertTrue(lp.hasGlobalIPv6Address());
+ assertTrue(lp.hasIpv4Address());
+ assertTrue(lp.hasGlobalIpv6Address());
// Adding an address twice has no effect.
// Removing an address that's not present has no effect.
assertFalse(lp.addLinkAddress(LINKADDRV4));
assertEquals(2, lp.getLinkAddresses().size());
- assertTrue(lp.hasIPv4Address());
+ assertTrue(lp.hasIpv4Address());
assertTrue(lp.removeLinkAddress(LINKADDRV4));
assertEquals(1, lp.getLinkAddresses().size());
- assertFalse(lp.hasIPv4Address());
+ assertFalse(lp.hasIpv4Address());
assertFalse(lp.removeLinkAddress(LINKADDRV4));
assertEquals(1, lp.getLinkAddresses().size());
@@ -464,18 +556,60 @@
}
@Test
- public void testSetLinkAddresses() {
+ public void testLinkAddresses() {
+ final LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(LINKADDRV4);
+ lp.addLinkAddress(LINKADDRV6);
+
+ final LinkProperties lp2 = new LinkProperties();
+ lp2.addLinkAddress(LINKADDRV6);
+
+ final LinkProperties lp3 = new LinkProperties();
+ final List<LinkAddress> linkAddresses = Arrays.asList(LINKADDRV4);
+ lp3.setLinkAddresses(linkAddresses);
+
+ assertFalse(lp.equals(lp2));
+ assertFalse(lp2.equals(lp3));
+
+ lp.removeLinkAddress(LINKADDRV4);
+ assertTrue(lp.equals(lp2));
+
+ lp2.setLinkAddresses(lp3.getLinkAddresses());
+ assertTrue(lp2.equals(lp3));
+ }
+
+ @Test
+ public void testNat64Prefix() throws Exception {
LinkProperties lp = new LinkProperties();
lp.addLinkAddress(LINKADDRV4);
lp.addLinkAddress(LINKADDRV6);
- LinkProperties lp2 = new LinkProperties();
- lp2.addLinkAddress(LINKADDRV6);
+ assertNull(lp.getNat64Prefix());
- assertFalse(lp.equals(lp2));
+ IpPrefix p = new IpPrefix("64:ff9b::/96");
+ lp.setNat64Prefix(p);
+ assertEquals(p, lp.getNat64Prefix());
- lp2.setLinkAddresses(lp.getLinkAddresses());
- assertTrue(lp.equals(lp));
+ p = new IpPrefix("2001:db8:a:b:1:2:3::/96");
+ lp.setNat64Prefix(p);
+ assertEquals(p, lp.getNat64Prefix());
+
+ p = new IpPrefix("2001:db8:a:b:1:2::/80");
+ try {
+ lp.setNat64Prefix(p);
+ } catch (IllegalArgumentException expected) {
+ }
+
+ p = new IpPrefix("64:ff9b::/64");
+ try {
+ lp.setNat64Prefix(p);
+ } catch (IllegalArgumentException expected) {
+ }
+
+ assertEquals(new IpPrefix("2001:db8:a:b:1:2:3::/96"), lp.getNat64Prefix());
+
+ lp.setNat64Prefix(null);
+ assertNull(lp.getNat64Prefix());
}
@Test
@@ -488,8 +622,8 @@
assertFalse("v4only:addr+dns", lp4.isProvisioned());
lp4.addRoute(new RouteInfo(GATEWAY1));
assertTrue("v4only:addr+dns+route", lp4.isProvisioned());
- assertTrue("v4only:addr+dns+route", lp4.isIPv4Provisioned());
- assertFalse("v4only:addr+dns+route", lp4.isIPv6Provisioned());
+ assertTrue("v4only:addr+dns+route", lp4.isIpv4Provisioned());
+ assertFalse("v4only:addr+dns+route", lp4.isIpv6Provisioned());
LinkProperties lp6 = new LinkProperties();
assertFalse("v6only:empty", lp6.isProvisioned());
@@ -500,11 +634,11 @@
lp6.addRoute(new RouteInfo(GATEWAY61));
assertFalse("v6only:fe80+dns+route", lp6.isProvisioned());
lp6.addLinkAddress(LINKADDRV6);
- assertTrue("v6only:fe80+global+dns+route", lp6.isIPv6Provisioned());
+ assertTrue("v6only:fe80+global+dns+route", lp6.isIpv6Provisioned());
assertTrue("v6only:fe80+global+dns+route", lp6.isProvisioned());
lp6.removeLinkAddress(LINKADDRV6LINKLOCAL);
- assertFalse("v6only:global+dns+route", lp6.isIPv4Provisioned());
- assertTrue("v6only:global+dns+route", lp6.isIPv6Provisioned());
+ assertFalse("v6only:global+dns+route", lp6.isIpv4Provisioned());
+ assertTrue("v6only:global+dns+route", lp6.isIpv6Provisioned());
assertTrue("v6only:global+dns+route", lp6.isProvisioned());
LinkProperties lp46 = new LinkProperties();
@@ -514,12 +648,12 @@
lp46.addDnsServer(DNS6);
assertFalse("dualstack:missing-routes", lp46.isProvisioned());
lp46.addRoute(new RouteInfo(GATEWAY1));
- assertTrue("dualstack:v4-provisioned", lp46.isIPv4Provisioned());
- assertFalse("dualstack:v4-provisioned", lp46.isIPv6Provisioned());
+ assertTrue("dualstack:v4-provisioned", lp46.isIpv4Provisioned());
+ assertFalse("dualstack:v4-provisioned", lp46.isIpv6Provisioned());
assertTrue("dualstack:v4-provisioned", lp46.isProvisioned());
lp46.addRoute(new RouteInfo(GATEWAY61));
- assertTrue("dualstack:both-provisioned", lp46.isIPv4Provisioned());
- assertTrue("dualstack:both-provisioned", lp46.isIPv6Provisioned());
+ assertTrue("dualstack:both-provisioned", lp46.isIpv4Provisioned());
+ assertTrue("dualstack:both-provisioned", lp46.isIpv6Provisioned());
assertTrue("dualstack:both-provisioned", lp46.isProvisioned());
// A link with an IPv6 address and default route, but IPv4 DNS server.
@@ -527,8 +661,8 @@
mixed.addLinkAddress(LINKADDRV6);
mixed.addDnsServer(DNS1);
mixed.addRoute(new RouteInfo(GATEWAY61));
- assertFalse("mixed:addr6+route6+dns4", mixed.isIPv4Provisioned());
- assertFalse("mixed:addr6+route6+dns4", mixed.isIPv6Provisioned());
+ assertFalse("mixed:addr6+route6+dns4", mixed.isIpv4Provisioned());
+ assertFalse("mixed:addr6+route6+dns4", mixed.isIpv6Provisioned());
assertFalse("mixed:addr6+route6+dns4", mixed.isProvisioned());
}
@@ -559,16 +693,16 @@
v6lp.addLinkAddress(LINKADDRV6);
v6lp.addRoute(new RouteInfo(GATEWAY61));
v6lp.addDnsServer(DNS6);
- assertFalse(v6lp.isIPv4Provisioned());
- assertTrue(v6lp.isIPv6Provisioned());
+ assertFalse(v6lp.isIpv4Provisioned());
+ assertTrue(v6lp.isIpv6Provisioned());
assertTrue(v6lp.isProvisioned());
LinkProperties v46lp = new LinkProperties(v6lp);
v46lp.addLinkAddress(LINKADDRV4);
v46lp.addRoute(new RouteInfo(GATEWAY1));
v46lp.addDnsServer(DNS1);
- assertTrue(v46lp.isIPv4Provisioned());
- assertTrue(v46lp.isIPv6Provisioned());
+ assertTrue(v46lp.isIpv4Provisioned());
+ assertTrue(v46lp.isIpv6Provisioned());
assertTrue(v46lp.isProvisioned());
assertEquals(ProvisioningChange.STILL_PROVISIONED,
@@ -617,9 +751,9 @@
assertTrue(v4lp.isReachable(DNS2));
final LinkProperties v6lp = new LinkProperties();
- final InetAddress kLinkLocalDns = NetworkUtils.numericToInetAddress("fe80::6:1");
- final InetAddress kLinkLocalDnsWithScope = NetworkUtils.numericToInetAddress("fe80::6:2%43");
- final InetAddress kOnLinkDns = NetworkUtils.numericToInetAddress("2001:db8:85a3::53");
+ final InetAddress kLinkLocalDns = Address("fe80::6:1");
+ final InetAddress kLinkLocalDnsWithScope = Address("fe80::6:2%43");
+ final InetAddress kOnLinkDns = Address("2001:db8:85a3::53");
assertFalse(v6lp.isReachable(kLinkLocalDns));
assertFalse(v6lp.isReachable(kLinkLocalDnsWithScope));
assertFalse(v6lp.isReachable(kOnLinkDns));
@@ -628,8 +762,7 @@
// Add a link-local route, making the link-local DNS servers reachable. Because
// we assume the presence of an IPv6 link-local address, link-local DNS servers
// are considered reachable, but only those with a non-zero scope identifier.
- assertTrue(v6lp.addRoute(new RouteInfo(
- new IpPrefix(NetworkUtils.numericToInetAddress("fe80::"), 64))));
+ assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(Address("fe80::"), 64))));
assertFalse(v6lp.isReachable(kLinkLocalDns));
assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
assertFalse(v6lp.isReachable(kOnLinkDns));
@@ -645,8 +778,7 @@
// Add a global route on link, but no global address yet. DNS servers reachable
// via a route that doesn't require a gateway: give them the benefit of the
// doubt and hope the link-local source address suffices for communication.
- assertTrue(v6lp.addRoute(new RouteInfo(
- new IpPrefix(NetworkUtils.numericToInetAddress("2001:db8:85a3::"), 64))));
+ assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(Address("2001:db8:85a3::"), 64))));
assertFalse(v6lp.isReachable(kLinkLocalDns));
assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
assertTrue(v6lp.isReachable(kOnLinkDns));
@@ -708,8 +840,8 @@
LinkProperties rmnet1 = new LinkProperties();
rmnet1.setInterfaceName("rmnet1");
rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8"));
- RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null,
- NetworkUtils.numericToInetAddress("10.0.0.1"), rmnet1.getInterfaceName());
+ RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null, Address("10.0.0.1"),
+ rmnet1.getInterfaceName());
RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
rmnet1.getInterfaceName());
rmnet1.addRoute(defaultRoute1);
@@ -727,8 +859,8 @@
rmnet2.setInterfaceName("rmnet2");
rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64"));
rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64"));
- RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null,
- NetworkUtils.numericToInetAddress("2001:db8::1"), rmnet2.getInterfaceName());
+ RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null, Address("2001:db8::1"),
+ rmnet2.getInterfaceName());
RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null,
rmnet2.getInterfaceName());
RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null,
@@ -790,7 +922,7 @@
}
@Test
- public void testLinkPropertiesParcelable() {
+ public void testLinkPropertiesParcelable() throws Exception {
LinkProperties source = new LinkProperties();
source.setInterfaceName(NAME);
// set 2 link addresses
@@ -808,15 +940,121 @@
source.setMtu(MTU);
- Parcel p = Parcel.obtain();
- source.writeToParcel(p, /* flags */ 0);
- p.setDataPosition(0);
- final byte[] marshalled = p.marshall();
- p = Parcel.obtain();
- p.unmarshall(marshalled, 0, marshalled.length);
- p.setDataPosition(0);
- LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
+ source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
- assertEquals(source, dest);
+ assertParcelingIsLossless(source);
+ }
+
+ @Test
+ public void testParcelUninitialized() throws Exception {
+ LinkProperties empty = new LinkProperties();
+ assertParcelingIsLossless(empty);
+ }
+
+ @Test
+ public void testConstructor() {
+ LinkProperties lp = new LinkProperties();
+ checkEmpty(lp);
+ assertLinkPropertiesEqual(lp, new LinkProperties(lp));
+ assertLinkPropertiesEqual(lp, new LinkProperties());
+
+ lp = makeTestObject();
+ assertLinkPropertiesEqual(lp, new LinkProperties(lp));
+ }
+
+ @Test
+ public void testDnsServers() {
+ final LinkProperties lp = new LinkProperties();
+ final List<InetAddress> dnsServers = Arrays.asList(DNS1, DNS2);
+ lp.setDnsServers(dnsServers);
+ assertEquals(2, lp.getDnsServers().size());
+ assertEquals(DNS1, lp.getDnsServers().get(0));
+ assertEquals(DNS2, lp.getDnsServers().get(1));
+
+ lp.removeDnsServer(DNS1);
+ assertEquals(1, lp.getDnsServers().size());
+ assertEquals(DNS2, lp.getDnsServers().get(0));
+
+ lp.addDnsServer(DNS6);
+ assertEquals(2, lp.getDnsServers().size());
+ assertEquals(DNS2, lp.getDnsServers().get(0));
+ assertEquals(DNS6, lp.getDnsServers().get(1));
+ }
+
+ @Test
+ public void testValidatedPrivateDnsServers() {
+ final LinkProperties lp = new LinkProperties();
+ final List<InetAddress> privDnsServers = Arrays.asList(PRIVDNS1, PRIVDNS2);
+ lp.setValidatedPrivateDnsServers(privDnsServers);
+ assertEquals(2, lp.getValidatedPrivateDnsServers().size());
+ assertEquals(PRIVDNS1, lp.getValidatedPrivateDnsServers().get(0));
+ assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(1));
+
+ lp.removeValidatedPrivateDnsServer(PRIVDNS1);
+ assertEquals(1, lp.getValidatedPrivateDnsServers().size());
+ assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(0));
+
+ lp.addValidatedPrivateDnsServer(PRIVDNS6);
+ assertEquals(2, lp.getValidatedPrivateDnsServers().size());
+ assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(0));
+ assertEquals(PRIVDNS6, lp.getValidatedPrivateDnsServers().get(1));
+ }
+
+ @Test
+ public void testPcscfServers() {
+ final LinkProperties lp = new LinkProperties();
+ final List<InetAddress> pcscfServers = Arrays.asList(PCSCFV4);
+ lp.setPcscfServers(pcscfServers);
+ assertEquals(1, lp.getPcscfServers().size());
+ assertEquals(PCSCFV4, lp.getPcscfServers().get(0));
+
+ lp.removePcscfServer(PCSCFV4);
+ assertEquals(0, lp.getPcscfServers().size());
+
+ lp.addPcscfServer(PCSCFV6);
+ assertEquals(1, lp.getPcscfServers().size());
+ assertEquals(PCSCFV6, lp.getPcscfServers().get(0));
+ }
+
+ @Test
+ public void testTcpBufferSizes() {
+ final LinkProperties lp = makeTestObject();
+ assertEquals(TCP_BUFFER_SIZES, lp.getTcpBufferSizes());
+
+ lp.setTcpBufferSizes(null);
+ assertNull(lp.getTcpBufferSizes());
+ }
+
+ @Test
+ public void testHasIpv6DefaultRoute() {
+ final LinkProperties lp = makeTestObject();
+ assertFalse(lp.hasIPv6DefaultRoute());
+
+ lp.addRoute(new RouteInfo(GATEWAY61));
+ assertTrue(lp.hasIPv6DefaultRoute());
+ }
+
+ @Test
+ public void testHttpProxy() {
+ final LinkProperties lp = makeTestObject();
+ assertTrue(lp.getHttpProxy().equals(ProxyInfo.buildDirectProxy("test", 8888)));
+ }
+
+ @Test
+ public void testPrivateDnsServerName() {
+ final LinkProperties lp = makeTestObject();
+ assertEquals(PRIV_DNS_SERVER_NAME, lp.getPrivateDnsServerName());
+
+ lp.setPrivateDnsServerName(null);
+ assertNull(lp.getPrivateDnsServerName());
+ }
+
+ @Test
+ public void testUsePrivateDns() {
+ final LinkProperties lp = makeTestObject();
+ assertTrue(lp.isPrivateDnsActive());
+
+ lp.clear();
+ assertFalse(lp.isPrivateDnsActive());
}
}
diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
similarity index 81%
rename from tests/net/java/android/net/NetworkCapabilitiesTest.java
rename to tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index a112fa6..2ca0d1a 100644
--- a/tests/net/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -20,20 +20,27 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -41,11 +48,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.os.Parcel;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -263,9 +269,9 @@
.setUids(uids)
.addCapability(NET_CAPABILITY_EIMS)
.addCapability(NET_CAPABILITY_NOT_METERED);
- assertEqualsThroughMarshalling(netCap);
+ assertParcelingIsLossless(netCap);
netCap.setSSID(TEST_SSID);
- assertEqualsThroughMarshalling(netCap);
+ assertParcelSane(netCap, 11);
}
@Test
@@ -303,7 +309,7 @@
assertTrue("Request: " + request + ", Network:" + network,
request.satisfiedByNetworkCapabilities(network));
- // Adding capabilities that doesn't exist in the network anyway
+ // Requesting absence of capabilities that network doesn't have. Request should satisfy.
request.addUnwantedCapability(NET_CAPABILITY_WIFI_P2P);
request.addUnwantedCapability(NET_CAPABILITY_NOT_METERED);
assertTrue(request.satisfiedByNetworkCapabilities(network));
@@ -319,7 +325,6 @@
assertTrue(request.hasUnwantedCapability(NET_CAPABILITY_NOT_RESTRICTED));
assertFalse(request.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
-
// Now this request won't be satisfied because network contains NOT_RESTRICTED.
assertFalse(request.satisfiedByNetworkCapabilities(network));
network.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
@@ -336,6 +341,24 @@
}
@Test
+ public void testConnectivityManagedCapabilities() {
+ NetworkCapabilities nc = new NetworkCapabilities();
+ assertFalse(nc.hasConnectivityManagedCapability());
+ // Check every single system managed capability.
+ nc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+ assertTrue(nc.hasConnectivityManagedCapability());
+ nc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+ nc.addCapability(NET_CAPABILITY_FOREGROUND);
+ assertTrue(nc.hasConnectivityManagedCapability());
+ nc.removeCapability(NET_CAPABILITY_FOREGROUND);
+ nc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ assertTrue(nc.hasConnectivityManagedCapability());
+ nc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ nc.addCapability(NET_CAPABILITY_VALIDATED);
+ assertTrue(nc.hasConnectivityManagedCapability());
+ }
+
+ @Test
public void testEqualsNetCapabilities() {
NetworkCapabilities nc1 = new NetworkCapabilities();
NetworkCapabilities nc2 = new NetworkCapabilities();
@@ -458,16 +481,67 @@
assertEquals(nc1, nc2);
}
- private void assertEqualsThroughMarshalling(NetworkCapabilities netCap) {
- Parcel p = Parcel.obtain();
- netCap.writeToParcel(p, /* flags */ 0);
- p.setDataPosition(0);
- byte[] marshalledData = p.marshall();
+ @Test
+ public void testSetNetworkSpecifierOnMultiTransportNc() {
+ // Sequence 1: Transport + Transport + NetworkSpecifier
+ NetworkCapabilities nc1 = new NetworkCapabilities();
+ nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI);
+ try {
+ nc1.setNetworkSpecifier(new StringNetworkSpecifier("specs"));
+ fail("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!");
+ } catch (IllegalStateException expected) {
+ // empty
+ }
- p = Parcel.obtain();
- p.unmarshall(marshalledData, 0, marshalledData.length);
- p.setDataPosition(0);
- assertEquals(NetworkCapabilities.CREATOR.createFromParcel(p), netCap);
+ // Sequence 2: Transport + NetworkSpecifier + Transport
+ NetworkCapabilities nc2 = new NetworkCapabilities();
+ nc2.addTransportType(TRANSPORT_CELLULAR).setNetworkSpecifier(
+ new StringNetworkSpecifier("specs"));
+ try {
+ nc2.addTransportType(TRANSPORT_WIFI);
+ fail("Cannot set a second TransportType of a network which has a NetworkSpecifier!");
+ } catch (IllegalStateException expected) {
+ // empty
+ }
+ }
+
+ @Test
+ public void testSetTransportInfoOnMultiTransportNc() {
+ // Sequence 1: Transport + Transport + TransportInfo
+ NetworkCapabilities nc1 = new NetworkCapabilities();
+ nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI)
+ .setTransportInfo(new TransportInfo() {});
+
+ // Sequence 2: Transport + NetworkSpecifier + Transport
+ NetworkCapabilities nc2 = new NetworkCapabilities();
+ nc2.addTransportType(TRANSPORT_CELLULAR).setTransportInfo(new TransportInfo() {})
+ .addTransportType(TRANSPORT_WIFI);
+ }
+
+ @Test
+ public void testCombineTransportInfo() {
+ NetworkCapabilities nc1 = new NetworkCapabilities();
+ nc1.setTransportInfo(new TransportInfo() {
+ // empty
+ });
+ NetworkCapabilities nc2 = new NetworkCapabilities();
+ // new TransportInfo so that object is not #equals to nc1's TransportInfo (that's where
+ // combine fails)
+ nc2.setTransportInfo(new TransportInfo() {
+ // empty
+ });
+
+ try {
+ nc1.combineCapabilities(nc2);
+ fail("Should not be able to combine NetworkCabilities which contain TransportInfos");
+ } catch (IllegalStateException expected) {
+ // empty
+ }
+
+ // verify that can combine with identical TransportInfo objects
+ NetworkCapabilities nc3 = new NetworkCapabilities();
+ nc3.setTransportInfo(nc1.getTransportInfo());
+ nc1.combineCapabilities(nc3);
}
@Test
@@ -503,4 +577,20 @@
nc2.set(nc1); // Overwrites, as opposed to combineCapabilities
assertEquals(nc1, nc2);
}
+
+ @Test
+ public void testGetTransportTypes() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ nc.addTransportType(TRANSPORT_CELLULAR);
+ nc.addTransportType(TRANSPORT_WIFI);
+ nc.addTransportType(TRANSPORT_VPN);
+ nc.addTransportType(TRANSPORT_TEST);
+
+ final int[] transportTypes = nc.getTransportTypes();
+ assertEquals(4, transportTypes.length);
+ assertEquals(TRANSPORT_CELLULAR, transportTypes[0]);
+ assertEquals(TRANSPORT_WIFI, transportTypes[1]);
+ assertEquals(TRANSPORT_VPN, transportTypes[2]);
+ assertEquals(TRANSPORT_TEST, transportTypes[3]);
+ }
}
diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java
similarity index 74%
rename from tests/net/java/android/net/NetworkTest.java
rename to tests/net/common/java/android/net/NetworkTest.java
index 94d01e9..11d44b8 100644
--- a/tests/net/java/android/net/NetworkTest.java
+++ b/tests/net/common/java/android/net/NetworkTest.java
@@ -18,28 +18,25 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.net.Network;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
-import java.io.IOException;
import java.net.DatagramSocket;
-import java.net.InetAddress;
import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.SocketException;
-import java.util.Objects;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -74,6 +71,7 @@
}
@Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testBindSocketOfConnectedDatagramSocketThrows() throws Exception {
final DatagramSocket mDgramSocket = new DatagramSocket(0, (InetAddress) Inet6Address.ANY);
mDgramSocket.connect((InetAddress) Inet6Address.LOOPBACK, 53);
@@ -121,29 +119,29 @@
Network three = new Network(3);
// None of the hashcodes are zero.
- assertNotEqual(0, one.hashCode());
- assertNotEqual(0, two.hashCode());
- assertNotEqual(0, three.hashCode());
+ assertNotEquals(0, one.hashCode());
+ assertNotEquals(0, two.hashCode());
+ assertNotEquals(0, three.hashCode());
// All the hashcodes are distinct.
- assertNotEqual(one.hashCode(), two.hashCode());
- assertNotEqual(one.hashCode(), three.hashCode());
- assertNotEqual(two.hashCode(), three.hashCode());
+ assertNotEquals(one.hashCode(), two.hashCode());
+ assertNotEquals(one.hashCode(), three.hashCode());
+ assertNotEquals(two.hashCode(), three.hashCode());
// None of the handles are zero.
- assertNotEqual(0, one.getNetworkHandle());
- assertNotEqual(0, two.getNetworkHandle());
- assertNotEqual(0, three.getNetworkHandle());
+ assertNotEquals(0, one.getNetworkHandle());
+ assertNotEquals(0, two.getNetworkHandle());
+ assertNotEquals(0, three.getNetworkHandle());
// All the handles are distinct.
- assertNotEqual(one.getNetworkHandle(), two.getNetworkHandle());
- assertNotEqual(one.getNetworkHandle(), three.getNetworkHandle());
- assertNotEqual(two.getNetworkHandle(), three.getNetworkHandle());
+ assertNotEquals(one.getNetworkHandle(), two.getNetworkHandle());
+ assertNotEquals(one.getNetworkHandle(), three.getNetworkHandle());
+ assertNotEquals(two.getNetworkHandle(), three.getNetworkHandle());
// The handles are not equal to the hashcodes.
- assertNotEqual(one.hashCode(), one.getNetworkHandle());
- assertNotEqual(two.hashCode(), two.getNetworkHandle());
- assertNotEqual(three.hashCode(), three.getNetworkHandle());
+ assertNotEquals(one.hashCode(), one.getNetworkHandle());
+ assertNotEquals(two.hashCode(), two.getNetworkHandle());
+ assertNotEquals(three.hashCode(), three.getNetworkHandle());
// Adjust as necessary to test an implementation's specific constants.
// When running with runtest, "adb logcat -s TestRunner" can be useful.
@@ -152,7 +150,11 @@
assertEquals(16290598925L, three.getNetworkHandle());
}
- private static <T> void assertNotEqual(T t1, T t2) {
- assertFalse(Objects.equals(t1, t2));
+ @Test
+ public void testGetPrivateDnsBypassingCopy() {
+ final Network copy = mNetwork.getPrivateDnsBypassingCopy();
+ assertEquals(mNetwork.netId, copy.netId);
+ assertNotEquals(copy.netId, copy.getNetIdForResolv());
+ assertNotEquals(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv());
}
}
diff --git a/tests/net/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java
similarity index 63%
rename from tests/net/java/android/net/RouteInfoTest.java
rename to tests/net/common/java/android/net/RouteInfoTest.java
index 831fefd..5ce8436 100644
--- a/tests/net/java/android/net/RouteInfoTest.java
+++ b/tests/net/common/java/android/net/RouteInfoTest.java
@@ -16,15 +16,20 @@
package android.net;
-import java.lang.reflect.Method;
-import java.net.InetAddress;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
-import android.net.IpPrefix;
-import android.net.RouteInfo;
-import android.os.Parcel;
+import static com.android.testutils.MiscAssertsKt.assertEqualBothWays;
+import static com.android.testutils.MiscAssertsKt.assertNotEqualEitherWay;
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
+import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
public class RouteInfoTest extends TestCase {
@@ -108,111 +113,119 @@
assertFalse(ipv4Default.matches(Address("2001:db8::f00")));
}
- private void assertAreEqual(Object o1, Object o2) {
- assertTrue(o1.equals(o2));
- assertTrue(o2.equals(o1));
- }
-
- private void assertAreNotEqual(Object o1, Object o2) {
- assertFalse(o1.equals(o2));
- assertFalse(o2.equals(o1));
- }
-
public void testEquals() {
// IPv4
RouteInfo r1 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0");
RouteInfo r2 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0");
- assertAreEqual(r1, r2);
+ assertEqualBothWays(r1, r2);
RouteInfo r3 = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("2001:db8::1"), "wlan0");
RouteInfo r4 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::2"), "wlan0");
RouteInfo r5 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "rmnet0");
- assertAreNotEqual(r1, r3);
- assertAreNotEqual(r1, r4);
- assertAreNotEqual(r1, r5);
+ assertNotEqualEitherWay(r1, r3);
+ assertNotEqualEitherWay(r1, r4);
+ assertNotEqualEitherWay(r1, r5);
// IPv6
r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0");
r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0");
- assertAreEqual(r1, r2);
+ assertEqualBothWays(r1, r2);
r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0");
r4 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.2"), "wlan0");
r5 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "rmnet0");
- assertAreNotEqual(r1, r3);
- assertAreNotEqual(r1, r4);
- assertAreNotEqual(r1, r5);
+ assertNotEqualEitherWay(r1, r3);
+ assertNotEqualEitherWay(r1, r4);
+ assertNotEqualEitherWay(r1, r5);
// Interfaces (but not destinations or gateways) can be null.
r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null);
r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null);
r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0");
- assertAreEqual(r1, r2);
- assertAreNotEqual(r1, r3);
+ assertEqualBothWays(r1, r2);
+ assertNotEqualEitherWay(r1, r3);
}
public void testHostAndDefaultRoutes() {
- RouteInfo r;
+ RouteInfo r;
- r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
- assertFalse(r.isHostRoute());
- assertTrue(r.isDefaultRoute());
- assertTrue(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
+ assertFalse(r.isHostRoute());
+ assertTrue(r.isDefaultRoute());
+ assertTrue(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
- assertFalse(r.isHostRoute());
- assertTrue(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertTrue(r.isIPv6Default());
+ r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
+ assertFalse(r.isHostRoute());
+ assertTrue(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertTrue(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
- assertFalse(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
- assertFalse(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE);
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE);
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
}
public void testTruncation() {
@@ -238,25 +251,6 @@
// No exceptions? Good.
}
- public RouteInfo passThroughParcel(RouteInfo r) {
- Parcel p = Parcel.obtain();
- RouteInfo r2 = null;
- try {
- r.writeToParcel(p, 0);
- p.setDataPosition(0);
- r2 = RouteInfo.CREATOR.createFromParcel(p);
- } finally {
- p.recycle();
- }
- assertNotNull(r2);
- return r2;
- }
-
- public void assertParcelingIsLossless(RouteInfo r) {
- RouteInfo r2 = passThroughParcel(r);
- assertEquals(r, r2);
- }
-
public void testParceling() {
RouteInfo r;
@@ -264,6 +258,6 @@
assertParcelingIsLossless(r);
r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
- assertParcelingIsLossless(r);
+ assertParcelSane(r, 6);
}
}
diff --git a/tests/net/java/android/net/StaticIpConfigurationTest.java b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
similarity index 75%
rename from tests/net/java/android/net/StaticIpConfigurationTest.java
rename to tests/net/common/java/android/net/StaticIpConfigurationTest.java
index 5bb5734..b5f23bf 100644
--- a/tests/net/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
@@ -18,21 +18,24 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import java.net.InetAddress;
-import java.util.HashSet;
-import java.util.Objects;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class StaticIpConfigurationTest {
@@ -45,6 +48,7 @@
private static final InetAddress DNS2 = IpAddress("8.8.4.4");
private static final InetAddress DNS3 = IpAddress("4.2.2.2");
private static final String IFACE = "eth0";
+ private static final String FAKE_DOMAINS = "google.com";
private static InetAddress IpAddress(String addr) {
return InetAddress.parseNumericAddress(addr);
@@ -57,10 +61,6 @@
assertEquals(0, s.dnsServers.size());
}
- private static <T> void assertNotEquals(T t1, T t2) {
- assertFalse(Objects.equals(t1, t2));
- }
-
private StaticIpConfiguration makeTestObject() {
StaticIpConfiguration s = new StaticIpConfiguration();
s.ipAddress = ADDR;
@@ -68,7 +68,7 @@
s.dnsServers.add(DNS1);
s.dnsServers.add(DNS2);
s.dnsServers.add(DNS3);
- s.domains = "google.com";
+ s.domains = FAKE_DOMAINS;
return s;
}
@@ -177,8 +177,8 @@
expected.addDnsServer(DNS3);
assertEquals(expected, s.toLinkProperties(IFACE));
- s.domains = "google.com";
- expected.setDomains("google.com");
+ s.domains = FAKE_DOMAINS;
+ expected.setDomains(FAKE_DOMAINS);
assertEquals(expected, s.toLinkProperties(IFACE));
s.gateway = null;
@@ -203,7 +203,7 @@
try {
s.writeToParcel(p, 0);
p.setDataPosition(0);
- s2 = StaticIpConfiguration.CREATOR.createFromParcel(p);
+ s2 = StaticIpConfiguration.readFromParcel(p);
} finally {
p.recycle();
}
@@ -217,4 +217,53 @@
StaticIpConfiguration s2 = passThroughParcel(s);
assertEquals(s, s2);
}
+
+ @Test
+ public void testBuilder() {
+ final ArrayList<InetAddress> dnsServers = new ArrayList<>();
+ dnsServers.add(DNS1);
+
+ final StaticIpConfiguration s = new StaticIpConfiguration.Builder()
+ .setIpAddress(ADDR)
+ .setGateway(GATEWAY)
+ .setDomains(FAKE_DOMAINS)
+ .setDnsServers(dnsServers)
+ .build();
+
+ assertEquals(s.ipAddress, s.getIpAddress());
+ assertEquals(ADDR, s.getIpAddress());
+ assertEquals(s.gateway, s.getGateway());
+ assertEquals(GATEWAY, s.getGateway());
+ assertEquals(s.domains, s.getDomains());
+ assertEquals(FAKE_DOMAINS, s.getDomains());
+ assertTrue(s.dnsServers.equals(s.getDnsServers()));
+ assertEquals(1, s.getDnsServers().size());
+ assertEquals(DNS1, s.getDnsServers().get(0));
+ }
+
+ @Test
+ public void testAddDnsServers() {
+ final StaticIpConfiguration s = new StaticIpConfiguration((StaticIpConfiguration) null);
+ checkEmpty(s);
+
+ s.addDnsServer(DNS1);
+ assertEquals(1, s.getDnsServers().size());
+ assertEquals(DNS1, s.getDnsServers().get(0));
+
+ s.addDnsServer(DNS2);
+ s.addDnsServer(DNS3);
+ assertEquals(3, s.getDnsServers().size());
+ assertEquals(DNS2, s.getDnsServers().get(1));
+ assertEquals(DNS3, s.getDnsServers().get(2));
+ }
+
+ @Test
+ public void testGetRoutes() {
+ final StaticIpConfiguration s = makeTestObject();
+ final List<RouteInfo> routeInfoList = s.getRoutes(IFACE);
+
+ assertEquals(2, routeInfoList.size());
+ assertEquals(new RouteInfo(ADDR, (InetAddress) null, IFACE), routeInfoList.get(0));
+ assertEquals(new RouteInfo((IpPrefix) null, GATEWAY, IFACE), routeInfoList.get(1));
+ }
}
diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
new file mode 100644
index 0000000..f4f804a
--- /dev/null
+++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 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.apf;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ApfCapabilitiesTest {
+ @Test
+ public void testConstructAndParcel() {
+ final ApfCapabilities caps = new ApfCapabilities(123, 456, 789);
+ assertEquals(123, caps.apfVersionSupported);
+ assertEquals(456, caps.maximumApfProgramSize);
+ assertEquals(789, caps.apfPacketFormat);
+
+ assertParcelSane(caps, 3);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(new ApfCapabilities(1, 2, 3), new ApfCapabilities(1, 2, 3));
+ assertNotEquals(new ApfCapabilities(2, 2, 3), new ApfCapabilities(1, 2, 3));
+ assertNotEquals(new ApfCapabilities(1, 3, 3), new ApfCapabilities(1, 2, 3));
+ assertNotEquals(new ApfCapabilities(1, 2, 4), new ApfCapabilities(1, 2, 3));
+ }
+
+ @Test
+ public void testHasDataAccess() {
+ //hasDataAccess is only supported starting at apf version 4.
+ ApfCapabilities caps = new ApfCapabilities(1 /* apfVersionSupported */, 2, 3);
+ assertFalse(caps.hasDataAccess());
+
+ caps = new ApfCapabilities(4 /* apfVersionSupported */, 5, 6);
+ assertTrue(caps.hasDataAccess());
+ }
+}
diff --git a/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
new file mode 100644
index 0000000..0b7b740
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.metrics;
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ApfProgramEventTest {
+ private infix fun Int.hasFlag(flag: Int) = (this and (1 shl flag)) != 0
+
+ @Test
+ fun testBuilderAndParcel() {
+ val apfProgramEvent = ApfProgramEvent.Builder()
+ .setLifetime(1)
+ .setActualLifetime(2)
+ .setFilteredRas(3)
+ .setCurrentRas(4)
+ .setProgramLength(5)
+ .setFlags(true, true)
+ .build()
+
+ assertEquals(1, apfProgramEvent.lifetime)
+ assertEquals(2, apfProgramEvent.actualLifetime)
+ assertEquals(3, apfProgramEvent.filteredRas)
+ assertEquals(4, apfProgramEvent.currentRas)
+ assertEquals(5, apfProgramEvent.programLength)
+ assertEquals(ApfProgramEvent.flagsFor(true, true), apfProgramEvent.flags)
+
+ assertParcelSane(apfProgramEvent, 6)
+ }
+
+ @Test
+ fun testFlagsFor() {
+ var flags = ApfProgramEvent.flagsFor(false, false)
+ assertFalse(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+ assertFalse(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+ flags = ApfProgramEvent.flagsFor(true, false)
+ assertTrue(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+ assertFalse(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+ flags = ApfProgramEvent.flagsFor(false, true)
+ assertFalse(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+ assertTrue(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+ flags = ApfProgramEvent.flagsFor(true, true)
+ assertTrue(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+ assertTrue(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+ }
+}
diff --git a/tests/net/common/java/android/net/metrics/ApfStatsTest.kt b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
new file mode 100644
index 0000000..46a8c8e
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 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.metrics
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ApfStatsTest {
+ @Test
+ fun testBuilderAndParcel() {
+ val apfStats = ApfStats.Builder()
+ .setDurationMs(Long.MAX_VALUE)
+ .setReceivedRas(1)
+ .setMatchingRas(2)
+ .setDroppedRas(3)
+ .setZeroLifetimeRas(4)
+ .setParseErrors(5)
+ .setProgramUpdates(6)
+ .setProgramUpdatesAll(7)
+ .setProgramUpdatesAllowingMulticast(8)
+ .setMaxProgramSize(9)
+ .build()
+
+ assertEquals(Long.MAX_VALUE, apfStats.durationMs)
+ assertEquals(1, apfStats.receivedRas)
+ assertEquals(2, apfStats.matchingRas)
+ assertEquals(3, apfStats.droppedRas)
+ assertEquals(4, apfStats.zeroLifetimeRas)
+ assertEquals(5, apfStats.parseErrors)
+ assertEquals(6, apfStats.programUpdates)
+ assertEquals(7, apfStats.programUpdatesAll)
+ assertEquals(8, apfStats.programUpdatesAllowingMulticast)
+ assertEquals(9, apfStats.maxProgramSize)
+
+ assertParcelSane(apfStats, 10)
+ }
+}
diff --git a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
new file mode 100644
index 0000000..236f72e
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
@@ -0,0 +1,65 @@
+package android.net.metrics
+
+import android.net.metrics.DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH
+import android.net.metrics.DhcpErrorEvent.errorCodeWithOption
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.parcelingRoundTrip
+import java.lang.reflect.Modifier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_ERROR_CODE = 12345
+//DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java due to it's protected)
+private const val DHCP_SUBNET_MASK = 1
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DhcpErrorEventTest {
+
+ @Test
+ fun testConstructor() {
+ val event = DhcpErrorEvent(TEST_ERROR_CODE)
+ assertEquals(TEST_ERROR_CODE, event.errorCode)
+ }
+
+ @Test
+ fun testParcelUnparcel() {
+ val event = DhcpErrorEvent(TEST_ERROR_CODE)
+ val parceled = parcelingRoundTrip(event)
+ assertEquals(TEST_ERROR_CODE, parceled.errorCode)
+ }
+
+ @Test
+ fun testErrorCodeWithOption() {
+ val errorCode = errorCodeWithOption(DHCP_INVALID_OPTION_LENGTH, DHCP_SUBNET_MASK);
+ assertTrue((DHCP_INVALID_OPTION_LENGTH and errorCode) == DHCP_INVALID_OPTION_LENGTH);
+ assertTrue((DHCP_SUBNET_MASK and errorCode) == DHCP_SUBNET_MASK);
+ }
+
+ @Test
+ fun testToString() {
+ val names = listOf("L2_ERROR", "L3_ERROR", "L4_ERROR", "DHCP_ERROR", "MISC_ERROR")
+ val errorFields = DhcpErrorEvent::class.java.declaredFields.filter {
+ it.type == Int::class.javaPrimitiveType
+ && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
+ && it.name !in names
+ }
+
+ errorFields.forEach {
+ val intValue = it.getInt(null)
+ val stringValue = DhcpErrorEvent(intValue).toString()
+ assertTrue("Invalid string for error 0x%08X (field %s): %s".format(intValue, it.name,
+ stringValue),
+ stringValue.contains(it.name))
+ }
+ }
+
+ @Test
+ fun testToString_InvalidErrorCode() {
+ assertNotNull(DhcpErrorEvent(TEST_ERROR_CODE).toString())
+ }
+}
diff --git a/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java b/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java
new file mode 100644
index 0000000..d4780d3
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2019 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.metrics;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.Network;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.BitUtils;
+
+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.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConnectivityLogTest {
+ private static final int FAKE_NET_ID = 100;
+ private static final int[] FAKE_TRANSPORT_TYPES = BitUtils.unpackBits(TRANSPORT_WIFI);
+ private static final long FAKE_TIME_STAMP = System.currentTimeMillis();
+ private static final String FAKE_INTERFACE_NAME = "test";
+ private static final IpReachabilityEvent FAKE_EV =
+ new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
+
+ @Mock IIpConnectivityMetrics mMockService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testLoggingEvents() throws Exception {
+ IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+ assertTrue(logger.log(FAKE_EV));
+ assertTrue(logger.log(FAKE_TIME_STAMP, FAKE_EV));
+ assertTrue(logger.log(FAKE_NET_ID, FAKE_TRANSPORT_TYPES, FAKE_EV));
+ assertTrue(logger.log(new Network(FAKE_NET_ID), FAKE_TRANSPORT_TYPES, FAKE_EV));
+ assertTrue(logger.log(FAKE_INTERFACE_NAME, FAKE_EV));
+ assertTrue(logger.log(makeExpectedEvent(FAKE_TIME_STAMP, FAKE_NET_ID, TRANSPORT_WIFI,
+ FAKE_INTERFACE_NAME)));
+
+ List<ConnectivityMetricsEvent> got = verifyEvents(6);
+ assertEventsEqual(makeExpectedEvent(got.get(0).timestamp, 0, 0, null), got.get(0));
+ assertEventsEqual(makeExpectedEvent(FAKE_TIME_STAMP, 0, 0, null), got.get(1));
+ assertEventsEqual(makeExpectedEvent(got.get(2).timestamp, FAKE_NET_ID,
+ TRANSPORT_WIFI, null), got.get(2));
+ assertEventsEqual(makeExpectedEvent(got.get(3).timestamp, FAKE_NET_ID,
+ TRANSPORT_WIFI, null), got.get(3));
+ assertEventsEqual(makeExpectedEvent(got.get(4).timestamp, 0, 0, FAKE_INTERFACE_NAME),
+ got.get(4));
+ assertEventsEqual(makeExpectedEvent(FAKE_TIME_STAMP, FAKE_NET_ID,
+ TRANSPORT_WIFI, FAKE_INTERFACE_NAME), got.get(5));
+ }
+
+ @Test
+ public void testLoggingEventsWithMultipleCallers() throws Exception {
+ IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+ final int nCallers = 10;
+ final int nEvents = 10;
+ for (int n = 0; n < nCallers; n++) {
+ final int i = n;
+ new Thread() {
+ public void run() {
+ for (int j = 0; j < nEvents; j++) {
+ assertTrue(logger.log(makeExpectedEvent(
+ FAKE_TIME_STAMP + i * 100 + j,
+ FAKE_NET_ID + i * 100 + j,
+ ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR,
+ FAKE_INTERFACE_NAME)));
+ }
+ }
+ }.start();
+ }
+
+ List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
+ Collections.sort(got, EVENT_COMPARATOR);
+ Iterator<ConnectivityMetricsEvent> iter = got.iterator();
+ for (int i = 0; i < nCallers; i++) {
+ for (int j = 0; j < nEvents; j++) {
+ final long expectedTimestamp = FAKE_TIME_STAMP + i * 100 + j;
+ final int expectedNetId = FAKE_NET_ID + i * 100 + j;
+ final long expectedTransports =
+ ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR;
+ assertEventsEqual(makeExpectedEvent(expectedTimestamp, expectedNetId,
+ expectedTransports, FAKE_INTERFACE_NAME), iter.next());
+ }
+ }
+ }
+
+ private List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
+ ArgumentCaptor<ConnectivityMetricsEvent> captor =
+ ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
+ verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
+ return captor.getAllValues();
+ }
+
+ private List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
+ return verifyEvents(n, 10);
+ }
+
+
+ private ConnectivityMetricsEvent makeExpectedEvent(long timestamp, int netId, long transports,
+ String ifname) {
+ ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+ ev.timestamp = timestamp;
+ ev.data = FAKE_EV;
+ ev.netId = netId;
+ ev.transports = transports;
+ ev.ifname = ifname;
+ return ev;
+ }
+
+ /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
+ private void assertEventsEqual(ConnectivityMetricsEvent expected,
+ ConnectivityMetricsEvent got) {
+ assertEquals(expected.data, got.data);
+ assertEquals(expected.timestamp, got.timestamp);
+ assertEquals(expected.netId, got.netId);
+ assertEquals(expected.transports, got.transports);
+ assertEquals(expected.ifname, got.ifname);
+ }
+
+ static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
+ Comparator.comparingLong((ev) -> ev.timestamp);
+}
diff --git a/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
new file mode 100644
index 0000000..55b5e49
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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.metrics
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class IpReachabilityEventTest {
+ @Test
+ fun testConstructorAndParcel() {
+ (IpReachabilityEvent.PROBE..IpReachabilityEvent.PROVISIONING_LOST_ORGANIC).forEach {
+ val ipReachabilityEvent = IpReachabilityEvent(it)
+ assertEquals(it, ipReachabilityEvent.eventType)
+
+ assertParcelSane(ipReachabilityEvent, 1)
+ }
+ }
+}
diff --git a/tests/net/common/java/android/net/metrics/RaEventTest.kt b/tests/net/common/java/android/net/metrics/RaEventTest.kt
new file mode 100644
index 0000000..d9b7203
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/RaEventTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.metrics
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val NO_LIFETIME: Long = -1L
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class RaEventTest {
+ @Test
+ fun testConstructorAndParcel() {
+ var raEvent = RaEvent.Builder().build()
+ assertEquals(NO_LIFETIME, raEvent.routerLifetime)
+ assertEquals(NO_LIFETIME, raEvent.prefixValidLifetime)
+ assertEquals(NO_LIFETIME, raEvent.prefixPreferredLifetime)
+ assertEquals(NO_LIFETIME, raEvent.routeInfoLifetime)
+ assertEquals(NO_LIFETIME, raEvent.rdnssLifetime)
+ assertEquals(NO_LIFETIME, raEvent.dnsslLifetime)
+
+ raEvent = RaEvent.Builder()
+ .updateRouterLifetime(1)
+ .updatePrefixValidLifetime(2)
+ .updatePrefixPreferredLifetime(3)
+ .updateRouteInfoLifetime(4)
+ .updateRdnssLifetime(5)
+ .updateDnsslLifetime(6)
+ .build()
+ assertEquals(1, raEvent.routerLifetime)
+ assertEquals(2, raEvent.prefixValidLifetime)
+ assertEquals(3, raEvent.prefixPreferredLifetime)
+ assertEquals(4, raEvent.routeInfoLifetime)
+ assertEquals(5, raEvent.rdnssLifetime)
+ assertEquals(6, raEvent.dnsslLifetime)
+
+ raEvent = RaEvent.Builder()
+ .updateRouterLifetime(Long.MIN_VALUE)
+ .updateRouterLifetime(Long.MAX_VALUE)
+ .build()
+ assertEquals(Long.MIN_VALUE, raEvent.routerLifetime)
+
+ raEvent = RaEvent(1, 2, 3, 4, 5, 6)
+ assertEquals(1, raEvent.routerLifetime)
+ assertEquals(2, raEvent.prefixValidLifetime)
+ assertEquals(3, raEvent.prefixPreferredLifetime)
+ assertEquals(4, raEvent.routeInfoLifetime)
+ assertEquals(5, raEvent.rdnssLifetime)
+ assertEquals(6, raEvent.dnsslLifetime)
+
+ assertParcelSane(raEvent, 6)
+ }
+}
diff --git a/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
new file mode 100644
index 0000000..51c0d41
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.metrics
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import java.lang.reflect.Modifier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val FIRST_VALIDATION: Int = 1 shl 8
+private const val REVALIDATION: Int = 2 shl 8
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ValidationProbeEventTest {
+ private infix fun Int.hasType(type: Int) = (type and this) == type
+
+ @Test
+ fun testBuilderAndParcel() {
+ var validationProbeEvent = ValidationProbeEvent.Builder()
+ .setProbeType(ValidationProbeEvent.PROBE_DNS, false).build()
+
+ assertTrue(validationProbeEvent.probeType hasType REVALIDATION)
+
+ validationProbeEvent = ValidationProbeEvent.Builder()
+ .setDurationMs(Long.MAX_VALUE)
+ .setProbeType(ValidationProbeEvent.PROBE_DNS, true)
+ .setReturnCode(ValidationProbeEvent.DNS_SUCCESS)
+ .build()
+
+ assertEquals(Long.MAX_VALUE, validationProbeEvent.durationMs)
+ assertTrue(validationProbeEvent.probeType hasType ValidationProbeEvent.PROBE_DNS)
+ assertTrue(validationProbeEvent.probeType hasType FIRST_VALIDATION)
+ assertEquals(ValidationProbeEvent.DNS_SUCCESS, validationProbeEvent.returnCode)
+
+ assertParcelSane(validationProbeEvent, 3)
+ }
+
+ @Test
+ fun testGetProbeName() {
+ val probeFields = ValidationProbeEvent::class.java.declaredFields.filter {
+ it.type == Int::class.javaPrimitiveType
+ && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
+ && it.name.contains("PROBE")
+ }
+
+ probeFields.forEach {
+ val intValue = it.getInt(null)
+ val stringValue = ValidationProbeEvent.getProbeName(intValue)
+ assertEquals(it.name, stringValue)
+ }
+
+ }
+}
diff --git a/tests/net/common/java/android/net/util/SocketUtilsTest.kt b/tests/net/common/java/android/net/util/SocketUtilsTest.kt
new file mode 100644
index 0000000..9c7cfb0
--- /dev/null
+++ b/tests/net/common/java/android/net/util/SocketUtilsTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import android.system.NetlinkSocketAddress
+import android.system.Os
+import android.system.OsConstants.AF_INET
+import android.system.OsConstants.ETH_P_ALL
+import android.system.OsConstants.IPPROTO_UDP
+import android.system.OsConstants.RTMGRP_NEIGH
+import android.system.OsConstants.SOCK_DGRAM
+import android.system.PacketSocketAddress
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_INDEX = 123
+private const val TEST_PORT = 555
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SocketUtilsTest {
+ @Test
+ fun testMakeNetlinkSocketAddress() {
+ val nlAddress = SocketUtils.makeNetlinkSocketAddress(TEST_PORT, RTMGRP_NEIGH)
+ if (nlAddress is NetlinkSocketAddress) {
+ assertEquals(TEST_PORT, nlAddress.getPortId())
+ assertEquals(RTMGRP_NEIGH, nlAddress.getGroupsMask())
+ } else {
+ fail("Not NetlinkSocketAddress object")
+ }
+ }
+
+ @Test
+ fun testMakePacketSocketAddress() {
+ val pkAddress = SocketUtils.makePacketSocketAddress(ETH_P_ALL, TEST_INDEX)
+ assertTrue("Not PacketSocketAddress object", pkAddress is PacketSocketAddress)
+
+ val ff = 0xff.toByte()
+ val pkAddress2 = SocketUtils.makePacketSocketAddress(TEST_INDEX,
+ byteArrayOf(ff, ff, ff, ff, ff, ff))
+ assertTrue("Not PacketSocketAddress object", pkAddress2 is PacketSocketAddress)
+ }
+
+ @Test
+ fun testCloseSocket() {
+ // Expect no exception happening with null object.
+ SocketUtils.closeSocket(null)
+
+ val fd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
+ assertTrue(fd.valid())
+ SocketUtils.closeSocket(fd)
+ assertFalse(fd.valid())
+ // Expecting socket should be still invalid even closed socket again.
+ SocketUtils.closeSocket(fd)
+ assertFalse(fd.valid())
+ }
+}
diff --git a/tests/net/java/android/app/usage/NetworkStatsManagerTest.java b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java
index 25e1474..899295a 100644
--- a/tests/net/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/net/java/android/app/usage/NetworkStatsManagerTest.java
@@ -26,7 +26,6 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -38,9 +37,10 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.RemoteException;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
@@ -202,8 +202,7 @@
assertFalse(stats.hasNextBucket());
}
- private void assertBucketMatches(Entry expected,
- NetworkStats.Bucket actual) {
+ private void assertBucketMatches(Entry expected, NetworkStats.Bucket actual) {
assertEquals(expected.uid, actual.getUid());
assertEquals(expected.rxBytes, actual.getRxBytes());
assertEquals(expected.rxPackets, actual.getRxPackets());
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index 03a617c..7ede144 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -34,7 +34,6 @@
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.Mockito.any;
@@ -47,19 +46,20 @@
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
-import android.net.ConnectivityManager;
-import android.net.NetworkCapabilities;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkCapabilities;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
-import android.content.pm.ApplicationInfo;
-import android.os.Build.VERSION_CODES;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
@@ -219,7 +219,7 @@
// callback triggers
captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE));
verify(callback, timeout(500).times(1)).onAvailable(any(Network.class),
- any(NetworkCapabilities.class), any(LinkProperties.class));
+ any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean());
// unregister callback
manager.unregisterNetworkCallback(callback);
@@ -247,7 +247,7 @@
// callback triggers
captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE));
verify(callback, timeout(100).times(1)).onAvailable(any(Network.class),
- any(NetworkCapabilities.class), any(LinkProperties.class));
+ any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean());
// unregister callback
manager.unregisterNetworkCallback(callback);
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
new file mode 100644
index 0000000..b81ca36
--- /dev/null
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2018 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.Status;
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpMemoryStoreTest {
+ private static final String TAG = IpMemoryStoreTest.class.getSimpleName();
+ private static final String TEST_CLIENT_ID = "testClientId";
+ private static final String TEST_DATA_NAME = "testData";
+ private static final String TEST_OTHER_DATA_NAME = TEST_DATA_NAME + "Other";
+ private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
+ -128, 0, 89, 112, 91, -34 };
+ private static final NetworkAttributes TEST_NETWORK_ATTRIBUTES = buildTestNetworkAttributes(
+ "hint", 219);
+
+ @Mock
+ Context mMockContext;
+ @Mock
+ NetworkStackClient mNetworkStackClient;
+ @Mock
+ IIpMemoryStore mMockService;
+ @Mock
+ IOnStatusListener mIOnStatusListener;
+ IpMemoryStore mStore;
+
+ @Captor
+ ArgumentCaptor<IIpMemoryStoreCallbacks> mCbCaptor;
+ @Captor
+ ArgumentCaptor<NetworkAttributesParcelable> mNapCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private void startIpMemoryStore(boolean supplyService) {
+ if (supplyService) {
+ doAnswer(invocation -> {
+ ((IIpMemoryStoreCallbacks) invocation.getArgument(0))
+ .onIpMemoryStoreFetched(mMockService);
+ return null;
+ }).when(mNetworkStackClient).fetchIpMemoryStore(any());
+ } else {
+ doNothing().when(mNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture());
+ }
+ mStore = new IpMemoryStore(mMockContext) {
+ @Override
+ protected NetworkStackClient getNetworkStackClient() {
+ return mNetworkStackClient;
+ }
+ };
+ }
+
+ private static NetworkAttributes buildTestNetworkAttributes(String hint, int mtu) {
+ return new NetworkAttributes.Builder()
+ .setGroupHint(hint)
+ .setMtu(mtu)
+ .build();
+ }
+
+ @Test
+ public void testNetworkAttributes() throws Exception {
+ startIpMemoryStore(true /* supplyService */);
+ final String l2Key = "fakeKey";
+
+ mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ verify(mMockService, times(1)).storeNetworkAttributes(eq(l2Key),
+ mNapCaptor.capture(), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+
+ mStore.retrieveNetworkAttributes(l2Key,
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+ });
+
+ verify(mMockService, times(1)).retrieveNetworkAttributes(eq(l2Key), any());
+ }
+
+ @Test
+ public void testPrivateData() throws RemoteException {
+ startIpMemoryStore(true /* supplyService */);
+ final Blob b = new Blob();
+ b.data = TEST_BLOB_DATA;
+ final String l2Key = "fakeKey";
+
+ mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ status -> {
+ assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+ });
+ verify(mMockService, times(1)).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+ eq(b), any());
+
+ mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertTrue(Arrays.equals(b.data, data.data));
+ });
+ verify(mMockService, times(1)).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+ eq(TEST_OTHER_DATA_NAME), any());
+ }
+
+ @Test
+ public void testFindL2Key()
+ throws UnknownHostException, RemoteException, Exception {
+ startIpMemoryStore(true /* supplyService */);
+ final String l2Key = "fakeKey";
+
+ mStore.findL2Key(TEST_NETWORK_ATTRIBUTES,
+ (status, key) -> {
+ assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ });
+ verify(mMockService, times(1)).findL2Key(mNapCaptor.capture(), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+ }
+
+ @Test
+ public void testIsSameNetwork() throws UnknownHostException, RemoteException {
+ startIpMemoryStore(true /* supplyService */);
+ final String l2Key1 = "fakeKey1";
+ final String l2Key2 = "fakeKey2";
+
+ mStore.isSameNetwork(l2Key1, l2Key2,
+ (status, answer) -> {
+ assertFalse("Retrieve network sameness suspiciously successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+ assertNull(answer);
+ });
+ verify(mMockService, times(1)).isSameNetwork(eq(l2Key1), eq(l2Key2), any());
+ }
+
+ @Test
+ public void testEnqueuedIpMsRequests() throws Exception {
+ startIpMemoryStore(false /* supplyService */);
+
+ final Blob b = new Blob();
+ b.data = TEST_BLOB_DATA;
+ final String l2Key = "fakeKey";
+
+ // enqueue multiple ipms requests
+ mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveNetworkAttributes(l2Key,
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+ });
+ mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertTrue(Arrays.equals(b.data, data.data));
+ });
+
+ // get ipms service ready
+ mCbCaptor.getValue().onIpMemoryStoreFetched(mMockService);
+
+ InOrder inOrder = inOrder(mMockService);
+
+ inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
+ inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any());
+ inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+ eq(b), any());
+ inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+ eq(TEST_OTHER_DATA_NAME), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+ }
+
+ @Test
+ public void testEnqueuedIpMsRequestsWithException() throws Exception {
+ startIpMemoryStore(true /* supplyService */);
+ doThrow(RemoteException.class).when(mMockService).retrieveNetworkAttributes(any(), any());
+
+ final Blob b = new Blob();
+ b.data = TEST_BLOB_DATA;
+ final String l2Key = "fakeKey";
+
+ // enqueue multiple ipms requests
+ mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveNetworkAttributes(l2Key,
+ (status, key, attr) -> {
+ assertTrue("Retrieve network attributes not successful : "
+ + status.resultCode, status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+ });
+ mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertTrue(Arrays.equals(b.data, data.data));
+ });
+
+ // verify the rest of the queue is still processed in order even if the remote exception
+ // occurs when calling one or more requests
+ InOrder inOrder = inOrder(mMockService);
+
+ inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
+ inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+ eq(b), any());
+ inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+ eq(TEST_OTHER_DATA_NAME), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+ }
+
+ @Test
+ public void testEnqueuedIpMsRequestsCallbackFunctionWithException() throws Exception {
+ startIpMemoryStore(true /* supplyService */);
+
+ final Blob b = new Blob();
+ b.data = TEST_BLOB_DATA;
+ final String l2Key = "fakeKey";
+
+ // enqueue multiple ipms requests
+ mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+ status -> assertTrue("Store not successful : " + status.resultCode,
+ status.isSuccess()));
+ mStore.retrieveNetworkAttributes(l2Key,
+ (status, key, attr) -> {
+ throw new RuntimeException("retrieveNetworkAttributes test");
+ });
+ mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+ status -> {
+ throw new RuntimeException("storeBlob test");
+ });
+ mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+ (status, key, name, data) -> {
+ assertTrue("Retrieve blob status not successful : " + status.resultCode,
+ status.isSuccess());
+ assertEquals(l2Key, key);
+ assertEquals(name, TEST_DATA_NAME);
+ assertTrue(Arrays.equals(b.data, data.data));
+ });
+
+ // verify the rest of the queue is still processed in order even if when one or more
+ // callback throw the remote exception
+ InOrder inOrder = inOrder(mMockService);
+
+ inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(),
+ any());
+ inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any());
+ inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+ eq(b), any());
+ inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+ eq(TEST_OTHER_DATA_NAME), any());
+ assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+ }
+
+ @Test
+ public void testFactoryReset() throws RemoteException {
+ startIpMemoryStore(true /* supplyService */);
+ mStore.factoryReset();
+ verify(mMockService, times(1)).factoryReset();
+ }
+}
diff --git a/tests/net/java/android/net/IpSecAlgorithmTest.java b/tests/net/java/android/net/IpSecAlgorithmTest.java
index 85e8361..8e9d08c 100644
--- a/tests/net/java/android/net/IpSecAlgorithmTest.java
+++ b/tests/net/java/android/net/IpSecAlgorithmTest.java
@@ -20,17 +20,18 @@
import static org.junit.Assert.fail;
import android.os.Parcel;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.Random;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
/** Unit tests for {@link IpSecAlgorithm}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java
index 771faaf..c9888b2 100644
--- a/tests/net/java/android/net/IpSecConfigTest.java
+++ b/tests/net/java/android/net/IpSecConfigTest.java
@@ -16,13 +16,14 @@
package android.net;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
-import android.os.Parcel;
-import android.support.test.filters.SmallTest;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,6 +48,7 @@
assertNull(c.getEncryption());
assertNull(c.getAuthentication());
assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId());
+ assertEquals(0, c.getXfrmInterfaceId());
}
private IpSecConfig getSampleConfig() {
@@ -77,6 +79,7 @@
c.setNattKeepaliveInterval(42);
c.setMarkValue(12);
c.setMarkMask(23);
+ c.setXfrmInterfaceId(34);
return c;
}
@@ -86,23 +89,15 @@
IpSecConfig original = getSampleConfig();
IpSecConfig copy = new IpSecConfig(original);
- assertTrue(IpSecConfig.equals(original, copy));
- assertFalse(original == copy);
+ assertEquals(original, copy);
+ assertNotSame(original, copy);
}
@Test
- public void testParcelUnparcel() throws Exception {
+ public void testParcelUnparcel() {
assertParcelingIsLossless(new IpSecConfig());
IpSecConfig c = getSampleConfig();
- assertParcelingIsLossless(c);
- }
-
- private void assertParcelingIsLossless(IpSecConfig ci) throws Exception {
- Parcel p = Parcel.obtain();
- ci.writeToParcel(p, 0);
- p.setDataPosition(0);
- IpSecConfig co = IpSecConfig.CREATOR.createFromParcel(p);
- assertTrue(IpSecConfig.equals(co, ci));
+ assertParcelSane(c, 15);
}
}
diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java
index 8160924..730e2d5 100644
--- a/tests/net/java/android/net/IpSecManagerTest.java
+++ b/tests/net/java/android/net/IpSecManagerTest.java
@@ -19,6 +19,7 @@
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -30,21 +31,22 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.test.mock.MockContext;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.system.Os;
+import android.test.mock.MockContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.server.IpSecService;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
/** Unit tests for {@link IpSecManager}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/tests/net/java/android/net/IpSecTransformTest.java b/tests/net/java/android/net/IpSecTransformTest.java
index ffd1f06..424f23d 100644
--- a/tests/net/java/android/net/IpSecTransformTest.java
+++ b/tests/net/java/android/net/IpSecTransformTest.java
@@ -16,10 +16,10 @@
package android.net;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
-import android.support.test.filters.SmallTest;
+import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,7 +43,7 @@
config.setSpiResourceId(1985);
IpSecTransform postModification = new IpSecTransform(null, config);
- assertFalse(IpSecTransform.equals(preModification, postModification));
+ assertNotEquals(preModification, postModification);
}
@Test
@@ -57,6 +57,6 @@
IpSecTransform config1 = new IpSecTransform(null, config);
IpSecTransform config2 = new IpSecTransform(null, config);
- assertTrue(IpSecTransform.equals(config1, config2));
+ assertEquals(config1, config2);
}
}
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 04266c5..b0e5fb1 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -16,20 +16,22 @@
package android.net;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Random;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet6Address;
+import java.util.Arrays;
+import java.util.Random;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class MacAddressTest {
@@ -252,6 +254,19 @@
}
}
+ /**
+ * Tests that link-local address generation from MAC is valid.
+ */
+ @Test
+ public void testLinkLocalFromMacGeneration() {
+ MacAddress mac = MacAddress.fromString("52:74:f2:b1:a8:7f");
+ byte[] inet6ll = {(byte) 0xfe, (byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x74,
+ (byte) 0xf2, (byte) 0xff, (byte) 0xfe, (byte) 0xb1, (byte) 0xa8, 0x7f};
+ Inet6Address llv6 = mac.getLinkLocalIpv6FromEui48Mac();
+ assertTrue(llv6.isLinkLocalAddress());
+ assertArrayEquals(inet6ll, llv6.getAddress());
+ }
+
static byte[] toByteArray(int... in) {
byte[] out = new byte[in.length];
for (int i = 0; i < in.length; i++) {
diff --git a/tests/net/java/android/net/NetworkStatsHistoryTest.java b/tests/net/java/android/net/NetworkStatsHistoryTest.java
index 301d04d..13558cd 100644
--- a/tests/net/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/net/java/android/net/NetworkStatsHistoryTest.java
@@ -16,14 +16,14 @@
package android.net;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLong;
+import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLong;
+import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
import static android.net.NetworkStatsHistory.FIELD_ALL;
import static android.net.NetworkStatsHistory.FIELD_OPERATIONS;
import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
import static android.net.NetworkStatsHistory.FIELD_RX_PACKETS;
import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
-import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLong;
-import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLong;
-import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
import static android.net.TrafficStats.GB_IN_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -32,29 +32,30 @@
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.frameworks.tests.net.R;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.Random;
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetworkStatsHistoryTest {
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index d6dbf5a..c16a0f4 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -19,6 +19,7 @@
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.INTERFACES_ALL;
import static android.net.NetworkStats.METERED_ALL;
import static android.net.NetworkStats.METERED_NO;
@@ -26,31 +27,30 @@
import static android.net.NetworkStats.ROAMING_ALL;
import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.ROAMING_YES;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.SET_DBG_VPN_IN;
import static android.net.NetworkStats.SET_DBG_VPN_OUT;
-import static android.net.NetworkStats.SET_ALL;
-import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import android.net.NetworkStats.Entry;
import android.os.Process;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.filters.SmallTest;
import android.util.ArrayMap;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.google.android.collect.Sets;
-import java.util.HashSet;
-
-import org.junit.runner.RunWith;
import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -448,22 +448,58 @@
}
@Test
- public void testWithoutUid() throws Exception {
- final NetworkStats before = new NetworkStats(TEST_START, 3)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+ public void testRemoveUids() throws Exception {
+ final NetworkStats before = new NetworkStats(TEST_START, 3);
- final NetworkStats after = before.withoutUids(new int[] { 100 });
- assertEquals(6, before.size());
- assertEquals(2, after.size());
- assertValues(after, 0, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L);
- assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L);
+ // Test 0 item stats.
+ NetworkStats after = before.clone();
+ after.removeUids(new int[0]);
+ assertEquals(0, after.size());
+ after.removeUids(new int[] {100});
+ assertEquals(0, after.size());
+
+ // Test 1 item stats.
+ before.addValues(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L);
+ after = before.clone();
+ after.removeUids(new int[0]);
+ assertEquals(1, after.size());
+ assertValues(after, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L);
+ after.removeUids(new int[] {99});
+ assertEquals(0, after.size());
+
+ // Append remaining test items.
+ before.addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L)
+ .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L);
+ assertEquals(7, before.size());
+
+ // Test remove with empty uid list.
+ after = before.clone();
+ after.removeUids(new int[0]);
+ assertValues(after.getTotalIncludingTags(null), 127L, 254L, 0L, 4L, 40L);
+
+ // Test remove uids don't exist in stats.
+ after.removeUids(new int[] {98, 0, Integer.MIN_VALUE, Integer.MAX_VALUE});
+ assertValues(after.getTotalIncludingTags(null), 127L, 254L, 0L, 4L, 40L);
+
+ // Test remove all uids.
+ after.removeUids(new int[] {99, 100, 100, 101});
+ assertEquals(0, after.size());
+
+ // Test remove in the middle.
+ after = before.clone();
+ after.removeUids(new int[] {100});
+ assertEquals(3, after.size());
+ assertValues(after, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L);
+ assertValues(after, 1, TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 32L, 4L, 0L, 0L, 0L);
+ assertValues(after, 2, TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 64L, 2L, 0L, 0L, 0L);
}
@Test
@@ -533,7 +569,7 @@
.addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
- assertTrue(delta.toString(), delta.migrateTun(tunUid, tunIface, underlyingIface));
+ delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface});
assertEquals(20, delta.size());
// tunIface and TEST_IFACE entries are not changed.
@@ -614,7 +650,7 @@
.addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L);
- assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
+ delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
assertEquals(9, delta.size());
// tunIface entries should not be changed.
@@ -777,6 +813,37 @@
}
@Test
+ public void testFilterDebugEntries() {
+ NetworkStats.Entry entry1 = new NetworkStats.Entry(
+ "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry2 = new NetworkStats.Entry(
+ "test2", 10101, SET_DBG_VPN_IN, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry3 = new NetworkStats.Entry(
+ "test2", 10101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats.Entry entry4 = new NetworkStats.Entry(
+ "test2", 10101, SET_DBG_VPN_OUT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
+
+ NetworkStats stats = new NetworkStats(TEST_START, 4)
+ .addValues(entry1)
+ .addValues(entry2)
+ .addValues(entry3)
+ .addValues(entry4);
+
+ stats.filterDebugEntries();
+
+ assertEquals(2, stats.size());
+ assertEquals(entry1, stats.getValues(0, null));
+ assertEquals(entry3, stats.getValues(1, null));
+ }
+
+ @Test
public void testApply464xlatAdjustments() {
final String v4Iface = "v4-wlan0";
final String baseIface = "wlan0";
@@ -796,25 +863,23 @@
0 /* operations */);
// Traffic measured for the root uid on the base interface if eBPF is in use.
- // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation
- // overhead (20 bytes per packet), only for TX traffic.
final NetworkStats.Entry ebpfRootUidEntry = new NetworkStats.Entry(
baseIface, rootUid, SET_DEFAULT, TAG_NONE,
163577 /* rxBytes */,
187 /* rxPackets */,
- 1169942 /* txBytes */,
- 13902 /* txPackets */,
+ 17607 /* txBytes */,
+ 97 /* txPackets */,
0 /* operations */);
// Traffic measured for the root uid on the base interface if xt_qtaguid is in use.
// Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation
- // overhead (20 bytes per packet), in both directions.
+ // overhead (20 bytes per packet), in rx direction.
final NetworkStats.Entry xtRootUidEntry = new NetworkStats.Entry(
baseIface, rootUid, SET_DEFAULT, TAG_NONE,
31113087 /* rxBytes */,
22588 /* rxPackets */,
- 1169942 /* txBytes */,
- 13902 /* txPackets */,
+ 17607 /* txBytes */,
+ 97 /* txPackets */,
0 /* operations */);
final NetworkStats.Entry otherEntry = new NetworkStats.Entry(
diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java
index a5ee8e3..7748288 100644
--- a/tests/net/java/android/net/NetworkUtilsTest.java
+++ b/tests/net/java/android/net/NetworkUtilsTest.java
@@ -16,61 +16,20 @@
package android.net;
-import android.net.NetworkUtils;
-import android.test.suitebuilder.annotation.SmallTest;
+import static junit.framework.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.math.BigInteger;
-import java.net.Inet4Address;
-import java.net.InetAddress;
import java.util.TreeSet;
-import junit.framework.TestCase;
-
-public class NetworkUtilsTest extends TestCase {
-
- private InetAddress Address(String addr) {
- return InetAddress.parseNumericAddress(addr);
- }
-
- private Inet4Address IPv4Address(String addr) {
- return (Inet4Address) Address(addr);
- }
-
- @SmallTest
- public void testGetImplicitNetmask() {
- assertEquals(8, NetworkUtils.getImplicitNetmask(IPv4Address("4.2.2.2")));
- assertEquals(8, NetworkUtils.getImplicitNetmask(IPv4Address("10.5.6.7")));
- assertEquals(16, NetworkUtils.getImplicitNetmask(IPv4Address("173.194.72.105")));
- assertEquals(16, NetworkUtils.getImplicitNetmask(IPv4Address("172.23.68.145")));
- assertEquals(24, NetworkUtils.getImplicitNetmask(IPv4Address("192.0.2.1")));
- assertEquals(24, NetworkUtils.getImplicitNetmask(IPv4Address("192.168.5.1")));
- assertEquals(32, NetworkUtils.getImplicitNetmask(IPv4Address("224.0.0.1")));
- assertEquals(32, NetworkUtils.getImplicitNetmask(IPv4Address("255.6.7.8")));
- }
-
- private void assertInvalidNetworkMask(Inet4Address addr) {
- try {
- NetworkUtils.netmaskToPrefixLength(addr);
- fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception");
- } catch (IllegalArgumentException expected) {
- }
- }
-
- @SmallTest
- public void testNetmaskToPrefixLength() {
- assertEquals(0, NetworkUtils.netmaskToPrefixLength(IPv4Address("0.0.0.0")));
- assertEquals(9, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.128.0.0")));
- assertEquals(17, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.255.128.0")));
- assertEquals(23, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.255.254.0")));
- assertEquals(31, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.255.255.254")));
- assertEquals(32, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.255.255.255")));
-
- assertInvalidNetworkMask(IPv4Address("0.0.0.1"));
- assertInvalidNetworkMask(IPv4Address("255.255.255.253"));
- assertInvalidNetworkMask(IPv4Address("255.255.0.255"));
- }
-
- @SmallTest
+@RunWith(AndroidJUnit4.class)
+@androidx.test.filters.SmallTest
+public class NetworkUtilsTest {
+ @Test
public void testRoutedIPv4AddressCount() {
final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator());
// No routes routes to no addresses.
@@ -118,7 +77,7 @@
assertEquals(7l - 4 + 4 + 16 + 65536, NetworkUtils.routedIPv4AddressCount(set));
}
- @SmallTest
+ @Test
public void testRoutedIPv6AddressCount() {
final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator());
// No routes routes to no addresses.
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
new file mode 100644
index 0000000..5cb0d7e
--- /dev/null
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.SocketKeepalive.InvalidPacketException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+@RunWith(JUnit4.class)
+public final class TcpKeepalivePacketDataTest {
+ private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 1};
+ private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 5};
+
+ @Before
+ public void setUp() {}
+
+ @Test
+ public void testV4TcpKeepalivePacket() throws Exception {
+ final int srcPort = 1234;
+ final int dstPort = 4321;
+ final int seq = 0x11111111;
+ final int ack = 0x22222222;
+ final int wnd = 8000;
+ final int wndScale = 2;
+ final int tos = 4;
+ final int ttl = 64;
+ TcpKeepalivePacketData resultData = null;
+ final TcpKeepalivePacketDataParcelable testInfo = new TcpKeepalivePacketDataParcelable();
+ testInfo.srcAddress = IPV4_KEEPALIVE_SRC_ADDR;
+ testInfo.srcPort = srcPort;
+ testInfo.dstAddress = IPV4_KEEPALIVE_DST_ADDR;
+ testInfo.dstPort = dstPort;
+ testInfo.seq = seq;
+ testInfo.ack = ack;
+ testInfo.rcvWnd = wnd;
+ testInfo.rcvWndScale = wndScale;
+ testInfo.tos = tos;
+ testInfo.ttl = ttl;
+ try {
+ resultData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo);
+ } catch (InvalidPacketException e) {
+ fail("InvalidPacketException: " + e);
+ }
+
+ assertEquals(InetAddress.getByAddress(testInfo.srcAddress), resultData.srcAddress);
+ assertEquals(InetAddress.getByAddress(testInfo.dstAddress), resultData.dstAddress);
+ assertEquals(testInfo.srcPort, resultData.srcPort);
+ assertEquals(testInfo.dstPort, resultData.dstPort);
+ assertEquals(testInfo.seq, resultData.tcpSeq);
+ assertEquals(testInfo.ack, resultData.tcpAck);
+ assertEquals(testInfo.rcvWnd, resultData.tcpWnd);
+ assertEquals(testInfo.rcvWndScale, resultData.tcpWndScale);
+ assertEquals(testInfo.tos, resultData.ipTos);
+ assertEquals(testInfo.ttl, resultData.ipTtl);
+
+ assertParcelingIsLossless(resultData);
+
+ final byte[] packet = resultData.getPacket();
+ // IP version and IHL
+ assertEquals(packet[0], 0x45);
+ // TOS
+ assertEquals(packet[1], tos);
+ // TTL
+ assertEquals(packet[8], ttl);
+ // Source IP address.
+ byte[] ip = new byte[4];
+ ByteBuffer buf = ByteBuffer.wrap(packet, 12, 4);
+ buf.get(ip);
+ assertArrayEquals(ip, IPV4_KEEPALIVE_SRC_ADDR);
+ // Destination IP address.
+ buf = ByteBuffer.wrap(packet, 16, 4);
+ buf.get(ip);
+ assertArrayEquals(ip, IPV4_KEEPALIVE_DST_ADDR);
+
+ buf = ByteBuffer.wrap(packet, 20, 12);
+ // Source port.
+ assertEquals(buf.getShort(), srcPort);
+ // Destination port.
+ assertEquals(buf.getShort(), dstPort);
+ // Sequence number.
+ assertEquals(buf.getInt(), seq);
+ // Ack.
+ assertEquals(buf.getInt(), ack);
+ // Window size.
+ buf = ByteBuffer.wrap(packet, 34, 2);
+ assertEquals(buf.getShort(), wnd >> wndScale);
+ }
+
+ //TODO: add ipv6 test when ipv6 supported
+
+ @Test
+ public void testParcel() throws Exception {
+ final int srcPort = 1234;
+ final int dstPort = 4321;
+ final int sequence = 0x11111111;
+ final int ack = 0x22222222;
+ final int wnd = 48_000;
+ final int wndScale = 2;
+ final int tos = 4;
+ final int ttl = 64;
+ final TcpKeepalivePacketDataParcelable testInfo = new TcpKeepalivePacketDataParcelable();
+ testInfo.srcAddress = IPV4_KEEPALIVE_SRC_ADDR;
+ testInfo.srcPort = srcPort;
+ testInfo.dstAddress = IPV4_KEEPALIVE_DST_ADDR;
+ testInfo.dstPort = dstPort;
+ testInfo.seq = sequence;
+ testInfo.ack = ack;
+ testInfo.rcvWnd = wnd;
+ testInfo.rcvWndScale = wndScale;
+ testInfo.tos = tos;
+ testInfo.ttl = ttl;
+ TcpKeepalivePacketData testData = null;
+ TcpKeepalivePacketDataParcelable resultData = null;
+ testData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo);
+ resultData = testData.toStableParcelable();
+ assertArrayEquals(resultData.srcAddress, IPV4_KEEPALIVE_SRC_ADDR);
+ assertArrayEquals(resultData.dstAddress, IPV4_KEEPALIVE_DST_ADDR);
+ assertEquals(resultData.srcPort, srcPort);
+ assertEquals(resultData.dstPort, dstPort);
+ assertEquals(resultData.seq, sequence);
+ assertEquals(resultData.ack, ack);
+ assertEquals(resultData.rcvWnd, wnd);
+ assertEquals(resultData.rcvWndScale, wndScale);
+ assertEquals(resultData.tos, tos);
+ assertEquals(resultData.ttl, ttl);
+ }
+}
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
new file mode 100644
index 0000000..1a3ea60
--- /dev/null
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 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.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Modifier;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ParcelableTests {
+ @Test
+ public void testNetworkAttributesParceling() throws Exception {
+ final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+ NetworkAttributes in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+ // lease will expire in two hours
+ builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
+ // groupHint stays null this time around
+ builder.setDnsAddresses(Collections.emptyList());
+ builder.setMtu(18);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9"));
+ builder.setAssignedV4AddressExpiry(System.currentTimeMillis() + 3_600_000);
+ builder.setGroupHint("groupHint");
+ builder.setDnsAddresses(Arrays.asList(
+ InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"),
+ InetAddress.getByName("6.7.8.9")));
+ builder.setMtu(1_000_000);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ builder.setMtu(null);
+ in = builder.build();
+ assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+ // Verify that this test does not miss any new field added later.
+ // If any field is added to NetworkAttributes it must be tested here for parceling
+ // roundtrip.
+ assertEquals(5, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+ .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
+ }
+
+ @Test
+ public void testPrivateDataParceling() throws Exception {
+ final Blob in = new Blob();
+ in.data = new byte[] {89, 111, 108, 111};
+ final Blob out = parcelingRoundTrip(in);
+ // Object.equals on byte[] tests the references
+ assertEquals(in.data.length, out.data.length);
+ assertTrue(Arrays.equals(in.data, out.data));
+ }
+
+ @Test
+ public void testSameL3NetworkResponseParceling() throws Exception {
+ final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+ parcelable.l2Key1 = "key 1";
+ parcelable.l2Key2 = "key 2";
+ parcelable.confidence = 0.43f;
+
+ final SameL3NetworkResponse in = new SameL3NetworkResponse(parcelable);
+ assertEquals("key 1", in.l2Key1);
+ assertEquals("key 2", in.l2Key2);
+ assertEquals(0.43f, in.confidence, 0.01f /* delta */);
+
+ final SameL3NetworkResponse out =
+ new SameL3NetworkResponse(parcelingRoundTrip(in.toParcelable()));
+
+ assertEquals(in, out);
+ assertEquals(in.l2Key1, out.l2Key1);
+ assertEquals(in.l2Key2, out.l2Key2);
+ assertEquals(in.confidence, out.confidence, 0.01f /* delta */);
+ }
+
+ private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception {
+ final Parcel p = Parcel.obtain();
+ in.writeToParcel(p, /* flags */ 0);
+ p.setDataPosition(0);
+ final byte[] marshalledData = p.marshall();
+ p.recycle();
+
+ final Parcel q = Parcel.obtain();
+ q.unmarshall(marshalledData, 0, marshalledData.length);
+ q.setDataPosition(0);
+
+ final Parcelable.Creator<T> creator = (Parcelable.Creator<T>)
+ in.getClass().getField("CREATOR").get(null); // static object, so null receiver
+ final T unmarshalled = (T) creator.createFromParcel(q);
+ q.recycle();
+ return unmarshalled;
+ }
+}
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
index 0a5a6aa..cf7587a 100644
--- a/tests/net/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -21,24 +21,25 @@
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
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 static com.android.internal.util.TestUtils.waitForIdleHandler;
-import android.os.HandlerThread;
-import android.os.Handler;
-import android.os.Looper;
import android.content.Context;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.util.AsyncChannel;
+import com.android.testutils.HandlerUtilsKt;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -46,8 +47,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.function.Consumer;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NsdManagerTest {
@@ -74,7 +73,7 @@
@After
public void tearDown() throws Exception {
- mServiceHandler.waitForIdle(mTimeoutMs);
+ HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs);
mServiceHandler.chan.disconnect();
mServiceHandler.stop();
if (mManager != null) {
@@ -334,7 +333,7 @@
}
int verifyRequest(int expectedMessageType) {
- mServiceHandler.waitForIdle(mTimeoutMs);
+ HandlerUtilsKt.waitForIdle(mServiceHandler, mTimeoutMs);
verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
reset(mServiceHandler);
Message received = mServiceHandler.getLastMessage();
@@ -366,10 +365,6 @@
lastMessage.copyFrom(msg);
}
- void waitForIdle(long timeoutMs) {
- waitForIdleHandler(this, timeoutMs);
- }
-
@Override
public void handleMessage(Message msg) {
setLastMessage(msg);
diff --git a/tests/net/java/android/net/nsd/NsdServiceInfoTest.java b/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
index e48b522..94dfc75 100644
--- a/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/net/java/android/net/nsd/NsdServiceInfoTest.java
@@ -24,20 +24,18 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.StrictMode;
-import android.net.nsd.NsdServiceInfo;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Map;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NsdServiceInfoTest {
diff --git a/tests/net/java/android/net/util/DnsUtilsTest.java b/tests/net/java/android/net/util/DnsUtilsTest.java
new file mode 100644
index 0000000..b626db8
--- /dev/null
+++ b/tests/net/java/android/net/util/DnsUtilsTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_GLOBAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_LINKLOCAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_SITELOCAL;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsUtilsTest {
+ private InetAddress stringToAddress(@NonNull String addr) {
+ return InetAddresses.parseNumericAddress(addr);
+ }
+
+ private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr) {
+ return makeSortableAddress(addr, null);
+ }
+
+ private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr,
+ @Nullable String srcAddr) {
+ return new DnsUtils.SortableAddress(stringToAddress(addr),
+ srcAddr != null ? stringToAddress(srcAddr) : null);
+ }
+
+ @Test
+ public void testRfc6724Comparator() {
+ final List<DnsUtils.SortableAddress> test = Arrays.asList(
+ // Ipv4
+ makeSortableAddress("216.58.200.36", "192.168.1.1"),
+ // global with different scope src
+ makeSortableAddress("2404:6800:4008:801::2004", "fe80::1111:2222"),
+ // global without src addr
+ makeSortableAddress("2404:6800:cafe:801::1"),
+ // loop back
+ makeSortableAddress("::1", "::1"),
+ // link local
+ makeSortableAddress("fe80::c46f:1cff:fe04:39b4", "fe80::1"),
+ // teredo tunneling
+ makeSortableAddress("2001::47c1", "2001::2"),
+ // 6bone without src addr
+ makeSortableAddress("3ffe::1234:5678"),
+ // IPv4-compatible
+ makeSortableAddress("::216.58.200.36", "::216.58.200.9"),
+ // 6bone
+ makeSortableAddress("3ffe::1234:5678", "3ffe::1234:1"),
+ // IPv4-mapped IPv6
+ makeSortableAddress("::ffff:192.168.95.7", "::ffff:192.168.95.1"));
+
+ final List<InetAddress> expected = Arrays.asList(
+ stringToAddress("::1"), // loop back
+ stringToAddress("fe80::c46f:1cff:fe04:39b4"), // link local
+ stringToAddress("216.58.200.36"), // Ipv4
+ stringToAddress("::ffff:192.168.95.7"), // IPv4-mapped IPv6
+ stringToAddress("2001::47c1"), // teredo tunneling
+ stringToAddress("::216.58.200.36"), // IPv4-compatible
+ stringToAddress("3ffe::1234:5678"), // 6bone
+ stringToAddress("2404:6800:4008:801::2004"), // global with different scope src
+ stringToAddress("2404:6800:cafe:801::1"), // global without src addr
+ stringToAddress("3ffe::1234:5678")); // 6bone without src addr
+
+ Collections.sort(test, new DnsUtils.Rfc6724Comparator());
+
+ for (int i = 0; i < test.size(); ++i) {
+ assertEquals(test.get(i).address, expected.get(i));
+ }
+
+ // TODO: add more combinations
+ }
+
+ @Test
+ public void testV4SortableAddress() {
+ // Test V4 address
+ DnsUtils.SortableAddress test = makeSortableAddress("216.58.200.36");
+ assertEquals(test.hasSrcAddr, 0);
+ assertEquals(test.prefixMatchLen, 0);
+ assertEquals(test.address, stringToAddress("216.58.200.36"));
+ assertEquals(test.labelMatch, 0);
+ assertEquals(test.scopeMatch, 0);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 4);
+ assertEquals(test.precedence, 35);
+
+ // Test V4 loopback address with the same source address
+ test = makeSortableAddress("127.1.2.3", "127.1.2.3");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.prefixMatchLen, 0);
+ assertEquals(test.address, stringToAddress("127.1.2.3"));
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 4);
+ assertEquals(test.precedence, 35);
+ }
+
+ @Test
+ public void testV6SortableAddress() {
+ // Test global address
+ DnsUtils.SortableAddress test = makeSortableAddress("2404:6800:4008:801::2004");
+ assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test global address with global source address
+ test = makeSortableAddress("2404:6800:4008:801::2004",
+ "2401:fa00:fc:fd00:6d6c:7199:b8e7:41d6");
+ assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+ assertEquals(test.prefixMatchLen, 13);
+
+ // Test global address with linklocal source address
+ test = makeSortableAddress("2404:6800:4008:801::2004", "fe80::c46f:1cff:fe04:39b4");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 0);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+ assertEquals(test.prefixMatchLen, 0);
+
+ // Test loopback address with the same source address
+ test = makeSortableAddress("::1", "::1");
+ assertEquals(test.hasSrcAddr, 1);
+ assertEquals(test.prefixMatchLen, 16 * 8);
+ assertEquals(test.labelMatch, 1);
+ assertEquals(test.scopeMatch, 1);
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 0);
+ assertEquals(test.precedence, 50);
+
+ // Test linklocal address
+ test = makeSortableAddress("fe80::c46f:1cff:fe04:39b4");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test linklocal address
+ test = makeSortableAddress("fe80::");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+ assertEquals(test.label, 1);
+ assertEquals(test.precedence, 40);
+
+ // Test 6to4 address
+ test = makeSortableAddress("2002:c000:0204::");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 2);
+ assertEquals(test.precedence, 30);
+
+ // Test unique local address
+ test = makeSortableAddress("fc00::c000:13ab");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 13);
+ assertEquals(test.precedence, 3);
+
+ // Test teredo tunneling address
+ test = makeSortableAddress("2001::47c1");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 5);
+ assertEquals(test.precedence, 5);
+
+ // Test IPv4-compatible addresses
+ test = makeSortableAddress("::216.58.200.36");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 3);
+ assertEquals(test.precedence, 1);
+
+ // Test site-local address
+ test = makeSortableAddress("fec0::cafe:3ab2");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_SITELOCAL);
+ assertEquals(test.label, 11);
+ assertEquals(test.precedence, 1);
+
+ // Test 6bone address
+ test = makeSortableAddress("3ffe::1234:5678");
+ assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+ assertEquals(test.label, 12);
+ assertEquals(test.precedence, 1);
+ }
+}
diff --git a/tests/net/java/android/net/util/KeepaliveUtilsTest.kt b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
new file mode 100644
index 0000000..8ea226d
--- /dev/null
+++ b/tests/net/java/android/net/util/KeepaliveUtilsTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2019 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.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.MAX_TRANSPORT
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+
+/**
+ * Tests for [KeepaliveUtils].
+ *
+ * Build, install and run with:
+ * atest android.net.util.KeepaliveUtilsTest
+ */
+@RunWith(JUnit4::class)
+@SmallTest
+class KeepaliveUtilsTest {
+
+ // Prepare mocked context with given resource strings.
+ private fun getMockedContextWithStringArrayRes(id: Int, res: Array<out String?>?): Context {
+ val mockRes = mock(Resources::class.java)
+ doReturn(res).`when`(mockRes).getStringArray(ArgumentMatchers.eq(id))
+
+ return mock(Context::class.java).apply {
+ doReturn(mockRes).`when`(this).getResources()
+ }
+ }
+
+ @Test
+ fun testGetSupportedKeepalives() {
+ fun assertRunWithException(res: Array<out String?>?) {
+ try {
+ val mockContext = getMockedContextWithStringArrayRes(
+ R.array.config_networkSupportedKeepaliveCount, res)
+ KeepaliveUtils.getSupportedKeepalives(mockContext)
+ fail("Expected KeepaliveDeviceConfigurationException")
+ } catch (expected: KeepaliveUtils.KeepaliveDeviceConfigurationException) {
+ }
+ }
+
+ // Check resource with various invalid format.
+ assertRunWithException(null)
+ assertRunWithException(arrayOf<String?>(null))
+ assertRunWithException(arrayOfNulls<String?>(10))
+ assertRunWithException(arrayOf(""))
+ assertRunWithException(arrayOf("3,ABC"))
+ assertRunWithException(arrayOf("6,3,3"))
+ assertRunWithException(arrayOf("5"))
+
+ // Check resource with invalid slots value.
+ assertRunWithException(arrayOf("3,-1"))
+
+ // Check resource with invalid transport type.
+ assertRunWithException(arrayOf("-1,3"))
+ assertRunWithException(arrayOf("10,3"))
+
+ // Check valid customization generates expected array.
+ val validRes = arrayOf("0,3", "1,0", "4,4")
+ val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0)
+
+ val mockContext = getMockedContextWithStringArrayRes(
+ R.array.config_networkSupportedKeepaliveCount, validRes)
+ val actual = KeepaliveUtils.getSupportedKeepalives(mockContext)
+ assertArrayEquals(expectedValidRes, actual)
+ }
+
+ @Test
+ fun testGetSupportedKeepalivesForNetworkCapabilities() {
+ // Mock customized supported keepalives for each transport type, and assuming:
+ // 3 for cellular,
+ // 6 for wifi,
+ // 0 for others.
+ val cust = IntArray(MAX_TRANSPORT + 1).apply {
+ this[TRANSPORT_CELLULAR] = 3
+ this[TRANSPORT_WIFI] = 6
+ }
+
+ val nc = NetworkCapabilities()
+ // Check supported keepalives with single transport type.
+ nc.transportTypes = intArrayOf(TRANSPORT_CELLULAR)
+ assertEquals(3, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+ // Check supported keepalives with multiple transport types.
+ nc.transportTypes = intArrayOf(TRANSPORT_WIFI, TRANSPORT_VPN)
+ assertEquals(0, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+ // Check supported keepalives with non-customized transport type.
+ nc.transportTypes = intArrayOf(TRANSPORT_ETHERNET)
+ assertEquals(0, KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc))
+
+ // Check supported keepalives with undefined transport type.
+ nc.transportTypes = intArrayOf(MAX_TRANSPORT + 1)
+ try {
+ KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(cust, nc)
+ fail("Expected ArrayIndexOutOfBoundsException")
+ } catch (expected: ArrayIndexOutOfBoundsException) {
+ }
+ }
+}
diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
deleted file mode 100644
index 788924b..0000000
--- a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2011 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.internal.net;
-
-import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
-import static android.net.NetworkStats.METERED_NO;
-import static android.net.NetworkStats.ROAMING_NO;
-import static android.net.NetworkStats.SET_ALL;
-import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.SET_FOREGROUND;
-import static android.net.NetworkStats.TAG_NONE;
-import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.content.res.Resources;
-import android.net.NetworkStats;
-import android.net.TrafficStats;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.frameworks.tests.net.R;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import org.junit.runner.RunWith;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Tests for {@link NetworkStatsFactory}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class NetworkStatsFactoryTest {
- private File mTestProc;
- private NetworkStatsFactory mFactory;
-
- @Before
- public void setUp() throws Exception {
- mTestProc = new File(InstrumentationRegistry.getContext().getFilesDir(), "proc");
- if (mTestProc.exists()) {
- IoUtils.deleteContents(mTestProc);
- }
-
- mFactory = new NetworkStatsFactory(mTestProc, false);
- }
-
- @After
- public void tearDown() throws Exception {
- mFactory = null;
-
- if (mTestProc.exists()) {
- IoUtils.deleteContents(mTestProc);
- }
- }
-
- @Test
- public void testNetworkStatsDetail() throws Exception {
- final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
-
- assertEquals(70, stats.size());
- assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
- assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
- assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
- assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
- assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
- }
-
- @Test
- public void testKernelTags() throws Exception {
- assertEquals(0, kernelToTag("0x0000000000000000"));
- assertEquals(0x32, kernelToTag("0x0000003200000000"));
- assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
- assertEquals(0, kernelToTag("0x0000000000000000"));
- assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
-
- assertEquals(0, kernelToTag("0x0"));
- assertEquals(0, kernelToTag("0xf00d"));
- assertEquals(1, kernelToTag("0x100000000"));
- assertEquals(14438007, kernelToTag("0xdc4e7700000000"));
- assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
- }
-
- @Test
- public void testNetworkStatsWithSet() throws Exception {
- final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
- assertEquals(70, stats.size());
- assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L,
- 676L);
- assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
- }
-
- @Test
- public void testNetworkStatsSingle() throws Exception {
- stageFile(R.raw.xt_qtaguid_iface_typical, file("net/xt_qtaguid/iface_stat_all"));
-
- final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
- assertEquals(6, stats.size());
- assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
- assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
- assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
- }
-
- @Test
- public void testNetworkStatsXt() throws Exception {
- stageFile(R.raw.xt_qtaguid_iface_fmt_typical, file("net/xt_qtaguid/iface_stat_fmt"));
-
- final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
- assertEquals(3, stats.size());
- assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
- assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L,
- 2468L);
- assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
- }
-
- @Test
- public void testDoubleClatAccounting() throws Exception {
- NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
-
- // xt_qtaguid_with_clat_simple is a synthetic file that simulates
- // - 213 received 464xlat packets of size 200 bytes
- // - 41 sent 464xlat packets of size 100 bytes
- // - no other traffic on base interface for root uid.
- NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_simple);
- assertEquals(4, stats.size());
-
- assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 46860L, 4920L);
- assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L);
-
- stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat);
- assertEquals(42, stats.size());
-
- assertStatsEntry(stats, "v4-wlan0", 0, SET_DEFAULT, 0x0, 356L, 276L);
- assertStatsEntry(stats, "v4-wlan0", 1000, SET_DEFAULT, 0x0, 30812L, 2310L);
- assertStatsEntry(stats, "v4-wlan0", 10102, SET_DEFAULT, 0x0, 10022L, 3330L);
- assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 9532772L, 254112L);
- assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 15229L, 5766L);
- assertStatsEntry(stats, "wlan0", 1000, SET_DEFAULT, 0x0, 6126L, 2013L);
- assertStatsEntry(stats, "wlan0", 10013, SET_DEFAULT, 0x0, 0L, 144L);
- assertStatsEntry(stats, "wlan0", 10018, SET_DEFAULT, 0x0, 5980263L, 167667L);
- assertStatsEntry(stats, "wlan0", 10060, SET_DEFAULT, 0x0, 134356L, 8705L);
- assertStatsEntry(stats, "wlan0", 10079, SET_DEFAULT, 0x0, 10926L, 1507L);
- assertStatsEntry(stats, "wlan0", 10102, SET_DEFAULT, 0x0, 25038L, 8245L);
- assertStatsEntry(stats, "wlan0", 10103, SET_DEFAULT, 0x0, 0L, 192L);
- assertStatsEntry(stats, "dummy0", 0, SET_DEFAULT, 0x0, 0L, 168L);
- assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L);
-
- NetworkStatsFactory.clearStackedIfaces();
- }
-
- @Test
- public void testDoubleClatAccounting100MBDownload() throws Exception {
- // Downloading 100mb from an ipv4 only destination in a foreground activity
-
- long appRxBytesBefore = 328684029L;
- long appRxBytesAfter = 439237478L;
- assertEquals("App traffic should be ~100MB", 110553449, appRxBytesAfter - appRxBytesBefore);
-
- long rootRxBytesBefore = 1394011L;
- long rootRxBytesAfter = 1398634L;
- assertEquals("UID 0 traffic should be ~0", 4623, rootRxBytesAfter - rootRxBytesBefore);
-
- NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
- NetworkStats stats;
-
- // Stats snapshot before the download
- stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_before);
- assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesBefore, 5199872L);
- assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesBefore, 647888L);
-
- // Stats snapshot after the download
- stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_after);
- assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L);
- assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesAfter, 647587L);
-
- NetworkStatsFactory.clearStackedIfaces();
- }
-
- /**
- * Copy a {@link Resources#openRawResource(int)} into {@link File} for
- * testing purposes.
- */
- private void stageFile(int rawId, File file) throws Exception {
- new File(file.getParent()).mkdirs();
- InputStream in = null;
- OutputStream out = null;
- try {
- in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
- out = new FileOutputStream(file);
- Streams.copy(in, out);
- } finally {
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(out);
- }
- }
-
- private void stageLong(long value, File file) throws Exception {
- new File(file.getParent()).mkdirs();
- FileWriter out = null;
- try {
- out = new FileWriter(file);
- out.write(Long.toString(value));
- } finally {
- IoUtils.closeQuietly(out);
- }
- }
-
- private File file(String path) throws Exception {
- return new File(mTestProc, path);
- }
-
- private NetworkStats parseDetailedStats(int resourceId) throws Exception {
- stageFile(resourceId, file("net/xt_qtaguid/stats"));
- return mFactory.readNetworkStatsDetail();
- }
-
- private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
- int tag, long rxBytes, long txBytes) {
- final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO);
- if (i < 0) {
- fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
- iface, uid, set, tag));
- }
- final NetworkStats.Entry entry = stats.getValues(i, null);
- assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
- assertEquals("unexpected txBytes", txBytes, entry.txBytes);
- }
-
- private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
- int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
- final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO);
- if (i < 0) {
- fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
- iface, uid, set, tag));
- }
- final NetworkStats.Entry entry = stats.getValues(i, null);
- assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
- assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
- assertEquals("unexpected txBytes", txBytes, entry.txBytes);
- assertEquals("unexpected txPackets", txPackets, entry.txPackets);
- }
-}
diff --git a/tests/net/java/com/android/internal/util/BitUtilsTest.java b/tests/net/java/com/android/internal/util/BitUtilsTest.java
index f4dc12a..01fb0df 100644
--- a/tests/net/java/com/android/internal/util/BitUtilsTest.java
+++ b/tests/net/java/com/android/internal/util/BitUtilsTest.java
@@ -16,14 +16,24 @@
package com.android.internal.util;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import java.nio.ByteBuffer;
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.internal.util.BitUtils.bytesToLEInt;
+import static com.android.internal.util.BitUtils.getUint16;
+import static com.android.internal.util.BitUtils.getUint32;
+import static com.android.internal.util.BitUtils.getUint8;
+import static com.android.internal.util.BitUtils.uint16;
+import static com.android.internal.util.BitUtils.uint32;
+import static com.android.internal.util.BitUtils.uint8;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.assertEquals;
-import static com.android.internal.util.BitUtils.*;
+import java.nio.ByteBuffer;
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/tests/net/java/com/android/internal/util/RingBufferTest.java b/tests/net/java/com/android/internal/util/RingBufferTest.java
index 90a373a..d06095a 100644
--- a/tests/net/java/com/android/internal/util/RingBufferTest.java
+++ b/tests/net/java/com/android/internal/util/RingBufferTest.java
@@ -16,18 +16,15 @@
package com.android.internal.util;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Arrays;
-import java.util.Objects;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -37,7 +34,7 @@
public void testEmptyRingBuffer() {
RingBuffer<String> buffer = new RingBuffer<>(String.class, 100);
- assertArraysEqual(new String[0], buffer.toArray());
+ assertArrayEquals(new String[0], buffer.toArray());
}
@Test
@@ -66,7 +63,7 @@
buffer.append("e");
String[] expected = {"a", "b", "c", "d", "e"};
- assertArraysEqual(expected, buffer.toArray());
+ assertArrayEquals(expected, buffer.toArray());
}
@Test
@@ -74,19 +71,19 @@
RingBuffer<String> buffer = new RingBuffer<>(String.class, 1);
buffer.append("a");
- assertArraysEqual(new String[]{"a"}, buffer.toArray());
+ assertArrayEquals(new String[]{"a"}, buffer.toArray());
buffer.append("b");
- assertArraysEqual(new String[]{"b"}, buffer.toArray());
+ assertArrayEquals(new String[]{"b"}, buffer.toArray());
buffer.append("c");
- assertArraysEqual(new String[]{"c"}, buffer.toArray());
+ assertArrayEquals(new String[]{"c"}, buffer.toArray());
buffer.append("d");
- assertArraysEqual(new String[]{"d"}, buffer.toArray());
+ assertArrayEquals(new String[]{"d"}, buffer.toArray());
buffer.append("e");
- assertArraysEqual(new String[]{"e"}, buffer.toArray());
+ assertArrayEquals(new String[]{"e"}, buffer.toArray());
}
@Test
@@ -101,7 +98,7 @@
buffer.append("e");
String[] expected1 = {"a", "b", "c", "d", "e"};
- assertArraysEqual(expected1, buffer.toArray());
+ assertArrayEquals(expected1, buffer.toArray());
String[] expected2 = new String[capacity];
int firstIndex = 0;
@@ -112,22 +109,22 @@
buffer.append("x");
expected2[i] = "x";
}
- assertArraysEqual(expected2, buffer.toArray());
+ assertArrayEquals(expected2, buffer.toArray());
buffer.append("x");
expected2[firstIndex] = "x";
- assertArraysEqual(expected2, buffer.toArray());
+ assertArrayEquals(expected2, buffer.toArray());
for (int i = 0; i < 10; i++) {
for (String s : expected2) {
buffer.append(s);
}
}
- assertArraysEqual(expected2, buffer.toArray());
+ assertArrayEquals(expected2, buffer.toArray());
buffer.append("a");
expected2[lastIndex] = "a";
- assertArraysEqual(expected2, buffer.toArray());
+ assertArrayEquals(expected2, buffer.toArray());
}
@Test
@@ -144,7 +141,7 @@
expected[i] = new DummyClass1();
expected[i].x = capacity * i;
}
- assertArraysEqual(expected, buffer.toArray());
+ assertArrayEquals(expected, buffer.toArray());
for (int i = 0; i < capacity; ++i) {
if (actual[i] != buffer.getNextSlot()) {
@@ -178,18 +175,4 @@
}
private static final class DummyClass3 {}
-
- static <T> void assertArraysEqual(T[] expected, T[] got) {
- if (expected.length != got.length) {
- fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
- + " did not have the same length");
- }
-
- for (int i = 0; i < expected.length; i++) {
- if (!Objects.equals(expected[i], got[i])) {
- fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
- + " were not equal");
- }
- }
- }
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index e6a889b..e08cb16 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -16,17 +16,27 @@
package com.android.server;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
-import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -41,6 +51,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
@@ -52,51 +63,78 @@
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.net.NetworkPolicyManager.RULE_ALLOW_METERED;
+import static android.net.NetworkPolicyManager.RULE_NONE;
+import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
-import static com.android.internal.util.TestUtils.waitForIdleHandler;
-import static com.android.internal.util.TestUtils.waitForIdleLooper;
+import static com.android.testutils.ConcurrentUtilsKt.await;
+import static com.android.testutils.ConcurrentUtilsKt.durationOf;
+import static com.android.testutils.ExceptionUtils.ignoreExceptions;
+import static com.android.testutils.HandlerUtilsKt.waitForIdleSerialExecutor;
+import static com.android.testutils.MiscAssertsKt.assertContainsExactly;
+import static com.android.testutils.MiscAssertsKt.assertEmpty;
+import static com.android.testutils.MiscAssertsKt.assertLength;
+import static com.android.testutils.MiscAssertsKt.assertRunsInAtMost;
+import static com.android.testutils.MiscAssertsKt.assertThrows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
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.Matchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+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.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-
+import android.annotation.NonNull;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.net.CaptivePortal;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.ConnectivityManager.PacketKeepaliveCallback;
import android.net.ConnectivityManager.TooManyRequestsException;
import android.net.ConnectivityThread;
+import android.net.IDnsResolver;
+import android.net.INetd;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
+import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MatchAllNetworkSpecifier;
@@ -109,15 +147,22 @@
import android.net.NetworkMisc;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
+import android.net.NetworkStackClient;
import android.net.NetworkState;
import android.net.NetworkUtils;
+import android.net.ProxyInfo;
+import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
-import android.net.StringNetworkSpecifier;
+import android.net.SocketKeepalive;
import android.net.UidRange;
-import android.net.VpnService;
-import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
+import android.os.BadParcelableException;
+import android.os.Binder;
+import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -125,17 +170,24 @@
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import android.system.Os;
import android.test.mock.MockContentResolver;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnInfo;
@@ -145,14 +197,19 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.connectivity.ConnectivityConstants;
import com.android.server.connectivity.DefaultNetworkMetrics;
-import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
-import com.android.server.connectivity.NetworkAgentInfo;
-import com.android.server.connectivity.NetworkMonitor;
+import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import com.android.server.connectivity.ProxyTracker;
+import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
import com.android.server.net.NetworkPinner;
import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.testutils.ExceptionUtils;
+import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.RecorderCallback.CallbackRecord;
+import com.android.testutils.TestableNetworkCallback;
import org.junit.After;
import org.junit.Before;
@@ -160,24 +217,35 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Predicate;
+import kotlin.reflect.KClass;
/**
* Tests for {@link ConnectivityService}.
@@ -191,10 +259,21 @@
private static final String TAG = "ConnectivityServiceTest";
private static final int TIMEOUT_MS = 500;
- private static final int TEST_LINGER_DELAY_MS = 120;
+ private static final int TEST_LINGER_DELAY_MS = 250;
+ // Chosen to be less than the linger timeout. This ensures that we can distinguish between a
+ // LOST callback that arrives immediately and a LOST callback that arrives after the linger
+ // timeout. For this, our assertions should run fast enough to leave less than
+ // (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are
+ // supposedly fired, and the time we call expectCallback.
+ private final static int TEST_CALLBACK_TIMEOUT_MS = 200;
+ // Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to
+ // complete before callbacks are verified.
+ private final static int TEST_REQUEST_TIMEOUT_MS = 150;
+ private static final String CLAT_PREFIX = "v4-";
private static final String MOBILE_IFNAME = "test_rmnet_data0";
private static final String WIFI_IFNAME = "test_wlan0";
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
private MockContext mServiceContext;
private WrappedConnectivityService mService;
@@ -204,13 +283,22 @@
private MockNetworkAgent mEthernetNetworkAgent;
private MockVpn mMockVpn;
private Context mContext;
+ private INetworkPolicyListener mPolicyListener;
@Mock IpConnectivityMetrics.Logger mMetricsService;
@Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
@Mock INetworkManagementService mNetworkManagementService;
@Mock INetworkStatsService mStatsService;
+ @Mock INetworkPolicyManager mNpm;
+ @Mock IDnsResolver mMockDnsResolver;
+ @Mock INetd mMockNetd;
+ @Mock NetworkStackClient mNetworkStack;
+ @Mock PackageManager mPackageManager;
+ @Mock UserManager mUserManager;
+ @Mock NotificationManager mNotificationManager;
- private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class);
+ private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
+ ArgumentCaptor.forClass(ResolverParamsParcel.class);
// This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods
// do not go through ConnectivityService but talk to netd directly, so they don't automatically
@@ -238,7 +326,7 @@
@Spy private Resources mResources;
private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();
- MockContext(Context base) {
+ MockContext(Context base, ContentProvider settingsProvider) {
super(base);
mResources = spy(base.getResources());
@@ -250,7 +338,7 @@
});
mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ mContentResolver.addProvider(Settings.AUTHORITY, settingsProvider);
}
@Override
@@ -277,7 +365,9 @@
@Override
public Object getSystemService(String name) {
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
- if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
+ if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager;
+ if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
+ if (Context.USER_SERVICE.equals(name)) return mUserManager;
return super.getSystemService(name);
}
@@ -290,23 +380,38 @@
public Resources getResources() {
return mResources;
}
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ // The mainline permission can only be held if signed with the network stack certificate
+ // Skip testing for this permission.
+ if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return;
+ // All other permissions should be held by the test or unnecessary: check as normal to
+ // make sure the code does not rely on unexpected permissions.
+ super.enforceCallingOrSelfPermission(permission, message);
+ }
}
public void waitForIdle(int timeoutMsAsInt) {
long timeoutMs = timeoutMsAsInt;
- waitForIdleHandler(mService.mHandlerThread, timeoutMs);
+ HandlerUtilsKt.waitForIdle(mService.mHandlerThread, timeoutMs);
waitForIdle(mCellNetworkAgent, timeoutMs);
waitForIdle(mWiFiNetworkAgent, timeoutMs);
waitForIdle(mEthernetNetworkAgent, timeoutMs);
- waitForIdleHandler(mService.mHandlerThread, timeoutMs);
- waitForIdleLooper(ConnectivityThread.getInstanceLooper(), timeoutMs);
+ HandlerUtilsKt.waitForIdle(mService.mHandlerThread, timeoutMs);
+ HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), timeoutMs);
}
public void waitForIdle(MockNetworkAgent agent, long timeoutMs) {
if (agent == null) {
return;
}
- waitForIdleHandler(agent.mHandlerThread, timeoutMs);
+ HandlerUtilsKt.waitForIdle(agent.mHandlerThread, timeoutMs);
}
private void waitForIdle() {
@@ -314,7 +419,7 @@
}
@Test
- public void testWaitForIdle() {
+ public void testWaitForIdle() throws Exception {
final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng.
// Tests that waitForIdle returns immediately if the service is already idle.
@@ -341,7 +446,7 @@
// This test has an inherent race condition in it, and cannot be enabled for continuous testing
// or presubmit tests. It is kept for manual runs and documentation purposes.
@Ignore
- public void verifyThatNotWaitingForIdleCausesRaceConditions() {
+ public void verifyThatNotWaitingForIdleCausesRaceConditions() throws Exception {
// Bring up a network that we can use to send messages to ConnectivityService.
ConditionVariable cv = waitForConnectivityBroadcasts(1);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -364,8 +469,18 @@
fail("expected race condition at least once in " + attempts + " attempts");
}
- private class MockNetworkAgent {
- private final WrappedNetworkMonitor mWrappedNetworkMonitor;
+ private class MockNetworkAgent implements TestableNetworkCallback.HasNetwork {
+ private static final int VALIDATION_RESULT_BASE = NETWORK_VALIDATION_PROBE_DNS
+ | NETWORK_VALIDATION_PROBE_HTTP
+ | NETWORK_VALIDATION_PROBE_HTTPS;
+ private static final int VALIDATION_RESULT_VALID = VALIDATION_RESULT_BASE
+ | NETWORK_VALIDATION_RESULT_VALID;
+ private static final int VALIDATION_RESULT_PARTIAL = VALIDATION_RESULT_BASE
+ | NETWORK_VALIDATION_PROBE_FALLBACK
+ | NETWORK_VALIDATION_RESULT_PARTIAL;
+ private static final int VALIDATION_RESULT_INVALID = 0;
+
+ private final INetworkMonitor mNetworkMonitor;
private final NetworkInfo mNetworkInfo;
private final NetworkCapabilities mNetworkCapabilities;
private final HandlerThread mHandlerThread;
@@ -374,20 +489,50 @@
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
private int mScore;
private NetworkAgent mNetworkAgent;
- private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
- private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE;
+ private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
+ private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
private Integer mExpectedKeepaliveSlot = null;
// Contains the redirectUrl from networkStatus(). Before reading, wait for
// mNetworkStatusReceived.
private String mRedirectUrl;
- MockNetworkAgent(int transport) {
+ private INetworkMonitorCallbacks mNmCallbacks;
+ private int mNmValidationResult = VALIDATION_RESULT_BASE;
+ private String mNmValidationRedirectUrl = null;
+ private boolean mNmProvNotificationRequested = false;
+
+ void setNetworkValid() {
+ mNmValidationResult = VALIDATION_RESULT_VALID;
+ mNmValidationRedirectUrl = null;
+ }
+
+ void setNetworkInvalid() {
+ mNmValidationResult = VALIDATION_RESULT_INVALID;
+ mNmValidationRedirectUrl = null;
+ }
+
+ void setNetworkPortal(String redirectUrl) {
+ setNetworkInvalid();
+ mNmValidationRedirectUrl = redirectUrl;
+ }
+
+ void setNetworkPartial() {
+ mNmValidationResult = VALIDATION_RESULT_PARTIAL;
+ mNmValidationRedirectUrl = null;
+ }
+
+ void setNetworkPartialValid() {
+ mNmValidationResult = VALIDATION_RESULT_PARTIAL | VALIDATION_RESULT_VALID;
+ mNmValidationRedirectUrl = null;
+ }
+
+ MockNetworkAgent(int transport) throws Exception {
this(transport, new LinkProperties());
}
- MockNetworkAgent(int transport, LinkProperties linkProperties) {
+ MockNetworkAgent(int transport, LinkProperties linkProperties) throws Exception {
final int type = transportToLegacyType(transport);
- final String typeName = ConnectivityManager.getNetworkTypeName(transport);
+ final String typeName = ConnectivityManager.getNetworkTypeName(type);
mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.addTransportType(transport);
@@ -413,24 +558,42 @@
}
mHandlerThread = new HandlerThread("Mock-" + typeName);
mHandlerThread.start();
+
+ mNetworkMonitor = mock(INetworkMonitor.class);
+ final Answer validateAnswer = inv -> {
+ new Thread(ignoreExceptions(this::onValidationRequested)).start();
+ return null;
+ };
+
+ doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any());
+ doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
+
+ final ArgumentCaptor<Network> nmNetworkCaptor = ArgumentCaptor.forClass(Network.class);
+ final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor =
+ ArgumentCaptor.forClass(INetworkMonitorCallbacks.class);
+ doNothing().when(mNetworkStack).makeNetworkMonitor(
+ nmNetworkCaptor.capture(),
+ any() /* name */,
+ nmCbCaptor.capture());
+
mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
"Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
- linkProperties, mScore, new NetworkMisc()) {
+ linkProperties, mScore, new NetworkMisc(), NetworkFactory.SerialNumber.NONE) {
@Override
public void unwanted() { mDisconnected.open(); }
@Override
- public void startPacketKeepalive(Message msg) {
+ public void startSocketKeepalive(Message msg) {
int slot = msg.arg1;
if (mExpectedKeepaliveSlot != null) {
assertEquals((int) mExpectedKeepaliveSlot, slot);
}
- onPacketKeepaliveEvent(slot, mStartKeepaliveError);
+ onSocketKeepaliveEvent(slot, mStartKeepaliveError);
}
@Override
- public void stopPacketKeepalive(Message msg) {
- onPacketKeepaliveEvent(msg.arg1, mStopKeepaliveError);
+ public void stopSocketKeepalive(Message msg) {
+ onSocketKeepaliveEvent(msg.arg1, mStopKeepaliveError);
}
@Override
@@ -443,11 +606,43 @@
protected void preventAutomaticReconnect() {
mPreventReconnectReceived.open();
}
+
+ @Override
+ protected void addKeepalivePacketFilter(Message msg) {
+ Log.i(TAG, "Add keepalive packet filter.");
+ }
+
+ @Override
+ protected void removeKeepalivePacketFilter(Message msg) {
+ Log.i(TAG, "Remove keepalive packet filter.");
+ }
};
+
+ assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId);
+ mNmCallbacks = nmCbCaptor.getValue();
+
+ mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
+
// Waits for the NetworkAgent to be registered, which includes the creation of the
// NetworkMonitor.
waitForIdle();
- mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
+ }
+
+ private void onValidationRequested() throws Exception {
+ if (mNmProvNotificationRequested
+ && ((mNmValidationResult & NETWORK_VALIDATION_RESULT_VALID) != 0)) {
+ mNmCallbacks.hideProvisioningNotification();
+ mNmProvNotificationRequested = false;
+ }
+
+ mNmCallbacks.notifyNetworkTested(
+ mNmValidationResult, mNmValidationRedirectUrl);
+
+ if (mNmValidationRedirectUrl != null) {
+ mNmCallbacks.showProvisioningNotification(
+ "test_provisioning_notif_action", "com.android.test.package");
+ mNmProvNotificationRequested = true;
+ }
}
public void adjustScore(int change) {
@@ -455,8 +650,12 @@
mNetworkAgent.sendNetworkScore(mScore);
}
- public void explicitlySelected(boolean acceptUnvalidated) {
- mNetworkAgent.explicitlySelected(acceptUnvalidated);
+ public int getScore() {
+ return mScore;
+ }
+
+ public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
+ mNetworkAgent.explicitlySelected(explicitlySelected, acceptUnvalidated);
}
public void addCapability(int capability) {
@@ -518,7 +717,7 @@
NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
if (validated) {
- mWrappedNetworkMonitor.gen204ProbeResult = 204;
+ setNetworkValid();
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(mNetworkCapabilities.getTransportTypes()[0])
.clearCapabilities()
@@ -543,15 +742,24 @@
if (validated) {
// Wait for network to validate.
waitFor(validatedCv);
- mWrappedNetworkMonitor.gen204ProbeResult = 500;
+ setNetworkInvalid();
}
if (callback != null) mCm.unregisterNetworkCallback(callback);
}
public void connectWithCaptivePortal(String redirectUrl) {
- mWrappedNetworkMonitor.gen204ProbeResult = 200;
- mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl;
+ setNetworkPortal(redirectUrl);
+ connect(false);
+ }
+
+ public void connectWithPartialConnectivity() {
+ setNetworkPartial();
+ connect(false);
+ }
+
+ public void connectWithPartialValidConnectivity() {
+ setNetworkPartialValid();
connect(false);
}
@@ -582,10 +790,6 @@
return mDisconnected;
}
- public WrappedNetworkMonitor getWrappedNetworkMonitor() {
- return mWrappedNetworkMonitor;
- }
-
public void sendLinkProperties(LinkProperties lp) {
mNetworkAgent.sendLinkProperties(lp);
}
@@ -619,7 +823,7 @@
/**
* A NetworkFactory that allows tests to wait until any in-flight NetworkRequest add or remove
* operations have been processed. Before ConnectivityService can add or remove any requests,
- * the factory must be told to expect those operations by calling expectAddRequests or
+ * the factory must be told to expect those operations by calling expectAddRequestsWithScores or
* expectRemoveRequests.
*/
private static class MockNetworkFactory extends NetworkFactory {
@@ -628,19 +832,26 @@
private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
// Used to expect that requests be removed or added on a separate thread, without sleeping.
- // Callers can call either expectAddRequests() or expectRemoveRequests() exactly once, then
- // cause some other thread to add or remove requests, then call waitForRequests(). We can
- // either expect requests to be added or removed, but not both, because CountDownLatch can
- // only count in one direction.
- private CountDownLatch mExpectations;
+ // Callers can call either expectAddRequestsWithScores() or expectRemoveRequests() exactly
+ // once, then cause some other thread to add or remove requests, then call
+ // waitForRequests().
+ // It is not possible to wait for both add and remove requests. When adding, the queue
+ // contains the expected score. When removing, the value is unused, all matters is the
+ // number of objects in the queue.
+ private final LinkedBlockingQueue<Integer> mExpectations;
// Whether we are currently expecting requests to be added or removed. Valid only if
- // mExpectations is non-null.
+ // mExpectations is non-empty.
private boolean mExpectingAdditions;
+ // Used to collect the networks requests managed by this factory. This is a duplicate of
+ // the internal information stored in the NetworkFactory (which is private).
+ private SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>();
+
public MockNetworkFactory(Looper looper, Context context, String logTag,
NetworkCapabilities filter) {
super(looper, context, logTag, filter);
+ mExpectations = new LinkedBlockingQueue<>();
}
public int getMyRequestCount() {
@@ -672,74 +883,96 @@
}
@Override
- protected void handleAddRequest(NetworkRequest request, int score) {
- // If we're expecting anything, we must be expecting additions.
- if (mExpectations != null && !mExpectingAdditions) {
- fail("Can't add requests while expecting requests to be removed");
- }
+ protected void handleAddRequest(NetworkRequest request, int score,
+ int factorySerialNumber) {
+ synchronized (mExpectations) {
+ final Integer expectedScore = mExpectations.poll(); // null if the queue is empty
- // Add the request.
- super.handleAddRequest(request, score);
+ assertNotNull("Added more requests than expected (" + request + " score : "
+ + score + ")", expectedScore);
+ // If we're expecting anything, we must be expecting additions.
+ if (!mExpectingAdditions) {
+ fail("Can't add requests while expecting requests to be removed");
+ }
+ if (expectedScore != score) {
+ fail("Expected score was " + expectedScore + " but actual was " + score
+ + " in added request");
+ }
- // Reduce the number of request additions we're waiting for.
- if (mExpectingAdditions) {
- assertTrue("Added more requests than expected", mExpectations.getCount() > 0);
- mExpectations.countDown();
+ // Add the request.
+ mNetworkRequests.put(request.requestId, request);
+ super.handleAddRequest(request, score, factorySerialNumber);
+ mExpectations.notify();
}
}
@Override
protected void handleRemoveRequest(NetworkRequest request) {
- // If we're expecting anything, we must be expecting removals.
- if (mExpectations != null && mExpectingAdditions) {
- fail("Can't remove requests while expecting requests to be added");
- }
+ synchronized (mExpectations) {
+ final Integer expectedScore = mExpectations.poll(); // null if the queue is empty
- // Remove the request.
- super.handleRemoveRequest(request);
+ assertTrue("Removed more requests than expected", expectedScore != null);
+ // If we're expecting anything, we must be expecting removals.
+ if (mExpectingAdditions) {
+ fail("Can't remove requests while expecting requests to be added");
+ }
- // Reduce the number of request removals we're waiting for.
- if (!mExpectingAdditions) {
- assertTrue("Removed more requests than expected", mExpectations.getCount() > 0);
- mExpectations.countDown();
+ // Remove the request.
+ mNetworkRequests.remove(request.requestId);
+ super.handleRemoveRequest(request);
+ mExpectations.notify();
}
}
+ // Trigger releasing the request as unfulfillable
+ public void triggerUnfulfillable(NetworkRequest r) {
+ super.releaseRequestAsUnfulfillableByAnyFactory(r);
+ }
+
private void assertNoExpectations() {
- if (mExpectations != null) {
- fail("Can't add expectation, " + mExpectations.getCount() + " already pending");
+ if (mExpectations.size() != 0) {
+ fail("Can't add expectation, " + mExpectations.size() + " already pending");
}
}
- // Expects that count requests will be added.
- public void expectAddRequests(final int count) {
+ // Expects that requests with the specified scores will be added.
+ public void expectAddRequestsWithScores(final int... scores) {
assertNoExpectations();
mExpectingAdditions = true;
- mExpectations = new CountDownLatch(count);
+ for (int score : scores) {
+ mExpectations.add(score);
+ }
}
// Expects that count requests will be removed.
public void expectRemoveRequests(final int count) {
assertNoExpectations();
mExpectingAdditions = false;
- mExpectations = new CountDownLatch(count);
+ for (int i = 0; i < count; ++i) {
+ mExpectations.add(0); // For removals the score is ignored so any value will do.
+ }
}
// Waits for the expected request additions or removals to happen within a timeout.
public void waitForRequests() throws InterruptedException {
- assertNotNull("Nothing to wait for", mExpectations);
- mExpectations.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- final long count = mExpectations.getCount();
+ final long deadline = SystemClock.elapsedRealtime() + TIMEOUT_MS;
+ synchronized (mExpectations) {
+ while (mExpectations.size() > 0 && SystemClock.elapsedRealtime() < deadline) {
+ mExpectations.wait(deadline - SystemClock.elapsedRealtime());
+ }
+ }
+ final long count = mExpectations.size();
final String msg = count + " requests still not " +
(mExpectingAdditions ? "added" : "removed") +
" after " + TIMEOUT_MS + " ms";
assertEquals(msg, 0, count);
- mExpectations = null;
}
- public void waitForNetworkRequests(final int count) throws InterruptedException {
+ public SparseArray<NetworkRequest> waitForNetworkRequests(final int count)
+ throws InterruptedException {
waitForRequests();
assertEquals(count, getMyRequestCount());
+ return mNetworkRequests;
}
}
@@ -799,10 +1032,19 @@
return mConnected; // Similar trickery
}
- public void connect() {
+ private void connect(boolean isAlwaysMetered) {
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
mConnected = true;
mConfig = new VpnConfig();
+ mConfig.isMetered = isAlwaysMetered;
+ }
+
+ public void connectAsAlwaysMetered() {
+ connect(true /* isAlwaysMetered */);
+ }
+
+ public void connect() {
+ connect(false /* isAlwaysMetered */);
}
@Override
@@ -863,28 +1105,6 @@
}
}
- // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
- private class WrappedNetworkMonitor extends NetworkMonitor {
- public final Handler connectivityHandler;
- // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
- public int gen204ProbeResult = 500;
- public String gen204ProbeRedirectUrl = null;
-
- public WrappedNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
- IpConnectivityLog log) {
- super(context, handler, networkAgentInfo, defaultRequest, log,
- NetworkMonitor.NetworkMonitorSettings.DEFAULT);
- connectivityHandler = handler;
- }
-
- @Override
- protected CaptivePortalProbeResult isCaptivePortal() {
- if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
- return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
- }
- }
-
private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
public volatile boolean configRestrictsAvoidBadWifi;
public volatile int configMeteredMultipathPreference;
@@ -906,13 +1126,13 @@
private class WrappedConnectivityService extends ConnectivityService {
public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
- private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
private MockableSystemProperties mSystemProperties;
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
- IpConnectivityLog log) {
- super(context, netManager, statsService, policyManager, log);
+ IpConnectivityLog log, INetd netd, IDnsResolver dnsResolver) {
+ super(context, netManager, statsService, policyManager, dnsResolver, log, netd);
+ mNetd = netd;
mLingerDelayMs = TEST_LINGER_DELAY_MS;
}
@@ -927,6 +1147,16 @@
}
@Override
+ protected Tethering makeTethering() {
+ return mock(Tethering.class);
+ }
+
+ @Override
+ protected ProxyTracker makeProxyTracker() {
+ return mock(ProxyTracker.class);
+ }
+
+ @Override
protected int reserveNetId() {
while (true) {
final int netId = super.reserveNetId();
@@ -949,12 +1179,12 @@
}
@Override
- public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
- NetworkAgentInfo nai, NetworkRequest defaultRequest) {
- final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(
- context, handler, nai, defaultRequest, mock(IpConnectivityLog.class));
- mLastCreatedNetworkMonitor = monitor;
- return monitor;
+ protected boolean queryUserAccess(int uid, int netId) {
+ return true;
+ }
+
+ public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
+ return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
}
@Override
@@ -969,6 +1199,11 @@
}
@Override
+ protected NetworkStackClient getNetworkStack() {
+ return mNetworkStack;
+ }
+
+ @Override
public WakeupMessage makeWakeupMessage(
Context context, Handler handler, String cmdName, int cmd, Object obj) {
return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
@@ -990,10 +1225,6 @@
protected void registerNetdEventCallback() {
}
- public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
- return mLastCreatedNetworkMonitor;
- }
-
public void mockVpn(int uid) {
synchronized (mVpns) {
int userId = UserHandle.getUserId(uid);
@@ -1006,12 +1237,21 @@
}
public void waitForIdle(int timeoutMs) {
- waitForIdleHandler(mHandlerThread, timeoutMs);
+ HandlerUtilsKt.waitForIdle(mHandlerThread, timeoutMs);
}
public void waitForIdle() {
waitForIdle(TIMEOUT_MS);
}
+
+ public void setUidRulesChanged(int uidRules) throws RemoteException {
+ mPolicyListener.onUidRulesChanged(Process.myUid(), uidRules);
+ }
+
+ public void setRestrictBackgroundChanged(boolean restrictBackground)
+ throws RemoteException {
+ mPolicyListener.onRestrictBackgroundChanged(restrictBackground);
+ }
}
/**
@@ -1025,6 +1265,11 @@
fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
}
+ private static final int VPN_USER = 0;
+ private static final int APP1_UID = UserHandle.getUid(VPN_USER, 10100);
+ private static final int APP2_UID = UserHandle.getUid(VPN_USER, 10101);
+ private static final int VPN_UID = UserHandle.getUid(VPN_USER, 10043);
+
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
@@ -1032,21 +1277,37 @@
MockitoAnnotations.initMocks(this);
when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
+ when(mUserManager.getUsers(eq(true))).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(VPN_USER, "", 0),
+ }));
+
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 .
if (Looper.myLooper() == null) {
Looper.prepare();
}
+ mockDefaultPackages();
- mServiceContext = new MockContext(InstrumentationRegistry.getContext());
+ FakeSettingsProvider.clearSettingsProvider();
+ mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
+ new FakeSettingsProvider());
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
LocalServices.addService(
NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
+
mService = new WrappedConnectivityService(mServiceContext,
mNetworkManagementService,
mStatsService,
- mock(INetworkPolicyManager.class),
- mock(IpConnectivityLog.class));
+ mNpm,
+ mock(IpConnectivityLog.class),
+ mMockNetd,
+ mMockDnsResolver);
+
+ final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor =
+ ArgumentCaptor.forClass(INetworkPolicyListener.class);
+ verify(mNpm).registerListener(policyListenerCaptor.capture());
+ mPolicyListener = policyListenerCaptor.getValue();
// Create local CM before sending system ready so that we can answer
// getSystemService() correctly.
@@ -1057,13 +1318,13 @@
// Ensure that the default setting for Captive Portals is used for most tests
setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
- setMobileDataAlwaysOn(false);
+ setAlwaysOnNetworks(false);
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
}
@After
public void tearDown() throws Exception {
- setMobileDataAlwaysOn(false);
+ setAlwaysOnNetworks(false);
if (mCellNetworkAgent != null) {
mCellNetworkAgent.disconnect();
mCellNetworkAgent = null;
@@ -1076,9 +1337,27 @@
mEthernetNetworkAgent.disconnect();
mEthernetNetworkAgent = null;
}
+ FakeSettingsProvider.clearSettingsProvider();
}
- private static int transportToLegacyType(int transport) {
+ private void mockDefaultPackages() throws Exception {
+ final String testPackageName = mContext.getPackageName();
+ final PackageInfo testPackageInfo = mContext.getPackageManager().getPackageInfo(
+ testPackageName, PackageManager.GET_PERMISSIONS);
+ when(mPackageManager.getPackagesForUid(Binder.getCallingUid())).thenReturn(
+ new String[] {testPackageName});
+ when(mPackageManager.getPackageInfoAsUser(eq(testPackageName), anyInt(),
+ eq(UserHandle.getCallingUserId()))).thenReturn(testPackageInfo);
+
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(/* SYSTEM */ false, APP1_UID),
+ buildPackageInfo(/* SYSTEM */ false, APP2_UID),
+ buildPackageInfo(/* SYSTEM */ false, VPN_UID)
+ }));
+ }
+
+ private static int transportToLegacyType(int transport) {
switch (transport) {
case TRANSPORT_ETHERNET:
return TYPE_ETHERNET;
@@ -1086,6 +1365,8 @@
return TYPE_WIFI;
case TRANSPORT_CELLULAR:
return TYPE_MOBILE;
+ case TRANSPORT_VPN:
+ return TYPE_VPN;
default:
return TYPE_NONE;
}
@@ -1418,39 +1699,10 @@
verifyActiveNetwork(TRANSPORT_WIFI);
}
- enum CallbackState {
- NONE,
- AVAILABLE,
- NETWORK_CAPABILITIES,
- LINK_PROPERTIES,
- SUSPENDED,
- RESUMED,
- LOSING,
- LOST,
- UNAVAILABLE
- }
-
- private static class CallbackInfo {
- public final CallbackState state;
- public final Network network;
- public final Object arg;
- public CallbackInfo(CallbackState s, Network n, Object o) {
- state = s; network = n; arg = o;
- }
- public String toString() {
- return String.format("%s (%s) (%s)", state, network, arg);
- }
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof CallbackInfo)) return false;
- // Ignore timeMs, since it's unpredictable.
- CallbackInfo other = (CallbackInfo) o;
- return (state == other.state) && Objects.equals(network, other.network);
- }
- @Override
- public int hashCode() {
- return Objects.hash(state, network);
- }
+ @Test
+ public void testRequiresValidation() {
+ assertTrue(NetworkMonitorUtils.isValidationRequired(
+ mCm.getDefaultRequest().networkCapabilities));
}
/**
@@ -1458,208 +1710,28 @@
* this class receives, by calling expectCallback() exactly once each time a callback is
* received. assertNoCallback may be called at any time.
*/
- private class TestNetworkCallback extends NetworkCallback {
- // Chosen to be much less than the linger timeout. This ensures that we can distinguish
- // between a LOST callback that arrives immediately and a LOST callback that arrives after
- // the linger timeout.
- private final static int TIMEOUT_MS = 100;
-
- private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
- private Network mLastAvailableNetwork;
-
- protected void setLastCallback(CallbackState state, Network network, Object o) {
- mCallbacks.offer(new CallbackInfo(state, network, o));
+ private class TestNetworkCallback extends TestableNetworkCallback {
+ @Override
+ public void assertNoCallback() {
+ // TODO: better support this use case in TestableNetworkCallback
+ waitForIdle();
+ assertNoCallback(0 /* timeout */);
}
@Override
- public void onAvailable(Network network) {
- mLastAvailableNetwork = network;
- setLastCallback(CallbackState.AVAILABLE, network, null);
- }
-
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
- setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
- }
-
- @Override
- public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
- setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
- }
-
- @Override
- public void onUnavailable() {
- setLastCallback(CallbackState.UNAVAILABLE, null, null);
- }
-
- @Override
- public void onNetworkSuspended(Network network) {
- setLastCallback(CallbackState.SUSPENDED, network, null);
- }
-
- @Override
- public void onNetworkResumed(Network network) {
- setLastCallback(CallbackState.RESUMED, network, null);
- }
-
- @Override
- public void onLosing(Network network, int maxMsToLive) {
- setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
- }
-
- @Override
- public void onLost(Network network) {
- mLastAvailableNetwork = null;
- setLastCallback(CallbackState.LOST, network, null);
- }
-
- public Network getLastAvailableNetwork() {
- return mLastAvailableNetwork;
- }
-
- CallbackInfo nextCallback(int timeoutMs) {
- CallbackInfo cb = null;
- try {
- cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- }
- if (cb == null) {
- // LinkedBlockingQueue.poll() returns null if it timeouts.
- fail("Did not receive callback after " + timeoutMs + "ms");
- }
- return cb;
- }
-
- CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent, int timeoutMs) {
- final Network expectedNetwork = (agent != null) ? agent.getNetwork() : null;
- CallbackInfo expected = new CallbackInfo(state, expectedNetwork, 0);
- CallbackInfo actual = nextCallback(timeoutMs);
- assertEquals("Unexpected callback:", expected, actual);
-
- if (state == CallbackState.LOSING) {
+ public <T extends CallbackRecord> T expectCallback(final KClass<T> type, final HasNetwork n,
+ final long timeoutMs) {
+ final T callback = super.expectCallback(type, n, timeoutMs);
+ if (callback instanceof CallbackRecord.Losing) {
+ // TODO : move this to the specific test(s) needing this rather than here.
+ final CallbackRecord.Losing losing = (CallbackRecord.Losing) callback;
+ final int maxMsToLive = losing.getMaxMsToLive();
String msg = String.format(
"Invalid linger time value %d, must be between %d and %d",
- actual.arg, 0, TEST_LINGER_DELAY_MS);
- int maxMsToLive = (Integer) actual.arg;
- assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= TEST_LINGER_DELAY_MS);
+ maxMsToLive, 0, mService.mLingerDelayMs);
+ assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= mService.mLingerDelayMs);
}
-
- return actual;
- }
-
- CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent) {
- return expectCallback(state, agent, TIMEOUT_MS);
- }
-
- CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn) {
- return expectCallbackLike(fn, TIMEOUT_MS);
- }
-
- CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn, int timeoutMs) {
- int timeLeft = timeoutMs;
- while (timeLeft > 0) {
- long start = SystemClock.elapsedRealtime();
- CallbackInfo info = nextCallback(timeLeft);
- if (fn.test(info)) {
- return info;
- }
- timeLeft -= (SystemClock.elapsedRealtime() - start);
- }
- fail("Did not receive expected callback after " + timeoutMs + "ms");
- return null;
- }
-
- // Expects onAvailable and the callbacks that follow it. These are:
- // - onSuspended, iff the network was suspended when the callbacks fire.
- // - onCapabilitiesChanged.
- // - onLinkPropertiesChanged.
- //
- // @param agent the network to expect the callbacks on.
- // @param expectSuspended whether to expect a SUSPENDED callback.
- // @param expectValidated the expected value of the VALIDATED capability in the
- // onCapabilitiesChanged callback.
- // @param timeoutMs how long to wait for the callbacks.
- void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended,
- boolean expectValidated, int timeoutMs) {
- expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
- if (expectSuspended) {
- expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
- }
- if (expectValidated) {
- expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent, timeoutMs);
- } else {
- expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent, timeoutMs);
- }
- expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
- }
-
- // Expects the available callbacks (validated), plus onSuspended.
- void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) {
- expectAvailableCallbacks(agent, true, expectValidated, TIMEOUT_MS);
- }
-
- void expectAvailableCallbacksValidated(MockNetworkAgent agent) {
- expectAvailableCallbacks(agent, false, true, TIMEOUT_MS);
- }
-
- void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) {
- expectAvailableCallbacks(agent, false, false, TIMEOUT_MS);
- }
-
- // Expects the available callbacks (where the onCapabilitiesChanged must contain the
- // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
- // one we just sent.
- // TODO: this is likely a bug. Fix it and remove this method.
- void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) {
- expectCallback(CallbackState.AVAILABLE, agent, TIMEOUT_MS);
- NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
- expectCallback(CallbackState.LINK_PROPERTIES, agent, TIMEOUT_MS);
- NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
- assertEquals(nc1, nc2);
- }
-
- // Expects the available callbacks where the onCapabilitiesChanged must not have validated,
- // then expects another onCapabilitiesChanged that has the validated bit set. This is used
- // when a network connects and satisfies a callback, and then immediately validates.
- void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) {
- expectAvailableCallbacksUnvalidated(agent);
- expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
- }
-
- NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
- return expectCapabilitiesWith(capability, agent, TIMEOUT_MS);
- }
-
- NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent,
- int timeoutMs) {
- CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
- NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
- assertTrue(nc.hasCapability(capability));
- return nc;
- }
-
- NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
- return expectCapabilitiesWithout(capability, agent, TIMEOUT_MS);
- }
-
- NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent,
- int timeoutMs) {
- CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
- NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
- assertFalse(nc.hasCapability(capability));
- return nc;
- }
-
- void expectCapabilitiesLike(Predicate<NetworkCapabilities> fn, MockNetworkAgent agent) {
- CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
- assertTrue("Received capabilities don't match expectations : " + cbi.arg,
- fn.test((NetworkCapabilities) cbi.arg));
- }
-
- void assertNoCallback() {
- waitForIdle();
- CallbackInfo c = mCallbacks.peek();
- assertNull("Unexpected callback: " + c, c);
+ return callback;
}
}
@@ -1713,16 +1785,16 @@
cv = waitForConnectivityBroadcasts(2);
mWiFiNetworkAgent.disconnect();
- genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
cellNetworkCallback.assertNoCallback();
waitFor(cv);
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
cv = waitForConnectivityBroadcasts(1);
mCellNetworkAgent.disconnect();
- genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
waitFor(cv);
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1743,26 +1815,32 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
mWiFiNetworkAgent.disconnect();
- genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
mCellNetworkAgent.disconnect();
- genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
}
@Test
- public void testMultipleLingering() {
+ public void testMultipleLingering() throws Exception {
+ // This test would be flaky with the default 120ms timer: that is short enough that
+ // lingered networks are torn down before assertions can be run. We don't want to mock the
+ // lingering timer to keep the WakeupMessage logic realistic: this has already proven useful
+ // in detecting races.
+ mService.mLingerDelayMs = 300;
+
NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities().addCapability(NET_CAPABILITY_NOT_METERED)
.build();
@@ -1791,7 +1869,7 @@
// We then get LOSING when wifi validates and cell is outscored.
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// TODO: Investigate sending validated before losing.
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1800,15 +1878,15 @@
mEthernetNetworkAgent.connect(true);
callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
// TODO: Investigate sending validated before losing.
- callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mWiFiNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mEthernetNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -1824,7 +1902,7 @@
newNetwork = mWiFiNetworkAgent;
}
- callback.expectCallback(CallbackState.LOSING, oldNetwork);
+ callback.expectCallback(CallbackRecord.LOSING, oldNetwork);
// TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
// longer lingering?
defaultCallback.expectAvailableCallbacksValidated(newNetwork);
@@ -1838,7 +1916,7 @@
// We expect a notification about the capabilities change, and nothing else.
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
defaultCallback.assertNoCallback();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Wifi no longer satisfies our listen, which is for an unmetered network.
@@ -1847,11 +1925,11 @@
// Disconnect our test networks.
mWiFiNetworkAgent.disconnect();
- defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mCellNetworkAgent.disconnect();
- defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
waitForIdle();
assertEquals(null, mCm.getActiveNetwork());
@@ -1882,8 +1960,8 @@
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -1894,15 +1972,15 @@
mWiFiNetworkAgent.adjustScore(50);
mWiFiNetworkAgent.connect(false); // Score: 70
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Tear down wifi.
mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -1913,19 +1991,19 @@
mWiFiNetworkAgent.connect(true);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// TODO: Investigate sending validated before losing.
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
waitForIdle();
assertEquals(null, mCm.getActiveNetwork());
@@ -1940,7 +2018,7 @@
defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// TODO: Investigate sending validated before losing.
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -1951,13 +2029,13 @@
// TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer
// lingering?
mCm.unregisterNetworkCallback(noopCallback);
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
// Similar to the above: lingering can start even after the lingered request is removed.
// Disconnect wifi and switch to cell.
mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -1976,12 +2054,12 @@
callback.assertNoCallback();
// Now unregister cellRequest and expect cell to start lingering.
mCm.unregisterNetworkCallback(noopCallback);
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
// Let linger run its course.
callback.assertNoCallback();
- final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
- callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
+ final int lingerTimeoutMs = mService.mLingerDelayMs + mService.mLingerDelayMs / 4;
+ callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent, lingerTimeoutMs);
// Register a TRACK_DEFAULT request and check that it does not affect lingering.
TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
@@ -1990,20 +2068,20 @@
mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
mEthernetNetworkAgent.connect(true);
callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
- callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mWiFiNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
// Let linger run its course.
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
// Clean up.
mEthernetNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
- trackDefaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+ trackDefaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
mCm.unregisterNetworkCallback(callback);
mCm.unregisterNetworkCallback(defaultCallback);
@@ -2011,8 +2089,8 @@
}
@Test
- public void testNetworkGoesIntoBackgroundAfterLinger() {
- setMobileDataAlwaysOn(true);
+ public void testNetworkGoesIntoBackgroundAfterLinger() throws Exception {
+ setAlwaysOnNetworks(true);
NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities()
.build();
@@ -2033,7 +2111,7 @@
mWiFiNetworkAgent.connect(true);
defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
// File a request for cellular, then release it.
@@ -2042,7 +2120,7 @@
NetworkCallback noopCallback = new NetworkCallback();
mCm.requestNetwork(cellRequest, noopCallback);
mCm.unregisterNetworkCallback(noopCallback);
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
// Let linger run its course.
callback.assertNoCallback();
@@ -2056,7 +2134,7 @@
}
@Test
- public void testExplicitlySelected() {
+ public void testExplicitlySelected() throws Exception {
NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
.build();
@@ -2070,7 +2148,7 @@
// Bring up unvalidated wifi with explicitlySelected=true.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.explicitlySelected(true, false);
mWiFiNetworkAgent.connect(false);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -2086,28 +2164,28 @@
// If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to
// wifi even though it's unvalidated.
mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false);
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Disconnect wifi, and then reconnect, again with explicitlySelected=true.
mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.explicitlySelected(true, false);
mWiFiNetworkAgent.connect(false);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
// network to disconnect.
mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
// Reconnect, again with explicitlySelected=true, but this time validate.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- mWiFiNetworkAgent.explicitlySelected(false);
+ mWiFiNetworkAgent.explicitlySelected(true, false);
mWiFiNetworkAgent.connect(true);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -2119,14 +2197,42 @@
assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
callback.assertNoCallback();
+ // Disconnect wifi, and then reconnect as if the user had selected "yes, don't ask again"
+ // (i.e., with explicitlySelected=true and acceptUnvalidated=true). Expect to switch to
+ // wifi immediately.
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(true, true);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mEthernetNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ mEthernetNetworkAgent.disconnect();
+ callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+
+ // Disconnect and reconnect with explicitlySelected=false and acceptUnvalidated=true.
+ // Check that the network is not scored specially and that the device prefers cell data.
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(false, true);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
// Clean up.
mWiFiNetworkAgent.disconnect();
mCellNetworkAgent.disconnect();
- mEthernetNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
- callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ }
+
+ private int[] makeIntArray(final int size, final int value) {
+ final int[] array = new int[size];
+ Arrays.fill(array, value);
+ return array;
}
private void tryNetworkFactoryRequests(int capability) throws Exception {
@@ -2150,7 +2256,7 @@
mServiceContext, "testFactory", filter);
testFactory.setScoreFilter(40);
ConditionVariable cv = testFactory.getNetworkStartedCV();
- testFactory.expectAddRequests(1);
+ testFactory.expectAddRequestsWithScores(0);
testFactory.register();
testFactory.waitForNetworkRequests(1);
int expectedRequestCount = 1;
@@ -2161,7 +2267,7 @@
assertFalse(testFactory.getMyStartRequested());
NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
networkCallback = new NetworkCallback();
- testFactory.expectAddRequests(1);
+ testFactory.expectAddRequestsWithScores(0); // New request
mCm.requestNetwork(request, networkCallback);
expectedRequestCount++;
testFactory.waitForNetworkRequests(expectedRequestCount);
@@ -2181,7 +2287,7 @@
// When testAgent connects, ConnectivityService will re-send us all current requests with
// the new score. There are expectedRequestCount such requests, and we must wait for all of
// them.
- testFactory.expectAddRequests(expectedRequestCount);
+ testFactory.expectAddRequestsWithScores(makeIntArray(expectedRequestCount, 50));
testAgent.connect(false);
testAgent.addCapability(capability);
waitFor(cv);
@@ -2189,7 +2295,7 @@
assertFalse(testFactory.getMyStartRequested());
// Bring in a bunch of requests.
- testFactory.expectAddRequests(10);
+ testFactory.expectAddRequestsWithScores(makeIntArray(10, 50));
assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
ConnectivityManager.NetworkCallback[] networkCallbacks =
new ConnectivityManager.NetworkCallback[10];
@@ -2212,8 +2318,11 @@
// Drop the higher scored network.
cv = testFactory.getNetworkStartedCV();
+ // With the default network disconnecting, the requests are sent with score 0 to factories.
+ testFactory.expectAddRequestsWithScores(makeIntArray(expectedRequestCount, 0));
testAgent.disconnect();
waitFor(cv);
+ testFactory.waitForNetworkRequests(expectedRequestCount);
assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
assertTrue(testFactory.getMyStartRequested());
@@ -2253,10 +2362,10 @@
.build();
Class<IllegalArgumentException> expected = IllegalArgumentException.class;
- assertException(() -> { mCm.requestNetwork(request1, new NetworkCallback()); }, expected);
- assertException(() -> { mCm.requestNetwork(request1, pendingIntent); }, expected);
- assertException(() -> { mCm.requestNetwork(request2, new NetworkCallback()); }, expected);
- assertException(() -> { mCm.requestNetwork(request2, pendingIntent); }, expected);
+ assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback()));
+ assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent));
+ assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback()));
+ assertThrows(expected, () -> mCm.requestNetwork(request2, pendingIntent));
}
@Test
@@ -2328,7 +2437,186 @@
}
@Test
- public void testCaptivePortal() {
+ public void testPartialConnectivity() throws Exception {
+ // Register network callback.
+ NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+
+ // Bring up validated mobile data.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ // Bring up wifi with partial connectivity.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connectWithPartialConnectivity();
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
+
+ // Mobile data should be the default network.
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ callback.assertNoCallback();
+
+ // With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
+ // probe.
+ mWiFiNetworkAgent.setNetworkPartialValid();
+ // If the user chooses yes to use this partial connectivity wifi, switch the default
+ // network to wifi and check if wifi becomes valid or not.
+ mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
+ false /* always */);
+ // If user accepts partial connectivity network,
+ // NetworkMonitor#setAcceptPartialConnectivity() should be called too.
+ waitForIdle();
+ verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+
+ // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
+ // validated.
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
+ NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED,
+ mWiFiNetworkAgent);
+ assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Disconnect and reconnect wifi with partial connectivity again.
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connectWithPartialConnectivity();
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
+
+ // Mobile data should be the default network.
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // If the user chooses no, disconnect wifi immediately.
+ mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */,
+ false /* always */);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+
+ // If user accepted partial connectivity before, and device reconnects to that network
+ // again, but now the network has full connectivity. The network shouldn't contain
+ // NET_CAPABILITY_PARTIAL_CONNECTIVITY.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ // acceptUnvalidated is also used as setting for accepting partial networks.
+ mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */,
+ true /* acceptUnvalidated */);
+ mWiFiNetworkAgent.connect(true);
+
+ // If user accepted partial connectivity network before,
+ // NetworkMonitor#setAcceptPartialConnectivity() will be called in
+ // ConnectivityService#updateNetworkInfo().
+ waitForIdle();
+ verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
+ nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
+
+ // Wifi should be the default network.
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+
+ // The user accepted partial connectivity and selected "don't ask again". Now the user
+ // reconnects to the partial connectivity network. Switch to wifi as soon as partial
+ // connectivity is detected.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */,
+ true /* acceptUnvalidated */);
+ mWiFiNetworkAgent.connectWithPartialConnectivity();
+ // If user accepted partial connectivity network before,
+ // NetworkMonitor#setAcceptPartialConnectivity() will be called in
+ // ConnectivityService#updateNetworkInfo().
+ waitForIdle();
+ verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
+ mWiFiNetworkAgent.setNetworkValid();
+
+ // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
+ // validated.
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+
+ // If the user accepted partial connectivity, and the device auto-reconnects to the partial
+ // connectivity network, it should contain both PARTIAL_CONNECTIVITY and VALIDATED.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.explicitlySelected(false /* explicitlySelected */,
+ true /* acceptUnvalidated */);
+
+ // NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as
+ // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
+ // notifyNetworkConnected.
+ mWiFiNetworkAgent.connectWithPartialValidConnectivity();
+ waitForIdle();
+ verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
+ callback.expectCapabilitiesWith(
+ NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ }
+
+ @Test
+ public void testCaptivePortalOnPartialConnectivity() throws Exception {
+ final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+ final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+ mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+ final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+ final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_VALIDATED).build();
+ mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+
+ // Bring up a network with a captive portal.
+ // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ String redirectUrl = "http://android.com/path";
+ mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl);
+ captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
+
+ // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
+ mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork());
+ verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
+ .launchCaptivePortalApp();
+
+ // Report that the captive portal is dismissed with partial connectivity, and check that
+ // callbacks are fired.
+ mWiFiNetworkAgent.setNetworkPartial();
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ waitForIdle();
+ captivePortalCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ mWiFiNetworkAgent);
+
+ // Report partial connectivity is accepted.
+ mWiFiNetworkAgent.setNetworkPartialValid();
+ mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
+ false /* always */);
+ waitForIdle();
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+ NetworkCapabilities nc =
+ validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ mWiFiNetworkAgent);
+
+ mCm.unregisterNetworkCallback(captivePortalCallback);
+ mCm.unregisterNetworkCallback(validatedCallback);
+ }
+
+ @Test
+ public void testCaptivePortal() throws Exception {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2350,7 +2638,7 @@
// Take down network.
// Expect onLost callback.
mWiFiNetworkAgent.disconnect();
- captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
// Bring up a network with a captive portal.
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
@@ -2362,22 +2650,25 @@
// Make captive portal disappear then revalidate.
// Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+ mWiFiNetworkAgent.setNetworkValid();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
- captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
// Expect NET_CAPABILITY_VALIDATED onAvailable callback.
validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+ // Expect no notification to be shown when captive portal disappears by itself
+ verify(mNotificationManager, never()).notifyAsUser(
+ anyString(), eq(NotificationType.LOGGED_IN.eventId), any(), any());
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
- validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ validatedCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
}
@Test
- public void testCaptivePortalApp() {
+ public void testCaptivePortalApp() throws Exception {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2397,33 +2688,45 @@
// Check that calling startCaptivePortalApp does nothing.
final int fastTimeoutMs = 100;
mCm.startCaptivePortalApp(wifiNetwork);
+ waitForIdle();
+ verify(mWiFiNetworkAgent.mNetworkMonitor, never()).launchCaptivePortalApp();
mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
// Turn into a captive portal.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
+ mWiFiNetworkAgent.setNetworkPortal("http://example.com");
mCm.reportNetworkConnectivity(wifiNetwork, false);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ validatedCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
- // Check that startCaptivePortalApp sends the expected intent.
+ // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
mCm.startCaptivePortalApp(wifiNetwork);
- Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
- assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
- assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK));
+ waitForIdle();
+ verify(mWiFiNetworkAgent.mNetworkMonitor).launchCaptivePortalApp();
- // Have the app report that the captive portal is dismissed, and check that we revalidate.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
- CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
- c.reportCaptivePortalDismissed();
+ // NetworkMonitor uses startCaptivePortal(Network, Bundle) (startCaptivePortalAppInternal)
+ final Bundle testBundle = new Bundle();
+ final String testKey = "testkey";
+ final String testValue = "testvalue";
+ testBundle.putString(testKey, testValue);
+ mCm.startCaptivePortalApp(wifiNetwork, testBundle);
+ final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
+ assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
+ assertEquals(testValue, signInIntent.getStringExtra(testKey));
+
+ // Report that the captive portal is dismissed, and check that callbacks are fired
+ mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
- captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ verify(mNotificationManager, times(1)).notifyAsUser(anyString(),
+ eq(NotificationType.LOGGED_IN.eventId), any(), eq(UserHandle.ALL));
mCm.unregisterNetworkCallback(validatedCallback);
mCm.unregisterNetworkCallback(captivePortalCallback);
}
@Test
- public void testAvoidOrIgnoreCaptivePortals() {
+ public void testAvoidOrIgnoreCaptivePortals() throws Exception {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2447,36 +2750,82 @@
waitFor(avoidCv);
assertNoCallbacks(captivePortalCallback, validatedCallback);
-
- // Now test ignore mode.
- setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
-
- // Bring up a network with a captive portal.
- // Since we're ignoring captive portals, the network will validate.
- mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- String secondRedirectUrl = "http://example.com/secondPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-
- // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
- validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
- // But there should be no CaptivePortal callback.
- captivePortalCallback.assertNoCallback();
}
private NetworkRequest.Builder newWifiRequestBuilder() {
return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
}
+ /**
+ * Verify request matching behavior with network specifiers.
+ *
+ * Note: this test is somewhat problematic since it involves removing capabilities from
+ * agents - i.e. agents rejecting requests which they previously accepted. This is flagged
+ * as a WTF bug in
+ * {@link ConnectivityService#mixInCapabilities(NetworkAgentInfo, NetworkCapabilities)} but
+ * does work.
+ */
@Test
- public void testNetworkSpecifier() {
+ public void testNetworkSpecifier() throws Exception {
+ // A NetworkSpecifier subclass that matches all networks but must not be visible to apps.
+ class ConfidentialMatchAllNetworkSpecifier extends NetworkSpecifier implements
+ Parcelable {
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ return true;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {}
+
+ @Override
+ public NetworkSpecifier redact() {
+ return null;
+ }
+ }
+
+ // A network specifier that matches either another LocalNetworkSpecifier with the same
+ // string or a ConfidentialMatchAllNetworkSpecifier, and can be passed to apps as is.
+ class LocalStringNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+ private String mString;
+
+ LocalStringNetworkSpecifier(String string) {
+ mString = string;
+ }
+
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ if (other instanceof LocalStringNetworkSpecifier) {
+ return TextUtils.equals(mString,
+ ((LocalStringNetworkSpecifier) other).mString);
+ }
+ if (other instanceof ConfidentialMatchAllNetworkSpecifier) return true;
+ return false;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {}
+ }
+
+
NetworkRequest rEmpty1 = newWifiRequestBuilder().build();
NetworkRequest rEmpty2 = newWifiRequestBuilder().setNetworkSpecifier((String) null).build();
NetworkRequest rEmpty3 = newWifiRequestBuilder().setNetworkSpecifier("").build();
NetworkRequest rEmpty4 = newWifiRequestBuilder().setNetworkSpecifier(
(NetworkSpecifier) null).build();
- NetworkRequest rFoo = newWifiRequestBuilder().setNetworkSpecifier("foo").build();
+ NetworkRequest rFoo = newWifiRequestBuilder().setNetworkSpecifier(
+ new LocalStringNetworkSpecifier("foo")).build();
NetworkRequest rBar = newWifiRequestBuilder().setNetworkSpecifier(
- new StringNetworkSpecifier("bar")).build();
+ new LocalStringNetworkSpecifier("bar")).build();
TestNetworkCallback cEmpty1 = new TestNetworkCallback();
TestNetworkCallback cEmpty2 = new TestNetworkCallback();
@@ -2485,7 +2834,7 @@
TestNetworkCallback cFoo = new TestNetworkCallback();
TestNetworkCallback cBar = new TestNetworkCallback();
TestNetworkCallback[] emptyCallbacks = new TestNetworkCallback[] {
- cEmpty1, cEmpty2, cEmpty3 };
+ cEmpty1, cEmpty2, cEmpty3, cEmpty4 };
mCm.registerNetworkCallback(rEmpty1, cEmpty1);
mCm.registerNetworkCallback(rEmpty2, cEmpty2);
@@ -2494,6 +2843,9 @@
mCm.registerNetworkCallback(rFoo, cFoo);
mCm.registerNetworkCallback(rBar, cBar);
+ LocalStringNetworkSpecifier nsFoo = new LocalStringNetworkSpecifier("foo");
+ LocalStringNetworkSpecifier nsBar = new LocalStringNetworkSpecifier("bar");
+
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -2502,52 +2854,70 @@
cEmpty4.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertNoCallbacks(cFoo, cBar);
- mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo"));
+ mWiFiNetworkAgent.setNetworkSpecifier(nsFoo);
cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
for (TestNetworkCallback c: emptyCallbacks) {
- c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+ c.expectCapabilitiesThat(mWiFiNetworkAgent,
+ (caps) -> caps.getNetworkSpecifier().equals(nsFoo));
}
- cFoo.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+ cFoo.expectCapabilitiesThat(mWiFiNetworkAgent,
+ (caps) -> caps.getNetworkSpecifier().equals(nsFoo));
+ assertEquals(nsFoo,
+ mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
cFoo.assertNoCallback();
- mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar"));
- cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ mWiFiNetworkAgent.setNetworkSpecifier(nsBar);
+ cFoo.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
for (TestNetworkCallback c: emptyCallbacks) {
- c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+ c.expectCapabilitiesThat(mWiFiNetworkAgent,
+ (caps) -> caps.getNetworkSpecifier().equals(nsBar));
}
- cBar.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+ cBar.expectCapabilitiesThat(mWiFiNetworkAgent,
+ (caps) -> caps.getNetworkSpecifier().equals(nsBar));
+ assertEquals(nsBar,
+ mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
+ cBar.assertNoCallback();
+
+ mWiFiNetworkAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier());
+ cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ for (TestNetworkCallback c : emptyCallbacks) {
+ c.expectCapabilitiesThat(mWiFiNetworkAgent,
+ (caps) -> caps.getNetworkSpecifier() == null);
+ }
+ cFoo.expectCapabilitiesThat(mWiFiNetworkAgent,
+ (caps) -> caps.getNetworkSpecifier() == null);
+ cBar.expectCapabilitiesThat(mWiFiNetworkAgent,
+ (caps) -> caps.getNetworkSpecifier() == null);
+ assertNull(
+ mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
+ cFoo.assertNoCallback();
cBar.assertNoCallback();
mWiFiNetworkAgent.setNetworkSpecifier(null);
- cBar.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ cFoo.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ cBar.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
for (TestNetworkCallback c: emptyCallbacks) {
- c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+ c.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
}
- assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cFoo, cBar);
+ assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
}
@Test
public void testInvalidNetworkSpecifier() {
- try {
+ assertThrows(IllegalArgumentException.class, () -> {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.setNetworkSpecifier(new MatchAllNetworkSpecifier());
- fail("NetworkRequest builder with MatchAllNetworkSpecifier");
- } catch (IllegalArgumentException expected) {
- // expected
- }
+ });
- try {
+ assertThrows(IllegalArgumentException.class, () -> {
NetworkCapabilities networkCapabilities = new NetworkCapabilities();
networkCapabilities.addTransportType(TRANSPORT_WIFI)
.setNetworkSpecifier(new MatchAllNetworkSpecifier());
mService.requestNetwork(networkCapabilities, null, 0, null,
ConnectivityManager.TYPE_WIFI);
- fail("ConnectivityService requestNetwork with MatchAllNetworkSpecifier");
- } catch (IllegalArgumentException expected) {
- // expected
- }
+ });
class NonParcelableSpecifier extends NetworkSpecifier {
public boolean satisfiedBy(NetworkSpecifier other) { return false; }
@@ -2556,24 +2926,22 @@
@Override public int describeContents() { return 0; }
@Override public void writeToParcel(Parcel p, int flags) {}
}
- NetworkRequest.Builder builder;
- builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
- try {
+ final NetworkRequest.Builder builder =
+ new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
+ assertThrows(ClassCastException.class, () -> {
builder.setNetworkSpecifier(new NonParcelableSpecifier());
Parcel parcelW = Parcel.obtain();
builder.build().writeToParcel(parcelW, 0);
- fail("Parceling a non-parcelable specifier did not throw an exception");
- } catch (Exception e) {
- // expected
- }
+ });
- builder = new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET);
- builder.setNetworkSpecifier(new ParcelableSpecifier());
- NetworkRequest nr = builder.build();
+ final NetworkRequest nr =
+ new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET)
+ .setNetworkSpecifier(new ParcelableSpecifier())
+ .build();
assertNotNull(nr);
- try {
+ assertThrows(BadParcelableException.class, () -> {
Parcel parcelW = Parcel.obtain();
nr.writeToParcel(parcelW, 0);
byte[] bytes = parcelW.marshall();
@@ -2583,14 +2951,11 @@
parcelR.unmarshall(bytes, 0, bytes.length);
parcelR.setDataPosition(0);
NetworkRequest rereadNr = NetworkRequest.CREATOR.createFromParcel(parcelR);
- fail("Unparceling a non-framework NetworkSpecifier did not throw an exception");
- } catch (Exception e) {
- // expected
- }
+ });
}
@Test
- public void testNetworkSpecifierUidSpoofSecurityException() {
+ public void testNetworkSpecifierUidSpoofSecurityException() throws Exception {
class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
@Override
public boolean satisfiedBy(NetworkSpecifier other) {
@@ -2615,12 +2980,34 @@
NetworkRequest networkRequest = newWifiRequestBuilder().setNetworkSpecifier(
networkSpecifier).build();
TestNetworkCallback networkCallback = new TestNetworkCallback();
- try {
+ assertThrows(SecurityException.class, () -> {
mCm.requestNetwork(networkRequest, networkCallback);
- fail("Network request with spoofed UID did not throw a SecurityException");
- } catch (SecurityException e) {
- // expected
- }
+ });
+ }
+
+ @Test
+ public void testInvalidSignalStrength() {
+ NetworkRequest r = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_WIFI)
+ .setSignalStrength(-75)
+ .build();
+ // Registering a NetworkCallback with signal strength but w/o NETWORK_SIGNAL_STRENGTH_WAKEUP
+ // permission should get SecurityException.
+ assertThrows(SecurityException.class, () ->
+ mCm.registerNetworkCallback(r, new NetworkCallback()));
+
+ assertThrows(SecurityException.class, () ->
+ mCm.registerNetworkCallback(r, PendingIntent.getService(
+ mServiceContext, 0, new Intent(), 0)));
+
+ // Requesting a Network with signal strength should get IllegalArgumentException.
+ assertThrows(IllegalArgumentException.class, () ->
+ mCm.requestNetwork(r, new NetworkCallback()));
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mCm.requestNetwork(r, PendingIntent.getService(
+ mServiceContext, 0, new Intent(), 0)));
}
@Test
@@ -2654,7 +3041,7 @@
// Bring down cell. Expect no default network callback, since it wasn't the default.
mCellNetworkAgent.disconnect();
- cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
defaultNetworkCallback.assertNoCallback();
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -2669,11 +3056,11 @@
// followed by AVAILABLE cell.
mWiFiNetworkAgent.disconnect();
cellNetworkCallback.assertNoCallback();
- defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ defaultNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
- cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
- defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ defaultNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
waitForIdle();
assertEquals(null, mCm.getActiveNetwork());
@@ -2689,7 +3076,7 @@
assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
vpnNetworkAgent.disconnect();
- defaultNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ defaultNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
waitForIdle();
assertEquals(null, mCm.getActiveNetwork());
}
@@ -2717,14 +3104,15 @@
lp.setInterfaceName("foonet_data0");
mCellNetworkAgent.sendLinkProperties(lp);
// We should get onLinkPropertiesChanged().
- cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
+ mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
// Suspend the network.
mCellNetworkAgent.suspend();
cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_SUSPENDED,
mCellNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.SUSPENDED, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.SUSPENDED, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
// Register a garden variety default network request.
@@ -2739,7 +3127,7 @@
mCellNetworkAgent.resume();
cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_SUSPENDED,
mCellNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.RESUMED, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.RESUMED, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
dfltNetworkCallback = new TestNetworkCallback();
@@ -2757,10 +3145,10 @@
Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode);
}
- private void setMobileDataAlwaysOn(boolean enable) {
+ private void setAlwaysOnNetworks(boolean enable) {
ContentResolver cr = mServiceContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.MOBILE_DATA_ALWAYS_ON, enable ? 1 : 0);
- mService.updateMobileDataAlwaysOn();
+ mService.updateAlwaysOnNetworks();
waitForIdle();
}
@@ -2782,7 +3170,7 @@
public void testBackgroundNetworks() throws Exception {
// Create a background request. We can't do this ourselves because ConnectivityService
// doesn't have an API for it. So just turn on mobile data always on.
- setMobileDataAlwaysOn(true);
+ setAlwaysOnNetworks(true);
final NetworkRequest request = new NetworkRequest.Builder().build();
final NetworkRequest fgRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_FOREGROUND).build();
@@ -2802,10 +3190,10 @@
// When wifi connects, cell lingers.
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ fgCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -2813,7 +3201,7 @@
// When lingering is complete, cell is still there but is now in the background.
waitForIdle();
int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
- fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
+ fgCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent, timeoutMs);
// Expect a network capabilities update sans FOREGROUND.
callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
assertFalse(isForegroundNetwork(mCellNetworkAgent));
@@ -2839,7 +3227,7 @@
// Release the request. The network immediately goes into the background, since it was not
// lingering.
mCm.unregisterNetworkCallback(cellCallback);
- fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ fgCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
// Expect a network capabilities update sans FOREGROUND.
callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
assertFalse(isForegroundNetwork(mCellNetworkAgent));
@@ -2847,8 +3235,8 @@
// Disconnect wifi and check that cell is foreground again.
mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ fgCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
@@ -2884,7 +3272,7 @@
};
}
- assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
+ assertRunsInAtMost("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
for (NetworkCallback cb : callbacks) {
mCm.registerNetworkCallback(request, cb);
}
@@ -2897,7 +3285,7 @@
mCellNetworkAgent.connect(false);
long onAvailableDispatchingDuration = durationOf(() -> {
- awaitLatch(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
+ await(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
});
Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms",
NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
@@ -2912,7 +3300,7 @@
mWiFiNetworkAgent.connect(false);
long onLostDispatchingDuration = durationOf(() -> {
- awaitLatch(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
+ await(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
});
Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms",
NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration));
@@ -2920,33 +3308,13 @@
NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS),
onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS);
- assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
+ assertRunsInAtMost("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
for (NetworkCallback cb : callbacks) {
mCm.unregisterNetworkCallback(cb);
}
});
}
- private long durationOf(Runnable fn) {
- long startTime = SystemClock.elapsedRealtime();
- fn.run();
- return SystemClock.elapsedRealtime() - startTime;
- }
-
- private void assertTimeLimit(String descr, long timeLimit, Runnable fn) {
- long timeTaken = durationOf(fn);
- String msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit);
- Log.d(TAG, msg);
- assertTrue(msg, timeTaken <= timeLimit);
- }
-
- private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
- try {
- return l.await(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {}
- return false;
- }
-
@Test
public void testMobileDataAlwaysOn() throws Exception {
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
@@ -2964,30 +3332,31 @@
testFactory.setScoreFilter(40);
// Register the factory and expect it to start looking for a network.
- testFactory.expectAddRequests(1);
+ testFactory.expectAddRequestsWithScores(0); // Score 0 as the request is not served yet.
testFactory.register();
testFactory.waitForNetworkRequests(1);
assertTrue(testFactory.getMyStartRequested());
// Bring up wifi. The factory stops looking for a network.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
- testFactory.expectAddRequests(2); // Because the default request changes score twice.
+ // Score 60 - 40 penalty for not validated yet, then 60 when it validates
+ testFactory.expectAddRequestsWithScores(20, 60);
mWiFiNetworkAgent.connect(true);
- testFactory.waitForNetworkRequests(1);
+ testFactory.waitForRequests();
assertFalse(testFactory.getMyStartRequested());
ContentResolver cr = mServiceContext.getContentResolver();
// Turn on mobile data always on. The factory starts looking again.
- testFactory.expectAddRequests(1);
- setMobileDataAlwaysOn(true);
+ testFactory.expectAddRequestsWithScores(0); // Always on requests comes up with score 0
+ setAlwaysOnNetworks(true);
testFactory.waitForNetworkRequests(2);
assertTrue(testFactory.getMyStartRequested());
// Bring up cell data and check that the factory stops looking.
assertLength(1, mCm.getAllNetworks());
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
- testFactory.expectAddRequests(2); // Because the cell request changes score twice.
+ testFactory.expectAddRequestsWithScores(10, 50); // Unvalidated, then validated
mCellNetworkAgent.connect(true);
cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
testFactory.waitForNetworkRequests(2);
@@ -3000,11 +3369,11 @@
// Turn off mobile data always on and expect the request to disappear...
testFactory.expectRemoveRequests(1);
- setMobileDataAlwaysOn(false);
+ setAlwaysOnNetworks(false);
testFactory.waitForNetworkRequests(1);
// ... and cell data to be torn down.
- cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
assertLength(1, mCm.getAllNetworks());
testFactory.unregister();
@@ -3092,10 +3461,10 @@
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
- validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ validatedWifiCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
// Because avoid bad wifi is off, we don't switch to cellular.
defaultCallback.assertNoCallback();
@@ -3136,10 +3505,10 @@
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
- mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+ mWiFiNetworkAgent.setNetworkInvalid();
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
- validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ validatedWifiCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
// Simulate the user selecting "switch" and checking the don't ask again checkbox.
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
@@ -3166,7 +3535,7 @@
// If cell goes down, we switch to wifi.
mCellNetworkAgent.disconnect();
- defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedWifiCallback.assertNoCallback();
@@ -3200,16 +3569,16 @@
* time-out period expires.
*/
@Test
- public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() {
+ public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() throws Exception {
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- final int timeoutMs = 150;
- mCm.requestNetwork(nr, networkCallback, timeoutMs);
+ mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, timeoutMs);
+ networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false,
+ TEST_CALLBACK_TIMEOUT_MS);
// pass timeout and validate that UNAVAILABLE is not called
networkCallback.assertNoCallback();
@@ -3220,19 +3589,18 @@
* not trigger onUnavailable() once the time-out period expires.
*/
@Test
- public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
+ public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() throws Exception {
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- final int requestTimeoutMs = 50;
- mCm.requestNetwork(nr, networkCallback, requestTimeoutMs);
+ mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- final int assertTimeoutMs = 100;
- networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, assertTimeoutMs);
+ networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false,
+ TEST_CALLBACK_TIMEOUT_MS);
mWiFiNetworkAgent.disconnect();
- networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ networkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
// Validate that UNAVAILABLE is not called
networkCallback.assertNoCallback();
@@ -3244,7 +3612,7 @@
* (somehow) satisfied - the callback isn't called later.
*/
@Test
- public void testTimedoutNetworkRequest() {
+ public void testTimedoutNetworkRequest() throws Exception {
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
@@ -3252,7 +3620,7 @@
mCm.requestNetwork(nr, networkCallback, timeoutMs);
// pass timeout and validate that UNAVAILABLE is called
- networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
+ networkCallback.expectCallback(CallbackRecord.UNAVAILABLE, null);
// create a network satisfying request - validate that request not triggered
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -3265,7 +3633,7 @@
* trigger the callback.
*/
@Test
- public void testNoCallbackAfterUnregisteredNetworkRequest() {
+ public void testNoCallbackAfterUnregisteredNetworkRequest() throws Exception {
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
@@ -3283,9 +3651,81 @@
networkCallback.assertNoCallback();
}
+ @Test
+ public void testUnfulfillableNetworkRequest() throws Exception {
+ runUnfulfillableNetworkRequest(false);
+ }
+
+ @Test
+ public void testUnfulfillableNetworkRequestAfterUnregister() throws Exception {
+ runUnfulfillableNetworkRequest(true);
+ }
+
+ /**
+ * Validate the callback flow for a factory releasing a request as unfulfillable.
+ */
+ private void runUnfulfillableNetworkRequest(boolean preUnregister) throws Exception {
+ NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+ NetworkCapabilities.TRANSPORT_WIFI).build();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+
+ final HandlerThread handlerThread = new HandlerThread("testUnfulfillableNetworkRequest");
+ handlerThread.start();
+ NetworkCapabilities filter = new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET);
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter);
+ testFactory.setScoreFilter(40);
+
+ // Register the factory and expect it to receive the default request.
+ testFactory.expectAddRequestsWithScores(0); // default request score is 0, not served yet
+ testFactory.register();
+ SparseArray<NetworkRequest> requests = testFactory.waitForNetworkRequests(1);
+
+ assertEquals(1, requests.size()); // have 1 request at this point
+ int origRequestId = requests.valueAt(0).requestId;
+
+ // Now file the test request and expect it.
+ testFactory.expectAddRequestsWithScores(0);
+ mCm.requestNetwork(nr, networkCallback);
+ requests = testFactory.waitForNetworkRequests(2); // have 2 requests at this point
+
+ int newRequestId = 0;
+ for (int i = 0; i < requests.size(); ++i) {
+ if (requests.valueAt(i).requestId != origRequestId) {
+ newRequestId = requests.valueAt(i).requestId;
+ break;
+ }
+ }
+
+ testFactory.expectRemoveRequests(1);
+ if (preUnregister) {
+ mCm.unregisterNetworkCallback(networkCallback);
+
+ // Simulate the factory releasing the request as unfulfillable: no-op since
+ // the callback has already been unregistered (but a test that no exceptions are
+ // thrown).
+ testFactory.triggerUnfulfillable(requests.get(newRequestId));
+ } else {
+ // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
+ testFactory.triggerUnfulfillable(requests.get(newRequestId));
+
+ networkCallback.expectCallback(CallbackRecord.UNAVAILABLE, null);
+ testFactory.waitForRequests();
+
+ // unregister network callback - a no-op (since already freed by the
+ // on-unavailable), but should not fail or throw exceptions.
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
+ testFactory.unregister();
+ handlerThread.quit();
+ }
+
private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
- public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+ public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }
private class CallbackValue {
public CallbackType callbackType;
@@ -3316,7 +3756,7 @@
}
}
- private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+ private final LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
@Override
public void onStarted() {
@@ -3333,30 +3773,104 @@
mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
}
- private void expectCallback(CallbackValue callbackValue) {
- try {
- assertEquals(
- callbackValue,
- mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
- }
+ private void expectCallback(CallbackValue callbackValue) throws InterruptedException {
+ assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
- public void expectStarted() {
+ public void expectStarted() throws Exception {
expectCallback(new CallbackValue(CallbackType.ON_STARTED));
}
- public void expectStopped() {
+ public void expectStopped() throws Exception {
expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
}
- public void expectError(int error) {
+ public void expectError(int error) throws Exception {
expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
}
}
- private Network connectKeepaliveNetwork(LinkProperties lp) {
+ private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+
+ public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+ private class CallbackValue {
+ public CallbackType callbackType;
+ public int error;
+
+ CallbackValue(CallbackType type) {
+ this.callbackType = type;
+ this.error = SocketKeepalive.SUCCESS;
+ assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
+ }
+
+ CallbackValue(CallbackType type, int error) {
+ this.callbackType = type;
+ this.error = error;
+ assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof CallbackValue
+ && this.callbackType == ((CallbackValue) o).callbackType
+ && this.error == ((CallbackValue) o).error;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType,
+ error);
+ }
+ }
+
+ private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+ private final Executor mExecutor;
+
+ TestSocketKeepaliveCallback(@NonNull Executor executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onStarted() {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
+ }
+
+ @Override
+ public void onStopped() {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
+ }
+
+ @Override
+ public void onError(int error) {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
+ }
+
+ private void expectCallback(CallbackValue callbackValue) throws InterruptedException {
+ assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ }
+
+ public void expectStarted() throws InterruptedException {
+ expectCallback(new CallbackValue(CallbackType.ON_STARTED));
+ }
+
+ public void expectStopped() throws InterruptedException {
+ expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
+ }
+
+ public void expectError(int error) throws InterruptedException {
+ expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
+ }
+
+ public void assertNoCallback() {
+ waitForIdleSerialExecutor(mExecutor, TIMEOUT_MS);
+ CallbackValue cv = mCallbacks.peek();
+ assertNull("Unexpected callback: " + cv, cv);
+ }
+ }
+
+ private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception {
// Ensure the network is disconnected before we do anything.
if (mWiFiNetworkAgent != null) {
assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()));
@@ -3460,19 +3974,6 @@
myNet = connectKeepaliveNetwork(lp);
mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS);
- // Check things work as expected when the keepalive is stopped and the network disconnects.
- ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4);
- callback.expectStarted();
- ka.stop();
- mWiFiNetworkAgent.disconnect();
- waitFor(mWiFiNetworkAgent.getDisconnectedCV());
- waitForIdle();
- callback.expectStopped();
-
- // Reconnect.
- myNet = connectKeepaliveNetwork(lp);
- mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS);
-
// Check that keepalive slots start from 1 and increment. The first one gets slot 1.
mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4);
@@ -3502,6 +4003,331 @@
callback3.expectStopped();
}
+ // Helper method to prepare the executor and run test
+ private void runTestWithSerialExecutors(ExceptionUtils.ThrowingConsumer<Executor> functor)
+ throws Exception {
+ final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor();
+ final Executor executorInline = (Runnable r) -> r.run();
+ functor.accept(executorSingleThread);
+ executorSingleThread.shutdown();
+ functor.accept(executorInline);
+ }
+
+ @Test
+ public void testNattSocketKeepalives() throws Exception {
+ runTestWithSerialExecutors(executor -> doTestNattSocketKeepalivesWithExecutor(executor));
+ runTestWithSerialExecutors(executor -> doTestNattSocketKeepalivesFdWithExecutor(executor));
+ }
+
+ private void doTestNattSocketKeepalivesWithExecutor(Executor executor) throws Exception {
+ // TODO: 1. Move this outside of ConnectivityServiceTest.
+ // 2. Make test to verify that Nat-T keepalive socket is created by IpSecService.
+ // 3. Mock ipsec service.
+ final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
+ final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
+ final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1");
+ final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
+ final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888");
+
+ final int validKaInterval = 15;
+ final int invalidKaInterval = 9;
+
+ final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket();
+ final int srcPort = testSocket.getPort();
+
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("wlan12");
+ lp.addLinkAddress(new LinkAddress(myIPv6, 64));
+ lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+
+ Network notMyNet = new Network(61234);
+ Network myNet = connectKeepaliveNetwork(lp);
+
+ TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor);
+
+ // Attempt to start keepalives with invalid parameters and check for errors.
+ // Invalid network.
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ notMyNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ }
+
+ // Invalid interval.
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(invalidKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+ }
+
+ // Invalid destination.
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv6, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ }
+
+ // Invalid source;
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv6, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ }
+
+ // NAT-T is only supported for IPv4.
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv6, dstIPv6, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ }
+
+ // Sanity check before testing started keepalive.
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_UNSUPPORTED);
+ }
+
+ // Check that a started keepalive can be stopped.
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+ ka.stop();
+ callback.expectStopped();
+
+ // Check that keepalive could be restarted.
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ callback.expectStopped();
+
+ // Check that keepalive can be restarted without waiting for callback.
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ ka.start(validKaInterval);
+ callback.expectStopped();
+ callback.expectStarted();
+ ka.stop();
+ callback.expectStopped();
+ }
+
+ // Check that deleting the IP address stops the keepalive.
+ LinkProperties bogusLp = new LinkProperties(lp);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
+ bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
+ mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ mWiFiNetworkAgent.sendLinkProperties(lp);
+ }
+
+ // Check that a started keepalive is stopped correctly when the network disconnects.
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ // ... and that stopping it after that has no adverse effects.
+ waitForIdle();
+ final Network myNetAlias = myNet;
+ assertNull(mCm.getNetworkCapabilities(myNetAlias));
+ ka.stop();
+ callback.assertNoCallback();
+ }
+
+ // Reconnect.
+ myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+ // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+ int srcPort2 = 0;
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+
+ // The second one gets slot 2.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+ final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket();
+ srcPort2 = testSocket2.getPort();
+ TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor);
+ try (SocketKeepalive ka2 = mCm.createSocketKeepalive(
+ myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) {
+ ka2.start(validKaInterval);
+ callback2.expectStarted();
+
+ ka.stop();
+ callback.expectStopped();
+
+ ka2.stop();
+ callback2.expectStopped();
+
+ testSocket.close();
+ testSocket2.close();
+ }
+ }
+
+ // Check that there is no port leaked after all keepalives and sockets are closed.
+ // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7.
+ // assertFalse(isUdpPortInUse(srcPort));
+ // assertFalse(isUdpPortInUse(srcPort2));
+
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ mWiFiNetworkAgent = null;
+ }
+
+ @Test
+ public void testTcpSocketKeepalives() throws Exception {
+ runTestWithSerialExecutors(executor -> doTestTcpSocketKeepalivesWithExecutor(executor));
+ }
+
+ private void doTestTcpSocketKeepalivesWithExecutor(Executor executor) throws Exception {
+ final int srcPortV4 = 12345;
+ final int srcPortV6 = 23456;
+ final InetAddress myIPv4 = InetAddress.getByName("127.0.0.1");
+ final InetAddress myIPv6 = InetAddress.getByName("::1");
+
+ final int validKaInterval = 15;
+
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("wlan12");
+ lp.addLinkAddress(new LinkAddress(myIPv6, 64));
+ lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("127.0.0.254")));
+
+ final Network notMyNet = new Network(61234);
+ final Network myNet = connectKeepaliveNetwork(lp);
+
+ final Socket testSocketV4 = new Socket();
+ final Socket testSocketV6 = new Socket();
+
+ TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor);
+
+ // Attempt to start Tcp keepalives with invalid parameters and check for errors.
+ // Invalid network.
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ notMyNet, testSocketV4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ }
+
+ // Invalid Socket (socket is not bound with IPv4 address).
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocketV4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ }
+
+ // Invalid Socket (socket is not bound with IPv6 address).
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocketV6, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ }
+
+ // Bind the socket address
+ testSocketV4.bind(new InetSocketAddress(myIPv4, srcPortV4));
+ testSocketV6.bind(new InetSocketAddress(myIPv6, srcPortV6));
+
+ // Invalid Socket (socket is bound with IPv4 address).
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocketV4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ }
+
+ // Invalid Socket (socket is bound with IPv6 address).
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocketV6, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ }
+
+ testSocketV4.close();
+ testSocketV6.close();
+
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ mWiFiNetworkAgent = null;
+ }
+
+ private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception {
+ final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
+ final InetAddress anyIPv4 = InetAddress.getByName("0.0.0.0");
+ final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
+ final int validKaInterval = 15;
+
+ // Prepare the target network.
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("wlan12");
+ lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+ Network myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+ mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+
+ TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor);
+
+ // Prepare the target file descriptor, keep only one instance.
+ final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket();
+ final int srcPort = testSocket.getPort();
+ final ParcelFileDescriptor testPfd =
+ ParcelFileDescriptor.dup(testSocket.getFileDescriptor());
+ testSocket.close();
+ assertTrue(isUdpPortInUse(srcPort));
+
+ // Start keepalive and explicit make the variable goes out of scope with try-with-resources
+ // block.
+ try (SocketKeepalive ka = mCm.createNattKeepalive(
+ myNet, testPfd, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ callback.expectStopped();
+ }
+
+ // Check that the ParcelFileDescriptor is still valid after keepalive stopped,
+ // ErrnoException with EBADF will be thrown if the socket is closed when checking local
+ // address.
+ assertTrue(isUdpPortInUse(srcPort));
+ final InetSocketAddress sa =
+ (InetSocketAddress) Os.getsockname(testPfd.getFileDescriptor());
+ assertEquals(anyIPv4, sa.getAddress());
+
+ testPfd.close();
+ // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7.
+ // assertFalse(isUdpPortInUse(srcPort));
+
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ mWiFiNetworkAgent = null;
+ }
+
+ private static boolean isUdpPortInUse(int port) {
+ try (DatagramSocket ignored = new DatagramSocket(port)) {
+ return false;
+ } catch (IOException alreadyInUse) {
+ return true;
+ }
+ }
+
@Test
public void testGetCaptivePortalServerUrl() throws Exception {
String url = mCm.getCaptivePortalServerUrl();
@@ -3509,23 +4335,19 @@
}
private static class TestNetworkPinner extends NetworkPinner {
- public static boolean awaitPin(int timeoutMs) {
+ public static boolean awaitPin(int timeoutMs) throws InterruptedException {
synchronized(sLock) {
if (sNetwork == null) {
- try {
- sLock.wait(timeoutMs);
- } catch (InterruptedException e) {}
+ sLock.wait(timeoutMs);
}
return sNetwork != null;
}
}
- public static boolean awaitUnpin(int timeoutMs) {
+ public static boolean awaitUnpin(int timeoutMs) throws InterruptedException {
synchronized(sLock) {
if (sNetwork != null) {
- try {
- sLock.wait(timeoutMs);
- } catch (InterruptedException e) {}
+ sLock.wait(timeoutMs);
}
return sNetwork == null;
}
@@ -3548,7 +4370,7 @@
}
@Test
- public void testNetworkPinner() {
+ public void testNetworkPinner() throws Exception {
NetworkRequest wifiRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
.build();
@@ -3643,25 +4465,20 @@
}
// Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added.
- try {
- mCm.requestNetwork(networkRequest, new NetworkCallback());
- fail("Registering " + MAX_REQUESTS + " network requests did not throw exception");
- } catch (TooManyRequestsException expected) {}
- try {
- mCm.registerNetworkCallback(networkRequest, new NetworkCallback());
- fail("Registering " + MAX_REQUESTS + " network callbacks did not throw exception");
- } catch (TooManyRequestsException expected) {}
- try {
- mCm.requestNetwork(networkRequest,
- PendingIntent.getBroadcast(mContext, 0, new Intent("c"), 0));
- fail("Registering " + MAX_REQUESTS + " PendingIntent requests did not throw exception");
- } catch (TooManyRequestsException expected) {}
- try {
- mCm.registerNetworkCallback(networkRequest,
- PendingIntent.getBroadcast(mContext, 0, new Intent("d"), 0));
- fail("Registering " + MAX_REQUESTS
- + " PendingIntent callbacks did not throw exception");
- } catch (TooManyRequestsException expected) {}
+ assertThrows(TooManyRequestsException.class, () ->
+ mCm.requestNetwork(networkRequest, new NetworkCallback())
+ );
+ assertThrows(TooManyRequestsException.class, () ->
+ mCm.registerNetworkCallback(networkRequest, new NetworkCallback())
+ );
+ assertThrows(TooManyRequestsException.class, () ->
+ mCm.requestNetwork(networkRequest,
+ PendingIntent.getBroadcast(mContext, 0, new Intent("c"), 0))
+ );
+ assertThrows(TooManyRequestsException.class, () ->
+ mCm.registerNetworkCallback(networkRequest,
+ PendingIntent.getBroadcast(mContext, 0, new Intent("d"), 0))
+ );
for (Object o : registered) {
if (o instanceof NetworkCallback) {
@@ -3705,7 +4522,7 @@
}
@Test
- public void testNetworkInfoOfTypeNone() {
+ public void testNetworkInfoOfTypeNone() throws Exception {
ConditionVariable broadcastCV = waitForConnectivityBroadcasts(1);
verifyNoNetwork();
@@ -3735,7 +4552,7 @@
// Disconnect wifi aware network.
wifiAware.disconnect();
- callback.expectCallbackLike((info) -> info.state == CallbackState.LOST, TIMEOUT_MS);
+ callback.expectCallbackThat(TIMEOUT_MS, (info) -> info instanceof CallbackRecord.Lost);
mCm.unregisterNetworkCallback(callback);
verifyNoNetwork();
@@ -3752,21 +4569,21 @@
assertNull(mCm.getLinkProperties(TYPE_NONE));
assertFalse(mCm.isNetworkSupported(TYPE_NONE));
- assertException(() -> { mCm.networkCapabilitiesForType(TYPE_NONE); },
- IllegalArgumentException.class);
+ assertThrows(IllegalArgumentException.class,
+ () -> { mCm.networkCapabilitiesForType(TYPE_NONE); });
Class<UnsupportedOperationException> unsupported = UnsupportedOperationException.class;
- assertException(() -> { mCm.startUsingNetworkFeature(TYPE_WIFI, ""); }, unsupported);
- assertException(() -> { mCm.stopUsingNetworkFeature(TYPE_WIFI, ""); }, unsupported);
+ assertThrows(unsupported, () -> { mCm.startUsingNetworkFeature(TYPE_WIFI, ""); });
+ assertThrows(unsupported, () -> { mCm.stopUsingNetworkFeature(TYPE_WIFI, ""); });
// TODO: let test context have configuration application target sdk version
// and test that pre-M requesting for TYPE_NONE sends back APN_REQUEST_FAILED
- assertException(() -> { mCm.startUsingNetworkFeature(TYPE_NONE, ""); }, unsupported);
- assertException(() -> { mCm.stopUsingNetworkFeature(TYPE_NONE, ""); }, unsupported);
- assertException(() -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); }, unsupported);
+ assertThrows(unsupported, () -> { mCm.startUsingNetworkFeature(TYPE_NONE, ""); });
+ assertThrows(unsupported, () -> { mCm.stopUsingNetworkFeature(TYPE_NONE, ""); });
+ assertThrows(unsupported, () -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); });
}
@Test
- public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() {
+ public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() throws Exception {
final NetworkRequest networkRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
@@ -3784,13 +4601,15 @@
// ConnectivityService.
MockNetworkAgent networkAgent = new MockNetworkAgent(TRANSPORT_WIFI, lp);
networkAgent.connect(true);
- networkCallback.expectCallback(CallbackState.AVAILABLE, networkAgent);
- networkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, networkAgent);
- CallbackInfo cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+ networkCallback.expectCallback(CallbackRecord.AVAILABLE, networkAgent);
+ networkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, networkAgent);
+ CallbackRecord.LinkPropertiesChanged cbi =
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
networkAgent);
+ networkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, networkAgent);
networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent);
networkCallback.assertNoCallback();
- checkDirectlyConnectedRoutes(cbi.arg, Arrays.asList(myIpv4Address),
+ checkDirectlyConnectedRoutes(cbi.getLp(), Arrays.asList(myIpv4Address),
Arrays.asList(myIpv4DefaultRoute));
checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()),
Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute));
@@ -3802,9 +4621,9 @@
newLp.addLinkAddress(myIpv6Address1);
newLp.addLinkAddress(myIpv6Address2);
networkAgent.sendLinkProperties(newLp);
- cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, networkAgent);
+ cbi = networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, networkAgent);
networkCallback.assertNoCallback();
- checkDirectlyConnectedRoutes(cbi.arg,
+ checkDirectlyConnectedRoutes(cbi.getLp(),
Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
Arrays.asList(myIpv4DefaultRoute));
mCm.unregisterNetworkCallback(networkCallback);
@@ -3828,11 +4647,8 @@
mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle();
verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(
- eq(onlyCell),
- eq(new VpnInfo[0]),
- any(NetworkState[].class),
- eq(MOBILE_IFNAME));
+ .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+ eq(new VpnInfo[0]));
reset(mStatsService);
// Default network switch should update ifaces.
@@ -3841,82 +4657,61 @@
waitForIdle();
assertEquals(wifiLp, mService.getActiveLinkProperties());
verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(
- eq(onlyWifi),
- eq(new VpnInfo[0]),
- any(NetworkState[].class),
- eq(WIFI_IFNAME));
+ .forceUpdateIfaces(eq(onlyWifi), any(NetworkState[].class), eq(WIFI_IFNAME),
+ eq(new VpnInfo[0]));
reset(mStatsService);
// Disconnect should update ifaces.
mWiFiNetworkAgent.disconnect();
waitForIdle();
verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(
- eq(onlyCell),
- eq(new VpnInfo[0]),
- any(NetworkState[].class),
- eq(MOBILE_IFNAME));
+ .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class),
+ eq(MOBILE_IFNAME), eq(new VpnInfo[0]));
reset(mStatsService);
// Metered change should update ifaces
mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
waitForIdle();
verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(
- eq(onlyCell),
- eq(new VpnInfo[0]),
- any(NetworkState[].class),
- eq(MOBILE_IFNAME));
+ .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+ eq(new VpnInfo[0]));
reset(mStatsService);
mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
waitForIdle();
verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(
- eq(onlyCell),
- eq(new VpnInfo[0]),
- any(NetworkState[].class),
- eq(MOBILE_IFNAME));
+ .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+ eq(new VpnInfo[0]));
reset(mStatsService);
// Captive portal change shouldn't update ifaces
mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
waitForIdle();
verify(mStatsService, never())
- .forceUpdateIfaces(
- eq(onlyCell),
- eq(new VpnInfo[0]),
- any(NetworkState[].class),
- eq(MOBILE_IFNAME));
+ .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+ eq(new VpnInfo[0]));
reset(mStatsService);
// Roaming change should update ifaces
mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
waitForIdle();
verify(mStatsService, atLeastOnce())
- .forceUpdateIfaces(
- eq(onlyCell),
- eq(new VpnInfo[0]),
- any(NetworkState[].class),
- eq(MOBILE_IFNAME));
+ .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
+ eq(new VpnInfo[0]));
reset(mStatsService);
}
@Test
public void testBasicDnsConfigurationPushed() throws Exception {
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
- ArgumentCaptor<String[]> tlsServers = ArgumentCaptor.forClass(String[].class);
// Clear any interactions that occur as a result of CS starting up.
- reset(mNetworkManagementService);
+ reset(mMockDnsResolver);
- final String[] EMPTY_STRING_ARRAY = new String[0];
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
waitForIdle();
- verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork(
- anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY));
- verifyNoMoreInteractions(mNetworkManagementService);
+ verify(mMockDnsResolver, never()).setResolverConfiguration(any());
+ verifyNoMoreInteractions(mMockDnsResolver);
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -3932,64 +4727,61 @@
mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(false);
waitForIdle();
- // CS tells netd about the empty DNS config for this network.
- verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
- anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY));
- reset(mNetworkManagementService);
+
+ verify(mMockDnsResolver, times(1)).createNetworkCache(
+ eq(mCellNetworkAgent.getNetwork().netId));
+ // CS tells dnsresolver about the empty DNS config for this network.
+ verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
+ reset(mMockDnsResolver);
cellLp.addDnsServer(InetAddress.getByName("2001:db8::1"));
mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle();
- verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
- anyInt(), mStringArrayCaptor.capture(), any(), any(),
- eq(""), tlsServers.capture());
- assertEquals(1, mStringArrayCaptor.getValue().length);
- assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "2001:db8::1"));
+ verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
+ mResolverParamsParcelCaptor.capture());
+ ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
+ assertEquals(1, resolvrParams.servers.length);
+ assertTrue(ArrayUtils.contains(resolvrParams.servers, "2001:db8::1"));
// Opportunistic mode.
- assertTrue(ArrayUtils.contains(tlsServers.getValue(), "2001:db8::1"));
- reset(mNetworkManagementService);
+ assertTrue(ArrayUtils.contains(resolvrParams.tlsServers, "2001:db8::1"));
+ reset(mMockDnsResolver);
cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
mCellNetworkAgent.sendLinkProperties(cellLp);
waitForIdle();
- verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
- anyInt(), mStringArrayCaptor.capture(), any(), any(),
- eq(""), tlsServers.capture());
- assertEquals(2, mStringArrayCaptor.getValue().length);
- assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
+ verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
+ mResolverParamsParcelCaptor.capture());
+ resolvrParams = mResolverParamsParcelCaptor.getValue();
+ assertEquals(2, resolvrParams.servers.length);
+ assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
new String[]{"2001:db8::1", "192.0.2.1"}));
// Opportunistic mode.
- assertEquals(2, tlsServers.getValue().length);
- assertTrue(ArrayUtils.containsAll(tlsServers.getValue(),
+ assertEquals(2, resolvrParams.tlsServers.length);
+ assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
new String[]{"2001:db8::1", "192.0.2.1"}));
- reset(mNetworkManagementService);
+ reset(mMockDnsResolver);
final String TLS_SPECIFIER = "tls.example.com";
final String TLS_SERVER6 = "2001:db8:53::53";
final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) };
final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 };
- final Handler h = mCellNetworkAgent.getWrappedNetworkMonitor().connectivityHandler;
- h.sendMessage(h.obtainMessage(
- NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0,
- mCellNetworkAgent.getNetwork().netId,
- new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS)));
+ mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved(
+ new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel());
+
waitForIdle();
- verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
- anyInt(), mStringArrayCaptor.capture(), any(), any(),
- eq(TLS_SPECIFIER), eq(TLS_SERVERS));
- assertEquals(2, mStringArrayCaptor.getValue().length);
- assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
+ verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
+ mResolverParamsParcelCaptor.capture());
+ resolvrParams = mResolverParamsParcelCaptor.getValue();
+ assertEquals(2, resolvrParams.servers.length);
+ assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
new String[]{"2001:db8::1", "192.0.2.1"}));
- reset(mNetworkManagementService);
+ reset(mMockDnsResolver);
}
@Test
public void testPrivateDnsSettingsChange() throws Exception {
- final String[] EMPTY_STRING_ARRAY = new String[0];
- ArgumentCaptor<String[]> tlsServers = ArgumentCaptor.forClass(String[].class);
-
// Clear any interactions that occur as a result of CS starting up.
- reset(mNetworkManagementService);
+ reset(mMockDnsResolver);
// The default on Android is opportunistic mode ("Automatic").
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
@@ -4002,9 +4794,8 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
waitForIdle();
// CS tells netd about the empty DNS config for this network.
- verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork(
- anyInt(), eq(EMPTY_STRING_ARRAY), any(), any(), eq(""), eq(EMPTY_STRING_ARRAY));
- verifyNoMoreInteractions(mNetworkManagementService);
+ verify(mMockDnsResolver, never()).setResolverConfiguration(any());
+ verifyNoMoreInteractions(mMockDnsResolver);
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -4023,65 +4814,60 @@
mCellNetworkAgent.sendLinkProperties(cellLp);
mCellNetworkAgent.connect(false);
waitForIdle();
- verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
- anyInt(), mStringArrayCaptor.capture(), any(), any(),
- eq(""), tlsServers.capture());
- assertEquals(2, mStringArrayCaptor.getValue().length);
- assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
- new String[]{"2001:db8::1", "192.0.2.1"}));
+ verify(mMockDnsResolver, times(1)).createNetworkCache(
+ eq(mCellNetworkAgent.getNetwork().netId));
+ verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
+ mResolverParamsParcelCaptor.capture());
+ ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
+ assertEquals(2, resolvrParams.tlsServers.length);
+ assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
+ new String[] { "2001:db8::1", "192.0.2.1" }));
// Opportunistic mode.
- assertEquals(2, tlsServers.getValue().length);
- assertTrue(ArrayUtils.containsAll(tlsServers.getValue(),
- new String[]{"2001:db8::1", "192.0.2.1"}));
- reset(mNetworkManagementService);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES,
+ assertEquals(2, resolvrParams.tlsServers.length);
+ assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
+ new String[] { "2001:db8::1", "192.0.2.1" }));
+ reset(mMockDnsResolver);
+ cellNetworkCallback.expectCallback(CallbackRecord.AVAILABLE, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED,
mCellNetworkAgent);
- CallbackInfo cbi = cellNetworkCallback.expectCallback(
- CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ CallbackRecord.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback(
+ CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
- assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
- assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+ assertFalse(cbi.getLp().isPrivateDnsActive());
+ assertNull(cbi.getLp().getPrivateDnsServerName());
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
- verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
- anyInt(), mStringArrayCaptor.capture(), any(), any(),
- eq(""), eq(EMPTY_STRING_ARRAY));
- assertEquals(2, mStringArrayCaptor.getValue().length);
- assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
- new String[]{"2001:db8::1", "192.0.2.1"}));
- reset(mNetworkManagementService);
+ verify(mMockDnsResolver, times(1)).setResolverConfiguration(
+ mResolverParamsParcelCaptor.capture());
+ resolvrParams = mResolverParamsParcelCaptor.getValue();
+ assertEquals(2, resolvrParams.servers.length);
+ assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
+ new String[] { "2001:db8::1", "192.0.2.1" }));
+ reset(mMockDnsResolver);
cellNetworkCallback.assertNoCallback();
setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
- verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
- anyInt(), mStringArrayCaptor.capture(), any(), any(),
- eq(""), tlsServers.capture());
- assertEquals(2, mStringArrayCaptor.getValue().length);
- assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
- new String[]{"2001:db8::1", "192.0.2.1"}));
- assertEquals(2, tlsServers.getValue().length);
- assertTrue(ArrayUtils.containsAll(tlsServers.getValue(),
- new String[]{"2001:db8::1", "192.0.2.1"}));
- reset(mNetworkManagementService);
+ verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
+ mResolverParamsParcelCaptor.capture());
+ resolvrParams = mResolverParamsParcelCaptor.getValue();
+ assertEquals(2, resolvrParams.servers.length);
+ assertTrue(ArrayUtils.containsAll(resolvrParams.servers,
+ new String[] { "2001:db8::1", "192.0.2.1" }));
+ assertEquals(2, resolvrParams.tlsServers.length);
+ assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
+ new String[] { "2001:db8::1", "192.0.2.1" }));
+ reset(mMockDnsResolver);
cellNetworkCallback.assertNoCallback();
setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com");
// Can't test dns configuration for strict mode without properly mocking
// out the DNS lookups, but can test that LinkProperties is updated.
- cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+ cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
- assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
- assertEquals("strict.example.com", ((LinkProperties)cbi.arg).getPrivateDnsServerName());
-
- // Send the same LinkProperties and expect getting the same result including private dns.
- // b/118518971
- LinkProperties oldLp = (LinkProperties) cbi.arg;
- mCellNetworkAgent.sendLinkProperties(cellLp);
- waitForIdle();
- LinkProperties newLp = mCm.getLinkProperties(cbi.network);
- assertEquals(oldLp, newLp);
+ assertTrue(cbi.getLp().isPrivateDnsActive());
+ assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName());
}
@Test
@@ -4100,16 +4886,17 @@
mCellNetworkAgent.sendLinkProperties(lp);
mCellNetworkAgent.connect(false);
waitForIdle();
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES,
+ cellNetworkCallback.expectCallback(CallbackRecord.AVAILABLE, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED,
mCellNetworkAgent);
- CallbackInfo cbi = cellNetworkCallback.expectCallback(
- CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ CallbackRecord.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback(
+ CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ cellNetworkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
- assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
- assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+ assertFalse(cbi.getLp().isPrivateDnsActive());
+ assertNull(cbi.getLp().getPrivateDnsServerName());
Set<InetAddress> dnsServers = new HashSet<>();
- checkDnsServers(cbi.arg, dnsServers);
+ checkDnsServers(cbi.getLp(), dnsServers);
// Send a validation event for a server that is not part of the current
// resolver config. The validation event should be ignored.
@@ -4121,13 +4908,13 @@
LinkProperties lp2 = new LinkProperties(lp);
lp2.addDnsServer(InetAddress.getByName("145.100.185.16"));
mCellNetworkAgent.sendLinkProperties(lp2);
- cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+ cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
- assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
- assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+ assertFalse(cbi.getLp().isPrivateDnsActive());
+ assertNull(cbi.getLp().getPrivateDnsServerName());
dnsServers.add(InetAddress.getByName("145.100.185.16"));
- checkDnsServers(cbi.arg, dnsServers);
+ checkDnsServers(cbi.getLp(), dnsServers);
// Send a validation event containing a hostname that is not part of
// the current resolver config. The validation event should be ignored.
@@ -4145,39 +4932,39 @@
// private dns fields should be sent.
mService.mNetdEventCallback.onPrivateDnsValidationEvent(
mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true);
- cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+ cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
- assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
- assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
- checkDnsServers(cbi.arg, dnsServers);
+ assertTrue(cbi.getLp().isPrivateDnsActive());
+ assertNull(cbi.getLp().getPrivateDnsServerName());
+ checkDnsServers(cbi.getLp(), dnsServers);
// The private dns fields in LinkProperties should be preserved when
// the network agent sends unrelated changes.
LinkProperties lp3 = new LinkProperties(lp2);
lp3.setMtu(1300);
mCellNetworkAgent.sendLinkProperties(lp3);
- cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+ cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
- assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
- assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
- checkDnsServers(cbi.arg, dnsServers);
- assertEquals(1300, ((LinkProperties)cbi.arg).getMtu());
+ assertTrue(cbi.getLp().isPrivateDnsActive());
+ assertNull(cbi.getLp().getPrivateDnsServerName());
+ checkDnsServers(cbi.getLp(), dnsServers);
+ assertEquals(1300, cbi.getLp().getMtu());
// Removing the only validated server should affect the private dns
// fields in LinkProperties.
LinkProperties lp4 = new LinkProperties(lp3);
lp4.removeDnsServer(InetAddress.getByName("145.100.185.16"));
mCellNetworkAgent.sendLinkProperties(lp4);
- cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+ cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
- assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
- assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+ assertFalse(cbi.getLp().isPrivateDnsActive());
+ assertNull(cbi.getLp().getPrivateDnsServerName());
dnsServers.remove(InetAddress.getByName("145.100.185.16"));
- checkDnsServers(cbi.arg, dnsServers);
- assertEquals(1300, ((LinkProperties)cbi.arg).getMtu());
+ checkDnsServers(cbi.getLp(), dnsServers);
+ assertEquals(1300, cbi.getLp().getMtu());
}
private void checkDirectlyConnectedRoutes(Object callbackObj,
@@ -4204,31 +4991,8 @@
assertTrue(lp.getDnsServers().containsAll(dnsServers));
}
- private static <T> void assertEmpty(T[] ts) {
- int length = ts.length;
- assertEquals("expected empty array, but length was " + length, 0, length);
- }
-
- private static <T> void assertLength(int expected, T[] got) {
- int length = got.length;
- assertEquals(String.format("expected array of length %s, but length was %s for %s",
- expected, length, Arrays.toString(got)), expected, length);
- }
-
- private static <T> void assertException(Runnable block, Class<T> expected) {
- try {
- block.run();
- fail("Expected exception of type " + expected);
- } catch (Exception got) {
- if (!got.getClass().equals(expected)) {
- fail("Expected exception of type " + expected + " but got " + got);
- }
- return;
- }
- }
-
@Test
- public void testVpnNetworkActive() {
+ public void testVpnNetworkActive() throws Exception {
final int uid = Process.myUid();
final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
@@ -4266,6 +5030,11 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
+ // VPN networks do not satisfy the default request and are automatically validated
+ // by NetworkMonitor
+ assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
+ vpnNetworkAgent.setNetworkValid();
+
vpnNetworkAgent.connect(false);
mMockVpn.connect();
mMockVpn.setUnderlyingNetworks(new Network[0]);
@@ -4277,19 +5046,19 @@
defaultCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
- genericNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
genericNotVpnNetworkCallback.assertNoCallback();
- vpnNetworkCallback.expectCapabilitiesLike(nc -> null == nc.getUids(), vpnNetworkAgent);
- defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, nc -> null == nc.getUids());
+ defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
ranges.clear();
vpnNetworkAgent.setUids(ranges);
- genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
- vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ vpnNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
// TODO : The default network callback should actually get a LOST call here (also see the
// comment below for AVAILABLE). This is because ConnectivityService does not look at UID
@@ -4297,7 +5066,7 @@
// can't currently update their UIDs without disconnecting, so this does not matter too
// much, but that is the reason the test here has to check for an update to the
// capabilities instead of the expected LOST then AVAILABLE.
- defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
ranges.add(new UidRange(uid, uid));
mMockVpn.setUids(ranges);
@@ -4309,23 +5078,23 @@
vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
// TODO : Here like above, AVAILABLE would be correct, but because this can't actually
// happen outside of the test, ConnectivityService does not rematch callbacks.
- defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
mWiFiNetworkAgent.disconnect();
- genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- genericNotVpnNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ genericNotVpnNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
vpnNetworkCallback.assertNoCallback();
defaultCallback.assertNoCallback();
vpnNetworkAgent.disconnect();
- genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ genericNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
genericNotVpnNetworkCallback.assertNoCallback();
wifiNetworkCallback.assertNoCallback();
- vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
- defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ vpnNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
assertEquals(null, mCm.getActiveNetwork());
mCm.unregisterNetworkCallback(genericNetworkCallback);
@@ -4335,7 +5104,7 @@
}
@Test
- public void testVpnWithoutInternet() {
+ public void testVpnWithoutInternet() throws Exception {
final int uid = Process.myUid();
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
@@ -4365,7 +5134,7 @@
}
@Test
- public void testVpnWithInternet() {
+ public void testVpnWithInternet() throws Exception {
final int uid = Process.myUid();
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
@@ -4389,14 +5158,66 @@
assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
vpnNetworkAgent.disconnect();
- defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+ defaultCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
mCm.unregisterNetworkCallback(defaultCallback);
}
@Test
- public void testVpnSetUnderlyingNetworks() {
+ public void testVpnUnvalidated() throws Exception {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(callback);
+
+ // Bring up Ethernet.
+ mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+ mEthernetNetworkAgent.connect(true);
+ callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+ callback.assertNoCallback();
+
+ // Bring up a VPN that has the INTERNET capability, initially unvalidated.
+ final int uid = Process.myUid();
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */);
+ mMockVpn.connect();
+
+ // Even though the VPN is unvalidated, it becomes the default network for our app.
+ callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+ // TODO: this looks like a spurious callback.
+ callback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
+ callback.assertNoCallback();
+
+ assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore());
+ assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, vpnNetworkAgent.getScore());
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+ assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED));
+ assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET));
+
+ assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
+ assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired(
+ vpnNetworkAgent.mNetworkCapabilities));
+
+ // Pretend that the VPN network validates.
+ vpnNetworkAgent.setNetworkValid();
+ vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ // Expect to see the validated capability, but no other changes, because the VPN is already
+ // the default network for the app.
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, vpnNetworkAgent);
+ callback.assertNoCallback();
+
+ vpnNetworkAgent.disconnect();
+ callback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
+ callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent);
+ }
+
+ @Test
+ public void testVpnSetUnderlyingNetworks() throws Exception {
final int uid = Process.myUid();
final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
@@ -4431,10 +5252,10 @@
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
@@ -4443,58 +5264,58 @@
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
// Don't disconnect, but note the VPN is not using wifi any more.
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
// Use Wifi but not cell. Note the VPN is now unmetered.
mService.setUnderlyingNetworksForVpn(
new Network[] { mWiFiNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
- && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
// Use both again.
mService.setUnderlyingNetworksForVpn(
new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
// Disconnect cell. Receive update without even removing the dead network from the
// underlying networks – it's dead anyway. Not metered any more.
mCellNetworkAgent.disconnect();
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
- && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
// Disconnect wifi too. No underlying networks means this is now metered.
mWiFiNetworkAgent.disconnect();
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
mMockVpn.disconnect();
}
@Test
- public void testNullUnderlyingNetworks() {
+ public void testNullUnderlyingNetworks() throws Exception {
final int uid = Process.myUid();
final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
@@ -4527,20 +5348,20 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
// Connect to WiFi; WiFi is the new default.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
mWiFiNetworkAgent.connect(true);
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
- && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
// Disconnect Cell. The default network did not change, so there shouldn't be any changes in
// the capabilities.
@@ -4549,11 +5370,824 @@
// Disconnect wifi too. Now we have no default network.
mWiFiNetworkAgent.disconnect();
- vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+ vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+ (caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
- && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- vpnNetworkAgent);
+ && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
mMockVpn.disconnect();
}
+
+ @Test
+ public void testIsActiveNetworkMeteredOverWifi() throws Exception {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+
+ assertFalse(mCm.isActiveNetworkMetered());
+ }
+
+ @Test
+ public void testIsActiveNetworkMeteredOverCell() throws Exception {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+
+ assertTrue(mCm.isActiveNetworkMetered());
+ }
+
+ @Test
+ public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() throws Exception {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // Connect VPN network. By default it is using current default network (Cell).
+ MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ final int uid = Process.myUid();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ waitForIdle();
+ // Ensure VPN is now the active network.
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Expect VPN to be metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // Connect WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ // VPN should still be the active network.
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // Expect VPN to be unmetered as it should now be using WiFi (new default).
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Disconnecting Cell should not affect VPN's meteredness.
+ mCellNetworkAgent.disconnect();
+ waitForIdle();
+
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Disconnect WiFi; Now there is no platform default network.
+ mWiFiNetworkAgent.disconnect();
+ waitForIdle();
+
+ // VPN without any underlying networks is treated as metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
+ }
+
+ @Test
+ public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() throws Exception {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Connect VPN network.
+ MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ final int uid = Process.myUid();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ waitForIdle();
+ // Ensure VPN is now the active network.
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ // VPN is using Cell
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Expect VPN to be metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // VPN is now using WiFi
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mWiFiNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Expect VPN to be unmetered
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // VPN is using Cell | WiFi.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Expect VPN to be metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // VPN is using WiFi | Cell.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mWiFiNetworkAgent.getNetwork(), mCellNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Order should not matter and VPN should still be metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // VPN is not using any underlying networks.
+ mService.setUnderlyingNetworksForVpn(new Network[0]);
+ waitForIdle();
+
+ // VPN without underlying networks is treated as metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ vpnNetworkAgent.disconnect();
+ mMockVpn.disconnect();
+ }
+
+ @Test
+ public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() throws Exception {
+ // Returns true by default when no network is available.
+ assertTrue(mCm.isActiveNetworkMetered());
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertFalse(mCm.isActiveNetworkMetered());
+
+ // Connect VPN network.
+ MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ final int uid = Process.myUid();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.setUids(ranges);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connectAsAlwaysMetered();
+ waitForIdle();
+ assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ // VPN is tracking current platform default (WiFi).
+ mService.setUnderlyingNetworksForVpn(null);
+ waitForIdle();
+
+ // Despite VPN using WiFi (which is unmetered), VPN itself is marked as always metered.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // VPN explicitly declares WiFi as its underlying network.
+ mService.setUnderlyingNetworksForVpn(
+ new Network[] { mWiFiNetworkAgent.getNetwork() });
+ waitForIdle();
+
+ // Doesn't really matter whether VPN declares its underlying networks explicitly.
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ // With WiFi lost, VPN is basically without any underlying networks. And in that case it is
+ // anyways suppose to be metered.
+ mWiFiNetworkAgent.disconnect();
+ waitForIdle();
+
+ assertTrue(mCm.isActiveNetworkMetered());
+
+ vpnNetworkAgent.disconnect();
+ }
+
+ @Test
+ public void testNetworkBlockedStatus() throws Exception {
+ final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build();
+ mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
+
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ mService.setUidRulesChanged(RULE_REJECT_ALL);
+ cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+
+ // ConnectivityService should cache it not to invoke the callback again.
+ mService.setUidRulesChanged(RULE_REJECT_METERED);
+ cellNetworkCallback.assertNoCallback();
+
+ mService.setUidRulesChanged(RULE_NONE);
+ cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+
+ mService.setUidRulesChanged(RULE_REJECT_METERED);
+ cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+
+ // Restrict the network based on UID rule and NOT_METERED capability change.
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
+ cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+ mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
+ mCellNetworkAgent);
+ cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+ mService.setUidRulesChanged(RULE_ALLOW_METERED);
+ cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+
+ mService.setUidRulesChanged(RULE_NONE);
+ cellNetworkCallback.assertNoCallback();
+
+ // Restrict the network based on BackgroundRestricted.
+ mService.setRestrictBackgroundChanged(true);
+ cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+ mService.setRestrictBackgroundChanged(true);
+ cellNetworkCallback.assertNoCallback();
+ mService.setRestrictBackgroundChanged(false);
+ cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+ cellNetworkCallback.assertNoCallback();
+
+ mCm.unregisterNetworkCallback(cellNetworkCallback);
+ }
+
+ @Test
+ public void testNetworkBlockedStatusBeforeAndAfterConnect() throws Exception {
+ final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(defaultCallback);
+
+ // No Networkcallbacks invoked before any network is active.
+ mService.setUidRulesChanged(RULE_REJECT_ALL);
+ mService.setUidRulesChanged(RULE_NONE);
+ mService.setUidRulesChanged(RULE_REJECT_METERED);
+ defaultCallback.assertNoCallback();
+
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+ defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent);
+
+ // Allow to use the network after switching to NOT_METERED network.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.connect(true);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+
+ // Switch to METERED network. Restrict the use of the network.
+ mWiFiNetworkAgent.disconnect();
+ defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellNetworkAgent);
+
+ // Network becomes NOT_METERED.
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
+ defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+
+ // Verify there's no Networkcallbacks invoked after data saver on/off.
+ mService.setRestrictBackgroundChanged(true);
+ mService.setRestrictBackgroundChanged(false);
+ defaultCallback.assertNoCallback();
+
+ mCellNetworkAgent.disconnect();
+ defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ defaultCallback.assertNoCallback();
+
+ mCm.unregisterNetworkCallback(defaultCallback);
+ }
+
+ /**
+ * Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info.
+ */
+ private InterfaceConfiguration getClatInterfaceConfig(LinkAddress la) {
+ InterfaceConfiguration cfg = new InterfaceConfiguration();
+ cfg.setHardwareAddress("11:22:33:44:55:66");
+ cfg.setLinkAddress(la);
+ return cfg;
+ }
+
+ /**
+ * Make expected stack link properties, copied from Nat464Xlat.
+ */
+ private LinkProperties makeClatLinkProperties(LinkAddress la) {
+ LinkAddress clatAddress = la;
+ LinkProperties stacked = new LinkProperties();
+ stacked.setInterfaceName(CLAT_PREFIX + MOBILE_IFNAME);
+ RouteInfo ipv4Default = new RouteInfo(
+ new LinkAddress(Inet4Address.ANY, 0),
+ clatAddress.getAddress(), CLAT_PREFIX + MOBILE_IFNAME);
+ stacked.addRoute(ipv4Default);
+ stacked.addLinkAddress(clatAddress);
+ return stacked;
+ }
+
+ @Test
+ public void testStackedLinkProperties() throws Exception {
+ final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24");
+ final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64");
+ final String kNat64PrefixString = "2001:db8:64:64:64:64::";
+ final IpPrefix kNat64Prefix = new IpPrefix(InetAddress.getByName(kNat64PrefixString), 96);
+
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(networkRequest, networkCallback);
+
+ // Prepare ipv6 only link properties.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+ final LinkProperties cellLp = new LinkProperties();
+ cellLp.setInterfaceName(MOBILE_IFNAME);
+ cellLp.addLinkAddress(myIpv6);
+ cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME));
+ cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME));
+ reset(mNetworkManagementService);
+ reset(mMockDnsResolver);
+ reset(mMockNetd);
+ when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME))
+ .thenReturn(getClatInterfaceConfig(myIpv4));
+
+ // Connect with ipv6 link properties. Expect prefix discovery to be started.
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ mCellNetworkAgent.connect(true);
+
+ verify(mMockNetd, times(1)).networkCreatePhysical(eq(cellNetId), anyInt());
+ verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
+
+ networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
+
+ // Switching default network updates TCP buffer sizes.
+ verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
+
+ // Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that
+ // the NAT64 prefix was removed because one was never discovered.
+ cellLp.addLinkAddress(myIpv4);
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
+ verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
+
+ verifyNoMoreInteractions(mMockNetd);
+ verifyNoMoreInteractions(mMockDnsResolver);
+ reset(mMockNetd);
+ reset(mMockDnsResolver);
+
+ // Remove IPv4 address. Expect prefix discovery to be started again.
+ cellLp.removeLinkAddress(myIpv4);
+ cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
+
+ // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started.
+ Nat464Xlat clat = mService.getNat464Xlat(mCellNetworkAgent);
+ assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix());
+ mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
+ kNat64PrefixString, 96);
+ LinkProperties lpBeforeClat = networkCallback.expectCallback(
+ CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp();
+ assertEquals(0, lpBeforeClat.getStackedLinks().size());
+ assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix());
+ verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
+
+ // Clat iface comes up. Expect stacked link to be added.
+ clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork())
+ .getStackedLinks();
+ assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0));
+
+ // Change trivial linkproperties and see if stacked link is preserved.
+ cellLp.addDnsServer(InetAddress.getByName("8.8.8.8"));
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+
+ List<LinkProperties> stackedLpsAfterChange =
+ mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getStackedLinks();
+ assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST);
+ assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0));
+
+ verify(mMockDnsResolver, times(1)).setResolverConfiguration(
+ mResolverParamsParcelCaptor.capture());
+ ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
+ assertEquals(1, resolvrParams.servers.length);
+ assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8"));
+
+ // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
+ // linkproperties are cleaned up.
+ cellLp.addLinkAddress(myIpv4);
+ cellLp.addRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
+ verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
+
+ // As soon as stop is called, the linkproperties lose the stacked interface.
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
+ LinkProperties expected = new LinkProperties(cellLp);
+ expected.setNat64Prefix(kNat64Prefix);
+ assertEquals(expected, actualLpAfterIpv4);
+ assertEquals(0, actualLpAfterIpv4.getStackedLinks().size());
+
+ // The interface removed callback happens but has no effect after stop is called.
+ clat.interfaceRemoved(CLAT_PREFIX + MOBILE_IFNAME);
+ networkCallback.assertNoCallback();
+
+ verifyNoMoreInteractions(mMockNetd);
+ verifyNoMoreInteractions(mMockDnsResolver);
+ reset(mMockNetd);
+ reset(mMockDnsResolver);
+
+ // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone.
+ mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
+ kNat64PrefixString, 96);
+ networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+ (lp) -> lp.getNat64Prefix() == null);
+
+ // Remove IPv4 address and expect prefix discovery and clatd to be started again.
+ cellLp.removeLinkAddress(myIpv4);
+ cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
+ cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8"));
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
+ mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
+ kNat64PrefixString, 96);
+ networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+ verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
+
+
+ // Clat iface comes up. Expect stacked link to be added.
+ clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
+ networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+ (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null);
+
+ // NAT64 prefix is removed. Expect that clat is stopped.
+ mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
+ kNat64PrefixString, 96);
+ networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+ (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null);
+ verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
+ networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+ (lp) -> lp.getStackedLinks().size() == 0);
+
+ // Clean up.
+ mCellNetworkAgent.disconnect();
+ networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ networkCallback.assertNoCallback();
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
+ @Test
+ public void testDataActivityTracking() throws Exception {
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ mCm.registerNetworkCallback(networkRequest, networkCallback);
+
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ final LinkProperties cellLp = new LinkProperties();
+ cellLp.setInterfaceName(MOBILE_IFNAME);
+ mCellNetworkAgent.sendLinkProperties(cellLp);
+ reset(mNetworkManagementService);
+ mCellNetworkAgent.connect(true);
+ networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(),
+ eq(ConnectivityManager.TYPE_MOBILE));
+
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ final LinkProperties wifiLp = new LinkProperties();
+ wifiLp.setInterfaceName(WIFI_IFNAME);
+ mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+
+ // Network switch
+ reset(mNetworkManagementService);
+ mWiFiNetworkAgent.connect(true);
+ networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ networkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
+ networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ verify(mNetworkManagementService, times(1)).addIdleTimer(eq(WIFI_IFNAME), anyInt(),
+ eq(ConnectivityManager.TYPE_WIFI));
+ verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(MOBILE_IFNAME));
+
+ // Disconnect wifi and switch back to cell
+ reset(mNetworkManagementService);
+ mWiFiNetworkAgent.disconnect();
+ networkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+ assertNoCallbacks(networkCallback);
+ verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME));
+ verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(),
+ eq(ConnectivityManager.TYPE_MOBILE));
+
+ // reconnect wifi
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ wifiLp.setInterfaceName(WIFI_IFNAME);
+ mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+ mWiFiNetworkAgent.connect(true);
+ networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ networkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
+ networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+
+ // Disconnect cell
+ reset(mNetworkManagementService);
+ reset(mMockNetd);
+ mCellNetworkAgent.disconnect();
+ networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+ // LOST callback is triggered earlier than removing idle timer. Broadcast should also be
+ // sent as network being switched. Ensure rule removal for cell will not be triggered
+ // unexpectedly before network being removed.
+ waitForIdle();
+ verify(mNetworkManagementService, times(0)).removeIdleTimer(eq(MOBILE_IFNAME));
+ verify(mMockNetd, times(1)).networkDestroy(eq(mCellNetworkAgent.getNetwork().netId));
+ verify(mMockDnsResolver, times(1))
+ .destroyNetworkCache(eq(mCellNetworkAgent.getNetwork().netId));
+
+ // Disconnect wifi
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ reset(mNetworkManagementService);
+ mWiFiNetworkAgent.disconnect();
+ waitFor(cv);
+ verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME));
+
+ // Clean up
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
+ private void verifyTcpBufferSizeChange(String tcpBufferSizes) throws Exception {
+ String[] values = tcpBufferSizes.split(",");
+ String rmemValues = String.join(" ", values[0], values[1], values[2]);
+ String wmemValues = String.join(" ", values[3], values[4], values[5]);
+ waitForIdle();
+ verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues);
+ reset(mMockNetd);
+ }
+
+ @Test
+ public void testTcpBufferReset() throws Exception {
+ final String testTcpBufferSizes = "1,2,3,4,5,6";
+
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ reset(mMockNetd);
+ // Switching default network updates TCP buffer sizes.
+ mCellNetworkAgent.connect(false);
+ verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
+
+ // Change link Properties should have updated tcp buffer size.
+ LinkProperties lp = new LinkProperties();
+ lp.setTcpBufferSizes(testTcpBufferSizes);
+ mCellNetworkAgent.sendLinkProperties(lp);
+ verifyTcpBufferSizeChange(testTcpBufferSizes);
+ }
+
+ @Test
+ public void testGetGlobalProxyForNetwork() throws Exception {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ final Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+ when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo);
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork));
+ }
+
+ @Test
+ public void testGetProxyForActiveNetwork() throws Exception {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ final LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(testProxyInfo);
+
+ mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ }
+
+ @Test
+ public void testGetProxyForVPN() throws Exception {
+ final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
+
+ // Set up a WiFi network with no proxy
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Set up a VPN network with a proxy
+ final int uid = Process.myUid();
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+ final ArraySet<UidRange> ranges = new ArraySet<>();
+ ranges.add(new UidRange(uid, uid));
+ mMockVpn.setUids(ranges);
+ LinkProperties testLinkProperties = new LinkProperties();
+ testLinkProperties.setHttpProxy(testProxyInfo);
+ vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+
+ // Connect to VPN with proxy
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ vpnNetworkAgent.connect(true);
+ mMockVpn.connect();
+ waitForIdle();
+
+ // Test that the VPN network returns a proxy, and the WiFi does not.
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+
+ // Test that the VPN network returns no proxy when it is set to null.
+ testLinkProperties.setHttpProxy(null);
+ vpnNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(vpnNetworkAgent.getNetwork()));
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Set WiFi proxy and check that the vpn proxy is still null.
+ testLinkProperties.setHttpProxy(testProxyInfo);
+ mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+ waitForIdle();
+ assertNull(mService.getProxyForNetwork(null));
+
+ // Disconnect from VPN and check that the active network, which is now the WiFi, has the
+ // correct proxy setting.
+ vpnNetworkAgent.disconnect();
+ waitForIdle();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+ assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
+ }
+
+ @Test
+ public void testFullyRoutedVpnResultsInInterfaceFilteringRules() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+
+ // Connected VPN should have interface rules set up. There are two expected invocations,
+ // one during VPN uid update, one during VPN LinkProperties update
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
+ assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
+
+ vpnNetworkAgent.disconnect();
+ waitForIdle();
+
+ // Disconnected VPN should have interface rules removed
+ verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
+ }
+
+ @Test
+ public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
+
+ // Legacy VPN should not have interface rules set up
+ verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ }
+
+ @Test
+ public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule()
+ throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0"));
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
+
+ // IPv6 unreachable route should not be misinterpreted as a default route
+ verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ }
+
+ @Test
+ public void testVpnHandoverChangesInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+
+ // Connected VPN should have interface rules set up. There are two expected invocations,
+ // one during VPN uid update, one during VPN LinkProperties update
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ InOrder inOrder = inOrder(mMockNetd);
+ lp.setInterfaceName("tun1");
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // VPN handover (switch to a new interface) should result in rules being updated (old rules
+ // removed first, then new rules added)
+ inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ lp = new LinkProperties();
+ lp.setInterfaceName("tun1");
+ lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // VPN not routing everything should no longer have interface filtering rules
+ verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ lp = new LinkProperties();
+ lp.setInterfaceName("tun1");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // Back to routing all IPv6 traffic should have filtering rules
+ verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ }
+
+ @Test
+ public void testUidUpdateChangesInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final UidRange vpnRange = UidRange.createForUser(VPN_USER);
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID,
+ Collections.singleton(vpnRange));
+
+ reset(mMockNetd);
+ InOrder inOrder = inOrder(mMockNetd);
+
+ // Update to new range which is old range minus APP1, i.e. only APP2
+ final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
+ new UidRange(vpnRange.start, APP1_UID - 1),
+ new UidRange(APP1_UID + 1, vpnRange.stop)));
+ vpnNetworkAgent.setUids(newRanges);
+ waitForIdle();
+
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ // Verify old rules are removed before new rules are added
+ inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP2_UID);
+ }
+
+
+ private MockNetworkAgent establishVpn(LinkProperties lp, int establishingUid,
+ Set<UidRange> vpnRange) throws Exception {
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN, lp);
+ vpnNetworkAgent.getNetworkCapabilities().setEstablishingVpnAppUid(establishingUid);
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.connect();
+ mMockVpn.setUids(vpnRange);
+ vpnNetworkAgent.connect(true);
+ waitForIdle();
+ return vpnNetworkAgent;
+ }
+
+ private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.requestedPermissions = new String[0];
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.privateFlags = 0;
+ packageInfo.applicationInfo.uid = UserHandle.getUid(UserHandle.USER_SYSTEM,
+ UserHandle.getAppId(uid));
+ return packageInfo;
+ }
}
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index e573d35..7c40adf 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -16,6 +16,9 @@
package com.android.server;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -34,26 +37,30 @@
import android.net.IpSecConfig;
import android.net.IpSecManager;
import android.net.IpSecSpiResponse;
+import android.net.IpSecTransform;
import android.net.IpSecTransformResponse;
import android.net.IpSecTunnelInterfaceResponse;
+import android.net.IpSecUdpEncapResponse;
import android.net.LinkAddress;
import android.net.Network;
import android.net.NetworkUtils;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
-import android.test.mock.MockContext;
-import android.support.test.filters.SmallTest;
import android.system.Os;
+import android.test.mock.MockContext;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.net.Socket;
import java.util.Arrays;
import java.util.Collection;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
/** Unit tests for {@link IpSecService}. */
@SmallTest
@RunWith(Parameterized.class)
@@ -61,16 +68,20 @@
private static final int TEST_SPI = 0xD1201D;
- private final String mDestinationAddr;
private final String mSourceAddr;
+ private final String mDestinationAddr;
private final LinkAddress mLocalInnerAddress;
+ private final int mFamily;
+
+ private static final int[] ADDRESS_FAMILIES =
+ new int[] {AF_INET, AF_INET6};
@Parameterized.Parameters
public static Collection ipSecConfigs() {
return Arrays.asList(
new Object[][] {
- {"1.2.3.4", "8.8.4.4", "10.0.1.1/24"},
- {"2601::2", "2601::10", "2001:db8::1/64"}
+ {"1.2.3.4", "8.8.4.4", "10.0.1.1/24", AF_INET},
+ {"2601::2", "2601::10", "2001:db8::1/64", AF_INET6}
});
}
@@ -120,6 +131,7 @@
IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
IpSecService mIpSecService;
Network fakeNetwork = new Network(0xAB);
+ int mUid = Os.getuid();
private static final IpSecAlgorithm AUTH_ALGO =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4);
@@ -127,12 +139,14 @@
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
private static final IpSecAlgorithm AEAD_ALGO =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
+ private static final int REMOTE_ENCAP_PORT = 4500;
public IpSecServiceParameterizedTest(
- String sourceAddr, String destAddr, String localInnerAddr) {
+ String sourceAddr, String destAddr, String localInnerAddr, int family) {
mSourceAddr = sourceAddr;
mDestinationAddr = destAddr;
mLocalInnerAddress = new LinkAddress(localInnerAddr);
+ mFamily = family;
}
@Before
@@ -155,6 +169,8 @@
.thenReturn(AppOpsManager.MODE_IGNORED);
}
+ //TODO: Add a test to verify SPI.
+
@Test
public void testIpSecServiceReserveSpi() throws Exception {
when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI)))
@@ -180,16 +196,16 @@
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
- eq(spiResp.resourceId),
+ eq(mUid),
anyString(),
anyString(),
eq(TEST_SPI),
anyInt(),
+ anyInt(),
anyInt());
// Verify quota and RefcountedResource objects cleaned up
- IpSecService.UserRecord userRecord =
- mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
try {
userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId);
@@ -208,8 +224,7 @@
mIpSecService.allocateSecurityParameterIndex(
mDestinationAddr, TEST_SPI, new Binder());
- IpSecService.UserRecord userRecord =
- mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
IpSecService.RefcountedResource refcountedRecord =
userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId);
@@ -217,11 +232,12 @@
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
- eq(spiResp.resourceId),
+ eq(mUid),
anyString(),
anyString(),
eq(TEST_SPI),
anyInt(),
+ anyInt(),
anyInt());
// Verify quota and RefcountedResource objects cleaned up
@@ -257,6 +273,48 @@
config.setAuthentication(AUTH_ALGO);
}
+ private void addEncapSocketToIpSecConfig(int resourceId, IpSecConfig config) throws Exception {
+ config.setEncapType(IpSecTransform.ENCAP_ESPINUDP);
+ config.setEncapSocketResourceId(resourceId);
+ config.setEncapRemotePort(REMOTE_ENCAP_PORT);
+ }
+
+ private void verifyTransformNetdCalledForCreatingSA(
+ IpSecConfig config, IpSecTransformResponse resp) throws Exception {
+ verifyTransformNetdCalledForCreatingSA(config, resp, 0);
+ }
+
+ private void verifyTransformNetdCalledForCreatingSA(
+ IpSecConfig config, IpSecTransformResponse resp, int encapSocketPort) throws Exception {
+ IpSecAlgorithm auth = config.getAuthentication();
+ IpSecAlgorithm crypt = config.getEncryption();
+ IpSecAlgorithm authCrypt = config.getAuthenticatedEncryption();
+
+ verify(mMockNetd, times(1))
+ .ipSecAddSecurityAssociation(
+ eq(mUid),
+ eq(config.getMode()),
+ eq(config.getSourceAddress()),
+ eq(config.getDestinationAddress()),
+ eq((config.getNetwork() != null) ? config.getNetwork().netId : 0),
+ eq(TEST_SPI),
+ eq(0),
+ eq(0),
+ eq((auth != null) ? auth.getName() : ""),
+ eq((auth != null) ? auth.getKey() : new byte[] {}),
+ eq((auth != null) ? auth.getTruncationLengthBits() : 0),
+ eq((crypt != null) ? crypt.getName() : ""),
+ eq((crypt != null) ? crypt.getKey() : new byte[] {}),
+ eq((crypt != null) ? crypt.getTruncationLengthBits() : 0),
+ eq((authCrypt != null) ? authCrypt.getName() : ""),
+ eq((authCrypt != null) ? authCrypt.getKey() : new byte[] {}),
+ eq((authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0),
+ eq(config.getEncapType()),
+ eq(encapSocketPort),
+ eq(config.getEncapRemotePort()),
+ eq(config.getXfrmInterfaceId()));
+ }
+
@Test
public void testCreateTransform() throws Exception {
IpSecConfig ipSecConfig = new IpSecConfig();
@@ -267,28 +325,7 @@
mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
- verify(mMockNetd)
- .ipSecAddSecurityAssociation(
- eq(createTransformResp.resourceId),
- anyInt(),
- anyString(),
- anyString(),
- anyInt(),
- eq(TEST_SPI),
- anyInt(),
- anyInt(),
- eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
- eq(AUTH_KEY),
- anyInt(),
- eq(IpSecAlgorithm.CRYPT_AES_CBC),
- eq(CRYPT_KEY),
- anyInt(),
- eq(""),
- eq(new byte[] {}),
- eq(0),
- anyInt(),
- anyInt(),
- anyInt());
+ verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
}
@Test
@@ -302,28 +339,59 @@
mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
assertEquals(IpSecManager.Status.OK, createTransformResp.status);
- verify(mMockNetd)
- .ipSecAddSecurityAssociation(
- eq(createTransformResp.resourceId),
- anyInt(),
- anyString(),
- anyString(),
- anyInt(),
- eq(TEST_SPI),
- anyInt(),
- anyInt(),
- eq(""),
- eq(new byte[] {}),
- eq(0),
- eq(""),
- eq(new byte[] {}),
- eq(0),
- eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
- eq(AEAD_KEY),
- anyInt(),
- anyInt(),
- anyInt(),
- anyInt());
+ verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
+ }
+
+ @Test
+ public void testCreateTransportModeTransformWithEncap() throws Exception {
+ IpSecUdpEncapResponse udpSock = mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ ipSecConfig.setMode(IpSecTransform.MODE_TRANSPORT);
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
+ addEncapSocketToIpSecConfig(udpSock.resourceId, ipSecConfig);
+
+ if (mFamily == AF_INET) {
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ assertEquals(IpSecManager.Status.OK, createTransformResp.status);
+
+ verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
+ } else {
+ try {
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+ }
+
+ @Test
+ public void testCreateTunnelModeTransformWithEncap() throws Exception {
+ IpSecUdpEncapResponse udpSock = mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
+ addEncapSocketToIpSecConfig(udpSock.resourceId, ipSecConfig);
+
+ if (mFamily == AF_INET) {
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ assertEquals(IpSecManager.Status.OK, createTransformResp.status);
+
+ verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port);
+ } else {
+ try {
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
}
@Test
@@ -360,17 +428,17 @@
IpSecTransformResponse createTransformResp =
mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
- IpSecService.UserRecord userRecord =
- mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId());
verify(mMockNetd, times(0))
.ipSecDeleteSecurityAssociation(
- eq(createTransformResp.resourceId),
+ eq(mUid),
anyString(),
anyString(),
eq(TEST_SPI),
anyInt(),
+ anyInt(),
anyInt());
// quota is not released until the SPI is released by the Transform
assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
@@ -388,16 +456,16 @@
verify(mMockNetd, times(1))
.ipSecDeleteSecurityAssociation(
- eq(createTransformResp.resourceId),
+ eq(mUid),
anyString(),
anyString(),
eq(TEST_SPI),
anyInt(),
+ anyInt(),
anyInt());
// Verify quota and RefcountedResource objects cleaned up
- IpSecService.UserRecord userRecord =
- mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent);
assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent);
@@ -411,6 +479,7 @@
anyString(),
anyInt(),
anyInt(),
+ anyInt(),
anyInt());
assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent);
@@ -432,8 +501,7 @@
IpSecTransformResponse createTransformResp =
mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
- IpSecService.UserRecord userRecord =
- mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
IpSecService.RefcountedResource refcountedRecord =
userRecord.mTransformRecords.getRefcountedResourceOrThrow(
createTransformResp.resourceId);
@@ -442,11 +510,12 @@
verify(mMockNetd)
.ipSecDeleteSecurityAssociation(
- eq(createTransformResp.resourceId),
+ eq(mUid),
anyString(),
anyString(),
eq(TEST_SPI),
anyInt(),
+ anyInt(),
anyInt());
// Verify quota and RefcountedResource objects cleaned up
@@ -468,15 +537,18 @@
IpSecTransformResponse createTransformResp =
mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
- ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
+
+ Socket socket = new Socket();
+ socket.bind(null);
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
int resourceId = createTransformResp.resourceId;
mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId);
verify(mMockNetd)
.ipSecApplyTransportModeTransform(
- eq(pfd.getFileDescriptor()),
- eq(resourceId),
+ eq(pfd),
+ eq(mUid),
eq(IpSecManager.DIRECTION_OUT),
anyString(),
anyString(),
@@ -485,10 +557,12 @@
@Test
public void testRemoveTransportModeTransform() throws Exception {
- ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
+ Socket socket = new Socket();
+ socket.bind(null);
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
mIpSecService.removeTransportModeTransforms(pfd);
- verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
+ verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd);
}
private IpSecTunnelInterfaceResponse createAndValidateTunnel(
@@ -508,19 +582,19 @@
createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
// Check that we have stored the tracking object, and retrieve it
- IpSecService.UserRecord userRecord =
- mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
IpSecService.RefcountedResource refcountedRecord =
userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
createTunnelResp.resourceId);
assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent);
verify(mMockNetd)
- .addVirtualTunnelInterface(
+ .ipSecAddTunnelInterface(
eq(createTunnelResp.interfaceName),
eq(mSourceAddr),
eq(mDestinationAddr),
anyInt(),
+ anyInt(),
anyInt());
}
@@ -529,14 +603,13 @@
IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
- IpSecService.UserRecord userRecord =
- mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, "blessedPackage");
// Verify quota and RefcountedResource objects cleaned up
assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
- verify(mMockNetd).removeVirtualTunnelInterface(eq(createTunnelResp.interfaceName));
+ verify(mMockNetd).ipSecRemoveTunnelInterface(eq(createTunnelResp.interfaceName));
try {
userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
createTunnelResp.resourceId);
@@ -550,8 +623,7 @@
IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
- IpSecService.UserRecord userRecord =
- mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid());
+ IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid);
IpSecService.RefcountedResource refcountedRecord =
userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
createTunnelResp.resourceId);
@@ -560,7 +632,7 @@
// Verify quota and RefcountedResource objects cleaned up
assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent);
- verify(mMockNetd).removeVirtualTunnelInterface(eq(createTunnelResp.interfaceName));
+ verify(mMockNetd).ipSecRemoveTunnelInterface(eq(createTunnelResp.interfaceName));
try {
userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow(
createTunnelResp.resourceId);
@@ -570,6 +642,41 @@
}
@Test
+ public void testApplyTunnelModeTransform() throws Exception {
+ IpSecConfig ipSecConfig = new IpSecConfig();
+ ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
+ addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+ addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+ IpSecTransformResponse createTransformResp =
+ mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage");
+ IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage");
+
+ int transformResourceId = createTransformResp.resourceId;
+ int tunnelResourceId = createTunnelResp.resourceId;
+ mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT,
+ transformResourceId, "blessedPackage");
+
+ for (int selAddrFamily : ADDRESS_FAMILIES) {
+ verify(mMockNetd)
+ .ipSecUpdateSecurityPolicy(
+ eq(mUid),
+ eq(selAddrFamily),
+ eq(IpSecManager.DIRECTION_OUT),
+ anyString(),
+ anyString(),
+ eq(TEST_SPI),
+ anyInt(), // iKey/oKey
+ anyInt(), // mask
+ eq(tunnelResourceId));
+ }
+
+ ipSecConfig.setXfrmInterfaceId(tunnelResourceId);
+ verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
+ }
+
+ @Test
public void testAddRemoveAddressFromTunnelInterface() throws Exception {
for (String pkgName : new String[]{"blessedPackage", "systemPackage"}) {
IpSecTunnelInterfaceResponse createTunnelResp =
@@ -592,6 +699,7 @@
}
}
+ @Ignore
@Test
public void testAddTunnelFailsForBadPackageName() throws Exception {
try {
diff --git a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index cf8f715..22a2c94 100644
--- a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
@@ -31,22 +32,23 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.server.IpSecService.IResource;
import com.android.server.IpSecService.RefcountedResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
/** Unit tests for {@link IpSecService.RefcountedResource}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -133,11 +135,11 @@
IBinder binderMock = mock(IBinder.class);
doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt());
- RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
-
- // Verify that cleanup is performed (Spy limitations prevent verification of method calls
- // for binder death scenario; check refcount to determine if cleanup was performed.)
- assertEquals(-1, refcountedResource.mRefCount);
+ try {
+ getTestRefcountedResource(binderMock);
+ fail("Expected exception to propogate when binder fails to link to death");
+ } catch (RuntimeException expected) {
+ }
}
@Test
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 2c94a60..4a35015 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -20,6 +20,7 @@
import static android.system.OsConstants.EADDRINUSE;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -39,19 +40,24 @@
import android.net.IpSecConfig;
import android.net.IpSecManager;
import android.net.IpSecSpiResponse;
-import android.net.IpSecTransform;
import android.net.IpSecUdpEncapResponse;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import dalvik.system.SocketTagger;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.ServerSocket;
@@ -60,11 +66,6 @@
import java.util.ArrayList;
import java.util.List;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-
/** Unit tests for {@link IpSecService}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -155,10 +156,21 @@
@Test
public void testOpenAndCloseUdpEncapsulationSocket() throws Exception {
- int localport = findUnusedPort();
+ int localport = -1;
+ IpSecUdpEncapResponse udpEncapResp = null;
- IpSecUdpEncapResponse udpEncapResp =
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ for (int i = 0; i < IpSecService.MAX_PORT_BIND_ATTEMPTS; i++) {
+ localport = findUnusedPort();
+
+ udpEncapResp = mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ assertNotNull(udpEncapResp);
+ if (udpEncapResp.status == IpSecManager.Status.OK) {
+ break;
+ }
+
+ // Else retry to reduce possibility for port-bind failures.
+ }
+
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
assertEquals(localport, udpEncapResp.port);
@@ -203,12 +215,11 @@
@Test
public void testOpenUdpEncapsulationSocketAfterClose() throws Exception {
- int localport = findUnusedPort();
IpSecUdpEncapResponse udpEncapResp =
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
- assertEquals(localport, udpEncapResp.port);
+ int localport = udpEncapResp.port;
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
udpEncapResp.fileDescriptor.close();
@@ -225,12 +236,11 @@
*/
@Test
public void testUdpEncapPortNotReleased() throws Exception {
- int localport = findUnusedPort();
IpSecUdpEncapResponse udpEncapResp =
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
- assertEquals(localport, udpEncapResp.port);
+ int localport = udpEncapResp.port;
udpEncapResp.fileDescriptor.close();
@@ -272,14 +282,11 @@
@Test
public void testOpenUdpEncapsulationSocketTwice() throws Exception {
- int localport = findUnusedPort();
-
IpSecUdpEncapResponse udpEncapResp =
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
- assertEquals(localport, udpEncapResp.port);
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ int localport = udpEncapResp.port;
IpSecUdpEncapResponse testUdpEncapResp =
mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
@@ -422,10 +429,12 @@
@Test
public void testRemoveTransportModeTransform() throws Exception {
- ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
+ Socket socket = new Socket();
+ socket.bind(null);
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
mIpSecService.removeTransportModeTransforms(pfd);
- verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
+ verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd);
}
@Test
@@ -620,10 +629,10 @@
mIpSecService.openUdpEncapsulationSocket(0, new Binder());
FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
- ArgumentMatcher<FileDescriptor> fdMatcher = (arg) -> {
+ ArgumentMatcher<ParcelFileDescriptor> fdMatcher = (arg) -> {
try {
StructStat sockStat = Os.fstat(sockFd);
- StructStat argStat = Os.fstat(arg);
+ StructStat argStat = Os.fstat(arg.getFileDescriptor());
return sockStat.st_ino == argStat.st_ino
&& sockStat.st_dev == argStat.st_dev;
diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
new file mode 100644
index 0000000..f045369
--- /dev/null
+++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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 android.net.ConnectivityManager.TYPE_ETHERNET
+import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.ConnectivityManager.TYPE_WIMAX
+import android.net.NetworkInfo.DetailedState.CONNECTED
+import android.net.NetworkInfo.DetailedState.DISCONNECTED
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.server.ConnectivityService.LegacyTypeTracker
+import com.android.server.connectivity.NetworkAgentInfo
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+const val UNSUPPORTED_TYPE = TYPE_WIMAX
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class LegacyTypeTrackerTest {
+ private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET)
+
+ private val mMockService = mock(ConnectivityService::class.java).apply {
+ doReturn(false).`when`(this).isDefaultNetwork(any())
+ }
+ private val mTracker = LegacyTypeTracker(mMockService).apply {
+ supportedTypes.forEach {
+ addSupportedType(it)
+ }
+ }
+
+ @Test
+ fun testSupportedTypes() {
+ try {
+ mTracker.addSupportedType(supportedTypes[0])
+ fail("Expected IllegalStateException")
+ } catch (expected: IllegalStateException) {}
+ supportedTypes.forEach {
+ assertTrue(mTracker.isTypeSupported(it))
+ }
+ assertFalse(mTracker.isTypeSupported(UNSUPPORTED_TYPE))
+ }
+
+ @Test
+ fun testAddNetwork() {
+ val mobileNai = mock(NetworkAgentInfo::class.java)
+ val wifiNai = mock(NetworkAgentInfo::class.java)
+ mTracker.add(TYPE_MOBILE, mobileNai)
+ mTracker.add(TYPE_WIFI, wifiNai)
+ assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+ assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+ // Make sure adding a second NAI does not change the results.
+ val secondMobileNai = mock(NetworkAgentInfo::class.java)
+ mTracker.add(TYPE_MOBILE, secondMobileNai)
+ assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+ assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+ // Make sure removing a network that wasn't added for this type is a no-op.
+ mTracker.remove(TYPE_MOBILE, wifiNai, false /* wasDefault */)
+ assertSame(mTracker.getNetworkForType(TYPE_MOBILE), mobileNai)
+ assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+ // Remove the top network for mobile and make sure the second one becomes the network
+ // of record for this type.
+ mTracker.remove(TYPE_MOBILE, mobileNai, false /* wasDefault */)
+ assertSame(mTracker.getNetworkForType(TYPE_MOBILE), secondMobileNai)
+ assertSame(mTracker.getNetworkForType(TYPE_WIFI), wifiNai)
+ // Make sure adding a network for an unsupported type does not register it.
+ mTracker.add(UNSUPPORTED_TYPE, mobileNai)
+ assertNull(mTracker.getNetworkForType(UNSUPPORTED_TYPE))
+ }
+
+ @Test
+ fun testBroadcastOnDisconnect() {
+ val mobileNai1 = mock(NetworkAgentInfo::class.java)
+ val mobileNai2 = mock(NetworkAgentInfo::class.java)
+ doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1)
+ mTracker.add(TYPE_MOBILE, mobileNai1)
+ verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE)
+ reset(mMockService)
+ doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2)
+ mTracker.add(TYPE_MOBILE, mobileNai2)
+ verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt())
+ mTracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */)
+ verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, DISCONNECTED, TYPE_MOBILE)
+ verify(mMockService).sendLegacyNetworkBroadcast(mobileNai2, CONNECTED, TYPE_MOBILE)
+ }
+}
diff --git a/tests/net/java/com/android/server/NetworkManagementServiceTest.java b/tests/net/java/com/android/server/NetworkManagementServiceTest.java
index 56a075b..968b307 100644
--- a/tests/net/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/net/java/com/android/server/NetworkManagementServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -23,28 +24,28 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.annotation.NonNull;
import android.content.Context;
import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
import android.net.LinkAddress;
-import android.net.LocalSocket;
-import android.net.LocalServerSocket;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.IBinder;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.app.IBatteryStats;
import com.android.server.NetworkManagementService.SystemServices;
import com.android.server.net.BaseNetworkObserver;
-import java.io.IOException;
-import java.io.OutputStream;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -55,16 +56,16 @@
@SmallTest
public class NetworkManagementServiceTest {
- private static final String SOCKET_NAME = "__test__NetworkManagementServiceTest";
private NetworkManagementService mNMService;
- private LocalServerSocket mServerSocket;
- private LocalSocket mSocket;
- private OutputStream mOutputStream;
@Mock private Context mContext;
@Mock private IBatteryStats.Stub mBatteryStatsService;
@Mock private INetd.Stub mNetdService;
+ @NonNull
+ @Captor
+ private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
+
private final SystemServices mServices = new SystemServices() {
@Override
public IBinder getService(String name) {
@@ -87,32 +88,15 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
-
- // Set up a sheltered test environment.
- mServerSocket = new LocalServerSocket(SOCKET_NAME);
-
+ doNothing().when(mNetdService)
+ .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
// Start the service and wait until it connects to our socket.
- mNMService = NetworkManagementService.create(mContext, SOCKET_NAME, mServices);
- mSocket = mServerSocket.accept();
- mOutputStream = mSocket.getOutputStream();
+ mNMService = NetworkManagementService.create(mContext, mServices);
}
@After
public void tearDown() throws Exception {
mNMService.shutdown();
- // Once NetworkManagementService#shutdown() actually does something and shutdowns
- // the underlying NativeDaemonConnector, the block below should be uncommented.
- // if (mOutputStream != null) mOutputStream.close();
- // if (mSocket != null) mSocket.close();
- // if (mServerSocket != null) mServerSocket.close();
- }
-
- /**
- * Sends a message on the netd socket and gives the events some time to make it back.
- */
- private void sendMessage(String message) throws IOException {
- // Strings are null-terminated, so add "\0" at the end.
- mOutputStream.write((message + "\0").getBytes());
}
private static <T> T expectSoon(T mock) {
@@ -130,125 +114,78 @@
// Forget everything that happened to the mock so far, so we can explicitly verify
// everything that happens and does not happen to it from now on.
- reset(observer);
- // Now send NetworkManagementService messages and ensure that the observer methods are
- // called. After every valid message we expect a callback soon after; to ensure that
+ INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
+ reset(observer);
+ // Now call unsolListener methods and ensure that the observer methods are
+ // called. After every method we expect a callback soon after; to ensure that
// invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
/**
* Interface changes.
*/
- sendMessage("600 Iface added rmnet12");
+ unsolListener.onInterfaceAdded("rmnet12");
expectSoon(observer).interfaceAdded("rmnet12");
- sendMessage("600 Iface removed eth1");
+ unsolListener.onInterfaceRemoved("eth1");
expectSoon(observer).interfaceRemoved("eth1");
- sendMessage("607 Iface removed eth1");
- // Invalid code.
-
- sendMessage("600 Iface borked lo down");
- // Invalid event.
-
- sendMessage("600 Iface changed clat4 up again");
- // Extra tokens.
-
- sendMessage("600 Iface changed clat4 up");
+ unsolListener.onInterfaceChanged("clat4", true);
expectSoon(observer).interfaceStatusChanged("clat4", true);
- sendMessage("600 Iface linkstate rmnet0 down");
+ unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
- sendMessage("600 IFACE linkstate clat4 up");
- // Invalid group.
-
/**
* Bandwidth control events.
*/
- sendMessage("601 limit alert data rmnet_usb0");
+ unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
expectSoon(observer).limitReached("data", "rmnet_usb0");
- sendMessage("601 invalid alert data rmnet0");
- // Invalid group.
-
- sendMessage("601 limit increased data rmnet0");
- // Invalid event.
-
-
/**
* Interface class activity.
*/
-
- sendMessage("613 IfaceClass active 1 1234 10012");
+ unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, 0);
expectSoon(observer).interfaceClassDataActivityChanged("1", true, 1234);
- sendMessage("613 IfaceClass idle 9 5678");
+ unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, 0);
expectSoon(observer).interfaceClassDataActivityChanged("9", false, 5678);
- sendMessage("613 IfaceClass reallyactive 9 4321");
+ unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, 0);
expectSoon(observer).interfaceClassDataActivityChanged("9", false, 4321);
- sendMessage("613 InterfaceClass reallyactive 1");
- // Invalid group.
-
-
/**
* IP address changes.
*/
- sendMessage("614 Address updated fe80::1/64 wlan0 128 253");
+ unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
- // There is no "added", so we take this as "removed".
- sendMessage("614 Address added fe80::1/64 wlan0 128 253");
+ unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
- sendMessage("614 Address removed 2001:db8::1/64 wlan0 1 0");
+ unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
- sendMessage("614 Address removed 2001:db8::1/64 wlan0 1");
- // Not enough arguments.
-
- sendMessage("666 Address removed 2001:db8::1/64 wlan0 1 0");
- // Invalid code.
-
-
/**
* DNS information broadcasts.
*/
- sendMessage("615 DnsInfo servers rmnet_usb0 3600 2001:db8::1");
+ unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
new String[]{"2001:db8::1"});
- sendMessage("615 DnsInfo servers wlan0 14400 2001:db8::1,2001:db8::2");
+ unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
+ new String[]{"2001:db8::1", "2001:db8::2"});
expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
new String[]{"2001:db8::1", "2001:db8::2"});
// We don't check for negative lifetimes, only for parse errors.
- sendMessage("615 DnsInfo servers wlan0 -3600 ::1");
+ unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
new String[]{"::1"});
- sendMessage("615 DnsInfo servers wlan0 SIXHUNDRED ::1");
- // Non-numeric lifetime.
-
- sendMessage("615 DnsInfo servers wlan0 2001:db8::1");
- // Missing lifetime.
-
- sendMessage("615 DnsInfo servers wlan0 3600");
- // No servers.
-
- sendMessage("615 DnsInfo servers 3600 wlan0 2001:db8::1,2001:db8::2");
- // Non-numeric lifetime.
-
- sendMessage("615 DnsInfo wlan0 7200 2001:db8::1,2001:db8::2");
- // Invalid tokens.
-
- sendMessage("666 DnsInfo servers wlan0 5400 2001:db8::1");
- // Invalid code.
-
// No syntax checking on the addresses.
- sendMessage("615 DnsInfo servers wlan0 600 ,::,,foo,::1,");
+ unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
+ new String[]{"", "::", "", "foo", "::1"});
expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
new String[]{"", "::", "", "foo", "::1"});
diff --git a/tests/net/java/com/android/server/NsdServiceTest.java b/tests/net/java/com/android/server/NsdServiceTest.java
index b88c784..a90fa68 100644
--- a/tests/net/java/com/android/server/NsdServiceTest.java
+++ b/tests/net/java/com/android/server/NsdServiceTest.java
@@ -22,23 +22,25 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
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.content.ContentResolver;
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
-import android.content.Context;
-import android.content.ContentResolver;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.NsdService.DaemonConnection;
import com.android.server.NsdService.DaemonConnectionSupplier;
import com.android.server.NsdService.NativeCallbackReceiver;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 01b468a..8fa0ab9 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -17,7 +17,6 @@
package com.android.server.connectivity;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
@@ -29,32 +28,31 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
+import android.net.IDnsResolver;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.RouteInfo;
-import android.os.INetworkManagementService;
+import android.net.shared.PrivateDnsConfig;
import android.provider.Settings;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.test.mock.MockContentResolver;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
-import com.android.server.connectivity.MockableSystemProperties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.util.Arrays;
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
/**
* Tests for {@link DnsManager}.
*
@@ -75,7 +73,7 @@
MockContentResolver mContentResolver;
@Mock Context mCtx;
- @Mock INetworkManagementService mNMService;
+ @Mock IDnsResolver mMockDnsResolver;
@Mock MockableSystemProperties mSystemProperties;
@Before
@@ -85,7 +83,7 @@
mContentResolver.addProvider(Settings.AUTHORITY,
new FakeSettingsProvider());
when(mCtx.getContentResolver()).thenReturn(mContentResolver);
- mDnsManager = new DnsManager(mCtx, mNMService, mSystemProperties);
+ mDnsManager = new DnsManager(mCtx, mMockDnsResolver, mSystemProperties);
// Clear the private DNS settings
Settings.Global.putString(mContentResolver, PRIVATE_DNS_DEFAULT_MODE, "");
@@ -133,7 +131,7 @@
PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "strictmode.com");
mDnsManager.updatePrivateDns(new Network(TEST_NETID),
- new DnsManager.PrivateDnsConfig("strictmode.com", new InetAddress[] {
+ new PrivateDnsConfig("strictmode.com", new InetAddress[] {
InetAddress.parseNumericAddress("6.6.6.6"),
InetAddress.parseNumericAddress("2001:db8:66:66::1")
}));
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 0656c5f..70495cc 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -16,52 +16,44 @@
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.server.connectivity.MetricsTestUtil.aBool;
-import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
import static com.android.server.connectivity.MetricsTestUtil.aLong;
import static com.android.server.connectivity.MetricsTestUtil.aString;
import static com.android.server.connectivity.MetricsTestUtil.aType;
import static com.android.server.connectivity.MetricsTestUtil.anInt;
-import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
-import static com.android.server.connectivity.MetricsTestUtil.b;
import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.BLUETOOTH;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.CELLULAR;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET;
+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;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
-import android.net.metrics.ConnectStats;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.DhcpErrorEvent;
-import android.net.metrics.DnsEvent;
-import android.net.metrics.DnsEvent;
import android.net.metrics.IpManagerEvent;
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.net.metrics.WakeupStats;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.Arrays;
import java.util.List;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
@RunWith(AndroidJUnit4.class)
@SmallTest
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8359fe2..3a07166 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -18,14 +18,12 @@
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -34,12 +32,11 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
-import android.net.RouteInfo;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.RouteInfo;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
-import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpManagerEvent;
@@ -47,27 +44,23 @@
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.os.Parcelable;
-import android.support.test.runner.AndroidJUnit4;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.util.BitUtils;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -97,48 +90,6 @@
}
@Test
- public void testLoggingEvents() throws Exception {
- IpConnectivityLog logger = new IpConnectivityLog(mMockService);
-
- assertTrue(logger.log(1, FAKE_EV));
- assertTrue(logger.log(2, FAKE_EV));
- assertTrue(logger.log(3, FAKE_EV));
-
- List<ConnectivityMetricsEvent> got = verifyEvents(3);
- assertEventsEqual(expectedEvent(1), got.get(0));
- assertEventsEqual(expectedEvent(2), got.get(1));
- assertEventsEqual(expectedEvent(3), got.get(2));
- }
-
- @Test
- public void testLoggingEventsWithMultipleCallers() throws Exception {
- IpConnectivityLog logger = new IpConnectivityLog(mMockService);
-
- final int nCallers = 10;
- final int nEvents = 10;
- for (int n = 0; n < nCallers; n++) {
- final int i = n;
- new Thread() {
- public void run() {
- for (int j = 0; j < nEvents; j++) {
- assertTrue(logger.log(1 + i * 100 + j, FAKE_EV));
- }
- }
- }.start();
- }
-
- List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
- Collections.sort(got, EVENT_COMPARATOR);
- Iterator<ConnectivityMetricsEvent> iter = got.iterator();
- for (int i = 0; i < nCallers; i++) {
- for (int j = 0; j < nEvents; j++) {
- int expectedTimestamp = 1 + i * 100 + j;
- assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
- }
- }
- }
-
- @Test
public void testBufferFlushing() {
String output1 = getdump("flush");
assertEquals("", output1);
@@ -154,7 +105,7 @@
@Test
public void testRateLimiting() {
final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
- final ApfProgramEvent ev = new ApfProgramEvent();
+ final ApfProgramEvent ev = new ApfProgramEvent.Builder().build();
final long fakeTimestamp = 1;
int attempt = 100; // More than burst quota, but less than buffer size.
@@ -304,26 +255,31 @@
when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
- ApfStats apfStats = new ApfStats();
- apfStats.durationMs = 45000;
- apfStats.receivedRas = 10;
- apfStats.matchingRas = 2;
- apfStats.droppedRas = 2;
- apfStats.parseErrors = 2;
- apfStats.zeroLifetimeRas = 1;
- apfStats.programUpdates = 4;
- apfStats.programUpdatesAll = 7;
- apfStats.programUpdatesAllowingMulticast = 3;
- apfStats.maxProgramSize = 2048;
+ ApfStats apfStats = new ApfStats.Builder()
+ .setDurationMs(45000)
+ .setReceivedRas(10)
+ .setMatchingRas(2)
+ .setDroppedRas(2)
+ .setParseErrors(2)
+ .setZeroLifetimeRas(1)
+ .setProgramUpdates(4)
+ .setProgramUpdatesAll(7)
+ .setProgramUpdatesAllowingMulticast(3)
+ .setMaxProgramSize(2048)
+ .build();
- ValidationProbeEvent validationEv = new ValidationProbeEvent();
- validationEv.durationMs = 40730;
- validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
- validationEv.returnCode = 204;
+ final ValidationProbeEvent validationEv = new ValidationProbeEvent.Builder()
+ .setDurationMs(40730)
+ .setProbeType(ValidationProbeEvent.PROBE_HTTP, true)
+ .setReturnCode(204)
+ .build();
+ final DhcpClientEvent event = new DhcpClientEvent.Builder()
+ .setMsg("SomeState")
+ .setDurationMs(192)
+ .build();
Parcelable[] events = {
- new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
- new DhcpClientEvent("SomeState", 192),
+ new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED), event,
new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
validationEv,
apfStats,
@@ -424,7 +380,7 @@
" validation_probe_event <",
" latency_ms: 40730",
" probe_result: 204",
- " probe_type: 1",
+ " probe_type: 257",
" >",
">",
"events <",
@@ -647,16 +603,7 @@
return nai;
}
- List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
- ArgumentCaptor<ConnectivityMetricsEvent> captor =
- ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
- verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
- return captor.getAllValues();
- }
- List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
- return verifyEvents(n, 10);
- }
static void verifySerialization(String want, String output) {
try {
@@ -668,28 +615,4 @@
fail(e.toString());
}
}
-
- static String joinLines(String ... elems) {
- StringBuilder b = new StringBuilder();
- for (String s : elems) {
- b.append(s).append("\n");
- }
- return b.toString();
- }
-
- static ConnectivityMetricsEvent expectedEvent(int timestamp) {
- ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
- ev.timestamp = timestamp;
- ev.data = FAKE_EV;
- return ev;
- }
-
- /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
- static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
- assertEquals(expected.timestamp, got.timestamp);
- assertEquals(expected.data, got.data);
- }
-
- static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
- Comparator.comparingLong((ev) -> ev.timestamp);
}
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 354cf2f..142769f 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -23,31 +23,35 @@
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.reset;
import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.IDnsResolver;
+import android.net.INetd;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkMisc;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.filters.SmallTest;
+import android.os.INetworkManagementService;
import android.text.format.DateUtils;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.R;
import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
-import org.junit.runner.RunWith;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -66,6 +70,9 @@
LingerMonitor mMonitor;
@Mock ConnectivityService mConnService;
+ @Mock IDnsResolver mDnsResolver;
+ @Mock INetd mNetd;
+ @Mock INetworkManagementService mNMS;
@Mock Context mCtx;
@Mock NetworkMisc mMisc;
@Mock NetworkNotificationManager mNotifier;
@@ -76,7 +83,6 @@
MockitoAnnotations.initMocks(this);
when(mCtx.getResources()).thenReturn(mResources);
when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity");
- when(mConnService.createNetworkMonitor(any(), any(), any(), any())).thenReturn(null);
mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
}
@@ -349,7 +355,8 @@
caps.addCapability(0);
caps.addTransportType(transport);
NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
- caps, 50, mCtx, null, mMisc, null, mConnService);
+ caps, 50, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS,
+ NetworkFactory.SerialNumber.NONE);
nai.everValidated = true;
return nai;
}
diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index e58811b..b783467 100644
--- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -28,7 +28,6 @@
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
-import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
@@ -54,13 +53,14 @@
import android.net.StringNetworkSpecifier;
import android.os.Handler;
import android.provider.Settings;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import android.test.mock.MockContentResolver;
import android.util.DataUnit;
import android.util.RecurrenceRule;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index dfe31bd..b709af1 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -16,26 +16,31 @@
package com.android.server.connectivity;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.net.ConnectivityManager;
+import android.net.IDnsResolver;
+import android.net.INetd;
import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
+import android.net.NetworkMisc;
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.test.TestLooper;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.server.ConnectivityService;
@@ -43,6 +48,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -53,8 +59,13 @@
static final String BASE_IFACE = "test0";
static final String STACKED_IFACE = "v4-test0";
static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29");
+ static final String NAT64_PREFIX = "64:ff9b::/96";
+ static final int NETID = 42;
@Mock ConnectivityService mConnectivity;
+ @Mock NetworkMisc mMisc;
+ @Mock IDnsResolver mDnsResolver;
+ @Mock INetd mNetd;
@Mock INetworkManagementService mNms;
@Mock InterfaceConfiguration mConfig;
@Mock NetworkAgentInfo mNai;
@@ -63,7 +74,11 @@
Handler mHandler;
Nat464Xlat makeNat464Xlat() {
- return new Nat464Xlat(mNms, mNai);
+ return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) {
+ @Override protected int getNetId() {
+ return NETID;
+ }
+ };
}
@Before
@@ -78,12 +93,31 @@
mNai.networkInfo = new NetworkInfo(null);
mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
when(mNai.connService()).thenReturn(mConnectivity);
+ when(mNai.netMisc()).thenReturn(mMisc);
when(mNai.handler()).thenReturn(mHandler);
when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig);
when(mConfig.getLinkAddress()).thenReturn(ADDR);
}
+ private void assertRequiresClat(boolean expected, NetworkAgentInfo nai) {
+ String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
+ + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
+ nai.networkInfo.getDetailedState(),
+ mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ nai.linkProperties.getLinkAddresses());
+ assertEquals(msg, expected, Nat464Xlat.requiresClat(nai));
+ }
+
+ private void assertShouldStartClat(boolean expected, NetworkAgentInfo nai) {
+ String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
+ + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
+ nai.networkInfo.getDetailedState(),
+ mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ nai.linkProperties.getLinkAddresses());
+ assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai));
+ }
+
@Test
public void testRequiresClat() throws Exception {
final int[] supportedTypes = {
@@ -99,13 +133,45 @@
NetworkInfo.DetailedState.SUSPENDED,
};
+ LinkProperties oldLp = new LinkProperties(mNai.linkProperties);
for (int type : supportedTypes) {
mNai.networkInfo.setType(type);
for (NetworkInfo.DetailedState state : supportedDetailedStates) {
mNai.networkInfo.setDetailedState(state, "reason", "extraInfo");
- assertTrue(
- String.format("requiresClat expected for type=%d state=%s", type, state),
- Nat464Xlat.requiresClat(mNai));
+
+ mNai.linkProperties.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
+ assertRequiresClat(false, mNai);
+ assertShouldStartClat(false, mNai);
+
+ mNai.linkProperties.addLinkAddress(new LinkAddress("fc00::1/64"));
+ assertRequiresClat(false, mNai);
+ assertShouldStartClat(false, mNai);
+
+ mNai.linkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+ assertRequiresClat(true, mNai);
+ assertShouldStartClat(true, mNai);
+
+ mMisc.skip464xlat = true;
+ assertRequiresClat(false, mNai);
+ assertShouldStartClat(false, mNai);
+
+ mMisc.skip464xlat = false;
+ assertRequiresClat(true, mNai);
+ assertShouldStartClat(true, mNai);
+
+ mNai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.2/24"));
+ assertRequiresClat(false, mNai);
+ assertShouldStartClat(false, mNai);
+
+ mNai.linkProperties.removeLinkAddress(new LinkAddress("192.0.2.2/24"));
+ assertRequiresClat(true, mNai);
+ assertShouldStartClat(true, mNai);
+
+ mNai.linkProperties.setNat64Prefix(null);
+ assertRequiresClat(true, mNai);
+ assertShouldStartClat(false, mNai);
+
+ mNai.linkProperties = new LinkProperties(oldLp);
}
}
}
@@ -115,11 +181,13 @@
Nat464Xlat nat = makeNat464Xlat();
ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
- // ConnectivityService starts clat.
+ nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+
+ // Start clat.
nat.start();
verify(mNms).registerObserver(eq(nat));
- verify(mNms).startClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
// Stacked interface up notification arrives.
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -131,22 +199,109 @@
assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
assertRunning(nat);
- // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
+ // Stop clat (Network disconnects, IPv4 addr appears, ...).
nat.stop();
- verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStop(eq(BASE_IFACE));
+ verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
+ verify(mNms).unregisterObserver(eq(nat));
+ assertTrue(c.getValue().getStackedLinks().isEmpty());
+ assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
+ assertIdle(nat);
- // Stacked interface removed notification arrives.
+ // Stacked interface removed notification arrives and is ignored.
nat.interfaceRemoved(STACKED_IFACE);
mLooper.dispatchNext();
- verify(mNms).unregisterObserver(eq(nat));
- verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
+ verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
+ }
+
+ private void checkStartStopStart(boolean interfaceRemovedFirst) throws Exception {
+ Nat464Xlat nat = makeNat464Xlat();
+ ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
+ InOrder inOrder = inOrder(mNetd, mConnectivity);
+
+ nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+
+ nat.start();
+
+ inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+
+ // Stacked interface up notification arrives.
+ nat.interfaceLinkStateChanged(STACKED_IFACE, true);
+ mLooper.dispatchNext();
+
+ inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
+ assertFalse(c.getValue().getStackedLinks().isEmpty());
+ assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ assertRunning(nat);
+
+ // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
+ nat.stop();
+
+ inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE));
+
+ inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
assertTrue(c.getValue().getStackedLinks().isEmpty());
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
assertIdle(nat);
- verifyNoMoreInteractions(mNms, mConnectivity);
+ if (interfaceRemovedFirst) {
+ // Stacked interface removed notification arrives and is ignored.
+ nat.interfaceRemoved(STACKED_IFACE);
+ mLooper.dispatchNext();
+ nat.interfaceLinkStateChanged(STACKED_IFACE, false);
+ mLooper.dispatchNext();
+ }
+
+ assertTrue(c.getValue().getStackedLinks().isEmpty());
+ assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ assertIdle(nat);
+ inOrder.verifyNoMoreInteractions();
+
+ nat.start();
+
+ inOrder.verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
+
+ if (!interfaceRemovedFirst) {
+ // Stacked interface removed notification arrives and is ignored.
+ nat.interfaceRemoved(STACKED_IFACE);
+ mLooper.dispatchNext();
+ nat.interfaceLinkStateChanged(STACKED_IFACE, false);
+ mLooper.dispatchNext();
+ }
+
+ // Stacked interface up notification arrives.
+ nat.interfaceLinkStateChanged(STACKED_IFACE, true);
+ mLooper.dispatchNext();
+
+ inOrder.verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
+ assertFalse(c.getValue().getStackedLinks().isEmpty());
+ assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ assertRunning(nat);
+
+ // ConnectivityService stops clat again.
+ nat.stop();
+
+ inOrder.verify(mNetd).clatdStop(eq(BASE_IFACE));
+
+ inOrder.verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
+ assertTrue(c.getValue().getStackedLinks().isEmpty());
+ assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ assertIdle(nat);
+
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testStartStopStart() throws Exception {
+ checkStartStopStart(true);
+ }
+
+ @Test
+ public void testStartStopStartBeforeInterfaceRemoved() throws Exception {
+ checkStartStopStart(false);
}
@Test
@@ -154,11 +309,12 @@
Nat464Xlat nat = makeNat464Xlat();
ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
- // ConnectivityService starts clat.
+ nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+
nat.start();
verify(mNms).registerObserver(eq(nat));
- verify(mNms).startClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
// Stacked interface up notification arrives.
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
@@ -174,9 +330,10 @@
nat.interfaceRemoved(STACKED_IFACE);
mLooper.dispatchNext();
- verify(mNms).unregisterObserver(eq(nat));
- verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStop(eq(BASE_IFACE));
verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
+ verify(mNms).unregisterObserver(eq(nat));
+ verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
assertTrue(c.getValue().getStackedLinks().isEmpty());
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
assertIdle(nat);
@@ -184,58 +341,61 @@
// ConnectivityService stops clat: no-op.
nat.stop();
- verifyNoMoreInteractions(mNms, mConnectivity);
+ verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
}
@Test
public void testStopBeforeClatdStarts() throws Exception {
Nat464Xlat nat = makeNat464Xlat();
- // ConnectivityService starts clat.
+ nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+
nat.start();
verify(mNms).registerObserver(eq(nat));
- verify(mNms).startClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
// ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
nat.stop();
+ verify(mNetd).clatdStop(eq(BASE_IFACE));
verify(mNms).unregisterObserver(eq(nat));
- verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
assertIdle(nat);
// In-flight interface up notification arrives: no-op
nat.interfaceLinkStateChanged(STACKED_IFACE, true);
mLooper.dispatchNext();
-
// Interface removed notification arrives after stopClatd() takes effect: no-op.
nat.interfaceRemoved(STACKED_IFACE);
mLooper.dispatchNext();
assertIdle(nat);
- verifyNoMoreInteractions(mNms, mConnectivity);
+ verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
}
@Test
public void testStopAndClatdNeverStarts() throws Exception {
Nat464Xlat nat = makeNat464Xlat();
- // ConnectivityService starts clat.
+ nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+
nat.start();
verify(mNms).registerObserver(eq(nat));
- verify(mNms).startClatd(eq(BASE_IFACE));
+ verify(mNetd).clatdStart(eq(BASE_IFACE), eq(NAT64_PREFIX));
// ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
nat.stop();
+ verify(mNetd).clatdStop(eq(BASE_IFACE));
verify(mNms).unregisterObserver(eq(nat));
- verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mDnsResolver).stopPrefix64Discovery(eq(NETID));
assertIdle(nat);
- verifyNoMoreInteractions(mNms, mConnectivity);
+ verifyNoMoreInteractions(mNetd, mNms, mConnectivity);
}
static void assertIdle(Nat464Xlat nat) {
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 67805c9..aef9386 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -18,43 +18,38 @@
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
+
+import static com.android.testutils.MiscAssertsKt.assertStringContains;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-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.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
-import android.support.test.runner.AndroidJUnit4;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
-import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.DNSLookupBatch;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetdEventListenerServiceTest {
@@ -117,15 +112,15 @@
String[] events2 = remove(listNetdEvent(), baseline);
int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
assertEquals(expectedLength2, events2.length);
- assertContains(events2[0], "WakeupStats");
- assertContains(events2[0], "wlan0");
- assertContains(events2[0], "0x800");
- assertContains(events2[0], "0x86dd");
+ assertStringContains(events2[0], "WakeupStats");
+ assertStringContains(events2[0], "wlan0");
+ assertStringContains(events2[0], "0x800");
+ assertStringContains(events2[0], "0x86dd");
for (int i = 0; i < uids.length; i++) {
String got = events2[i+1];
- assertContains(got, "WakeupEvent");
- assertContains(got, "wlan0");
- assertContains(got, "uid: " + uids[i]);
+ assertStringContains(got, "WakeupEvent");
+ assertStringContains(got, "wlan0");
+ assertStringContains(got, "uid: " + uids[i]);
}
int uid = 20000;
@@ -137,13 +132,13 @@
String[] events3 = remove(listNetdEvent(), baseline);
int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
assertEquals(expectedLength3, events3.length);
- assertContains(events2[0], "WakeupStats");
- assertContains(events2[0], "wlan0");
+ assertStringContains(events2[0], "WakeupStats");
+ assertStringContains(events2[0], "wlan0");
for (int i = 1; i < expectedLength3; i++) {
String got = events3[i];
- assertContains(got, "WakeupEvent");
- assertContains(got, "wlan0");
- assertContains(got, "uid: " + uid);
+ assertStringContains(got, "WakeupEvent");
+ assertStringContains(got, "wlan0");
+ assertStringContains(got, "uid: " + uid);
}
uid = 45678;
@@ -151,9 +146,9 @@
String[] events4 = remove(listNetdEvent(), baseline);
String lastEvent = events4[events4.length - 1];
- assertContains(lastEvent, "WakeupEvent");
- assertContains(lastEvent, "wlan0");
- assertContains(lastEvent, "uid: " + uid);
+ assertStringContains(lastEvent, "WakeupEvent");
+ assertStringContains(lastEvent, "wlan0");
+ assertStringContains(lastEvent, "uid: " + uid);
}
@Test
@@ -535,10 +530,6 @@
return buffer.toString().split("\\n");
}
- static void assertContains(String got, String want) {
- assertTrue(got + " did not contain \"" + want + "\"", got.contains(want));
- }
-
static <T> T[] remove(T[] array, T[] filtered) {
List<T> c = Arrays.asList(filtered);
int next = 0;
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 125fe72..9580763 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
@@ -34,26 +35,25 @@
import android.content.res.Resources;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.filters.SmallTest;
import android.telephony.TelephonyManager;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetworkNotificationManagerTest {
@@ -194,4 +194,54 @@
mManager.clearNotification(id);
verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
}
+
+ @Test
+ public void testSameLevelNotifications() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+ }
+
+ @Test
+ public void testClearNotificationByType() {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+
+ // clearNotification(int id, NotificationType notifyType) will check if given type is equal
+ // to previous type or not. If they are equal then clear the notification; if they are not
+ // equal then return.
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ // Previous notification is LOGGED_IN and given type is LOGGED_IN too. The notification
+ // should be cleared.
+ mManager.clearNotification(id, LOGGED_IN);
+ verify(mNotificationManager, times(1))
+ .cancelAsUser(eq(tag), eq(LOGGED_IN.eventId), any());
+
+ mManager.showNotification(id, LOGGED_IN, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(2))
+ .notifyAsUser(eq(tag), eq(LOGGED_IN.eventId), any(), any());
+
+ // LOST_INTERNET notification popup after LOGGED_IN notification.
+ mManager.showNotification(id, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+ verify(mNotificationManager, times(1))
+ .notifyAsUser(eq(tag), eq(LOST_INTERNET.eventId), any(), any());
+
+ // Previous notification is LOST_INTERNET and given type is LOGGED_IN. The notification
+ // shouldn't be cleared.
+ mManager.clearNotification(id, LOGGED_IN);
+ // LOST_INTERNET shouldn't be cleared.
+ verify(mNotificationManager, never())
+ .cancelAsUser(eq(tag), eq(LOST_INTERNET.eventId), any());
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index f025f41..cd2bd26 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -20,81 +20,206 @@
import static android.Manifest.permission.CHANGE_WIFI_STATE;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.NETWORK_STACK;
-import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_OEM;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.connectivity.PermissionMonitor.NETWORK;
+import static com.android.server.connectivity.PermissionMonitor.SYSTEM;
+
+import static junit.framework.Assert.fail;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
+import static org.mockito.AdditionalMatchers.aryEq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageList;
import android.content.pm.PackageManager;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
+import android.net.INetd;
+import android.net.UidRange;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.SparseIntArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
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 org.mockito.invocation.InvocationOnMock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PermissionMonitorTest {
- private static final int MOCK_UID = 10001;
- private static final String[] MOCK_PACKAGE_NAMES = new String[] { "com.foo.bar" };
+ private static final int MOCK_USER1 = 0;
+ private static final int MOCK_USER2 = 1;
+ private static final int MOCK_UID1 = 10001;
+ private static final int MOCK_UID2 = 10086;
+ private static final int SYSTEM_UID1 = 1000;
+ private static final int SYSTEM_UID2 = 1008;
+ private static final int VPN_UID = 10002;
+ private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
+ private static final String MOCK_PACKAGE1 = "appName1";
+ private static final String MOCK_PACKAGE2 = "appName2";
+ private static final String SYSTEM_PACKAGE1 = "sysName1";
+ private static final String SYSTEM_PACKAGE2 = "sysName2";
+ private static final String VPN_PACKAGE = "vpnApp";
+ private static final String PARTITION_SYSTEM = "system";
+ private static final String PARTITION_OEM = "oem";
+ private static final String PARTITION_PRODUCT = "product";
+ private static final String PARTITION_VENDOR = "vendor";
+ private static final int VERSION_P = Build.VERSION_CODES.P;
+ private static final int VERSION_Q = Build.VERSION_CODES.Q;
@Mock private Context mContext;
@Mock private PackageManager mPackageManager;
+ @Mock private INetd mNetdService;
+ @Mock private PackageManagerInternal mMockPmi;
+ @Mock private UserManager mUserManager;
+ private PackageManagerInternal.PackageListObserver mObserver;
private PermissionMonitor mPermissionMonitor;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mPackageManager.getPackagesForUid(MOCK_UID)).thenReturn(MOCK_PACKAGE_NAMES);
- mPermissionMonitor = new PermissionMonitor(mContext, null);
+ when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
+ when(mUserManager.getUsers(eq(true))).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(MOCK_USER1, "", 0),
+ new UserInfo(MOCK_USER2, "", 0),
+ }));
+
+ mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService));
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mMockPmi);
+ when(mMockPmi.getPackageList(any())).thenReturn(new PackageList(new ArrayList<String>(),
+ /* observer */ null));
+ when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(/* empty app list */ null);
+ mPermissionMonitor.startMonitoring();
+
+ final ArgumentCaptor<PackageManagerInternal.PackageListObserver> observerCaptor =
+ ArgumentCaptor.forClass(PackageManagerInternal.PackageListObserver.class);
+ verify(mMockPmi).getPackageList(observerCaptor.capture());
+ mObserver = observerCaptor.getValue();
}
- private void expectPermission(String[] permissions, boolean preinstalled) throws Exception {
- final PackageInfo packageInfo = packageInfoWithPermissions(permissions, preinstalled);
+ private boolean hasBgPermission(String partition, int targetSdkVersion, int uid,
+ String... permission) throws Exception {
+ final PackageInfo packageInfo = packageInfoWithPermissions(permission, partition);
+ packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion;
+ packageInfo.applicationInfo.uid = uid;
when(mPackageManager.getPackageInfoAsUser(
- eq(MOCK_PACKAGE_NAMES[0]), eq(GET_PERMISSIONS), anyInt())).thenReturn(packageInfo);
+ eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS), anyInt())).thenReturn(packageInfo);
+ when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[] {MOCK_PACKAGE1});
+ return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
}
- private PackageInfo packageInfoWithPermissions(String[] permissions, boolean preinstalled) {
+ private static PackageInfo packageInfoWithPermissions(String[] permissions, String partition) {
+ int[] requestedPermissionsFlags = new int[permissions.length];
+ for (int i = 0; i < permissions.length; i++) {
+ requestedPermissionsFlags[i] = REQUESTED_PERMISSION_GRANTED;
+ }
+ return packageInfoWithPermissions(permissions, partition,
+ requestedPermissionsFlags);
+ }
+
+ private static PackageInfo packageInfoWithPermissions(String[] permissions, String partition,
+ int[] requestedPermissionsFlags) {
final PackageInfo packageInfo = new PackageInfo();
packageInfo.requestedPermissions = permissions;
packageInfo.applicationInfo = new ApplicationInfo();
- packageInfo.applicationInfo.flags = preinstalled ? FLAG_SYSTEM : 0;
+ packageInfo.requestedPermissionsFlags = requestedPermissionsFlags;
+ int privateFlags = 0;
+ switch (partition) {
+ case PARTITION_OEM:
+ privateFlags = PRIVATE_FLAG_OEM;
+ break;
+ case PARTITION_PRODUCT:
+ privateFlags = PRIVATE_FLAG_PRODUCT;
+ break;
+ case PARTITION_VENDOR:
+ privateFlags = PRIVATE_FLAG_VENDOR;
+ break;
+ }
+ packageInfo.applicationInfo.privateFlags = privateFlags;
return packageInfo;
}
+ private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
+ final PackageInfo pkgInfo;
+ if (hasSystemPermission) {
+ final String[] systemPermissions = new String[]{
+ CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS
+ };
+ pkgInfo = packageInfoWithPermissions(systemPermissions, PARTITION_SYSTEM);
+ } else {
+ pkgInfo = packageInfoWithPermissions(new String[] {}, "");
+ }
+ pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+ return pkgInfo;
+ }
+
@Test
public void testHasPermission() {
- PackageInfo app = packageInfoWithPermissions(new String[] {}, false);
+ PackageInfo app = packageInfoWithPermissions(new String[] {}, PARTITION_SYSTEM);
assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
app = packageInfoWithPermissions(new String[] {
- CHANGE_NETWORK_STATE, NETWORK_STACK
- }, false);
+ CHANGE_NETWORK_STATE, NETWORK_STACK
+ }, PARTITION_SYSTEM);
assertTrue(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
assertTrue(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
app = packageInfoWithPermissions(new String[] {
- CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL
- }, false);
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL
+ }, PARTITION_SYSTEM);
assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
@@ -102,35 +227,439 @@
}
@Test
- public void testIsPreinstalledSystemApp() {
- PackageInfo app = packageInfoWithPermissions(new String[] {}, false);
- assertFalse(mPermissionMonitor.isPreinstalledSystemApp(app));
-
- app = packageInfoWithPermissions(new String[] {}, true);
- assertTrue(mPermissionMonitor.isPreinstalledSystemApp(app));
+ public void testIsVendorApp() {
+ PackageInfo app = packageInfoWithPermissions(new String[] {}, PARTITION_SYSTEM);
+ assertFalse(mPermissionMonitor.isVendorApp(app.applicationInfo));
+ app = packageInfoWithPermissions(new String[] {}, PARTITION_OEM);
+ assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
+ app = packageInfoWithPermissions(new String[] {}, PARTITION_PRODUCT);
+ assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
+ app = packageInfoWithPermissions(new String[] {}, PARTITION_VENDOR);
+ assertTrue(mPermissionMonitor.isVendorApp(app.applicationInfo));
}
@Test
public void testHasUseBackgroundNetworksPermission() throws Exception {
- expectPermission(new String[] { CHANGE_NETWORK_STATE }, false);
- assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+ assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1));
+ assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE));
+ assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, NETWORK_STACK));
+ assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL));
+ assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+ assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE));
- expectPermission(new String[] { NETWORK_STACK, CONNECTIVITY_INTERNAL }, false);
- assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+ assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1));
+ assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE));
+ }
- // TODO : make this false when b/31479477 is fixed
- expectPermission(new String[] {}, true);
- assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
- expectPermission(new String[] { CHANGE_WIFI_STATE }, true);
- assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+ @Test
+ public void testHasUseBackgroundNetworksPermissionSystemUid() throws Exception {
+ doReturn(VERSION_P).when(mPermissionMonitor).getDeviceFirstSdkInt();
+ assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID));
+ assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CHANGE_WIFI_STATE));
+ assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS));
- expectPermission(new String[] { NETWORK_STACK, CONNECTIVITY_INTERNAL }, true);
- assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+ doReturn(VERSION_Q).when(mPermissionMonitor).getDeviceFirstSdkInt();
+ assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID));
+ assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CHANGE_WIFI_STATE));
+ assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+ }
- expectPermission(new String[] {}, false);
- assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+ @Test
+ public void testHasUseBackgroundNetworksPermissionVendorApp() throws Exception {
+ assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1));
+ assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE));
+ assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, NETWORK_STACK));
+ assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL));
+ assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1,
+ CONNECTIVITY_USE_RESTRICTED_NETWORKS));
+ assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE));
- expectPermission(new String[] { CHANGE_WIFI_STATE }, false);
- assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
+ assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1));
+ assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE));
+ }
+
+ private class NetdMonitor {
+ private final HashMap<Integer, Boolean> mApps = new HashMap<>();
+
+ NetdMonitor(INetd mockNetd) throws Exception {
+ // Add hook to verify and track result of setPermission.
+ doAnswer((InvocationOnMock invocation) -> {
+ final Object[] args = invocation.getArguments();
+ final Boolean isSystem = args[0].equals(INetd.PERMISSION_SYSTEM);
+ for (final int uid : (int[]) args[1]) {
+ // TODO: Currently, permission monitor will send duplicate commands for each uid
+ // corresponding to each user. Need to fix that and uncomment below test.
+ // if (mApps.containsKey(uid) && mApps.get(uid) == isSystem) {
+ // fail("uid " + uid + " is already set to " + isSystem);
+ // }
+ mApps.put(uid, isSystem);
+ }
+ return null;
+ }).when(mockNetd).networkSetPermissionForUser(anyInt(), any(int[].class));
+
+ // Add hook to verify and track result of clearPermission.
+ doAnswer((InvocationOnMock invocation) -> {
+ final Object[] args = invocation.getArguments();
+ for (final int uid : (int[]) args[0]) {
+ // TODO: Currently, permission monitor will send duplicate commands for each uid
+ // corresponding to each user. Need to fix that and uncomment below test.
+ // if (!mApps.containsKey(uid)) {
+ // fail("uid " + uid + " does not exist.");
+ // }
+ mApps.remove(uid);
+ }
+ return null;
+ }).when(mockNetd).networkClearPermissionForUser(any(int[].class));
+ }
+
+ public void expectPermission(Boolean permission, int[] users, int[] apps) {
+ for (final int user : users) {
+ for (final int app : apps) {
+ final int uid = UserHandle.getUid(user, app);
+ if (!mApps.containsKey(uid)) {
+ fail("uid " + uid + " does not exist.");
+ }
+ if (mApps.get(uid) != permission) {
+ fail("uid " + uid + " has wrong permission: " + permission);
+ }
+ }
+ }
+ }
+
+ public void expectNoPermission(int[] users, int[] apps) {
+ for (final int user : users) {
+ for (final int app : apps) {
+ final int uid = UserHandle.getUid(user, app);
+ if (mApps.containsKey(uid)) {
+ fail("uid " + uid + " has listed permissions, expected none.");
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testUserAndPackageAddRemove() throws Exception {
+ final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
+
+ // MOCK_UID1: MOCK_PACKAGE1 only has network permission.
+ // SYSTEM_UID: SYSTEM_PACKAGE1 has system permission.
+ // SYSTEM_UID: SYSTEM_PACKAGE2 only has network permission.
+ doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(eq(SYSTEM), anyString());
+ doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(any(),
+ eq(SYSTEM_PACKAGE1));
+ doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(),
+ eq(SYSTEM_PACKAGE2));
+ doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(),
+ eq(MOCK_PACKAGE1));
+
+ // Add SYSTEM_PACKAGE2, expect only have network permission.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+
+ // Add SYSTEM_PACKAGE1, expect permission escalate.
+ addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+ new int[]{SYSTEM_UID});
+
+ addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+ new int[]{SYSTEM_UID});
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+ new int[]{MOCK_UID1});
+
+ // Remove MOCK_UID1, expect no permission left for all user.
+ mPermissionMonitor.onPackageRemoved(MOCK_UID1);
+ removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_UID1);
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
+
+ // Remove SYSTEM_PACKAGE1, expect permission downgrade.
+ when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2});
+ removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_UID);
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+ new int[]{SYSTEM_UID});
+
+ mPermissionMonitor.onUserRemoved(MOCK_USER1);
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
+
+ // Remove all packages, expect no permission left.
+ when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{});
+ removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_UID);
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+ new int[]{SYSTEM_UID, MOCK_UID1});
+
+ // Remove last user, expect no redundant clearPermission is invoked.
+ mPermissionMonitor.onUserRemoved(MOCK_USER2);
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+ new int[]{SYSTEM_UID, MOCK_UID1});
+ }
+
+ @Test
+ public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(/* SYSTEM */ true, SYSTEM_UID1, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, MOCK_UID1, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, MOCK_UID2, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, VPN_UID, MOCK_USER1)
+ }));
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
+ buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
+ mPermissionMonitor.startMonitoring();
+ // Every app on user 0 except MOCK_UID2 are under VPN.
+ final Set<UidRange> vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] {
+ new UidRange(0, MOCK_UID2 - 1),
+ new UidRange(MOCK_UID2 + 1, UserHandle.PER_USER_RANGE - 1)}));
+ final Set<UidRange> vpnRange2 = Collections.singleton(new UidRange(MOCK_UID2, MOCK_UID2));
+
+ // When VPN is connected, expect a rule to be set up for user app MOCK_UID1
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ reset(mNetdService);
+
+ // When MOCK_UID1 package is uninstalled and reinstalled, expect Netd to be updated
+ mPermissionMonitor.onPackageRemoved(UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ reset(mNetdService);
+
+ // During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
+ // old UID rules then adds the new ones. Expect netd to be updated
+ mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID2}));
+
+ reset(mNetdService);
+
+ // When VPN is disconnected, expect rules to be torn down
+ mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID2}));
+ assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
+ }
+
+ @Test
+ public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(true, SYSTEM_UID1, MOCK_USER1),
+ buildPackageInfo(false, VPN_UID, MOCK_USER1)
+ }));
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
+ buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
+
+ mPermissionMonitor.startMonitoring();
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1));
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange, VPN_UID);
+
+ // Newly-installed package should have uid rules added
+ mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ // Removed package should have its uid rules removed
+ mPermissionMonitor.onPackageRemoved(UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ }
+
+
+ // Normal package add/remove operations will trigger multiple intent for uids corresponding to
+ // each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
+ // called multiple times with the uid corresponding to each user.
+ private void addPackageForUsers(int[] users, String packageName, int uid) {
+ for (final int user : users) {
+ mPermissionMonitor.onPackageAdded(packageName, UserHandle.getUid(user, uid));
+ }
+ }
+
+ private void removePackageForUsers(int[] users, int uid) {
+ for (final int user : users) {
+ mPermissionMonitor.onPackageRemoved(UserHandle.getUid(user, uid));
+ }
+ }
+
+ private class NetdServiceMonitor {
+ private final HashMap<Integer, Integer> mPermissions = new HashMap<>();
+
+ NetdServiceMonitor(INetd mockNetdService) throws Exception {
+ // Add hook to verify and track result of setPermission.
+ doAnswer((InvocationOnMock invocation) -> {
+ final Object[] args = invocation.getArguments();
+ final int permission = (int) args[0];
+ for (final int uid : (int[]) args[1]) {
+ mPermissions.put(uid, permission);
+ }
+ return null;
+ }).when(mockNetdService).trafficSetNetPermForUids(anyInt(), any(int[].class));
+ }
+
+ public void expectPermission(int permission, int[] apps) {
+ for (final int app : apps) {
+ if (!mPermissions.containsKey(app)) {
+ fail("uid " + app + " does not exist.");
+ }
+ if (mPermissions.get(app) != permission) {
+ fail("uid " + app + " has wrong permission: " + mPermissions.get(app));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testPackagePermissionUpdate() throws Exception {
+ final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+ // MOCK_UID1: MOCK_PACKAGE1 only has internet permission.
+ // MOCK_UID2: MOCK_PACKAGE2 does not have any permission.
+ // SYSTEM_UID1: SYSTEM_PACKAGE1 has internet permission and update device stats permission.
+ // SYSTEM_UID2: SYSTEM_PACKAGE2 has only update device stats permission.
+
+ SparseIntArray netdPermissionsAppIds = new SparseIntArray();
+ netdPermissionsAppIds.put(MOCK_UID1, INetd.PERMISSION_INTERNET);
+ netdPermissionsAppIds.put(MOCK_UID2, INetd.PERMISSION_NONE);
+ netdPermissionsAppIds.put(SYSTEM_UID1, INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS);
+ netdPermissionsAppIds.put(SYSTEM_UID2, INetd.PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Send the permission information to netd, expect permission updated.
+ mPermissionMonitor.sendPackagePermissionsToNetd(netdPermissionsAppIds);
+
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET,
+ new int[]{MOCK_UID1});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID2});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{SYSTEM_UID1});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UPDATE_DEVICE_STATS,
+ new int[]{SYSTEM_UID2});
+
+ // Update permission of MOCK_UID1, expect new permission show up.
+ mPermissionMonitor.sendPackagePermissionsForUid(MOCK_UID1,
+ INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS);
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+ // Change permissions of SYSTEM_UID2, expect new permission show up and old permission
+ // revoked.
+ mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID2,
+ INetd.PERMISSION_INTERNET);
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{SYSTEM_UID2});
+
+ // Revoke permission from SYSTEM_UID1, expect no permission stored.
+ mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID1, INetd.PERMISSION_NONE);
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{SYSTEM_UID1});
+ }
+
+ private PackageInfo addPackage(String packageName, int uid, String[] permissions)
+ throws Exception {
+ PackageInfo packageInfo = packageInfoWithPermissions(permissions, PARTITION_SYSTEM);
+ when(mPackageManager.getPackageInfo(eq(packageName), anyInt())).thenReturn(packageInfo);
+ when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[]{packageName});
+ mObserver.onPackageAdded(packageName, uid);
+ return packageInfo;
+ }
+
+ @Test
+ public void testPackageInstall() throws Exception {
+ final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+ addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+ addPackage(MOCK_PACKAGE2, MOCK_UID2, new String[] {INTERNET});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID2});
+ }
+
+ @Test
+ public void testPackageInstallSharedUid() throws Exception {
+ final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+ PackageInfo packageInfo1 = addPackage(MOCK_PACKAGE1, MOCK_UID1,
+ new String[] {INTERNET, UPDATE_DEVICE_STATS});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+ // Install another package with the same uid and no permissions should not cause the UID to
+ // lose permissions.
+ PackageInfo packageInfo2 = packageInfoWithPermissions(new String[]{}, PARTITION_SYSTEM);
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
+ when(mPackageManager.getPackagesForUid(MOCK_UID1))
+ .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
+ mObserver.onPackageAdded(MOCK_PACKAGE2, MOCK_UID1);
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+ }
+
+ @Test
+ public void testPackageUninstallBasic() throws Exception {
+ final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+ addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+ when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
+ mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
+ }
+
+ @Test
+ public void testPackageUpdate() throws Exception {
+ final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+ addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+ // Remove and install the same package to simulate the update action
+ when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
+ mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
+
+ addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+ }
+
+ @Test
+ public void testPackageUninstallWithMultiplePackages() throws Exception {
+ final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+ addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+ | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+ // Mock another package with the same uid but different permissions.
+ PackageInfo packageInfo2 = packageInfoWithPermissions(new String[] {INTERNET},
+ PARTITION_SYSTEM);
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
+ when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{
+ MOCK_PACKAGE2});
+
+ mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
+ mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+ }
+
+ @Test
+ public void testRealSystemPermission() throws Exception {
+ // Use the real context as this test must ensure the *real* system package holds the
+ // necessary permission.
+ final Context realContext = InstrumentationRegistry.getContext();
+ final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
+ final PackageManager manager = realContext.getPackageManager();
+ final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
+ GET_PERMISSIONS | MATCH_ANY_USER);
+ assertTrue(monitor.hasPermission(systemInfo, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
}
}
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index a0a4ad1..ce50bef 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -28,6 +28,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -57,7 +58,6 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.Network;
@@ -73,11 +73,12 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.util.ArrayMap;
import android.util.ArraySet;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.R;
import com.android.internal.net.VpnConfig;
@@ -90,6 +91,7 @@
import org.mockito.MockitoAnnotations;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -97,7 +99,6 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -170,6 +171,8 @@
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());
}
@@ -240,6 +243,30 @@
}
@Test
+ public void testGetAlwaysAndOnGetLockDown() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.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 testLockdownChangingPackage() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
@@ -248,11 +275,11 @@
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on without lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on with lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -261,7 +288,7 @@
assertUnblocked(vpn, user.start + PKG_UIDS[1]);
// Switch to another app.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -275,6 +302,87 @@
}
@Test
+ public void testLockdownWhitelist() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+ final UidRange user = UidRange.createForUser(primaryUser.id);
+
+ // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
+ new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+
+ // Change whitelisted app to PKGS[3].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1),
+ new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
+
+ // Change the VPN app.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+ new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
+ new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start, user.start + PKG_UIDS[0] - 1),
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1)
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
+
+ // Remove the whitelist.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
+ new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.stop),
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2],
+ user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[0]);
+
+ // Add the whitelist.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
+ new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
+ }));
+ assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+ assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
+
+ // Try whitelisting 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 whitelist, they (and only they) should be ignored.
+ // Whitelisted 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(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
+ new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
+ }));
+ verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[]{
+ new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1),
+ new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+ }));
+ }
+
+ @Test
public void testLockdownAddingAProfile() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
setMockedUsers(primaryUser);
@@ -288,7 +396,7 @@
final UidRange profile = UidRange.createForUser(tempProfile.id);
// Set lockdown.
- assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -414,7 +522,7 @@
.cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Start showing a notification for disconnected once always-on.
- vpn.setAlwaysOnPackage(PKGS[0], false);
+ vpn.setAlwaysOnPackage(PKGS[0], false, null);
order.verify(mNotificationManager)
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
@@ -428,7 +536,7 @@
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Notification should be cleared after unsetting always-on package.
- vpn.setAlwaysOnPackage(null, false);
+ vpn.setAlwaysOnPackage(null, false, null);
order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
}
@@ -441,24 +549,28 @@
final Network wifi = new Network(2);
final Map<Network, NetworkCapabilities> networks = new HashMap<>();
- networks.put(mobile, new NetworkCapabilities()
- .addTransportType(TRANSPORT_CELLULAR)
- .addCapability(NET_CAPABILITY_INTERNET)
- .addCapability(NET_CAPABILITY_NOT_METERED)
- .addCapability(NET_CAPABILITY_NOT_CONGESTED)
- .setLinkDownstreamBandwidthKbps(10));
- networks.put(wifi, new NetworkCapabilities()
- .addTransportType(TRANSPORT_WIFI)
- .addCapability(NET_CAPABILITY_INTERNET)
- .addCapability(NET_CAPABILITY_NOT_ROAMING)
- .addCapability(NET_CAPABILITY_NOT_CONGESTED)
- .setLinkUpstreamBandwidthKbps(20));
+ networks.put(
+ mobile,
+ new NetworkCapabilities()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .setLinkDownstreamBandwidthKbps(10));
+ networks.put(
+ wifi,
+ new NetworkCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_NOT_ROAMING)
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .setLinkUpstreamBandwidthKbps(20));
setMockedNetworks(networks);
final NetworkCapabilities caps = new NetworkCapabilities();
Vpn.applyUnderlyingCapabilities(
- mConnectivityManager, new Network[] {}, caps);
+ mConnectivityManager, new Network[] {}, caps, false /* isAlwaysMetered */);
assertTrue(caps.hasTransport(TRANSPORT_VPN));
assertFalse(caps.hasTransport(TRANSPORT_CELLULAR));
assertFalse(caps.hasTransport(TRANSPORT_WIFI));
@@ -469,18 +581,32 @@
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
Vpn.applyUnderlyingCapabilities(
- mConnectivityManager, new Network[] {mobile}, caps);
+ mConnectivityManager,
+ new Network[] {mobile},
+ caps,
+ false /* isAlwaysMetered */);
assertTrue(caps.hasTransport(TRANSPORT_VPN));
assertTrue(caps.hasTransport(TRANSPORT_CELLULAR));
assertFalse(caps.hasTransport(TRANSPORT_WIFI));
assertEquals(10, caps.getLinkDownstreamBandwidthKbps());
assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkUpstreamBandwidthKbps());
- assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED));
assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
Vpn.applyUnderlyingCapabilities(
- mConnectivityManager, new Network[] {wifi}, caps);
+ mConnectivityManager, new Network[] {wifi}, caps, false /* isAlwaysMetered */);
+ assertTrue(caps.hasTransport(TRANSPORT_VPN));
+ assertFalse(caps.hasTransport(TRANSPORT_CELLULAR));
+ assertTrue(caps.hasTransport(TRANSPORT_WIFI));
+ assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkDownstreamBandwidthKbps());
+ assertEquals(20, caps.getLinkUpstreamBandwidthKbps());
+ assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+ assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
+
+ Vpn.applyUnderlyingCapabilities(
+ mConnectivityManager, new Network[] {wifi}, caps, true /* isAlwaysMetered */);
assertTrue(caps.hasTransport(TRANSPORT_VPN));
assertFalse(caps.hasTransport(TRANSPORT_CELLULAR));
assertTrue(caps.hasTransport(TRANSPORT_WIFI));
@@ -491,7 +617,10 @@
assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
Vpn.applyUnderlyingCapabilities(
- mConnectivityManager, new Network[] {mobile, wifi}, caps);
+ mConnectivityManager,
+ new Network[] {mobile, wifi},
+ caps,
+ false /* isAlwaysMetered */);
assertTrue(caps.hasTransport(TRANSPORT_VPN));
assertTrue(caps.hasTransport(TRANSPORT_CELLULAR));
assertTrue(caps.hasTransport(TRANSPORT_WIFI));
@@ -511,13 +640,15 @@
private static void assertBlocked(Vpn vpn, int... uids) {
for (int uid : uids) {
- assertTrue("Uid " + uid + " should be blocked", vpn.isBlockingUid(uid));
+ final boolean blocked = vpn.getLockdown() && vpn.isBlockingUid(uid);
+ assertTrue("Uid " + uid + " should be blocked", blocked);
}
}
private static void assertUnblocked(Vpn vpn, int... uids) {
for (int uid : uids) {
- assertFalse("Uid " + uid + " should not be blocked", vpn.isBlockingUid(uid));
+ final boolean blocked = vpn.getLockdown() && vpn.isBlockingUid(uid);
+ assertFalse("Uid " + uid + " should not be blocked", blocked);
}
}
@@ -563,7 +694,9 @@
doAnswer(invocation -> {
final String appName = (String) invocation.getArguments()[0];
final int userId = (int) invocation.getArguments()[1];
- return UserHandle.getUid(userId, packages.get(appName));
+ 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) {
}
@@ -594,84 +727,4 @@
"::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6",
"fe00::/8", "2605:ef80:e:af1d::/64");
}
-
- @Test
- public void testProvidesRoutesToMostDestinations() {
- final LinkProperties lp = new LinkProperties();
-
- // Default route provides routes to all IPv4 destinations.
- lp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
- assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
- // Empty LP provides routes to no destination
- lp.clear();
- assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
- // All IPv4 routes except for local networks. This is the case most relevant
- // to this function. It provides routes to almost the entire space.
- // (clone the stream so that we can reuse it later)
- publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
- assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
- // Removing a 16-bit prefix, which is 65536 addresses. This is still enough to
- // provide routes to "most" destinations.
- lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16")));
- assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
- // Remove the /2 route, which represent a quarter of the available routing space.
- // This LP does not provides routes to "most" destinations any more.
- lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2")));
- assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
- lp.clear();
- publicIpV6Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
- assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
- lp.removeRoute(new RouteInfo(new IpPrefix("::/1")));
- assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
- // V6 does not provide sufficient coverage but v4 does
- publicIpV4Routes().forEach(s -> lp.addRoute(new RouteInfo(new IpPrefix(s))));
- assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
- // V4 still does
- lp.removeRoute(new RouteInfo(new IpPrefix("192.169.0.0/16")));
- assertTrue(Vpn.providesRoutesToMostDestinations(lp));
-
- // V4 does not any more
- lp.removeRoute(new RouteInfo(new IpPrefix("64.0.0.0/2")));
- assertFalse(Vpn.providesRoutesToMostDestinations(lp));
-
- // V4 does not, but V6 has sufficient coverage again
- lp.addRoute(new RouteInfo(new IpPrefix("::/1")));
- assertTrue(Vpn.providesRoutesToMostDestinations(lp));
- }
-
- @Test
- public void testDoesNotLockUpWithTooManyRoutes() {
- final LinkProperties lp = new LinkProperties();
- final byte[] ad = new byte[4];
- // Actually evaluating this many routes under 1500ms is impossible on
- // current hardware and for some time, as the algorithm is O(n²).
- // Make sure the system has a safeguard against this and does not
- // lock up.
- final int MAX_ROUTES = 4000;
- final long MAX_ALLOWED_TIME_MS = 1500;
- for (int i = 0; i < MAX_ROUTES; ++i) {
- ad[0] = (byte)((i >> 24) & 0xFF);
- ad[1] = (byte)((i >> 16) & 0xFF);
- ad[2] = (byte)((i >> 8) & 0xFF);
- ad[3] = (byte)(i & 0xFF);
- try {
- lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.getByAddress(ad), 32)));
- } catch (UnknownHostException e) {
- // UnknownHostException is only thrown for an address of illegal length,
- // which can't happen in the case above.
- }
- }
- final long start = SystemClock.currentThreadTimeMillis();
- assertTrue(Vpn.providesRoutesToMostDestinations(lp));
- final long end = SystemClock.currentThreadTimeMillis();
- assertTrue(end - start < MAX_ALLOWED_TIME_MS);
- }
}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
index b870bbd..858358c 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -26,10 +26,11 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.server.LocalServices;
import org.junit.After;
@@ -160,7 +161,7 @@
}
private void setHasCarrierPrivileges(boolean hasPrivileges) {
- when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn(
+ when(mTm.checkCarrierPrivilegesForPackageAnyPhone(TEST_PKG)).thenReturn(
hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
: TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
new file mode 100644
index 0000000..28785f7
--- /dev/null
+++ b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.ROAMING_YES;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.NetworkStats;
+
+import com.android.internal.net.VpnInfo;
+
+/** Superclass with utilities for NetworkStats(Service|Factory)Test */
+abstract class NetworkStatsBaseTest {
+ static final String TEST_IFACE = "test0";
+ static final String TEST_IFACE2 = "test1";
+ static final String TUN_IFACE = "test_nss_tun0";
+
+ static final int UID_RED = 1001;
+ static final int UID_BLUE = 1002;
+ static final int UID_GREEN = 1003;
+ static final int UID_VPN = 1004;
+
+ void assertValues(NetworkStats stats, String iface, int uid, long rxBytes,
+ long rxPackets, long txBytes, long txPackets) {
+ assertValues(
+ stats, iface, uid, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+ rxBytes, rxPackets, txBytes, txPackets, 0);
+ }
+
+ void assertValues(NetworkStats stats, String iface, int uid, int set, int tag,
+ int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
+ long txBytes, long txPackets, long operations) {
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final int[] sets;
+ if (set == SET_ALL) {
+ sets = new int[] {SET_ALL, SET_DEFAULT, SET_FOREGROUND};
+ } else {
+ sets = new int[] {set};
+ }
+
+ final int[] roamings;
+ if (roaming == ROAMING_ALL) {
+ roamings = new int[] {ROAMING_ALL, ROAMING_YES, ROAMING_NO};
+ } else {
+ roamings = new int[] {roaming};
+ }
+
+ final int[] meterings;
+ if (metered == METERED_ALL) {
+ meterings = new int[] {METERED_ALL, METERED_YES, METERED_NO};
+ } else {
+ meterings = new int[] {metered};
+ }
+
+ final int[] defaultNetworks;
+ if (defaultNetwork == DEFAULT_NETWORK_ALL) {
+ defaultNetworks =
+ new int[] {DEFAULT_NETWORK_ALL, DEFAULT_NETWORK_YES, DEFAULT_NETWORK_NO};
+ } else {
+ defaultNetworks = new int[] {defaultNetwork};
+ }
+
+ for (int s : sets) {
+ for (int r : roamings) {
+ for (int m : meterings) {
+ for (int d : defaultNetworks) {
+ final int i = stats.findIndex(iface, uid, s, tag, m, r, d);
+ if (i != -1) {
+ entry.add(stats.getValues(i, null));
+ }
+ }
+ }
+ }
+ }
+
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ assertEquals("unexpected operations", operations, entry.operations);
+ }
+
+ VpnInfo createVpnInfo(String[] underlyingIfaces) {
+ VpnInfo info = new VpnInfo();
+ info.ownerUid = UID_VPN;
+ info.vpnIface = TUN_IFACE;
+ info.underlyingIfaces = underlyingIfaces;
+ return info;
+ }
+}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 6f14332..8f90f13 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -26,12 +26,14 @@
import static android.os.Process.myUid;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.server.net.NetworkStatsCollection.multiplySafe;
+
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
-import static com.android.server.net.NetworkStatsCollection.multiplySafe;
-
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkIdentity;
@@ -40,20 +42,25 @@
import android.net.NetworkTemplate;
import android.os.Process;
import android.os.UserHandle;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
-import android.test.MoreAsserts;
import android.text.format.DateUtils;
import android.util.RecurrenceRule;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.frameworks.tests.net.R;
import libcore.io.IoUtils;
import libcore.io.Streams;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
@@ -68,11 +75,6 @@
import java.util.ArrayList;
import java.util.List;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
/**
* Tests for {@link NetworkStatsCollection}.
*/
@@ -99,6 +101,7 @@
@After
public void tearDown() throws Exception {
RecurrenceRule.sClock = sOriginalClock;
+ NetworkTemplate.resetForceAllNetworkTypes();
}
private void setClock(Instant instant) {
@@ -237,11 +240,11 @@
60 * MINUTE_IN_MILLIS, entry);
// Verify the set of relevant UIDs for each access level.
- MoreAsserts.assertEquals(new int[] { myUid },
+ assertArrayEquals(new int[] { myUid },
collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
- MoreAsserts.assertEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
+ assertArrayEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
collection.getRelevantUids(NetworkStatsAccess.Level.USER));
- MoreAsserts.assertEquals(
+ assertArrayEquals(
new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
diff --git a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
new file mode 100644
index 0000000..a21f509
--- /dev/null
+++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2011 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;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_ALL;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.content.res.Resources;
+import android.net.NetworkStats;
+import android.net.TrafficStats;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.tests.net.R;
+import com.android.internal.net.VpnInfo;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/** Tests for {@link NetworkStatsFactory}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
+ private static final String CLAT_PREFIX = "v4-";
+
+ private File mTestProc;
+ private NetworkStatsFactory mFactory;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestProc = new File(InstrumentationRegistry.getContext().getFilesDir(), "proc");
+ if (mTestProc.exists()) {
+ IoUtils.deleteContents(mTestProc);
+ }
+
+ // The libandroid_servers which have the native method is not available to
+ // applications. So in order to have a test support native library, the native code
+ // related to networkStatsFactory is compiled to a minimal native library and loaded here.
+ System.loadLibrary("networkstatsfactorytestjni");
+ mFactory = new NetworkStatsFactory(mTestProc, false);
+ mFactory.updateVpnInfos(new VpnInfo[0]);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mFactory = null;
+
+ if (mTestProc.exists()) {
+ IoUtils.deleteContents(mTestProc);
+ }
+ }
+
+ @Test
+ public void testNetworkStatsDetail() throws Exception {
+ final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
+
+ assertEquals(70, stats.size());
+ assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 18621L, 2898L);
+ assertStatsEntry(stats, "wlan0", 10011, SET_DEFAULT, 0x0, 35777L, 5718L);
+ assertStatsEntry(stats, "wlan0", 10021, SET_DEFAULT, 0x7fffff01, 562386L, 49228L);
+ assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 227423L);
+ assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
+ }
+
+ @Test
+ public void vpnRewriteTrafficThroughItself() throws Exception {
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+ mFactory.updateVpnInfos(vpnInfos);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+ // overhead per packet):
+ //
+ // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+ // over VPN.
+ // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+ // over VPN.
+ //
+ // VPN UID rewrites packets read from TUN back to TUN, plus some of its own traffic
+
+ final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_rewrite_through_self);
+
+ assertValues(tunStats, TUN_IFACE, UID_RED, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, 2000L, 200L, 1000L, 100L, 0);
+ assertValues(tunStats, TUN_IFACE, UID_BLUE, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, 1000L, 100L, 500L, 50L, 0);
+ assertValues(tunStats, TUN_IFACE, UID_VPN, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, 0L, 0L, 1600L, 160L, 0);
+
+ assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
+ assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
+ assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 260L, 26L);
+ }
+
+ @Test
+ public void vpnWithClat() throws Exception {
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {CLAT_PREFIX + TEST_IFACE})};
+ mFactory.updateVpnInfos(vpnInfos);
+ mFactory.noteStackedIface(CLAT_PREFIX + TEST_IFACE, TEST_IFACE);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+ // overhead per packet):
+ // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+ // over VPN.
+ // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+ // over VPN.
+ // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over v4-WiFi, and clat
+ // added 20 bytes per packet of extra overhead
+ //
+ // For 1650 bytes sent over v4-WiFi, 4650 bytes were actually sent over WiFi, which is
+ // expected to be split as follows:
+ // UID_RED: 1000 bytes, 100 packets
+ // UID_BLUE: 500 bytes, 50 packets
+ // UID_VPN: 3150 bytes, 0 packets
+ //
+ // For 3300 bytes received over v4-WiFi, 9300 bytes were actually sent over WiFi, which is
+ // expected to be split as follows:
+ // UID_RED: 2000 bytes, 200 packets
+ // UID_BLUE: 1000 bytes, 100 packets
+ // UID_VPN: 6300 bytes, 0 packets
+ final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_with_clat);
+
+ assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_RED, 2000L, 200L, 1000, 100L);
+ assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
+ assertValues(tunStats, CLAT_PREFIX + TEST_IFACE, UID_VPN, 6300L, 0L, 3150L, 0L);
+ }
+
+ @Test
+ public void vpnWithOneUnderlyingIface() throws Exception {
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+ mFactory.updateVpnInfos(vpnInfos);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+ // overhead per packet):
+ // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+ // over VPN.
+ // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+ // over VPN.
+ // VPN sent 1650 bytes (150 packets), and received 3300 (300 packets) over WiFi.
+ // Of 1650 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes
+ // attributed to UID_BLUE, and 150 bytes attributed to UID_VPN.
+ // Of 3300 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes
+ // attributed to UID_BLUE, and 300 bytes attributed to UID_VPN.
+ final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying);
+
+ assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
+ assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
+ assertValues(tunStats, TEST_IFACE, UID_VPN, 300L, 0L, 150L, 0L);
+ }
+
+ @Test
+ public void vpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception {
+ // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+ mFactory.updateVpnInfos(vpnInfos);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+ // overhead per packet):
+ // 1000 bytes (100 packets) were sent, and 2000 bytes (200 packets) were received by UID_RED
+ // over VPN.
+ // 500 bytes (50 packets) were sent, and 1000 bytes (100 packets) were received by UID_BLUE
+ // over VPN.
+ // Additionally, the VPN sends 6000 bytes (600 packets) of its own traffic into the tun
+ // interface (passing that traffic to the VPN endpoint), and receives 5000 bytes (500
+ // packets) from it. Including overhead that is 6600/5500 bytes.
+ // VPN sent 8250 bytes (750 packets), and received 8800 (800 packets) over WiFi.
+ // Of 8250 bytes sent over WiFi, expect 1000 bytes attributed to UID_RED, 500 bytes
+ // attributed to UID_BLUE, and 6750 bytes attributed to UID_VPN.
+ // Of 8800 bytes received over WiFi, expect 2000 bytes attributed to UID_RED, 1000 bytes
+ // attributed to UID_BLUE, and 5800 bytes attributed to UID_VPN.
+ final NetworkStats tunStats =
+ parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_own_traffic);
+
+ assertValues(tunStats, TEST_IFACE, UID_RED, 2000L, 200L, 1000L, 100L);
+ assertValues(tunStats, TEST_IFACE, UID_BLUE, 1000L, 100L, 500L, 50L);
+ assertValues(tunStats, TEST_IFACE, UID_VPN, 5800L, 500L, 6750L, 600L);
+ }
+
+ @Test
+ public void vpnWithOneUnderlyingIface_withCompression() throws Exception {
+ // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+ mFactory.updateVpnInfos(vpnInfos);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+ // overhead per packet):
+ // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
+ // 3000 bytes (300 packets) were sent/received by UID_BLUE over VPN.
+ // VPN sent/received 1000 bytes (100 packets) over WiFi.
+ // Of 1000 bytes over WiFi, expect 250 bytes attributed UID_RED and 750 bytes to UID_BLUE,
+ // with nothing attributed to UID_VPN for both rx/tx traffic.
+ final NetworkStats tunStats =
+ parseDetailedStats(R.raw.xt_qtaguid_vpn_one_underlying_compression);
+
+ assertValues(tunStats, TEST_IFACE, UID_RED, 250L, 25L, 250L, 25L);
+ assertValues(tunStats, TEST_IFACE, UID_BLUE, 750L, 75L, 750L, 75L);
+ assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
+ }
+
+ @Test
+ public void vpnWithTwoUnderlyingIfaces_packetDuplication() throws Exception {
+ // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+ // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+ // Additionally, VPN is duplicating traffic across both WiFi and Cell.
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+ mFactory.updateVpnInfos(vpnInfos);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+ // overhead per packet):
+ // 1000 bytes (100 packets) were sent/received by UID_RED and UID_BLUE over VPN.
+ // VPN sent/received 4400 bytes (400 packets) over both WiFi and Cell (8800 bytes in total).
+ // Of 8800 bytes over WiFi/Cell, expect:
+ // - 500 bytes rx/tx each over WiFi/Cell attributed to both UID_RED and UID_BLUE.
+ // - 1200 bytes rx/tx each over WiFi/Cell for VPN_UID.
+ final NetworkStats tunStats =
+ parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_duplication);
+
+ assertValues(tunStats, TEST_IFACE, UID_RED, 500L, 50L, 500L, 50L);
+ assertValues(tunStats, TEST_IFACE, UID_BLUE, 500L, 50L, 500L, 50L);
+ assertValues(tunStats, TEST_IFACE, UID_VPN, 1200L, 100L, 1200L, 100L);
+ assertValues(tunStats, TEST_IFACE2, UID_RED, 500L, 50L, 500L, 50L);
+ assertValues(tunStats, TEST_IFACE2, UID_BLUE, 500L, 50L, 500L, 50L);
+ assertValues(tunStats, TEST_IFACE2, UID_VPN, 1200L, 100L, 1200L, 100L);
+ }
+
+ @Test
+ public void vpnWithTwoUnderlyingIfaces_splitTraffic() throws Exception {
+ // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+ // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+ // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell.
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+ mFactory.updateVpnInfos(vpnInfos);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+ // overhead per packet):
+ // 1000 bytes (100 packets) were sent, and 500 bytes (50 packets) received by UID_RED over
+ // VPN.
+ // VPN sent 660 bytes (60 packets) over WiFi and 440 bytes (40 packets) over Cell.
+ // And, it received 330 bytes (30 packets) over WiFi and 220 bytes (20 packets) over Cell.
+ // For UID_RED, expect 600 bytes attributed over WiFi and 400 bytes over Cell for sent (tx)
+ // traffic. For received (rx) traffic, expect 300 bytes over WiFi and 200 bytes over Cell.
+ //
+ // For UID_VPN, expect 60 bytes attributed over WiFi and 40 bytes over Cell for tx traffic.
+ // And, 30 bytes over WiFi and 20 bytes over Cell for rx traffic.
+ final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_split);
+
+ assertValues(tunStats, TEST_IFACE, UID_RED, 300L, 30L, 600L, 60L);
+ assertValues(tunStats, TEST_IFACE, UID_VPN, 30L, 0L, 60L, 0L);
+ assertValues(tunStats, TEST_IFACE2, UID_RED, 200L, 20L, 400L, 40L);
+ assertValues(tunStats, TEST_IFACE2, UID_VPN, 20L, 0L, 40L, 0L);
+ }
+
+ @Test
+ public void vpnWithTwoUnderlyingIfaces_splitTrafficWithCompression() throws Exception {
+ // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
+ // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
+ // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell.
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+ mFactory.updateVpnInfos(vpnInfos);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface:
+ // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
+ // VPN sent/received 600 bytes (60 packets) over WiFi and 200 bytes (20 packets) over Cell.
+ // For UID_RED, expect 600 bytes attributed over WiFi and 200 bytes over Cell for both
+ // rx/tx.
+ // UID_VPN gets nothing attributed to it (avoiding negative stats).
+ final NetworkStats tunStats =
+ parseDetailedStats(R.raw.xt_qtaguid_vpn_two_underlying_split_compression);
+
+ assertValues(tunStats, TEST_IFACE, UID_RED, 600L, 60L, 600L, 60L);
+ assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
+ assertValues(tunStats, TEST_IFACE2, UID_RED, 200L, 20L, 200L, 20L);
+ assertValues(tunStats, TEST_IFACE2, UID_VPN, 0L, 0L, 0L, 0L);
+ }
+
+ @Test
+ public void vpnWithIncorrectUnderlyingIface() throws Exception {
+ // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2),
+ // but has declared only WiFi (TEST_IFACE) in its underlying network set.
+ VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+ mFactory.updateVpnInfos(vpnInfos);
+
+ // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
+ // overhead per packet):
+ // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
+ // VPN sent/received 1100 bytes (100 packets) over Cell.
+ // Of 1100 bytes over Cell, expect all of it attributed to UID_VPN for both rx/tx traffic.
+ final NetworkStats tunStats = parseDetailedStats(R.raw.xt_qtaguid_vpn_incorrect_iface);
+
+ assertValues(tunStats, TEST_IFACE, UID_RED, 0L, 0L, 0L, 0L);
+ assertValues(tunStats, TEST_IFACE, UID_VPN, 0L, 0L, 0L, 0L);
+ assertValues(tunStats, TEST_IFACE2, UID_RED, 0L, 0L, 0L, 0L);
+ assertValues(tunStats, TEST_IFACE2, UID_VPN, 1100L, 100L, 1100L, 100L);
+ }
+
+ @Test
+ public void testKernelTags() throws Exception {
+ assertEquals(0, kernelToTag("0x0000000000000000"));
+ assertEquals(0x32, kernelToTag("0x0000003200000000"));
+ assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
+ assertEquals(0, kernelToTag("0x0000000000000000"));
+ assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
+
+ assertEquals(0, kernelToTag("0x0"));
+ assertEquals(0, kernelToTag("0xf00d"));
+ assertEquals(1, kernelToTag("0x100000000"));
+ assertEquals(14438007, kernelToTag("0xdc4e7700000000"));
+ assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
+ }
+
+ @Test
+ public void testNetworkStatsWithSet() throws Exception {
+ final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
+ assertEquals(70, stats.size());
+ assertStatsEntry(stats, "rmnet1", 10021, SET_DEFAULT, 0x30100000, 219110L, 578L, 227423L,
+ 676L);
+ assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
+ }
+
+ @Test
+ public void testNetworkStatsSingle() throws Exception {
+ stageFile(R.raw.xt_qtaguid_iface_typical, file("net/xt_qtaguid/iface_stat_all"));
+
+ final NetworkStats stats = mFactory.readNetworkStatsSummaryDev();
+ assertEquals(6, stats.size());
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
+ assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
+ assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
+ }
+
+ @Test
+ public void testNetworkStatsXt() throws Exception {
+ stageFile(R.raw.xt_qtaguid_iface_fmt_typical, file("net/xt_qtaguid/iface_stat_fmt"));
+
+ final NetworkStats stats = mFactory.readNetworkStatsSummaryXt();
+ assertEquals(3, stats.size());
+ assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 6824L, 16L, 5692L, 10L);
+ assertStatsEntry(stats, "rmnet1", UID_ALL, SET_ALL, TAG_NONE, 11153922L, 8051L, 190226L,
+ 2468L);
+ assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
+ }
+
+ @Test
+ public void testDoubleClatAccountingSimple() throws Exception {
+ mFactory.noteStackedIface("v4-wlan0", "wlan0");
+
+ // xt_qtaguid_with_clat_simple is a synthetic file that simulates
+ // - 213 received 464xlat packets of size 200 bytes
+ // - 41 sent 464xlat packets of size 100 bytes
+ // - no other traffic on base interface for root uid.
+ NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_simple);
+ assertEquals(3, stats.size());
+
+ assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 46860L, 4920L);
+ assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 0L, 0L);
+ }
+
+ @Test
+ public void testDoubleClatAccounting() throws Exception {
+ mFactory.noteStackedIface("v4-wlan0", "wlan0");
+
+ NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat);
+ assertEquals(42, stats.size());
+
+ assertStatsEntry(stats, "v4-wlan0", 0, SET_DEFAULT, 0x0, 356L, 276L);
+ assertStatsEntry(stats, "v4-wlan0", 1000, SET_DEFAULT, 0x0, 30812L, 2310L);
+ assertStatsEntry(stats, "v4-wlan0", 10102, SET_DEFAULT, 0x0, 10022L, 3330L);
+ assertStatsEntry(stats, "v4-wlan0", 10060, SET_DEFAULT, 0x0, 9532772L, 254112L);
+ assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, 15229L, 0L);
+ assertStatsEntry(stats, "wlan0", 1000, SET_DEFAULT, 0x0, 6126L, 2013L);
+ assertStatsEntry(stats, "wlan0", 10013, SET_DEFAULT, 0x0, 0L, 144L);
+ assertStatsEntry(stats, "wlan0", 10018, SET_DEFAULT, 0x0, 5980263L, 167667L);
+ assertStatsEntry(stats, "wlan0", 10060, SET_DEFAULT, 0x0, 134356L, 8705L);
+ assertStatsEntry(stats, "wlan0", 10079, SET_DEFAULT, 0x0, 10926L, 1507L);
+ assertStatsEntry(stats, "wlan0", 10102, SET_DEFAULT, 0x0, 25038L, 8245L);
+ assertStatsEntry(stats, "wlan0", 10103, SET_DEFAULT, 0x0, 0L, 192L);
+ assertStatsEntry(stats, "dummy0", 0, SET_DEFAULT, 0x0, 0L, 168L);
+ assertStatsEntry(stats, "lo", 0, SET_DEFAULT, 0x0, 1288L, 1288L);
+
+ assertNoStatsEntry(stats, "wlan0", 1029, SET_DEFAULT, 0x0);
+ }
+
+ @Test
+ public void testDoubleClatAccounting100MBDownload() throws Exception {
+ // Downloading 100mb from an ipv4 only destination in a foreground activity
+
+ long appRxBytesBefore = 328684029L;
+ long appRxBytesAfter = 439237478L;
+ assertEquals("App traffic should be ~100MB", 110553449, appRxBytesAfter - appRxBytesBefore);
+
+ long rootRxBytesBefore = 1394011L;
+ long rootRxBytesAfter = 1398634L;
+ assertEquals("UID 0 traffic should be ~0", 4623, rootRxBytesAfter - rootRxBytesBefore);
+
+ mFactory.noteStackedIface("v4-wlan0", "wlan0");
+ NetworkStats stats;
+
+ // Stats snapshot before the download
+ stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_before);
+ assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesBefore, 5199872L);
+ assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesBefore, 0L);
+
+ // Stats snapshot after the download
+ stats = parseDetailedStats(R.raw.xt_qtaguid_with_clat_100mb_download_after);
+ assertStatsEntry(stats, "v4-wlan0", 10106, SET_FOREGROUND, 0x0, appRxBytesAfter, 7867488L);
+ assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0x0, rootRxBytesAfter, 0L);
+ }
+
+ /**
+ * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+ * testing purposes.
+ */
+ private void stageFile(int rawId, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private void stageLong(long value, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ FileWriter out = null;
+ try {
+ out = new FileWriter(file);
+ out.write(Long.toString(value));
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private File file(String path) throws Exception {
+ return new File(mTestProc, path);
+ }
+
+ private NetworkStats parseDetailedStats(int resourceId) throws Exception {
+ stageFile(resourceId, file("net/xt_qtaguid/stats"));
+ return mFactory.readNetworkStatsDetail();
+ }
+
+ private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long txBytes) {
+ final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO);
+ if (i < 0) {
+ fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d)",
+ iface, uid, set, tag));
+ }
+ final NetworkStats.Entry entry = stats.getValues(i, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ }
+
+ private static void assertNoStatsEntry(NetworkStats stats, String iface, int uid, int set,
+ int tag) {
+ final int i = stats.findIndex(iface, uid, set, tag, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO);
+ if (i >= 0) {
+ fail("unexpected NetworkStats entry at " + i);
+ }
+ }
+
+ private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ assertStatsEntry(stats, iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertStatsEntry(NetworkStats stats, String iface, int uid, int set,
+ int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
+ long txBytes, long txPackets) {
+ final int i = stats.findIndex(iface, uid, set, tag, metered, roaming, defaultNetwork);
+
+ if (i < 0) {
+ fail(String.format("no NetworkStats for (iface: %s, uid: %d, set: %d, tag: %d, metered:"
+ + " %d, roaming: %d, defaultNetwork: %d)",
+ iface, uid, set, tag, metered, roaming, defaultNetwork));
+ }
+ final NetworkStats.Entry entry = stats.getValues(i, null);
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ }
+}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index 185c3eb..c0f9dc1 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -17,7 +17,6 @@
package com.android.server.net;
import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
import static android.net.NetworkStats.METERED_NO;
@@ -29,15 +28,10 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static com.android.internal.util.TestUtils.waitForIdleHandler;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.when;
import android.app.usage.NetworkStatsManager;
import android.net.DataUsageRequest;
@@ -49,22 +43,17 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.Messenger;
import android.os.Process;
import android.os.UserHandle;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
-import com.android.internal.net.VpnInfo;
-import com.android.server.net.NetworkStatsService;
-import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.List;
+import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
+import com.android.testutils.HandlerUtilsKt;
import org.junit.Before;
import org.junit.Test;
@@ -73,6 +62,8 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.util.Objects;
+
/**
* Tests for {@link NetworkStatsObservers}.
*/
@@ -101,8 +92,6 @@
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
private static final int INVALID_TYPE = -1;
- private static final VpnInfo[] VPN_INFO = new VpnInfo[0];
-
private long mElapsedRealtime;
private HandlerThread mObserverHandlerThread;
@@ -255,8 +244,7 @@
NetworkStats uidSnapshot = null;
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
}
@@ -279,15 +267,13 @@
.addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
NetworkStats uidSnapshot = null;
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
xtSnapshot = new NetworkStats(TEST_START, 1 /* initialSize */)
.addIfaceValues(TEST_IFACE, BASE_BYTES + 1024L, 10L, BASE_BYTES + 2048L, 20L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
}
@@ -311,16 +297,14 @@
.addIfaceValues(TEST_IFACE, BASE_BYTES, 8L, BASE_BYTES, 16L);
NetworkStats uidSnapshot = null;
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
xtSnapshot = new NetworkStats(TEST_START + MINUTE_IN_MILLIS, 1 /* initialSize */)
.addIfaceValues(TEST_IFACE, BASE_BYTES + THRESHOLD_BYTES, 12L,
BASE_BYTES + THRESHOLD_BYTES, 22L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
}
@@ -345,8 +329,7 @@
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
@@ -354,8 +337,7 @@
DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
}
@@ -380,8 +362,7 @@
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
@@ -389,8 +370,7 @@
DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
}
@@ -414,8 +394,7 @@
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
@@ -423,8 +402,7 @@
DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L,
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
}
@@ -449,8 +427,7 @@
.addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
@@ -458,13 +435,12 @@
ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
mStatsObservers.updateStats(
- xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces,
- VPN_INFO, TEST_START);
+ xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
}
private void waitForObserverToIdle() {
- waitForIdleHandler(mObserverHandlerThread, WAIT_TIMEOUT_MS);
- waitForIdleHandler(mHandler, WAIT_TIMEOUT_MS);
+ HandlerUtilsKt.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
+ HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
}
}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 7cf1dc4..1d29a82 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -19,10 +19,10 @@
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
-import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.INTERFACES_ALL;
@@ -37,6 +37,7 @@
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.STATS_PER_IFACE;
import static android.net.NetworkStats.STATS_PER_UID;
+import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStatsHistory.FIELD_ALL;
@@ -50,7 +51,6 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
-import static com.android.internal.util.TestUtils.waitForIdleHandler;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
import static org.junit.Assert.assertEquals;
@@ -59,7 +59,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -91,17 +90,18 @@
import android.os.Messenger;
import android.os.PowerManager;
import android.os.SimpleClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.net.VpnInfo;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.server.LocalServices;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
+import com.android.testutils.HandlerUtilsKt;
import libcore.io.IoUtils;
@@ -127,11 +127,9 @@
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class NetworkStatsServiceTest {
+public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
private static final String TAG = "NetworkStatsServiceTest";
- private static final String TEST_IFACE = "test0";
- private static final String TEST_IFACE2 = "test1";
private static final long TEST_START = 1194220800000L;
private static final String IMSI_1 = "310004";
@@ -142,13 +140,10 @@
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
- private static final int UID_RED = 1001;
- private static final int UID_BLUE = 1002;
- private static final int UID_GREEN = 1003;
-
-
private static final Network WIFI_NETWORK = new Network(100);
private static final Network MOBILE_NETWORK = new Network(101);
+ private static final Network VPN_NETWORK = new Network(102);
+
private static final Network[] NETWORKS_WIFI = new Network[]{ WIFI_NETWORK };
private static final Network[] NETWORKS_MOBILE = new Network[]{ MOBILE_NETWORK };
@@ -161,6 +156,7 @@
private File mStatsDir;
private @Mock INetworkManagementService mNetManager;
+ private @Mock NetworkStatsFactory mStatsFactory;
private @Mock NetworkStatsSettings mSettings;
private @Mock IBinder mBinder;
private @Mock AlarmManager mAlarmManager;
@@ -196,8 +192,8 @@
mService = new NetworkStatsService(
mServiceContext, mNetManager, mAlarmManager, wakeLock, mClock,
- TelephonyManager.getDefault(), mSettings, new NetworkStatsObservers(),
- mStatsDir, getBaseDir(mStatsDir));
+ TelephonyManager.getDefault(), mSettings, mStatsFactory,
+ new NetworkStatsObservers(), mStatsDir, getBaseDir(mStatsDir));
mHandlerThread = new HandlerThread("HandlerThread");
mHandlerThread.start();
Handler.Callback callback = new NetworkStatsService.HandlerCallback(mService);
@@ -211,10 +207,12 @@
expectSystemReady();
mService.systemReady();
+ // Verify that system ready fetches realtime stats
+ verify(mStatsFactory).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
+
mSession = mService.openSession();
assertNotNull("openSession() failed", mSession);
-
// catch INetworkManagementEventObserver during systemReady()
ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
@@ -224,9 +222,6 @@
@After
public void tearDown() throws Exception {
- // Registered by NetworkStatsService's constructor.
- LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class);
-
IoUtils.deleteContents(mStatsDir);
mServiceContext = null;
@@ -247,9 +242,8 @@
NetworkState[] states = new NetworkState[] {buildWifiState()};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
// verify service has empty history for wifi
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -291,9 +285,8 @@
NetworkState[] states = new NetworkState[] {buildWifiState()};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
// verify service has empty history for wifi
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -365,10 +358,8 @@
NetworkState[] states = new NetworkState[] {buildWifiState()};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
// modify some number on wifi, and trigger poll event
incrementCurrentTime(2 * HOUR_IN_MILLIS);
@@ -407,10 +398,8 @@
NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
// create some traffic on first network
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -443,9 +432,8 @@
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
.addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
+ mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
forcePollAndWaitForIdle();
@@ -483,10 +471,8 @@
NetworkState[] states = new NetworkState[] {buildWifiState()};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
// create some traffic
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -542,10 +528,8 @@
NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
// create some traffic
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -570,9 +554,8 @@
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
.addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
+ mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
forcePollAndWaitForIdle();
@@ -600,10 +583,8 @@
NetworkState[] states = new NetworkState[] {buildWifiState()};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
// create some traffic for two apps
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -659,9 +640,8 @@
NetworkState[] states = new NetworkState[] {buildWifiState()};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
NetworkStats.Entry entry1 = new NetworkStats.Entry(
TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L);
@@ -703,9 +683,8 @@
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
NetworkStats.Entry uidStats = new NetworkStats.Entry(
TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
@@ -717,10 +696,13 @@
"otherif", UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
final String[] ifaceFilter = new String[] { TEST_IFACE };
+ final String[] augmentedIfaceFilter = new String[] { stackedIface, TEST_IFACE };
incrementCurrentTime(HOUR_IN_MILLIS);
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
- when(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL), any()))
+ when(mStatsFactory.augmentWithStackedInterfaces(eq(ifaceFilter)))
+ .thenReturn(augmentedIfaceFilter);
+ when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL)))
.thenReturn(new NetworkStats(getElapsedRealtime(), 1)
.addValues(uidStats));
when(mNetManager.getNetworkStatsTethering(STATS_PER_UID))
@@ -730,11 +712,19 @@
NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
- verify(mNetManager, times(1)).getNetworkStatsUidDetail(eq(UID_ALL), argThat(ifaces ->
- ifaces != null && ifaces.length == 2
- && ArrayUtils.contains(ifaces, TEST_IFACE)
- && ArrayUtils.contains(ifaces, stackedIface)));
-
+ // mStatsFactory#readNetworkStatsDetail() has the following invocations:
+ // 1) NetworkStatsService#systemReady from #setUp.
+ // 2) mService#forceUpdateIfaces in the test above.
+ //
+ // Additionally, we should have one call from the above call to mService#getDetailedUidStats
+ // with the augmented ifaceFilter.
+ verify(mStatsFactory, times(2)).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
+ verify(mStatsFactory, times(1)).readNetworkStatsDetail(
+ eq(UID_ALL),
+ eq(augmentedIfaceFilter),
+ eq(TAG_ALL));
+ assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE));
+ assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface));
assertEquals(2, stats.size());
assertEquals(uidStats, stats.getValues(0, null));
assertEquals(tetheredStats1, stats.getValues(1, null));
@@ -747,10 +737,8 @@
NetworkState[] states = new NetworkState[] {buildWifiState()};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
// create some initial traffic
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -805,10 +793,8 @@
NetworkState[] states = new NetworkState[] {buildWifiState(true /* isMetered */)};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
// create some initial traffic
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -846,10 +832,8 @@
new NetworkState[] {buildMobile3gState(IMSI_1, true /* isRoaming */)};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
// Create some traffic
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -885,10 +869,8 @@
NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_MOBILE, new VpnInfo[0], states, getActiveIface(states));
-
+ mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
// create some tethering traffic
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -917,7 +899,6 @@
assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0);
-
}
@Test
@@ -928,13 +909,11 @@
NetworkState[] states = new NetworkState[] {buildWifiState()};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
- expectBandwidthControlCheck();
- mService.forceUpdateIfaces(NETWORKS_WIFI, new VpnInfo[0], states, getActiveIface(states));
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
// verify service has empty history for wifi
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
- String callingPackage = "the.calling.package";
long thresholdInBytes = 1L; // very small; should be overriden by framework
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdInBytes);
@@ -949,11 +928,9 @@
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
-
-
// Register and verify request and that binder was called
DataUsageRequest request =
- mService.registerUsageCallback(callingPackage, inputRequest,
+ mService.registerUsageCallback(mServiceContext.getOpPackageName(), inputRequest,
messenger, mBinder);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
@@ -962,14 +939,11 @@
// Send dummy message to make sure that any previous message has been handled
mHandler.sendMessage(mHandler.obtainMessage(-1));
- waitForIdleHandler(mHandler, WAIT_TIMEOUT);
-
-
+ HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
// Make sure that the caller binder gets connected
verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
-
// modify some number on wifi, and trigger poll event
// not enough traffic to call data usage callback
incrementCurrentTime(HOUR_IN_MILLIS);
@@ -1075,7 +1049,6 @@
private void expectSystemReady() throws Exception {
expectNetworkStatsSummary(buildEmptyStats());
- expectBandwidthControlCheck();
}
private String getActiveIface(NetworkState... states) throws Exception {
@@ -1097,11 +1070,11 @@
}
private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
- when(mNetManager.getNetworkStatsSummaryDev()).thenReturn(summary);
+ when(mStatsFactory.readNetworkStatsSummaryDev()).thenReturn(summary);
}
private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
- when(mNetManager.getNetworkStatsSummaryXt()).thenReturn(summary);
+ when(mStatsFactory.readNetworkStatsSummaryXt()).thenReturn(summary);
}
private void expectNetworkStatsTethering(int how, NetworkStats stats)
@@ -1115,7 +1088,8 @@
private void expectNetworkStatsUidDetail(NetworkStats detail, NetworkStats tetherStats)
throws Exception {
- when(mNetManager.getNetworkStatsUidDetail(UID_ALL, INTERFACES_ALL)).thenReturn(detail);
+ when(mStatsFactory.readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL))
+ .thenReturn(detail);
// also include tethering details, since they are folded into UID
when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)).thenReturn(tetherStats);
@@ -1143,10 +1117,6 @@
when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
}
- private void expectBandwidthControlCheck() throws Exception {
- when(mNetManager.isBandwidthControlEnabled()).thenReturn(true);
- }
-
private void assertStatsFilesExist(boolean exist) {
final File basePath = new File(mStatsDir, "netstats");
if (exist) {
@@ -1156,59 +1126,6 @@
}
}
- private static void assertValues(NetworkStats stats, String iface, int uid, int set,
- int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets,
- long txBytes, long txPackets, int operations) {
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- final int[] sets;
- if (set == SET_ALL) {
- sets = new int[] { SET_ALL, SET_DEFAULT, SET_FOREGROUND };
- } else {
- sets = new int[] { set };
- }
-
- final int[] roamings;
- if (roaming == ROAMING_ALL) {
- roamings = new int[] { ROAMING_ALL, ROAMING_YES, ROAMING_NO };
- } else {
- roamings = new int[] { roaming };
- }
-
- final int[] meterings;
- if (metered == METERED_ALL) {
- meterings = new int[] { METERED_ALL, METERED_YES, METERED_NO };
- } else {
- meterings = new int[] { metered };
- }
-
- final int[] defaultNetworks;
- if (defaultNetwork == DEFAULT_NETWORK_ALL) {
- defaultNetworks = new int[] { DEFAULT_NETWORK_ALL, DEFAULT_NETWORK_YES,
- DEFAULT_NETWORK_NO };
- } else {
- defaultNetworks = new int[] { defaultNetwork };
- }
-
- for (int s : sets) {
- for (int r : roamings) {
- for (int m : meterings) {
- for (int d : defaultNetworks) {
- final int i = stats.findIndex(iface, uid, s, tag, m, r, d);
- if (i != -1) {
- entry.add(stats.getValues(i, null));
- }
- }
- }
- }
- }
-
- assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
- assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
- assertEquals("unexpected txBytes", txBytes, entry.txBytes);
- assertEquals("unexpected txPackets", txPackets, entry.txPackets);
- assertEquals("unexpected operations", operations, entry.operations);
- }
-
private static void assertValues(NetworkStatsHistory stats, long start, long end, long rxBytes,
long rxPackets, long txBytes, long txPackets, int operations) {
final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
@@ -1266,6 +1183,14 @@
return new NetworkStats(getElapsedRealtime(), 0);
}
+ private static NetworkState buildVpnState() {
+ final NetworkInfo info = new NetworkInfo(TYPE_VPN, 0, null, null);
+ info.setDetailedState(DetailedState.CONNECTED, null, null);
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(TUN_IFACE);
+ return new NetworkState(info, prop, new NetworkCapabilities(), VPN_NETWORK, null, null);
+ }
+
private long getElapsedRealtime() {
return mElapsedRealtime;
}
@@ -1286,7 +1211,7 @@
mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
// Send dummy message to make sure that any previous message has been handled
mHandler.sendMessage(mHandler.obtainMessage(-1));
- waitForIdleHandler(mHandler, WAIT_TIMEOUT);
+ HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
}
static class LatchedHandler extends Handler {
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
new file mode 100644
index 0000000..fb84611
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 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.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.ipmemorystore.NetworkAttributes;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/** Unit tests for {@link NetworkAttributes}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NetworkAttributesTest {
+ private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_";
+ private static final float EPSILON = 0.0001f;
+
+ // This is running two tests to make sure the total weight is the sum of all weights. To be
+ // sure this is not fireproof, but you'd kind of need to do it on purpose to pass.
+ @Test
+ public void testTotalWeight() throws IllegalAccessException, UnknownHostException {
+ // Make sure that TOTAL_WEIGHT is equal to the sum of the fields starting with WEIGHT_
+ float sum = 0f;
+ final Field[] fieldList = NetworkAttributes.class.getDeclaredFields();
+ for (final Field field : fieldList) {
+ if (!field.getName().startsWith(WEIGHT_FIELD_NAME_PREFIX)) continue;
+ field.setAccessible(true);
+ sum += (float) field.get(null);
+ }
+ assertEquals(sum, NetworkAttributes.TOTAL_WEIGHT, EPSILON);
+
+ // Use directly the constructor with all attributes, and make sure that when compared
+ // to itself the score is a clean 1.0f.
+ final NetworkAttributes na =
+ new NetworkAttributes(
+ (Inet4Address) Inet4Address.getByAddress(new byte[] {1, 2, 3, 4}),
+ System.currentTimeMillis() + 7_200_000,
+ "some hint",
+ Arrays.asList(Inet4Address.getByAddress(new byte[] {5, 6, 7, 8}),
+ Inet4Address.getByAddress(new byte[] {9, 0, 1, 2})),
+ 98);
+ assertEquals(1.0f, na.getNetworkGroupSamenessConfidence(na), EPSILON);
+ }
+}
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_incorrect_iface b/tests/net/res/raw/xt_qtaguid_vpn_incorrect_iface
new file mode 100644
index 0000000..fc92715
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_incorrect_iface
@@ -0,0 +1,3 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test1 0x0 1004 0 1100 100 1100 100 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_one_underlying b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying
new file mode 100644
index 0000000..1ef1889
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying
@@ -0,0 +1,5 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0
+4 test0 0x0 1004 0 3300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+5 test0 0x0 1004 1 0 0 1650 150 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_compression b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_compression
new file mode 100644
index 0000000..6d6bf55
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_compression
@@ -0,0 +1,4 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 3000 300 3000 300 0 0 0 0 0 0 0 0 0 0 0 0
+4 test0 0x0 1004 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_own_traffic b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_own_traffic
new file mode 100644
index 0000000..2c2e5d2
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_one_underlying_own_traffic
@@ -0,0 +1,6 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0
+4 test_nss_tun0 0x0 1004 0 5000 500 6000 600 0 0 0 0 0 0 0 0 0 0 0 0
+5 test0 0x0 1004 0 8800 800 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+6 test0 0x0 1004 1 0 0 8250 750 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_rewrite_through_self b/tests/net/res/raw/xt_qtaguid_vpn_rewrite_through_self
new file mode 100644
index 0000000..afcdd71
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_rewrite_through_self
@@ -0,0 +1,6 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0
+4 test_nss_tun0 0x0 1004 0 0 0 1600 160 0 0 0 0 0 0 0 0 0 0 0 0
+5 test0 0x0 1004 1 0 0 1760 176 0 0 0 0 0 0 0 0 0 0 0 0
+6 test0 0x0 1004 0 3300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_duplication b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_duplication
new file mode 100644
index 0000000..d7c7eb9
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_duplication
@@ -0,0 +1,5 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+4 test0 0x0 1004 0 2200 200 2200 200 0 0 0 0 0 0 0 0 0 0 0 0
+5 test1 0x0 1004 0 2200 200 2200 200 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split
new file mode 100644
index 0000000..38a3dce
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split
@@ -0,0 +1,4 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 500 50 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test0 0x0 1004 0 330 30 660 60 0 0 0 0 0 0 0 0 0 0 0 0
+4 test1 0x0 1004 0 220 20 440 40 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split_compression b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split_compression
new file mode 100644
index 0000000..d35244b
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_two_underlying_split_compression
@@ -0,0 +1,4 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 1000 100 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test0 0x0 1004 0 600 60 600 60 0 0 0 0 0 0 0 0 0 0 0 0
+4 test1 0x0 1004 0 200 20 200 20 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_vpn_with_clat b/tests/net/res/raw/xt_qtaguid_vpn_with_clat
new file mode 100644
index 0000000..0d893d5
--- /dev/null
+++ b/tests/net/res/raw/xt_qtaguid_vpn_with_clat
@@ -0,0 +1,8 @@
+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
+2 test_nss_tun0 0x0 1001 0 2000 200 1000 100 0 0 0 0 0 0 0 0 0 0 0 0
+3 test_nss_tun0 0x0 1002 0 1000 100 500 50 0 0 0 0 0 0 0 0 0 0 0 0
+4 v4-test0 0x0 1004 0 3300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+5 v4-test0 0x0 1004 1 0 0 1650 150 0 0 0 0 0 0 0 0 0 0 0 0
+6 test0 0x0 0 0 9300 300 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+7 test0 0x0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+8 test0 0x0 1029 0 0 0 4650 150 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat b/tests/net/res/raw/xt_qtaguid_with_clat
index 77e5c7b..6cd7499 100644
--- a/tests/net/res/raw/xt_qtaguid_with_clat
+++ b/tests/net/res/raw/xt_qtaguid_with_clat
@@ -7,7 +7,7 @@
7 v4-wlan0 0x0 10060 1 1448660 1041 31192 753 1448660 1041 0 0 0 0 31192 753 0 0 0 0
8 v4-wlan0 0x0 10102 0 9702 16 2870 23 9702 16 0 0 0 0 2870 23 0 0 0 0
9 v4-wlan0 0x0 10102 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-10 wlan0 0x0 0 0 11058671 7892 312046 5113 11043898 7811 13117 61 1656 20 306544 5046 3230 38 2272 29
+10 wlan0 0x0 0 0 11058671 7892 0 0 11043898 7811 13117 61 1656 20 0 0 0 0 0 0
11 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
12 wlan0 0x0 1000 0 6126 13 2013 16 5934 11 192 2 0 0 1821 14 192 2 0 0
13 wlan0 0x0 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
@@ -41,3 +41,5 @@
41 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
42 lo 0x0 0 0 1288 16 1288 16 0 0 532 8 756 8 0 0 532 8 756 8
43 lo 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+44 wlan0 0x0 1029 0 0 0 312046 5113 0 0 0 0 0 0 306544 5046 3230 38 2272 29
+45 wlan0 0x0 1029 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
\ No newline at end of file
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after
index c78f84f..9f86153 100644
--- a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_after
@@ -9,7 +9,7 @@
9 v4-wlan0 0x0 10057 1 728 7 392 7 0 0 728 7 0 0 0 0 392 7 0 0
10 v4-wlan0 0x0 10106 0 2232 18 2232 18 0 0 2232 18 0 0 0 0 2232 18 0 0
11 v4-wlan0 0x0 10106 1 432952718 314238 5442288 121260 432950238 314218 2480 20 0 0 5433900 121029 8388 231 0 0
-12 wlan0 0x0 0 0 440746376 329772 8524052 130894 439660007 315369 232001 1276 854368 13127 7871216 121284 108568 1325 544268 8285
+12 wlan0 0x0 0 0 440746376 329772 0 0 439660007 315369 232001 1276 854368 13127 0 0 0 0 0 0
13 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
14 wlan0 0x0 1000 0 77113 272 56151 575 77113 272 0 0 0 0 19191 190 36960 385 0 0
15 wlan0 0x0 1000 1 20227 80 8356 72 18539 74 1688 6 0 0 7562 66 794 6 0 0
@@ -185,3 +185,5 @@
185 wlan0 0xffffff0900000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
186 dummy0 0x0 0 0 0 0 168 3 0 0 0 0 0 0 0 0 0 0 168 3
187 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+188 wlan0 0x0 1029 0 0 0 8524052 130894 0 0 0 0 0 0 7871216 121284 108568 1325 544268 8285
+189 wlan0 0x0 1029 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before
index d035387..ce4bcc3 100644
--- a/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_100mb_download_before
@@ -9,7 +9,7 @@
9 v4-wlan0 0x0 10057 1 728 7 392 7 0 0 728 7 0 0 0 0 392 7 0 0
10 v4-wlan0 0x0 10106 0 1488 12 1488 12 0 0 1488 12 0 0 0 0 1488 12 0 0
11 v4-wlan0 0x0 10106 1 323981189 235142 3509032 84542 323979453 235128 1736 14 0 0 3502676 84363 6356 179 0 0
-12 wlan0 0x0 0 0 330187296 250652 5855801 94173 329106990 236273 226202 1255 854104 13124 5208040 84634 103637 1256 544124 8283
+12 wlan0 0x0 0 0 330187296 250652 0 0 329106990 236273 226202 1255 854104 13124 0 0 0 0 0 0
13 wlan0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
14 wlan0 0x0 1000 0 77113 272 56151 575 77113 272 0 0 0 0 19191 190 36960 385 0 0
15 wlan0 0x0 1000 1 20227 80 8356 72 18539 74 1688 6 0 0 7562 66 794 6 0 0
@@ -183,3 +183,5 @@
183 wlan0 0xffffff0900000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
184 dummy0 0x0 0 0 0 0 168 3 0 0 0 0 0 0 0 0 0 0 168 3
185 dummy0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+186 wlan0 0x0 1029 0 0 0 5855801 94173 0 0 0 0 0 0 5208040 84634 103637 1256 544124 8283
+187 wlan0 0x0 1029 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/tests/net/res/raw/xt_qtaguid_with_clat_simple b/tests/net/res/raw/xt_qtaguid_with_clat_simple
index 7f0e56f..b37fae6 100644
--- a/tests/net/res/raw/xt_qtaguid_with_clat_simple
+++ b/tests/net/res/raw/xt_qtaguid_with_clat_simple
@@ -1,5 +1,5 @@
idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets
-2 v4-wlan0 0x0 10060 0 42600 213 4100 41 42600 213 4100 41 0 0 0 0 0 0 0 0
+2 v4-wlan0 0x0 10060 0 42600 213 4100 41 42600 213 0 0 0 0 4100 41 0 0 0 0
3 v4-wlan0 0x0 10060 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
-4 wlan0 0x0 0 0 46860 213 4920 41 46860 213 4920 41 0 0 0 0 0 0 0 0
-5 wlan0 0x0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 wlan0 0x0 0 0 46860 213 0 0 46860 213 0 0 0 0 0 0 0 0 0 0
+5 wlan0 0x0 1029 0 0 0 4920 41 0 0 0 0 0 0 4920 41 0 0 0 0
diff --git a/tests/net/smoketest/Android.bp b/tests/net/smoketest/Android.bp
new file mode 100644
index 0000000..84ae2b5
--- /dev/null
+++ b/tests/net/smoketest/Android.bp
@@ -0,0 +1,22 @@
+// This test exists only because the jni_libs list for these tests is difficult to
+// maintain: the test itself only depends on libnetworkstatsfactorytestjni, but the test
+// fails to load that library unless *all* the dependencies of that library are explicitly
+// listed in jni_libs. This means that whenever any of the dependencies changes the test
+// starts failing and breaking presubmits in frameworks/base. We cannot easily put
+// FrameworksNetTests into global presubmit because they are at times flaky, but this
+// test is effectively empty beyond validating that the libraries load correctly, and
+// thus should be stable enough to put in global presubmit.
+//
+// TODO: remove this hack when there is a better solution for jni_libs that includes
+// dependent libraries.
+android_test {
+ name: "FrameworksNetSmokeTests",
+ defaults: ["FrameworksNetTests-jni-defaults"],
+ srcs: ["java/SmokeTest.java"],
+ test_suites: ["device-tests"],
+ static_libs: [
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "services.core",
+ ],
+}
\ No newline at end of file