Merge "Address API review comments for DhcpOption object"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 780ba26..302c0b3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,9 @@
]
},
{
+ "name": "netd_updatable_unit_test"
+ },
+ {
"name": "TetheringTests"
},
{
@@ -40,6 +43,10 @@
"name": "bpf_existence_test"
},
{
+ "name": "netd_updatable_unit_test",
+ "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
+ },
+ {
"name": "libclat_test"
},
{
@@ -63,6 +70,9 @@
]
},
{
+ "name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ },
+ {
"name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index c0e6749..ae96e8c 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -65,6 +65,7 @@
android_library {
name: "TetheringApiCurrentLib",
defaults: [
+ "ConnectivityNextEnableDefaults",
"TetheringAndroidLibraryDefaults",
"TetheringApiLevel"
],
@@ -98,7 +99,6 @@
],
min_sdk_version: "30",
header_libs: [
- "bpf_syscall_wrappers",
"bpf_connectivity_headers",
],
srcs: [
@@ -111,7 +111,6 @@
static_libs: [
"libnet_utils_device_common_bpfjni",
"libnetjniutils",
- "libtcutils",
],
// We cannot use plain "libc++" here to link libc++ dynamically because it results in:
@@ -159,7 +158,7 @@
// Non-updatable tethering running in the system server process for devices not using the module
android_app {
name: "InProcessTethering",
- defaults: ["TetheringAppDefaults", "TetheringApiLevel"],
+ defaults: ["TetheringAppDefaults", "TetheringApiLevel", "ConnectivityNextEnableDefaults"],
static_libs: ["TetheringApiCurrentLib"],
certificate: "platform",
manifest: "AndroidManifest_InProcess.xml",
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 53a14ad..ea3f8d6 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -52,9 +52,9 @@
first: {
jni_libs: [
"libservice-connectivity",
- "libcom_android_connectivity_com_android_net_module_util_jni",
- "libtraffic_controller_jni",
+ "libandroid_net_connectivity_com_android_net_module_util_jni",
],
+ native_shared_libs: ["libnetd_updatable"],
},
both: {
jni_libs: ["libframework-connectivity-jni"],
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
index 06e9617..5a03347 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 0 1000 0750
/bin/for-system/clatd 1029 1029 06755
diff --git a/Tethering/tests/jarjar-rules.txt b/Tethering/tests/jarjar-rules.txt
index 23d3f56..a7c7488 100644
--- a/Tethering/tests/jarjar-rules.txt
+++ b/Tethering/tests/jarjar-rules.txt
@@ -13,6 +13,9 @@
# Classes from net-utils-framework-common
rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1
+# Classes from net-tests-utils
+rule com.android.testutils.TestBpfMap* com.android.networkstack.tethering.testutils.TestBpfMap@1
+
# TODO: either stop using frameworks-base-testutils or remove the unit test classes it contains.
# TestableLooper from "testables" can be used instead of TestLooper from frameworks-base-testutils.
zap android.os.test.TestLooperTest*
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index c5969d2..d51f6fd 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -89,7 +89,6 @@
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
-import android.system.ErrnoException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -100,7 +99,6 @@
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkStackConstants;
-import com.android.net.module.util.Struct;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkSocket;
@@ -110,6 +108,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.TestBpfMap;
import com.android.testutils.TestableNetworkStatsProviderCbBinder;
import org.junit.Before;
@@ -128,10 +127,7 @@
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.function.BiConsumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -157,60 +153,6 @@
UPSTREAM_IFACE, UPSTREAM_IFINDEX, null /* macAddr, rawip */,
NetworkStackConstants.ETHER_MTU);
- // The test fake BPF map class is needed because the test has no privilege to access the BPF
- // map. All member functions which eventually call JNI to access the real native BPF map need
- // to be overridden.
- // TODO: consider moving to an individual file.
- private class TestBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
- private final HashMap<K, V> mMap = new HashMap<K, V>();
-
- TestBpfMap(final Class<K> key, final Class<V> value) {
- super(key, value);
- }
-
- @Override
- public void forEach(BiConsumer<K, V> action) throws ErrnoException {
- // TODO: consider using mocked #getFirstKey and #getNextKey to iterate. It helps to
- // implement the entry deletion in the iteration if required.
- for (Map.Entry<K, V> entry : mMap.entrySet()) {
- action.accept(entry.getKey(), entry.getValue());
- }
- }
-
- @Override
- public void updateEntry(K key, V value) throws ErrnoException {
- mMap.put(key, value);
- }
-
- @Override
- public void insertEntry(K key, V value) throws ErrnoException,
- IllegalArgumentException {
- // The entry is created if and only if it doesn't exist. See BpfMap#insertEntry.
- if (mMap.get(key) != null) {
- throw new IllegalArgumentException(key + " already exist");
- }
- mMap.put(key, value);
- }
-
- @Override
- public boolean deleteEntry(Struct key) throws ErrnoException {
- return mMap.remove(key) != null;
- }
-
- @Override
- public V getValue(@NonNull K key) throws ErrnoException {
- // Return value for a given key. Otherwise, return null without an error ENOENT.
- // BpfMap#getValue treats that the entry is not found as no error.
- return mMap.get(key);
- }
-
- @Override
- public void clear() throws ErrnoException {
- // TODO: consider using mocked #getFirstKey and #deleteEntry to implement.
- mMap.clear();
- }
- };
-
@Mock private NetworkStatsManager mStatsManager;
@Mock private INetd mNetd;
@Mock private IpServer mIpServer;
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index cce2b71..6718402 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/netd",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service/native/libs/libclat",
"//packages/modules/Connectivity/Tethering",
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index f0df97b..2ddc7b8 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -130,7 +130,8 @@
STANDBY_MATCH = (1 << 3),
POWERSAVE_MATCH = (1 << 4),
RESTRICTED_MATCH = (1 << 5),
- IIF_MATCH = (1 << 6),
+ LOW_POWER_STANDBY_MATCH = (1 << 6),
+ IIF_MATCH = (1 << 7),
};
enum BpfPermissionMatch {
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index f0af8b4..c1a74e7 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -210,6 +210,9 @@
if ((enabledRules & RESTRICTED_MATCH) && !(uidRules & RESTRICTED_MATCH)) {
return BPF_DROP;
}
+ if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
+ return BPF_DROP;
+ }
}
if (direction == BPF_INGRESS && (uidRules & IIF_MATCH)) {
// Drops packets not coming from lo nor the allowlisted interface
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index abcfbeb..b5aedeb 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -26,7 +26,9 @@
// The above defaults can be used to disable framework-connectivity t
// targets while minimizing merge conflicts in the build rules.
-
+// SDK library for connectivity bootclasspath classes that were part of the non-updatable API before
+// T, and were moved to the module in T. Other bootclasspath classes in connectivity should go to
+// framework-connectivity.
java_sdk_library {
name: "framework-connectivity-tiramisu",
sdk_version: "module_current",
@@ -37,14 +39,40 @@
],
srcs: [
":framework-connectivity-tiramisu-updatable-sources",
+ ":framework-nearby-java-sources",
+ ],
+ // Do not add static_libs to this library: put them in framework-connectivity instead.
+ // The jarjar rules are only so that references to jarjared utils in
+ // framework-connectivity-pre-jarjar match at runtime.
+ jarjar_rules: ":connectivity-jarjar-rules",
+ stub_only_libs: [
+ // Use prebuilt framework-connectivity stubs to avoid circular dependencies
+ "sdk_module-lib_current_framework-connectivity",
],
libs: [
"unsupportedappusage",
"app-compat-annotations",
+ "sdk_module-lib_current_framework-connectivity",
+ ],
+ impl_only_libs: [
+ // The build system will use framework-bluetooth module_current stubs, because
+ // of sdk_version: "module_current" above.
+ "framework-bluetooth",
+ // Compile against the entire implementation of framework-connectivity,
+ // including hidden methods. This is safe because if framework-connectivity-t is
+ // on the bootclasspath (i.e., T), then framework-connectivity is also on the
+ // bootclasspath (because it shipped in S).
+ //
+ // This compiles against the pre-jarjar target so that this code can use
+ // non-jarjard names of widely-used packages such as com.android.net.module.util.
+ "framework-connectivity-pre-jarjar",
],
permitted_packages: [
"android.net",
"android.net.nsd",
+ "android.nearby",
+ "com.android.connectivity",
+ "com.android.nearby",
],
apex_available: [
"com.android.tethering",
@@ -54,10 +82,12 @@
// In preparation for future move
"//packages/modules/Connectivity/apex",
"//packages/modules/Connectivity/service-t",
+ "//packages/modules/Nearby/service",
"//frameworks/base",
// Tests using hidden APIs
"//cts/tests/netlegacy22.api",
+ "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
"//external/sl4a:__subpackages__",
"//frameworks/libs/net/common/testutils",
"//frameworks/libs/net/common/tests:__subpackages__",
@@ -66,6 +96,7 @@
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/NetworkStack/tests:__subpackages__",
+ "//packages/modules/Nearby/tests:__subpackages__",
"//packages/modules/Wifi/service/tests/wifitests",
],
}
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 0443456..7977cc5 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -3,6 +3,8 @@
public final class NsdManager {
method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
+ method public void discoverServices(@NonNull String, int, @Nullable android.net.Network, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
@@ -43,12 +45,14 @@
method public int describeContents();
method public java.util.Map<java.lang.String,byte[]> getAttributes();
method public java.net.InetAddress getHost();
+ method @Nullable public android.net.Network getNetwork();
method public int getPort();
method public String getServiceName();
method public String getServiceType();
method public void removeAttribute(String);
method public void setAttribute(String, String);
method public void setHost(java.net.InetAddress);
+ method public void setNetwork(@Nullable android.net.Network);
method public void setPort(int);
method public void setServiceName(String);
method public void setServiceType(String);
diff --git a/framework/Android.bp b/framework/Android.bp
index de505c7..d3e46fa 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -55,12 +55,11 @@
],
}
-java_sdk_library {
- name: "framework-connectivity",
+java_defaults {
+ name: "framework-connectivity-defaults",
+ defaults: ["framework-module-defaults"],
sdk_version: "module_current",
min_sdk_version: "30",
- defaults: ["framework-module-defaults"],
- installable: true,
srcs: [
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
@@ -76,6 +75,9 @@
"frameworks/native/aidl/binder", // For PersistableBundle.aidl
],
},
+ stub_only_libs: [
+ "framework-connectivity-tiramisu.stubs.module_lib",
+ ],
impl_only_libs: [
"framework-tethering.stubs.module_lib",
"framework-wifi.stubs.module_lib",
@@ -83,16 +85,42 @@
],
static_libs: [
"modules-utils-build",
+ "modules-utils-preconditions",
],
libs: [
+ "framework-connectivity-tiramisu.stubs.module_lib",
"unsupportedappusage",
],
- jarjar_rules: "jarjar-rules.txt",
+ apex_available: [
+ "com.android.tethering",
+ ],
+ lint: { strict_updatability_linting: true },
+}
+
+java_library {
+ name: "framework-connectivity-pre-jarjar",
+ defaults: ["framework-connectivity-defaults"],
+ libs: [
+ // This cannot be in the defaults clause above because if it were, it would be used
+ // to generate the connectivity stubs. That would create a circular dependency
+ // because the tethering stubs depend on the connectivity stubs (e.g.,
+ // TetheringRequest depends on LinkAddress).
+ "framework-tethering.stubs.module_lib",
+ ],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"]
+}
+
+java_sdk_library {
+ name: "framework-connectivity",
+ defaults: ["framework-connectivity-defaults"],
+ installable: true,
+ jarjar_rules: ":connectivity-jarjar-rules",
permitted_packages: ["android.net"],
impl_library_visibility: [
"//packages/modules/Connectivity/Tethering/apex",
// In preparation for future move
"//packages/modules/Connectivity/apex",
+ "//packages/modules/Connectivity/framework-t",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service-t",
"//frameworks/base/packages/Connectivity/service",
@@ -100,6 +128,7 @@
// Tests using hidden APIs
"//cts/tests/netlegacy22.api",
+ "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
"//external/sl4a:__subpackages__",
"//frameworks/base/packages/Connectivity/tests:__subpackages__",
"//frameworks/libs/net/common/testutils",
@@ -111,10 +140,6 @@
"//packages/modules/NetworkStack/tests:__subpackages__",
"//packages/modules/Wifi/service/tests/wifitests",
],
- apex_available: [
- "com.android.tethering",
- ],
- lint: { strict_updatability_linting: true },
}
cc_library_shared {
diff --git a/framework/api/current.txt b/framework/api/current.txt
index a373b71..547b7e2 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -205,6 +205,21 @@
method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String);
}
+ public final class IpConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.net.ProxyInfo getHttpProxy();
+ method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
+ }
+
+ public static final class IpConfiguration.Builder {
+ ctor public IpConfiguration.Builder();
+ method @NonNull public android.net.IpConfiguration build();
+ method @NonNull public android.net.IpConfiguration.Builder setHttpProxy(@Nullable android.net.ProxyInfo);
+ method @NonNull public android.net.IpConfiguration.Builder setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+ }
+
public final class IpPrefix implements android.os.Parcelable {
ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
method public boolean contains(@NonNull java.net.InetAddress);
@@ -485,6 +500,25 @@
method public void onStopped();
}
+ public final class StaticIpConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
+ method @Nullable public String getDomains();
+ method @Nullable public java.net.InetAddress getGateway();
+ method @NonNull public android.net.LinkAddress getIpAddress();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+ }
+
+ public static final class StaticIpConfiguration.Builder {
+ ctor public StaticIpConfiguration.Builder();
+ method @NonNull public android.net.StaticIpConfiguration build();
+ method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
+ method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
+ method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
+ method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@NonNull android.net.LinkAddress);
+ }
+
public interface TransportInfo {
}
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 8da421d..5961e72 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -43,9 +43,11 @@
field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
+ field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20
field public static final int BLOCKED_REASON_NONE = 0; // 0x0
field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
+ field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5
field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3
field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
@@ -132,6 +134,7 @@
}
public final class NetworkCapabilities implements android.os.Parcelable {
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public java.util.Set<java.lang.Integer> getAccessUids();
method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
method public boolean hasForbiddenCapability(int);
field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
@@ -143,6 +146,7 @@
}
public static final class NetworkCapabilities.Builder {
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAccessUids(@NonNull java.util.Set<java.lang.Integer>);
method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
}
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index d420958..764cffa 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -133,17 +133,12 @@
public final class IpConfiguration implements android.os.Parcelable {
ctor public IpConfiguration();
ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
- method public int describeContents();
- method @Nullable public android.net.ProxyInfo getHttpProxy();
method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment();
method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings();
- method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration();
method public void setHttpProxy(@Nullable android.net.ProxyInfo);
method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment);
method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings);
method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
}
public enum IpConfiguration.IpAssignment {
@@ -484,23 +479,7 @@
ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
method public void addDnsServer(@NonNull java.net.InetAddress);
method public void clear();
- method public int describeContents();
- method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
- method @Nullable public String getDomains();
- method @Nullable public java.net.InetAddress getGateway();
- method @Nullable public android.net.LinkAddress getIpAddress();
method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String);
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
- }
-
- public static final class StaticIpConfiguration.Builder {
- ctor public StaticIpConfiguration.Builder();
- method @NonNull public android.net.StaticIpConfiguration build();
- method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
- method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
- method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
- method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress);
}
public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
deleted file mode 100644
index ae6b9c3..0000000
--- a/framework/jarjar-rules.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
-rule com.android.modules.utils.** android.net.connectivity.framework.modules.utils.@1
-rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 7593482..5246623 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -877,6 +877,15 @@
public static final int BLOCKED_REASON_LOCKDOWN_VPN = 1 << 4;
/**
+ * Flag to indicate that an app is subject to Low Power Standby restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 1 << 5;
+
+ /**
* Flag to indicate that an app is subject to Data saver restrictions that would
* result in its metered network access being blocked.
*
@@ -914,6 +923,7 @@
BLOCKED_REASON_APP_STANDBY,
BLOCKED_REASON_RESTRICTED_MODE,
BLOCKED_REASON_LOCKDOWN_VPN,
+ BLOCKED_REASON_LOW_POWER_STANDBY,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -931,6 +941,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
+ // LINT.IfChange(firewall_chain)
/**
* Firewall chain for device idle (doze mode).
* Allowlist of apps that have network access in device idle.
@@ -963,15 +974,25 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_RESTRICTED = 4;
+ /**
+ * Firewall chain used for low power standby.
+ * Allowlist of apps that have access in low power standby.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
FIREWALL_CHAIN_POWERSAVE,
- FIREWALL_CHAIN_RESTRICTED
+ FIREWALL_CHAIN_RESTRICTED,
+ FIREWALL_CHAIN_LOW_POWER_STANDBY
})
public @interface FirewallChain {}
+ // LINT.ThenChange(packages/modules/Connectivity/service/native/include/Common.h)
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
diff --git a/framework/src/android/net/IpConfiguration.java b/framework/src/android/net/IpConfiguration.java
index d5f8b2e..99835aa 100644
--- a/framework/src/android/net/IpConfiguration.java
+++ b/framework/src/android/net/IpConfiguration.java
@@ -28,16 +28,16 @@
import java.util.Objects;
/**
- * A class representing a configured network.
- * @hide
+ * A class representing the IP configuration of a network.
*/
-@SystemApi
public final class IpConfiguration implements Parcelable {
private static final String TAG = "IpConfiguration";
// This enum has been used by apps through reflection for many releases.
// Therefore they can't just be removed. Duplicating these constants to
// give an alternate SystemApi is a worse option than exposing them.
+ /** @hide */
+ @SystemApi
@SuppressLint("Enum")
public enum IpAssignment {
/* Use statically configured IP settings. Configuration can be accessed
@@ -59,6 +59,8 @@
// This enum has been used by apps through reflection for many releases.
// Therefore they can't just be removed. Duplicating these constants to
// give an alternate SystemApi is a worse option than exposing them.
+ /** @hide */
+ @SystemApi
@SuppressLint("Enum")
public enum ProxySettings {
/* No proxy is to be used. Any existing proxy settings
@@ -94,6 +96,8 @@
null : new ProxyInfo(httpProxy);
}
+ /** @hide */
+ @SystemApi
public IpConfiguration() {
init(IpAssignment.UNASSIGNED, ProxySettings.UNASSIGNED, null, null);
}
@@ -107,6 +111,8 @@
init(ipAssignment, proxySettings, staticIpConfiguration, httpProxy);
}
+ /** @hide */
+ @SystemApi
public IpConfiguration(@NonNull IpConfiguration source) {
this();
if (source != null) {
@@ -115,34 +121,58 @@
}
}
+ /** @hide */
+ @SystemApi
public @NonNull IpAssignment getIpAssignment() {
return ipAssignment;
}
+ /** @hide */
+ @SystemApi
public void setIpAssignment(@NonNull IpAssignment ipAssignment) {
this.ipAssignment = ipAssignment;
}
+ /**
+ * Get the current static IP configuration (possibly null). Configured via
+ * {@link Builder#setStaticIpConfiguration(StaticIpConfiguration)}.
+ *
+ * @return Current static IP configuration.
+ */
public @Nullable StaticIpConfiguration getStaticIpConfiguration() {
return staticIpConfiguration;
}
+ /** @hide */
+ @SystemApi
public void setStaticIpConfiguration(@Nullable StaticIpConfiguration staticIpConfiguration) {
this.staticIpConfiguration = staticIpConfiguration;
}
+ /** @hide */
+ @SystemApi
public @NonNull ProxySettings getProxySettings() {
return proxySettings;
}
+ /** @hide */
+ @SystemApi
public void setProxySettings(@NonNull ProxySettings proxySettings) {
this.proxySettings = proxySettings;
}
+ /**
+ * The proxy configuration of this object.
+ *
+ * @return The proxy information of this object configured via
+ * {@link Builder#setHttpProxy(ProxyInfo)}.
+ */
public @Nullable ProxyInfo getHttpProxy() {
return httpProxy;
}
+ /** @hide */
+ @SystemApi
public void setHttpProxy(@Nullable ProxyInfo httpProxy) {
this.httpProxy = httpProxy;
}
@@ -220,4 +250,56 @@
return new IpConfiguration[size];
}
};
+
+ /**
+ * Builder used to construct {@link IpConfiguration} objects.
+ */
+ public static final class Builder {
+ private StaticIpConfiguration mStaticIpConfiguration;
+ private ProxyInfo mProxyInfo;
+
+ /**
+ * Set a static IP configuration.
+ *
+ * @param config Static IP configuration.
+ * @return A {@link Builder} object to allow chaining.
+ */
+ public @NonNull Builder setStaticIpConfiguration(@Nullable StaticIpConfiguration config) {
+ mStaticIpConfiguration = config;
+ return this;
+ }
+
+ /**
+ * Set a proxy configuration.
+ *
+ * @param proxyInfo Proxy configuration.
+ * @return A {@link Builder} object to allow chaining.
+ */
+ public @NonNull Builder setHttpProxy(@Nullable ProxyInfo proxyInfo) {
+ mProxyInfo = proxyInfo;
+ return this;
+ }
+
+ /**
+ * Construct an {@link IpConfiguration}.
+ *
+ * @return A new {@link IpConfiguration} object.
+ */
+ public @NonNull IpConfiguration build() {
+ IpConfiguration config = new IpConfiguration();
+ config.setStaticIpConfiguration(mStaticIpConfiguration);
+ config.setIpAssignment(
+ mStaticIpConfiguration == null ? IpAssignment.DHCP : IpAssignment.STATIC);
+
+ config.setHttpProxy(mProxyInfo);
+ if (mProxyInfo == null) {
+ config.setProxySettings(ProxySettings.NONE);
+ } else {
+ config.setProxySettings(
+ mProxyInfo.getPacFileUrl() == null ? ProxySettings.STATIC
+ : ProxySettings.PAC);
+ }
+ return config;
+ }
+ }
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 4ae3a06..b6cd760 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -264,6 +264,7 @@
mTransportInfo = null;
mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
mUids = null;
+ mAccessUids.clear();
mAdministratorUids = new int[0];
mOwnerUid = Process.INVALID_UID;
mSSID = null;
@@ -294,6 +295,7 @@
}
mSignalStrength = nc.mSignalStrength;
mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids);
+ setAccessUids(nc.mAccessUids);
setAdministratorUids(nc.getAdministratorUids());
mOwnerUid = nc.mOwnerUid;
mForbiddenNetworkCapabilities = nc.mForbiddenNetworkCapabilities;
@@ -619,22 +621,22 @@
* Network capabilities that are expected to be mutable, i.e., can change while a particular
* network is connected.
*/
- private static final long MUTABLE_CAPABILITIES =
+ private static final long MUTABLE_CAPABILITIES = NetworkCapabilitiesUtils.packBitList(
// TRUSTED can change when user explicitly connects to an untrusted network in Settings.
// http://b/18206275
- (1 << NET_CAPABILITY_TRUSTED)
- | (1 << NET_CAPABILITY_VALIDATED)
- | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
- | (1 << NET_CAPABILITY_NOT_ROAMING)
- | (1 << NET_CAPABILITY_FOREGROUND)
- | (1 << NET_CAPABILITY_NOT_CONGESTED)
- | (1 << NET_CAPABILITY_NOT_SUSPENDED)
- | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY)
- | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
- | (1 << NET_CAPABILITY_NOT_VCN_MANAGED)
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_NOT_ROAMING,
+ NET_CAPABILITY_FOREGROUND,
+ NET_CAPABILITY_NOT_CONGESTED,
+ NET_CAPABILITY_NOT_SUSPENDED,
+ NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+ NET_CAPABILITY_NOT_VCN_MANAGED,
// The value of NET_CAPABILITY_HEAD_UNIT is 32, which cannot use int to do bit shift,
// otherwise there will be an overflow. Use long to do bit shift instead.
- | (1L << NET_CAPABILITY_HEAD_UNIT);
+ NET_CAPABILITY_HEAD_UNIT);
/**
* Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -648,25 +650,26 @@
// in an infinite loop about these.
private static final long NON_REQUESTABLE_CAPABILITIES =
MUTABLE_CAPABILITIES
- & ~(1 << NET_CAPABILITY_TRUSTED)
- & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+ & ~(1L << NET_CAPABILITY_TRUSTED)
+ & ~(1L << NET_CAPABILITY_NOT_VCN_MANAGED);
/**
* Capabilities that are set by default when the object is constructed.
*/
- private static final long DEFAULT_CAPABILITIES =
- (1 << NET_CAPABILITY_NOT_RESTRICTED)
- | (1 << NET_CAPABILITY_TRUSTED)
- | (1 << NET_CAPABILITY_NOT_VPN);
+ private static final long DEFAULT_CAPABILITIES = NetworkCapabilitiesUtils.packBitList(
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_NOT_VPN);
/**
* Capabilities that are managed by ConnectivityService.
*/
private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
- (1 << NET_CAPABILITY_VALIDATED)
- | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
- | (1 << NET_CAPABILITY_FOREGROUND)
- | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ NetworkCapabilitiesUtils.packBitList(
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_FOREGROUND,
+ NET_CAPABILITY_PARTIAL_CONNECTIVITY);
/**
* Capabilities that are allowed for test networks. This list must be set so that it is safe
@@ -675,14 +678,15 @@
* INTERNET, IMS, SUPL, etc.
*/
private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
- (1 << NET_CAPABILITY_NOT_METERED)
- | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
- | (1 << NET_CAPABILITY_NOT_RESTRICTED)
- | (1 << NET_CAPABILITY_NOT_VPN)
- | (1 << NET_CAPABILITY_NOT_ROAMING)
- | (1 << NET_CAPABILITY_NOT_CONGESTED)
- | (1 << NET_CAPABILITY_NOT_SUSPENDED)
- | (1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+ NetworkCapabilitiesUtils.packBitList(
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_NOT_ROAMING,
+ NET_CAPABILITY_NOT_CONGESTED,
+ NET_CAPABILITY_NOT_SUSPENDED,
+ NET_CAPABILITY_NOT_VCN_MANAGED);
/**
* Adds the given capability to this {@code NetworkCapability} instance.
@@ -1025,6 +1029,7 @@
final int[] originalAdministratorUids = getAdministratorUids();
final TransportInfo originalTransportInfo = getTransportInfo();
final Set<Integer> originalSubIds = getSubscriptionIds();
+ final Set<Integer> originalAccessUids = new ArraySet<>(mAccessUids);
clearAll();
if (0 != (originalCapabilities & (1 << NET_CAPABILITY_NOT_RESTRICTED))) {
// If the test network is not restricted, then it is only allowed to declare some
@@ -1044,6 +1049,7 @@
mNetworkSpecifier = originalSpecifier;
mSignalStrength = originalSignalStrength;
mTransportInfo = originalTransportInfo;
+ mAccessUids.addAll(originalAccessUids);
// Only retain the owner and administrator UIDs if they match the app registering the remote
// caller that registered the network.
@@ -1152,12 +1158,13 @@
/**
* Allowed transports on an unrestricted test network (in addition to TRANSPORT_TEST).
*/
- private static final int UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
- 1 << TRANSPORT_TEST
- // Test ethernet networks can be created with EthernetManager#setIncludeTestInterfaces
- | 1 << TRANSPORT_ETHERNET
- // Test VPN networks can be created but their UID ranges must be empty.
- | 1 << TRANSPORT_VPN;
+ private static final long UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
+ NetworkCapabilitiesUtils.packBitList(
+ TRANSPORT_TEST,
+ // Test eth networks are created with EthernetManager#setIncludeTestInterfaces
+ TRANSPORT_ETHERNET,
+ // Test VPN networks can be created but their UID ranges must be empty.
+ TRANSPORT_VPN);
/**
* Adds the given transport type to this {@code NetworkCapability} instance.
@@ -1608,8 +1615,8 @@
* <p>
* Note that when used to register a network callback, this specifies the minimum acceptable
* signal strength. When received as the state of an existing network it specifies the current
- * value. A value of code SIGNAL_STRENGTH_UNSPECIFIED} means no value when received and has no
- * effect when requesting a callback.
+ * value. A value of {@link #SIGNAL_STRENGTH_UNSPECIFIED} means no value when received and has
+ * no effect when requesting a callback.
*
* @param signalStrength the bearer-specific signal strength.
* @hide
@@ -1809,6 +1816,86 @@
}
/**
+ * List of UIDs that can always access this network.
+ * <p>
+ * UIDs in this list have access to this network, even if the network doesn't have the
+ * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+ * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+ * This is only useful for restricted networks. For non-restricted networks it has no effect.
+ * <p>
+ * This is disallowed in {@link NetworkRequest}, and can only be set by network agents. Network
+ * agents also have restrictions on how they can set these ; they can only back a public
+ * Android API. As such, Ethernet agents can set this when backing the per-UID access API, and
+ * Telephony can set exactly one UID which has to match the manager app for the associated
+ * subscription. Failure to comply with these rules will see this member cleared.
+ * <p>
+ * This member is never null, but can be empty.
+ * @hide
+ */
+ @NonNull
+ private final ArraySet<Integer> mAccessUids = new ArraySet<>();
+
+ /**
+ * Set the list of UIDs that can always access this network.
+ * @param uids
+ * @hide
+ */
+ public void setAccessUids(@NonNull final Set<Integer> uids) {
+ // could happen with nc.set(nc), cheaper than always making a defensive copy
+ if (uids == mAccessUids) return;
+
+ Objects.requireNonNull(uids);
+ mAccessUids.clear();
+ mAccessUids.addAll(uids);
+ }
+
+ /**
+ * The list of UIDs that can always access this network.
+ *
+ * The UIDs in this list can always access this network, even if it is restricted and
+ * the UID doesn't hold the USE_RESTRICTED_NETWORKS permission. This is defined by the
+ * network agent in charge of creating the network.
+ *
+ * The UIDs are only visible to network factories and the system server, since the system
+ * server makes sure to redact them before sending a NetworkCapabilities to a process
+ * that doesn't hold the permission.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public @NonNull Set<Integer> getAccessUids() {
+ return new ArraySet<>(mAccessUids);
+ }
+
+ /** @hide */
+ // For internal clients that know what they are doing and need to avoid the performance hit
+ // of the defensive copy.
+ public @NonNull ArraySet<Integer> getAccessUidsNoCopy() {
+ return mAccessUids;
+ }
+
+ /**
+ * Test whether this UID has special permission to access this network, as per mAccessUids.
+ * @hide
+ */
+ public boolean isAccessUid(int uid) {
+ return mAccessUids.contains(uid);
+ }
+
+ /**
+ * @return whether any UID is in the list of access UIDs
+ * @hide
+ */
+ public boolean hasAccessUids() {
+ return !mAccessUids.isEmpty();
+ }
+
+ private boolean equalsAccessUids(@NonNull NetworkCapabilities other) {
+ return mAccessUids.equals(other.mAccessUids);
+ }
+
+ /**
* The SSID of the network, or null if not applicable or unknown.
* <p>
* This is filled in by wifi code.
@@ -1962,6 +2049,7 @@
&& equalsSpecifier(that)
&& equalsTransportInfo(that)
&& equalsUids(that)
+ && equalsAccessUids(that)
&& equalsSSID(that)
&& equalsOwnerUid(that)
&& equalsPrivateDnsBroken(that)
@@ -1986,15 +2074,16 @@
+ mSignalStrength * 29
+ mOwnerUid * 31
+ Objects.hashCode(mUids) * 37
- + Objects.hashCode(mSSID) * 41
- + Objects.hashCode(mTransportInfo) * 43
- + Objects.hashCode(mPrivateDnsBroken) * 47
- + Objects.hashCode(mRequestorUid) * 53
- + Objects.hashCode(mRequestorPackageName) * 59
- + Arrays.hashCode(mAdministratorUids) * 61
- + Objects.hashCode(mSubIds) * 67
- + Objects.hashCode(mUnderlyingNetworks) * 71
- + mEnterpriseId * 73;
+ + Objects.hashCode(mAccessUids) * 41
+ + Objects.hashCode(mSSID) * 43
+ + Objects.hashCode(mTransportInfo) * 47
+ + Objects.hashCode(mPrivateDnsBroken) * 53
+ + Objects.hashCode(mRequestorUid) * 59
+ + Objects.hashCode(mRequestorPackageName) * 61
+ + Arrays.hashCode(mAdministratorUids) * 67
+ + Objects.hashCode(mSubIds) * 71
+ + Objects.hashCode(mUnderlyingNetworks) * 73
+ + mEnterpriseId * 79;
}
@Override
@@ -2022,6 +2111,7 @@
dest.writeParcelable((Parcelable) mTransportInfo, flags);
dest.writeInt(mSignalStrength);
writeParcelableArraySet(dest, mUids, flags);
+ dest.writeIntArray(CollectionUtils.toIntArray(mAccessUids));
dest.writeString(mSSID);
dest.writeBoolean(mPrivateDnsBroken);
dest.writeIntArray(getAdministratorUids());
@@ -2034,7 +2124,7 @@
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
- new Creator<NetworkCapabilities>() {
+ new Creator<>() {
@Override
public NetworkCapabilities createFromParcel(Parcel in) {
NetworkCapabilities netCap = new NetworkCapabilities();
@@ -2048,6 +2138,11 @@
netCap.mTransportInfo = in.readParcelable(null);
netCap.mSignalStrength = in.readInt();
netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */);
+ final int[] accessUids = in.createIntArray();
+ netCap.mAccessUids.ensureCapacity(accessUids.length);
+ for (int uid : accessUids) {
+ netCap.mAccessUids.add(uid);
+ }
netCap.mSSID = in.readString();
netCap.mPrivateDnsBroken = in.readBoolean();
netCap.setAdministratorUids(in.createIntArray());
@@ -2124,6 +2219,11 @@
sb.append(" Uids: <").append(mUids).append(">");
}
}
+
+ if (hasAccessUids()) {
+ sb.append(" AccessUids: <").append(mAccessUids).append(">");
+ }
+
if (mOwnerUid != Process.INVALID_UID) {
sb.append(" OwnerUid: ").append(mOwnerUid);
}
@@ -2300,7 +2400,7 @@
private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
if (!isValidCapability(capability)) {
- throw new IllegalArgumentException("NetworkCapability " + capability + "out of range");
+ throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
}
}
@@ -2909,6 +3009,44 @@
}
/**
+ * Set a list of UIDs that can always access this network
+ * <p>
+ * Provide a list of UIDs that can access this network even if the network doesn't have the
+ * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+ * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+ * <p>
+ * This is disallowed in {@link NetworkRequest}, and can only be set by
+ * {@link NetworkAgent}s, who hold the
+ * {@link android.Manifest.permission.NETWORK_FACTORY} permission.
+ * Network agents also have restrictions on how they can set these ; they can only back
+ * a public Android API. As such, Ethernet agents can set this when backing the per-UID
+ * access API, and Telephony can set exactly one UID which has to match the manager app for
+ * the associated subscription. Failure to comply with these rules will see this member
+ * cleared.
+ * <p>
+ * These UIDs are only visible to network factories and the system server, since the system
+ * server makes sure to redact them before sending a {@link NetworkCapabilities} instance
+ * to a process that doesn't hold the {@link android.Manifest.permission.NETWORK_FACTORY}
+ * permission.
+ * <p>
+ * This list cannot be null, but it can be empty to mean that no UID without the
+ * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission
+ * gets to access this network.
+ *
+ * @param uids the list of UIDs that can always access this network
+ * @return this builder
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public Builder setAccessUids(@NonNull Set<Integer> uids) {
+ Objects.requireNonNull(uids);
+ mCaps.setAccessUids(uids);
+ return this;
+ }
+
+ /**
* Set the underlying networks of this network.
*
* @param networks The underlying networks of this network.
@@ -2941,4 +3079,4 @@
return new NetworkCapabilities(mCaps);
}
}
-}
+}
\ No newline at end of file
diff --git a/framework/src/android/net/StaticIpConfiguration.java b/framework/src/android/net/StaticIpConfiguration.java
index 7904f7a..194cffd 100644
--- a/framework/src/android/net/StaticIpConfiguration.java
+++ b/framework/src/android/net/StaticIpConfiguration.java
@@ -26,6 +26,7 @@
import com.android.net.module.util.InetAddressUtils;
+import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
@@ -33,24 +34,7 @@
/**
* Class that describes static IP configuration.
- *
- * <p>This class is different from {@link LinkProperties} because it represents
- * configuration intent. The general contract is that if we can represent
- * a configuration here, then we should be able to configure it on a network.
- * The intent is that it closely match the UI we have for configuring networks.
- *
- * <p>In contrast, {@link LinkProperties} represents current state. It is much more
- * expressive. For example, it supports multiple IP addresses, multiple routes,
- * stacked interfaces, and so on. Because LinkProperties is so expressive,
- * using it to represent configuration intent as well as current state causes
- * problems. For example, we could unknowingly save a configuration that we are
- * not in fact capable of applying, or we could save a configuration that the
- * UI cannot display, which has the potential for malicious code to hide
- * hostile or unexpected configuration from the user.
- *
- * @hide
*/
-@SystemApi
public final class StaticIpConfiguration implements Parcelable {
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -69,10 +53,14 @@
@Nullable
public String domains;
+ /** @hide */
+ @SystemApi
public StaticIpConfiguration() {
dnsServers = new ArrayList<>();
}
+ /** @hide */
+ @SystemApi
public StaticIpConfiguration(@Nullable StaticIpConfiguration source) {
this();
if (source != null) {
@@ -84,6 +72,8 @@
}
}
+ /** @hide */
+ @SystemApi
public void clear() {
ipAddress = null;
gateway = null;
@@ -94,7 +84,7 @@
/**
* Get the static IP address included in the configuration.
*/
- public @Nullable LinkAddress getIpAddress() {
+ public @NonNull LinkAddress getIpAddress() {
return ipAddress;
}
@@ -130,10 +120,15 @@
private String mDomains;
/**
- * Set the IP address to be included in the configuration; null by default.
+ * Set the IP address to be included in the configuration.
+ *
* @return The {@link Builder} for chaining.
*/
- public @NonNull Builder setIpAddress(@Nullable LinkAddress ipAddress) {
+ public @NonNull Builder setIpAddress(@NonNull LinkAddress ipAddress) {
+ if (ipAddress != null && !(ipAddress.getAddress() instanceof Inet4Address)) {
+ throw new IllegalArgumentException(
+ "Only IPv4 addresses can be used for the IP configuration");
+ }
mIpAddress = ipAddress;
return this;
}
@@ -143,6 +138,10 @@
* @return The {@link Builder} for chaining.
*/
public @NonNull Builder setGateway(@Nullable InetAddress gateway) {
+ if (gateway != null && !(gateway instanceof Inet4Address)) {
+ throw new IllegalArgumentException(
+ "Only IPv4 addresses can be used for the gateway configuration");
+ }
mGateway = gateway;
return this;
}
@@ -153,6 +152,12 @@
*/
public @NonNull Builder setDnsServers(@NonNull Iterable<InetAddress> dnsServers) {
Objects.requireNonNull(dnsServers);
+ for (InetAddress inetAddress: dnsServers) {
+ if (!(inetAddress instanceof Inet4Address)) {
+ throw new IllegalArgumentException(
+ "Only IPv4 addresses can be used for the DNS server configuration");
+ }
+ }
mDnsServers = dnsServers;
return this;
}
@@ -171,6 +176,8 @@
/**
* Create a {@link StaticIpConfiguration} from the parameters in this {@link Builder}.
* @return The newly created StaticIpConfiguration.
+ * @throws IllegalArgumentException if an invalid configuration is attempted, e.g.
+ * if an IP Address was not configured via {@link #setIpAddress(LinkAddress)}.
*/
public @NonNull StaticIpConfiguration build() {
final StaticIpConfiguration config = new StaticIpConfiguration();
@@ -188,7 +195,9 @@
/**
* Add a DNS server to this configuration.
+ * @hide
*/
+ @SystemApi
public void addDnsServer(@NonNull InetAddress server) {
dnsServers.add(server);
}
@@ -197,7 +206,9 @@
* Returns the network routes specified by this object. Will typically include a
* directly-connected route for the IP address's local subnet and a default route.
* @param iface Interface to include in the routes.
+ * @hide
*/
+ @SystemApi
public @NonNull List<RouteInfo> getRoutes(@Nullable String iface) {
List<RouteInfo> routes = new ArrayList<RouteInfo>(3);
if (ipAddress != null) {
@@ -305,7 +316,7 @@
/** Implement the Parcelable interface */
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(ipAddress, flags);
InetAddressUtils.parcelInetAddress(dest, gateway, flags);
dest.writeInt(dnsServers.size());
@@ -316,7 +327,7 @@
}
/** @hide */
- public static StaticIpConfiguration readFromParcel(Parcel in) {
+ public static @NonNull StaticIpConfiguration readFromParcel(Parcel in) {
final StaticIpConfiguration s = new StaticIpConfiguration();
s.ipAddress = in.readParcelable(null);
s.gateway = InetAddressUtils.unparcelInetAddress(in);
diff --git a/nearby/Android.bp b/nearby/Android.bp
new file mode 100644
index 0000000..baa0740
--- /dev/null
+++ b/nearby/Android.bp
@@ -0,0 +1,39 @@
+//
+// 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 {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Empty sources and libraries to avoid merge conflicts with downstream
+// branches
+// TODO: remove once the Nearby sources are available in this branch
+filegroup {
+ name: "framework-nearby-java-sources",
+ srcs: [],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+
+java_library {
+ name: "service-nearby",
+ srcs: [],
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ apex_available: ["com.android.tethering"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/netd/Android.bp b/netd/Android.bp
new file mode 100644
index 0000000..b98a859
--- /dev/null
+++ b/netd/Android.bp
@@ -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.
+
+cc_library {
+ name: "libnetd_updatable",
+ version_script: "libnetd_updatable.map.txt",
+ stubs: {
+ versions: [
+ "1",
+ ],
+ symbol_file: "libnetd_updatable.map.txt",
+ },
+ defaults: ["netd_defaults"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "libcutils_headers",
+ ],
+ srcs: [
+ "BpfHandler.cpp",
+ "NetdUpdatable.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libnetdutils",
+ ],
+ export_include_dirs: ["include"],
+ header_abi_checker: {
+ enabled: true,
+ symbol_file: "libnetd_updatable.map.txt",
+ },
+ sanitize: {
+ cfi: true,
+ },
+ apex_available: ["com.android.tethering"],
+ min_sdk_version: "30",
+}
+
+cc_test {
+ name: "netd_updatable_unit_test",
+ defaults: ["netd_defaults"],
+ test_suites: ["general-tests"],
+ require_root: true, // required by setrlimitForTest()
+ header_libs: [
+ "bpf_connectivity_headers",
+ ],
+ srcs: [
+ "BpfHandlerTest.cpp",
+ ],
+ static_libs: [
+ "libnetd_updatable",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ "libnetdutils",
+ ],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
new file mode 100644
index 0000000..3cd5e13
--- /dev/null
+++ b/netd/BpfHandler.cpp
@@ -0,0 +1,212 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "BpfHandler"
+
+#include "BpfHandler.h"
+
+#include <linux/bpf.h>
+
+#include <android-base/unique_fd.h>
+#include <bpf/WaitForProgsLoaded.h>
+#include <log/log.h>
+#include <netdutils/UidConstants.h>
+#include <private/android_filesystem_config.h>
+
+#include "BpfSyscallWrappers.h"
+
+namespace android {
+namespace net {
+
+using base::unique_fd;
+using bpf::NONEXISTENT_COOKIE;
+using bpf::getSocketCookie;
+using bpf::retrieveProgram;
+using netdutils::Status;
+using netdutils::statusFromErrno;
+
+constexpr int PER_UID_STATS_ENTRIES_LIMIT = 500;
+// At most 90% of the stats map may be used by tagged traffic entries. This ensures
+// that 10% of the map is always available to count untagged traffic, one entry per UID.
+// Otherwise, apps would be able to avoid data usage accounting entirely by filling up the
+// map with tagged traffic entries.
+constexpr int TOTAL_UID_STATS_ENTRIES_LIMIT = STATS_MAP_SIZE * 0.9;
+
+static_assert(STATS_MAP_SIZE - TOTAL_UID_STATS_ENTRIES_LIMIT > 100,
+ "The limit for stats map is to high, stats data may be lost due to overflow");
+
+static Status attachProgramToCgroup(const char* programPath, const unique_fd& cgroupFd,
+ bpf_attach_type type) {
+ unique_fd cgroupProg(retrieveProgram(programPath));
+ if (cgroupProg == -1) {
+ int ret = errno;
+ ALOGE("Failed to get program from %s: %s", programPath, strerror(ret));
+ return statusFromErrno(ret, "cgroup program get failed");
+ }
+ if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
+ int ret = errno;
+ ALOGE("Program from %s attach failed: %s", programPath, strerror(ret));
+ return statusFromErrno(ret, "program attach failed");
+ }
+ return netdutils::status::ok;
+}
+
+static Status initPrograms(const char* cg2_path) {
+ unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+ if (cg_fd == -1) {
+ int ret = errno;
+ ALOGE("Failed to open the cgroup directory: %s", strerror(ret));
+ return statusFromErrno(ret, "Open the cgroup directory failed");
+ }
+ RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
+ RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
+
+ // For the devices that support cgroup socket filter, the socket filter
+ // should be loaded successfully by bpfloader. So we attach the filter to
+ // cgroup if the program is pinned properly.
+ // TODO: delete the if statement once all devices should support cgroup
+ // socket filter (ie. the minimum kernel version required is 4.14).
+ if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+ RETURN_IF_NOT_OK(
+ attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+ }
+ return netdutils::status::ok;
+}
+
+BpfHandler::BpfHandler()
+ : mPerUidStatsEntriesLimit(PER_UID_STATS_ENTRIES_LIMIT),
+ mTotalUidStatsEntriesLimit(TOTAL_UID_STATS_ENTRIES_LIMIT) {}
+
+BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
+ : mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+
+Status BpfHandler::init(const char* cg2_path) {
+ // Make sure BPF programs are loaded before doing anything
+ android::bpf::waitForProgsLoaded();
+ ALOGI("BPF programs are loaded");
+
+ RETURN_IF_NOT_OK(initPrograms(cg2_path));
+ RETURN_IF_NOT_OK(initMaps());
+
+ return netdutils::status::ok;
+}
+
+Status BpfHandler::initMaps() {
+ std::lock_guard guard(mMutex);
+ RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH));
+ RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
+ RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
+ RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
+ RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
+ BPF_ANY));
+ RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+
+ return netdutils::status::ok;
+}
+
+bool BpfHandler::hasUpdateDeviceStatsPermission(uid_t uid) {
+ // This implementation is the same logic as method ActivityManager#checkComponentPermission.
+ // It implies that the real uid can never be the same as PER_USER_RANGE.
+ uint32_t appId = uid % PER_USER_RANGE;
+ auto permission = mUidPermissionMap.readValue(appId);
+ if (permission.ok() && (permission.value() & BPF_PERMISSION_UPDATE_DEVICE_STATS)) {
+ return true;
+ }
+ return ((appId == AID_ROOT) || (appId == AID_SYSTEM) || (appId == AID_DNS));
+}
+
+int BpfHandler::tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+ std::lock_guard guard(mMutex);
+ if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) {
+ return -EPERM;
+ }
+
+ uint64_t sock_cookie = getSocketCookie(sockFd);
+ if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+ UidTagValue newKey = {.uid = (uint32_t)chargeUid, .tag = tag};
+
+ uint32_t totalEntryCount = 0;
+ uint32_t perUidEntryCount = 0;
+ // Now we go through the stats map and count how many entries are associated
+ // with chargeUid. If the uid entry hit the limit for each chargeUid, we block
+ // the request to prevent the map from overflow. It is safe here to iterate
+ // over the map since when mMutex is hold, system server cannot toggle
+ // the live stats map and clean it. So nobody can delete entries from the map.
+ const auto countUidStatsEntries = [chargeUid, &totalEntryCount, &perUidEntryCount](
+ const StatsKey& key,
+ const BpfMap<StatsKey, StatsValue>&) {
+ if (key.uid == chargeUid) {
+ perUidEntryCount++;
+ }
+ totalEntryCount++;
+ return base::Result<void>();
+ };
+ auto configuration = mConfigurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
+ if (!configuration.ok()) {
+ ALOGE("Failed to get current configuration: %s, fd: %d",
+ strerror(configuration.error().code()), mConfigurationMap.getMap().get());
+ return -configuration.error().code();
+ }
+ if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+ ALOGE("unknown configuration value: %d", configuration.value());
+ return -EINVAL;
+ }
+
+ BpfMap<StatsKey, StatsValue>& currentMap =
+ (configuration.value() == SELECT_MAP_A) ? mStatsMapA : mStatsMapB;
+ base::Result<void> res = currentMap.iterate(countUidStatsEntries);
+ if (!res.ok()) {
+ ALOGE("Failed to count the stats entry in map %d: %s", currentMap.getMap().get(),
+ strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ if (totalEntryCount > mTotalUidStatsEntriesLimit ||
+ perUidEntryCount > mPerUidStatsEntriesLimit) {
+ ALOGE("Too many stats entries in the map, total count: %u, chargeUid(%u) count: %u,"
+ " blocking tag request to prevent map overflow",
+ totalEntryCount, chargeUid, perUidEntryCount);
+ return -EMFILE;
+ }
+ // Update the tag information of a socket to the cookieUidMap. Use BPF_ANY
+ // flag so it will insert a new entry to the map if that value doesn't exist
+ // yet. And update the tag if there is already a tag stored. Since the eBPF
+ // program in kernel only read this map, and is protected by rcu read lock. It
+ // should be fine to cocurrently update the map while eBPF program is running.
+ res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
+ if (!res.ok()) {
+ ALOGE("Failed to tag the socket: %s, fd: %d", strerror(res.error().code()),
+ mCookieTagMap.getMap().get());
+ return -res.error().code();
+ }
+ return 0;
+}
+
+int BpfHandler::untagSocket(int sockFd) {
+ std::lock_guard guard(mMutex);
+ uint64_t sock_cookie = getSocketCookie(sockFd);
+
+ if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+ base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
+ if (!res.ok()) {
+ ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+ return -res.error().code();
+ }
+ return 0;
+}
+
+} // namespace net
+} // namespace android
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
new file mode 100644
index 0000000..2ede1c1
--- /dev/null
+++ b/netd/BpfHandler.h
@@ -0,0 +1,83 @@
+/**
+ * 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.
+ */
+
+#pragma once
+
+#include <mutex>
+
+#include <netdutils/Status.h>
+#include "bpf/BpfMap.h"
+#include "bpf_shared.h"
+
+using android::bpf::BpfMap;
+
+namespace android {
+namespace net {
+
+class BpfHandler {
+ public:
+ BpfHandler();
+ BpfHandler(const BpfHandler&) = delete;
+ BpfHandler& operator=(const BpfHandler&) = delete;
+ netdutils::Status init(const char* cg2_path);
+ /*
+ * Tag the socket with the specified tag and uid. In the qtaguid module, the
+ * first tag request that grab the spinlock of rb_tree can update the tag
+ * information first and other request need to wait until it finish. All the
+ * tag request will be addressed in the order of they obtaining the spinlock.
+ * In the eBPF implementation, the kernel will try to update the eBPF map
+ * entry with the tag request. And the hashmap update process is protected by
+ * the spinlock initialized with the map. So the behavior of two modules
+ * should be the same. No additional lock needed.
+ */
+ int tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid);
+
+ /*
+ * The untag process is similar to tag socket and both old qtaguid module and
+ * new eBPF module have spinlock inside the kernel for concurrent update. No
+ * external lock is required.
+ */
+ int untagSocket(int sockFd);
+
+ private:
+ // For testing
+ BpfHandler(uint32_t perUidLimit, uint32_t totalLimit);
+
+ netdutils::Status initMaps();
+ bool hasUpdateDeviceStatsPermission(uid_t uid);
+
+ BpfMap<uint64_t, UidTagValue> mCookieTagMap;
+ BpfMap<StatsKey, StatsValue> mStatsMapA;
+ BpfMap<StatsKey, StatsValue> mStatsMapB;
+ BpfMap<uint32_t, uint8_t> mConfigurationMap;
+ BpfMap<uint32_t, uint8_t> mUidPermissionMap;
+
+ std::mutex mMutex;
+
+ // The limit on the number of stats entries a uid can have in the per uid stats map. BpfHandler
+ // will block that specific uid from tagging new sockets after the limit is reached.
+ const uint32_t mPerUidStatsEntriesLimit;
+
+ // The limit on the total number of stats entries in the per uid stats map. BpfHandler will
+ // block all tagging requests after the limit is reached.
+ const uint32_t mTotalUidStatsEntriesLimit;
+
+ // For testing
+ friend class BpfHandlerTest;
+};
+
+} // namespace net
+} // namespace android
\ No newline at end of file
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
new file mode 100644
index 0000000..db59c7c
--- /dev/null
+++ b/netd/BpfHandlerTest.cpp
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ *
+ * BpfHandlerTest.cpp - unit tests for BpfHandler.cpp
+ */
+
+#include <sys/socket.h>
+
+#include <gtest/gtest.h>
+
+#include "BpfHandler.h"
+
+using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted
+
+namespace android {
+namespace net {
+
+using base::Result;
+
+constexpr int TEST_MAP_SIZE = 10;
+constexpr int TEST_COOKIE = 1;
+constexpr uid_t TEST_UID = 10086;
+constexpr uid_t TEST_UID2 = 54321;
+constexpr uint32_t TEST_TAG = 42;
+constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr uint32_t TEST_PER_UID_STATS_ENTRIES_LIMIT = 3;
+constexpr uint32_t TEST_TOTAL_UID_STATS_ENTRIES_LIMIT = 7;
+
+#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+
+class BpfHandlerTest : public ::testing::Test {
+ protected:
+ BpfHandlerTest()
+ : mBh(TEST_PER_UID_STATS_ENTRIES_LIMIT, TEST_TOTAL_UID_STATS_ENTRIES_LIMIT) {}
+ BpfHandler mBh;
+ BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
+ BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+ BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+ BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+
+ void SetUp() {
+ std::lock_guard guard(mBh.mMutex);
+ ASSERT_EQ(0, setrlimitForTest());
+
+ mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
+ TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeCookieTagMap);
+
+ mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
+ TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeStatsMapA);
+
+ mFakeConfigurationMap.reset(
+ createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+ ASSERT_VALID(mFakeConfigurationMap);
+
+ mFakeUidPermissionMap.reset(
+ createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+ ASSERT_VALID(mFakeUidPermissionMap);
+
+ mBh.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+ ASSERT_VALID(mBh.mCookieTagMap);
+ mBh.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+ ASSERT_VALID(mBh.mStatsMapA);
+ mBh.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+ ASSERT_VALID(mBh.mConfigurationMap);
+ // Always write to stats map A by default.
+ ASSERT_RESULT_OK(mBh.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
+ SELECT_MAP_A, BPF_ANY));
+ mBh.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+ ASSERT_VALID(mBh.mUidPermissionMap);
+ }
+
+ int dupFd(const android::base::unique_fd& mapFd) {
+ return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
+ }
+
+ int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
+ uid_t realUid) {
+ int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ EXPECT_LE(0, sock);
+ *cookie = getSocketCookie(sock);
+ EXPECT_NE(NONEXISTENT_COOKIE, *cookie);
+ EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, realUid));
+ return sock;
+ }
+
+ void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
+ Result<UidTagValue> tagResult = mFakeCookieTagMap.readValue(cookie);
+ ASSERT_RESULT_OK(tagResult);
+ EXPECT_EQ(uid, tagResult.value().uid);
+ EXPECT_EQ(tag, tagResult.value().tag);
+ }
+
+ void expectNoTag(uint64_t cookie) { EXPECT_FALSE(mFakeCookieTagMap.readValue(cookie).ok()); }
+
+ void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
+ UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
+ EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
+ *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
+ StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
+ EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+ key->tag = 0;
+ EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+ // put tag information back to statsKey
+ key->tag = tag;
+ }
+
+ template <class Key, class Value>
+ void expectMapEmpty(BpfMap<Key, Value>& map) {
+ auto isEmpty = map.isEmpty();
+ EXPECT_RESULT_OK(isEmpty);
+ EXPECT_TRUE(isEmpty.value());
+ }
+
+ void expectTagSocketReachLimit(uint32_t tag, uint32_t uid) {
+ int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ EXPECT_LE(0, sock);
+ if (sock < 0) return;
+ uint64_t sockCookie = getSocketCookie(sock);
+ EXPECT_NE(NONEXISTENT_COOKIE, sockCookie);
+ EXPECT_EQ(-EMFILE, mBh.tagSocket(sock, tag, uid, uid));
+ expectNoTag(sockCookie);
+
+ // Delete stats entries then tag socket success
+ StatsKey key = {.uid = uid, .tag = 0, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
+ ASSERT_RESULT_OK(mFakeStatsMapA.deleteValue(key));
+ EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, uid));
+ expectUidTag(sockCookie, uid, tag);
+ }
+};
+
+TEST_F(BpfHandlerTest, TestTagSocketV4) {
+ uint64_t sockCookie;
+ int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+ expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+ ASSERT_EQ(0, mBh.untagSocket(v4socket));
+ expectNoTag(sockCookie);
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestReTagSocket) {
+ uint64_t sockCookie;
+ int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+ expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+ ASSERT_EQ(0, mBh.tagSocket(v4socket, TEST_TAG + 1, TEST_UID + 1, TEST_UID + 1));
+ expectUidTag(sockCookie, TEST_UID + 1, TEST_TAG + 1);
+}
+
+TEST_F(BpfHandlerTest, TestTagTwoSockets) {
+ uint64_t sockCookie1;
+ uint64_t sockCookie2;
+ int v4socket1 = setUpSocketAndTag(AF_INET, &sockCookie1, TEST_TAG, TEST_UID, TEST_UID);
+ setUpSocketAndTag(AF_INET, &sockCookie2, TEST_TAG, TEST_UID, TEST_UID);
+ expectUidTag(sockCookie1, TEST_UID, TEST_TAG);
+ expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
+ ASSERT_EQ(0, mBh.untagSocket(v4socket1));
+ expectNoTag(sockCookie1);
+ expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
+ ASSERT_FALSE(mFakeCookieTagMap.getNextKey(sockCookie2).ok());
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketV6) {
+ uint64_t sockCookie;
+ int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+ expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+ ASSERT_EQ(0, mBh.untagSocket(v6socket));
+ expectNoTag(sockCookie);
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagInvalidSocket) {
+ int invalidSocket = -1;
+ ASSERT_GT(0, mBh.tagSocket(invalidSocket, TEST_TAG, TEST_UID, TEST_UID));
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithoutPermission) {
+ int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_NE(-1, sock);
+ ASSERT_EQ(-EPERM, mBh.tagSocket(sock, TEST_TAG, TEST_UID, TEST_UID2));
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithPermission) {
+ // Grant permission to real uid. In practice, the uid permission map will be updated by
+ // TrafficController::setPermissionForUids().
+ uid_t realUid = TEST_UID2;
+ ASSERT_RESULT_OK(mFakeUidPermissionMap.writeValue(realUid,
+ BPF_PERMISSION_UPDATE_DEVICE_STATS, BPF_ANY));
+
+ // Tag a socket to a different uid other then realUid.
+ uint64_t sockCookie;
+ int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, realUid);
+ expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+ EXPECT_EQ(0, mBh.untagSocket(v6socket));
+ expectNoTag(sockCookie);
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestUntagInvalidSocket) {
+ int invalidSocket = -1;
+ ASSERT_GT(0, mBh.untagSocket(invalidSocket));
+ int v4socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_GT(0, mBh.untagSocket(v4socket));
+ expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketReachLimitFail) {
+ uid_t uid = TEST_UID;
+ StatsKey tagStatsMapKey[3];
+ for (int i = 0; i < 3; i++) {
+ uint64_t cookie = TEST_COOKIE + i;
+ uint32_t tag = TEST_TAG + i;
+ populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+ }
+ expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketReachTotalLimitFail) {
+ StatsKey tagStatsMapKey[4];
+ for (int i = 0; i < 4; i++) {
+ uint64_t cookie = TEST_COOKIE + i;
+ uint32_t tag = TEST_TAG + i;
+ uid_t uid = TEST_UID + i;
+ populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+ }
+ expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+} // namespace net
+} // namespace android
diff --git a/netd/NetdUpdatable.cpp b/netd/NetdUpdatable.cpp
new file mode 100644
index 0000000..f0997fc
--- /dev/null
+++ b/netd/NetdUpdatable.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NetdUpdatable"
+
+#include "NetdUpdatable.h"
+
+#include <android-base/logging.h>
+#include <netdutils/Status.h>
+
+#include "NetdUpdatablePublic.h"
+
+int libnetd_updatable_init(const char* cg2_path) {
+ android::base::InitLogging(/*argv=*/nullptr);
+ LOG(INFO) << __func__ << ": Initializing";
+
+ android::net::gNetdUpdatable = android::net::NetdUpdatable::getInstance();
+ android::netdutils::Status ret = android::net::gNetdUpdatable->mBpfHandler.init(cg2_path);
+ if (!android::netdutils::isOk(ret)) {
+ LOG(ERROR) << __func__ << ": BPF handler init failed";
+ return -ret.code();
+ }
+ return 0;
+}
+
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+ if (android::net::gNetdUpdatable == nullptr) return -EPERM;
+ return android::net::gNetdUpdatable->mBpfHandler.tagSocket(sockFd, tag, chargeUid, realUid);
+}
+
+int libnetd_updatable_untagSocket(int sockFd) {
+ if (android::net::gNetdUpdatable == nullptr) return -EPERM;
+ return android::net::gNetdUpdatable->mBpfHandler.untagSocket(sockFd);
+}
+
+namespace android {
+namespace net {
+
+NetdUpdatable* gNetdUpdatable = nullptr;
+
+NetdUpdatable* NetdUpdatable::getInstance() {
+ // Instantiated on first use.
+ static NetdUpdatable instance;
+ return &instance;
+}
+
+} // namespace net
+} // namespace android
diff --git a/netd/NetdUpdatable.h b/netd/NetdUpdatable.h
new file mode 100644
index 0000000..333037f
--- /dev/null
+++ b/netd/NetdUpdatable.h
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+
+#pragma once
+
+#include "BpfHandler.h"
+
+namespace android {
+namespace net {
+
+class NetdUpdatable {
+ public:
+ NetdUpdatable() = default;
+ NetdUpdatable(const NetdUpdatable&) = delete;
+ NetdUpdatable& operator=(const NetdUpdatable&) = delete;
+ static NetdUpdatable* getInstance();
+
+ BpfHandler mBpfHandler;
+};
+
+extern NetdUpdatable* gNetdUpdatable;
+
+} // namespace net
+} // namespace android
\ No newline at end of file
diff --git a/netd/include/NetdUpdatablePublic.h b/netd/include/NetdUpdatablePublic.h
new file mode 100644
index 0000000..1ca5ea2
--- /dev/null
+++ b/netd/include/NetdUpdatablePublic.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/*
+ * Initial function for libnetd_updatable library.
+ *
+ * The function uses |cg2_path| as cgroup v2 mount location to attach BPF programs so that the
+ * kernel can record packet number, size, etc. in BPF maps when packets pass through, and let user
+ * space retrieve statistics.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on
+ * failure.
+ */
+int libnetd_updatable_init(const char* cg2_path);
+
+/*
+ * Set the socket tag and owning UID for traffic statistics on the specified socket. Permission
+ * check is performed based on the |realUid| before socket tagging.
+ *
+ * The |sockFd| is a file descriptor of the socket that needs to tag. The |tag| is the mark to tag.
+ * It can be an arbitrary value in uint32_t range. The |chargeUid| is owning uid which will be
+ * tagged along with the |tag|. The |realUid| is an effective uid of the calling process, which is
+ * used for permission check before socket tagging.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
+ */
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid,
+ uid_t realUid);
+
+/*
+ * Untag a network socket. Future traffic on this socket will no longer be associated with any
+ * previously configured tag and uid.
+ *
+ * The |sockFd| is a file descriptor of the socket that wants to untag.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
+ */
+int libnetd_updatable_untagSocket(int sockFd);
+
+__END_DECLS
\ No newline at end of file
diff --git a/netd/libnetd_updatable.map.txt b/netd/libnetd_updatable.map.txt
new file mode 100644
index 0000000..dcb11a1
--- /dev/null
+++ b/netd/libnetd_updatable.map.txt
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+# This lists the entry points visible to applications that use the libnetd_updatable
+# library. Other entry points present in the library won't be usable.
+
+LIBNETD_UPDATABLE {
+ global:
+ libnetd_updatable_init; # apex
+ libnetd_updatable_tagSocket; # apex
+ libnetd_updatable_untagSocket; # apex
+ local:
+ *;
+};
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 48c74c6..f33be63 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -36,15 +36,17 @@
],
libs: [
"framework-annotations-lib",
- "framework-connectivity.impl",
+ "framework-connectivity-pre-jarjar",
"framework-connectivity-tiramisu.impl",
"service-connectivity-pre-jarjar",
"unsupportedappusage",
],
static_libs: [
- "modules-utils-build",
+ // Do not add static_libs here if they are already included in framework-connectivity
+ // or in service-connectivity. They are not necessary (included via
+ // service-connectivity-pre-jarjar), and in the case of code that is already in
+ // framework-connectivity, the classes would be included in the apex twice.
"modules-utils-statemachine",
- "net-utils-framework-common",
],
apex_available: [
"com.android.tethering",
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index bceeefa..d24b14b 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -61,11 +61,12 @@
"-Wno-unused-parameter",
"-Wthread-safety",
],
- static_libs: ["libgmock"],
+ static_libs: [
+ "libgmock",
+ "libnetworkstats",
+ ],
shared_libs: [
"libbase",
"liblog",
- "libnetworkstats",
- "libutils",
],
}
diff --git a/service/Android.bp b/service/Android.bp
index 33d4b66..33fe3ea 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -20,9 +20,9 @@
}
// The library name match the service-connectivity jarjar rules that put the JNI utils in the
-// com.android.connectivity.com.android.net.module.util package.
+// android.net.connectivity.com.android.net.module.util package.
cc_library_shared {
- name: "libcom_android_connectivity_com_android_net_module_util_jni",
+ name: "libandroid_net_connectivity_com_android_net_module_util_jni",
min_sdk_version: "30",
cflags: [
"-Wall",
@@ -33,10 +33,8 @@
srcs: [
"jni/com_android_net_module_util/onload.cpp",
],
- stl: "libc++_static",
static_libs: [
"libnet_utils_device_common_bpfjni",
- "libtcutils",
],
shared_libs: [
"liblog",
@@ -57,21 +55,24 @@
"-Wthread-safety",
],
srcs: [
- "jni/com_android_server_TestNetworkService.cpp",
+ "jni/com_android_server_BpfNetMaps.cpp",
"jni/com_android_server_connectivity_ClatCoordinator.cpp",
+ "jni/com_android_server_TestNetworkService.cpp",
"jni/onload.cpp",
],
- stl: "libc++_static",
header_libs: [
- "libbase_headers",
+ "bpf_connectivity_headers",
],
static_libs: [
- "libbase",
"libclat",
"libip_checksum",
"libnetjniutils",
+ "libtraffic_controller",
+ "netd_aidl_interface-lateststable-ndk",
],
shared_libs: [
+ "libbase",
+ "libnetdutils",
"liblog",
"libnativehelper",
],
@@ -93,20 +94,20 @@
],
libs: [
"framework-annotations-lib",
- "framework-connectivity.impl",
+ "framework-connectivity-pre-jarjar",
"framework-tethering.stubs.module_lib",
"framework-wifi.stubs.module_lib",
"unsupportedappusage",
"ServiceConnectivityResources",
],
static_libs: [
+ // Do not add libs here if they are already included
+ // in framework-connectivity
"dnsresolver_aidl_interface-V9-java",
- "modules-utils-build",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-bpf",
"net-utils-device-common-netlink",
- "net-utils-framework-common",
"netd-client",
"networkstack-client",
"PlatformProperties",
@@ -152,8 +153,9 @@
static_libs: [
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
+ "service-nearby",
],
- jarjar_rules: "jarjar-rules.txt",
+ jarjar_rules: ":connectivity-jarjar-rules",
apex_available: [
"com.android.tethering",
],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index f658a5e..e3b26fd 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -1,6 +1,15 @@
+# Classes in framework-connectivity are restricted to the android.net package.
+# This cannot be changed because it is harcoded in ART in S.
+# Any missing jarjar rule for framework-connectivity would be caught by the
+# build as an unexpected class outside of the android.net package.
+rule com.android.net.module.util.** android.net.connectivity.@0
+rule com.android.modules.utils.** android.net.connectivity.@0
+rule android.net.NetworkFactory* android.net.connectivity.@0
+
+# From modules-utils-preconditions
+rule com.android.internal.util.Preconditions* android.net.connectivity.@0
+
rule android.sysprop.** com.android.connectivity.@0
-rule com.android.net.module.util.** com.android.connectivity.@0
-rule com.android.modules.utils.** com.android.connectivity.@0
# internal util classes from framework-connectivity-shared-srcs
rule android.util.LocalLog* com.android.connectivity.@0
@@ -23,9 +32,6 @@
rule android.net.ResolverParamsParcel* com.android.connectivity.@0
# Also includes netd event listener AIDL, but this is handled by netd-client rules
-# From net-utils-device-common
-rule android.net.NetworkFactory* com.android.connectivity.@0
-
# From netd-client (newer AIDLs should go to android.net.netd.aidl)
rule android.net.netd.aidl.** com.android.connectivity.@0
# Avoid including android.net.INetdEventCallback, used in tests but not part of the module
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
index 07ae31c..2f09e55 100644
--- a/service/jni/com_android_net_module_util/onload.cpp
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -30,10 +30,10 @@
}
if (register_com_android_net_module_util_BpfMap(env,
- "com/android/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
+ "android/net/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
if (register_com_android_net_module_util_TcUtils(env,
- "com/android/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
+ "android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
return JNI_VERSION_1_6;
}
diff --git a/service/native/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
similarity index 95%
rename from service/native/jni/com_android_server_BpfNetMaps.cpp
rename to service/jni/com_android_server_BpfNetMaps.cpp
index 7ab4d46..2aaa4c3 100644
--- a/service/native/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -16,17 +16,17 @@
#define LOG_TAG "TrafficControllerJni"
+#include "TrafficController.h"
+
+#include <bpf_shared.h>
#include <jni.h>
+#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <net/if.h>
#include <vector>
-#include "TrafficController.h"
-#include "android-base/logging.h"
-#include "bpf_shared.h"
-#include "utils/Log.h"
using android::net::TrafficController;
using android::netdutils::Status;
@@ -41,7 +41,7 @@
static void native_init(JNIEnv* env, jobject clazz) {
Status status = mTc.start();
if (!isOk(status)) {
- ALOGE("%s failed", __func__);
+ ALOGE("%s failed, error code = %d", __func__, status.code());
}
}
@@ -50,7 +50,7 @@
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
if (!isOk(status)) {
- ALOGE("%s failed, errer code = %d", __func__, status.code());
+ ALOGE("%s failed, error code = %d", __func__, status.code());
}
return (jint)status.code();
}
@@ -60,7 +60,7 @@
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
if (!isOk(status)) {
- ALOGE("%s failed, errer code = %d", __func__, status.code());
+ ALOGE("%s failed, error code = %d", __func__, status.code());
}
return (jint)status.code();
}
@@ -70,7 +70,7 @@
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
if (!isOk(status)) {
- ALOGE("%s failed, errer code = %d", __func__, status.code());
+ ALOGE("%s failed, error code = %d", __func__, status.code());
}
return (jint)status.code();
}
@@ -80,7 +80,7 @@
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
if (!isOk(status)) {
- ALOGD("%s failed, errer code = %d", __func__, status.code());
+ ALOGD("%s failed, error code = %d", __func__, status.code());
}
return (jint)status.code();
}
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index 1a0de32..4efd0e1 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -98,7 +98,7 @@
{"jniCreateTunTap", "(ZLjava/lang/String;)I", (void*)create},
};
-int register_android_server_TestNetworkService(JNIEnv* env) {
+int register_com_android_server_TestNetworkService(JNIEnv* env) {
return jniRegisterNativeMethods(env, "com/android/server/TestNetworkService", gMethods,
NELEM(gMethods));
}
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index b445462..ee512ec 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -519,7 +519,7 @@
(void*)com_android_server_connectivity_ClatCoordinator_stopClatd},
};
-int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
+int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator",
gMethods, NELEM(gMethods));
}
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 04d9671..facdad7 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -19,8 +19,9 @@
namespace android {
-int register_android_server_TestNetworkService(JNIEnv* env);
-int register_android_server_connectivity_ClatCoordinator(JNIEnv* env);
+int register_com_android_server_TestNetworkService(JNIEnv* env);
+int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env);
+int register_com_android_server_BpfNetMaps(JNIEnv* env);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -29,11 +30,15 @@
return JNI_ERR;
}
- if (register_android_server_TestNetworkService(env) < 0) {
+ if (register_com_android_server_TestNetworkService(env) < 0) {
return JNI_ERR;
}
- if (register_android_server_connectivity_ClatCoordinator(env) < 0) {
+ if (register_com_android_server_connectivity_ClatCoordinator(env) < 0) {
+ return JNI_ERR;
+ }
+
+ if (register_com_android_server_BpfNetMaps(env) < 0) {
return JNI_ERR;
}
diff --git a/service/native/Android.bp b/service/native/Android.bp
index b49457d..cb26bc3 100644
--- a/service/native/Android.bp
+++ b/service/native/Android.bp
@@ -26,11 +26,8 @@
],
header_libs: [
"bpf_connectivity_headers",
- "bpf_headers",
- "bpf_syscall_wrappers",
],
static_libs: [
- "libnetdutils",
// TrafficController would use the constants of INetd so that add
// netd_aidl_interface-lateststable-ndk.
"netd_aidl_interface-lateststable-ndk",
@@ -39,6 +36,7 @@
// TODO: Find a good way to remove libbase.
"libbase",
"libcutils",
+ "libnetdutils",
"libutils",
"liblog",
],
@@ -52,37 +50,6 @@
min_sdk_version: "30",
}
-cc_library_shared {
- name: "libtraffic_controller_jni",
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- "-Wthread-safety",
- ],
- srcs: [
- "jni/*.cpp",
- ],
- header_libs: [
- "bpf_connectivity_headers",
- ],
- static_libs: [
- "libnetdutils",
- "libtraffic_controller",
- "netd_aidl_interface-lateststable-ndk",
- ],
- shared_libs: [
- "libbase",
- "liblog",
- "libutils",
- "libnativehelper",
- ],
- apex_available: [
- "com.android.tethering",
- ],
- min_sdk_version: "30",
-}
-
cc_test {
name: "traffic_controller_unit_test",
test_suites: ["general-tests"],
@@ -101,6 +68,7 @@
"libnetdutils",
"libtraffic_controller",
"libutils",
+ "libnetd_updatable",
"netd_aidl_interface-lateststable-ndk",
],
}
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 97fcc43..5981906 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -75,6 +75,7 @@
const char* TrafficController::LOCAL_STANDBY = "fw_standby";
const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave";
const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted";
+const char* TrafficController::LOCAL_LOW_POWER_STANDBY = "fw_low_power_standby";
static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET,
"Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET");
@@ -97,6 +98,7 @@
FLAG_MSG_TRANS(matchType, STANDBY_MATCH, match);
FLAG_MSG_TRANS(matchType, POWERSAVE_MATCH, match);
FLAG_MSG_TRANS(matchType, RESTRICTED_MATCH, match);
+ FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match);
FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
if (match) {
return StringPrintf("Unknown match: %u", match);
@@ -426,6 +428,8 @@
return ALLOWLIST;
case RESTRICTED:
return ALLOWLIST;
+ case LOW_POWER_STANDBY:
+ return ALLOWLIST;
case NONE:
default:
return DENYLIST;
@@ -448,6 +452,9 @@
case RESTRICTED:
res = updateOwnerMapEntry(RESTRICTED_MATCH, uid, rule, type);
break;
+ case LOW_POWER_STANDBY:
+ res = updateOwnerMapEntry(LOW_POWER_STANDBY_MATCH, uid, rule, type);
+ break;
case NONE:
default:
ALOGW("Unknown child chain: %d", chain);
@@ -526,6 +533,8 @@
res = replaceRulesInMap(POWERSAVE_MATCH, uids);
} else if (!name.compare(LOCAL_RESTRICTED)) {
res = replaceRulesInMap(RESTRICTED_MATCH, uids);
+ } else if (!name.compare(LOCAL_LOW_POWER_STANDBY)) {
+ res = replaceRulesInMap(LOW_POWER_STANDBY_MATCH, uids);
} else {
ALOGE("unknown chain name: %s", name.c_str());
return -EINVAL;
@@ -562,6 +571,9 @@
case RESTRICTED:
match = RESTRICTED_MATCH;
break;
+ case LOW_POWER_STANDBY:
+ match = LOW_POWER_STANDBY_MATCH;
+ break;
default:
return -EINVAL;
}
@@ -683,8 +695,6 @@
}
}
-const String16 TrafficController::DUMP_KEYWORD = String16("trafficcontroller");
-
void TrafficController::dump(DumpWriter& dw, bool verbose) {
std::lock_guard guard(mMutex);
ScopedIndent indentTop(dw);
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index f401636..d0eca34 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -38,6 +38,7 @@
#include "TrafficController.h"
#include "bpf/BpfUtils.h"
+#include "NetdUpdatablePublic.h"
using namespace android::bpf; // NOLINT(google-build-using-namespace): grandfathered
@@ -469,6 +470,7 @@
checkUidOwnerRuleForChain(STANDBY, STANDBY_MATCH);
checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH);
checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
+ checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
}
@@ -479,6 +481,7 @@
checkUidMapReplace("fw_standby", uids, STANDBY_MATCH);
checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH);
+ checkUidMapReplace("fw_low_power_standby", uids, LOW_POWER_STANDBY_MATCH);
ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
}
@@ -746,5 +749,133 @@
expectPrivilegedUserSetEmpty();
}
+constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
+constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
+
+using android::base::Error;
+using android::base::Result;
+using android::bpf::BpfMap;
+
+// This test set up a SkDestroyListener that is running parallel with the production
+// SkDestroyListener. The test will create thousands of sockets and tag them on the
+// production cookieUidTagMap and close them in a short time. When the number of
+// sockets get closed exceeds the buffer size, it will start to return ENOBUFF
+// error. The error will be ignored by the production SkDestroyListener and the
+// test will clean up the tags in tearDown if there is any remains.
+
+// TODO: Instead of test the ENOBUFF error, we can test the production
+// SkDestroyListener to see if it failed to delete a tagged socket when ENOBUFF
+// triggered.
+class NetlinkListenerTest : public testing::Test {
+ protected:
+ NetlinkListenerTest() {}
+ BpfMap<uint64_t, UidTagValue> mCookieTagMap;
+
+ void SetUp() {
+ mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
+ ASSERT_TRUE(mCookieTagMap.isValid());
+ }
+
+ void TearDown() {
+ const auto deleteTestCookieEntries = [](const uint64_t& key, const UidTagValue& value,
+ BpfMap<uint64_t, UidTagValue>& map) {
+ if ((value.uid == TEST_UID) && (value.tag == TEST_TAG)) {
+ Result<void> res = map.deleteValue(key);
+ if (res.ok() || (res.error().code() == ENOENT)) {
+ return Result<void>();
+ }
+ ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+ strerror(res.error().code()));
+ }
+ // Move forward to next cookie in the map.
+ return Result<void>();
+ };
+ EXPECT_RESULT_OK(mCookieTagMap.iterateWithValue(deleteTestCookieEntries));
+ }
+
+ Result<void> checkNoGarbageTagsExist() {
+ const auto checkGarbageTags = [](const uint64_t&, const UidTagValue& value,
+ const BpfMap<uint64_t, UidTagValue>&) -> Result<void> {
+ if ((TEST_UID == value.uid) && (TEST_TAG == value.tag)) {
+ return Error(EUCLEAN) << "Closed socket is not untagged";
+ }
+ return {};
+ };
+ return mCookieTagMap.iterateWithValue(checkGarbageTags);
+ }
+
+ bool checkMassiveSocketDestroy(int totalNumber, bool expectError) {
+ std::unique_ptr<android::netdutils::NetlinkListenerInterface> skDestroyListener;
+ auto result = android::net::TrafficController::makeSkDestroyListener();
+ if (!isOk(result)) {
+ ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
+ } else {
+ skDestroyListener = std::move(result.value());
+ }
+ int rxErrorCount = 0;
+ // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
+ const auto rxErrorHandler = [&rxErrorCount](const int, const int) { rxErrorCount++; };
+ skDestroyListener->registerSkErrorHandler(rxErrorHandler);
+ int fds[totalNumber];
+ for (int i = 0; i < totalNumber; i++) {
+ fds[i] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ // The likely reason for a failure is running out of available file descriptors.
+ EXPECT_LE(0, fds[i]) << i << " of " << totalNumber;
+ if (fds[i] < 0) {
+ // EXPECT_LE already failed above, so test case is a failure, but we don't
+ // want potentially tens of thousands of extra failures creating and then
+ // closing all these fds cluttering up the logs.
+ totalNumber = i;
+ break;
+ };
+ libnetd_updatable_tagSocket(fds[i], TEST_TAG, TEST_UID, 1000);
+ }
+
+ // TODO: Use a separate thread that has its own fd table so we can
+ // close sockets even faster simply by terminating that thread.
+ for (int i = 0; i < totalNumber; i++) {
+ EXPECT_EQ(0, close(fds[i]));
+ }
+ // wait a bit for netlink listener to handle all the messages.
+ usleep(SOCK_CLOSE_WAIT_US);
+ if (expectError) {
+ // If ENOBUFS triggered, check it only called into the handler once, ie.
+ // that the netlink handler is not spinning.
+ int currentErrorCount = rxErrorCount;
+ // 0 error count is acceptable because the system has chances to close all sockets
+ // normally.
+ EXPECT_LE(0, rxErrorCount);
+ if (!rxErrorCount) return true;
+
+ usleep(ENOBUFS_POLL_WAIT_US);
+ EXPECT_EQ(currentErrorCount, rxErrorCount);
+ } else {
+ EXPECT_RESULT_OK(checkNoGarbageTagsExist());
+ EXPECT_EQ(0, rxErrorCount);
+ }
+ return false;
+ }
+};
+
+TEST_F(NetlinkListenerTest, TestAllSocketUntagged) {
+ checkMassiveSocketDestroy(10, false);
+ checkMassiveSocketDestroy(100, false);
+}
+
+// Disabled because flaky on blueline-userdebug; this test relies on the main thread
+// winning a race against the NetlinkListener::run() thread. There's no way to ensure
+// things will be scheduled the same way across all architectures and test environments.
+TEST_F(NetlinkListenerTest, DISABLED_TestSkDestroyError) {
+ bool needRetry = false;
+ int retryCount = 0;
+ do {
+ needRetry = checkMassiveSocketDestroy(32500, true);
+ if (needRetry) retryCount++;
+ } while (needRetry && retryCount < 3);
+ // Should review test if it can always close all sockets correctly.
+ EXPECT_GT(3, retryCount);
+}
+
+
} // namespace net
} // namespace android
diff --git a/service/native/include/Common.h b/service/native/include/Common.h
index 7c0b797..dc44845 100644
--- a/service/native/include/Common.h
+++ b/service/native/include/Common.h
@@ -27,11 +27,14 @@
enum FirewallType { ALLOWLIST = INetd::FIREWALL_ALLOWLIST, DENYLIST = INetd::FIREWALL_DENYLIST };
+// LINT.IfChange(firewall_chain)
enum ChildChain {
- NONE = INetd::FIREWALL_CHAIN_NONE,
- DOZABLE = INetd::FIREWALL_CHAIN_DOZABLE,
- STANDBY = INetd::FIREWALL_CHAIN_STANDBY,
- POWERSAVE = INetd::FIREWALL_CHAIN_POWERSAVE,
- RESTRICTED = INetd::FIREWALL_CHAIN_RESTRICTED,
+ NONE = 0,
+ DOZABLE = 1,
+ STANDBY = 2,
+ POWERSAVE = 3,
+ RESTRICTED = 4,
+ LOW_POWER_STANDBY = 5,
INVALID_CHAIN
};
+// LINT.ThenChange(packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java)
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index c050871..e741dd6 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -25,7 +25,6 @@
#include "netdutils/DumpWriter.h"
#include "netdutils/NetlinkListener.h"
#include "netdutils/StatusOr.h"
-#include "utils/String16.h"
namespace android {
namespace net {
@@ -34,6 +33,8 @@
class TrafficController {
public:
+ static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
+
/*
* Initialize the whole controller
*/
@@ -84,7 +85,6 @@
netdutils::Status updateUidOwnerMap(const uint32_t uid,
UidOwnerMatchType matchType, IptOp op) EXCLUDES(mMutex);
- static const String16 DUMP_KEYWORD;
int toggleUidOwnerMap(ChildChain chain, bool enable) EXCLUDES(mMutex);
@@ -99,6 +99,7 @@
static const char* LOCAL_STANDBY;
static const char* LOCAL_POWERSAVE;
static const char* LOCAL_RESTRICTED;
+ static const char* LOCAL_LOW_POWER_STANDBY;
private:
/*
@@ -160,7 +161,7 @@
* the map right now:
* - Entry with UID_RULES_CONFIGURATION_KEY:
* Store the configuration for the current uid rules. It indicates the device
- * is in doze/powersave/standby/restricted mode.
+ * is in doze/powersave/standby/restricted/low power standby mode.
* - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
* Stores the current live stats map that kernel program is writing to.
* Userspace can do scraping and cleaning job on the other one depending on the
diff --git a/service/native/jni/onload.cpp b/service/native/jni/onload.cpp
deleted file mode 100644
index df7c77b..0000000
--- a/service/native/jni/onload.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "TrafficControllerJni"
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include "utils/Log.h"
-
-namespace android {
-
-int register_com_android_server_BpfNetMaps(JNIEnv* env);
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
- JNIEnv *env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- ALOGE("%s: ERROR: GetEnv failed", __func__);
- return JNI_ERR;
- }
-
- if (register_com_android_server_BpfNetMaps(env) < 0)
- return JNI_ERR;
-
- return JNI_VERSION_1_6;
-}
-
-}; // namespace android
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 5e208d8..17ee996 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -21,8 +21,7 @@
defaults: ["netd_defaults"],
header_libs: [
"bpf_connectivity_headers",
- "bpf_headers",
- "bpf_syscall_wrappers",
+ "libbase_headers",
],
srcs: [
"TcUtils.cpp", // TODO: move to frameworks/libs/net
@@ -31,7 +30,6 @@
],
stl: "libc++_static",
static_libs: [
- "libbase",
"libip_checksum",
"libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
],
@@ -47,8 +45,6 @@
test_suites: ["device-tests"],
header_libs: [
"bpf_connectivity_headers",
- "bpf_headers",
- "bpf_syscall_wrappers",
],
srcs: [
"TcUtilsTest.cpp",
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 9d89788..7a3bab3 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -16,10 +16,14 @@
package com.android.server;
+import android.net.INetd;
+import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.system.Os;
import android.util.Log;
+import com.android.modules.utils.build.SdkLevel;
+
/**
* BpfNetMaps is responsible for providing traffic controller relevant functionality.
*
@@ -27,86 +31,119 @@
*/
public class BpfNetMaps {
private static final String TAG = "BpfNetMaps";
+ private final INetd mNetd;
+ // Use legacy netd for releases before T.
+ private static final boolean USE_NETD = !SdkLevel.isAtLeastT();
+ private static boolean sInitialized = false;
- static {
- System.loadLibrary("traffic_controller_jni");
- native_init();
+ /**
+ * Initializes the class if it is not already initialized. This method will open maps but not
+ * cause any other effects. This method may be called multiple times on any thread.
+ */
+ private static synchronized void ensureInitialized() {
+ if (sInitialized) return;
+ if (!USE_NETD) {
+ System.loadLibrary("service-connectivity");
+ native_init();
+ }
+ sInitialized = true;
}
- /**
- * Add naughty app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void addNaughtyApp(final int uid) {
+ public BpfNetMaps(INetd netd) {
+ ensureInitialized();
+ mNetd = netd;
+ }
+
+ private void maybeThrow(final int err, final String msg) {
+ if (err != 0) {
+ throw new ServiceSpecificException(err, msg + ": " + Os.strerror(err));
+ }
+ }
+
+ /**
+ * Add naughty app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addNaughtyApp(final int uid) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.bandwidthAddNaughtyApp(uid);
+ return;
+ }
final int err = native_addNaughtyApp(uid);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to add naughty app: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to add naughty app");
}
- /**
- * Remove naughty app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void removeNaughtyApp(final int uid) {
+ /**
+ * Remove naughty app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeNaughtyApp(final int uid) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.bandwidthRemoveNaughtyApp(uid);
+ return;
+ }
final int err = native_removeNaughtyApp(uid);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to remove naughty app: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to remove naughty app");
}
- /**
- * Add nice app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void addNiceApp(final int uid) {
+ /**
+ * Add nice app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addNiceApp(final int uid) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.bandwidthAddNiceApp(uid);
+ return;
+ }
final int err = native_addNiceApp(uid);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to add nice app: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to add nice app");
}
- /**
- * Remove nice app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void removeNiceApp(final int uid) {
+ /**
+ * Remove nice app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeNiceApp(final int uid) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.bandwidthRemoveNiceApp(uid);
+ return;
+ }
final int err = native_removeNiceApp(uid);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to remove nice app: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to remove nice app");
}
- /**
- * Set target firewall child chain
- *
- * @param childChain target chain to enable
- * @param enable whether to enable or disable child chain.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void setChildChain(final int childChain, final boolean enable) {
- final int err = native_setChildChain(childChain, enable);
- if (err != 0) {
- throw new ServiceSpecificException(-err, "Unable to set child chain: "
- + Os.strerror(-err));
+ /**
+ * Set target firewall child chain
+ *
+ * @param childChain target chain to enable
+ * @param enable whether to enable or disable child chain.
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void setChildChain(final int childChain, final boolean enable) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallEnableChildChain(childChain, enable);
+ return;
}
+ final int err = native_setChildChain(childChain, enable);
+ maybeThrow(err, "Unable to set child chain");
}
/**
@@ -114,16 +151,21 @@
*
* The chain may be an allowlist chain or a denylist chain. A denylist chain contains DROP
* rules for the specified UIDs and a RETURN rule at the end. An allowlist chain contains RETURN
- * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for for the specified
+ * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for the specified
* UIDs, and a DROP rule at the end. The chain will be created if it does not exist.
*
- * @param chainName The name of the chain to replace.
+ * @param chainName The name of the chain to replace.
* @param isAllowlist Whether this is an allowlist or denylist chain.
- * @param uids The list of UIDs to allow/deny.
- * @return true if the chain was successfully replaced, false otherwise.
+ * @param uids The list of UIDs to allow/deny.
+ * @return 0 if the chain was successfully replaced, errno otherwise.
+ * @throws RemoteException when netd has crashed.
*/
public int replaceUidChain(final String chainName, final boolean isAllowlist,
- final int[] uids) {
+ final int[] uids) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallReplaceUidChain(chainName, isAllowlist, uids);
+ return 0;
+ }
final int err = native_replaceUidChain(chainName, isAllowlist, uids);
if (err != 0) {
Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
@@ -131,22 +173,24 @@
return -err;
}
- /**
- * Set firewall rule for uid
- *
- * @param childChain target chain
- * @param uid uid to allow/deny
- * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void setUidRule(final int childChain, final int uid,
- final int firewallRule) {
- final int err = native_setUidRule(childChain, uid, firewallRule);
- if (err != 0) {
- throw new ServiceSpecificException(-err, "Unable to set uid rule: "
- + Os.strerror(-err));
+ /**
+ * Set firewall rule for uid
+ *
+ * @param childChain target chain
+ * @param uid uid to allow/deny
+ * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void setUidRule(final int childChain, final int uid, final int firewallRule)
+ throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallSetUidRule(childChain, uid, firewallRule);
+ return;
}
+ final int err = native_setUidRule(childChain, uid, firewallRule);
+ maybeThrow(err, "Unable to set uid rule");
}
/**
@@ -160,17 +204,19 @@
* instead. Otherwise calling this method will not affect existing rules set on other UIDs.
*
* @param ifName the name of the interface on which the filtering rules will allow packets to
- be received.
- * @param uids an array of UIDs which the filtering rules will be set
+ * be received.
+ * @param uids an array of UIDs which the filtering rules will be set
+ * @throws RemoteException when netd has crashed.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
+ * cause of the failure.
*/
- public void addUidInterfaceRules(final String ifName, final int[] uids) {
- final int err = native_addUidInterfaceRules(ifName, uids);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to add uid interface rules: "
- + Os.strerror(err));
+ public void addUidInterfaceRules(final String ifName, final int[] uids) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallAddUidInterfaceRules(ifName, uids);
+ return;
}
+ final int err = native_addUidInterfaceRules(ifName, uids);
+ maybeThrow(err, "Unable to add uid interface rules");
}
/**
@@ -180,68 +226,77 @@
* by addUidInterfaceRules(). Ignore any uid which does not have filtering rule.
*
* @param uids an array of UIDs from which the filtering rules will be removed
+ * @throws RemoteException when netd has crashed.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
+ * cause of the failure.
*/
- public void removeUidInterfaceRules(final int[] uids) {
+ public void removeUidInterfaceRules(final int[] uids) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.firewallRemoveUidInterfaceRules(uids);
+ return;
+ }
final int err = native_removeUidInterfaceRules(uids);
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to remove uid interface rules: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to remove uid interface rules");
}
- /**
- * Request netd to change the current active network stats map.
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- public void swapActiveStatsMap() {
+ /**
+ * Request netd to change the current active network stats map.
+ *
+ * @throws RemoteException when netd has crashed.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void swapActiveStatsMap() throws RemoteException {
+ if (USE_NETD) {
+ mNetd.trafficSwapActiveStatsMap();
+ return;
+ }
final int err = native_swapActiveStatsMap();
- if (err != 0) {
- throw new ServiceSpecificException(err, "Unable to swap active stats map: "
- + Os.strerror(err));
- }
+ maybeThrow(err, "Unable to swap active stats map");
}
- /**
- * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
- * specified. Or remove all permissions from the uids.
- *
- * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or
- * PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
- * revoke all permissions for the uids.
- * @param uids uid of users to grant permission
- */
- public void setNetPermForUids(final int permission, final int[] uids) {
- native_setPermissionForUids(permission, uids);
+ /**
+ * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
+ * specified. Or remove all permissions from the uids.
+ *
+ * @param permissions The permission to grant, it could be either PERMISSION_INTERNET and/or
+ * PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
+ * revoke all permissions for the uids.
+ * @param uids uid of users to grant permission
+ * @throws RemoteException when netd has crashed.
+ */
+ public void setNetPermForUids(final int permissions, final int[] uids) throws RemoteException {
+ if (USE_NETD) {
+ mNetd.trafficSetNetPermForUids(permissions, uids);
+ return;
+ }
+ native_setPermissionForUids(permissions, uids);
}
/**
* Set counter set for uid
*
* @param counterSet either SET_DEFAULT or SET_FOREGROUND
- * @param uid uid to foreground/background
+ * @param uid uid to foreground/background
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
*/
- public int setCounterSet(final int counterSet, final int uid) {
+ public void setCounterSet(final int counterSet, final int uid) {
final int err = native_setCounterSet(counterSet, uid);
- if (err != 0) {
- Log.e(TAG, "setCounterSet failed: " + Os.strerror(-err));
- }
- return -err;
+ maybeThrow(err, "setCounterSet failed");
}
/**
* Reset Uid stats
+ *
* @param tag default 0
* @param uid given uid to be clear
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
*/
- public int deleteTagData(final int tag, final int uid) {
+ public void deleteTagData(final int tag, final int uid) {
final int err = native_deleteTagData(tag, uid);
- if (err != 0) {
- Log.e(TAG, "deleteTagData failed: " + Os.strerror(-err));
- }
- return -err;
+ maybeThrow(err, "deleteTagData failed");
}
private static native void native_init();
@@ -255,7 +310,7 @@
private native int native_addUidInterfaceRules(String ifName, int[] uids);
private native int native_removeUidInterfaceRules(int[] uids);
private native int native_swapActiveStatsMap();
- private native void native_setPermissionForUids(int permission, int[] uids);
+ private native void native_setPermissionForUids(int permissions, int[] uids);
private native int native_setCounterSet(int counterSet, int uid);
private native int native_deleteTagData(int tag, int uid);
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index fa7dfa9..d833bc2 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -249,6 +249,7 @@
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.netlink.InetDiagMessage;
import com.android.server.connectivity.AutodestructReference;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.DnsManager;
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
@@ -396,6 +397,7 @@
private NetworkStatsManager mStatsManager;
private NetworkPolicyManager mPolicyManager;
private final NetdCallback mNetdCallback;
+ private final BpfNetMaps mBpfNetMaps;
/**
* TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple
@@ -438,8 +440,6 @@
* Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
* The default request uses PREFERENCE_ORDER_DEFAULT.
*/
- // Bound for the lowest valid preference order.
- static final int PREFERENCE_ORDER_LOWEST = 999;
// Used when sending to netd to code for "no order".
static final int PREFERENCE_ORDER_NONE = 0;
// Order for requests that don't code for a per-app preference. As it is
@@ -447,11 +447,6 @@
// PREFERENCE_ORDER_NONE when sending to netd.
@VisibleForTesting
static final int PREFERENCE_ORDER_INVALID = Integer.MAX_VALUE;
- // Order for the default internet request. Since this must always have the
- // lowest priority, its value is larger than the largest acceptable value. As
- // it is out of the valid range, the corresponding order should be
- // PREFERENCE_ORDER_NONE when sending to netd.
- static final int PREFERENCE_ORDER_DEFAULT = 1000;
// As a security feature, VPNs have the top priority.
static final int PREFERENCE_ORDER_VPN = 0; // Netd supports only 0 for VPN.
// Order of per-app OEM preference. See {@link #setOemNetworkPreference}.
@@ -466,6 +461,13 @@
// See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
@VisibleForTesting
static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
+ // Preference order that signifies the network shouldn't be set as a default network for
+ // the UIDs, only give them access to it. TODO : replace this with a boolean
+ // in NativeUidRangeConfig
+ @VisibleForTesting
+ static final int PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT = 999;
+ // Bound for the lowest valid preference order.
+ static final int PREFERENCE_ORDER_LOWEST = 999;
/**
* used internally to clear a wakelock when transitioning
@@ -593,6 +595,13 @@
// Handle private DNS validation status updates.
private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
+ /**
+ * used to remove a network request, either a listener or a real request and call unavailable
+ * arg1 = UID of caller
+ * obj = NetworkRequest
+ */
+ private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39;
+
/**
* Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
* been tested.
@@ -760,6 +769,7 @@
private Set<String> mWolSupportedInterfaces;
private final TelephonyManager mTelephonyManager;
+ private final CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
private final AppOpsManager mAppOpsManager;
private final LocationPermissionChecker mLocationPermissionChecker;
@@ -1329,12 +1339,33 @@
}
/**
+ * @see CarrierPrivilegeAuthenticator
+ */
+ public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
+ @NonNull final Context context, @NonNull final TelephonyManager tm) {
+ if (SdkLevel.isAtLeastT()) {
+ return new CarrierPrivilegeAuthenticator(context, tm);
+ } else {
+ return null;
+ }
+ }
+
+ /**
* @see DeviceConfigUtils#isFeatureEnabled
*/
public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
TETHERING_MODULE_NAME, defaultEnabled);
}
+
+ /**
+ * Get the BpfNetMaps implementation to use in ConnectivityService.
+ * @param netd
+ * @return BpfNetMaps implementation.
+ */
+ public BpfNetMaps getBpfNetMaps(INetd netd) {
+ return new BpfNetMaps(netd);
+ }
}
public ConnectivityService(Context context) {
@@ -1403,9 +1434,12 @@
mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
mNetd = netd;
+ mBpfNetMaps = mDeps.getBpfNetMaps(netd);
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+ mCarrierPrivilegeAuthenticator =
+ mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -1430,7 +1464,7 @@
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
+ mPermissionMonitor = new PermissionMonitor(mContext, mNetd, mBpfNetMaps);
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
// Listen for user add/removes to inform PermissionMonitor.
@@ -2084,6 +2118,7 @@
newNc.setAdministratorUids(new int[0]);
if (!checkAnyPermissionOf(
callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
+ newNc.setAccessUids(new ArraySet<>());
newNc.setSubscriptionIds(Collections.emptySet());
}
@@ -3333,18 +3368,8 @@
switch (msg.what) {
case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
- NetworkCapabilities networkCapabilities = (NetworkCapabilities) arg.second;
- if (networkCapabilities.hasConnectivityManagedCapability()) {
- Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
- }
- if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
- // Make sure the original object is not mutated. NetworkAgent normally
- // makes a copy of the capabilities when sending the message through
- // the Messenger, but if this ever changes, not making a defensive copy
- // here will give attack vectors to clients using this code path.
- networkCapabilities = new NetworkCapabilities(networkCapabilities);
- networkCapabilities.restrictCapabilitiesForTestNetwork(nai.creatorUid);
- }
+ final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
+ (NetworkCapabilities) arg.second);
processCapabilitiesFromAgent(nai, networkCapabilities);
updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
break;
@@ -4140,6 +4165,15 @@
}
}
+ private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
+ @NonNull final NetworkCapabilities caps) {
+ if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
+ return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ callingUid, caps);
+ }
+ return false;
+ }
+
private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
// handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
@@ -4163,6 +4197,7 @@
private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
ensureRunningOnConnectivityServiceThread();
+ NetworkRequest requestToBeReleased = null;
for (final NetworkRequestInfo nri : nris) {
mNetworkRequestInfoLogs.log("REGISTER " + nri);
checkNrisConsistency(nri);
@@ -4177,7 +4212,15 @@
}
}
}
+ if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ if (!hasCarrierPrivilegeForNetworkCaps(nri.mUid, req.networkCapabilities)
+ && !checkConnectivityRestrictedNetworksPermission(
+ nri.mPid, nri.mUid)) {
+ requestToBeReleased = req;
+ }
+ }
}
+
// If this NRI has a satisfier already, it is replacing an older request that
// has been removed. Track it.
final NetworkRequest activeRequest = nri.getActiveRequest();
@@ -4187,6 +4230,11 @@
}
}
+ if (requestToBeReleased != null) {
+ releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
+ return;
+ }
+
if (mFlags.noRematchAllRequestsOnRegister()) {
rematchNetworksAndRequests(nris);
} else {
@@ -5026,6 +5074,11 @@
/* callOnUnavailable */ false);
break;
}
+ case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: {
+ handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
+ /* callOnUnavailable */ true);
+ break;
+ }
case EVENT_SET_ACCEPT_UNVALIDATED: {
Network network = (Network) msg.obj;
handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
@@ -6095,13 +6148,6 @@
}
}
- private void ensureRequestableCapabilities(NetworkCapabilities networkCapabilities) {
- final String badCapability = networkCapabilities.describeFirstNonRequestableCapability();
- if (badCapability != null) {
- throw new IllegalArgumentException("Cannot request network with " + badCapability);
- }
- }
-
// This checks that the passed capabilities either do not request a
// specific SSID/SignalStrength, or the calling app has permission to do so.
private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
@@ -6159,7 +6205,7 @@
nai.onSignalStrengthThresholdsUpdated(thresholdsArray);
}
- private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+ private static void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
if (nc == null) {
return;
}
@@ -6172,11 +6218,22 @@
}
}
- private void ensureValid(NetworkCapabilities nc) {
+ private static void ensureListenableCapabilities(@NonNull final NetworkCapabilities nc) {
ensureValidNetworkSpecifier(nc);
if (nc.isPrivateDnsBroken()) {
throw new IllegalArgumentException("Can't request broken private DNS");
}
+ if (nc.hasAccessUids()) {
+ throw new IllegalArgumentException("Can't request access UIDs");
+ }
+ }
+
+ private void ensureRequestableCapabilities(@NonNull final NetworkCapabilities nc) {
+ ensureListenableCapabilities(nc);
+ final String badCapability = nc.describeFirstNonRequestableCapability();
+ if (badCapability != null) {
+ throw new IllegalArgumentException("Cannot request network with " + badCapability);
+ }
}
// TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@@ -6271,7 +6328,6 @@
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
- ensureValid(networkCapabilities);
final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), reqType);
@@ -6330,12 +6386,24 @@
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
String callingPackageName, String callingAttributionTag) {
if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
- enforceConnectivityRestrictedNetworksPermission();
+ if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ enforceConnectivityRestrictedNetworksPermission();
+ }
} else {
enforceChangePermission(callingPackageName, callingAttributionTag);
}
}
+ private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) {
+ if (checkAnyPermissionOf(callerPid, callerUid,
+ android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
+ || checkAnyPermissionOf(callerPid, callerUid,
+ android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
+ return true;
+ }
+ return false;
+ }
+
@Override
public boolean requestBandwidthUpdate(Network network) {
enforceAccessPermission();
@@ -6399,7 +6467,6 @@
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
- ensureValidNetworkSpecifier(networkCapabilities);
restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
callingUid, callingPackageName);
@@ -6468,7 +6535,7 @@
// There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
// can't request networks.
restrictBackgroundRequestForCaller(nc);
- ensureValid(nc);
+ ensureListenableCapabilities(nc);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
@@ -6490,7 +6557,7 @@
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
- ensureValid(networkCapabilities);
+ ensureListenableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
@@ -6518,6 +6585,13 @@
EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest));
}
+ private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) {
+ ensureNetworkRequestHasType(networkRequest);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0,
+ networkRequest));
+ }
+
private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
if (mNetworkProviderInfos.containsKey(npi.messenger)) {
// Avoid creating duplicates. even if an app makes a direct AIDL call.
@@ -6915,28 +6989,18 @@
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId,
int uid) {
- if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
- // Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in
- // the call to mixInCapabilities below anyway, but sanitizing here means the NAI never
- // sees capabilities that may be malicious, which might prevent mistakes in the future.
- networkCapabilities = new NetworkCapabilities(networkCapabilities);
- networkCapabilities.restrictCapabilitiesForTestNetwork(uid);
- }
- LinkProperties lp = new LinkProperties(linkProperties);
-
- final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+ // At this point the capabilities/properties are untrusted and unverified, e.g. checks that
+ // the capabilities' access UID comply with security limitations. They will be sanitized
+ // as the NAI registration finishes, in handleRegisterNetworkAgent(). This is
+ // because some of the checks must happen on the handler thread.
final NetworkAgentInfo nai = new NetworkAgentInfo(na,
- new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
+ new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo),
+ linkProperties, networkCapabilities,
currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
mQosCallbackTracker, mDeps);
- // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
- processCapabilitiesFromAgent(nai, nc);
- nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
- processLinkPropertiesFromAgent(nai, nai.linkProperties);
-
final String extraInfo = networkInfo.getExtraInfo();
final String name = TextUtils.isEmpty(extraInfo)
? nai.networkCapabilities.getSsid() : extraInfo;
@@ -6951,8 +7015,20 @@
}
private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+ if (VDBG) log("Network Monitor created for " + nai);
+ // nai.nc and nai.lp are the same object that was passed by the network agent if the agent
+ // lives in the same process as this code (e.g. wifi), so make sure this code doesn't
+ // mutate their object
+ final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+ final LinkProperties lp = new LinkProperties(nai.linkProperties);
+ // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
+ processCapabilitiesFromAgent(nai, nc);
+ nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
+ processLinkPropertiesFromAgent(nai, lp);
+ nai.linkProperties = lp;
+
nai.onNetworkMonitorCreated(networkMonitor);
- if (VDBG) log("Got NetworkAgent Messenger");
+
mNetworkAgentInfos.add(nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.put(nai.network.getNetId(), nai);
@@ -6963,10 +7039,11 @@
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
+
nai.notifyRegistered();
NetworkInfo networkInfo = nai.networkInfo;
updateNetworkInfo(nai, networkInfo);
- updateUids(nai, null, nai.networkCapabilities);
+ updateVpnUids(nai, null, nai.networkCapabilities);
}
private class NetworkOfferInfo implements IBinder.DeathRecipient {
@@ -7410,9 +7487,11 @@
* Stores into |nai| any data coming from the agent that might also be written to the network's
* NetworkCapabilities by ConnectivityService itself. This ensures that the data provided by the
* agent is not lost when updateCapabilities is called.
- * This method should never alter the agent's NetworkCapabilities, only store data in |nai|.
*/
private void processCapabilitiesFromAgent(NetworkAgentInfo nai, NetworkCapabilities nc) {
+ if (nc.hasConnectivityManagedCapability()) {
+ Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
+ }
// Note: resetting the owner UID before storing the agent capabilities in NAI means that if
// the agent attempts to change the owner UID, then nai.declaredCapabilities will not
// actually be the same as the capabilities sent by the agent. Still, it is safer to reset
@@ -7423,6 +7502,8 @@
nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
}
nai.declaredCapabilities = new NetworkCapabilities(nc);
+ NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
+ mCarrierPrivilegeAuthenticator);
}
/** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@@ -7598,7 +7679,8 @@
updateNetworkPermissions(nai, newNc);
final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc);
- updateUids(nai, prevNc, newNc);
+ updateVpnUids(nai, prevNc, newNc);
+ updateAccessUids(nai, prevNc, newNc);
nai.updateScoreForNetworkAgentUpdate();
if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) {
@@ -7687,6 +7769,17 @@
return stableRanges;
}
+ private static UidRangeParcel[] intsToUidRangeStableParcels(
+ final @NonNull ArraySet<Integer> uids) {
+ final UidRangeParcel[] stableRanges = new UidRangeParcel[uids.size()];
+ int index = 0;
+ for (int uid : uids) {
+ stableRanges[index] = new UidRangeParcel(uid, uid);
+ index++;
+ }
+ return stableRanges;
+ }
+
private static UidRangeParcel[] toUidRangeStableParcels(UidRange[] ranges) {
final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length];
for (int i = 0; i < ranges.length; i++) {
@@ -7757,8 +7850,8 @@
}
}
- private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
- NetworkCapabilities newNc) {
+ private void updateVpnUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
+ @Nullable NetworkCapabilities newNc) {
Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUidRanges();
Set<UidRange> newRanges = null == newNc ? null : newNc.getUidRanges();
if (null == prevRanges) prevRanges = new ArraySet<>();
@@ -7813,7 +7906,50 @@
}
} catch (Exception e) {
// Never crash!
- loge("Exception in updateUids: ", e);
+ loge("Exception in updateVpnUids: ", e);
+ }
+ }
+
+ private void updateAccessUids(@NonNull NetworkAgentInfo nai,
+ @Nullable NetworkCapabilities prevNc, @Nullable NetworkCapabilities newNc) {
+ // In almost all cases both NC code for empty access UIDs. return as fast as possible.
+ final boolean prevEmpty = null == prevNc || prevNc.getAccessUidsNoCopy().isEmpty();
+ final boolean newEmpty = null == newNc || newNc.getAccessUidsNoCopy().isEmpty();
+ if (prevEmpty && newEmpty) return;
+
+ final ArraySet<Integer> prevUids =
+ null == prevNc ? new ArraySet<>() : prevNc.getAccessUidsNoCopy();
+ final ArraySet<Integer> newUids =
+ null == newNc ? new ArraySet<>() : newNc.getAccessUidsNoCopy();
+
+ if (prevUids.equals(newUids)) return;
+
+ // This implementation is very simple and vastly faster for sets of Integers than
+ // CompareOrUpdateResult, which is tuned for sets that need to be compared based on
+ // a key computed from the value and has storage for that.
+ final ArraySet<Integer> toRemove = new ArraySet<>(prevUids);
+ final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
+ toRemove.removeAll(newUids);
+ toAdd.removeAll(prevUids);
+
+ try {
+ if (!toAdd.isEmpty()) {
+ mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
+ nai.network.netId,
+ intsToUidRangeStableParcels(toAdd),
+ PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+ }
+ if (!toRemove.isEmpty()) {
+ mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ nai.network.netId,
+ intsToUidRangeStableParcels(toRemove),
+ PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+ }
+ } catch (ServiceSpecificException e) {
+ // Has the interface disappeared since the network was built ?
+ Log.i(TAG, "Can't set access UIDs for network " + nai.network, e);
+ } catch (RemoteException e) {
+ // Netd died. This usually causes a runtime restart anyway.
}
}
@@ -8726,6 +8862,7 @@
}
networkAgent.created = true;
networkAgent.onNetworkCreated();
+ updateAccessUids(networkAgent, null, networkAgent.networkCapabilities);
}
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
@@ -8779,7 +8916,7 @@
} else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.disconnect();
if (networkAgent.isVPN()) {
- updateUids(networkAgent, networkAgent.networkCapabilities, null);
+ updateVpnUids(networkAgent, networkAgent.networkCapabilities, null);
}
disconnectAndDestroyNetwork(networkAgent);
if (networkAgent.isVPN()) {
@@ -9783,7 +9920,7 @@
android.Manifest.permission.NETWORK_STACK);
final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
if (!nc.hasTransport(TRANSPORT_TEST)) {
- throw new SecurityException("Data Stall simluation is only possible for test networks");
+ throw new SecurityException("Data Stall simulation is only possible for test networks");
}
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
@@ -10709,9 +10846,9 @@
try {
if (add) {
- mNetd.bandwidthAddNiceApp(uid);
+ mBpfNetMaps.addNiceApp(uid);
} else {
- mNetd.bandwidthRemoveNiceApp(uid);
+ mBpfNetMaps.removeNiceApp(uid);
}
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
@@ -10724,9 +10861,9 @@
try {
if (add) {
- mNetd.bandwidthAddNaughtyApp(uid);
+ mBpfNetMaps.addNaughtyApp(uid);
} else {
- mNetd.bandwidthRemoveNaughtyApp(uid);
+ mBpfNetMaps.removeNaughtyApp(uid);
}
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
@@ -10738,7 +10875,7 @@
enforceNetworkStackOrSettingsPermission();
try {
- mNetd.firewallSetUidRule(chain, uid,
+ mBpfNetMaps.setUidRule(chain, uid,
allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
@@ -10750,7 +10887,7 @@
enforceNetworkStackOrSettingsPermission();
try {
- mNetd.firewallEnableChildChain(chain, enable);
+ mBpfNetMaps.setChildChain(chain, enable);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
@@ -10763,16 +10900,20 @@
try {
switch (chain) {
case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
- mNetd.firewallReplaceUidChain("fw_dozable", true /* isAllowList */, uids);
+ mBpfNetMaps.replaceUidChain("fw_dozable", true /* isAllowList */, uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
- mNetd.firewallReplaceUidChain("fw_standby", false /* isAllowList */, uids);
+ mBpfNetMaps.replaceUidChain("fw_standby", false /* isAllowList */, uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_POWERSAVE:
- mNetd.firewallReplaceUidChain("fw_powersave", true /* isAllowList */, uids);
+ mBpfNetMaps.replaceUidChain("fw_powersave", true /* isAllowList */, uids);
break;
case ConnectivityManager.FIREWALL_CHAIN_RESTRICTED:
- mNetd.firewallReplaceUidChain("fw_restricted", true /* isAllowList */, uids);
+ mBpfNetMaps.replaceUidChain("fw_restricted", true /* isAllowList */, uids);
+ break;
+ case ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ mBpfNetMaps.replaceUidChain("fw_low_power_standby", true /* isAllowList */,
+ uids);
break;
default:
throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
@@ -10787,7 +10928,7 @@
public void swapActiveStatsMap() {
enforceNetworkStackOrSettingsPermission();
try {
- mNetd.trafficSwapActiveStatsMap();
+ mBpfNetMaps.swapActiveStatsMap();
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
new file mode 100644
index 0000000..b761762
--- /dev/null
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -0,0 +1,325 @@
+/*
+ * 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.connectivity;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_NOT_EXPORTED;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.apishim.TelephonyManagerShimImpl;
+import com.android.networkstack.apishim.common.TelephonyManagerShim;
+import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Tracks the uid of the carrier privileged app that provides the carrier config.
+ * Authenticates if the caller has same uid as
+ * carrier privileged app that provides the carrier config
+ * @hide
+ */
+public class CarrierPrivilegeAuthenticator extends BroadcastReceiver {
+ private static final String TAG = CarrierPrivilegeAuthenticator.class.getSimpleName();
+ private static final boolean DBG = true;
+
+ // The context is for the current user (system server)
+ private final Context mContext;
+ private final TelephonyManagerShim mTelephonyManagerShim;
+ private final TelephonyManager mTelephonyManager;
+ @GuardedBy("mLock")
+ private int[] mCarrierServiceUid;
+ @GuardedBy("mLock")
+ private int mModemCount = 0;
+ private final Object mLock = new Object();
+ private final HandlerThread mThread;
+ private final Handler mHandler;
+ @NonNull
+ private final List<CarrierPrivilegesListenerShim> mCarrierPrivilegesChangedListeners =
+ new ArrayList<>();
+
+ public CarrierPrivilegeAuthenticator(@NonNull final Context c,
+ @NonNull final TelephonyManager t,
+ @NonNull final TelephonyManagerShimImpl telephonyManagerShim) {
+ mContext = c;
+ mTelephonyManager = t;
+ mTelephonyManagerShim = telephonyManagerShim;
+ mThread = new HandlerThread(TAG);
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper()) {};
+ synchronized (mLock) {
+ mModemCount = mTelephonyManager.getActiveModemCount();
+ registerForCarrierChanges();
+ updateCarrierServiceUid();
+ }
+ }
+
+ public CarrierPrivilegeAuthenticator(@NonNull final Context c,
+ @NonNull final TelephonyManager t) {
+ mContext = c;
+ mTelephonyManager = t;
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
+ mTelephonyManagerShim = new TelephonyManagerShimImpl(mTelephonyManager);
+ } else {
+ mTelephonyManagerShim = null;
+ }
+ mThread = new HandlerThread(TAG);
+ mThread.start();
+ mHandler = new Handler(mThread.getLooper()) {};
+ synchronized (mLock) {
+ mModemCount = mTelephonyManager.getActiveModemCount();
+ registerForCarrierChanges();
+ updateCarrierServiceUid();
+ }
+ }
+
+ /**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * TODO : migrate to the version in frameworks/libs/net when it's ready
+ *
+ * @hide
+ */
+ public class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+ public HandlerExecutor(@NonNull Handler handler) {
+ mHandler = handler;
+ }
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
+ }
+ }
+
+ /**
+ * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
+ *
+ * <p>The broadcast receiver is registered with mHandler
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+ handleActionMultiSimConfigChanged(context, intent);
+ break;
+ default:
+ Log.d(TAG, "Unknown intent received with action: " + intent.getAction());
+ }
+ }
+
+ private void handleActionMultiSimConfigChanged(Context context, Intent intent) {
+ unregisterCarrierPrivilegesListeners();
+ synchronized (mLock) {
+ mModemCount = mTelephonyManager.getActiveModemCount();
+ }
+ registerCarrierPrivilegesListeners();
+ updateCarrierServiceUid();
+ }
+
+ private void registerForCarrierChanges() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+ mContext.registerReceiver(this, filter, null, mHandler, RECEIVER_NOT_EXPORTED /* flags */);
+ registerCarrierPrivilegesListeners();
+ }
+
+ private void registerCarrierPrivilegesListeners() {
+ final HandlerExecutor executor = new HandlerExecutor(mHandler);
+ int modemCount;
+ synchronized (mLock) {
+ modemCount = mModemCount;
+ }
+ try {
+ for (int i = 0; i < modemCount; i++) {
+ CarrierPrivilegesListenerShim carrierPrivilegesListener =
+ new CarrierPrivilegesListenerShim() {
+ @Override
+ public void onCarrierPrivilegesChanged(
+ @NonNull List<String> privilegedPackageNames,
+ @NonNull int[] privilegedUids) {
+ // Re-trigger the synchronous check (which is also very cheap due
+ // to caching in CarrierPrivilegesTracker). This allows consistency
+ // with the onSubscriptionsChangedListener and broadcasts.
+ updateCarrierServiceUid();
+ }
+ };
+ addCarrierPrivilegesListener(i, executor, carrierPrivilegesListener);
+ mCarrierPrivilegesChangedListeners.add(carrierPrivilegesListener);
+ }
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Encountered exception registering carrier privileges listeners", e);
+ }
+ }
+
+ private void addCarrierPrivilegesListener(int logicalSlotIndex, Executor executor,
+ CarrierPrivilegesListenerShim listener) {
+ if (mTelephonyManagerShim == null) {
+ return;
+ }
+ try {
+ mTelephonyManagerShim.addCarrierPrivilegesListener(
+ logicalSlotIndex, executor, listener);
+ } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ Log.e(TAG, "addCarrierPrivilegesListener API is not available");
+ }
+ }
+
+ private void removeCarrierPrivilegesListener(CarrierPrivilegesListenerShim listener) {
+ if (mTelephonyManagerShim == null) {
+ return;
+ }
+ try {
+ mTelephonyManagerShim.removeCarrierPrivilegesListener(listener);
+ } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ Log.e(TAG, "removeCarrierPrivilegesListener API is not available");
+ }
+ }
+
+ private String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
+ if (mTelephonyManagerShim == null) {
+ return null;
+ }
+ try {
+ return mTelephonyManagerShim.getCarrierServicePackageNameForLogicalSlot(
+ logicalSlotIndex);
+ } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ Log.e(TAG, "getCarrierServicePackageNameForLogicalSlot API is not available");
+ }
+ return null;
+ }
+
+ private void unregisterCarrierPrivilegesListeners() {
+ for (CarrierPrivilegesListenerShim carrierPrivilegesListener :
+ mCarrierPrivilegesChangedListeners) {
+ removeCarrierPrivilegesListener(carrierPrivilegesListener);
+ }
+ mCarrierPrivilegesChangedListeners.clear();
+ }
+
+ /**
+ * Check if a UID is the carrier service app of the subscription ID in the provided capabilities
+ *
+ * This returns whether the passed UID is the carrier service package for the subscription ID
+ * stored in the telephony network specifier in the passed network capabilities.
+ * If the capabilities don't code for a cellular network, or if they don't have the
+ * subscription ID in their specifier, this returns false.
+ *
+ * This method can be used to check that a network request for {@link NET_CAPABILITY_CBS} is
+ * allowed for the UID of a caller, which must hold carrier privilege and provide the carrier
+ * config.
+ * It can also be used to check that a factory is entitled to grant access to a given network
+ * to a given UID on grounds that it is the carrier service package.
+ *
+ * @param callingUid uid of the app claimed to be the carrier service package.
+ * @param networkCapabilities the network capabilities for which carrier privilege is checked.
+ * @return true if uid provides the relevant carrier config else false.
+ */
+ public boolean hasCarrierPrivilegeForNetworkCapabilities(int callingUid,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ if (callingUid == Process.INVALID_UID) return false;
+ if (!networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)) return false;
+ final int subId = getSubIdFromNetworkSpecifier(networkCapabilities.getNetworkSpecifier());
+ if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) return false;
+ return callingUid == getCarrierServiceUidForSubId(subId);
+ }
+
+ @VisibleForTesting
+ void updateCarrierServiceUid() {
+ synchronized (mLock) {
+ mCarrierServiceUid = new int[mModemCount];
+ for (int i = 0; i < mModemCount; i++) {
+ mCarrierServiceUid[i] = getCarrierServicePackageUidForSlot(i);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ int getCarrierServiceUidForSubId(int subId) {
+ final int slotId = getSlotIndex(subId);
+ synchronized (mLock) {
+ if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mModemCount) {
+ return mCarrierServiceUid[slotId];
+ }
+ }
+ return Process.INVALID_UID;
+ }
+
+ @VisibleForTesting
+ protected int getSlotIndex(int subId) {
+ return SubscriptionManager.getSlotIndex(subId);
+ }
+
+ @VisibleForTesting
+ int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+
+ @VisibleForTesting
+ int getUidForPackage(String pkgName) {
+ if (pkgName == null) {
+ return Process.INVALID_UID;
+ }
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm != null) {
+ ApplicationInfo applicationInfo = pm.getApplicationInfo(pkgName, 0);
+ if (applicationInfo != null) {
+ return applicationInfo.uid;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException exception) {
+ // Didn't find package. Try other users
+ Log.i(TAG, "Unable to find uid for package " + pkgName);
+ }
+ return Process.INVALID_UID;
+ }
+
+ @VisibleForTesting
+ int getCarrierServicePackageUidForSlot(int slotId) {
+ return getUidForPackage(getCarrierServicePackageNameForLogicalSlot(slotId));
+ }
+}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 1a7248a..e917b3f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,7 +17,9 @@
package com.android.server.connectivity;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.transportNamesOf;
import android.annotation.NonNull;
@@ -52,11 +54,13 @@
import android.os.SystemClock;
import android.telephony.data.EpsBearerQosSessionAttributes;
import android.telephony.data.NrQosSessionAttributes;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
import com.android.server.ConnectivityService;
import java.io.PrintWriter;
@@ -1188,6 +1192,54 @@
return mConnectivityReport;
}
+ /**
+ * Make sure the NC from network agents don't contain stuff they shouldn't.
+ *
+ * @param nc the capabilities to sanitize
+ * @param creatorUid the UID of the process creating this network agent
+ * @param authenticator the carrier privilege authenticator to check for telephony constraints
+ */
+ public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+ final int creatorUid, @NonNull final CarrierPrivilegeAuthenticator authenticator) {
+ if (nc.hasTransport(TRANSPORT_TEST)) {
+ nc.restrictCapabilitiesForTestNetwork(creatorUid);
+ }
+ if (!areAccessUidsAcceptableFromNetworkAgent(nc, authenticator)) {
+ nc.setAccessUids(new ArraySet<>());
+ }
+ }
+
+ private static boolean areAccessUidsAcceptableFromNetworkAgent(
+ @NonNull final NetworkCapabilities nc,
+ @Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
+ // NCs without access UIDs are fine.
+ if (!nc.hasAccessUids()) return true;
+ // S and below must never accept access UIDs, even if an agent sends them, because netd
+ // didn't support the required feature in S.
+ if (!SdkLevel.isAtLeastT()) return false;
+
+ // On a non-restricted network, access UIDs make no sense
+ if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
+
+ // If this network has TRANSPORT_TEST, then the caller can do whatever they want to
+ // access UIDs
+ if (nc.hasTransport(TRANSPORT_TEST)) return true;
+
+ // Factories that make cell networks can allow the UID for the carrier service package.
+ // This can only work in T where there is support for CarrierPrivilegeAuthenticator
+ if (null != carrierPrivilegeAuthenticator
+ && nc.hasSingleTransport(TRANSPORT_CELLULAR)
+ && (1 == nc.getAccessUidsNoCopy().size())
+ && (carrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ nc.getAccessUidsNoCopy().valueAt(0), nc))) {
+ return true;
+ }
+
+ // TODO : accept Railway callers
+
+ return false;
+ }
+
// TODO: Print shorter members first and only print the boolean variable which value is true
// to improve readability.
public String toString() {
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 439db89..ac46054 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -58,7 +58,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -68,6 +67,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.net.module.util.CollectionUtils;
+import com.android.server.BpfNetMaps;
import java.util.ArrayList;
import java.util.HashMap;
@@ -93,6 +93,7 @@
private final INetd mNetd;
private final Dependencies mDeps;
private final Context mContext;
+ private final BpfNetMaps mBpfNetMaps;
@GuardedBy("this")
private final Set<UserHandle> mUsers = new HashSet<>();
@@ -184,12 +185,14 @@
}
}
- public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) {
- this(context, netd, new Dependencies());
+ public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd,
+ @NonNull final BpfNetMaps bpfNetMaps) {
+ this(context, netd, bpfNetMaps, new Dependencies());
}
@VisibleForTesting
PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd,
+ @NonNull final BpfNetMaps bpfNetMaps,
@NonNull final Dependencies deps) {
mPackageManager = context.getPackageManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -197,6 +200,7 @@
mNetd = netd;
mDeps = deps;
mContext = context;
+ mBpfNetMaps = bpfNetMaps;
}
private int getPackageNetdNetworkPermission(@NonNull final PackageInfo app) {
@@ -803,17 +807,11 @@
}
try {
if (add) {
- mNetd.firewallAddUidInterfaceRules(iface, toIntArray(uids));
+ mBpfNetMaps.addUidInterfaceRules(iface, toIntArray(uids));
} else {
- mNetd.firewallRemoveUidInterfaceRules(toIntArray(uids));
+ mBpfNetMaps.removeUidInterfaceRules(toIntArray(uids));
}
- } catch (ServiceSpecificException e) {
- // Silently ignore exception when device does not support eBPF, otherwise just log
- // the exception and do not crash
- if (e.errorCode != OsConstants.EOPNOTSUPP) {
- loge("Exception when updating permissions: ", e);
- }
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
loge("Exception when updating permissions: ", e);
}
}
@@ -878,26 +876,27 @@
try {
// TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
if (allPermissionAppIds.size() != 0) {
- mNetd.trafficSetNetPermForUids(
+ mBpfNetMaps.setNetPermForUids(
PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS,
toIntArray(allPermissionAppIds));
}
if (internetPermissionAppIds.size() != 0) {
- mNetd.trafficSetNetPermForUids(PERMISSION_INTERNET,
+ mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET,
toIntArray(internetPermissionAppIds));
}
if (updateStatsPermissionAppIds.size() != 0) {
- mNetd.trafficSetNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS,
+ mBpfNetMaps.setNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS,
toIntArray(updateStatsPermissionAppIds));
}
if (noPermissionAppIds.size() != 0) {
- mNetd.trafficSetNetPermForUids(PERMISSION_NONE, toIntArray(noPermissionAppIds));
+ mBpfNetMaps.setNetPermForUids(PERMISSION_NONE,
+ toIntArray(noPermissionAppIds));
}
if (uninstalledAppIds.size() != 0) {
- mNetd.trafficSetNetPermForUids(PERMISSION_UNINSTALLED,
+ mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED,
toIntArray(uninstalledAppIds));
}
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Pass appId list of special permission failed." + e);
}
}
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index bea00a9..742044b 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -308,6 +308,48 @@
}
}
+ @Test @IgnoreUpTo(SC_V2)
+ public void testSetAccessUids() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ assertThrows(NullPointerException.class, () -> nc.setAccessUids(null));
+ assertFalse(nc.hasAccessUids());
+ assertFalse(nc.isAccessUid(0));
+ assertFalse(nc.isAccessUid(1000));
+ assertEquals(0, nc.getAccessUids().size());
+ nc.setAccessUids(new ArraySet<>());
+ assertFalse(nc.hasAccessUids());
+ assertFalse(nc.isAccessUid(0));
+ assertFalse(nc.isAccessUid(1000));
+ assertEquals(0, nc.getAccessUids().size());
+
+ final ArraySet<Integer> uids = new ArraySet<>();
+ uids.add(200);
+ uids.add(250);
+ uids.add(-1);
+ uids.add(Integer.MAX_VALUE);
+ nc.setAccessUids(uids);
+ assertNotEquals(nc, new NetworkCapabilities());
+ assertTrue(nc.hasAccessUids());
+
+ final List<Integer> includedList = List.of(-2, 0, 199, 700, 901, 1000, Integer.MIN_VALUE);
+ final List<Integer> excludedList = List.of(-1, 200, 250, Integer.MAX_VALUE);
+ for (final int uid : includedList) {
+ assertFalse(nc.isAccessUid(uid));
+ }
+ for (final int uid : excludedList) {
+ assertTrue(nc.isAccessUid(uid));
+ }
+
+ final Set<Integer> outUids = nc.getAccessUids();
+ assertEquals(4, outUids.size());
+ for (final int uid : includedList) {
+ assertFalse(outUids.contains(uid));
+ }
+ for (final int uid : excludedList) {
+ assertTrue(outUids.contains(uid));
+ }
+ }
+
@Test
public void testParcelNetworkCapabilities() {
final Set<Range<Integer>> uids = new ArraySet<>();
@@ -318,6 +360,10 @@
.addCapability(NET_CAPABILITY_EIMS)
.addCapability(NET_CAPABILITY_NOT_METERED);
if (isAtLeastS()) {
+ final ArraySet<Integer> accessUids = new ArraySet<>();
+ accessUids.add(4);
+ accessUids.add(9);
+ netCap.setAccessUids(accessUids);
netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
netCap.setUids(uids);
}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index f66231d..e979a3b 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -70,7 +70,7 @@
// devices.
android_test {
name: "CtsNetTestCases",
- defaults: ["CtsNetTestCasesDefaults", "NetworkStackNextEnableDefaults"],
+ defaults: ["CtsNetTestCasesDefaults", "ConnectivityNextEnableDefaults"],
// TODO: CTS should not depend on the entirety of the networkstack code.
static_libs: [
"NetworkStackApiCurrentLib",
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 232114e..68fa38d 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -278,18 +278,23 @@
assertTrue("Didn't receive broadcast for ACTION_CARRIER_CONFIG_CHANGED for subId=" + subId,
carrierConfigReceiver.waitForCarrierConfigChanged());
- assertTrue("Don't have Carrier Privileges after adding cert for this package",
- mTelephonyManager.createForSubscriptionId(subId).hasCarrierPrivileges());
// Wait for CarrierPrivilegesTracker to receive the ACTION_CARRIER_CONFIG_CHANGED
// broadcast. CPT then needs to update the corresponding DataConnection, which then
// updates ConnectivityService. Unfortunately, this update to the NetworkCapabilities in
// CS does not trigger NetworkCallback#onCapabilitiesChanged as changing the
// administratorUids is not a publicly visible change. In lieu of a better signal to
- // detministically wait for, use Thread#sleep here.
+ // deterministically wait for, use Thread#sleep here.
// TODO(b/157949581): replace this Thread#sleep with a deterministic signal
Thread.sleep(DELAY_FOR_ADMIN_UIDS_MILLIS);
+ // TODO(b/217559768): Receiving carrier config change and immediately checking carrier
+ // privileges is racy, as the CP status is updated after receiving the same signal. Move
+ // the CP check after sleep to temporarily reduce the flakiness. This will soon be fixed
+ // by switching to CarrierPrivilegesListener.
+ assertTrue("Don't have Carrier Privileges after adding cert for this package",
+ mTelephonyManager.createForSubscriptionId(subId).hasCarrierPrivileges());
+
final TestConnectivityDiagnosticsCallback connDiagsCallback =
createAndRegisterConnectivityDiagnosticsCallback(CELLULAR_NETWORK_REQUEST);
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt
new file mode 100644
index 0000000..049372f
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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 android.net.cts
+
+import android.net.nsd.NsdManager
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.networkstack.apishim.ConnectivityFrameworkInitShimImpl
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+
+private val cfiShim = ConnectivityFrameworkInitShimImpl.newInstance()
+
+@RunWith(DevSdkIgnoreRunner::class)
+// ConnectivityFrameworkInitializerTiramisu was added in T
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+class ConnectivityFrameworkInitializerTiramisuTest {
+ @Test
+ fun testServicesRegistered() {
+ val ctx = InstrumentationRegistry.getInstrumentation().context as android.content.Context
+ assertNotNull(ctx.getSystemService(NsdManager::class.java),
+ "NsdManager not registered")
+ }
+
+ // registerServiceWrappers can only be called during initialization and should throw otherwise
+ @Test(expected = IllegalStateException::class)
+ fun testThrows() {
+ cfiShim.registerServiceWrappers()
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 1f39fee..886b078 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -146,8 +146,9 @@
fun tearDown() {
agentsToCleanUp.forEach { it.unregister() }
callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
+
+ // reader.stop() cleans up tun fd
reader.handler.post { reader.stop() }
- Os.close(iface.fileDescriptor.fileDescriptor)
handlerThread.quitSafely()
}
@@ -519,4 +520,4 @@
private fun <T> Context.assertHasService(manager: Class<T>): T {
return getSystemService(manager) ?: fail("Service $manager not found")
-}
\ No newline at end of file
+}
diff --git a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
index 385bf9e..d221694 100644
--- a/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
+++ b/tests/cts/net/src/android/net/cts/IpConfigurationTest.java
@@ -25,12 +25,16 @@
import android.net.LinkAddress;
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
+import android.os.Build;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.DevSdkIgnoreRule;
+
import libcore.net.InetAddressUtils;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,6 +54,9 @@
private StaticIpConfiguration mStaticIpConfig;
private ProxyInfo mProxy;
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
@Before
public void setUp() {
dnsServers.add(DNS1);
@@ -99,6 +106,18 @@
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
}
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testBuilder() {
+ final IpConfiguration c = new IpConfiguration.Builder()
+ .setStaticIpConfiguration(mStaticIpConfig)
+ .setHttpProxy(mProxy)
+ .build();
+
+ assertEquals(mStaticIpConfig, c.getStaticIpConfiguration());
+ assertEquals(mProxy, c.getHttpProxy());
+ }
+
private void checkEmpty(IpConfiguration config) {
assertEquals(IpConfiguration.IpAssignment.UNASSIGNED,
config.getIpAssignment().UNASSIGNED);
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index ef5dc77..344482b 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -23,7 +23,6 @@
import android.net.INetworkAgentRegistry
import android.net.InetAddresses
import android.net.IpPrefix
-import android.net.KeepalivePacketData
import android.net.LinkAddress
import android.net.LinkProperties
import android.net.NattKeepalivePacketData
@@ -42,6 +41,7 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkInfo
@@ -53,7 +53,6 @@
import android.net.QosCallback
import android.net.QosCallbackException
import android.net.QosCallback.QosCallbackRegistrationException
-import android.net.QosFilter
import android.net.QosSession
import android.net.QosSessionAttributes
import android.net.QosSocketInfo
@@ -67,7 +66,6 @@
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
-import android.os.Looper
import android.os.Message
import android.os.SystemClock
import android.telephony.TelephonyManager
@@ -82,6 +80,8 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.Losing
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkAgent
@@ -100,7 +100,6 @@
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
import com.android.testutils.TestableNetworkCallback
import org.junit.After
-import org.junit.Assert.assertArrayEquals
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
@@ -462,6 +461,36 @@
}
}
+ private fun ncWithAccessUids(vararg uids: Int) = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .setAccessUids(uids.toSet()).build()
+
+ @Test
+ fun testRejectedUpdates() {
+ val callback = TestableNetworkCallback()
+ // will be cleaned up in tearDown
+ registerNetworkCallback(makeTestNetworkRequest(), callback)
+ val agent = createNetworkAgent(initialNc = ncWithAccessUids(200))
+ agent.register()
+ agent.markConnected()
+
+ // Make sure the UIDs have been ignored.
+ callback.expectCallback<Available>(agent.network!!)
+ callback.expectCapabilitiesThat(agent.network!!) {
+ it.accessUids.isEmpty() && !it.hasCapability(NET_CAPABILITY_VALIDATED)
+ }
+ callback.expectCallback<LinkPropertiesChanged>(agent.network!!)
+ callback.expectCallback<BlockedStatus>(agent.network!!)
+ callback.expectCapabilitiesThat(agent.network!!) {
+ it.accessUids.isEmpty() && it.hasCapability(NET_CAPABILITY_VALIDATED)
+ }
+ callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+
+ // Make sure that the UIDs are also ignored upon update
+ agent.sendNetworkCapabilities(ncWithAccessUids(200, 300))
+ callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+ }
+
@Test
fun testSendScore() {
// This test will create two networks and check that the one with the stronger
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
new file mode 100644
index 0000000..147fca9
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.cts;
+
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL;
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_NO;
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_YES;
+import static android.app.usage.NetworkStats.Bucket.METERED_ALL;
+import static android.app.usage.NetworkStats.Bucket.METERED_NO;
+import static android.app.usage.NetworkStats.Bucket.METERED_YES;
+import static android.app.usage.NetworkStats.Bucket.STATE_ALL;
+import static android.app.usage.NetworkStats.Bucket.STATE_DEFAULT;
+import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
+import static android.app.usage.NetworkStats.Bucket.TAG_NONE;
+import static android.app.usage.NetworkStats.Bucket.UID_ALL;
+
+import android.app.AppOpsManager;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.TrafficStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.TelephonyManager;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+
+public class NetworkStatsManagerTest extends InstrumentationTestCase {
+ private static final String LOG_TAG = "NetworkStatsManagerTest";
+ private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
+ private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
+
+ private static final long MINUTE = 1000 * 60;
+ private static final int TIMEOUT_MILLIS = 15000;
+
+ private static final String CHECK_CONNECTIVITY_URL = "http://www.265.com/";
+ private static final int HOST_RESOLUTION_RETRIES = 4;
+ private static final int HOST_RESOLUTION_INTERVAL_MS = 500;
+
+ private static final int NETWORK_TAG = 0xf00d;
+ private static final long THRESHOLD_BYTES = 2 * 1024 * 1024; // 2 MB
+
+ private abstract class NetworkInterfaceToTest {
+ private boolean mMetered;
+ private boolean mIsDefault;
+
+ abstract int getNetworkType();
+ abstract int getTransportType();
+
+ public boolean getMetered() {
+ return mMetered;
+ }
+
+ public void setMetered(boolean metered) {
+ this.mMetered = metered;
+ }
+
+ public boolean getIsDefault() {
+ return mIsDefault;
+ }
+
+ public void setIsDefault(boolean isDefault) {
+ mIsDefault = isDefault;
+ }
+
+ abstract String getSystemFeature();
+ abstract String getErrorMessage();
+ }
+
+ private final NetworkInterfaceToTest[] mNetworkInterfacesToTest =
+ new NetworkInterfaceToTest[] {
+ new NetworkInterfaceToTest() {
+ @Override
+ public int getNetworkType() {
+ return ConnectivityManager.TYPE_WIFI;
+ }
+
+ @Override
+ public int getTransportType() {
+ return NetworkCapabilities.TRANSPORT_WIFI;
+ }
+
+ @Override
+ public String getSystemFeature() {
+ return PackageManager.FEATURE_WIFI;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ return " Please make sure you are connected to a WiFi access point.";
+ }
+ },
+ new NetworkInterfaceToTest() {
+ @Override
+ public int getNetworkType() {
+ return ConnectivityManager.TYPE_MOBILE;
+ }
+
+ @Override
+ public int getTransportType() {
+ return NetworkCapabilities.TRANSPORT_CELLULAR;
+ }
+
+ @Override
+ public String getSystemFeature() {
+ return PackageManager.FEATURE_TELEPHONY;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ return " Please make sure you have added a SIM card with data plan to"
+ + " your phone, have enabled data over cellular and in case of"
+ + " dual SIM devices, have selected the right SIM "
+ + "for data connection.";
+ }
+ }
+ };
+
+ private String mPkg;
+ private NetworkStatsManager mNsm;
+ private ConnectivityManager mCm;
+ private PackageManager mPm;
+ private long mStartTime;
+ private long mEndTime;
+
+ private long mBytesRead;
+ private String mWriteSettingsMode;
+ private String mUsageStatsMode;
+
+ private void exerciseRemoteHost(Network network, URL url) throws Exception {
+ NetworkInfo networkInfo = mCm.getNetworkInfo(network);
+ if (networkInfo == null) {
+ Log.w(LOG_TAG, "Network info is null");
+ } else {
+ Log.w(LOG_TAG, "Network: " + networkInfo.toString());
+ }
+ InputStreamReader in = null;
+ HttpURLConnection urlc = null;
+ String originalKeepAlive = System.getProperty("http.keepAlive");
+ System.setProperty("http.keepAlive", "false");
+ try {
+ TrafficStats.setThreadStatsTag(NETWORK_TAG);
+ urlc = (HttpURLConnection) network.openConnection(url);
+ urlc.setConnectTimeout(TIMEOUT_MILLIS);
+ urlc.setUseCaches(false);
+ // Disable compression so we generate enough traffic that assertWithinPercentage will
+ // not be affected by the small amount of traffic (5-10kB) sent by the test harness.
+ urlc.setRequestProperty("Accept-Encoding", "identity");
+ urlc.connect();
+ boolean ping = urlc.getResponseCode() == 200;
+ if (ping) {
+ in = new InputStreamReader(
+ (InputStream) urlc.getContent());
+
+ mBytesRead = 0;
+ while (in.read() != -1) ++mBytesRead;
+ }
+ } catch (Exception e) {
+ Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
+ } finally {
+ TrafficStats.clearThreadStatsTag();
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // don't care
+ }
+ }
+ if (urlc != null) {
+ urlc.disconnect();
+ }
+ if (originalKeepAlive == null) {
+ System.clearProperty("http.keepAlive");
+ } else {
+ System.setProperty("http.keepAlive", originalKeepAlive);
+ }
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mNsm = (NetworkStatsManager) getInstrumentation().getContext()
+ .getSystemService(Context.NETWORK_STATS_SERVICE);
+ mNsm.setPollForce(true);
+
+ mCm = (ConnectivityManager) getInstrumentation().getContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ mPm = getInstrumentation().getContext().getPackageManager();
+
+ mPkg = getInstrumentation().getContext().getPackageName();
+
+ mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
+ setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
+ mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mWriteSettingsMode != null) {
+ setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
+ }
+ if (mUsageStatsMode != null) {
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
+ }
+ super.tearDown();
+ }
+
+ private void setAppOpsMode(String appop, String mode) throws Exception {
+ final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
+ SystemUtil.runShellCommand(command);
+ }
+
+ private String getAppOpsMode(String appop) throws Exception {
+ final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
+ String result = SystemUtil.runShellCommand(command);
+ if (result == null) {
+ Log.w(LOG_TAG, "App op " + appop + " could not be read.");
+ }
+ return result;
+ }
+
+ private boolean isInForeground() throws IOException {
+ String result = SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd activity get-uid-state " + Process.myUid());
+ return result.contains("FOREGROUND");
+ }
+
+ private class NetworkCallback extends ConnectivityManager.NetworkCallback {
+ private long mTolerance;
+ private URL mUrl;
+ public boolean success;
+ public boolean metered;
+ public boolean isDefault;
+
+ NetworkCallback(long tolerance, URL url) {
+ mTolerance = tolerance;
+ mUrl = url;
+ success = false;
+ metered = false;
+ isDefault = false;
+ }
+
+ // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4,
+ // we need to wait until IPv4 is available or the test will spuriously fail.
+ private void waitForHostResolution(Network network) {
+ for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) {
+ try {
+ network.getAllByName(mUrl.getHost());
+ return;
+ } catch (UnknownHostException e) {
+ SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS);
+ }
+ }
+ fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)",
+ mUrl.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS));
+ }
+
+ @Override
+ public void onAvailable(Network network) {
+ try {
+ mStartTime = System.currentTimeMillis() - mTolerance;
+ isDefault = network.equals(mCm.getActiveNetwork());
+ waitForHostResolution(network);
+ exerciseRemoteHost(network, mUrl);
+ mEndTime = System.currentTimeMillis() + mTolerance;
+ success = true;
+ metered = !mCm.getNetworkCapabilities(network)
+ .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ synchronized (NetworkStatsManagerTest.this) {
+ NetworkStatsManagerTest.this.notify();
+ }
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "exercising remote host failed.", e);
+ success = false;
+ }
+ }
+ }
+
+ private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
+ throws Exception {
+ boolean hasFeature = mPm.hasSystemFeature(
+ mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
+ if (!hasFeature) {
+ return false;
+ }
+ NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL));
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType())
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build(), callback);
+ synchronized (this) {
+ try {
+ wait((int) (TIMEOUT_MILLIS * 1.2));
+ } catch (InterruptedException e) {
+ }
+ }
+ if (callback.success) {
+ mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
+ mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
+ return true;
+ }
+
+ // This will always fail at this point as we know 'hasFeature' is true.
+ assertFalse(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature()
+ + " is a reported system feature, "
+ + "however no corresponding connected network interface was found or the attempt "
+ + "to connect has timed out (timeout = " + TIMEOUT_MILLIS + "ms)."
+ + mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
+ return false;
+ }
+
+ private String getSubscriberId(int networkIndex) {
+ int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
+ if (ConnectivityManager.TYPE_MOBILE == networkType) {
+ TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
+ .getSystemService(Context.TELEPHONY_SERVICE);
+ return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
+ (telephonyManager) -> telephonyManager.getSubscriberId());
+ }
+ return "";
+ }
+
+ @AppModeFull
+ public void testDeviceSummary() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats.Bucket bucket = null;
+ try {
+ bucket = mNsm.querySummaryForDevice(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ } catch (RemoteException | SecurityException e) {
+ fail("testDeviceSummary fails with exception: " + e.toString());
+ }
+ assertNotNull(bucket);
+ assertTimestamps(bucket);
+ assertEquals(bucket.getState(), STATE_ALL);
+ assertEquals(bucket.getUid(), UID_ALL);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ bucket = mNsm.querySummaryForDevice(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ fail("negative testDeviceSummary fails: no exception thrown.");
+ } catch (RemoteException e) {
+ fail("testDeviceSummary fails with exception: " + e.toString());
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testUserSummary() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats.Bucket bucket = null;
+ try {
+ bucket = mNsm.querySummaryForUser(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ } catch (RemoteException | SecurityException e) {
+ fail("testUserSummary fails with exception: " + e.toString());
+ }
+ assertNotNull(bucket);
+ assertTimestamps(bucket);
+ assertEquals(bucket.getState(), STATE_ALL);
+ assertEquals(bucket.getUid(), UID_ALL);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ bucket = mNsm.querySummaryForUser(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ fail("negative testUserSummary fails: no exception thrown.");
+ } catch (RemoteException e) {
+ fail("testUserSummary fails with exception: " + e.toString());
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testAppSummary() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Use tolerance value that large enough to make sure stats of at
+ // least one bucket is included. However, this is possible that
+ // the test will see data of different app but with the same UID
+ // that created before testing.
+ // TODO: Consider query stats before testing and use the difference to verify.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ result = mNsm.querySummary(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ assertNotNull(result);
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ long totalTxBytes = 0;
+ long totalRxBytes = 0;
+ boolean hasCorrectMetering = false;
+ boolean hasCorrectDefaultStatus = false;
+ int expectedMetering = mNetworkInterfacesToTest[i].getMetered()
+ ? METERED_YES : METERED_NO;
+ int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault()
+ ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ assertTimestamps(bucket);
+ hasCorrectMetering |= bucket.getMetered() == expectedMetering;
+ if (bucket.getUid() == Process.myUid()) {
+ totalTxPackets += bucket.getTxPackets();
+ totalRxPackets += bucket.getRxPackets();
+ totalTxBytes += bucket.getTxBytes();
+ totalRxBytes += bucket.getRxBytes();
+ hasCorrectDefaultStatus |=
+ bucket.getDefaultNetworkStatus() == expectedDefaultStatus;
+ }
+ }
+ assertFalse(result.getNextBucket(bucket));
+ assertTrue("Incorrect metering for NetworkType: "
+ + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering);
+ assertTrue("Incorrect isDefault for NetworkType: "
+ + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus);
+ assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
+ assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
+ assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
+ assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.querySummary(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ fail("negative testAppSummary fails: no exception thrown.");
+ } catch (RemoteException e) {
+ fail("testAppSummary fails with exception: " + e.toString());
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testAppDetails() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ result = mNsm.queryDetails(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result);
+
+ // Test without filtering by subscriberId
+ result = mNsm.queryDetails(
+ mNetworkInterfacesToTest[i].getNetworkType(), null,
+ mStartTime, mEndTime);
+
+ assertTrue("More bytes with subscriberId filter than without.",
+ getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId);
+ } catch (RemoteException | SecurityException e) {
+ fail("testAppDetails fails with exception: " + e.toString());
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.queryDetails(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime);
+ fail("negative testAppDetails fails: no exception thrown.");
+ } catch (RemoteException e) {
+ fail("testAppDetails fails with exception: " + e.toString());
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testUidDetails() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ result = mNsm.queryDetailsForUid(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid());
+ assertNotNull(result);
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ long totalTxBytes = 0;
+ long totalRxBytes = 0;
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ assertTimestamps(bucket);
+ assertEquals(bucket.getState(), STATE_ALL);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ assertEquals(bucket.getUid(), Process.myUid());
+ totalTxPackets += bucket.getTxPackets();
+ totalRxPackets += bucket.getRxPackets();
+ totalTxBytes += bucket.getTxBytes();
+ totalRxBytes += bucket.getRxBytes();
+ }
+ assertFalse(result.getNextBucket(bucket));
+ assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
+ assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
+ assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
+ assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.queryDetailsForUid(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid());
+ fail("negative testUidDetails fails: no exception thrown.");
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testTagDetails() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ result = mNsm.queryDetailsForUidTag(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+ assertNotNull(result);
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ long totalTxBytes = 0;
+ long totalRxBytes = 0;
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ assertTimestamps(bucket);
+ assertEquals(bucket.getState(), STATE_ALL);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ assertEquals(bucket.getUid(), Process.myUid());
+ if (bucket.getTag() == NETWORK_TAG) {
+ totalTxPackets += bucket.getTxPackets();
+ totalRxPackets += bucket.getRxPackets();
+ totalTxBytes += bucket.getTxBytes();
+ totalRxBytes += bucket.getRxBytes();
+ }
+ }
+ assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+ + " for uid " + Process.myUid(), totalRxBytes > 0);
+ assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+ + " for uid " + Process.myUid(), totalRxPackets > 0);
+ assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+ + " for uid " + Process.myUid(), totalTxBytes > 0);
+ assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+ + " for uid " + Process.myUid(), totalTxPackets > 0);
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.queryDetailsForUidTag(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+ fail("negative testUidDetails fails: no exception thrown.");
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ class QueryResult {
+ public final int tag;
+ public final int state;
+ public final long total;
+
+ QueryResult(int tag, int state, NetworkStats stats) {
+ this.tag = tag;
+ this.state = state;
+ total = getTotalAndAssertNotEmpty(stats, tag, state);
+ }
+
+ public String toString() {
+ return String.format("QueryResult(tag=%s state=%s total=%d)",
+ tagToString(tag), stateToString(state), total);
+ }
+ }
+
+ private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) {
+ return mNsm.queryDetailsForUidTagState(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid(), tag, state);
+ }
+
+ private void assertWithinPercentage(String msg, long expected, long actual, int percentage) {
+ long lowerBound = expected * (100 - percentage) / 100;
+ long upperBound = expected * (100 + percentage) / 100;
+ msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected);
+ assertTrue(msg, lowerBound <= actual);
+ assertTrue(msg, upperBound >= actual);
+ }
+
+ private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag,
+ int expectedState, long maxUnexpected) {
+ long total = 0;
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ total += bucket.getRxBytes() + bucket.getTxBytes();
+ }
+ if (total <= maxUnexpected) return;
+
+ fail(String.format("More than %d bytes of traffic when querying for "
+ + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d",
+ maxUnexpected, tagToString(expectedTag), stateToString(expectedState),
+ bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()),
+ bucket.getRxBytes(), bucket.getTxBytes()));
+ }
+
+ @AppModeFull
+ public void testUidTagStateDetails() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+ NetworkStats result = null;
+ try {
+ int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT;
+ int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT;
+
+ int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE};
+ int[] statesWithTraffic = {currentState, STATE_ALL};
+ ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>();
+
+ int[] statesWithNoTraffic = {otherState};
+ int[] tagsWithNoTraffic = {NETWORK_TAG + 1};
+ ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>();
+
+ // Expect to see traffic when querying for any combination of a tag in
+ // tagsWithTraffic and a state in statesWithTraffic.
+ for (int tag : tagsWithTraffic) {
+ for (int state : statesWithTraffic) {
+ result = getNetworkStatsForTagState(i, tag, state);
+ resultsWithTraffic.add(new QueryResult(tag, state, result));
+ result.close();
+ result = null;
+ }
+ }
+
+ // Expect that the results are within a few percentage points of each other.
+ // This is ensures that FIN retransmits after the transfer is complete don't cause
+ // the test to be flaky. The test URL currently returns just over 100k so this
+ // should not be too noisy. It also ensures that the traffic sent by the test
+ // harness, which is untagged, won't cause a failure.
+ long firstTotal = resultsWithTraffic.get(0).total;
+ for (QueryResult queryResult : resultsWithTraffic) {
+ assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 10);
+ }
+
+ // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
+ // state in statesWithNoTraffic.
+ for (int tag : tagsWithNoTraffic) {
+ for (int state : statesWithTraffic) {
+ result = getNetworkStatsForTagState(i, tag, state);
+ assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
+ result.close();
+ result = null;
+ }
+ }
+ for (int tag : tagsWithTraffic) {
+ for (int state : statesWithNoTraffic) {
+ result = getNetworkStatsForTagState(i, tag, state);
+ assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
+ result.close();
+ result = null;
+ }
+ }
+ } finally {
+ if (result != null) {
+ result.close();
+ }
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+ try {
+ result = mNsm.queryDetailsForUidTag(
+ mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+ mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+ fail("negative testUidDetails fails: no exception thrown.");
+ } catch (SecurityException e) {
+ // expected outcome
+ }
+ }
+ }
+
+ @AppModeFull
+ public void testCallback() throws Exception {
+ for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+ // Relatively large tolerance to accommodate for history bucket size.
+ if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+ continue;
+ }
+ setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+
+ TestUsageCallback usageCallback = new TestUsageCallback();
+ HandlerThread thread = new HandlerThread("callback-thread");
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
+ getSubscriberId(i), THRESHOLD_BYTES, usageCallback, handler);
+
+ // TODO: Force traffic and check whether the callback is invoked.
+ // Right now the test only covers whether the callback can be registered, but not
+ // whether it is invoked upon data usage since we don't have a scalable way of
+ // storing files of >2MB in CTS.
+
+ mNsm.unregisterUsageCallback(usageCallback);
+ }
+ }
+
+ private String tagToString(Integer tag) {
+ if (tag == null) return "null";
+ switch (tag) {
+ case TAG_NONE:
+ return "TAG_NONE";
+ default:
+ return "0x" + Integer.toHexString(tag);
+ }
+ }
+
+ private String stateToString(Integer state) {
+ if (state == null) return "null";
+ switch (state) {
+ case STATE_ALL:
+ return "STATE_ALL";
+ case STATE_DEFAULT:
+ return "STATE_DEFAULT";
+ case STATE_FOREGROUND:
+ return "STATE_FOREGROUND";
+ }
+ throw new IllegalArgumentException("Unknown state " + state);
+ }
+
+ private long getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag,
+ Integer expectedState) {
+ assertTrue(result != null);
+ NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+ long totalTxPackets = 0;
+ long totalRxPackets = 0;
+ long totalTxBytes = 0;
+ long totalRxBytes = 0;
+ while (result.hasNextBucket()) {
+ assertTrue(result.getNextBucket(bucket));
+ assertTimestamps(bucket);
+ if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag);
+ if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState);
+ assertEquals(bucket.getMetered(), METERED_ALL);
+ assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+ if (bucket.getUid() == Process.myUid()) {
+ totalTxPackets += bucket.getTxPackets();
+ totalRxPackets += bucket.getRxPackets();
+ totalTxBytes += bucket.getTxBytes();
+ totalRxBytes += bucket.getRxBytes();
+ }
+ }
+ assertFalse(result.getNextBucket(bucket));
+ String msg = String.format("uid %d tag %s state %s",
+ Process.myUid(), tagToString(expectedTag), stateToString(expectedState));
+ assertTrue("No Rx bytes usage for " + msg, totalRxBytes > 0);
+ assertTrue("No Rx packets usage for " + msg, totalRxPackets > 0);
+ assertTrue("No Tx bytes usage for " + msg, totalTxBytes > 0);
+ assertTrue("No Tx packets usage for " + msg, totalTxPackets > 0);
+
+ return totalRxBytes + totalTxBytes;
+ }
+
+ private long getTotalAndAssertNotEmpty(NetworkStats result) {
+ return getTotalAndAssertNotEmpty(result, null, STATE_ALL);
+ }
+
+ private void assertTimestamps(final NetworkStats.Bucket bucket) {
+ assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than "
+ + mStartTime, bucket.getStartTimeStamp() >= mStartTime);
+ assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than "
+ + mEndTime, bucket.getEndTimeStamp() <= mEndTime);
+ }
+
+ private static class TestUsageCallback extends NetworkStatsManager.UsageCallback {
+ @Override
+ public void onThresholdReached(int networkType, String subscriberId) {
+ Log.v(LOG_TAG, "Called onThresholdReached for networkType=" + networkType
+ + " subscriberId=" + subscriberId);
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 9307c27..9506081 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -15,6 +15,19 @@
*/
package android.net.cts
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkRequest
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.net.TestNetworkSpecifier
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
@@ -32,14 +45,27 @@
import android.net.nsd.NsdManager.RegistrationListener
import android.net.nsd.NsdManager.ResolveListener
import android.net.nsd.NsdServiceInfo
+import android.os.HandlerThread
import android.platform.test.annotations.AppModeFull
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
+import com.android.networkstack.apishim.ConstantsShim
+import com.android.networkstack.apishim.NsdShimImpl
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.SC_V2
+import com.android.testutils.TestableNetworkAgent
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.runAsShell
+import com.android.testutils.tryTest
+import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.net.ServerSocket
@@ -57,12 +83,37 @@
private const val TIMEOUT_MS = 2000L
private const val DBG = false
+private val nsdShim = NsdShimImpl.newInstance()
+
@AppModeFull(reason = "Socket cannot bind in instant app mode")
@RunWith(AndroidJUnit4::class)
class NsdManagerTest {
+ // NsdManager is not updatable before S, so tests do not need to be backwards compatible
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
- private val serviceName = "NsdTest%04d".format(Random().nextInt(1000))
+
+ private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+ private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
+ private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
+
+ private lateinit var testNetwork1: TestTapNetwork
+ private lateinit var testNetwork2: TestTapNetwork
+
+ private class TestTapNetwork(
+ val iface: TestNetworkInterface,
+ val requestCb: NetworkCallback,
+ val agent: TestableNetworkAgent,
+ val network: Network
+ ) {
+ fun close(cm: ConnectivityManager) {
+ cm.unregisterNetworkCallback(requestCb)
+ agent.unregister()
+ iface.fileDescriptor.close()
+ }
+ }
private interface NsdEvent
private open class NsdRecord<T : NsdEvent> private constructor(
@@ -163,9 +214,14 @@
add(ServiceLost(si))
}
- fun waitForServiceDiscovered(serviceName: String): NsdServiceInfo {
+ fun waitForServiceDiscovered(
+ serviceName: String,
+ expectedNetwork: Network? = null
+ ): NsdServiceInfo {
return expectCallbackEventually<ServiceFound> {
- it.serviceInfo.serviceName == serviceName
+ it.serviceInfo.serviceName == serviceName &&
+ (expectedNetwork == null ||
+ expectedNetwork == nsdShim.getNetwork(it.serviceInfo))
}.serviceInfo
}
}
@@ -188,6 +244,58 @@
}
}
+ @Before
+ fun setUp() {
+ handlerThread.start()
+
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1 = createTestNetwork()
+ testNetwork2 = createTestNetwork()
+ }
+ }
+
+ private fun createTestNetwork(): TestTapNetwork {
+ val tnm = context.getSystemService(TestNetworkManager::class.java)
+ val iface = tnm.createTapInterface()
+ val cb = TestableNetworkCallback()
+ val testNetworkSpecifier = TestNetworkSpecifier(iface.interfaceName)
+ cm.requestNetwork(NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .addTransportType(TRANSPORT_TEST)
+ .setNetworkSpecifier(testNetworkSpecifier)
+ .build(), cb)
+ val agent = registerTestNetworkAgent(iface.interfaceName)
+ val network = agent.network ?: fail("Registered agent should have a network")
+ // The network has no INTERNET capability, so will be marked validated immediately
+ cb.expectAvailableThenValidatedCallbacks(network)
+ return TestTapNetwork(iface, cb, agent, network)
+ }
+
+ private fun registerTestNetworkAgent(ifaceName: String): TestableNetworkAgent {
+ val agent = TestableNetworkAgent(context, handlerThread.looper,
+ NetworkCapabilities().apply {
+ removeCapability(NET_CAPABILITY_TRUSTED)
+ addTransportType(TRANSPORT_TEST)
+ setNetworkSpecifier(TestNetworkSpecifier(ifaceName))
+ },
+ LinkProperties().apply {
+ interfaceName = ifaceName
+ },
+ NetworkAgentConfig.Builder().build())
+ agent.register()
+ agent.markConnected()
+ return agent
+ }
+
+ @After
+ fun tearDown() {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ testNetwork1.close(cm)
+ testNetwork2.close(cm)
+ }
+ handlerThread.quitSafely()
+ }
+
@Test
fun testNsdManager() {
val si = NsdServiceInfo()
@@ -298,6 +406,149 @@
registrationRecord2.expectCallback<ServiceUnregistered>()
}
+ @Test
+ fun testNsdManager_DiscoverOnNetwork() {
+ // This tests requires shims supporting T+ APIs (discovering on specific network)
+ assumeTrue(ConstantsShim.VERSION > SC_V2)
+
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = this.serviceName
+ si.port = 12345 // Test won't try to connect so port does not matter
+
+ val registrationRecord = NsdRegistrationRecord()
+ val registeredInfo = registerService(registrationRecord, si)
+
+ tryTest {
+ val discoveryRecord = NsdDiscoveryRecord()
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, discoveryRecord)
+
+ val foundInfo = discoveryRecord.waitForServiceDiscovered(
+ serviceName, testNetwork1.network)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
+
+ // Rewind to ensure the service is not found on the other interface
+ discoveryRecord.nextEvents.rewind(0)
+ assertNull(discoveryRecord.nextEvents.poll(timeoutMs = 100L) {
+ it is ServiceFound &&
+ it.serviceInfo.serviceName == registeredInfo.serviceName &&
+ nsdShim.getNetwork(it.serviceInfo) != testNetwork1.network
+ }, "The service should not be found on this network")
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ }
+ }
+
+ @Test
+ fun testNsdManager_DiscoverWithNetworkRequest() {
+ // This tests requires shims supporting T+ APIs (discovering on network request)
+ assumeTrue(ConstantsShim.VERSION > SC_V2)
+
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = this.serviceName
+ si.port = 12345 // Test won't try to connect so port does not matter
+
+ val registrationRecord = NsdRegistrationRecord()
+ val registeredInfo1 = registerService(registrationRecord, si)
+ val discoveryRecord = NsdDiscoveryRecord()
+
+ tryTest {
+ val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .addTransportType(TRANSPORT_TEST)
+ .setNetworkSpecifier(specifier)
+ .build(),
+ discoveryRecord)
+
+ val discoveryStarted = discoveryRecord.expectCallback<DiscoveryStarted>()
+ assertEquals(SERVICE_TYPE, discoveryStarted.serviceType)
+
+ val serviceDiscovered = discoveryRecord.expectCallback<ServiceFound>()
+ assertEquals(registeredInfo1.serviceName, serviceDiscovered.serviceInfo.serviceName)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered.serviceInfo))
+
+ // Unregister, then register the service back: it should be lost and found again
+ nsdManager.unregisterService(registrationRecord)
+ val serviceLost1 = discoveryRecord.expectCallback<ServiceLost>()
+ assertEquals(registeredInfo1.serviceName, serviceLost1.serviceInfo.serviceName)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost1.serviceInfo))
+
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ val registeredInfo2 = registerService(registrationRecord, si)
+ val serviceDiscovered2 = discoveryRecord.expectCallback<ServiceFound>()
+ assertEquals(registeredInfo2.serviceName, serviceDiscovered2.serviceInfo.serviceName)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered2.serviceInfo))
+
+ // Teardown, then bring back up a network on the test interface: the service should
+ // go away, then come back
+ testNetwork1.agent.unregister()
+ val serviceLost = discoveryRecord.expectCallback<ServiceLost>()
+ assertEquals(registeredInfo2.serviceName, serviceLost.serviceInfo.serviceName)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost.serviceInfo))
+
+ val newAgent = runAsShell(MANAGE_TEST_NETWORKS) {
+ registerTestNetworkAgent(testNetwork1.iface.interfaceName)
+ }
+ val newNetwork = newAgent.network ?: fail("Registered agent should have a network")
+ val serviceDiscovered3 = discoveryRecord.expectCallback<ServiceFound>()
+ assertEquals(registeredInfo2.serviceName, serviceDiscovered3.serviceInfo.serviceName)
+ assertEquals(newNetwork, nsdShim.getNetwork(serviceDiscovered3.serviceInfo))
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ }
+ }
+
+ @Test
+ fun testNsdManager_ResolveOnNetwork() {
+ // This tests requires shims supporting T+ APIs (NsdServiceInfo.network)
+ assumeTrue(ConstantsShim.VERSION > SC_V2)
+
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = this.serviceName
+ si.port = 12345 // Test won't try to connect so port does not matter
+
+ val registrationRecord = NsdRegistrationRecord()
+ val registeredInfo = registerService(registrationRecord, si)
+ tryTest {
+ val resolveRecord = NsdResolveRecord()
+
+ val discoveryRecord = NsdDiscoveryRecord()
+ nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+
+ val foundInfo1 = discoveryRecord.waitForServiceDiscovered(
+ serviceName, testNetwork1.network)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo1))
+ // Rewind as the service could be found on each interface in any order
+ discoveryRecord.nextEvents.rewind(0)
+ val foundInfo2 = discoveryRecord.waitForServiceDiscovered(
+ serviceName, testNetwork2.network)
+ assertEquals(testNetwork2.network, nsdShim.getNetwork(foundInfo2))
+
+ nsdManager.resolveService(foundInfo1, resolveRecord)
+ val cb = resolveRecord.expectCallback<ServiceResolved>()
+ cb.serviceInfo.let {
+ // Resolved service type has leading dot
+ assertEquals(".$SERVICE_TYPE", it.serviceType)
+ assertEquals(registeredInfo.serviceName, it.serviceName)
+ assertEquals(si.port, it.port)
+ assertEquals(testNetwork1.network, nsdShim.getNetwork(it))
+ }
+ // TODO: check that MDNS packets are sent only on testNetwork1.
+ } cleanupStep {
+ nsdManager.unregisterService(registrationRecord)
+ } cleanup {
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
/**
* Register a service and return its registration record.
*/
diff --git a/tests/common/java/android/net/StaticIpConfigurationTest.java b/tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
similarity index 81%
rename from tests/common/java/android/net/StaticIpConfigurationTest.java
rename to tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
index b5f23bf..9b2756c 100644
--- a/tests/common/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/cts/net/src/android/net/cts/StaticIpConfigurationTest.java
@@ -14,20 +14,30 @@
* limitations under the License.
*/
-package android.net;
+package android.net.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.os.Build;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,15 +52,20 @@
private static final String ADDRSTR = "192.0.2.2/25";
private static final LinkAddress ADDR = new LinkAddress(ADDRSTR);
- private static final InetAddress GATEWAY = IpAddress("192.0.2.1");
- private static final InetAddress OFFLINKGATEWAY = IpAddress("192.0.2.129");
- private static final InetAddress DNS1 = IpAddress("8.8.8.8");
- private static final InetAddress DNS2 = IpAddress("8.8.4.4");
- private static final InetAddress DNS3 = IpAddress("4.2.2.2");
+ private static final InetAddress GATEWAY = ipAddress("192.0.2.1");
+ private static final InetAddress OFFLINKGATEWAY = ipAddress("192.0.2.129");
+ private static final InetAddress DNS1 = ipAddress("8.8.8.8");
+ private static final InetAddress DNS2 = ipAddress("8.8.4.4");
+ private static final InetAddress DNS3 = ipAddress("4.2.2.2");
+ private static final InetAddress IPV6_ADDRESS = ipAddress("2001:4860:800d::68");
+ private static final LinkAddress IPV6_LINK_ADDRESS = new LinkAddress("2001:db8::1/64");
private static final String IFACE = "eth0";
private static final String FAKE_DOMAINS = "google.com";
- private static InetAddress IpAddress(String addr) {
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
+ private static InetAddress ipAddress(String addr) {
return InetAddress.parseNumericAddress(addr);
}
@@ -241,6 +256,29 @@
assertEquals(DNS1, s.getDnsServers().get(0));
}
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+ @Test
+ public void testIllegalBuilders() {
+ assertThrows("Can't set IP Address to IPv6!", IllegalArgumentException.class, () -> {
+ StaticIpConfiguration.Builder b = new StaticIpConfiguration.Builder().setIpAddress(
+ IPV6_LINK_ADDRESS);
+ });
+
+ assertThrows("Can't set gateway to IPv6!", IllegalArgumentException.class, () -> {
+ StaticIpConfiguration.Builder b = new StaticIpConfiguration.Builder().setGateway(
+ IPV6_ADDRESS);
+ });
+
+ assertThrows("Can't set DNS servers using IPv6!", IllegalArgumentException.class, () -> {
+ final ArrayList<InetAddress> dnsServers = new ArrayList<>();
+ dnsServers.add(DNS1);
+ dnsServers.add(IPV6_ADDRESS);
+
+ StaticIpConfiguration.Builder b = new StaticIpConfiguration.Builder().setDnsServers(
+ dnsServers);
+ });
+ }
+
@Test
public void testAddDnsServers() {
final StaticIpConfiguration s = new StaticIpConfiguration((StaticIpConfiguration) null);
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 4d4e7b9..530fa91 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -53,7 +53,8 @@
// android_library does not include JNI libs: include NetworkStack dependencies here
"libnativehelper_compat_libc++",
"libnetworkstackutilsjni",
- "libcom_android_connectivity_com_android_net_module_util_jni",
+ "libandroid_net_connectivity_com_android_net_module_util_jni",
+ "libservice-connectivity",
],
jarjar_rules: ":connectivity-jarjar-rules",
}
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 4dc86ff..365c0cf 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -21,6 +21,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -109,6 +110,9 @@
case TRANSPORT_WIFI_AWARE:
mScore = new NetworkScore.Builder().setLegacyInt(20).build();
break;
+ case TRANSPORT_TEST:
+ mScore = new NetworkScore.Builder().build();
+ break;
case TRANSPORT_VPN:
mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
// VPNs deduce the SUSPENDED capability from their underlying networks and there
diff --git a/tests/unit/jarjar-rules.txt b/tests/unit/jarjar-rules.txt
index ca88672..eb3e32a 100644
--- a/tests/unit/jarjar-rules.txt
+++ b/tests/unit/jarjar-rules.txt
@@ -1,2 +1,3 @@
# Module library in frameworks/libs/net
rule com.android.net.module.util.** android.net.frameworktests.util.@1
+rule com.android.testutils.TestBpfMap* android.net.frameworktests.testutils.TestBpfMap@1
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index ca8cf07..e5e7ebc 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.Network;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -123,6 +124,7 @@
fullInfo.setServiceType("_kitten._tcp");
fullInfo.setPort(4242);
fullInfo.setHost(LOCALHOST);
+ fullInfo.setNetwork(new Network(123));
checkParcelable(fullInfo);
NsdServiceInfo noHostInfo = new NsdServiceInfo();
@@ -172,6 +174,7 @@
assertEquals(original.getServiceType(), result.getServiceType());
assertEquals(original.getHost(), result.getHost());
assertTrue(original.getPort() == result.getPort());
+ assertEquals(original.getNetwork(), result.getNetwork());
// Assert equality of attribute map.
Map<String, byte[]> originalMap = original.getAttributes();
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
new file mode 100644
index 0000000..ac21e77
--- /dev/null
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
+import static android.net.INetd.FIREWALL_RULE_ALLOW;
+import static android.net.INetd.PERMISSION_INTERNET;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.Mockito.verify;
+
+import android.net.INetd;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public final class BpfNetMapsTest {
+ private static final String TAG = "BpfNetMapsTest";
+ private static final int TEST_UID = 10086;
+ private static final int[] TEST_UIDS = {10002, 10003};
+ private static final String IFNAME = "wlan0";
+ private static final String CHAINNAME = "fw_dozable";
+ private BpfNetMaps mBpfNetMaps;
+
+ @Mock INetd mNetd;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mBpfNetMaps = new BpfNetMaps(mNetd);
+ }
+
+ @Test
+ public void testBpfNetMapsBeforeT() throws Exception {
+ assumeFalse(SdkLevel.isAtLeastT());
+ mBpfNetMaps.addNaughtyApp(TEST_UID);
+ verify(mNetd).bandwidthAddNaughtyApp(TEST_UID);
+ mBpfNetMaps.removeNaughtyApp(TEST_UID);
+ verify(mNetd).bandwidthRemoveNaughtyApp(TEST_UID);
+ mBpfNetMaps.addNiceApp(TEST_UID);
+ verify(mNetd).bandwidthAddNiceApp(TEST_UID);
+ mBpfNetMaps.removeNiceApp(TEST_UID);
+ verify(mNetd).bandwidthRemoveNiceApp(TEST_UID);
+ mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true);
+ verify(mNetd).firewallEnableChildChain(FIREWALL_CHAIN_DOZABLE, true);
+ mBpfNetMaps.replaceUidChain(CHAINNAME, true, TEST_UIDS);
+ verify(mNetd).firewallReplaceUidChain(CHAINNAME, true, TEST_UIDS);
+ mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW);
+ verify(mNetd).firewallSetUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW);
+ mBpfNetMaps.addUidInterfaceRules(IFNAME, TEST_UIDS);
+ verify(mNetd).firewallAddUidInterfaceRules(IFNAME, TEST_UIDS);
+ mBpfNetMaps.removeUidInterfaceRules(TEST_UIDS);
+ verify(mNetd).firewallRemoveUidInterfaceRules(TEST_UIDS);
+ mBpfNetMaps.swapActiveStatsMap();
+ verify(mNetd).trafficSwapActiveStatsMap();
+ mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+ verify(mNetd).trafficSetNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+ }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 652aee9..0132525 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,7 @@
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.GET_INTENT_SENDER_INTENT;
import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_FACTORY;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STACK;
@@ -108,6 +109,7 @@
import static android.net.NetworkCapabilities.REDACT_NONE;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -262,6 +264,7 @@
import android.net.RouteInfo;
import android.net.RouteInfoParcel;
import android.net.SocketKeepalive;
+import android.net.TelephonyNetworkSpecifier;
import android.net.TransportInfo;
import android.net.UidRange;
import android.net.UidRangeParcel;
@@ -332,6 +335,7 @@
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ConnectivityFlags;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
@@ -526,6 +530,8 @@
@Mock SystemConfigManager mSystemConfigManager;
@Mock Resources mResources;
@Mock PacProxyManager mPacProxyManager;
+ @Mock BpfNetMaps mBpfNetMaps;
+ @Mock CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
// BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
// underlying binder calls.
@@ -967,8 +973,6 @@
* @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
*/
public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
- assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET));
-
ConnectivityManager.NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
if (validated) {
@@ -1490,6 +1494,10 @@
r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
}
+ private UidRangeParcel[] intToUidRangeStableParcels(final @NonNull Set<Integer> ranges) {
+ return ranges.stream().map(r -> new UidRangeParcel(r, r)).toArray(UidRangeParcel[]::new);
+ }
+
private VpnManagerService makeVpnManagerService() {
final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
public int getCallingUid() {
@@ -1852,6 +1860,12 @@
}
@Override
+ public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
+ @NonNull final Context context, @NonNull final TelephonyManager tm) {
+ return SdkLevel.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
+ }
+
+ @Override
public boolean intentFilterEquals(final PendingIntent a, final PendingIntent b) {
return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
}
@@ -1944,6 +1958,11 @@
return super.isFeatureEnabled(context, name, defaultEnabled);
}
}
+
+ @Override
+ public BpfNetMaps getBpfNetMaps(INetd netd) {
+ return mBpfNetMaps;
+ }
}
private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) {
@@ -3813,14 +3832,14 @@
public void testNoMutableNetworkRequests() throws Exception {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
- NetworkRequest request1 = new NetworkRequest.Builder()
+ final NetworkRequest request1 = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_VALIDATED)
.build();
- NetworkRequest request2 = new NetworkRequest.Builder()
+ final NetworkRequest request2 = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
.build();
- Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback()));
assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent));
assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback()));
@@ -3828,6 +3847,36 @@
}
@Test
+ public void testNoAccessUidsInNetworkRequests() throws Exception {
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+ final NetworkRequest r = new NetworkRequest.Builder().build();
+ final ArraySet<Integer> accessUids = new ArraySet<>();
+ accessUids.add(6);
+ accessUids.add(9);
+ r.networkCapabilities.setAccessUids(accessUids);
+
+ final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+ final NetworkCallback cb = new NetworkCallback();
+
+ final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+ assertThrows(expected, () -> mCm.requestNetwork(r, cb));
+ assertThrows(expected, () -> mCm.requestNetwork(r, pendingIntent));
+ assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb));
+ assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb, handler));
+ assertThrows(expected, () -> mCm.registerNetworkCallback(r, pendingIntent));
+ assertThrows(expected, () -> mCm.registerBestMatchingNetworkCallback(r, cb, handler));
+
+ // Make sure that resetting the access UIDs to the empty set will allow calling
+ // requestNetwork and registerNetworkCallback.
+ r.networkCapabilities.setAccessUids(Collections.emptySet());
+ mCm.requestNetwork(r, cb);
+ mCm.unregisterNetworkCallback(cb);
+ mCm.registerNetworkCallback(r, cb);
+ mCm.unregisterNetworkCallback(cb);
+ }
+
+ @Test
public void testMMSonWiFi() throws Exception {
// Test bringing up cellular without MMS NetworkRequest gets reaped
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -5727,6 +5776,22 @@
}
}
+ /**
+ * Validate the callback flow CBS request without carrier privilege.
+ */
+ @Test
+ public void testCBSRequestWithoutCarrierPrivilege() throws Exception {
+ final NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+ TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_CBS).build();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+
+ mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+ // Now file the test request and expect it.
+ mCm.requestNetwork(nr, networkCallback);
+ networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }
@@ -10074,7 +10139,7 @@
// A connected VPN should have interface rules set up. There are two expected invocations,
// one during the VPN initial connection, one during the VPN LinkProperties update.
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
- verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
@@ -10083,7 +10148,7 @@
waitForIdle();
// Disconnected VPN should have interface rules removed
- verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
}
@@ -10100,7 +10165,7 @@
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
// Legacy VPN should not have interface rules set up
- verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
}
@Test
@@ -10116,7 +10181,7 @@
assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
// IPv6 unreachable route should not be misinterpreted as a default route
- verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
}
@Test
@@ -10133,33 +10198,33 @@
// Connected VPN should have interface rules set up. There are two expected invocations,
// one during VPN uid update, one during VPN LinkProperties update
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
- verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
- reset(mMockNetd);
- InOrder inOrder = inOrder(mMockNetd);
+ reset(mBpfNetMaps);
+ InOrder inOrder = inOrder(mBpfNetMaps);
lp.setInterfaceName("tun1");
mMockVpn.sendLinkProperties(lp);
waitForIdle();
// VPN handover (switch to a new interface) should result in rules being updated (old rules
// removed first, then new rules added)
- inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ inOrder.verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- reset(mMockNetd);
+ reset(mBpfNetMaps);
lp = new LinkProperties();
lp.setInterfaceName("tun1");
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
mMockVpn.sendLinkProperties(lp);
waitForIdle();
// VPN not routing everything should no longer have interface filtering rules
- verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- reset(mMockNetd);
+ reset(mBpfNetMaps);
lp = new LinkProperties();
lp.setInterfaceName("tun1");
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
@@ -10167,7 +10232,7 @@
mMockVpn.sendLinkProperties(lp);
waitForIdle();
// Back to routing all IPv6 traffic should have filtering rules
- verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
}
@@ -10196,8 +10261,8 @@
mMockVpn.establish(lp, VPN_UID, vpnRanges);
assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
- reset(mMockNetd);
- InOrder inOrder = inOrder(mMockNetd);
+ reset(mBpfNetMaps);
+ InOrder inOrder = inOrder(mBpfNetMaps);
// Update to new range which is old range minus APP1, i.e. only APP2
final Set<UidRange> newRanges = new HashSet<>(asList(
@@ -10208,9 +10273,9 @@
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
// Verify old rules are removed before new rules are added
- inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ inOrder.verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
- inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
assertContainsExactly(uidCaptor.getValue(), APP2_UID);
}
@@ -14555,6 +14620,189 @@
() -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback()));
}
+ @Test
+ public void testAccessUids() throws Exception {
+ final int preferenceOrder =
+ ConnectivityService.PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_TEST)
+ .build(),
+ cb);
+
+ final ArraySet<Integer> uids = new ArraySet<>();
+ uids.add(200);
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setAccessUids(uids)
+ .build();
+ final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(TRANSPORT_TEST,
+ new LinkProperties(), nc);
+ agent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(agent);
+
+ final InOrder inOrder = inOrder(mMockNetd);
+ final NativeUidRangeConfig uids200Parcel = new NativeUidRangeConfig(
+ agent.getNetwork().getNetId(),
+ intToUidRangeStableParcels(uids),
+ preferenceOrder);
+ if (SdkLevel.isAtLeastT()) {
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+ }
+
+ uids.add(300);
+ uids.add(400);
+ nc.setAccessUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ } else {
+ cb.assertNoCallback();
+ }
+
+ uids.remove(200);
+ final NativeUidRangeConfig uids300400Parcel = new NativeUidRangeConfig(
+ agent.getNetwork().getNetId(),
+ intToUidRangeStableParcels(uids),
+ preferenceOrder);
+ if (SdkLevel.isAtLeastT()) {
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+ }
+
+ nc.setAccessUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+ } else {
+ cb.assertNoCallback();
+ }
+
+ uids.clear();
+ uids.add(600);
+ nc.setAccessUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+ } else {
+ cb.assertNoCallback();
+ }
+ final NativeUidRangeConfig uids600Parcel = new NativeUidRangeConfig(
+ agent.getNetwork().getNetId(),
+ intToUidRangeStableParcels(uids),
+ preferenceOrder);
+ if (SdkLevel.isAtLeastT()) {
+ inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+ }
+
+ uids.clear();
+ nc.setAccessUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
+ inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+ } else {
+ cb.assertNoCallback();
+ verify(mMockNetd, never()).networkAddUidRangesParcel(any());
+ verify(mMockNetd, never()).networkRemoveUidRangesParcel(any());
+ }
+
+ }
+
+ @Test
+ public void testCbsAccessUids() throws Exception {
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+
+ // In this test TEST_PACKAGE_UID will be the UID of the carrier service UID.
+ doReturn(true).when(mCarrierPrivilegeAuthenticator)
+ .hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+
+ final ArraySet<Integer> serviceUidSet = new ArraySet<>();
+ serviceUidSet.add(TEST_PACKAGE_UID);
+ final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
+ nonServiceUidSet.add(TEST_PACKAGE_UID2);
+ final ArraySet<Integer> serviceUidSetPlus = new ArraySet<>();
+ serviceUidSetPlus.add(TEST_PACKAGE_UID);
+ serviceUidSetPlus.add(TEST_PACKAGE_UID2);
+
+ final TestNetworkCallback cb = new TestNetworkCallback();
+
+ // Simulate a restricted telephony network. The telephony factory is entitled to set
+ // the access UID to the service package on any of its restricted networks.
+ final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
+
+ // Cell gets to set the service UID as access UID
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build(), cb);
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+ new LinkProperties(), ncb.build());
+ mCellNetworkAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ ncb.setAccessUids(serviceUidSet);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(mCellNetworkAgent,
+ caps -> caps.getAccessUids().equals(serviceUidSet));
+ } else {
+ // S must ignore access UIDs.
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+ }
+
+ // ...but not to some other UID. Rejection sets UIDs to the empty set
+ ncb.setAccessUids(nonServiceUidSet);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ if (SdkLevel.isAtLeastT()) {
+ cb.expectCapabilitiesThat(mCellNetworkAgent,
+ caps -> caps.getAccessUids().isEmpty());
+ } else {
+ // S must ignore access UIDs.
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+ }
+
+ // ...and also not to multiple UIDs even including the service UID
+ ncb.setAccessUids(serviceUidSetPlus);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+
+ mCellNetworkAgent.disconnect();
+ cb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ mCm.unregisterNetworkCallback(cb);
+
+ // Must be unset before touching the transports, because remove and add transport types
+ // check the specifier on the builder immediately, contradicting normal builder semantics
+ // TODO : fix the builder
+ ncb.setNetworkSpecifier(null);
+ ncb.removeTransportType(TRANSPORT_CELLULAR);
+ ncb.addTransportType(TRANSPORT_WIFI);
+ // Wifi does not get to set access UID, even to the correct UID
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build(), cb);
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
+ new LinkProperties(), ncb.build());
+ mWiFiNetworkAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+ ncb.setAccessUids(serviceUidSet);
+ mWiFiNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+ mCm.unregisterNetworkCallback(cb);
+ }
+
/**
* Validate request counts are counted accurately on setProfileNetworkPreference on set/replace.
*/
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 6d1d765..5086943 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -218,14 +218,14 @@
client.discoverServices("a_type", PROTOCOL, listener2);
waitForIdle();
verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommand("discover 3 a_type");
+ verifyDaemonCommand("discover 3 a_type 0");
// Client resolve request
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
client.resolveService(request, listener3);
waitForIdle();
verify(mDaemon, times(1)).maybeStart();
- verifyDaemonCommand("resolve 4 a_name a_type local.");
+ verifyDaemonCommand("resolve 4 a_name a_type local. 0");
// Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
deathRecipient.binderDied();
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
new file mode 100644
index 0000000..553cb83
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.connectivity;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.networkstack.apishim.TelephonyManagerShimImpl;
+import com.android.networkstack.apishim.common.TelephonyManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for CarrierPrivilegeAuthenticatorTest.
+ *
+ * Build, install and run with:
+ * runtest frameworks-net -c com.android.server.connectivity.CarrierPrivilegeAuthenticatorTest
+ */
+@RunWith(DevSdkIgnoreRunner.class)
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+public class CarrierPrivilegeAuthenticatorTest {
+ // TODO : use ConstantsShim.RECEIVER_NOT_EXPORTED when it's available in tests.
+ private static final int RECEIVER_NOT_EXPORTED = 4;
+ private static final int SUBSCRIPTION_COUNT = 2;
+ private static final int TEST_SUBSCRIPTION_ID = 1;
+
+ @NonNull private final Context mContext;
+ @NonNull private final TelephonyManager mTelephonyManager;
+ @NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
+ @NonNull private final PackageManager mPackageManager;
+ @NonNull private TestCarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
+ private final int mCarrierConfigPkgUid = 12345;
+ private final String mTestPkg = "com.android.server.connectivity.test";
+
+ public class TestCarrierPrivilegeAuthenticator extends CarrierPrivilegeAuthenticator {
+ TestCarrierPrivilegeAuthenticator(@NonNull final Context c,
+ @NonNull final TelephonyManager t) {
+ super(c, t, mTelephonyManagerShim);
+ }
+ @Override
+ protected int getSlotIndex(int subId) {
+ if (SubscriptionManager.DEFAULT_SUBSCRIPTION_ID == subId) return TEST_SUBSCRIPTION_ID;
+ return subId;
+ }
+ }
+
+ public CarrierPrivilegeAuthenticatorTest() {
+ mContext = mock(Context.class);
+ mTelephonyManager = mock(TelephonyManager.class);
+ mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class);
+ mPackageManager = mock(PackageManager.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ doReturn(SUBSCRIPTION_COUNT).when(mTelephonyManager).getActiveModemCount();
+ doReturn(mTestPkg).when(mTelephonyManagerShim)
+ .getCarrierServicePackageNameForLogicalSlot(anyInt());
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = mCarrierConfigPkgUid;
+ doReturn(applicationInfo).when(mPackageManager)
+ .getApplicationInfo(eq(mTestPkg), anyInt());
+ mCarrierPrivilegeAuthenticator =
+ new TestCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
+ }
+
+ private IntentFilter getIntentFilter() {
+ final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mContext).registerReceiver(any(), captor.capture(), any(), any(), anyInt());
+ return captor.getValue();
+ }
+
+ private List<TelephonyManagerShim.CarrierPrivilegesListenerShim>
+ getCarrierPrivilegesListeners() {
+ final ArgumentCaptor<TelephonyManagerShim.CarrierPrivilegesListenerShim> captor =
+ ArgumentCaptor.forClass(TelephonyManagerShim.CarrierPrivilegesListenerShim.class);
+ try {
+ verify(mTelephonyManagerShim, atLeastOnce())
+ .addCarrierPrivilegesListener(anyInt(), any(), captor.capture());
+ } catch (UnsupportedApiLevelException e) {
+ }
+ return captor.getAllValues();
+ }
+
+ private Intent buildTestMultiSimConfigBroadcastIntent() {
+ final Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
+ return intent;
+ }
+ @Test
+ public void testConstructor() throws Exception {
+ verify(mContext).registerReceiver(
+ eq(mCarrierPrivilegeAuthenticator),
+ any(IntentFilter.class),
+ any(),
+ any(),
+ eq(RECEIVER_NOT_EXPORTED));
+ final IntentFilter filter = getIntentFilter();
+ assertEquals(1, filter.countActions());
+ assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED));
+
+ verify(mTelephonyManagerShim, times(2))
+ .addCarrierPrivilegesListener(anyInt(), any(), any());
+ verify(mTelephonyManagerShim)
+ .addCarrierPrivilegesListener(eq(0), any(), any());
+ verify(mTelephonyManagerShim)
+ .addCarrierPrivilegesListener(eq(1), any(), any());
+ assertEquals(2, getCarrierPrivilegesListeners().size());
+
+ final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ new TelephonyNetworkSpecifier(0);
+ final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+ networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
+ }
+
+ @Test
+ public void testMultiSimConfigChanged() throws Exception {
+ doReturn(1).when(mTelephonyManager).getActiveModemCount();
+ final List<TelephonyManagerShim.CarrierPrivilegesListenerShim> carrierPrivilegesListeners =
+ getCarrierPrivilegesListeners();
+
+ mCarrierPrivilegeAuthenticator.onReceive(
+ mContext, buildTestMultiSimConfigBroadcastIntent());
+ for (TelephonyManagerShim.CarrierPrivilegesListenerShim carrierPrivilegesListener
+ : carrierPrivilegesListeners) {
+ verify(mTelephonyManagerShim)
+ .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener));
+ }
+
+ // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other
+ // (2 previously registered during startup, for slots 0 & 1)
+ verify(mTelephonyManagerShim, times(3))
+ .addCarrierPrivilegesListener(anyInt(), any(), any());
+ verify(mTelephonyManagerShim, times(2))
+ .addCarrierPrivilegesListener(eq(0), any(), any());
+
+ final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ new TelephonyNetworkSpecifier(0);
+ final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+ networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
+ }
+
+ @Test
+ public void testOnCarrierPrivilegesChanged() throws Exception {
+ final TelephonyManagerShim.CarrierPrivilegesListenerShim listener =
+ getCarrierPrivilegesListeners().get(0);
+
+ final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ new TelephonyNetworkSpecifier(0);
+ final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+ networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = mCarrierConfigPkgUid + 1;
+ doReturn(applicationInfo).when(mPackageManager)
+ .getApplicationInfo(eq(mTestPkg), anyInt());
+ listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
+
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
+ }
+
+ @Test
+ public void testDefaultSubscription() throws Exception {
+ final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+ networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+
+ networkRequestBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+ assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+
+ // The builder for NetworkRequest doesn't allow removing the transport as long as a
+ // specifier is set, so unset it first. TODO : fix the builder
+ networkRequestBuilder.setNetworkSpecifier((NetworkSpecifier) null);
+ networkRequestBuilder.removeTransportType(TRANSPORT_CELLULAR);
+ networkRequestBuilder.addTransportType(TRANSPORT_WIFI);
+ networkRequestBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+ assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+ mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 99ef80b..6590543 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -89,6 +89,7 @@
import androidx.test.filters.SmallTest;
import com.android.net.module.util.CollectionUtils;
+import com.android.server.BpfNetMaps;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -146,9 +147,11 @@
@Mock private UserManager mUserManager;
@Mock private PermissionMonitor.Dependencies mDeps;
@Mock private SystemConfigManager mSystemConfigManager;
+ @Mock private BpfNetMaps mBpfNetMaps;
private PermissionMonitor mPermissionMonitor;
private NetdMonitor mNetdMonitor;
+ private BpfMapMonitor mBpfMapMonitor;
@Before
public void setUp() throws Exception {
@@ -177,8 +180,9 @@
// by default.
doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
- mPermissionMonitor = new PermissionMonitor(mContext, mNetdService, mDeps);
+ mPermissionMonitor = new PermissionMonitor(mContext, mNetdService, mBpfNetMaps, mDeps);
mNetdMonitor = new NetdMonitor(mNetdService);
+ mBpfMapMonitor = new BpfMapMonitor(mBpfNetMaps);
doReturn(List.of()).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
}
@@ -511,9 +515,37 @@
assertBackgroundPermission(true, MOCK_PACKAGE2, MOCK_UID12, NETWORK_STACK);
}
+ private class BpfMapMonitor {
+ private final SparseIntArray mAppIdsTrafficPermission = new SparseIntArray();
+ private static final int DOES_NOT_EXIST = -2;
+
+ BpfMapMonitor(BpfNetMaps mockBpfmap) throws Exception {
+ // Add hook to verify and track result of trafficSetNetPerm.
+ doAnswer((InvocationOnMock invocation) -> {
+ final Object[] args = invocation.getArguments();
+ final int permission = (int) args[0];
+ for (final int appId : (int[]) args[1]) {
+ mAppIdsTrafficPermission.put(appId, permission);
+ }
+ return null;
+ }).when(mockBpfmap).setNetPermForUids(anyInt(), any(int[].class));
+ }
+
+ public void expectTrafficPerm(int permission, int... appIds) {
+ for (final int appId : appIds) {
+ if (mAppIdsTrafficPermission.get(appId, DOES_NOT_EXIST) == DOES_NOT_EXIST) {
+ fail("appId " + appId + " does not exist.");
+ }
+ if (mAppIdsTrafficPermission.get(appId) != permission) {
+ fail("appId " + appId + " has wrong permission: "
+ + mAppIdsTrafficPermission.get(appId));
+ }
+ }
+ }
+ }
+
private class NetdMonitor {
private final SparseIntArray mUidsNetworkPermission = new SparseIntArray();
- private final SparseIntArray mAppIdsTrafficPermission = new SparseIntArray();
private static final int DOES_NOT_EXIST = -2;
NetdMonitor(INetd mockNetd) throws Exception {
@@ -545,16 +577,6 @@
}
return null;
}).when(mockNetd).networkClearPermissionForUser(any(int[].class));
-
- // Add hook to verify and track result of trafficSetNetPerm.
- doAnswer((InvocationOnMock invocation) -> {
- final Object[] args = invocation.getArguments();
- final int permission = (int) args[0];
- for (final int appId : (int[]) args[1]) {
- mAppIdsTrafficPermission.put(appId, permission);
- }
- return null;
- }).when(mockNetd).trafficSetNetPermForUids(anyInt(), any(int[].class));
}
public void expectNetworkPerm(int permission, UserHandle[] users, int... appIds) {
@@ -581,18 +603,6 @@
}
}
}
-
- public void expectTrafficPerm(int permission, int... appIds) {
- for (final int appId : appIds) {
- if (mAppIdsTrafficPermission.get(appId, DOES_NOT_EXIST) == DOES_NOT_EXIST) {
- fail("appId " + appId + " does not exist.");
- }
- if (mAppIdsTrafficPermission.get(appId) != permission) {
- fail("appId " + appId + " has wrong permission: "
- + mAppIdsTrafficPermission.get(appId));
- }
- }
- }
}
@Test
@@ -702,30 +712,30 @@
// When VPN is connected, expect a rule to be set up for user app MOCK_UID11
mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
- verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
- reset(mNetdService);
+ reset(mBpfNetMaps);
// When MOCK_UID11 package is uninstalled and reinstalled, expect Netd to be updated
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
- verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_UID11);
- verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
- reset(mNetdService);
+ reset(mBpfNetMaps);
// During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
// old UID rules then adds the new ones. Expect netd to be updated
mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
- verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID11}));
+ verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID11}));
mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
- verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID12}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID12}));
- reset(mNetdService);
+ reset(mBpfNetMaps);
// When VPN is disconnected, expect rules to be torn down
mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
- verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
+ verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
}
@@ -744,13 +754,13 @@
// Newly-installed package should have uid rules added
addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_APPID1);
- verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
- verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID21}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID21}));
// Removed package should have its uid rules removed
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
- verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
- verify(mNetdService, never()).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID21}));
+ verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
+ verify(mBpfNetMaps, never()).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID21}));
}
@@ -784,91 +794,91 @@
// Send the permission information to netd, expect permission updated.
mPermissionMonitor.sendAppIdsTrafficPermission(netdPermissionsAppIds);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID2);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, SYSTEM_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, SYSTEM_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, SYSTEM_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, SYSTEM_APPID2);
// Update permission of MOCK_APPID1, expect new permission show up.
mPermissionMonitor.sendPackagePermissionsForAppId(MOCK_APPID1, PERMISSION_TRAFFIC_ALL);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
// Change permissions of SYSTEM_APPID2, expect new permission show up and old permission
// revoked.
mPermissionMonitor.sendPackagePermissionsForAppId(SYSTEM_APPID2, PERMISSION_INTERNET);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, SYSTEM_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, SYSTEM_APPID2);
// Revoke permission from SYSTEM_APPID1, expect no permission stored.
mPermissionMonitor.sendPackagePermissionsForAppId(SYSTEM_APPID1, PERMISSION_NONE);
- mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, SYSTEM_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, SYSTEM_APPID1);
}
@Test
public void testPackageInstall() throws Exception {
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
addPackage(MOCK_PACKAGE2, MOCK_UID12, INTERNET);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID2);
}
@Test
public void testPackageInstallSharedUid() throws Exception {
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
// Install another package with the same uid and no permissions should not cause the app id
// to lose permissions.
addPackage(MOCK_PACKAGE2, MOCK_UID11);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
}
@Test
public void testPackageUninstallBasic() throws Exception {
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
- mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
}
@Test
public void testPackageRemoveThenAdd() throws Exception {
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
- mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
}
@Test
public void testPackageUpdate() throws Exception {
addPackage(MOCK_PACKAGE1, MOCK_UID11);
- mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
}
@Test
public void testPackageUninstallWithMultiplePackages() throws Exception {
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
// Install another package with the same uid but different permissions.
addPackage(MOCK_PACKAGE2, MOCK_UID11, INTERNET);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_UID11);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_UID11);
// Uninstall MOCK_PACKAGE1 and expect only INTERNET permission left.
when(mPackageManager.getPackagesForUid(eq(MOCK_UID11)))
.thenReturn(new String[]{MOCK_PACKAGE2});
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
}
@Test
@@ -876,7 +886,8 @@
// Use the real context as this test must ensure the *real* system package holds the
// necessary permission.
final Context realContext = InstrumentationRegistry.getContext();
- final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
+ final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService,
+ mBpfNetMaps);
final PackageManager manager = realContext.getPackageManager();
final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
GET_PERMISSIONS | MATCH_ANY_USER);
@@ -891,8 +902,8 @@
.thenReturn(new int[]{ MOCK_UID12 });
mPermissionMonitor.startMonitoring();
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID2);
}
private BroadcastReceiver expectBroadcastReceiver(String... actions) {
@@ -923,7 +934,7 @@
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11, INTERNET,
UPDATE_DEVICE_STATS);
receiver.onReceive(mContext, addedIntent);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
// Verify receiving PACKAGE_REMOVED intent.
when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
@@ -931,7 +942,7 @@
Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */));
removedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID11);
receiver.onReceive(mContext, removedIntent);
- mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
}
private ContentObserver expectRegisterContentObserver(Uri expectedUri) {
@@ -1070,7 +1081,7 @@
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
mPermissionMonitor.startMonitoring();
mNetdMonitor.expectNoNetworkPerm(new UserHandle[]{MOCK_USER1}, MOCK_APPID1, MOCK_APPID2);
- mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1, MOCK_APPID2);
final BroadcastReceiver receiver = expectBroadcastReceiver(
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1087,8 +1098,8 @@
MOCK_APPID1);
mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
MOCK_APPID2);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
}
@Test
@@ -1114,8 +1125,8 @@
MOCK_APPID1);
mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
MOCK_APPID2);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
}
@Test
@@ -1128,7 +1139,7 @@
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
mPermissionMonitor.startMonitoring();
mNetdMonitor.expectNoNetworkPerm(new UserHandle[]{MOCK_USER1}, MOCK_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
final BroadcastReceiver receiver = expectBroadcastReceiver(
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1140,7 +1151,7 @@
receiver.onReceive(mContext, externalIntent);
mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
MOCK_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID1);
}
@Test
@@ -1155,7 +1166,7 @@
mPermissionMonitor.startMonitoring();
mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
MOCK_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
final BroadcastReceiver receiver = expectBroadcastReceiver(
Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1169,7 +1180,7 @@
receiver.onReceive(mContext, externalIntent);
mNetdMonitor.expectNetworkPerm(PERMISSION_SYSTEM, new UserHandle[]{MOCK_USER1},
MOCK_APPID1);
- mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
}
@Test
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 0e2c293..6872f80 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -28,7 +28,7 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import static com.android.server.net.NetworkStatsFactory.kernelToTag;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 45f033c..76c0c38 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -198,6 +198,7 @@
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
private @Mock IBpfMap<U32, U8> mUidCounterSetMap;
+ private @Mock NetworkStatsService.TagStatsDeleter mTagStatsDeleter;
private NetworkStatsService mService;
private INetworkStatsSession mSession;
@@ -358,6 +359,11 @@
public IBpfMap<U32, U8> getUidCounterSetMap() {
return mUidCounterSetMap;
}
+
+ @Override
+ public NetworkStatsService.TagStatsDeleter getTagStatsDeleter() {
+ return mTagStatsDeleter;
+ }
};
}
@@ -696,8 +702,10 @@
final Intent intent = new Intent(ACTION_UID_REMOVED);
intent.putExtra(EXTRA_UID, UID_BLUE);
mServiceContext.sendBroadcast(intent);
+ verify(mTagStatsDeleter).deleteTagData(UID_BLUE);
intent.putExtra(EXTRA_UID, UID_RED);
mServiceContext.sendBroadcast(intent);
+ verify(mTagStatsDeleter).deleteTagData(UID_RED);
// existing uid and total should remain unchanged; but removed UID
// should be gone completely.