Merge changes from topic "libnetworkstats"
* changes:
Rename libnetdbpf to libnetworkstats
Copy libnetdbpf from system/net to tethering module
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
index 44c57ab..06e9617 100644
--- a/Tethering/apex/canned_fs_config
+++ b/Tethering/apex/canned_fs_config
@@ -1,2 +1,2 @@
/bin/for-system 0 1000 0550
-/bin/for-system/clatd 1029 1029 06555
+/bin/for-system/clatd 1029 1029 06755
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index a7155fd..fa5af49 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -42,6 +42,7 @@
// TODO: remove it when NetworkStatsService is moved into the mainline module and no more
// calls to JNI in libservices.core.
"//frameworks/base/services/core/jni",
+ "//packages/modules/Connectivity/service/native/libs/libclat",
"//packages/modules/Connectivity/Tethering",
"//packages/modules/Connectivity/service/native",
"//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 827da6d..a373b71 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -294,7 +294,7 @@
ctor public NetworkCapabilities(android.net.NetworkCapabilities);
method public int describeContents();
method @NonNull public int[] getCapabilities();
- method @NonNull public int[] getEnterpriseCapabilitySubLevels();
+ method @NonNull public int[] getEnterpriseIds();
method public int getLinkDownstreamBandwidthKbps();
method public int getLinkUpstreamBandwidthKbps();
method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
@@ -302,6 +302,7 @@
method public int getSignalStrength();
method @Nullable public android.net.TransportInfo getTransportInfo();
method public boolean hasCapability(int);
+ method public boolean hasEnterpriseId(int);
method public boolean hasTransport(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR;
@@ -325,6 +326,8 @@
field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12
field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15
field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
+ field public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35; // 0x23
+ field public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34; // 0x22
field public static final int NET_CAPABILITY_RCS = 8; // 0x8
field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
@@ -332,6 +335,11 @@
field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
field public static final int NET_CAPABILITY_XCAP = 9; // 0x9
+ field public static final int NET_ENTERPRISE_ID_1 = 1; // 0x1
+ field public static final int NET_ENTERPRISE_ID_2 = 2; // 0x2
+ field public static final int NET_ENTERPRISE_ID_3 = 3; // 0x3
+ field public static final int NET_ENTERPRISE_ID_4 = 4; // 0x4
+ field public static final int NET_ENTERPRISE_ID_5 = 5; // 0x5
field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000
field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
field public static final int TRANSPORT_CELLULAR = 0; // 0x0
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index ec169b6..db08c48 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -133,11 +133,6 @@
public final class NetworkCapabilities implements android.os.Parcelable {
method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
method public boolean hasForbiddenCapability(int);
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1 = 1; // 0x1
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2 = 2; // 0x2
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3 = 3; // 0x3
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4 = 4; // 0x4
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5 = 5; // 0x5
field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L
field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L
@@ -166,6 +161,7 @@
method @NonNull public java.util.List<java.lang.Integer> getExcludedUids();
method @NonNull public java.util.List<java.lang.Integer> getIncludedUids();
method public int getPreference();
+ method public int getPreferenceEnterpriseId();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.ProfileNetworkPreference> CREATOR;
}
@@ -176,6 +172,7 @@
method @NonNull public android.net.ProfileNetworkPreference.Builder setExcludedUids(@Nullable java.util.List<java.lang.Integer>);
method @NonNull public android.net.ProfileNetworkPreference.Builder setIncludedUids(@Nullable java.util.List<java.lang.Integer>);
method @NonNull public android.net.ProfileNetworkPreference.Builder setPreference(int);
+ method @NonNull public android.net.ProfileNetworkPreference.Builder setPreferenceEnterpriseId(int);
}
public final class TestNetworkInterface implements android.os.Parcelable {
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 8d084e6..b4b3588 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -294,11 +294,11 @@
ctor public NetworkCapabilities.Builder();
ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
- method @NonNull public android.net.NetworkCapabilities.Builder addEnterpriseCapabilitySubLevel(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder addEnterpriseId(int);
method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
method @NonNull public android.net.NetworkCapabilities build();
method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
- method @NonNull public android.net.NetworkCapabilities.Builder removeEnterpriseCapabilitySubLevel(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder removeEnterpriseId(int);
method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 009890c..7593482 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -16,6 +16,7 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
import static android.net.NetworkRequest.Type.LISTEN;
import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
@@ -5537,6 +5538,9 @@
ProfileNetworkPreference.Builder preferenceBuilder =
new ProfileNetworkPreference.Builder();
preferenceBuilder.setPreference(preference);
+ if (preference != PROFILE_NETWORK_PREFERENCE_DEFAULT) {
+ preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ }
setProfileNetworkPreferences(profile,
List.of(preferenceBuilder.build()), executor, listener);
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 84f7cbb..4ae3a06 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -16,8 +16,6 @@
package android.net;
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import android.annotation.IntDef;
@@ -149,67 +147,83 @@
private String mRequestorPackageName;
/**
- * enterprise capability sub level 1
- * @hide
+ * Enterprise capability identifier 1.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1 = 1;
+ public static final int NET_ENTERPRISE_ID_1 = 1;
/**
- * enterprise capability sub level 2
- * @hide
+ * Enterprise capability identifier 2.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2 = 2;
+ public static final int NET_ENTERPRISE_ID_2 = 2;
/**
- * enterprise capability sub level 3
- * @hide
+ * Enterprise capability identifier 3.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3 = 3;
+ public static final int NET_ENTERPRISE_ID_3 = 3;
/**
- * enterprise capability sub level 4
- * @hide
+ * Enterprise capability identifier 4.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4 = 4;
+ public static final int NET_ENTERPRISE_ID_4 = 4;
/**
- * enterprise capability sub level 5
- * @hide
+ * Enterprise capability identifier 5.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5 = 5;
+ public static final int NET_ENTERPRISE_ID_5 = 5;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1,
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2,
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3,
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4,
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5,
+ NET_ENTERPRISE_ID_1,
+ NET_ENTERPRISE_ID_2,
+ NET_ENTERPRISE_ID_3,
+ NET_ENTERPRISE_ID_4,
+ NET_ENTERPRISE_ID_5,
})
- public @interface EnterpriseCapabilitySubLevel {
+ public @interface EnterpriseId {
}
/**
- * Bitfield representing the network's enterprise capability sublevel. If any are specified
+ * Bitfield representing the network's enterprise capability identifier. If any are specified
* they will be satisfied by any Network that matches all of them.
- * {@see addEnterpriseCapabilitySubLevel} for details on how masks are added
+ * {@see addEnterpriseId} for details on how masks are added
*/
- private int mEnterpriseCapabilitySubLevel;
+ private int mEnterpriseId;
/**
- * @return all the enteprise capabilities sub level set on this {@code NetworkCapability}
- * instance.
+ * Get enteprise identifiers set.
+ *
+ * Get all the enterprise capabilities identifier set on this {@code NetworkCapability}
+ * If NET_CAPABILITY_ENTERPRISE is set and no enterprise ID is set, it is
+ * considered to have NET_CAPABILITY_ENTERPRISE by default.
+ * @return all the enterprise capabilities identifier set.
*
*/
- public @NonNull @EnterpriseCapabilitySubLevel int[] getEnterpriseCapabilitySubLevels() {
- return NetworkCapabilitiesUtils.unpackBits(mEnterpriseCapabilitySubLevel);
+ public @NonNull @EnterpriseId int[] getEnterpriseIds() {
+ if (hasCapability(NET_CAPABILITY_ENTERPRISE) && mEnterpriseId == 0) {
+ return new int[]{NET_ENTERPRISE_ID_1};
+ }
+ return NetworkCapabilitiesUtils.unpackBits(mEnterpriseId);
+ }
+
+ /**
+ * Tests for the presence of an enterprise capability identifier on this instance.
+ *
+ * If NET_CAPABILITY_ENTERPRISE is set and no enterprise ID is set, it is
+ * considered to have NET_CAPABILITY_ENTERPRISE by default.
+ * @param enterpriseId the enterprise capability identifier to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ if (enterpriseId == NET_ENTERPRISE_ID_1) {
+ if (hasCapability(NET_CAPABILITY_ENTERPRISE) && mEnterpriseId == 0) {
+ return true;
+ }
+ }
+ return isValidEnterpriseId(enterpriseId)
+ && ((mEnterpriseId & (1L << enterpriseId)) != 0);
}
public NetworkCapabilities() {
@@ -258,7 +272,7 @@
mRequestorPackageName = null;
mSubIds = new ArraySet<>();
mUnderlyingNetworks = null;
- mEnterpriseCapabilitySubLevel = 0;
+ mEnterpriseId = 0;
}
/**
@@ -291,7 +305,7 @@
// mUnderlyingNetworks is an unmodifiable list if non-null, so a defensive copy is not
// necessary.
mUnderlyingNetworks = nc.mUnderlyingNetworks;
- mEnterpriseCapabilitySubLevel = nc.mEnterpriseCapabilitySubLevel;
+ mEnterpriseId = nc.mEnterpriseId;
}
/**
@@ -343,6 +357,8 @@
NET_CAPABILITY_BIP,
NET_CAPABILITY_HEAD_UNIT,
NET_CAPABILITY_MMTEL,
+ NET_CAPABILITY_PRIORITIZE_LATENCY,
+ NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
})
public @interface NetCapability { }
@@ -586,8 +602,18 @@
*/
public static final int NET_CAPABILITY_MMTEL = 33;
+ /**
+ * Indicates that this network should be able to prioritize latency for the internet.
+ */
+ public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34;
+
+ /**
+ * Indicates that this network should be able to prioritize bandwidth for the internet.
+ */
+ public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_MMTEL;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -784,34 +810,34 @@
}
/**
- * Adds the given enterprise capability sub level to this {@code NetworkCapability} instance.
- * Note that when searching for a network to satisfy a request, all capabilities sub level
+ * Adds the given enterprise capability identifier to this {@code NetworkCapability} instance.
+ * Note that when searching for a network to satisfy a request, all capabilities identifier
* requested must be satisfied.
*
- * @param enterpriseCapabilitySubLevel the enterprise capability sub level to be added.
+ * @param enterpriseId the enterprise capability identifier to be added.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- private @NonNull NetworkCapabilities addEnterpriseCapabilitySubLevel(
- @EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- checkValidEnterpriseCapabilitySublevel(enterpriseCapabilitySubLevel);
- mEnterpriseCapabilitySubLevel |= 1 << enterpriseCapabilitySubLevel;
+ public @NonNull NetworkCapabilities addEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ checkValidEnterpriseId(enterpriseId);
+ mEnterpriseId |= 1 << enterpriseId;
return this;
}
/**
- * Removes (if found) the given enterprise capability sublevel from this
- * {@code NetworkCapability} instance that were added via addEnterpriseCapabilitySubLevel(int)
+ * Removes (if found) the given enterprise capability identifier from this
+ * {@code NetworkCapability} instance that were added via addEnterpriseId(int)
*
- * @param enterpriseCapabilitySubLevel the enterprise capability sublevel to be removed.
+ * @param enterpriseId the enterprise capability identifier to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- private @NonNull NetworkCapabilities removeEnterpriseCapabilitySubLevel(
- @EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- checkValidEnterpriseCapabilitySublevel(enterpriseCapabilitySubLevel);
- final int mask = ~(1 << enterpriseCapabilitySubLevel);
- mEnterpriseCapabilitySubLevel &= mask;
+ private @NonNull NetworkCapabilities removeEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ checkValidEnterpriseId(enterpriseId);
+ final int mask = ~(1 << enterpriseId);
+ mEnterpriseId &= mask;
return this;
}
@@ -915,16 +941,19 @@
return null;
}
- private boolean equalsEnterpriseCapabilitiesSubLevel(@NonNull NetworkCapabilities nc) {
- return nc.mEnterpriseCapabilitySubLevel == this.mEnterpriseCapabilitySubLevel;
+ private boolean equalsEnterpriseCapabilitiesId(@NonNull NetworkCapabilities nc) {
+ return nc.mEnterpriseId == this.mEnterpriseId;
}
- private boolean satisfiedByEnterpriseCapabilitiesSubLevel(@NonNull NetworkCapabilities nc) {
- final int requestedEnterpriseCapabilitiesSubLevel = mEnterpriseCapabilitySubLevel;
- final int providedEnterpriseCapabailitiesSubLevel = nc.mEnterpriseCapabilitySubLevel;
+ private boolean satisfiedByEnterpriseCapabilitiesId(@NonNull NetworkCapabilities nc) {
+ final int requestedEnterpriseCapabilitiesId = mEnterpriseId;
+ final int providedEnterpriseCapabailitiesId = nc.mEnterpriseId;
- if ((providedEnterpriseCapabailitiesSubLevel & requestedEnterpriseCapabilitiesSubLevel)
- == requestedEnterpriseCapabilitiesSubLevel) {
+ if ((providedEnterpriseCapabailitiesId & requestedEnterpriseCapabilitiesId)
+ == requestedEnterpriseCapabilitiesId) {
+ return true;
+ } else if (providedEnterpriseCapabailitiesId == 0
+ && (requestedEnterpriseCapabilitiesId == (1L << NET_ENTERPRISE_ID_1))) {
return true;
} else {
return false;
@@ -1836,7 +1865,7 @@
&& satisfiedByTransportTypes(nc)
&& (onlyImmutable || satisfiedByLinkBandwidths(nc))
&& satisfiedBySpecifier(nc)
- && satisfiedByEnterpriseCapabilitiesSubLevel(nc)
+ && satisfiedByEnterpriseCapabilitiesId(nc)
&& (onlyImmutable || satisfiedBySignalStrength(nc))
&& (onlyImmutable || satisfiedByUids(nc))
&& (onlyImmutable || satisfiedBySSID(nc))
@@ -1940,7 +1969,7 @@
&& equalsAdministratorUids(that)
&& equalsSubscriptionIds(that)
&& equalsUnderlyingNetworks(that)
- && equalsEnterpriseCapabilitiesSubLevel(that);
+ && equalsEnterpriseCapabilitiesId(that);
}
@Override
@@ -1965,7 +1994,7 @@
+ Arrays.hashCode(mAdministratorUids) * 61
+ Objects.hashCode(mSubIds) * 67
+ Objects.hashCode(mUnderlyingNetworks) * 71
- + mEnterpriseCapabilitySubLevel * 73;
+ + mEnterpriseId * 73;
}
@Override
@@ -2001,7 +2030,7 @@
dest.writeString(mRequestorPackageName);
dest.writeIntArray(CollectionUtils.toIntArray(mSubIds));
dest.writeTypedList(mUnderlyingNetworks);
- dest.writeInt(mEnterpriseCapabilitySubLevel);
+ dest.writeInt(mEnterpriseId);
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -2031,7 +2060,7 @@
netCap.mSubIds.add(subIdInts[i]);
}
netCap.setUnderlyingNetworks(in.createTypedArrayList(Network.CREATOR));
- netCap.mEnterpriseCapabilitySubLevel = in.readInt();
+ netCap.mEnterpriseId = in.readInt();
return netCap;
}
@Override
@@ -2123,10 +2152,10 @@
sb.append(" SubscriptionIds: ").append(mSubIds);
}
- if (0 != mEnterpriseCapabilitySubLevel) {
- sb.append(" EnterpriseCapabilitySublevel: ");
- appendStringRepresentationOfBitMaskToStringBuilder(sb, mEnterpriseCapabilitySubLevel,
- NetworkCapabilities::enterpriseCapabilitySublevelNameOf, "&");
+ if (0 != mEnterpriseId) {
+ sb.append(" EnterpriseId: ");
+ appendStringRepresentationOfBitMaskToStringBuilder(sb, mEnterpriseId,
+ NetworkCapabilities::enterpriseIdNameOf, "&");
}
sb.append(" UnderlyingNetworks: ");
@@ -2224,11 +2253,13 @@
case NET_CAPABILITY_BIP: return "BIP";
case NET_CAPABILITY_HEAD_UNIT: return "HEAD_UNIT";
case NET_CAPABILITY_MMTEL: return "MMTEL";
+ case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY";
+ case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH";
default: return Integer.toString(capability);
}
}
- private static @NonNull String enterpriseCapabilitySublevelNameOf(
+ private static @NonNull String enterpriseIdNameOf(
@NetCapability int capability) {
return Integer.toString(capability);
}
@@ -2273,17 +2304,17 @@
}
}
- private static boolean isValidEnterpriseCapabilitySubLevel(
- @NetworkCapabilities.EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- return enterpriseCapabilitySubLevel >= NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1
- && enterpriseCapabilitySubLevel <= NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5;
+ private static boolean isValidEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int enterpriseId) {
+ return enterpriseId >= NET_ENTERPRISE_ID_1
+ && enterpriseId <= NET_ENTERPRISE_ID_5;
}
- private static void checkValidEnterpriseCapabilitySublevel(
- @NetworkCapabilities.EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- if (!isValidEnterpriseCapabilitySubLevel(enterpriseCapabilitySubLevel)) {
- throw new IllegalArgumentException("enterprise capability sublevel "
- + enterpriseCapabilitySubLevel + " is out of range");
+ private static void checkValidEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int enterpriseId) {
+ if (!isValidEnterpriseId(enterpriseId)) {
+ throw new IllegalArgumentException("enterprise capability identifier "
+ + enterpriseId + " is out of range");
}
}
@@ -2613,33 +2644,33 @@
}
/**
- * Adds the given enterprise capability sub level.
- * Note that when searching for a network to satisfy a request, all capabilities sub level
- * requested must be satisfied. Enterprise capability sub-level is applicable only
+ * Adds the given enterprise capability identifier.
+ * Note that when searching for a network to satisfy a request, all capabilities identifier
+ * requested must be satisfied. Enterprise capability identifier is applicable only
* for NET_CAPABILITY_ENTERPRISE capability
*
- * @param enterpriseCapabilitySubLevel enterprise capability sub-level.
+ * @param enterpriseId enterprise capability identifier.
*
* @return this builder
*/
@NonNull
- public Builder addEnterpriseCapabilitySubLevel(
- @EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- mCaps.addEnterpriseCapabilitySubLevel(enterpriseCapabilitySubLevel);
+ public Builder addEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ mCaps.addEnterpriseId(enterpriseId);
return this;
}
/**
- * Removes the given enterprise capability sub level. Enterprise capability sub-level is
+ * Removes the given enterprise capability identifier. Enterprise capability identifier is
* applicable only for NET_CAPABILITY_ENTERPRISE capability
*
- * @param enterpriseCapabilitySubLevel the enterprise capability subLevel
+ * @param enterpriseId the enterprise capability identifier
* @return this builder
*/
@NonNull
- public Builder removeEnterpriseCapabilitySubLevel(
- @EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- mCaps.removeEnterpriseCapabilitySubLevel(enterpriseCapabilitySubLevel);
+ public Builder removeEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ mCaps.removeEnterpriseId(enterpriseId);
return this;
}
@@ -2902,10 +2933,10 @@
}
}
- if ((mCaps.getEnterpriseCapabilitySubLevels().length != 0)
+ if ((mCaps.getEnterpriseIds().length != 0)
&& !mCaps.hasCapability(NET_CAPABILITY_ENTERPRISE)) {
- throw new IllegalStateException("Enterprise capability sublevel is applicable only"
- + " with ENTERPRISE capability.");
+ throw new IllegalStateException("Enterprise capability identifier is applicable"
+ + " only with ENTERPRISE capability.");
}
return new NetworkCapabilities(mCaps);
}
diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java
index 0571b36..f43acce 100644
--- a/framework/src/android/net/ProfileNetworkPreference.java
+++ b/framework/src/android/net/ProfileNetworkPreference.java
@@ -18,6 +18,8 @@
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -38,12 +40,15 @@
@SystemApi(client = MODULE_LIBRARIES)
public final class ProfileNetworkPreference implements Parcelable {
private final @ProfileNetworkPreferencePolicy int mPreference;
+ private final @NetworkCapabilities.EnterpriseId int mPreferenceEnterpriseId;
private final List<Integer> mIncludedUids;
private final List<Integer> mExcludedUids;
private ProfileNetworkPreference(int preference, List<Integer> includedUids,
- List<Integer> excludedUids) {
+ List<Integer> excludedUids,
+ @NetworkCapabilities.EnterpriseId int preferenceEnterpriseId) {
mPreference = preference;
+ mPreferenceEnterpriseId = preferenceEnterpriseId;
if (includedUids != null) {
mIncludedUids = new ArrayList<>(includedUids);
} else {
@@ -61,6 +66,7 @@
mPreference = in.readInt();
mIncludedUids = in.readArrayList(Integer.class.getClassLoader());
mExcludedUids = in.readArrayList(Integer.class.getClassLoader());
+ mPreferenceEnterpriseId = in.readInt();
}
public int getPreference() {
@@ -95,12 +101,30 @@
return new ArrayList<>(mExcludedUids);
}
+ /**
+ * Get preference enterprise identifier.
+ *
+ * Preference enterprise identifier will be used to create different network preferences
+ * within enterprise preference category.
+ * Valid values starts from PROFILE_NETWORK_PREFERENCE_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * Preference identifier is not applicable if preference is set as
+ * PROFILE_NETWORK_PREFERENCE_DEFAULT. Default value is
+ * NetworkCapabilities.NET_ENTERPRISE_ID_1.
+ * @return Preference enterprise identifier.
+ *
+ */
+ public @NetworkCapabilities.EnterpriseId int getPreferenceEnterpriseId() {
+ return mPreferenceEnterpriseId;
+ }
+
@Override
public String toString() {
return "ProfileNetworkPreference{"
+ "mPreference=" + getPreference()
+ "mIncludedUids=" + mIncludedUids.toString()
+ "mExcludedUids=" + mExcludedUids.toString()
+ + "mPreferenceEnterpriseId=" + mPreferenceEnterpriseId
+ '}';
}
@@ -111,12 +135,14 @@
final ProfileNetworkPreference that = (ProfileNetworkPreference) o;
return mPreference == that.mPreference
&& (Objects.equals(mIncludedUids, that.mIncludedUids))
- && (Objects.equals(mExcludedUids, that.mExcludedUids));
+ && (Objects.equals(mExcludedUids, that.mExcludedUids))
+ && mPreferenceEnterpriseId == that.mPreferenceEnterpriseId;
}
@Override
public int hashCode() {
return mPreference
+ + mPreferenceEnterpriseId * 2
+ (Objects.hashCode(mIncludedUids) * 11)
+ (Objects.hashCode(mExcludedUids) * 13);
}
@@ -130,6 +156,7 @@
PROFILE_NETWORK_PREFERENCE_DEFAULT;
private @NonNull List<Integer> mIncludedUids = new ArrayList<>();
private @NonNull List<Integer> mExcludedUids = new ArrayList<>();
+ private int mPreferenceEnterpriseId;
/**
* Constructs an empty Builder with PROFILE_NETWORK_PREFERENCE_DEFAULT profile preference
@@ -191,7 +218,24 @@
return this;
}
- /**
+ /**
+ * Check if given preference enterprise identifier is valid
+ *
+ * Valid values starts from PROFILE_NETWORK_PREFERENCE_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * @return True if valid else false
+ * @hide
+ */
+ private boolean isEnterpriseIdentifierValid(
+ @NetworkCapabilities.EnterpriseId int identifier) {
+ if ((identifier >= NET_ENTERPRISE_ID_1)
+ && (identifier <= NET_ENTERPRISE_ID_5)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Returns an instance of {@link ProfileNetworkPreference} created from the
* fields set on this builder.
*/
@@ -201,7 +245,35 @@
throw new IllegalArgumentException("Both includedUids and excludedUids "
+ "cannot be nonempty");
}
- return new ProfileNetworkPreference(mPreference, mIncludedUids, mExcludedUids);
+
+ if (((mPreference != PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ && (!isEnterpriseIdentifierValid(mPreferenceEnterpriseId)))
+ || ((mPreference == PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ && (mPreferenceEnterpriseId != 0))) {
+ throw new IllegalStateException("Invalid preference enterprise identifier");
+ }
+ return new ProfileNetworkPreference(mPreference, mIncludedUids,
+ mExcludedUids, mPreferenceEnterpriseId);
+ }
+
+ /**
+ * Set the preference enterprise identifier.
+ *
+ * Preference enterprise identifier will be used to create different network preferences
+ * within enterprise preference category.
+ * Valid values starts from NetworkCapabilities.NET_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * Preference identifier is not applicable if preference is set as
+ * PROFILE_NETWORK_PREFERENCE_DEFAULT. Default value is
+ * NetworkCapabilities.NET_ENTERPRISE_ID_1.
+ * @param preferenceId preference sub level
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public Builder setPreferenceEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int preferenceId) {
+ mPreferenceEnterpriseId = preferenceId;
+ return this;
}
}
@@ -210,6 +282,7 @@
dest.writeInt(mPreference);
dest.writeList(mIncludedUids);
dest.writeList(mExcludedUids);
+ dest.writeInt(mPreferenceEnterpriseId);
}
@Override
diff --git a/service/Android.bp b/service/Android.bp
index e376ff7..d1a9004 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -65,6 +65,7 @@
"libbase_headers",
],
static_libs: [
+ "libbase",
"libclat",
"libip_checksum",
"libnetjniutils",
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index e5d1a88..cc0cbcc 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -90,5 +90,9 @@
# From services-connectivity-shared-srcs
rule android.net.util.NetworkConstants* com.android.connectivity.@0
+# From modules-utils-statemachine
+rule com.android.internal.util.IState* com.android.connectivity.@0
+rule com.android.internal.util.State* com.android.connectivity.@0
+
# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index a9d7946..9603139 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -19,17 +19,22 @@
#include <fcntl.h>
#include <linux/if_tun.h>
#include <linux/ioctl.h>
+#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <net/if.h>
+#include <string>
#include <netjniutils/netjniutils.h>
+#include "libclat/bpfhelper.h"
#include "libclat/clatutils.h"
#include "nativehelper/scoped_utf_chars.h"
// Sync from system/netd/include/netid_client.h
#define MARK_UNSET 0u
+#define DEVICEPREFIX "v4-"
+
namespace android {
static void throwIOException(JNIEnv* env, const char* msg, int error) {
jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
@@ -237,6 +242,86 @@
}
}
+int initTracker(const std::string& iface, const std::string& pfx96, const std::string& v4,
+ const std::string& v6, net::clat::ClatdTracker* output) {
+ strlcpy(output->iface, iface.c_str(), sizeof(output->iface));
+ output->ifIndex = if_nametoindex(iface.c_str());
+ if (output->ifIndex == 0) {
+ ALOGE("interface %s not found", output->iface);
+ return -1;
+ }
+
+ unsigned len = snprintf(output->v4iface, sizeof(output->v4iface),
+ "%s%s", DEVICEPREFIX, iface.c_str());
+ if (len >= sizeof(output->v4iface)) {
+ ALOGE("interface name too long '%s'", output->v4iface);
+ return -1;
+ }
+
+ output->v4ifIndex = if_nametoindex(output->v4iface);
+ if (output->v4ifIndex == 0) {
+ ALOGE("v4-interface %s not found", output->v4iface);
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET6, pfx96.c_str(), &output->pfx96)) {
+ ALOGE("invalid IPv6 address specified for plat prefix: %s", pfx96.c_str());
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET, v4.c_str(), &output->v4)) {
+ ALOGE("Invalid IPv4 address %s", v4.c_str());
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET6, v6.c_str(), &output->v6)) {
+ ALOGE("Invalid source address %s", v6.c_str());
+ return -1;
+ }
+
+ return 0;
+}
+
+// TODO: fork clatd and rename to .._startClatd.
+static jint com_android_server_connectivity_ClatCoordinator_maybeStartBpf(
+ JNIEnv* env, jobject clazz, jobject tunJavaFd, jobject readSockJavaFd,
+ jobject writeSockJavaFd, jstring iface, jstring pfx96, jstring v4, jstring v6) {
+ ScopedUtfChars ifaceStr(env, iface);
+ ScopedUtfChars pfx96Str(env, pfx96);
+ ScopedUtfChars v4Str(env, v4);
+ ScopedUtfChars v6Str(env, v6);
+
+ // Start BPF if any
+ if (!net::clat::initMaps()) {
+ net::clat::ClatdTracker tracker = {};
+ if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
+ &tracker)) {
+ net::clat::maybeStartBpf(tracker);
+ }
+ }
+
+ return 0; // TODO: return forked clatd pid.
+}
+
+// TODO: stop clatd and rename to .._stopClatd.
+static void com_android_server_connectivity_ClatCoordinator_maybeStopBpf(JNIEnv* env, jobject clazz,
+ jstring iface, jstring pfx96,
+ jstring v4, jstring v6,
+ jint pid /* unused */) {
+ ScopedUtfChars ifaceStr(env, iface);
+ ScopedUtfChars pfx96Str(env, pfx96);
+ ScopedUtfChars v4Str(env, v4);
+ ScopedUtfChars v6Str(env, v6);
+
+ if (!net::clat::initMaps()) {
+ net::clat::ClatdTracker tracker = {};
+ if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
+ &tracker)) {
+ net::clat::maybeStopBpf(tracker);
+ }
+ }
+}
+
/*
* JNI registration.
*/
@@ -259,6 +344,13 @@
(void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt},
{"native_configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
(void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket},
+ {"native_maybeStartBpf",
+ "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/lang/"
+ "String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_maybeStartBpf},
+ {"native_maybeStopBpf",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V",
+ (void*)com_android_server_connectivity_ClatCoordinator_maybeStopBpf},
};
int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 8540787..5e208d8 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -19,9 +19,22 @@
cc_library_static {
name: "libclat",
defaults: ["netd_defaults"],
- srcs: ["clatutils.cpp"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "bpf_headers",
+ "bpf_syscall_wrappers",
+ ],
+ srcs: [
+ "TcUtils.cpp", // TODO: move to frameworks/libs/net
+ "bpfhelper.cpp",
+ "clatutils.cpp",
+ ],
stl: "libc++_static",
- static_libs: ["libip_checksum"],
+ static_libs: [
+ "libbase",
+ "libip_checksum",
+ "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
+ ],
shared_libs: ["liblog"],
export_include_dirs: ["include"],
min_sdk_version: "30",
@@ -32,7 +45,13 @@
name: "libclat_test",
defaults: ["netd_defaults"],
test_suites: ["device-tests"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "bpf_headers",
+ "bpf_syscall_wrappers",
+ ],
srcs: [
+ "TcUtilsTest.cpp",
"clatutils_test.cpp",
],
static_libs: [
@@ -40,10 +59,12 @@
"libclat",
"libip_checksum",
"libnetd_test_tun_interface",
+ "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
+ "libtcutils",
],
shared_libs: [
"liblog",
"libnetutils",
],
require_root: true,
-}
\ No newline at end of file
+}
diff --git a/service/native/libs/libclat/TcUtils.cpp b/service/native/libs/libclat/TcUtils.cpp
new file mode 100644
index 0000000..cdfb763
--- /dev/null
+++ b/service/native/libs/libclat/TcUtils.cpp
@@ -0,0 +1,390 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TcUtils"
+
+#include "libclat/TcUtils.h"
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/netlink.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <log/log.h>
+
+#include "android-base/unique_fd.h"
+
+namespace android {
+namespace net {
+
+using std::max;
+
+// Sync from system/netd/server/NetlinkCommands.h
+const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+
+static int doSIOCGIF(const std::string& interface, int opt) {
+ base::unique_fd ufd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+
+ if (ufd < 0) {
+ const int err = errno;
+ ALOGE("socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)");
+ return -err;
+ };
+
+ struct ifreq ifr = {};
+ // We use strncpy() instead of strlcpy() since kernel has to be able
+ // to handle non-zero terminated junk passed in by userspace anyway,
+ // and this way too long interface names (more than IFNAMSIZ-1 = 15
+ // characters plus terminating NULL) will not get truncated to 15
+ // characters and zero-terminated and thus potentially erroneously
+ // match a truncated interface if one were to exist.
+ strncpy(ifr.ifr_name, interface.c_str(), sizeof(ifr.ifr_name));
+
+ if (ioctl(ufd, opt, &ifr, sizeof(ifr))) return -errno;
+
+ if (opt == SIOCGIFHWADDR) return ifr.ifr_hwaddr.sa_family;
+ if (opt == SIOCGIFMTU) return ifr.ifr_mtu;
+ return -EINVAL;
+}
+
+int hardwareAddressType(const std::string& interface) {
+ return doSIOCGIF(interface, SIOCGIFHWADDR);
+}
+
+int deviceMTU(const std::string& interface) {
+ return doSIOCGIF(interface, SIOCGIFMTU);
+}
+
+base::Result<bool> isEthernet(const std::string& interface) {
+ int rv = hardwareAddressType(interface);
+ if (rv < 0) {
+ errno = -rv;
+ return ErrnoErrorf("Get hardware address type of interface {} failed", interface);
+ }
+
+ switch (rv) {
+ case ARPHRD_ETHER:
+ return true;
+ case ARPHRD_NONE:
+ case ARPHRD_RAWIP: // in Linux 4.14+ rmnet support was upstreamed and this is 519
+ case 530: // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
+ return false;
+ default:
+ errno = EAFNOSUPPORT; // Address family not supported
+ return ErrnoErrorf("Unknown hardware address type {} on interface {}", rv, interface);
+ }
+}
+
+// TODO: use //system/netd/server/NetlinkCommands.cpp:openNetlinkSocket(protocol)
+// and //system/netd/server/SockDiag.cpp:checkError(fd)
+static int sendAndProcessNetlinkResponse(const void* req, int len) {
+ base::unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
+ if (fd == -1) {
+ const int err = errno;
+ ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)");
+ return -err;
+ }
+
+ static constexpr int on = 1;
+ int rv = setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on));
+ if (rv) ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
+
+ // this is needed to get sane strace netlink parsing, it allocates the pid
+ rv = bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
+ if (rv) {
+ const int err = errno;
+ ALOGE("bind(fd, {AF_NETLINK, 0, 0})");
+ return -err;
+ }
+
+ // we do not want to receive messages from anyone besides the kernel
+ rv = connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
+ if (rv) {
+ const int err = errno;
+ ALOGE("connect(fd, {AF_NETLINK, 0, 0})");
+ return -err;
+ }
+
+ rv = send(fd, req, len, 0);
+ if (rv == -1) return -errno;
+ if (rv != len) return -EMSGSIZE;
+
+ struct {
+ nlmsghdr h;
+ nlmsgerr e;
+ char buf[256];
+ } resp = {};
+
+ rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
+
+ if (rv == -1) {
+ const int err = errno;
+ ALOGE("recv() failed");
+ return -err;
+ }
+
+ if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
+ ALOGE("recv() returned short packet: %d", rv);
+ return -EMSGSIZE;
+ }
+
+ if (resp.h.nlmsg_len != (unsigned)rv) {
+ ALOGE("recv() returned invalid header length: %d != %d", resp.h.nlmsg_len, rv);
+ return -EBADMSG;
+ }
+
+ if (resp.h.nlmsg_type != NLMSG_ERROR) {
+ ALOGE("recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
+ return -EBADMSG;
+ }
+
+ return resp.e.error; // returns 0 on success
+}
+
+// ADD: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE
+// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE
+// DEL: nlMsgType=RTM_DELQDISC nlMsgFlags=0
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags) {
+ // This is the name of the qdisc we are attaching.
+ // Some hoop jumping to make this compile time constant with known size,
+ // so that the structure declaration is well defined at compile time.
+#define CLSACT "clsact"
+ // sizeof() includes the terminating NULL
+ static constexpr size_t ASCIIZ_LEN_CLSACT = sizeof(CLSACT);
+
+ const struct {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)];
+ } kind;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = nlMsgType,
+ .nlmsg_flags = static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags),
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0),
+ .tcm_parent = TC_H_CLSACT,
+ },
+ .kind =
+ {
+ .attr =
+ {
+ .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT,
+ .nla_type = TCA_KIND,
+ },
+ .str = CLSACT,
+ },
+ };
+#undef CLSACT
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet) {
+ // This is the name of the filter we're attaching (ie. this is the 'bpf'
+ // packet classifier enabled by kernel config option CONFIG_NET_CLS_BPF.
+ //
+ // We go through some hoops in order to make this compile time constants
+ // so that we can define the struct further down the function with the
+ // field for this sized correctly already during the build.
+#define BPF "bpf"
+ // sizeof() includes the terminating NULL
+ static constexpr size_t ASCIIZ_LEN_BPF = sizeof(BPF);
+
+ // This is to replicate program name suffix used by 'tc' Linux cli
+ // when it attaches programs.
+#define FSOBJ_SUFFIX ":[*fsobj]"
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_ingress6_clat_rawip:[*fsobj]
+ // and is the name of the pinned ingress ebpf program for ARPHRD_RAWIP interfaces.
+ // (also compatible with anything that has 0 size L2 header)
+ static constexpr char name_clat_rx_rawip[] = CLAT_INGRESS6_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_ingress6_clat_ether:[*fsobj]
+ // and is the name of the pinned ingress ebpf program for ARPHRD_ETHER interfaces.
+ // (also compatible with anything that has standard ethernet header)
+ static constexpr char name_clat_rx_ether[] = CLAT_INGRESS6_PROG_ETHER_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_egress4_clat_rawip:[*fsobj]
+ // and is the name of the pinned egress ebpf program for ARPHRD_RAWIP interfaces.
+ // (also compatible with anything that has 0 size L2 header)
+ static constexpr char name_clat_tx_rawip[] = CLAT_EGRESS4_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_egress4_clat_ether:[*fsobj]
+ // and is the name of the pinned egress ebpf program for ARPHRD_ETHER interfaces.
+ // (also compatible with anything that has standard ethernet header)
+ static constexpr char name_clat_tx_ether[] = CLAT_EGRESS4_PROG_ETHER_NAME FSOBJ_SUFFIX;
+
+#undef FSOBJ_SUFFIX
+
+ // The actual name we'll use is determined at run time via 'ethernet' and 'ingress'
+ // booleans. We need to compile time allocate enough space in the struct
+ // hence this macro magic to make sure we have enough space for either
+ // possibility. In practice some of these are actually the same size.
+ static constexpr size_t ASCIIZ_MAXLEN_NAME = max({
+ sizeof(name_clat_rx_rawip),
+ sizeof(name_clat_rx_ether),
+ sizeof(name_clat_tx_rawip),
+ sizeof(name_clat_tx_ether),
+ });
+
+ // These are not compile time constants: 'name' is used in strncpy below
+ const char* const name_clat_rx = ethernet ? name_clat_rx_ether : name_clat_rx_rawip;
+ const char* const name_clat_tx = ethernet ? name_clat_tx_ether : name_clat_tx_rawip;
+ const char* const name = ingress ? name_clat_rx : name_clat_tx;
+
+ struct {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_LEN_BPF)];
+ } kind;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } fd;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_MAXLEN_NAME)];
+ } name;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } flags;
+ } options;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_NEWTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = static_cast<__u32>((PRIO_CLAT << 16) | htons(proto)),
+ },
+ .kind =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.kind),
+ .nla_type = TCA_KIND,
+ },
+ .str = BPF,
+ },
+ .options =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options),
+ .nla_type = NLA_F_NESTED | TCA_OPTIONS,
+ },
+ .fd =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.fd),
+ .nla_type = TCA_BPF_FD,
+ },
+ .u32 = static_cast<__u32>(bpfFd),
+ },
+ .name =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.name),
+ .nla_type = TCA_BPF_NAME,
+ },
+ // Visible via 'tc filter show', but
+ // is overwritten by strncpy below
+ .str = "placeholder",
+ },
+ .flags =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.flags),
+ .nla_type = TCA_BPF_FLAGS,
+ },
+ .u32 = TCA_BPF_FLAG_ACT_DIRECT,
+ },
+ },
+ };
+#undef BPF
+
+ strncpy(req.options.name.str, name, sizeof(req.options.name.str));
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+// tc filter del dev .. in/egress prio 4 protocol ..
+int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
+ const struct {
+ nlmsghdr n;
+ tcmsg t;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_DELTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = (static_cast<uint32_t>(prio) << 16) |
+ static_cast<uint32_t>(htons(proto)),
+ },
+ };
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/TcUtilsTest.cpp b/service/native/libs/libclat/TcUtilsTest.cpp
new file mode 100644
index 0000000..08f3042
--- /dev/null
+++ b/service/native/libs/libclat/TcUtilsTest.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright 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.
+ *
+ * TcUtilsTest.cpp - unit tests for TcUtils.cpp
+ */
+
+#include <gtest/gtest.h>
+
+#include "libclat/TcUtils.h"
+
+#include <linux/if_arp.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "bpf/BpfUtils.h"
+#include "bpf_shared.h"
+
+namespace android {
+namespace net {
+
+class TcUtilsTest : public ::testing::Test {
+ public:
+ void SetUp() {}
+};
+
+TEST_F(TcUtilsTest, HardwareAddressTypeOfNonExistingIf) {
+ ASSERT_EQ(-ENODEV, hardwareAddressType("not_existing_if"));
+}
+
+TEST_F(TcUtilsTest, HardwareAddressTypeOfLoopback) {
+ ASSERT_EQ(ARPHRD_LOOPBACK, hardwareAddressType("lo"));
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+TEST_F(TcUtilsTest, HardwareAddressTypeOfWireless) {
+ int type = hardwareAddressType("wlan0");
+ if (type == -ENODEV) return;
+
+ ASSERT_EQ(ARPHRD_ETHER, type);
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+TEST_F(TcUtilsTest, HardwareAddressTypeOfCellular) {
+ int type = hardwareAddressType("rmnet_data0");
+ if (type == -ENODEV) return;
+
+ ASSERT_NE(ARPHRD_ETHER, type);
+
+ // ARPHRD_RAWIP is 530 on some pre-4.14 Qualcomm devices.
+ if (type == 530) return;
+
+ ASSERT_EQ(ARPHRD_RAWIP, type);
+}
+
+TEST_F(TcUtilsTest, IsEthernetOfNonExistingIf) {
+ auto res = isEthernet("not_existing_if");
+ ASSERT_FALSE(res.ok());
+ ASSERT_EQ(ENODEV, res.error().code());
+}
+
+TEST_F(TcUtilsTest, IsEthernetOfLoopback) {
+ auto res = isEthernet("lo");
+ ASSERT_FALSE(res.ok());
+ ASSERT_EQ(EAFNOSUPPORT, res.error().code());
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+// See also HardwareAddressTypeOfWireless.
+TEST_F(TcUtilsTest, IsEthernetOfWireless) {
+ auto res = isEthernet("wlan0");
+ if (!res.ok() && res.error().code() == ENODEV) return;
+
+ ASSERT_RESULT_OK(res);
+ ASSERT_TRUE(res.value());
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+// See also HardwareAddressTypeOfCellular.
+TEST_F(TcUtilsTest, IsEthernetOfCellular) {
+ auto res = isEthernet("rmnet_data0");
+ if (!res.ok() && res.error().code() == ENODEV) return;
+
+ ASSERT_RESULT_OK(res);
+ ASSERT_FALSE(res.value());
+}
+
+TEST_F(TcUtilsTest, DeviceMTUOfNonExistingIf) {
+ ASSERT_EQ(-ENODEV, deviceMTU("not_existing_if"));
+}
+
+TEST_F(TcUtilsTest, DeviceMTUofLoopback) {
+ ASSERT_EQ(65536, deviceMTU("lo"));
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4MapFd) {
+ int fd = getClatEgress4MapFd();
+ ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4RawIpProgFd) {
+ int fd = getClatEgress4ProgFd(RAWIP);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4EtherProgFd) {
+ int fd = getClatEgress4ProgFd(ETHER);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6MapFd) {
+ int fd = getClatIngress6MapFd();
+ ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6RawIpProgFd) {
+ int fd = getClatIngress6ProgFd(RAWIP);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6EtherProgFd) {
+ int fd = getClatIngress6ProgFd(ETHER);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+// See Linux kernel source in include/net/flow.h
+#define LOOPBACK_IFINDEX 1
+
+TEST_F(TcUtilsTest, AttachReplaceDetachClsactLo) {
+ // This attaches and detaches a configuration-less and thus no-op clsact
+ // qdisc to loopback interface (and it takes fractions of a second)
+ EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscReplaceDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+}
+
+static void checkAttachDetachBpfFilterClsactLo(const bool ingress, const bool ethernet) {
+ // Older kernels return EINVAL instead of ENOENT due to lacking proper error propagation...
+ const int errNOENT = android::bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+
+ int clatBpfFd = ingress ? getClatIngress6ProgFd(ethernet) : getClatEgress4ProgFd(ethernet);
+ ASSERT_GE(clatBpfFd, 3);
+
+ // This attaches and detaches a clsact plus ebpf program to loopback
+ // interface, but it should not affect traffic by virtue of us not
+ // actually populating the ebpf control map.
+ // Furthermore: it only takes fractions of a second.
+ EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ if (ingress) {
+ EXPECT_EQ(0, tcFilterAddDevIngressClatIpv6(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
+ EXPECT_EQ(0, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ } else {
+ EXPECT_EQ(0, tcFilterAddDevEgressClatIpv4(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
+ EXPECT_EQ(0, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ }
+ EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+
+ close(clatBpfFd);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactEgressLo) {
+ checkAttachDetachBpfFilterClsactLo(EGRESS, RAWIP);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactEgressLo) {
+ checkAttachDetachBpfFilterClsactLo(EGRESS, ETHER);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactIngressLo) {
+ checkAttachDetachBpfFilterClsactLo(INGRESS, RAWIP);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactIngressLo) {
+ checkAttachDetachBpfFilterClsactLo(INGRESS, ETHER);
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/bpfhelper.cpp b/service/native/libs/libclat/bpfhelper.cpp
new file mode 100644
index 0000000..6e230d0
--- /dev/null
+++ b/service/native/libs/libclat/bpfhelper.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * main.c - main function
+ */
+#define LOG_TAG "bpfhelper"
+
+#include "libclat/bpfhelper.h"
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+
+#include "bpf/BpfMap.h"
+#include "libclat/TcUtils.h"
+
+#define DEVICEPREFIX "v4-"
+
+using android::base::unique_fd;
+using android::net::RAWIP;
+using android::net::getClatEgress4MapFd;
+using android::net::getClatIngress6MapFd;
+using android::net::getClatEgress4ProgFd;
+using android::net::getClatIngress6ProgFd;
+using android::net::tcQdiscAddDevClsact;
+using android::net::tcFilterAddDevEgressClatIpv4;
+using android::net::tcFilterAddDevIngressClatIpv6;
+using android::net::tcFilterDelDevEgressClatIpv4;
+using android::net::tcFilterDelDevIngressClatIpv6;
+using android::bpf::BpfMap;
+
+BpfMap<ClatEgress4Key, ClatEgress4Value> mClatEgress4Map;
+BpfMap<ClatIngress6Key, ClatIngress6Value> mClatIngress6Map;
+
+namespace android {
+namespace net {
+namespace clat {
+
+// TODO: have a clearMap function to remove all stubs while system server crash.
+// For long term, move bpf access into java and map initialization should live
+// ClatCoordinator constructor.
+int initMaps(void) {
+ int rv = getClatEgress4MapFd();
+ if (rv < 0) {
+ ALOGE("getClatEgress4MapFd() failure: %s", strerror(-rv));
+ return -rv;
+ }
+ mClatEgress4Map.reset(rv);
+
+ rv = getClatIngress6MapFd();
+ if (rv < 0) {
+ ALOGE("getClatIngress6MapFd() failure: %s", strerror(-rv));
+ mClatEgress4Map.reset(-1);
+ return -rv;
+ }
+ mClatIngress6Map.reset(rv);
+
+ return 0;
+}
+
+void maybeStartBpf(const ClatdTracker& tracker) {
+ auto isEthernet = android::net::isEthernet(tracker.iface);
+ if (!isEthernet.ok()) {
+ ALOGE("isEthernet(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
+ isEthernet.error().message().c_str());
+ return;
+ }
+
+ // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
+ int rv = getClatEgress4ProgFd(RAWIP);
+ if (rv < 0) {
+ ALOGE("getClatEgress4ProgFd(RAWIP) failure: %s", strerror(-rv));
+ return;
+ }
+ unique_fd txRawIpProgFd(rv);
+
+ rv = getClatIngress6ProgFd(isEthernet.value());
+ if (rv < 0) {
+ ALOGE("getClatIngress6ProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
+ return;
+ }
+ unique_fd rxProgFd(rv);
+
+ ClatEgress4Key txKey = {
+ .iif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+ ClatEgress4Value txValue = {
+ .oif = tracker.ifIndex,
+ .local6 = tracker.v6,
+ .pfx96 = tracker.pfx96,
+ .oifIsEthernet = isEthernet.value(),
+ };
+
+ auto ret = mClatEgress4Map.writeValue(txKey, txValue, BPF_ANY);
+ if (!ret.ok()) {
+ ALOGE("mClatEgress4Map.writeValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ ClatIngress6Key rxKey = {
+ .iif = tracker.ifIndex,
+ .pfx96 = tracker.pfx96,
+ .local6 = tracker.v6,
+ };
+ ClatIngress6Value rxValue = {
+ // TODO: move all the clat code to eBPF and remove the tun interface entirely.
+ .oif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+
+ ret = mClatIngress6Map.writeValue(rxKey, rxValue, BPF_ANY);
+ if (!ret.ok()) {
+ ALOGE("mClatIngress6Map.writeValue failure: %s", strerror(ret.error().code()));
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ // We do tc setup *after* populating the maps, so scanning through them
+ // can always be used to tell us what needs cleanup.
+
+ // Usually the clsact will be added in RouteController::addInterfaceToPhysicalNetwork.
+ // But clat is started before the v4- interface is added to the network. The clat startup have
+ // to add clsact of v4- tun interface first for adding bpf filter in maybeStartBpf.
+ // TODO: move "qdisc add clsact" of v4- tun interface out from ClatdController.
+ rv = tcQdiscAddDevClsact(tracker.v4ifIndex);
+ if (rv) {
+ ALOGE("tcQdiscAddDevClsact(%d[%s]) failure: %s", tracker.v4ifIndex, tracker.v4iface,
+ strerror(-rv));
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ rv = tcFilterAddDevEgressClatIpv4(tracker.v4ifIndex, txRawIpProgFd, RAWIP);
+ if (rv) {
+ ALOGE("tcFilterAddDevEgressClatIpv4(%d[%s], RAWIP) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+
+ // The v4- interface clsact is not deleted for unwinding error because once it is created
+ // with interface addition, the lifetime is till interface deletion. Moreover, the clsact
+ // has no clat filter now. It should not break anything.
+
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ rv = tcFilterAddDevIngressClatIpv6(tracker.ifIndex, rxProgFd, isEthernet.value());
+ if (rv) {
+ ALOGE("tcFilterAddDevIngressClatIpv6(%d[%s], %d) failure: %s", tracker.ifIndex,
+ tracker.iface, isEthernet.value(), strerror(-rv));
+ rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
+ if (rv) {
+ ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+ }
+
+ // The v4- interface clsact is not deleted. See the reason in the error unwinding code of
+ // the egress filter attaching of v4- tun interface.
+
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ // success
+}
+
+void maybeStopBpf(const ClatdTracker& tracker) {
+ int rv = tcFilterDelDevIngressClatIpv6(tracker.ifIndex);
+ if (rv < 0) {
+ ALOGE("tcFilterDelDevIngressClatIpv6(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
+ strerror(-rv));
+ }
+
+ rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
+ if (rv < 0) {
+ ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+ }
+
+ // We cleanup the maps last, so scanning through them can be used to
+ // determine what still needs cleanup.
+
+ ClatEgress4Key txKey = {
+ .iif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+
+ auto ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok()) ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+
+ ClatIngress6Key rxKey = {
+ .iif = tracker.ifIndex,
+ .pfx96 = tracker.pfx96,
+ .local6 = tracker.v6,
+ };
+
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok()) ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+}
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/TcUtils.h b/service/native/libs/libclat/include/libclat/TcUtils.h
new file mode 100644
index 0000000..212838e
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/TcUtils.h
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <errno.h>
+#include <linux/if_ether.h>
+#include <linux/if_link.h>
+#include <linux/rtnetlink.h>
+
+#include <string>
+
+#include "bpf/BpfUtils.h"
+#include "bpf_shared.h"
+
+namespace android {
+namespace net {
+
+// For better code clarity - do not change values - used for booleans like
+// with_ethernet_header or isEthernet.
+constexpr bool RAWIP = false;
+constexpr bool ETHER = true;
+
+// For better code clarity when used for 'bool ingress' parameter.
+constexpr bool EGRESS = false;
+constexpr bool INGRESS = true;
+
+// The priority of clat hook - must be after tethering.
+constexpr uint16_t PRIO_CLAT = 4;
+
+// this returns an ARPHRD_* constant or a -errno
+int hardwareAddressType(const std::string& interface);
+
+// return MTU or -errno
+int deviceMTU(const std::string& interface);
+
+base::Result<bool> isEthernet(const std::string& interface);
+
+inline int getClatEgress4MapFd(void) {
+ const int fd = bpf::mapRetrieveRW(CLAT_EGRESS4_MAP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatEgress4ProgFd(bool with_ethernet_header) {
+ const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_EGRESS4_PROG_ETHER_PATH
+ : CLAT_EGRESS4_PROG_RAWIP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatIngress6MapFd(void) {
+ const int fd = bpf::mapRetrieveRW(CLAT_INGRESS6_MAP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatIngress6ProgFd(bool with_ethernet_header) {
+ const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_INGRESS6_PROG_ETHER_PATH
+ : CLAT_INGRESS6_PROG_RAWIP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags);
+
+inline int tcQdiscAddDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
+}
+
+inline int tcQdiscReplaceDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
+}
+
+inline int tcQdiscDelDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0);
+}
+
+// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet);
+
+// tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
+inline int tcFilterAddDevIngressClatIpv6(int ifIndex, int bpfFd, bool ethernet) {
+ return tcFilterAddDevBpf(ifIndex, INGRESS, ETH_P_IPV6, bpfFd, ethernet);
+}
+
+// tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/... direct-action
+inline int tcFilterAddDevEgressClatIpv4(int ifIndex, int bpfFd, bool ethernet) {
+ return tcFilterAddDevBpf(ifIndex, EGRESS, ETH_P_IP, bpfFd, ethernet);
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
+
+// tc filter del dev .. ingress prio 4 protocol ipv6
+inline int tcFilterDelDevIngressClatIpv6(int ifIndex) {
+ return tcFilterDelDev(ifIndex, INGRESS, PRIO_CLAT, ETH_P_IPV6);
+}
+
+// tc filter del dev .. egress prio 4 protocol ip
+inline int tcFilterDelDevEgressClatIpv4(int ifIndex) {
+ return tcFilterDelDev(ifIndex, EGRESS, PRIO_CLAT, ETH_P_IP);
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/bpfhelper.h b/service/native/libs/libclat/include/libclat/bpfhelper.h
new file mode 100644
index 0000000..c0328c0
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/bpfhelper.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+
+namespace android {
+namespace net {
+namespace clat {
+
+struct ClatdTracker {
+ unsigned ifIndex;
+ char iface[IFNAMSIZ];
+ unsigned v4ifIndex;
+ char v4iface[IFNAMSIZ];
+ in_addr v4;
+ in6_addr v6;
+ in6_addr pfx96;
+};
+
+int initMaps(void);
+void maybeStartBpf(const ClatdTracker& tracker);
+void maybeStopBpf(const ClatdTracker& tracker);
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 6c27c4a..ae6fe11 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -73,6 +73,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -10147,11 +10149,19 @@
switch (preference.getPreference()) {
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
nc = null;
+ if (preference.getPreferenceEnterpriseId() != 0) {
+ throw new IllegalArgumentException(
+ "Invalid enterprise identifier in setProfileNetworkPreferences");
+ }
break;
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK:
allowFallback = false;
// continue to process the enterprise preference.
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
+ if (!isEnterpriseIdentifierValid(preference.getPreferenceEnterpriseId())) {
+ throw new IllegalArgumentException(
+ "Invalid enterprise identifier in setProfileNetworkPreferences");
+ }
final Set<UidRange> uidRangeSet =
getUidListToBeAppliedForNetworkPreference(profile, preference);
if (!isRangeAlreadyInPreferenceList(preferenceList, uidRangeSet)) {
@@ -10161,6 +10171,8 @@
"Overlapping uid range in setProfileNetworkPreferences");
}
nc.addCapability(NET_CAPABILITY_ENTERPRISE);
+ nc.addEnterpriseId(
+ preference.getPreferenceEnterpriseId());
nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
break;
default:
@@ -10203,6 +10215,15 @@
return uidRangeSet;
}
+ private boolean isEnterpriseIdentifierValid(
+ @NetworkCapabilities.EnterpriseId int identifier) {
+ if ((identifier >= NET_ENTERPRISE_ID_1)
+ && (identifier <= NET_ENTERPRISE_ID_5)) {
+ return true;
+ }
+ return false;
+ }
+
private void validateNetworkCapabilitiesOfProfileNetworkPreference(
@Nullable final NetworkCapabilities nc) {
if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 4d243c4..5a5e24a 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -74,6 +74,12 @@
private final Dependencies mDeps;
@Nullable
private String mIface = null;
+ @Nullable
+ private String mNat64Prefix = null;
+ @Nullable
+ private String mXlatLocalAddress4 = null;
+ @Nullable
+ private String mXlatLocalAddress6 = null;
private int mPid = INVALID_PID;
@VisibleForTesting
@@ -162,6 +168,23 @@
throws IOException {
native_configurePacketSocket(sock, v6, ifindex);
}
+
+ /**
+ * Maybe start bpf.
+ */
+ public int maybeStartBpf(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
+ @NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96,
+ @NonNull String v4, @NonNull String v6) throws IOException {
+ return native_maybeStartBpf(tunfd, readsock6, writesock6, iface, pfx96, v4, v6);
+ }
+
+ /**
+ * Maybe stop bpf.
+ */
+ public void maybeStopBpf(String iface, String pfx96, String v4, String v6, int pid)
+ throws IOException {
+ native_maybeStopBpf(iface, pfx96, v4, v6, pid);
+ }
}
@VisibleForTesting
@@ -304,10 +327,38 @@
throw new IOException("configure packet socket failed: " + e);
}
+ // [5] Maybe start bpf.
+ try {
+ mDeps.maybeStartBpf(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
+ writeSock6.getFileDescriptor(), iface, pfx96, v4, v6);
+ mIface = iface;
+ mNat64Prefix = pfx96;
+ mXlatLocalAddress4 = v4;
+ mXlatLocalAddress6 = v6;
+ } catch (IOException e) {
+ throw new IOException("Error start bpf on " + iface + ": " + e);
+ }
+
// TODO: start clatd and returns local xlat464 v6 address.
return null;
}
+ /**
+ * Stop clatd
+ */
+ public void clatStop() throws IOException {
+ mDeps.maybeStopBpf(mIface, mNat64Prefix, mXlatLocalAddress4, mXlatLocalAddress6,
+ mPid /* unused */);
+ // TODO: remove setIptablesDropRule
+
+ Log.i(TAG, "clatd on " + mIface + " stopped");
+
+ mIface = null;
+ mNat64Prefix = null;
+ mXlatLocalAddress4 = null;
+ mXlatLocalAddress6 = null;
+ }
+
private static native String native_selectIpv4Address(String v4addr, int prefixlen)
throws IOException;
private static native String native_generateIpv6Address(String iface, String v4,
@@ -321,4 +372,9 @@
int ifindex) throws IOException;
private static native void native_configurePacketSocket(FileDescriptor sock, String v6,
int ifindex) throws IOException;
+ private static native int native_maybeStartBpf(FileDescriptor tunfd, FileDescriptor readsock6,
+ FileDescriptor writesock6, String iface, String pfx96, String v4, String v6)
+ throws IOException;
+ private static native void native_maybeStopBpf(String iface, String pfx96, String v4,
+ String v6, int pid) throws IOException;
}
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 09d36e5..bea00a9 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -23,11 +23,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
@@ -39,9 +34,16 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_2;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_4;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -415,6 +417,31 @@
assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities()));
}
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ public void testPrioritizeLatencyAndBandwidth() {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_LATENCY);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_LATENCY);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.R)
public void testOemPrivate() {
NetworkCapabilities nc = new NetworkCapabilities();
@@ -790,78 +817,79 @@
}
@Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
- public void testEnterpriseCapabilitySubLevel() {
+ public void testEnterpriseId() {
final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
.build();
- assertEquals(1, nc1.getEnterpriseCapabilitySubLevels().length);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1,
- nc1.getEnterpriseCapabilitySubLevels()[0]);
+ assertEquals(1, nc1.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc1.getEnterpriseIds()[0]);
final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
.build();
- assertEquals(2, nc2.getEnterpriseCapabilitySubLevels().length);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1,
- nc2.getEnterpriseCapabilitySubLevels()[0]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2,
- nc2.getEnterpriseCapabilitySubLevels()[1]);
+ assertEquals(2, nc2.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc2.getEnterpriseIds()[0]);
+ assertEquals(NET_ENTERPRISE_ID_2,
+ nc2.getEnterpriseIds()[1]);
final NetworkCapabilities nc3 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_3)
+ .addEnterpriseId(NET_ENTERPRISE_ID_4)
+ .addEnterpriseId(NET_ENTERPRISE_ID_5)
.build();
- assertEquals(5, nc3.getEnterpriseCapabilitySubLevels().length);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1,
- nc3.getEnterpriseCapabilitySubLevels()[0]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2,
- nc3.getEnterpriseCapabilitySubLevels()[1]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3,
- nc3.getEnterpriseCapabilitySubLevels()[2]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4,
- nc3.getEnterpriseCapabilitySubLevels()[3]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5,
- nc3.getEnterpriseCapabilitySubLevels()[4]);
+ assertEquals(5, nc3.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc3.getEnterpriseIds()[0]);
+ assertEquals(NET_ENTERPRISE_ID_2,
+ nc3.getEnterpriseIds()[1]);
+ assertEquals(NET_ENTERPRISE_ID_3,
+ nc3.getEnterpriseIds()[2]);
+ assertEquals(NET_ENTERPRISE_ID_4,
+ nc3.getEnterpriseIds()[3]);
+ assertEquals(NET_ENTERPRISE_ID_5,
+ nc3.getEnterpriseIds()[4]);
final Class<IllegalArgumentException> illegalArgumentExceptionClass =
IllegalArgumentException.class;
assertThrows(illegalArgumentExceptionClass, () -> new NetworkCapabilities.Builder()
- .addEnterpriseCapabilitySubLevel(6)
+ .addEnterpriseId(6)
.build());
assertThrows(illegalArgumentExceptionClass, () -> new NetworkCapabilities.Builder()
- .removeEnterpriseCapabilitySubLevel(6)
+ .removeEnterpriseId(6)
.build());
final Class<IllegalStateException> illegalStateException =
IllegalStateException.class;
assertThrows(illegalStateException, () -> new NetworkCapabilities.Builder()
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
.build());
final NetworkCapabilities nc4 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
- .removeEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .removeEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_1)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_2)
.build();
- assertEquals(0, nc4.getEnterpriseCapabilitySubLevels().length);
+ assertEquals(1, nc4.getEnterpriseIds().length);
+ assertTrue(nc4.hasEnterpriseId(NET_ENTERPRISE_ID_1));
final NetworkCapabilities nc5 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_CBS)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
- .removeEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .removeEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_1)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_2)
.build();
assertTrue(nc4.satisfiedByNetworkCapabilities(nc1));
- assertFalse(nc1.satisfiedByNetworkCapabilities(nc4));
+ assertTrue(nc1.satisfiedByNetworkCapabilities(nc4));
assertFalse(nc3.satisfiedByNetworkCapabilities(nc2));
assertTrue(nc2.satisfiedByNetworkCapabilities(nc3));
diff --git a/tests/cts/net/native/Android.bp b/tests/cts/net/native/Android.bp
index 1d1c18e..153ff51 100644
--- a/tests/cts/net/native/Android.bp
+++ b/tests/cts/net/native/Android.bp
@@ -43,6 +43,7 @@
static_libs: [
"libbpf_android",
"libgtest",
+ "libmodules-utils-build",
],
// Tag this module as a cts test artifact
diff --git a/tests/cts/net/native/src/BpfCompatTest.cpp b/tests/cts/net/native/src/BpfCompatTest.cpp
index 874bad4..97ecb9e 100644
--- a/tests/cts/net/native/src/BpfCompatTest.cpp
+++ b/tests/cts/net/native/src/BpfCompatTest.cpp
@@ -21,6 +21,8 @@
#include <gtest/gtest.h>
+#include "android-modules-utils/sdk_level.h"
+
#include "libbpf_android.h"
using namespace android::bpf;
@@ -33,11 +35,17 @@
EXPECT_EQ(28, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
}
-TEST(BpfTest, bpfStructSizeTest) {
+TEST(BpfTest, bpfStructSizeTestPreT) {
+ if (android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << "T+ device.";
doBpfStructSizeTest("/system/etc/bpf/netd.o");
doBpfStructSizeTest("/system/etc/bpf/clatd.o");
}
+TEST(BpfTest, bpfStructSizeTest) {
+ doBpfStructSizeTest("/system/etc/bpf/gpu_mem.o");
+ doBpfStructSizeTest("/system/etc/bpf/time_in_state.o");
+}
+
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 80c2db4..53e4ab7 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -168,6 +168,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
+import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
@@ -3021,6 +3022,13 @@
}
}
+ @Test
+ public void testDump() throws Exception {
+ final String dumpOutput = DumpTestUtils.dumpServiceWithShellPermission(
+ Context.CONNECTIVITY_SERVICE, "--short");
+ assertTrue(dumpOutput, dumpOutput.contains("Active default network"));
+ }
+
private void unregisterRegisteredCallbacks() {
for (NetworkCallback callback: mRegisteredCallbacks) {
mCm.unregisterNetworkCallback(callback);
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index f95e79f..2b698fd 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -88,7 +88,9 @@
"java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
"java/com/android/server/connectivity/VpnTest.java",
"java/com/android/server/net/ipmemorystore/*.java",
+ "java/com/android/server/net/BpfInterfaceMapUpdaterTest.java",
"java/com/android/server/net/NetworkStats*.java",
+ "java/com/android/server/net/TestableUsageCallback.kt",
]
}
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index 6ad8b06..ec0420e 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -17,7 +17,11 @@
package android.net
import android.content.Context
+import android.net.ConnectivityManager.MAX_NETWORK_TYPE
+import android.net.ConnectivityManager.TYPE_ETHERNET
import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_NONE
+import android.net.ConnectivityManager.TYPE_WIFI
import android.net.NetworkIdentity.OEM_NONE
import android.net.NetworkIdentity.OEM_PAID
import android.net.NetworkIdentity.OEM_PRIVATE
@@ -30,10 +34,12 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
private const val TEST_IMSI = "testimsi"
+private const val TEST_WIFI_KEY = "testwifikey"
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
@@ -126,6 +132,92 @@
assertEquals(identFromLegacyBuild, identFromSnapshot)
assertEquals(identFromConstructor, identFromSnapshot)
- // TODO: Add test cases for wifiNetworkKey and ratType.
+ // Assert non-wifi can't have wifi network key.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_ETHERNET)
+ .setWifiNetworkKey(TEST_WIFI_KEY)
+ .build()
+ }
+
+ // Assert non-mobile can't have ratType.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_WIFI)
+ .setRatType(TelephonyManager.NETWORK_TYPE_LTE)
+ .build()
+ }
+ }
+
+ @Test
+ fun testBuilder_type() {
+ // Assert illegal type values cannot make an identity.
+ listOf(Integer.MIN_VALUE, TYPE_NONE - 1, MAX_NETWORK_TYPE + 1, Integer.MAX_VALUE)
+ .forEach { type ->
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder().setType(type).build()
+ }
+ }
+
+ // Verify legitimate type values can make an identity.
+ for (type in TYPE_NONE..MAX_NETWORK_TYPE) {
+ NetworkIdentity.Builder().setType(type).build().also {
+ assertEquals(it.type, type)
+ }
+ }
+ }
+
+ @Test
+ fun testBuilder_ratType() {
+ // Assert illegal ratTypes cannot make an identity.
+ listOf(Integer.MIN_VALUE, NetworkTemplate.NETWORK_TYPE_ALL,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN - 1, Integer.MAX_VALUE)
+ .forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setRatType(it)
+ .build()
+ }
+ }
+
+ // Verify legitimate ratTypes can make an identity.
+ TelephonyManager.getAllNetworkTypes().toMutableList().also {
+ it.add(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+ }.forEach { rat ->
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setRatType(rat)
+ .build().also {
+ assertEquals(it.ratType, rat)
+ }
+ }
+ }
+
+ @Test
+ fun testBuilder_oemManaged() {
+ // Assert illegal oemManage values cannot make an identity.
+ listOf(Integer.MIN_VALUE, NetworkTemplate.OEM_MANAGED_ALL, NetworkTemplate.OEM_MANAGED_YES,
+ Integer.MAX_VALUE)
+ .forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setRatType(it)
+ .build()
+ }
+ }
+
+ // Verify legitimate oem managed values can make an identity.
+ listOf(NetworkTemplate.OEM_MANAGED_NO, NetworkTemplate.OEM_MANAGED_PAID,
+ NetworkTemplate.OEM_MANAGED_PRIVATE, NetworkTemplate.OEM_MANAGED_PAID or
+ NetworkTemplate.OEM_MANAGED_PRIVATE)
+ .forEach { oemManaged ->
+ NetworkIdentity.Builder()
+ .setOemManaged(oemManaged)
+ .build().also {
+ assertEquals(it.oemManaged, oemManaged)
+ }
+ }
}
}
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 2e82986..c27ee93 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -38,11 +38,13 @@
import static org.junit.Assert.fail;
import android.content.res.Resources;
+import android.net.NetworkStatsCollection.Key;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.RecurrenceRule;
import androidx.test.InstrumentationRegistry;
@@ -73,6 +75,7 @@
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Tests for {@link NetworkStatsCollection}.
@@ -530,6 +533,52 @@
assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
}
+ @Test
+ public void testBuilder() {
+ final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final NetworkIdentitySet ident = new NetworkIdentitySet();
+ final Key key1 = new Key(ident, 0, 0, 0);
+ final Key key2 = new Key(ident, 1, 0, 0);
+ final long bucketDuration = 10;
+
+ final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 10, 40,
+ 4, 50, 5, 60);
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 10, 3,
+ 41, 7, 1, 0);
+
+ NetworkStatsHistory history1 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .build();
+
+ NetworkStatsHistory history2 = new NetworkStatsHistory(10, 5);
+
+ NetworkStatsCollection actualCollection = new NetworkStatsCollection.Builder(bucketDuration)
+ .addEntry(key1, history1)
+ .addEntry(key2, history2)
+ .build();
+
+ // The builder will omit any entry with empty history. Thus, history2
+ // is not expected in the result collection.
+ expectedEntries.put(key1, history1);
+
+ final Map<Key, NetworkStatsHistory> actualEntries = actualCollection.getEntries();
+
+ assertEquals(expectedEntries.size(), actualEntries.size());
+ for (Key expectedKey : expectedEntries.keySet()) {
+ final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
+
+ final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
+ assertNotNull(actualHistory);
+
+ assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
+
+ actualEntries.remove(expectedKey);
+ }
+ assertEquals(0, actualEntries.size());
+ }
+
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.
@@ -587,6 +636,14 @@
actual.txBytes, actual.txPackets, 0L));
}
+ private static void assertEntry(NetworkStatsHistory.Entry expected,
+ NetworkStatsHistory.Entry actual) {
+ assertEntry(new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
+ actual.txBytes, actual.txPackets, 0L),
+ new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
+ actual.txBytes, actual.txPackets, 0L));
+ }
+
private static void assertEntry(NetworkStats.Entry expected,
NetworkStats.Entry actual) {
assertEquals("unexpected rxBytes", expected.rxBytes, actual.rxBytes);
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index c5f8c00..c170605 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -56,6 +56,7 @@
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.util.List;
import java.util.Random;
@RunWith(DevSdkIgnoreRunner.class)
@@ -532,6 +533,40 @@
assertEquals(512L + 4096L, stats.getTotalBytes());
}
+ @Test
+ public void testBuilder() {
+ final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 30, 40,
+ 4, 50, 5, 60);
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 15, 3,
+ 41, 7, 1, 0);
+ final NetworkStatsHistory.Entry entry3 = new NetworkStatsHistory.Entry(7, 301, 11,
+ 14, 31, 2, 80);
+
+ final NetworkStatsHistory statsEmpty = new NetworkStatsHistory
+ .Builder(HOUR_IN_MILLIS, 10).build();
+ assertEquals(0, statsEmpty.getEntries().size());
+ assertEquals(HOUR_IN_MILLIS, statsEmpty.getBucketDuration());
+
+ NetworkStatsHistory statsSingle = new NetworkStatsHistory
+ .Builder(HOUR_IN_MILLIS, 8)
+ .addEntry(entry1)
+ .build();
+ assertEquals(1, statsSingle.getEntries().size());
+ assertEquals(HOUR_IN_MILLIS, statsSingle.getBucketDuration());
+ assertEquals(entry1, statsSingle.getEntries().get(0));
+
+ NetworkStatsHistory statsMultiple = new NetworkStatsHistory
+ .Builder(SECOND_IN_MILLIS, 0)
+ .addEntry(entry1).addEntry(entry2).addEntry(entry3)
+ .build();
+ final List<NetworkStatsHistory.Entry> entries = statsMultiple.getEntries();
+ assertEquals(3, entries.size());
+ assertEquals(SECOND_IN_MILLIS, statsMultiple.getBucketDuration());
+ assertEquals(entry1, entries.get(0));
+ assertEquals(entry2, entries.get(1));
+ assertEquals(entry3, entries.get(2));
+ }
+
private static void assertIndexBeforeAfter(
NetworkStatsHistory stats, int before, int after, long time) {
assertEquals("unexpected before", before, stats.getIndexBefore(time));
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 14ff981..652aee9 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -101,6 +101,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -13780,6 +13781,14 @@
return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc);
}
+ private TestNetworkAgentWrapper makeEnterpriseNetworkAgent(int enterpriseId) throws Exception {
+ final NetworkCapabilities workNc = new NetworkCapabilities();
+ workNc.addCapability(NET_CAPABILITY_ENTERPRISE);
+ workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ workNc.addEnterpriseId(enterpriseId);
+ return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc);
+ }
+
private TestNetworkCallback mEnterpriseCallback;
private UserHandle setupEnterpriseNetwork() {
final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
@@ -13827,7 +13836,8 @@
inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
- final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+ final TestNetworkAgentWrapper workAgent =
+ makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
if (connectWorkProfileAgentAhead) {
workAgent.connect(false);
}
@@ -13841,7 +13851,7 @@
== PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK) {
allowFallback = false;
}
- if (allowFallback) {
+ if (allowFallback && !connectWorkProfileAgentAhead) {
// Setting a network preference for this user will create a new set of routing rules for
// the UID range that corresponds to this user, inorder to define the default network
// for these apps separately. This is true because the multi-layer request relevant to
@@ -13858,7 +13868,7 @@
// The enterprise network is not ready yet.
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
- if (allowFallback) {
+ if (allowFallback && !connectWorkProfileAgentAhead) {
assertNoCallbacks(profileDefaultNetworkCallback);
} else if (!connectWorkProfileAgentAhead) {
profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
@@ -13884,7 +13894,7 @@
uidRangeFor(testHandle, profileNetworkPreference),
PREFERENCE_ORDER_PROFILE));
- if (allowFallback) {
+ if (allowFallback && !connectWorkProfileAgentAhead) {
inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
mCellNetworkAgent.getNetwork().netId,
uidRangeFor(testHandle, profileNetworkPreference),
@@ -13897,7 +13907,10 @@
workAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)
- && nc.hasCapability(NET_CAPABILITY_ENTERPRISE));
+ && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
+ && nc.hasEnterpriseId(
+ profileNetworkPreference.getPreferenceEnterpriseId())
+ && nc.getEnterpriseIds().length == 1);
if (disAllowProfileDefaultNetworkCallback != null) {
assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
}
@@ -13988,7 +14001,8 @@
// If the control comes here, callbacks seem to behave correctly in the presence of
// a default network when the enterprise network goes up and down. Now, make sure they
// also behave correctly in the absence of a system-wide default network.
- final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent();
+ final TestNetworkAgentWrapper workAgent2 =
+ makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
workAgent2.connect(false);
profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2);
@@ -14006,7 +14020,10 @@
workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid());
profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
- && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasEnterpriseId(
+ profileNetworkPreference.getPreferenceEnterpriseId())
+ && nc.getEnterpriseIds().length == 1);
if (disAllowProfileDefaultNetworkCallback != null) {
assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
}
@@ -14037,10 +14054,11 @@
@Test
public void testPreferenceForUserNetworkUpDown() throws Exception {
final UserHandle testHandle = setupEnterpriseNetwork();
+ registerDefaultNetworkCallbacks();
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
- registerDefaultNetworkCallbacks();
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), false,
testHandle, mProfileDefaultNetworkCallback, null);
@@ -14058,6 +14076,7 @@
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(
PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
registerDefaultNetworkCallbacks();
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), false,
@@ -14078,6 +14097,7 @@
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(
PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
registerDefaultNetworkCallbacks();
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), true, testHandle,
@@ -14094,6 +14114,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder.setIncludedUids(
List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID)));
registerDefaultNetworkCallbacks();
@@ -14112,6 +14133,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder.setIncludedUids(
List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
registerDefaultNetworkCallbacks();
@@ -14130,6 +14152,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder.setExcludedUids(
List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
registerDefaultNetworkCallbacks();
@@ -14149,6 +14172,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder.setExcludedUids(
List.of(testHandle.getUid(0) - 1));
final TestOnCompleteListener listener = new TestOnCompleteListener();
@@ -14178,6 +14202,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder2.setIncludedUids(
List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
profileNetworkPreferenceBuilder.setIncludedUids(
@@ -14213,6 +14238,84 @@
}
/**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active. Make sure they behave as expected whether
+ * there is a general default network or not when configured to fallback to default network
+ * along with already connected enterprise work agent
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithFallbackWithAlreadyConnectedWorkAgent()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback,
+ null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithDefaultEnterpriseId()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback,
+ null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithId2()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(
+ NetworkCapabilities.NET_ENTERPRISE_ID_2);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback, null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithInvalidId()
+ throws Exception {
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(0);
+ registerDefaultNetworkCallbacks();
+ assertThrows("Should not be able to set invalid enterprise id",
+ IllegalStateException.class, () -> profileNetworkPreferenceBuilder.build());
+ }
+
+ /**
* Test that, in a given networking context, calling setPreferenceForUser to set per-profile
* defaults on then off works as expected.
*/
@@ -14362,10 +14465,15 @@
public void testProfileNetworkPrefWrongPreference() throws Exception {
final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
mServiceContext.setWorkProfile(testHandle, true);
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + 1);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
assertThrows("Should not be able to set an illegal preference",
IllegalArgumentException.class,
- () -> mCm.setProfileNetworkPreference(testHandle,
- PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + 1,
+ () -> mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
null, null));
}
diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
index ea29da0..0c58582 100644
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -32,6 +33,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.INetdUnsolicitedEventListener;
import android.net.LinkAddress;
@@ -71,6 +73,7 @@
public class NetworkManagementServiceTest {
private NetworkManagementService mNMService;
@Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
@Mock private IBatteryStats.Stub mBatteryStatsService;
@Mock private INetd.Stub mNetdService;
@@ -113,6 +116,9 @@
MockitoAnnotations.initMocks(this);
doNothing().when(mNetdService)
.registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ eq(ConnectivityManager.class));
+ doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
// Start the service and wait until it connects to our socket.
mNMService = NetworkManagementService.create(mContext, mDeps);
}
@@ -239,6 +245,7 @@
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
assertTrue("Should be true since mobile data usage is restricted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkDenyList(TEST_UID, true /* enabled */);
mNMService.setDataSaverModeEnabled(true);
verify(mNetdService).bandwidthEnableDataSaver(true);
@@ -246,13 +253,16 @@
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
assertTrue("Should be true since data saver is on and the uid is not allowlisted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkDenyList(TEST_UID, true /* false */);
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
assertFalse("Should be false since data saver is on and the uid is allowlisted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkAllowList(TEST_UID, true /* enabled */);
// remove uid from allowlist and turn datasaver off again
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
+ verify(mCm).updateMeteredNetworkAllowList(TEST_UID, false /* enabled */);
mNMService.setDataSaverModeEnabled(false);
verify(mNetdService).bandwidthEnableDataSaver(false);
assertFalse("Network should not be restricted when data saver is off",
@@ -306,12 +316,14 @@
for (int chain : chains) {
final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
mNMService.setFirewallChainEnabled(chain, true);
+ verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
for (int state : states) {
mNMService.setFirewallUidRule(chain, TEST_UID, state);
assertEquals(errorMsg.apply(chain, state),
expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
}
mNMService.setFirewallChainEnabled(chain, false);
+ verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
}
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index c86e699..ec51537 100644
--- a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -296,7 +296,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
}
@Test
@@ -315,7 +315,7 @@
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
}
@Test
@@ -334,7 +334,7 @@
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
}
@Test
@@ -351,7 +351,7 @@
// Default global setting should be used: 12 - 7 = 5
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
}
@Test
@@ -366,7 +366,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
// Update setting
setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14));
@@ -376,7 +376,7 @@
// Callback must have been re-registered with new setting
verify(mStatsManager, times(1)).unregisterUsageCallback(any());
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
}
@Test
@@ -391,7 +391,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
.thenReturn((int) DataUnit.MEGABYTES.toBytes(16));
@@ -402,6 +402,6 @@
// Uses the new setting (16 - 2 = 14MB)
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
}
}
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
new file mode 100644
index 0000000..987b7b7
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.MacAddress;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.Struct.U32;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class BpfInterfaceMapUpdaterTest {
+ private static final int TEST_INDEX = 1;
+ private static final int TEST_INDEX2 = 2;
+ private static final String TEST_INTERFACE_NAME = "test1";
+ private static final String TEST_INTERFACE_NAME2 = "test2";
+
+ private final TestLooper mLooper = new TestLooper();
+ private BaseNetdUnsolicitedEventListener mListener;
+ private BpfInterfaceMapUpdater mUpdater;
+ @Mock private IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ @Mock private INetd mNetd;
+ @Mock private Context mContext;
+
+ private class TestDependencies extends BpfInterfaceMapUpdater.Dependencies {
+ @Override
+ public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+ return mBpfMap;
+ }
+
+ @Override
+ public InterfaceParams getInterfaceParams(String ifaceName) {
+ if (ifaceName.equals(TEST_INTERFACE_NAME)) {
+ return new InterfaceParams(TEST_INTERFACE_NAME, TEST_INDEX,
+ MacAddress.ALL_ZEROS_ADDRESS);
+ } else if (ifaceName.equals(TEST_INTERFACE_NAME2)) {
+ return new InterfaceParams(TEST_INTERFACE_NAME2, TEST_INDEX2,
+ MacAddress.ALL_ZEROS_ADDRESS);
+ }
+
+ return null;
+ }
+
+ @Override
+ public INetd getINetd(Context ctx) {
+ return mNetd;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {TEST_INTERFACE_NAME});
+ mUpdater = new BpfInterfaceMapUpdater(mContext, new Handler(mLooper.getLooper()),
+ new TestDependencies());
+ }
+
+ private void verifyStartUpdater() throws Exception {
+ mUpdater.start();
+ mLooper.dispatchAll();
+ final ArgumentCaptor<BaseNetdUnsolicitedEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener.class);
+ verify(mNetd).registerUnsolicitedEventListener(listenerCaptor.capture());
+ mListener = listenerCaptor.getValue();
+ verify(mBpfMap).updateEntry(eq(new U32(TEST_INDEX)),
+ eq(new InterfaceMapValue(TEST_INTERFACE_NAME)));
+ }
+
+ @Test
+ public void testUpdateInterfaceMap() throws Exception {
+ verifyStartUpdater();
+
+ mListener.onInterfaceAdded(TEST_INTERFACE_NAME2);
+ mLooper.dispatchAll();
+ verify(mBpfMap).updateEntry(eq(new U32(TEST_INDEX2)),
+ eq(new InterfaceMapValue(TEST_INTERFACE_NAME2)));
+
+ // Check that when onInterfaceRemoved is called, nothing happens.
+ mListener.onInterfaceRemoved(TEST_INTERFACE_NAME);
+ mLooper.dispatchAll();
+ verifyNoMoreInteractions(mBpfMap);
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 8340a13..0e2c293 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -32,9 +32,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import android.content.Context;
import android.content.res.Resources;
-import android.net.INetd;
+import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
@@ -73,8 +76,8 @@
private File mTestProc;
private NetworkStatsFactory mFactory;
- @Mock
- private INetd mNetd;
+ @Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
@Before
public void setUp() throws Exception {
@@ -85,7 +88,10 @@
// 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, mNetd);
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ eq(ConnectivityManager.class));
+ doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
+ mFactory = new NetworkStatsFactory(mContext, mTestProc, false);
mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index d993d1f..66dcf6d 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -36,19 +36,15 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
-import android.app.usage.NetworkStatsManager;
import android.net.DataUsageRequest;
import android.net.NetworkIdentity;
import android.net.NetworkIdentitySet;
import android.net.NetworkStats;
import android.net.NetworkStatsAccess;
import android.net.NetworkTemplate;
-import android.os.ConditionVariable;
-import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Messenger;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -56,7 +52,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -97,21 +92,15 @@
private static final long WAIT_TIMEOUT_MS = 500;
private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
- private static final int INVALID_TYPE = -1;
-
- private long mElapsedRealtime;
private HandlerThread mObserverHandlerThread;
- private Handler mObserverNoopHandler;
-
- private LatchedHandler mHandler;
private NetworkStatsObservers mStatsObservers;
- private Messenger mMessenger;
private ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
- @Mock private IBinder mockBinder;
+ @Mock private IBinder mUsageCallbackBinder;
+ private TestableUsageCallback mUsageCallback;
@Before
public void setUp() throws Exception {
@@ -127,24 +116,29 @@
}
};
- mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable());
- mMessenger = new Messenger(mHandler);
-
mActiveIfaces = new ArrayMap<>();
mActiveUidIfaces = new ArrayMap<>();
+ mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
}
@Test
public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
- long thresholdTooLowBytes = 1L;
- DataUsageRequest inputRequest = new DataUsageRequest(
+ final long thresholdTooLowBytes = 1L;
+ final DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateWifi, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+ final DataUsageRequest requestByApp = mStatsObservers.register(inputRequest, mUsageCallback,
+ UID_RED, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(requestByApp.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, requestByApp.template));
+ assertEquals(THRESHOLD_BYTES, requestByApp.thresholdInBytes);
+
+ // Verify the threshold requested by system uid won't be overridden.
+ final DataUsageRequest requestBySystem = mStatsObservers.register(inputRequest,
+ mUsageCallback, Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(requestBySystem.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, requestBySystem.template));
+ assertEquals(1, requestBySystem.thresholdInBytes);
}
@Test
@@ -153,7 +147,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
@@ -165,13 +159,13 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
- DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request1 = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request1.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request1.template));
assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
- DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request2 = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request2.requestId > request1.requestId);
assertTrue(Objects.equals(sTemplateWifi, request2.template));
@@ -191,17 +185,19 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder).linkToDeath(any(IBinder.DeathRecipient.class),
+ anyInt());
mStatsObservers.unregister(request, Process.SYSTEM_UID);
waitForObserverToIdle();
- Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder).unlinkToDeath(any(IBinder.DeathRecipient.class),
+ anyInt());
}
@Test
@@ -209,17 +205,18 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder)
+ .linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
mStatsObservers.unregister(request, UID_BLUE);
waitForObserverToIdle();
- Mockito.verifyZeroInteractions(mockBinder);
+ Mockito.verifyZeroInteractions(mUsageCallbackBinder);
}
private NetworkIdentitySet makeTestIdentSet() {
@@ -236,7 +233,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -260,7 +257,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -290,7 +287,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -313,7 +310,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -321,7 +318,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -346,7 +343,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -354,7 +351,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -386,7 +383,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_BLUE, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -411,7 +408,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -419,7 +416,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -448,6 +445,5 @@
private void waitForObserverToIdle() {
HandlerUtils.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
- HandlerUtils.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
}
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index d7bbf50..ea35c31 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -80,7 +80,6 @@
import android.annotation.NonNull;
import android.app.AlarmManager;
-import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
@@ -101,13 +100,9 @@
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.wifi.WifiInfo;
import android.os.Build;
-import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
import android.os.PowerManager;
import android.os.SimpleClock;
import android.provider.Settings;
@@ -188,10 +183,12 @@
private @Mock TetheringManager mTetheringManager;
private @Mock NetworkStatsFactory mStatsFactory;
private @Mock NetworkStatsSettings mSettings;
- private @Mock IBinder mBinder;
+ private @Mock IBinder mUsageCallbackBinder;
+ private TestableUsageCallback mUsageCallback;
private @Mock AlarmManager mAlarmManager;
@Mock
private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor;
+ private @Mock BpfInterfaceMapUpdater mBpfInterfaceMapUpdater;
private HandlerThread mHandlerThread;
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
@@ -313,6 +310,8 @@
verify(mTetheringManager).registerTetheringEventCallback(
any(), tetheringEventCbCaptor.capture());
mTetheringEventCallback = tetheringEventCbCaptor.getValue();
+
+ mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
}
@NonNull
@@ -342,6 +341,12 @@
public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
return mLocationPermissionChecker;
}
+
+ @Override
+ public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
+ @NonNull Context ctx, @NonNull Handler handler) {
+ return mBpfInterfaceMapUpdater;
+ }
};
}
@@ -1240,20 +1245,14 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdInBytes);
- // Create a messenger that waits for callback activity
- ConditionVariable cv = new ConditionVariable(false);
- LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
- Messenger messenger = new Messenger(latchedHandler);
-
// Force poll
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
// Register and verify request and that binder was called
- DataUsageRequest request =
- mService.registerUsageCallback(mServiceContext.getOpPackageName(), inputRequest,
- messenger, mBinder);
+ DataUsageRequest request = mService.registerUsageCallback(
+ mServiceContext.getOpPackageName(), inputRequest, mUsageCallback);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
@@ -1262,7 +1261,7 @@
HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
// Make sure that the caller binder gets connected
- verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ verify(mUsageCallbackBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
// modify some number on wifi, and trigger poll event
// not enough traffic to call data usage callback
@@ -1277,7 +1276,7 @@
assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
// make sure callback has not being called
- assertEquals(INVALID_TYPE, latchedHandler.lastMessageType);
+ mUsageCallback.assertNoCallback();
// and bump forward again, with counters going higher. this is
// important, since it will trigger the data usage callback
@@ -1292,23 +1291,21 @@
assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
- // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
- assertTrue(cv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.lastMessageType);
- cv.close();
+ // Wait for the caller to invoke expectOnThresholdReached.
+ mUsageCallback.expectOnThresholdReached(request);
// Allow binder to disconnect
- when(mBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt())).thenReturn(true);
+ when(mUsageCallbackBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt()))
+ .thenReturn(true);
// Unregister request
mService.unregisterUsageRequest(request);
- // Wait for the caller to ack receipt of CALLBACK_RELEASED
- assertTrue(cv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.lastMessageType);
+ // Wait for the caller to invoke expectOnCallbackReleased.
+ mUsageCallback.expectOnCallbackReleased(request);
// Make sure that the caller binder gets disconnected
- verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ verify(mUsageCallbackBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
}
@Test
@@ -1884,21 +1881,4 @@
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
}
-
- static class LatchedHandler extends Handler {
- private final ConditionVariable mCv;
- int lastMessageType = INVALID_TYPE;
-
- LatchedHandler(Looper looper, ConditionVariable cv) {
- super(looper);
- mCv = cv;
- }
-
- @Override
- public void handleMessage(Message msg) {
- lastMessageType = msg.what;
- mCv.open();
- super.handleMessage(msg);
- }
- }
}
diff --git a/tests/unit/java/com/android/server/net/TestableUsageCallback.kt b/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
new file mode 100644
index 0000000..1917ec3
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net
+
+import android.net.DataUsageRequest
+import android.net.netstats.IUsageCallback
+import android.os.IBinder
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import kotlin.test.fail
+
+private const val DEFAULT_TIMEOUT_MS = 200L
+
+// TODO: Move the class to static libs once all downstream have IUsageCallback definition.
+class TestableUsageCallback(private val binder: IBinder) : IUsageCallback.Stub() {
+ sealed class CallbackType(val request: DataUsageRequest) {
+ class OnThresholdReached(request: DataUsageRequest) : CallbackType(request)
+ class OnCallbackReleased(request: DataUsageRequest) : CallbackType(request)
+ }
+
+ // TODO: Change to use ArrayTrackRecord once moved into to the module.
+ private val history = LinkedBlockingQueue<CallbackType>()
+
+ override fun onThresholdReached(request: DataUsageRequest) {
+ history.add(CallbackType.OnThresholdReached(request))
+ }
+
+ override fun onCallbackReleased(request: DataUsageRequest) {
+ history.add(CallbackType.OnCallbackReleased(request))
+ }
+
+ fun expectOnThresholdReached(request: DataUsageRequest) {
+ expectCallback<CallbackType.OnThresholdReached>(request, DEFAULT_TIMEOUT_MS)
+ }
+
+ fun expectOnCallbackReleased(request: DataUsageRequest) {
+ expectCallback<CallbackType.OnCallbackReleased>(request, DEFAULT_TIMEOUT_MS)
+ }
+
+ @JvmOverloads
+ fun assertNoCallback(timeout: Long = DEFAULT_TIMEOUT_MS) {
+ val cb = history.poll(timeout, TimeUnit.MILLISECONDS)
+ cb?.let { fail("Expected no callback but got $cb") }
+ }
+
+ // Expects a callback of the specified request on the specified network within the timeout.
+ // If no callback arrives, or a different callback arrives, fail.
+ private inline fun <reified T : CallbackType> expectCallback(
+ expectedRequest: DataUsageRequest,
+ timeoutMs: Long
+ ) {
+ history.poll(timeoutMs, TimeUnit.MILLISECONDS).let {
+ if (it !is T || it.request != expectedRequest) {
+ fail("Unexpected callback : $it," +
+ " expected ${T::class} with Request[$expectedRequest]")
+ } else {
+ it
+ }
+ }
+ }
+
+ override fun asBinder(): IBinder {
+ return binder
+ }
+}
\ No newline at end of file