Merge "Dexopt service-connectivity." into sc-mainline-prod
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 9f2ea35..302c0b3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,10 +19,19 @@
]
},
{
+ "name": "netd_updatable_unit_test"
+ },
+ {
"name": "TetheringTests"
},
{
"name": "TetheringIntegrationTests"
+ },
+ {
+ "name": "traffic_controller_unit_test"
+ },
+ {
+ "name": "libnetworkstats_test"
}
],
"postsubmit": [
@@ -34,7 +43,18 @@
"name": "bpf_existence_test"
},
{
+ "name": "netd_updatable_unit_test",
+ "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
+ },
+ {
"name": "libclat_test"
+ },
+ {
+ "name": "traffic_controller_unit_test",
+ "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
+ },
+ {
+ "name": "libnetworkstats_test"
}
],
"mainline-presubmit": [
@@ -50,7 +70,16 @@
]
},
{
+ "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]"
+ },
+ {
+ "name": "traffic_controller_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+ },
+ {
+ "name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
}
],
"mainline-postsubmit": [
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 041a3ae..ae96e8c 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -53,6 +53,7 @@
"framework-statsd.stubs.module_lib",
"framework-tethering.impl",
"framework-wifi",
+ "framework-bluetooth",
"unsupportedappusage",
],
plugins: ["java_api_finder"],
@@ -64,6 +65,7 @@
android_library {
name: "TetheringApiCurrentLib",
defaults: [
+ "ConnectivityNextEnableDefaults",
"TetheringAndroidLibraryDefaults",
"TetheringApiLevel"
],
@@ -97,7 +99,6 @@
],
min_sdk_version: "30",
header_libs: [
- "bpf_syscall_wrappers",
"bpf_connectivity_headers",
],
srcs: [
@@ -110,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:
@@ -158,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 2d9a3fc..416a7f7 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -31,6 +31,7 @@
// package names and keys, so that apex will be unused anyway.
apps: ["Tethering"], // Replace to "Tethering" if ConnectivityNextEnableDefaults is false.
}
+enable_tethering_next_apex = false
// This is a placeholder comment to avoid merge conflicts
// as the above target may have different "enabled" values
// depending on the branch
@@ -51,8 +52,9 @@
first: {
jni_libs: [
"libservice-connectivity",
- "libcom_android_connectivity_com_android_net_module_util_jni"
+ "libandroid_net_connectivity_com_android_net_module_util_jni",
],
+ native_shared_libs: ["libnetd_updatable"],
},
both: {
jni_libs: ["libframework-connectivity-jni"],
@@ -65,6 +67,7 @@
bpfs: [
"clatd.o_mainline",
"netd.o_mainline",
+ "dscp_policy.o",
"offload.o",
"test.o",
],
@@ -142,6 +145,7 @@
name: "com.android.tethering.inprocess",
base: "com.android.tethering",
package_name: "com.android.tethering.inprocess",
+ enabled: enable_tethering_next_apex,
apps: [
"ServiceConnectivityResources",
"InProcessTethering",
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
index 44c57ab..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/clatd 1029 1029 06555
+/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 bb9f5ead6..6718402 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -42,10 +42,13 @@
// 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",
+ "//packages/modules/Connectivity/service/native",
+ "//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
"//packages/modules/Connectivity/tests/unit/jni",
- // TODO: remove system/netd/* when all BPF code is moved out of Netd.
- "//system/netd/libnetdbpf",
"//system/netd/server",
"//system/netd/tests",
],
@@ -55,6 +58,16 @@
// bpf kernel programs
//
bpf {
+ name: "dscp_policy.o",
+ srcs: ["dscp_policy.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ sub_dir: "net_shared",
+}
+
+bpf {
name: "offload.o",
srcs: ["offload.c"],
cflags: [
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 8577d9d..2ddc7b8 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -20,7 +20,6 @@
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/in6.h>
-#include <netdutils/UidConstants.h>
// This header file is shared by eBPF kernel programs (C) and netd (C++) and
// some of the maps are also accessed directly from Java mainline module code.
@@ -131,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/dscp_policy.c b/bpf_progs/dscp_policy.c
new file mode 100644
index 0000000..9989e6b
--- /dev/null
+++ b/bpf_progs/dscp_policy.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <linux/types.h>
+#include <linux/bpf.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/if_ether.h>
+#include <linux/pkt_cls.h>
+#include <linux/tcp.h>
+#include <stdint.h>
+#include <netinet/in.h>
+#include <netinet/udp.h>
+#include <string.h>
+
+#include "bpf_helpers.h"
+
+#define MAX_POLICIES 16
+#define MAP_A 1
+#define MAP_B 2
+
+#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
+
+// TODO: these are already defined in /system/netd/bpf_progs/bpf_net_helpers.h
+// should they be moved to common location?
+static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) =
+ (void*)BPF_FUNC_get_socket_cookie;
+static int (*bpf_skb_store_bytes)(struct __sk_buff* skb, __u32 offset, const void* from, __u32 len,
+ __u64 flags) = (void*)BPF_FUNC_skb_store_bytes;
+static int (*bpf_l3_csum_replace)(struct __sk_buff* skb, __u32 offset, __u64 from, __u64 to,
+ __u64 flags) = (void*)BPF_FUNC_l3_csum_replace;
+
+typedef struct {
+ // Add family here to match __sk_buff ?
+ struct in_addr srcIp;
+ struct in_addr dstIp;
+ __be16 srcPort;
+ __be16 dstPort;
+ uint8_t proto;
+ uint8_t dscpVal;
+ uint8_t pad[2];
+} Ipv4RuleEntry;
+STRUCT_SIZE(Ipv4RuleEntry, 2 * 4 + 2 * 2 + 2 * 1 + 2); // 16, 4 for in_addr
+
+#define SRC_IP_MASK 1
+#define DST_IP_MASK 2
+#define SRC_PORT_MASK 4
+#define DST_PORT_MASK 8
+#define PROTO_MASK 16
+
+typedef struct {
+ struct in6_addr srcIp;
+ struct in6_addr dstIp;
+ __be16 srcPort;
+ __be16 dstPortStart;
+ __be16 dstPortEnd;
+ uint8_t proto;
+ uint8_t dscpVal;
+ uint8_t mask;
+ uint8_t pad[3];
+} Ipv4Policy;
+STRUCT_SIZE(Ipv4Policy, 2 * 16 + 3 * 2 + 3 * 1 + 3); // 44
+
+typedef struct {
+ struct in6_addr srcIp;
+ struct in6_addr dstIp;
+ __be16 srcPort;
+ __be16 dstPortStart;
+ __be16 dstPortEnd;
+ uint8_t proto;
+ uint8_t dscpVal;
+ uint8_t mask;
+ // should we override this struct to include the param bitmask for linear search?
+ // For mapping socket to policies, all the params should match exactly since we can
+ // pull any missing from the sock itself.
+} Ipv6RuleEntry;
+STRUCT_SIZE(Ipv6RuleEntry, 2 * 16 + 3 * 2 + 3 * 1 + 3); // 44
+
+// TODO: move to using 1 map. Map v4 address to 0xffff::v4
+DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_A, HASH, uint64_t, Ipv4RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_B, HASH, uint64_t, Ipv4RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_A, HASH, uint64_t, Ipv6RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_B, HASH, uint64_t, Ipv6RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(switch_comp_map, ARRAY, int, uint64_t, 1, AID_SYSTEM)
+
+DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, Ipv4Policy, MAX_POLICIES,
+ AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, Ipv6RuleEntry, MAX_POLICIES,
+ AID_SYSTEM)
+
+DEFINE_BPF_PROG_KVER("schedcls/set_dscp", AID_ROOT, AID_SYSTEM,
+ schedcls_set_dscp, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ int one = 0;
+ uint64_t* selectedMap = bpf_switch_comp_map_lookup_elem(&one);
+
+ // use this with HASH map so map lookup only happens once policies have been added?
+ if (!selectedMap) {
+ return TC_ACT_PIPE;
+ }
+
+ // used for map lookup
+ uint64_t cookie = bpf_get_socket_cookie(skb);
+
+ // Do we need separate maps for ipv4/ipv6
+ if (skb->protocol == htons(ETH_P_IP)) { //maybe bpf_htons()
+ Ipv4RuleEntry* v4Policy;
+ if (*selectedMap == MAP_A) {
+ v4Policy = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
+ } else {
+ v4Policy = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
+ }
+
+ // How to use bitmask here to compare params efficiently?
+ // TODO: add BPF_PROG_TYPE_SK_SKB prog type to Loader?
+
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ const struct iphdr* const iph = data;
+
+ // Must have ipv4 header
+ if (data + sizeof(*iph) > data_end) return TC_ACT_PIPE;
+
+ // IP version must be 4
+ if (iph->version != 4) return TC_ACT_PIPE;
+
+ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+ if (iph->ihl != 5) return TC_ACT_PIPE;
+
+ if (iph->protocol != IPPROTO_UDP) return TC_ACT_PIPE;
+
+ struct udphdr *udp;
+ udp = data + sizeof(struct iphdr); //sizeof(struct ethhdr)
+
+ if ((void*)(udp + 1) > data_end) return TC_ACT_PIPE;
+
+ // Source/destination port in udphdr are stored in be16, need to convert to le16.
+ // This can be done via ntohs or htons. Is there a more preferred way?
+ // Cached policy was found.
+ if (v4Policy && iph->saddr == v4Policy->srcIp.s_addr &&
+ iph->daddr == v4Policy->dstIp.s_addr &&
+ ntohs(udp->source) == v4Policy->srcPort &&
+ ntohs(udp->dest) == v4Policy->dstPort &&
+ iph->protocol == v4Policy->proto) {
+ // set dscpVal in packet. Least sig 2 bits of TOS
+ // reference ipv4_change_dsfield()
+
+ // TODO: fix checksum...
+ int ecn = iph->tos & 3;
+ uint8_t newDscpVal = (v4Policy->dscpVal << 2) + ecn;
+ int oldDscpVal = iph->tos >> 2;
+ bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
+ bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
+ return TC_ACT_PIPE;
+ }
+
+ // linear scan ipv4_dscp_policies_map, stored socket params do not match actual
+ int bestScore = -1;
+ uint32_t bestMatch = 0;
+
+ for (register uint64_t i = 0; i < MAX_POLICIES; i++) {
+ int score = 0;
+ uint8_t tempMask = 0;
+ // Using a uint62 in for loop prevents infinite loop during BPF load,
+ // but the key is uint32, so convert back.
+ uint32_t key = i;
+ Ipv4Policy* policy = bpf_ipv4_dscp_policies_map_lookup_elem(&key);
+
+ // if mask is 0 continue, key does not have corresponding policy value
+ if (policy && policy->mask != 0) {
+ if ((policy->mask & SRC_IP_MASK) == SRC_IP_MASK &&
+ iph->saddr == policy->srcIp.s6_addr32[3]) {
+ score++;
+ tempMask |= SRC_IP_MASK;
+ }
+ if ((policy->mask & DST_IP_MASK) == DST_IP_MASK &&
+ iph->daddr == policy->dstIp.s6_addr32[3]) {
+ score++;
+ tempMask |= DST_IP_MASK;
+ }
+ if ((policy->mask & SRC_PORT_MASK) == SRC_PORT_MASK &&
+ ntohs(udp->source) == htons(policy->srcPort)) {
+ score++;
+ tempMask |= SRC_PORT_MASK;
+ }
+ if ((policy->mask & DST_PORT_MASK) == DST_PORT_MASK &&
+ ntohs(udp->dest) >= htons(policy->dstPortStart) &&
+ ntohs(udp->dest) <= htons(policy->dstPortEnd)) {
+ score++;
+ tempMask |= DST_PORT_MASK;
+ }
+ if ((policy->mask & PROTO_MASK) == PROTO_MASK &&
+ iph->protocol == policy->proto) {
+ score++;
+ tempMask |= PROTO_MASK;
+ }
+
+ if (score > bestScore && tempMask == policy->mask) {
+ bestMatch = i;
+ bestScore = score;
+ }
+ }
+ }
+
+ uint8_t newDscpVal = 0; // Can 0 be used as default forwarding value?
+ uint8_t curDscp = iph->tos & 252;
+ if (bestScore > 0) {
+ Ipv4Policy* policy = bpf_ipv4_dscp_policies_map_lookup_elem(&bestMatch);
+ if (policy) {
+ // TODO: if DSCP value is already set ignore?
+ // TODO: update checksum, for testing increment counter...
+ int ecn = iph->tos & 3;
+ newDscpVal = (policy->dscpVal << 2) + ecn;
+ }
+ }
+
+ Ipv4RuleEntry value = {
+ .srcIp.s_addr = iph->saddr,
+ .dstIp.s_addr = iph->daddr,
+ .srcPort = udp->source,
+ .dstPort = udp->dest,
+ .proto = iph->protocol,
+ .dscpVal = newDscpVal,
+ };
+
+ if (!cookie)
+ return TC_ACT_PIPE;
+
+ // Update map
+ if (*selectedMap == MAP_A) {
+ bpf_ipv4_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
+ } else {
+ bpf_ipv4_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
+ }
+
+ // Need to store bytes after updating map or program will not load.
+ if (newDscpVal != curDscp) {
+ // 1 is the offset (Version/Header length)
+ int oldDscpVal = iph->tos >> 2;
+ bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
+ bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
+ }
+
+ } else if (skb->protocol == htons(ETH_P_IPV6)) { //maybe bpf_htons()
+ Ipv6RuleEntry* v6Policy;
+ if (*selectedMap == MAP_A) {
+ v6Policy = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
+ } else {
+ v6Policy = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
+ }
+
+ if (!v6Policy)
+ return TC_ACT_PIPE;
+
+ // TODO: Add code to process IPv6 packet.
+ }
+
+ // Always return TC_ACT_PIPE
+ return TC_ACT_PIPE;
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("Connectivity");
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 72ee431..c1a74e7 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -25,6 +25,7 @@
#include <linux/ipv6.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
+#include <netdutils/UidConstants.h>
#include <stdbool.h>
#include <stdint.h>
#include "bpf_net_helpers.h"
@@ -209,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/buildstubs-t/Android.bp b/buildstubs-t/Android.bp
index 0976e84..dfca6b3 100644
--- a/buildstubs-t/Android.bp
+++ b/buildstubs-t/Android.bp
@@ -32,8 +32,8 @@
srcs: ["stubs-src/**/*.java"],
}
-// Empty replacement for framework-connectivity-tiramisu.impl, as
-// framework-connectivity is disabled in the branch
+// Empty replacement for framework-connectivity-tiramisu.impl and stubs,
+// as framework-connectivity is disabled in the branch
java_library {
name: "framework-connectivity-tiramisu.impl",
min_sdk_version: "Tiramisu",
@@ -41,3 +41,9 @@
srcs: [],
}
+java_library {
+ name: "framework-connectivity-tiramisu.stubs.module_lib",
+ min_sdk_version: "Tiramisu",
+ sdk_version: "module_current",
+ srcs: [],
+}
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index e9bd3ea..a08c06a 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/aidl-export/android/net/DscpPolicy.aidl b/framework/aidl-export/android/net/DscpPolicy.aidl
new file mode 100644
index 0000000..8da42ca
--- /dev/null
+++ b/framework/aidl-export/android/net/DscpPolicy.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+@JavaOnlyStableParcelable parcelable DscpPolicy;
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 827da6d..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);
@@ -294,7 +309,7 @@
ctor public NetworkCapabilities(android.net.NetworkCapabilities);
method public int describeContents();
method @NonNull public int[] getCapabilities();
- method @NonNull public int[] getEnterpriseCapabilitySubLevels();
+ method @NonNull public int[] getEnterpriseIds();
method public int getLinkDownstreamBandwidthKbps();
method public int getLinkUpstreamBandwidthKbps();
method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
@@ -302,6 +317,7 @@
method public int getSignalStrength();
method @Nullable public android.net.TransportInfo getTransportInfo();
method public boolean hasCapability(int);
+ method public boolean hasEnterpriseId(int);
method public boolean hasTransport(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR;
@@ -325,6 +341,8 @@
field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12
field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15
field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
+ field public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35; // 0x23
+ field public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34; // 0x22
field public static final int NET_CAPABILITY_RCS = 8; // 0x8
field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
@@ -332,6 +350,11 @@
field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
field public static final int NET_CAPABILITY_XCAP = 9; // 0x9
+ field public static final int NET_ENTERPRISE_ID_1 = 1; // 0x1
+ field public static final int NET_ENTERPRISE_ID_2 = 2; // 0x2
+ field public static final int NET_ENTERPRISE_ID_3 = 3; // 0x3
+ field public static final int NET_ENTERPRISE_ID_4 = 4; // 0x4
+ field public static final int NET_ENTERPRISE_ID_5 = 5; // 0x5
field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000
field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
field public static final int TRANSPORT_CELLULAR = 0; // 0x0
@@ -477,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 ec169b6..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
@@ -127,17 +129,14 @@
public static final class NetworkAgentConfig.Builder {
method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setExcludeLocalRoutesVpn(boolean);
method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
}
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 int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1 = 1; // 0x1
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2 = 2; // 0x2
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3 = 3; // 0x3
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4 = 4; // 0x4
- field public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5 = 5; // 0x5
field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L
field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L
@@ -147,11 +146,14 @@
}
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>>);
}
public class NetworkRequest implements android.os.Parcelable {
+ method @NonNull public int[] getEnterpriseIds();
method @NonNull public int[] getForbiddenCapabilities();
+ method public boolean hasEnterpriseId(int);
method public boolean hasForbiddenCapability(int);
}
@@ -166,6 +168,7 @@
method @NonNull public java.util.List<java.lang.Integer> getExcludedUids();
method @NonNull public java.util.List<java.lang.Integer> getIncludedUids();
method public int getPreference();
+ method public int getPreferenceEnterpriseId();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.ProfileNetworkPreference> CREATOR;
}
@@ -176,6 +179,7 @@
method @NonNull public android.net.ProfileNetworkPreference.Builder setExcludedUids(@Nullable java.util.List<java.lang.Integer>);
method @NonNull public android.net.ProfileNetworkPreference.Builder setIncludedUids(@Nullable java.util.List<java.lang.Integer>);
method @NonNull public android.net.ProfileNetworkPreference.Builder setPreference(int);
+ method @NonNull public android.net.ProfileNetworkPreference.Builder setPreferenceEnterpriseId(int);
}
public final class TestNetworkInterface implements android.os.Parcelable {
diff --git a/framework/api/module-lib-lint-baseline.txt b/framework/api/module-lib-lint-baseline.txt
deleted file mode 100644
index c7b0db5..0000000
--- a/framework/api/module-lib-lint-baseline.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-NoByteOrShort: android.net.DhcpOption#DhcpOption(byte, byte[]) parameter #0:
- Should avoid odd sized primitives; use `int` instead of `byte` in parameter type in android.net.DhcpOption(byte type, byte[] value)
-NoByteOrShort: android.net.DhcpOption#describeContents():
- Should avoid odd sized primitives; use `int` instead of `byte` in method android.net.DhcpOption.describeContents()
-NoByteOrShort: android.net.DhcpOption#getType():
- Should avoid odd sized primitives; use `int` instead of `byte` in method android.net.DhcpOption.getType()
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 8d084e6..764cffa 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -93,6 +93,35 @@
method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network);
}
+ public final class DscpPolicy implements android.os.Parcelable {
+ method @Nullable public java.net.InetAddress getDestinationAddress();
+ method @Nullable public android.util.Range<java.lang.Integer> getDestinationPortRange();
+ method public int getDscpValue();
+ method public int getPolicyId();
+ method public int getProtocol();
+ method @Nullable public java.net.InetAddress getSourceAddress();
+ method public int getSourcePort();
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.DscpPolicy> CREATOR;
+ field public static final int PROTOCOL_ANY = -1; // 0xffffffff
+ field public static final int SOURCE_PORT_ANY = -1; // 0xffffffff
+ field public static final int STATUS_DELETED = 4; // 0x4
+ field public static final int STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3; // 0x3
+ field public static final int STATUS_POLICY_NOT_FOUND = 5; // 0x5
+ field public static final int STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2; // 0x2
+ field public static final int STATUS_REQUEST_DECLINED = 1; // 0x1
+ field public static final int STATUS_SUCCESS = 0; // 0x0
+ }
+
+ public static final class DscpPolicy.Builder {
+ ctor public DscpPolicy.Builder(int, int);
+ method @NonNull public android.net.DscpPolicy build();
+ method @NonNull public android.net.DscpPolicy.Builder setDestinationAddress(@NonNull java.net.InetAddress);
+ method @NonNull public android.net.DscpPolicy.Builder setDestinationPortRange(@NonNull android.util.Range<java.lang.Integer>);
+ method @NonNull public android.net.DscpPolicy.Builder setProtocol(int);
+ method @NonNull public android.net.DscpPolicy.Builder setSourceAddress(@NonNull java.net.InetAddress);
+ method @NonNull public android.net.DscpPolicy.Builder setSourcePort(int);
+ }
+
public final class InvalidPacketException extends java.lang.Exception {
ctor public InvalidPacketException(int);
method public int getError();
@@ -104,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 {
@@ -217,6 +241,7 @@
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
method public void onAutomaticReconnectDisabled();
method public void onBandwidthUpdateRequested();
+ method public void onDscpPolicyStatusUpdated(int, int);
method public void onNetworkCreated();
method public void onNetworkDestroyed();
method public void onNetworkUnwanted();
@@ -229,6 +254,7 @@
method public void onStopSocketKeepalive(int);
method public void onValidationStatus(int, @Nullable android.net.Uri);
method @NonNull public android.net.Network register();
+ method public void sendAddDscpPolicy(@NonNull android.net.DscpPolicy);
method public final void sendLinkProperties(@NonNull android.net.LinkProperties);
method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
method public final void sendNetworkScore(@NonNull android.net.NetworkScore);
@@ -236,6 +262,8 @@
method public final void sendQosCallbackError(int, int);
method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
method public final void sendQosSessionLost(int, int, int);
+ method public void sendRemoveAllDscpPolicies();
+ method public void sendRemoveDscpPolicy(int);
method public final void sendSocketKeepaliveEvent(int, int);
method @Deprecated public void setLegacySubtype(int, @NonNull String);
method public void setLingerDuration(@NonNull java.time.Duration);
@@ -294,11 +322,11 @@
ctor public NetworkCapabilities.Builder();
ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
- method @NonNull public android.net.NetworkCapabilities.Builder addEnterpriseCapabilitySubLevel(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder addEnterpriseId(int);
method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
method @NonNull public android.net.NetworkCapabilities build();
method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
- method @NonNull public android.net.NetworkCapabilities.Builder removeEnterpriseCapabilitySubLevel(int);
+ method @NonNull public android.net.NetworkCapabilities.Builder removeEnterpriseId(int);
method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
@@ -451,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 009890c..5246623 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -16,6 +16,7 @@
package android.net;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
import static android.net.NetworkRequest.Type.LISTEN;
import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
@@ -876,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.
*
@@ -913,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,
@@ -930,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.
@@ -962,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
@@ -5537,6 +5559,9 @@
ProfileNetworkPreference.Builder preferenceBuilder =
new ProfileNetworkPreference.Builder();
preferenceBuilder.setPreference(preference);
+ if (preference != PROFILE_NETWORK_PREFERENCE_DEFAULT) {
+ preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ }
setProfileNetworkPreferences(profile,
List.of(preferenceBuilder.build()), executor, listener);
}
diff --git a/framework/src/android/net/DhcpOption.java b/framework/src/android/net/DhcpOption.java
index a125290..b30470a 100644
--- a/framework/src/android/net/DhcpOption.java
+++ b/framework/src/android/net/DhcpOption.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,12 +36,13 @@
/**
* Constructs a DhcpOption object.
*
- * @param type the type of this option
+ * @param type the type of this option. For more information, see
+ * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml.
* @param value the value of this option. If {@code null}, DHCP packets containing this option
* will include the option type in the Parameter Request List. Otherwise, DHCP
* packets containing this option will include the option in the options section.
*/
- public DhcpOption(byte type, @Nullable byte[] value) {
+ public DhcpOption(@SuppressLint("NoByteOrShort") byte type, @Nullable byte[] value) {
mType = type;
mValue = value;
}
@@ -69,6 +71,7 @@
};
/** Get the type of DHCP option */
+ @SuppressLint("NoByteOrShort")
public byte getType() {
return mType;
}
diff --git a/framework/src/android/net/DscpPolicy.java b/framework/src/android/net/DscpPolicy.java
new file mode 100644
index 0000000..cda8205
--- /dev/null
+++ b/framework/src/android/net/DscpPolicy.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Range;
+
+import com.android.net.module.util.InetAddressUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.Objects;
+
+
+/**
+ * DSCP policy to be set on the requesting NetworkAgent.
+ * @hide
+ */
+@SystemApi
+public final class DscpPolicy implements Parcelable {
+ /**
+ * Indicates that the policy does not specify a protocol.
+ */
+ public static final int PROTOCOL_ANY = -1;
+
+ /**
+ * Indicates that the policy does not specify a port.
+ */
+ public static final int SOURCE_PORT_ANY = -1;
+
+ /**
+ * Policy was successfully added.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * Policy was rejected for any reason besides invalid classifier or insufficient resources.
+ */
+ public static final int STATUS_REQUEST_DECLINED = 1;
+
+ /**
+ * Requested policy contained a classifier which is not supported.
+ */
+ public static final int STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2;
+
+ /**
+ * TODO: should this error case be supported?
+ */
+ public static final int STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3;
+
+ /**
+ * Policy was deleted.
+ */
+ public static final int STATUS_DELETED = 4;
+
+ /**
+ * Policy was not found during deletion.
+ */
+ public static final int STATUS_POLICY_NOT_FOUND = 5;
+
+ /** The unique policy ID. Each requesting network is responsible for maintaining policy IDs
+ * unique within that network. In the case where a policy with an existing ID is created, the
+ * new policy will update the existing policy with the same ID.
+ */
+ private final int mPolicyId;
+
+ /** The QoS DSCP marking to be added to packets matching the policy. */
+ private final int mDscp;
+
+ /** The source IP address. */
+ private final @Nullable InetAddress mSrcAddr;
+
+ /** The destination IP address. */
+ private final @Nullable InetAddress mDstAddr;
+
+ /** The source port. */
+ private final int mSrcPort;
+
+ /** The IP protocol that the policy requires. */
+ private final int mProtocol;
+
+ /** Destination port range. Inclusive range. */
+ private final @Nullable Range<Integer> mDstPortRange;
+
+ /**
+ * Implement the Parcelable interface
+ *
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESS,
+ STATUS_REQUEST_DECLINED,
+ STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
+ STATUS_INSUFFICIENT_PROCESSING_RESOURCES,
+ STATUS_DELETED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DscpPolicyStatus {}
+
+ /* package */ DscpPolicy(
+ int policyId,
+ int dscp,
+ @Nullable InetAddress srcAddr,
+ @Nullable InetAddress dstAddr,
+ int srcPort,
+ int protocol,
+ Range<Integer> dstPortRange) {
+ this.mPolicyId = policyId;
+ this.mDscp = dscp;
+ this.mSrcAddr = srcAddr;
+ this.mDstAddr = dstAddr;
+ this.mSrcPort = srcPort;
+ this.mProtocol = protocol;
+ this.mDstPortRange = dstPortRange;
+
+ if (mPolicyId < 1 || mPolicyId > 255) {
+ throw new IllegalArgumentException("Policy ID not in valid range: " + mPolicyId);
+ }
+ if (mDscp < 0 || mDscp > 63) {
+ throw new IllegalArgumentException("DSCP value not in valid range: " + mDscp);
+ }
+ // Since SOURCE_PORT_ANY is the default source port value need to allow it as well.
+ // TODO: Move the default value into this constructor or throw an error from the
+ // instead.
+ if (mSrcPort < -1 || mSrcPort > 65535) {
+ throw new IllegalArgumentException("Source port not in valid range: " + mSrcPort);
+ }
+ if (mDstPortRange != null
+ && (dstPortRange.getLower() < 0 || mDstPortRange.getLower() > 65535)
+ && (mDstPortRange.getUpper() < 0 || mDstPortRange.getUpper() > 65535)) {
+ throw new IllegalArgumentException("Destination port not in valid range");
+ }
+ if (mSrcAddr != null && mDstAddr != null && (mSrcAddr instanceof Inet6Address)
+ != (mDstAddr instanceof Inet6Address)) {
+ throw new IllegalArgumentException("Source/destination address of different family");
+ }
+ }
+
+ /**
+ * The unique policy ID.
+ *
+ * Each requesting network is responsible for maintaining unique
+ * policy IDs. In the case where a policy with an existing ID is created, the new
+ * policy will update the existing policy with the same ID
+ *
+ * @return Policy ID set in Builder.
+ */
+ public int getPolicyId() {
+ return mPolicyId;
+ }
+
+ /**
+ * The QoS DSCP marking to be added to packets matching the policy.
+ *
+ * @return DSCP value set in Builder.
+ */
+ public int getDscpValue() {
+ return mDscp;
+ }
+
+ /**
+ * The source IP address.
+ *
+ * @return Source IP address set in Builder or {@code null} if none was set.
+ */
+ public @Nullable InetAddress getSourceAddress() {
+ return mSrcAddr;
+ }
+
+ /**
+ * The destination IP address.
+ *
+ * @return Destination IP address set in Builder or {@code null} if none was set.
+ */
+ public @Nullable InetAddress getDestinationAddress() {
+ return mDstAddr;
+ }
+
+ /**
+ * The source port.
+ *
+ * @return Source port set in Builder or {@link #SOURCE_PORT_ANY} if no port was set.
+ */
+ public int getSourcePort() {
+ return mSrcPort;
+ }
+
+ /**
+ * The IP protocol that the policy requires.
+ *
+ * @return Protocol set in Builder or {@link #PROTOCOL_ANY} if no protocol was set.
+ * {@link #PROTOCOL_ANY} indicates that any protocol will be matched.
+ */
+ public int getProtocol() {
+ return mProtocol;
+ }
+
+ /**
+ * Destination port range. Inclusive range.
+ *
+ * @return Range<Integer> set in Builder or {@code null} if none was set.
+ */
+ public @Nullable Range<Integer> getDestinationPortRange() {
+ return mDstPortRange;
+ }
+
+ @Override
+ public String toString() {
+ return "DscpPolicy { "
+ + "policyId = " + mPolicyId + ", "
+ + "dscp = " + mDscp + ", "
+ + "srcAddr = " + mSrcAddr + ", "
+ + "dstAddr = " + mDstAddr + ", "
+ + "srcPort = " + mSrcPort + ", "
+ + "protocol = " + mProtocol + ", "
+ + "dstPortRange = "
+ + (mDstPortRange == null ? "none" : mDstPortRange.toString())
+ + " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DscpPolicy)) return false;
+ DscpPolicy that = (DscpPolicy) o;
+ return true
+ && mPolicyId == that.mPolicyId
+ && mDscp == that.mDscp
+ && Objects.equals(mSrcAddr, that.mSrcAddr)
+ && Objects.equals(mDstAddr, that.mDstAddr)
+ && mSrcPort == that.mSrcPort
+ && mProtocol == that.mProtocol
+ && Objects.equals(mDstPortRange, that.mDstPortRange);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPolicyId, mDscp, mSrcAddr.hashCode(),
+ mDstAddr.hashCode(), mSrcPort, mProtocol, mDstPortRange.hashCode());
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mPolicyId);
+ dest.writeInt(mDscp);
+ InetAddressUtils.parcelInetAddress(dest, mSrcAddr, flags);
+ InetAddressUtils.parcelInetAddress(dest, mDstAddr, flags);
+ dest.writeInt(mSrcPort);
+ dest.writeInt(mProtocol);
+ dest.writeBoolean(mDstPortRange != null ? true : false);
+ if (mDstPortRange != null) {
+ dest.writeInt(mDstPortRange.getLower());
+ dest.writeInt(mDstPortRange.getUpper());
+ }
+ }
+
+ /** @hide */
+ DscpPolicy(@NonNull Parcel in) {
+ this.mPolicyId = in.readInt();
+ this.mDscp = in.readInt();
+ this.mSrcAddr = InetAddressUtils.unparcelInetAddress(in);
+ this.mDstAddr = InetAddressUtils.unparcelInetAddress(in);
+ this.mSrcPort = in.readInt();
+ this.mProtocol = in.readInt();
+ if (in.readBoolean()) {
+ this.mDstPortRange = new Range<Integer>(in.readInt(), in.readInt());
+ } else {
+ this.mDstPortRange = null;
+ }
+ }
+
+ /** @hide */
+ public @SystemApi static final @NonNull Parcelable.Creator<DscpPolicy> CREATOR =
+ new Parcelable.Creator<DscpPolicy>() {
+ @Override
+ public DscpPolicy[] newArray(int size) {
+ return new DscpPolicy[size];
+ }
+
+ @Override
+ public DscpPolicy createFromParcel(@NonNull android.os.Parcel in) {
+ return new DscpPolicy(in);
+ }
+ };
+
+ /**
+ * A builder for {@link DscpPolicy}
+ *
+ */
+ public static final class Builder {
+
+ private final int mPolicyId;
+ private final int mDscp;
+ private @Nullable InetAddress mSrcAddr;
+ private @Nullable InetAddress mDstAddr;
+ private int mSrcPort = SOURCE_PORT_ANY;
+ private int mProtocol = PROTOCOL_ANY;
+ private @Nullable Range<Integer> mDstPortRange;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param policyId The unique policy ID. Each requesting network is responsible for
+ * maintaining unique policy IDs. In the case where a policy with an
+ * existing ID is created, the new policy will update the existing
+ * policy with the same ID
+ * @param dscpValue The DSCP value to set.
+ */
+ public Builder(int policyId, int dscpValue) {
+ mPolicyId = policyId;
+ mDscp = dscpValue;
+ }
+
+ /**
+ * Specifies that this policy matches packets with the specified source IP address.
+ */
+ public @NonNull Builder setSourceAddress(@NonNull InetAddress value) {
+ mSrcAddr = value;
+ return this;
+ }
+
+ /**
+ * Specifies that this policy matches packets with the specified destination IP address.
+ */
+ public @NonNull Builder setDestinationAddress(@NonNull InetAddress value) {
+ mDstAddr = value;
+ return this;
+ }
+
+ /**
+ * Specifies that this policy matches packets with the specified source port.
+ */
+ public @NonNull Builder setSourcePort(int value) {
+ mSrcPort = value;
+ return this;
+ }
+
+ /**
+ * Specifies that this policy matches packets with the specified protocol.
+ */
+ public @NonNull Builder setProtocol(int value) {
+ mProtocol = value;
+ return this;
+ }
+
+ /**
+ * Specifies that this policy matches packets with the specified destination port range.
+ */
+ public @NonNull Builder setDestinationPortRange(@NonNull Range<Integer> range) {
+ mDstPortRange = range;
+ return this;
+ }
+
+ /**
+ * Constructs a DscpPolicy with the specified parameters.
+ */
+ public @NonNull DscpPolicy build() {
+ return new DscpPolicy(
+ mPolicyId,
+ mDscp,
+ mSrcAddr,
+ mDstAddr,
+ mSrcPort,
+ mProtocol,
+ mDstPortRange);
+ }
+ }
+}
diff --git a/framework/src/android/net/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl
index d941d4b..fa5175c 100644
--- a/framework/src/android/net/INetworkAgent.aidl
+++ b/framework/src/android/net/INetworkAgent.aidl
@@ -48,4 +48,5 @@
void onQosCallbackUnregistered(int qosCallbackId);
void onNetworkCreated();
void onNetworkDestroyed();
+ void onDscpPolicyStatusUpdated(int policyId, int status);
}
diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl
index 9a58add..08536ca 100644
--- a/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -15,6 +15,7 @@
*/
package android.net;
+import android.net.DscpPolicy;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -43,4 +44,7 @@
void sendQosCallbackError(int qosCallbackId, int exceptionType);
void sendTeardownDelayMs(int teardownDelayMs);
void sendLingerDuration(int durationMs);
+ void sendAddDscpPolicy(in DscpPolicy policy);
+ void sendRemoveDscpPolicy(int policyId);
+ void sendRemoveAllDscpPolicies();
}
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/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index adcf338..945e670 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -25,6 +25,7 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.net.DscpPolicy.DscpPolicyStatus;
import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
@@ -404,6 +405,35 @@
*/
public static final int EVENT_LINGER_DURATION_CHANGED = BASE + 24;
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to set add a DSCP policy.
+ *
+ * @hide
+ */
+ public static final int EVENT_ADD_DSCP_POLICY = BASE + 25;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to set remove a DSCP policy.
+ *
+ * @hide
+ */
+ public static final int EVENT_REMOVE_DSCP_POLICY = BASE + 26;
+
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to remove all DSCP policies.
+ *
+ * @hide
+ */
+ public static final int EVENT_REMOVE_ALL_DSCP_POLICIES = BASE + 27;
+
+ /**
+ * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent of an updated
+ * status for a DSCP policy.
+ *
+ * @hide
+ */
+ public static final int CMD_DSCP_POLICY_STATUS = BASE + 28;
+
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
config.legacyTypeName, config.legacySubTypeName);
@@ -611,6 +641,12 @@
onNetworkDestroyed();
break;
}
+ case CMD_DSCP_POLICY_STATUS: {
+ onDscpPolicyStatusUpdated(
+ msg.arg1 /* Policy ID */,
+ msg.arg2 /* DSCP Policy Status */);
+ break;
+ }
}
}
}
@@ -761,6 +797,13 @@
public void onNetworkDestroyed() {
mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DESTROYED));
}
+
+ @Override
+ public void onDscpPolicyStatusUpdated(final int policyId,
+ @DscpPolicyStatus final int status) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ CMD_DSCP_POLICY_STATUS, policyId, status));
+ }
}
/**
@@ -1104,6 +1147,11 @@
public void onNetworkDestroyed() {}
/**
+ * Called when when the DSCP Policy status has changed.
+ */
+ public void onDscpPolicyStatusUpdated(int policyId, @DscpPolicyStatus int status) {}
+
+ /**
* Requests that the network hardware send the specified packet at the specified interval.
*
* @param slot the hardware slot on which to start the keepalive.
@@ -1317,6 +1365,30 @@
queueOrSendMessage(ra -> ra.sendLingerDuration((int) durationMs));
}
+ /**
+ * Add a DSCP Policy.
+ * @param policy the DSCP policy to be added.
+ */
+ public void sendAddDscpPolicy(@NonNull final DscpPolicy policy) {
+ Objects.requireNonNull(policy);
+ queueOrSendMessage(ra -> ra.sendAddDscpPolicy(policy));
+ }
+
+ /**
+ * Remove the specified DSCP policy.
+ * @param policyId the ID corresponding to a specific DSCP Policy.
+ */
+ public void sendRemoveDscpPolicy(final int policyId) {
+ queueOrSendMessage(ra -> ra.sendRemoveDscpPolicy(policyId));
+ }
+
+ /**
+ * Remove all the DSCP policies on this network.
+ */
+ public void sendRemoveAllDscpPolicies() {
+ queueOrSendMessage(ra -> ra.sendRemoveAllDscpPolicies());
+ }
+
/** @hide */
protected void log(final String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index 93fc379..040bf31 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -425,8 +425,10 @@
* Sets whether the local traffic is exempted from VPN.
*
* @return this builder, to facilitate chaining.
- * @hide TODO(184750836): Unhide once the implementation is completed.
+ * @hide
*/
+ @NonNull
+ @SystemApi(client = MODULE_LIBRARIES)
public Builder setExcludeLocalRoutesVpn(boolean excludeLocalRoutes) {
mConfig.excludeLocalRouteVpn = excludeLocalRoutes;
return this;
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 84f7cbb..b6cd760 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -16,8 +16,6 @@
package android.net;
-import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
-
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import android.annotation.IntDef;
@@ -149,67 +147,83 @@
private String mRequestorPackageName;
/**
- * enterprise capability sub level 1
- * @hide
+ * Enterprise capability identifier 1.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1 = 1;
+ public static final int NET_ENTERPRISE_ID_1 = 1;
/**
- * enterprise capability sub level 2
- * @hide
+ * Enterprise capability identifier 2.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2 = 2;
+ public static final int NET_ENTERPRISE_ID_2 = 2;
/**
- * enterprise capability sub level 3
- * @hide
+ * Enterprise capability identifier 3.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3 = 3;
+ public static final int NET_ENTERPRISE_ID_3 = 3;
/**
- * enterprise capability sub level 4
- * @hide
+ * Enterprise capability identifier 4.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4 = 4;
+ public static final int NET_ENTERPRISE_ID_4 = 4;
/**
- * enterprise capability sub level 5
- * @hide
+ * Enterprise capability identifier 5.
*/
- @SystemApi(client = MODULE_LIBRARIES)
- public static final int NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5 = 5;
+ public static final int NET_ENTERPRISE_ID_5 = 5;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1,
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2,
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3,
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4,
- NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5,
+ NET_ENTERPRISE_ID_1,
+ NET_ENTERPRISE_ID_2,
+ NET_ENTERPRISE_ID_3,
+ NET_ENTERPRISE_ID_4,
+ NET_ENTERPRISE_ID_5,
})
- public @interface EnterpriseCapabilitySubLevel {
+ public @interface EnterpriseId {
}
/**
- * Bitfield representing the network's enterprise capability sublevel. If any are specified
+ * Bitfield representing the network's enterprise capability identifier. If any are specified
* they will be satisfied by any Network that matches all of them.
- * {@see addEnterpriseCapabilitySubLevel} for details on how masks are added
+ * {@see addEnterpriseId} for details on how masks are added
*/
- private int mEnterpriseCapabilitySubLevel;
+ private int mEnterpriseId;
/**
- * @return all the enteprise capabilities sub level set on this {@code NetworkCapability}
- * instance.
+ * Get enteprise identifiers set.
+ *
+ * Get all the enterprise capabilities identifier set on this {@code NetworkCapability}
+ * If NET_CAPABILITY_ENTERPRISE is set and no enterprise ID is set, it is
+ * considered to have NET_CAPABILITY_ENTERPRISE by default.
+ * @return all the enterprise capabilities identifier set.
*
*/
- public @NonNull @EnterpriseCapabilitySubLevel int[] getEnterpriseCapabilitySubLevels() {
- return NetworkCapabilitiesUtils.unpackBits(mEnterpriseCapabilitySubLevel);
+ public @NonNull @EnterpriseId int[] getEnterpriseIds() {
+ if (hasCapability(NET_CAPABILITY_ENTERPRISE) && mEnterpriseId == 0) {
+ return new int[]{NET_ENTERPRISE_ID_1};
+ }
+ return NetworkCapabilitiesUtils.unpackBits(mEnterpriseId);
+ }
+
+ /**
+ * Tests for the presence of an enterprise capability identifier on this instance.
+ *
+ * If NET_CAPABILITY_ENTERPRISE is set and no enterprise ID is set, it is
+ * considered to have NET_CAPABILITY_ENTERPRISE by default.
+ * @param enterpriseId the enterprise capability identifier to be tested for.
+ * @return {@code true} if set on this instance.
+ */
+ public boolean hasEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ if (enterpriseId == NET_ENTERPRISE_ID_1) {
+ if (hasCapability(NET_CAPABILITY_ENTERPRISE) && mEnterpriseId == 0) {
+ return true;
+ }
+ }
+ return isValidEnterpriseId(enterpriseId)
+ && ((mEnterpriseId & (1L << enterpriseId)) != 0);
}
public NetworkCapabilities() {
@@ -250,6 +264,7 @@
mTransportInfo = null;
mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
mUids = null;
+ mAccessUids.clear();
mAdministratorUids = new int[0];
mOwnerUid = Process.INVALID_UID;
mSSID = null;
@@ -258,7 +273,7 @@
mRequestorPackageName = null;
mSubIds = new ArraySet<>();
mUnderlyingNetworks = null;
- mEnterpriseCapabilitySubLevel = 0;
+ mEnterpriseId = 0;
}
/**
@@ -280,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;
@@ -291,7 +307,7 @@
// mUnderlyingNetworks is an unmodifiable list if non-null, so a defensive copy is not
// necessary.
mUnderlyingNetworks = nc.mUnderlyingNetworks;
- mEnterpriseCapabilitySubLevel = nc.mEnterpriseCapabilitySubLevel;
+ mEnterpriseId = nc.mEnterpriseId;
}
/**
@@ -343,6 +359,8 @@
NET_CAPABILITY_BIP,
NET_CAPABILITY_HEAD_UNIT,
NET_CAPABILITY_MMTEL,
+ NET_CAPABILITY_PRIORITIZE_LATENCY,
+ NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
})
public @interface NetCapability { }
@@ -586,29 +604,39 @@
*/
public static final int NET_CAPABILITY_MMTEL = 33;
+ /**
+ * Indicates that this network should be able to prioritize latency for the internet.
+ */
+ public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34;
+
+ /**
+ * Indicates that this network should be able to prioritize bandwidth for the internet.
+ */
+ public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_MMTEL;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
* 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
@@ -622,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
@@ -649,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.
@@ -784,34 +814,34 @@
}
/**
- * Adds the given enterprise capability sub level to this {@code NetworkCapability} instance.
- * Note that when searching for a network to satisfy a request, all capabilities sub level
+ * Adds the given enterprise capability identifier to this {@code NetworkCapability} instance.
+ * Note that when searching for a network to satisfy a request, all capabilities identifier
* requested must be satisfied.
*
- * @param enterpriseCapabilitySubLevel the enterprise capability sub level to be added.
+ * @param enterpriseId the enterprise capability identifier to be added.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- private @NonNull NetworkCapabilities addEnterpriseCapabilitySubLevel(
- @EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- checkValidEnterpriseCapabilitySublevel(enterpriseCapabilitySubLevel);
- mEnterpriseCapabilitySubLevel |= 1 << enterpriseCapabilitySubLevel;
+ public @NonNull NetworkCapabilities addEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ checkValidEnterpriseId(enterpriseId);
+ mEnterpriseId |= 1 << enterpriseId;
return this;
}
/**
- * Removes (if found) the given enterprise capability sublevel from this
- * {@code NetworkCapability} instance that were added via addEnterpriseCapabilitySubLevel(int)
+ * Removes (if found) the given enterprise capability identifier from this
+ * {@code NetworkCapability} instance that were added via addEnterpriseId(int)
*
- * @param enterpriseCapabilitySubLevel the enterprise capability sublevel to be removed.
+ * @param enterpriseId the enterprise capability identifier to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- private @NonNull NetworkCapabilities removeEnterpriseCapabilitySubLevel(
- @EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- checkValidEnterpriseCapabilitySublevel(enterpriseCapabilitySubLevel);
- final int mask = ~(1 << enterpriseCapabilitySubLevel);
- mEnterpriseCapabilitySubLevel &= mask;
+ private @NonNull NetworkCapabilities removeEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ checkValidEnterpriseId(enterpriseId);
+ final int mask = ~(1 << enterpriseId);
+ mEnterpriseId &= mask;
return this;
}
@@ -915,16 +945,19 @@
return null;
}
- private boolean equalsEnterpriseCapabilitiesSubLevel(@NonNull NetworkCapabilities nc) {
- return nc.mEnterpriseCapabilitySubLevel == this.mEnterpriseCapabilitySubLevel;
+ private boolean equalsEnterpriseCapabilitiesId(@NonNull NetworkCapabilities nc) {
+ return nc.mEnterpriseId == this.mEnterpriseId;
}
- private boolean satisfiedByEnterpriseCapabilitiesSubLevel(@NonNull NetworkCapabilities nc) {
- final int requestedEnterpriseCapabilitiesSubLevel = mEnterpriseCapabilitySubLevel;
- final int providedEnterpriseCapabailitiesSubLevel = nc.mEnterpriseCapabilitySubLevel;
+ private boolean satisfiedByEnterpriseCapabilitiesId(@NonNull NetworkCapabilities nc) {
+ final int requestedEnterpriseCapabilitiesId = mEnterpriseId;
+ final int providedEnterpriseCapabailitiesId = nc.mEnterpriseId;
- if ((providedEnterpriseCapabailitiesSubLevel & requestedEnterpriseCapabilitiesSubLevel)
- == requestedEnterpriseCapabilitiesSubLevel) {
+ if ((providedEnterpriseCapabailitiesId & requestedEnterpriseCapabilitiesId)
+ == requestedEnterpriseCapabilitiesId) {
+ return true;
+ } else if (providedEnterpriseCapabailitiesId == 0
+ && (requestedEnterpriseCapabilitiesId == (1L << NET_ENTERPRISE_ID_1))) {
return true;
} else {
return false;
@@ -996,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
@@ -1015,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.
@@ -1123,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.
@@ -1579,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
@@ -1780,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.
@@ -1836,7 +1952,7 @@
&& satisfiedByTransportTypes(nc)
&& (onlyImmutable || satisfiedByLinkBandwidths(nc))
&& satisfiedBySpecifier(nc)
- && satisfiedByEnterpriseCapabilitiesSubLevel(nc)
+ && satisfiedByEnterpriseCapabilitiesId(nc)
&& (onlyImmutable || satisfiedBySignalStrength(nc))
&& (onlyImmutable || satisfiedByUids(nc))
&& (onlyImmutable || satisfiedBySSID(nc))
@@ -1933,6 +2049,7 @@
&& equalsSpecifier(that)
&& equalsTransportInfo(that)
&& equalsUids(that)
+ && equalsAccessUids(that)
&& equalsSSID(that)
&& equalsOwnerUid(that)
&& equalsPrivateDnsBroken(that)
@@ -1940,7 +2057,7 @@
&& equalsAdministratorUids(that)
&& equalsSubscriptionIds(that)
&& equalsUnderlyingNetworks(that)
- && equalsEnterpriseCapabilitiesSubLevel(that);
+ && equalsEnterpriseCapabilitiesId(that);
}
@Override
@@ -1957,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
- + mEnterpriseCapabilitySubLevel * 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
@@ -1993,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());
@@ -2001,11 +2120,11 @@
dest.writeString(mRequestorPackageName);
dest.writeIntArray(CollectionUtils.toIntArray(mSubIds));
dest.writeTypedList(mUnderlyingNetworks);
- dest.writeInt(mEnterpriseCapabilitySubLevel);
+ dest.writeInt(mEnterpriseId);
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
- new Creator<NetworkCapabilities>() {
+ new Creator<>() {
@Override
public NetworkCapabilities createFromParcel(Parcel in) {
NetworkCapabilities netCap = new NetworkCapabilities();
@@ -2019,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());
@@ -2031,7 +2155,7 @@
netCap.mSubIds.add(subIdInts[i]);
}
netCap.setUnderlyingNetworks(in.createTypedArrayList(Network.CREATOR));
- netCap.mEnterpriseCapabilitySubLevel = in.readInt();
+ netCap.mEnterpriseId = in.readInt();
return netCap;
}
@Override
@@ -2095,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);
}
@@ -2123,10 +2252,10 @@
sb.append(" SubscriptionIds: ").append(mSubIds);
}
- if (0 != mEnterpriseCapabilitySubLevel) {
- sb.append(" EnterpriseCapabilitySublevel: ");
- appendStringRepresentationOfBitMaskToStringBuilder(sb, mEnterpriseCapabilitySubLevel,
- NetworkCapabilities::enterpriseCapabilitySublevelNameOf, "&");
+ if (0 != mEnterpriseId) {
+ sb.append(" EnterpriseId: ");
+ appendStringRepresentationOfBitMaskToStringBuilder(sb, mEnterpriseId,
+ NetworkCapabilities::enterpriseIdNameOf, "&");
}
sb.append(" UnderlyingNetworks: ");
@@ -2224,11 +2353,13 @@
case NET_CAPABILITY_BIP: return "BIP";
case NET_CAPABILITY_HEAD_UNIT: return "HEAD_UNIT";
case NET_CAPABILITY_MMTEL: return "MMTEL";
+ case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY";
+ case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH";
default: return Integer.toString(capability);
}
}
- private static @NonNull String enterpriseCapabilitySublevelNameOf(
+ private static @NonNull String enterpriseIdNameOf(
@NetCapability int capability) {
return Integer.toString(capability);
}
@@ -2269,21 +2400,21 @@
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");
}
}
- private static boolean isValidEnterpriseCapabilitySubLevel(
- @NetworkCapabilities.EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- return enterpriseCapabilitySubLevel >= NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1
- && enterpriseCapabilitySubLevel <= NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5;
+ private static boolean isValidEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int enterpriseId) {
+ return enterpriseId >= NET_ENTERPRISE_ID_1
+ && enterpriseId <= NET_ENTERPRISE_ID_5;
}
- private static void checkValidEnterpriseCapabilitySublevel(
- @NetworkCapabilities.EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- if (!isValidEnterpriseCapabilitySubLevel(enterpriseCapabilitySubLevel)) {
- throw new IllegalArgumentException("enterprise capability sublevel "
- + enterpriseCapabilitySubLevel + " is out of range");
+ private static void checkValidEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int enterpriseId) {
+ if (!isValidEnterpriseId(enterpriseId)) {
+ throw new IllegalArgumentException("enterprise capability identifier "
+ + enterpriseId + " is out of range");
}
}
@@ -2613,33 +2744,33 @@
}
/**
- * Adds the given enterprise capability sub level.
- * Note that when searching for a network to satisfy a request, all capabilities sub level
- * requested must be satisfied. Enterprise capability sub-level is applicable only
+ * Adds the given enterprise capability identifier.
+ * Note that when searching for a network to satisfy a request, all capabilities identifier
+ * requested must be satisfied. Enterprise capability identifier is applicable only
* for NET_CAPABILITY_ENTERPRISE capability
*
- * @param enterpriseCapabilitySubLevel enterprise capability sub-level.
+ * @param enterpriseId enterprise capability identifier.
*
* @return this builder
*/
@NonNull
- public Builder addEnterpriseCapabilitySubLevel(
- @EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- mCaps.addEnterpriseCapabilitySubLevel(enterpriseCapabilitySubLevel);
+ public Builder addEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ mCaps.addEnterpriseId(enterpriseId);
return this;
}
/**
- * Removes the given enterprise capability sub level. Enterprise capability sub-level is
+ * Removes the given enterprise capability identifier. Enterprise capability identifier is
* applicable only for NET_CAPABILITY_ENTERPRISE capability
*
- * @param enterpriseCapabilitySubLevel the enterprise capability subLevel
+ * @param enterpriseId the enterprise capability identifier
* @return this builder
*/
@NonNull
- public Builder removeEnterpriseCapabilitySubLevel(
- @EnterpriseCapabilitySubLevel int enterpriseCapabilitySubLevel) {
- mCaps.removeEnterpriseCapabilitySubLevel(enterpriseCapabilitySubLevel);
+ public Builder removeEnterpriseId(
+ @EnterpriseId int enterpriseId) {
+ mCaps.removeEnterpriseId(enterpriseId);
return this;
}
@@ -2878,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.
@@ -2902,12 +3071,12 @@
}
}
- if ((mCaps.getEnterpriseCapabilitySubLevels().length != 0)
+ if ((mCaps.getEnterpriseIds().length != 0)
&& !mCaps.hasCapability(NET_CAPABILITY_ENTERPRISE)) {
- throw new IllegalStateException("Enterprise capability sublevel is applicable only"
- + " with ENTERPRISE capability.");
+ throw new IllegalStateException("Enterprise capability identifier is applicable"
+ + " only with ENTERPRISE capability.");
}
return new NetworkCapabilities(mCaps);
}
}
-}
+}
\ No newline at end of file
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index afc76d6..b7a6076 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -725,6 +725,33 @@
}
/**
+ * Get the enteprise identifiers.
+ *
+ * Get all the enterprise identifiers set on this {@code NetworkCapability}
+ * @return array of all the enterprise identifiers.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public @NonNull @NetworkCapabilities.EnterpriseId int[] getEnterpriseIds() {
+ // No need to make a defensive copy here as NC#getCapabilities() already returns
+ // a new array.
+ return networkCapabilities.getEnterpriseIds();
+ }
+
+ /**
+ * Tests for the presence of an enterprise identifier on this instance.
+ *
+ * @param enterpriseId the enterprise capability identifier to be tested for.
+ * @return {@code true} if set on this instance.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public boolean hasEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int enterpriseId) {
+ return networkCapabilities.hasEnterpriseId(enterpriseId);
+ }
+
+ /**
* Gets all the forbidden capabilities set on this {@code NetworkRequest} instance.
*
* @return an array of forbidden capability values for this instance.
diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java
index 0571b36..f43acce 100644
--- a/framework/src/android/net/ProfileNetworkPreference.java
+++ b/framework/src/android/net/ProfileNetworkPreference.java
@@ -18,6 +18,8 @@
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -38,12 +40,15 @@
@SystemApi(client = MODULE_LIBRARIES)
public final class ProfileNetworkPreference implements Parcelable {
private final @ProfileNetworkPreferencePolicy int mPreference;
+ private final @NetworkCapabilities.EnterpriseId int mPreferenceEnterpriseId;
private final List<Integer> mIncludedUids;
private final List<Integer> mExcludedUids;
private ProfileNetworkPreference(int preference, List<Integer> includedUids,
- List<Integer> excludedUids) {
+ List<Integer> excludedUids,
+ @NetworkCapabilities.EnterpriseId int preferenceEnterpriseId) {
mPreference = preference;
+ mPreferenceEnterpriseId = preferenceEnterpriseId;
if (includedUids != null) {
mIncludedUids = new ArrayList<>(includedUids);
} else {
@@ -61,6 +66,7 @@
mPreference = in.readInt();
mIncludedUids = in.readArrayList(Integer.class.getClassLoader());
mExcludedUids = in.readArrayList(Integer.class.getClassLoader());
+ mPreferenceEnterpriseId = in.readInt();
}
public int getPreference() {
@@ -95,12 +101,30 @@
return new ArrayList<>(mExcludedUids);
}
+ /**
+ * Get preference enterprise identifier.
+ *
+ * Preference enterprise identifier will be used to create different network preferences
+ * within enterprise preference category.
+ * Valid values starts from PROFILE_NETWORK_PREFERENCE_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * Preference identifier is not applicable if preference is set as
+ * PROFILE_NETWORK_PREFERENCE_DEFAULT. Default value is
+ * NetworkCapabilities.NET_ENTERPRISE_ID_1.
+ * @return Preference enterprise identifier.
+ *
+ */
+ public @NetworkCapabilities.EnterpriseId int getPreferenceEnterpriseId() {
+ return mPreferenceEnterpriseId;
+ }
+
@Override
public String toString() {
return "ProfileNetworkPreference{"
+ "mPreference=" + getPreference()
+ "mIncludedUids=" + mIncludedUids.toString()
+ "mExcludedUids=" + mExcludedUids.toString()
+ + "mPreferenceEnterpriseId=" + mPreferenceEnterpriseId
+ '}';
}
@@ -111,12 +135,14 @@
final ProfileNetworkPreference that = (ProfileNetworkPreference) o;
return mPreference == that.mPreference
&& (Objects.equals(mIncludedUids, that.mIncludedUids))
- && (Objects.equals(mExcludedUids, that.mExcludedUids));
+ && (Objects.equals(mExcludedUids, that.mExcludedUids))
+ && mPreferenceEnterpriseId == that.mPreferenceEnterpriseId;
}
@Override
public int hashCode() {
return mPreference
+ + mPreferenceEnterpriseId * 2
+ (Objects.hashCode(mIncludedUids) * 11)
+ (Objects.hashCode(mExcludedUids) * 13);
}
@@ -130,6 +156,7 @@
PROFILE_NETWORK_PREFERENCE_DEFAULT;
private @NonNull List<Integer> mIncludedUids = new ArrayList<>();
private @NonNull List<Integer> mExcludedUids = new ArrayList<>();
+ private int mPreferenceEnterpriseId;
/**
* Constructs an empty Builder with PROFILE_NETWORK_PREFERENCE_DEFAULT profile preference
@@ -191,7 +218,24 @@
return this;
}
- /**
+ /**
+ * Check if given preference enterprise identifier is valid
+ *
+ * Valid values starts from PROFILE_NETWORK_PREFERENCE_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * @return True if valid else false
+ * @hide
+ */
+ private boolean isEnterpriseIdentifierValid(
+ @NetworkCapabilities.EnterpriseId int identifier) {
+ if ((identifier >= NET_ENTERPRISE_ID_1)
+ && (identifier <= NET_ENTERPRISE_ID_5)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Returns an instance of {@link ProfileNetworkPreference} created from the
* fields set on this builder.
*/
@@ -201,7 +245,35 @@
throw new IllegalArgumentException("Both includedUids and excludedUids "
+ "cannot be nonempty");
}
- return new ProfileNetworkPreference(mPreference, mIncludedUids, mExcludedUids);
+
+ if (((mPreference != PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ && (!isEnterpriseIdentifierValid(mPreferenceEnterpriseId)))
+ || ((mPreference == PROFILE_NETWORK_PREFERENCE_DEFAULT)
+ && (mPreferenceEnterpriseId != 0))) {
+ throw new IllegalStateException("Invalid preference enterprise identifier");
+ }
+ return new ProfileNetworkPreference(mPreference, mIncludedUids,
+ mExcludedUids, mPreferenceEnterpriseId);
+ }
+
+ /**
+ * Set the preference enterprise identifier.
+ *
+ * Preference enterprise identifier will be used to create different network preferences
+ * within enterprise preference category.
+ * Valid values starts from NetworkCapabilities.NET_ENTERPRISE_ID_1 to
+ * NetworkCapabilities.NET_ENTERPRISE_ID_5.
+ * Preference identifier is not applicable if preference is set as
+ * PROFILE_NETWORK_PREFERENCE_DEFAULT. Default value is
+ * NetworkCapabilities.NET_ENTERPRISE_ID_1.
+ * @param preferenceId preference sub level
+ * @return The builder to facilitate chaining.
+ */
+ @NonNull
+ public Builder setPreferenceEnterpriseId(
+ @NetworkCapabilities.EnterpriseId int preferenceId) {
+ mPreferenceEnterpriseId = preferenceId;
+ return this;
}
}
@@ -210,6 +282,7 @@
dest.writeInt(mPreference);
dest.writeList(mIncludedUids);
dest.writeList(mExcludedUids);
+ dest.writeInt(mPreferenceEnterpriseId);
}
@Override
diff --git a/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
new file mode 100644
index 0000000..d24b14b
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -0,0 +1,72 @@
+//
+// Copyright (C) 2017 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "libnetworkstats",
+ vendor_available: false,
+ host_supported: false,
+ header_libs: ["bpf_connectivity_headers"],
+ srcs: [
+ "BpfNetworkStats.cpp"
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ export_include_dirs: ["include"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ sanitize: {
+ cfi: true,
+ },
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ ],
+ min_sdk_version: "30",
+}
+
+cc_test {
+ name: "libnetworkstats_test",
+ test_suites: ["general-tests"],
+ require_root: true, // required by setrlimitForTest()
+ header_libs: ["bpf_connectivity_headers"],
+ srcs: [
+ "BpfNetworkStatsTest.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ static_libs: [
+ "libgmock",
+ "libnetworkstats",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+}
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
new file mode 100644
index 0000000..4d605ce
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <inttypes.h>
+#include <net/if.h>
+#include <string.h>
+#include <unordered_set>
+
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include "android-base/file.h"
+#include "android-base/strings.h"
+#include "android-base/unique_fd.h"
+#include "bpf/BpfMap.h"
+#include "bpf_shared.h"
+#include "netdbpf/BpfNetworkStats.h"
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+
+#define LOG_TAG "BpfNetworkStats"
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+
+// The target map for stats reading should be the inactive map, which is opposite
+// from the config value.
+static constexpr char const* STATS_MAP_PATH[] = {STATS_MAP_B_PATH, STATS_MAP_A_PATH};
+
+int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& appUidStatsMap) {
+ auto statsEntry = appUidStatsMap.readValue(uid);
+ if (statsEntry.ok()) {
+ stats->rxPackets = statsEntry.value().rxPackets;
+ stats->txPackets = statsEntry.value().txPackets;
+ stats->rxBytes = statsEntry.value().rxBytes;
+ stats->txBytes = statsEntry.value().txBytes;
+ }
+ return (statsEntry.ok() || statsEntry.error().code() == ENOENT) ? 0
+ : -statsEntry.error().code();
+}
+
+int bpfGetUidStats(uid_t uid, Stats* stats) {
+ BpfMapRO<uint32_t, StatsValue> appUidStatsMap(APP_UID_STATS_MAP_PATH);
+
+ if (!appUidStatsMap.isValid()) {
+ int ret = -errno;
+ ALOGE("Opening appUidStatsMap(%s) failed: %s", APP_UID_STATS_MAP_PATH, strerror(errno));
+ return ret;
+ }
+ return bpfGetUidStatsInternal(uid, stats, appUidStatsMap);
+}
+
+int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceNameMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ stats->tcpRxPackets = -1;
+ stats->tcpTxPackets = -1;
+ const auto processIfaceStats =
+ [iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal](
+ const uint32_t& key,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap) -> Result<void> {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceNameMap, ifaceStatsMap, key, ifname, key,
+ &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ if (!iface || !strcmp(iface, ifname)) {
+ Result<StatsValue> statsEntry = ifaceStatsMap.readValue(key);
+ if (!statsEntry.ok()) {
+ return statsEntry.error();
+ }
+ stats->rxPackets += statsEntry.value().rxPackets;
+ stats->txPackets += statsEntry.value().txPackets;
+ stats->rxBytes += statsEntry.value().rxBytes;
+ stats->txBytes += statsEntry.value().txBytes;
+ }
+ return Result<void>();
+ };
+ auto res = ifaceStatsMap.iterate(processIfaceStats);
+ return res.ok() ? 0 : -res.error().code();
+}
+
+int bpfGetIfaceStats(const char* iface, Stats* stats) {
+ BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+ int ret;
+ if (!ifaceStatsMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ if (!ifaceIndexNameMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap);
+}
+
+stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry,
+ const char* ifname) {
+ stats_line newLine;
+ strlcpy(newLine.iface, ifname, sizeof(newLine.iface));
+ newLine.uid = (int32_t)statsKey.uid;
+ newLine.set = (int32_t)statsKey.counterSet;
+ newLine.tag = (int32_t)statsKey.tag;
+ newLine.rxPackets = statsEntry.rxPackets;
+ newLine.txPackets = statsEntry.txPackets;
+ newLine.rxBytes = statsEntry.rxBytes;
+ newLine.txBytes = statsEntry.txBytes;
+ return newLine;
+}
+
+int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ const auto processDetailUidStats =
+ [lines, &limitIfaces, &limitTag, &limitUid, &unknownIfaceBytesTotal, &ifaceMap](
+ const StatsKey& key,
+ const BpfMap<StatsKey, StatsValue>& statsMap) -> Result<void> {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceMap, statsMap, key.ifaceIndex, ifname, key,
+ &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ std::string ifnameStr(ifname);
+ if (limitIfaces.size() > 0 &&
+ std::find(limitIfaces.begin(), limitIfaces.end(), ifnameStr) == limitIfaces.end()) {
+ // Nothing matched; skip this line.
+ return Result<void>();
+ }
+ if (limitTag != TAG_ALL && uint32_t(limitTag) != key.tag) {
+ return Result<void>();
+ }
+ if (limitUid != UID_ALL && uint32_t(limitUid) != key.uid) {
+ return Result<void>();
+ }
+ Result<StatsValue> statsEntry = statsMap.readValue(key);
+ if (!statsEntry.ok()) {
+ return base::ResultError(statsEntry.error().message(), statsEntry.error().code());
+ }
+ lines->push_back(populateStatsEntry(key, statsEntry.value(), ifname));
+ return Result<void>();
+ };
+ Result<void> res = statsMap.iterate(processDetailUidStats);
+ if (!res.ok()) {
+ ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s",
+ strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ // Since eBPF use hash map to record stats, network stats collected from
+ // eBPF will be out of order. And the performance of findIndexHinted in
+ // NetworkStats will also be impacted.
+ //
+ // Furthermore, since the StatsKey contains iface index, the network stats
+ // reported to framework would create items with the same iface, uid, tag
+ // and set, which causes NetworkStats maps wrong item to subtract.
+ //
+ // Thus, the stats needs to be properly sorted and grouped before reported.
+ groupNetworkStats(lines);
+ return 0;
+}
+
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid) {
+ BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ if (!ifaceIndexNameMap.isValid()) {
+ int ret = -errno;
+ ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
+ return ret;
+ }
+
+ BpfMapRO<uint32_t, uint8_t> configurationMap(CONFIGURATION_MAP_PATH);
+ if (!configurationMap.isValid()) {
+ int ret = -errno;
+ ALOGE("get configuration map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ auto configuration = configurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
+ if (!configuration.ok()) {
+ ALOGE("Cannot read the old configuration from map: %s",
+ configuration.error().message().c_str());
+ return -configuration.error().code();
+ }
+ const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
+ BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
+ if (!statsMap.isValid()) {
+ int ret = -errno;
+ ALOGE("get stats map fd failed: %s, path: %s", strerror(errno), statsMapPath);
+ return ret;
+ }
+
+ // It is safe to read and clear the old map now since the
+ // networkStatsFactory should call netd to swap the map in advance already.
+ int ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid, statsMap,
+ ifaceIndexNameMap);
+ if (ret) {
+ ALOGE("parse detail network stats failed: %s", strerror(errno));
+ return ret;
+ }
+
+ Result<void> res = statsMap.clear();
+ if (!res.ok()) {
+ ALOGE("Clean up current stats map failed: %s", strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ return 0;
+}
+
+int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
+ const BpfMap<uint32_t, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
+ int64_t unknownIfaceBytesTotal = 0;
+ const auto processDetailIfaceStats = [lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap](
+ const uint32_t& key, const StatsValue& value,
+ const BpfMap<uint32_t, StatsValue>&) {
+ char ifname[IFNAMSIZ];
+ if (getIfaceNameFromMap(ifaceMap, statsMap, key, ifname, key, &unknownIfaceBytesTotal)) {
+ return Result<void>();
+ }
+ StatsKey fakeKey = {
+ .uid = (uint32_t)UID_ALL,
+ .tag = (uint32_t)TAG_NONE,
+ .counterSet = (uint32_t)SET_ALL,
+ };
+ lines->push_back(populateStatsEntry(fakeKey, value, ifname));
+ return Result<void>();
+ };
+ Result<void> res = statsMap.iterateWithValue(processDetailIfaceStats);
+ if (!res.ok()) {
+ ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s",
+ strerror(res.error().code()));
+ return -res.error().code();
+ }
+
+ groupNetworkStats(lines);
+ return 0;
+}
+
+int parseBpfNetworkStatsDev(std::vector<stats_line>* lines) {
+ int ret = 0;
+ BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
+ if (!ifaceIndexNameMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
+ return ret;
+ }
+
+ BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+ if (!ifaceStatsMap.isValid()) {
+ ret = -errno;
+ ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
+ return ret;
+ }
+ return parseBpfNetworkStatsDevInternal(lines, ifaceStatsMap, ifaceIndexNameMap);
+}
+
+uint64_t combineUidTag(const uid_t uid, const uint32_t tag) {
+ return (uint64_t)uid << 32 | tag;
+}
+
+void groupNetworkStats(std::vector<stats_line>* lines) {
+ if (lines->size() <= 1) return;
+ std::sort(lines->begin(), lines->end());
+
+ // Similar to std::unique(), but aggregates the duplicates rather than discarding them.
+ size_t nextOutput = 0;
+ for (size_t i = 1; i < lines->size(); i++) {
+ if (lines->at(nextOutput) == lines->at(i)) {
+ lines->at(nextOutput) += lines->at(i);
+ } else {
+ nextOutput++;
+ if (nextOutput != i) {
+ lines->at(nextOutput) = lines->at(i);
+ }
+ }
+ }
+
+ if (lines->size() != nextOutput + 1) {
+ lines->resize(nextOutput + 1);
+ }
+}
+
+// True if lhs equals to rhs, only compare iface, uid, tag and set.
+bool operator==(const stats_line& lhs, const stats_line& rhs) {
+ return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.set == rhs.set) &&
+ !strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface)));
+}
+
+// True if lhs is smaller than rhs, only compare iface, uid, tag and set.
+bool operator<(const stats_line& lhs, const stats_line& rhs) {
+ int ret = strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface));
+ if (ret != 0) return ret < 0;
+ if (lhs.uid < rhs.uid) return true;
+ if (lhs.uid > rhs.uid) return false;
+ if (lhs.tag < rhs.tag) return true;
+ if (lhs.tag > rhs.tag) return false;
+ if (lhs.set < rhs.set) return true;
+ if (lhs.set > rhs.set) return false;
+ return false;
+}
+
+stats_line& stats_line::operator=(const stats_line& rhs) {
+ if (this == &rhs) return *this;
+
+ strlcpy(iface, rhs.iface, sizeof(iface));
+ uid = rhs.uid;
+ set = rhs.set;
+ tag = rhs.tag;
+ rxPackets = rhs.rxPackets;
+ txPackets = rhs.txPackets;
+ rxBytes = rhs.rxBytes;
+ txBytes = rhs.txBytes;
+ return *this;
+}
+
+stats_line& stats_line::operator+=(const stats_line& rhs) {
+ rxPackets += rhs.rxPackets;
+ txPackets += rhs.txPackets;
+ rxBytes += rhs.rxBytes;
+ txBytes += rhs.txBytes;
+ return *this;
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
new file mode 100644
index 0000000..4974b96
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+#include "netdbpf/BpfNetworkStats.h"
+
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+using base::unique_fd;
+
+constexpr int TEST_MAP_SIZE = 10;
+constexpr uid_t TEST_UID1 = 10086;
+constexpr uid_t TEST_UID2 = 12345;
+constexpr uint32_t TEST_TAG = 42;
+constexpr int TEST_COUNTERSET0 = 0;
+constexpr int TEST_COUNTERSET1 = 1;
+constexpr uint64_t TEST_BYTES0 = 1000;
+constexpr uint64_t TEST_BYTES1 = 2000;
+constexpr uint64_t TEST_PACKET0 = 100;
+constexpr uint64_t TEST_PACKET1 = 200;
+constexpr const char IFACE_NAME1[] = "lo";
+constexpr const char IFACE_NAME2[] = "wlan0";
+constexpr const char IFACE_NAME3[] = "rmnet_data0";
+// A iface name that the size is bigger than IFNAMSIZ
+constexpr const char LONG_IFACE_NAME[] = "wlanWithALongName";
+constexpr const char TRUNCATED_IFACE_NAME[] = "wlanWithALongNa";
+constexpr uint32_t IFACE_INDEX1 = 1;
+constexpr uint32_t IFACE_INDEX2 = 2;
+constexpr uint32_t IFACE_INDEX3 = 3;
+constexpr uint32_t IFACE_INDEX4 = 4;
+constexpr uint32_t UNKNOWN_IFACE = 0;
+
+class BpfNetworkStatsHelperTest : public testing::Test {
+ protected:
+ BpfNetworkStatsHelperTest() {}
+ BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
+ BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
+ BpfMap<StatsKey, StatsValue> mFakeStatsMap;
+ BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
+ BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap;
+
+ void SetUp() {
+ ASSERT_EQ(0, setrlimitForTest());
+
+ mFakeCookieTagMap = BpfMap<uint64_t, UidTagValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeCookieTagMap.getMap());
+
+ mFakeAppUidStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeAppUidStatsMap.getMap());
+
+ mFakeStatsMap = BpfMap<StatsKey, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeStatsMap.getMap());
+
+ mFakeIfaceIndexNameMap = BpfMap<uint32_t, IfaceValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeIfaceIndexNameMap.getMap());
+
+ mFakeIfaceStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ ASSERT_LE(0, mFakeIfaceStatsMap.getMap());
+ }
+
+ void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
+ auto tagResult = mFakeCookieTagMap.readValue(cookie);
+ EXPECT_RESULT_OK(tagResult);
+ EXPECT_EQ(uid, tagResult.value().uid);
+ EXPECT_EQ(tag, tagResult.value().tag);
+ }
+
+ void populateFakeStats(uid_t uid, uint32_t tag, uint32_t ifaceIndex, uint32_t counterSet,
+ StatsValue value, BpfMap<StatsKey, StatsValue>& map) {
+ StatsKey key = {
+ .uid = (uint32_t)uid, .tag = tag, .counterSet = counterSet, .ifaceIndex = ifaceIndex};
+ EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+ }
+
+ void updateIfaceMap(const char* ifaceName, uint32_t ifaceIndex) {
+ IfaceValue iface;
+ strlcpy(iface.name, ifaceName, IFNAMSIZ);
+ EXPECT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY));
+ }
+
+ void expectStatsEqual(const StatsValue& target, const Stats& result) {
+ EXPECT_EQ(target.rxPackets, result.rxPackets);
+ EXPECT_EQ(target.rxBytes, result.rxBytes);
+ EXPECT_EQ(target.txPackets, result.txPackets);
+ EXPECT_EQ(target.txBytes, result.txBytes);
+ }
+
+ void expectStatsLineEqual(const StatsValue target, const char* iface, uint32_t uid,
+ int counterSet, uint32_t tag, const stats_line& result) {
+ EXPECT_EQ(0, strcmp(iface, result.iface));
+ EXPECT_EQ(uid, (uint32_t)result.uid);
+ EXPECT_EQ((uint32_t) counterSet, result.set);
+ EXPECT_EQ(tag, (uint32_t)result.tag);
+ EXPECT_EQ(target.rxPackets, (uint64_t)result.rxPackets);
+ EXPECT_EQ(target.rxBytes, (uint64_t)result.rxBytes);
+ EXPECT_EQ(target.txPackets, (uint64_t)result.txPackets);
+ EXPECT_EQ(target.txBytes, (uint64_t)result.txBytes);
+ }
+};
+
+// TEST to verify the behavior of bpf map when cocurrent deletion happens when
+// iterating the same map.
+TEST_F(BpfNetworkStatsHelperTest, TestIterateMapWithDeletion) {
+ for (int i = 0; i < 5; i++) {
+ uint64_t cookie = i + 1;
+ UidTagValue tag = {.uid = TEST_UID1, .tag = TEST_TAG};
+ EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, tag, BPF_ANY));
+ }
+ uint64_t curCookie = 0;
+ auto nextCookie = mFakeCookieTagMap.getNextKey(curCookie);
+ EXPECT_RESULT_OK(nextCookie);
+ uint64_t headOfMap = nextCookie.value();
+ curCookie = nextCookie.value();
+ // Find the second entry in the map, then immediately delete it.
+ nextCookie = mFakeCookieTagMap.getNextKey(curCookie);
+ EXPECT_RESULT_OK(nextCookie);
+ EXPECT_RESULT_OK(mFakeCookieTagMap.deleteValue((nextCookie.value())));
+ // Find the entry that is now immediately after headOfMap, then delete that.
+ nextCookie = mFakeCookieTagMap.getNextKey(curCookie);
+ EXPECT_RESULT_OK(nextCookie);
+ EXPECT_RESULT_OK(mFakeCookieTagMap.deleteValue((nextCookie.value())));
+ // Attempting to read an entry that has been deleted fails with ENOENT.
+ curCookie = nextCookie.value();
+ auto tagResult = mFakeCookieTagMap.readValue(curCookie);
+ EXPECT_EQ(ENOENT, tagResult.error().code());
+ // Finding the entry after our deleted entry restarts iteration from the beginning of the map.
+ nextCookie = mFakeCookieTagMap.getNextKey(curCookie);
+ EXPECT_RESULT_OK(nextCookie);
+ EXPECT_EQ(headOfMap, nextCookie.value());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestBpfIterateMap) {
+ for (int i = 0; i < 5; i++) {
+ uint64_t cookie = i + 1;
+ UidTagValue tag = {.uid = TEST_UID1, .tag = TEST_TAG};
+ EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, tag, BPF_ANY));
+ }
+ int totalCount = 0;
+ int totalSum = 0;
+ const auto iterateWithoutDeletion =
+ [&totalCount, &totalSum](const uint64_t& key, const BpfMap<uint64_t, UidTagValue>&) {
+ EXPECT_GE((uint64_t)5, key);
+ totalCount++;
+ totalSum += key;
+ return Result<void>();
+ };
+ EXPECT_RESULT_OK(mFakeCookieTagMap.iterate(iterateWithoutDeletion));
+ EXPECT_EQ(5, totalCount);
+ EXPECT_EQ(1 + 2 + 3 + 4 + 5, totalSum);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestUidStatsNoTraffic) {
+ StatsValue value1 = {
+ .rxPackets = 0,
+ .rxBytes = 0,
+ .txPackets = 0,
+ .txBytes = 0,
+ };
+ Stats result1 = {};
+ ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID1, &result1, mFakeAppUidStatsMap));
+ expectStatsEqual(value1, result1);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetUidStatsTotal) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET0 * 2,
+ .rxBytes = TEST_BYTES0 * 2,
+ .txPackets = TEST_PACKET1 * 2,
+ .txBytes = TEST_BYTES1 * 2,
+ };
+ ASSERT_RESULT_OK(mFakeAppUidStatsMap.writeValue(TEST_UID1, value1, BPF_ANY));
+ ASSERT_RESULT_OK(mFakeAppUidStatsMap.writeValue(TEST_UID2, value2, BPF_ANY));
+ Stats result1 = {};
+ ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID1, &result1, mFakeAppUidStatsMap));
+ expectStatsEqual(value1, result1);
+
+ Stats result2 = {};
+ ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID2, &result2, mFakeAppUidStatsMap));
+ expectStatsEqual(value2, result2);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeStatsMap);
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)2, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME3, TEST_UID2, TEST_COUNTERSET1, 0, lines.front());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsInternal) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET1,
+ .rxBytes = TEST_BYTES1,
+ .txPackets = TEST_PACKET0,
+ .txBytes = TEST_BYTES0,
+ };
+ uint32_t ifaceStatsKey = IFACE_INDEX1;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX2;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX3;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+
+ Stats result1 = {};
+ ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME1, &result1, mFakeIfaceStatsMap,
+ mFakeIfaceIndexNameMap));
+ expectStatsEqual(value1, result1);
+ Stats result2 = {};
+ ASSERT_EQ(0, bpfGetIfaceStatsInternal(IFACE_NAME2, &result2, mFakeIfaceStatsMap,
+ mFakeIfaceIndexNameMap));
+ expectStatsEqual(value2, result2);
+ Stats totalResult = {};
+ ASSERT_EQ(0, bpfGetIfaceStatsInternal(NULL, &totalResult, mFakeIfaceStatsMap,
+ mFakeIfaceIndexNameMap));
+ StatsValue totalValue = {
+ .rxPackets = TEST_PACKET0 * 2 + TEST_PACKET1,
+ .rxBytes = TEST_BYTES0 * 2 + TEST_BYTES1,
+ .txPackets = TEST_PACKET1 * 2 + TEST_PACKET0,
+ .txBytes = TEST_BYTES1 * 2 + TEST_BYTES0,
+ };
+ expectStatsEqual(totalValue, totalResult);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsDetail) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value1,
+ mFakeStatsMap);
+ populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)4, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)3, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)2, lines.size());
+ lines.clear();
+ ifaces.push_back(std::string(IFACE_NAME1));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines.front());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsWithSkippedIface) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ populateFakeStats(0, 0, 0, OVERFLOW_COUNTERSET, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)4, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)3, lines.size());
+ lines.clear();
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, 0, lines.front());
+ lines.clear();
+ ifaces.push_back(std::string(IFACE_NAME1));
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
+ mFakeStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)2, lines.size());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestUnknownIfaceError) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0 * 20,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1 * 20,
+ };
+ uint32_t ifaceIndex = UNKNOWN_IFACE;
+ populateFakeStats(TEST_UID1, 0, ifaceIndex, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0 * 40,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1 * 40,
+ };
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap);
+ StatsKey curKey = {
+ .uid = TEST_UID1,
+ .tag = 0,
+ .counterSet = TEST_COUNTERSET0,
+ .ifaceIndex = ifaceIndex,
+ };
+ char ifname[IFNAMSIZ];
+ int64_t unknownIfaceBytesTotal = 0;
+ ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
+ ifname, curKey, &unknownIfaceBytesTotal));
+ ASSERT_EQ(((int64_t)(TEST_BYTES0 * 20 + TEST_BYTES1 * 20)), unknownIfaceBytesTotal);
+ curKey.ifaceIndex = IFACE_INDEX2;
+ ASSERT_EQ(-ENODEV, getIfaceNameFromMap(mFakeIfaceIndexNameMap, mFakeStatsMap, ifaceIndex,
+ ifname, curKey, &unknownIfaceBytesTotal));
+ ASSERT_EQ(-1, unknownIfaceBytesTotal);
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ // TODO: find a way to test the total of unknown Iface Bytes go above limit.
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front());
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsDetail) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME3, IFACE_INDEX3);
+ updateIfaceMap(LONG_IFACE_NAME, IFACE_INDEX4);
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET1,
+ .rxBytes = TEST_BYTES1,
+ .txPackets = TEST_PACKET0,
+ .txBytes = TEST_BYTES0,
+ };
+ uint32_t ifaceStatsKey = IFACE_INDEX1;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX2;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX3;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX4;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ std::vector<stats_line> lines;
+ ASSERT_EQ(0,
+ parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((unsigned long)4, lines.size());
+
+ expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
+ expectStatsLineEqual(value1, IFACE_NAME3, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
+ expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[2]);
+ ASSERT_EQ(0, strcmp(TRUNCATED_IFACE_NAME, lines[3].iface));
+ expectStatsLineEqual(value2, TRUNCATED_IFACE_NAME, UID_ALL, SET_ALL, TAG_NONE, lines[3]);
+}
+
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortedAndGrouped) {
+ // Create iface indexes with duplicate iface name.
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+ updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX3); // Duplicate!
+
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+ StatsValue value2 = {
+ .rxPackets = TEST_PACKET1,
+ .rxBytes = TEST_BYTES1,
+ .txPackets = TEST_PACKET0,
+ .txBytes = TEST_BYTES0,
+ };
+ StatsValue value3 = {
+ .rxPackets = TEST_PACKET0 * 2,
+ .rxBytes = TEST_BYTES0 * 2,
+ .txPackets = TEST_PACKET1 * 2,
+ .txBytes = TEST_BYTES1 * 2,
+ };
+
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+
+ // Test empty stats.
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 0, lines.size());
+ lines.clear();
+
+ // Test 1 line stats.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 1, lines.size());
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+ lines.clear();
+
+ // These items should not be grouped.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX2, TEST_COUNTERSET0, value2, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET1, value2, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
+ mFakeStatsMap);
+ populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 5, lines.size());
+ lines.clear();
+
+ // These items should be grouped.
+ populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 5, lines.size());
+
+ // Verify Sorted & Grouped.
+ expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+ expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG, lines[1]);
+ expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[2]);
+ expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG, lines[3]);
+ expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[4]);
+ lines.clear();
+
+ // Perform test on IfaceStats.
+ uint32_t ifaceStatsKey = IFACE_INDEX2;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
+ ifaceStatsKey = IFACE_INDEX1;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+
+ // This should be grouped.
+ ifaceStatsKey = IFACE_INDEX3;
+ EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
+
+ ASSERT_EQ(0,
+ parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 2, lines.size());
+
+ expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
+ expectStatsLineEqual(value2, IFACE_NAME2, UID_ALL, SET_ALL, TAG_NONE, lines[1]);
+ lines.clear();
+}
+
+// Test to verify that subtract overflow will not be triggered by the compare function invoked from
+// sorting. See http:/b/119193941.
+TEST_F(BpfNetworkStatsHelperTest, TestGetStatsSortAndOverflow) {
+ updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
+
+ StatsValue value1 = {
+ .rxPackets = TEST_PACKET0,
+ .rxBytes = TEST_BYTES0,
+ .txPackets = TEST_PACKET1,
+ .txBytes = TEST_BYTES1,
+ };
+
+ // Mutate uid, 0 < TEST_UID1 < INT_MAX < INT_MIN < UINT_MAX.
+ populateFakeStats(0, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(UINT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(INT_MIN, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(INT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ // Mutate tag, 0 < TEST_TAG < INT_MAX < INT_MIN < UINT_MAX.
+ populateFakeStats(TEST_UID1, INT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, INT_MIN, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+ populateFakeStats(TEST_UID1, UINT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+
+ // TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible.
+
+ std::vector<stats_line> lines;
+ std::vector<std::string> ifaces;
+ ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
+ mFakeIfaceIndexNameMap));
+ ASSERT_EQ((size_t) 8, lines.size());
+
+ // Uid 0 first
+ expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+
+ // Test uid, mutate tag.
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[1]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MAX, lines[2]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MIN, lines[3]);
+ expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, UINT_MAX, lines[4]);
+
+ // Mutate uid.
+ expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[5]);
+ expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN, TEST_COUNTERSET0, TEST_TAG, lines[6]);
+ expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[7]);
+ lines.clear();
+}
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
new file mode 100644
index 0000000..8ab7e25
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _BPF_NETWORKSTATS_H
+#define _BPF_NETWORKSTATS_H
+
+#include <bpf/BpfMap.h>
+#include "bpf_shared.h"
+
+namespace android {
+namespace bpf {
+
+// TODO: set this to a proper value based on the map size;
+constexpr int TAG_STATS_MAP_SOFT_LIMIT = 3;
+constexpr int UID_ALL = -1;
+constexpr int TAG_ALL = -1;
+constexpr int TAG_NONE = 0;
+constexpr int SET_ALL = -1;
+constexpr int SET_DEFAULT = 0;
+constexpr int SET_FOREGROUND = 1;
+
+// The limit for stats received by a unknown interface;
+constexpr const int64_t MAX_UNKNOWN_IFACE_BYTES = 100 * 1000;
+
+// This is used by
+// frameworks/base/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+// make sure it is consistent with the JNI code before changing this.
+struct stats_line {
+ char iface[32];
+ uint32_t uid;
+ uint32_t set;
+ uint32_t tag;
+ int64_t rxBytes;
+ int64_t rxPackets;
+ int64_t txBytes;
+ int64_t txPackets;
+
+ stats_line& operator=(const stats_line& rhs);
+ stats_line& operator+=(const stats_line& rhs);
+};
+
+bool operator==(const stats_line& lhs, const stats_line& rhs);
+bool operator<(const stats_line& lhs, const stats_line& rhs);
+
+// For test only
+int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& appUidStatsMap);
+// For test only
+int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
+ const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceNameMap);
+// For test only
+int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap);
+// For test only
+int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap);
+// For test only
+template <class Key>
+int getIfaceNameFromMap(const BpfMap<uint32_t, IfaceValue>& ifaceMap,
+ const BpfMap<Key, StatsValue>& statsMap, uint32_t ifaceIndex, char* ifname,
+ const Key& curKey, int64_t* unknownIfaceBytesTotal) {
+ auto iface = ifaceMap.readValue(ifaceIndex);
+ if (!iface.ok()) {
+ maybeLogUnknownIface(ifaceIndex, statsMap, curKey, unknownIfaceBytesTotal);
+ return -ENODEV;
+ }
+ strlcpy(ifname, iface.value().name, sizeof(IfaceValue));
+ return 0;
+}
+
+template <class Key>
+void maybeLogUnknownIface(int ifaceIndex, const BpfMap<Key, StatsValue>& statsMap,
+ const Key& curKey, int64_t* unknownIfaceBytesTotal) {
+ // Have we already logged an error?
+ if (*unknownIfaceBytesTotal == -1) {
+ return;
+ }
+
+ // Are we undercounting enough data to be worth logging?
+ auto statsEntry = statsMap.readValue(curKey);
+ if (!statsEntry.ok()) {
+ // No data is being undercounted.
+ return;
+ }
+
+ *unknownIfaceBytesTotal += (statsEntry.value().rxBytes + statsEntry.value().txBytes);
+ if (*unknownIfaceBytesTotal >= MAX_UNKNOWN_IFACE_BYTES) {
+ ALOGE("Unknown name for ifindex %d with more than %" PRId64 " bytes of traffic", ifaceIndex,
+ *unknownIfaceBytesTotal);
+ *unknownIfaceBytesTotal = -1;
+ }
+}
+
+// For test only
+int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
+ const BpfMap<uint32_t, StatsValue>& statsMap,
+ const BpfMap<uint32_t, IfaceValue>& ifaceMap);
+
+int bpfGetUidStats(uid_t uid, Stats* stats);
+int bpfGetIfaceStats(const char* iface, Stats* stats);
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
+ const std::vector<std::string>& limitIfaces, int limitTag,
+ int limitUid);
+
+int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
+void groupNetworkStats(std::vector<stats_line>* lines);
+int cleanStatsMap();
+} // namespace bpf
+} // namespace android
+
+#endif // _BPF_NETWORKSTATS_H
diff --git a/service/Android.bp b/service/Android.bp
index e376ff7..d600231 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,7 +33,6 @@
srcs: [
"jni/com_android_net_module_util/onload.cpp",
],
- stl: "libc++_static",
static_libs: [
"libnet_utils_device_common_bpfjni",
],
@@ -56,20 +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: [
"libclat",
"libip_checksum",
"libnetjniutils",
+ "libtraffic_controller",
+ "netd_aidl_interface-lateststable-ndk",
],
shared_libs: [
+ "libbase",
+ "libnetdutils",
"liblog",
"libnativehelper",
],
@@ -91,24 +94,25 @@
],
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",
"service-connectivity-protos",
+ "NetworkStackApiStableShims",
],
apex_available: [
"com.android.tethering",
@@ -149,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 e5d1a88..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
@@ -90,5 +96,12 @@
# From services-connectivity-shared-srcs
rule android.net.util.NetworkConstants* com.android.connectivity.@0
+# From modules-utils-statemachine
+rule com.android.internal.util.IState* com.android.connectivity.@0
+rule com.android.internal.util.State* com.android.connectivity.@0
+
+# From the API shims
+rule com.android.networkstack.apishim.** com.android.connectivity.@0
+
# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
index 1d17622..2f09e55 100644
--- a/service/jni/com_android_net_module_util/onload.cpp
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -20,6 +20,7 @@
namespace android {
int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
+int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -29,7 +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,
+ "android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
return JNI_VERSION_1_6;
}
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
new file mode 100644
index 0000000..2aaa4c3
--- /dev/null
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -0,0 +1,266 @@
+/*
+ * 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 "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>
+
+
+using android::net::TrafficController;
+using android::netdutils::Status;
+
+using UidOwnerMatchType::PENALTY_BOX_MATCH;
+using UidOwnerMatchType::HAPPY_BOX_MATCH;
+
+static android::net::TrafficController mTc;
+
+namespace android {
+
+static void native_init(JNIEnv* env, jobject clazz) {
+ Status status = mTc.start();
+ if (!isOk(status)) {
+ ALOGE("%s failed, error code = %d", __func__, status.code());
+ }
+}
+
+static jint native_addNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+ const uint32_t appUids = static_cast<uint32_t>(abs(uid));
+ Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOp::IptOpInsert);
+ if (!isOk(status)) {
+ ALOGE("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_removeNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+ const uint32_t appUids = static_cast<uint32_t>(abs(uid));
+ Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOp::IptOpDelete);
+ if (!isOk(status)) {
+ ALOGE("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_addNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+ const uint32_t appUids = static_cast<uint32_t>(abs(uid));
+ Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
+ TrafficController::IptOp::IptOpInsert);
+ if (!isOk(status)) {
+ ALOGE("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_removeNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+ const uint32_t appUids = static_cast<uint32_t>(abs(uid));
+ Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
+ TrafficController::IptOp::IptOpDelete);
+ if (!isOk(status)) {
+ ALOGD("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_setChildChain(JNIEnv* env, jobject clazz, jint childChain, jboolean enable) {
+ auto chain = static_cast<ChildChain>(childChain);
+ int res = mTc.toggleUidOwnerMap(chain, enable);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+static jint native_replaceUidChain(JNIEnv* env, jobject clazz, jstring name, jboolean isAllowlist,
+ jintArray jUids) {
+ const ScopedUtfChars chainNameUtf8(env, name);
+ if (chainNameUtf8.c_str() == nullptr) {
+ return -EINVAL;
+ }
+ const std::string chainName(chainNameUtf8.c_str());
+
+ ScopedIntArrayRO uids(env, jUids);
+ if (uids.get() == nullptr) {
+ return -EINVAL;
+ }
+
+ size_t size = uids.size();
+ std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
+ int res = mTc.replaceUidOwnerMap(chainName, isAllowlist, data);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+static FirewallType getFirewallType(ChildChain chain) {
+ switch (chain) {
+ case DOZABLE:
+ return ALLOWLIST;
+ case STANDBY:
+ return DENYLIST;
+ case POWERSAVE:
+ return ALLOWLIST;
+ case RESTRICTED:
+ return ALLOWLIST;
+ case NONE:
+ default:
+ return DENYLIST;
+ }
+}
+
+static jint native_setUidRule(JNIEnv* env, jobject clazz, jint childChain, jint uid,
+ jint firewallRule) {
+ auto chain = static_cast<ChildChain>(childChain);
+ auto rule = static_cast<FirewallRule>(firewallRule);
+ FirewallType fType = getFirewallType(chain);
+
+ int res = mTc.changeUidOwnerRule(chain, uid, rule, fType);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+static jint native_addUidInterfaceRules(JNIEnv* env, jobject clazz, jstring ifName,
+ jintArray jUids) {
+ const ScopedUtfChars ifNameUtf8(env, ifName);
+ if (ifNameUtf8.c_str() == nullptr) {
+ return -EINVAL;
+ }
+ const std::string interfaceName(ifNameUtf8.c_str());
+ const int ifIndex = if_nametoindex(interfaceName.c_str());
+
+ ScopedIntArrayRO uids(env, jUids);
+ if (uids.get() == nullptr) {
+ return -EINVAL;
+ }
+
+ size_t size = uids.size();
+ std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
+ Status status = mTc.addUidInterfaceRules(ifIndex, data);
+ if (!isOk(status)) {
+ ALOGE("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_removeUidInterfaceRules(JNIEnv* env, jobject clazz, jintArray jUids) {
+ ScopedIntArrayRO uids(env, jUids);
+ if (uids.get() == nullptr) {
+ return -EINVAL;
+ }
+
+ size_t size = uids.size();
+ std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
+ Status status = mTc.removeUidInterfaceRules(data);
+ if (!isOk(status)) {
+ ALOGE("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static jint native_swapActiveStatsMap(JNIEnv* env, jobject clazz) {
+ Status status = mTc.swapActiveStatsMap();
+ if (!isOk(status)) {
+ ALOGD("%s failed, error code = %d", __func__, status.code());
+ }
+ return (jint)status.code();
+}
+
+static void native_setPermissionForUids(JNIEnv* env, jobject clazz, jint permission,
+ jintArray jUids) {
+ ScopedIntArrayRO uids(env, jUids);
+ if (uids.get() == nullptr) return;
+
+ size_t size = uids.size();
+ static_assert(sizeof(*(uids.get())) == sizeof(uid_t));
+ std::vector<uid_t> data ((uid_t *)&uids[0], (uid_t*)&uids[size]);
+ mTc.setPermissionForUids(permission, data);
+}
+
+static jint native_setCounterSet(JNIEnv* env, jobject clazz, jint setNum, jint uid) {
+ uid_t callingUid = getuid();
+ int res = mTc.setCounterSet(setNum, (uid_t)uid, callingUid);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+static jint native_deleteTagData(JNIEnv* env, jobject clazz, jint tagNum, jint uid) {
+ uid_t callingUid = getuid();
+ int res = mTc.deleteTagData(tagNum, (uid_t)uid, callingUid);
+ if (res) {
+ ALOGE("%s failed, error code = %d", __func__, res);
+ }
+ return (jint)res;
+}
+
+/*
+ * JNI registration.
+ */
+// clang-format off
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"native_init", "()V",
+ (void*)native_init},
+ {"native_addNaughtyApp", "(I)I",
+ (void*)native_addNaughtyApp},
+ {"native_removeNaughtyApp", "(I)I",
+ (void*)native_removeNaughtyApp},
+ {"native_addNiceApp", "(I)I",
+ (void*)native_addNiceApp},
+ {"native_removeNiceApp", "(I)I",
+ (void*)native_removeNiceApp},
+ {"native_setChildChain", "(IZ)I",
+ (void*)native_setChildChain},
+ {"native_replaceUidChain", "(Ljava/lang/String;Z[I)I",
+ (void*)native_replaceUidChain},
+ {"native_setUidRule", "(III)I",
+ (void*)native_setUidRule},
+ {"native_addUidInterfaceRules", "(Ljava/lang/String;[I)I",
+ (void*)native_addUidInterfaceRules},
+ {"native_removeUidInterfaceRules", "([I)I",
+ (void*)native_removeUidInterfaceRules},
+ {"native_swapActiveStatsMap", "()I",
+ (void*)native_swapActiveStatsMap},
+ {"native_setPermissionForUids", "(I[I)V",
+ (void*)native_setPermissionForUids},
+ {"native_setCounterSet", "(II)I",
+ (void*)native_setCounterSet},
+ {"native_deleteTagData", "(II)I",
+ (void*)native_deleteTagData},
+};
+// clang-format on
+
+int register_com_android_server_BpfNetMaps(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "com/android/server/BpfNetMaps",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
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 a9d7946..ee512ec 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -13,24 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#define LOG_TAG "jniClatCoordinator"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/if_tun.h>
#include <linux/ioctl.h>
+#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <net/if.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <string>
#include <netjniutils/netjniutils.h>
+#include "libclat/bpfhelper.h"
#include "libclat/clatutils.h"
#include "nativehelper/scoped_utf_chars.h"
// Sync from system/netd/include/netid_client.h
#define MARK_UNSET 0u
+// Sync from system/netd/server/NetdConstants.h
+#define __INT_STRLEN(i) sizeof(#i)
+#define _INT_STRLEN(i) __INT_STRLEN(i)
+#define INT32_STRLEN _INT_STRLEN(INT32_MIN)
+
+#define DEVICEPREFIX "v4-"
+
namespace android {
+static const char* kClatdPath = "/apex/com.android.tethering/bin/for-system/clatd";
+
static void throwIOException(JNIEnv* env, const char* msg, int error) {
jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
}
@@ -237,6 +252,242 @@
}
}
+int initTracker(const std::string& iface, const std::string& pfx96, const std::string& v4,
+ const std::string& v6, net::clat::ClatdTracker* output) {
+ strlcpy(output->iface, iface.c_str(), sizeof(output->iface));
+ output->ifIndex = if_nametoindex(iface.c_str());
+ if (output->ifIndex == 0) {
+ ALOGE("interface %s not found", output->iface);
+ return -1;
+ }
+
+ unsigned len = snprintf(output->v4iface, sizeof(output->v4iface),
+ "%s%s", DEVICEPREFIX, iface.c_str());
+ if (len >= sizeof(output->v4iface)) {
+ ALOGE("interface name too long '%s'", output->v4iface);
+ return -1;
+ }
+
+ output->v4ifIndex = if_nametoindex(output->v4iface);
+ if (output->v4ifIndex == 0) {
+ ALOGE("v4-interface %s not found", output->v4iface);
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET6, pfx96.c_str(), &output->pfx96)) {
+ ALOGE("invalid IPv6 address specified for plat prefix: %s", pfx96.c_str());
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET, v4.c_str(), &output->v4)) {
+ ALOGE("Invalid IPv4 address %s", v4.c_str());
+ return -1;
+ }
+
+ if (!inet_pton(AF_INET6, v6.c_str(), &output->v6)) {
+ ALOGE("Invalid source address %s", v6.c_str());
+ return -1;
+ }
+
+ return 0;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_startClatd(
+ JNIEnv* env, jobject clazz, jobject tunJavaFd, jobject readSockJavaFd,
+ jobject writeSockJavaFd, jstring iface, jstring pfx96, jstring v4, jstring v6) {
+ ScopedUtfChars ifaceStr(env, iface);
+ ScopedUtfChars pfx96Str(env, pfx96);
+ ScopedUtfChars v4Str(env, v4);
+ ScopedUtfChars v6Str(env, v6);
+
+ int tunFd = netjniutils::GetNativeFileDescriptor(env, tunJavaFd);
+ if (tunFd < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid tun file descriptor");
+ return -1;
+ }
+
+ int readSock = netjniutils::GetNativeFileDescriptor(env, readSockJavaFd);
+ if (readSock < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid read socket");
+ return -1;
+ }
+
+ int writeSock = netjniutils::GetNativeFileDescriptor(env, writeSockJavaFd);
+ if (writeSock < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid write socket");
+ return -1;
+ }
+
+ // 1. create a throwaway socket to reserve a file descriptor number
+ int passedTunFd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (passedTunFd == -1) {
+ throwIOException(env, "socket(ipv6/udp) for tun fd failed", errno);
+ return -1;
+ }
+ int passedSockRead = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (passedSockRead == -1) {
+ throwIOException(env, "socket(ipv6/udp) for read socket failed", errno);
+ return -1;
+ }
+ int passedSockWrite = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (passedSockWrite == -1) {
+ throwIOException(env, "socket(ipv6/udp) for write socket failed", errno);
+ return -1;
+ }
+
+ // these are the FD we'll pass to clatd on the cli, so need it as a string
+ char passedTunFdStr[INT32_STRLEN];
+ char passedSockReadStr[INT32_STRLEN];
+ char passedSockWriteStr[INT32_STRLEN];
+ snprintf(passedTunFdStr, sizeof(passedTunFdStr), "%d", passedTunFd);
+ snprintf(passedSockReadStr, sizeof(passedSockReadStr), "%d", passedSockRead);
+ snprintf(passedSockWriteStr, sizeof(passedSockWriteStr), "%d", passedSockWrite);
+
+ // 2. we're going to use this as argv[0] to clatd to make ps output more useful
+ std::string progname("clatd-");
+ progname += ifaceStr.c_str();
+
+ // clang-format off
+ const char* args[] = {progname.c_str(),
+ "-i", ifaceStr.c_str(),
+ "-p", pfx96Str.c_str(),
+ "-4", v4Str.c_str(),
+ "-6", v6Str.c_str(),
+ "-t", passedTunFdStr,
+ "-r", passedSockReadStr,
+ "-w", passedSockWriteStr,
+ nullptr};
+ // clang-format on
+
+ // 3. register vfork requirement
+ posix_spawnattr_t attr;
+ if (int ret = posix_spawnattr_init(&attr)) {
+ throwIOException(env, "posix_spawnattr_init failed", ret);
+ return -1;
+ }
+
+ // TODO: use android::base::ScopeGuard.
+ if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK)) {
+ posix_spawnattr_destroy(&attr);
+ throwIOException(env, "posix_spawnattr_setflags failed", ret);
+ return -1;
+ }
+
+ // 4. register dup2() action: this is what 'clears' the CLOEXEC flag
+ // on the tun fd that we want the child clatd process to inherit
+ // (this will happen after the vfork, and before the execve)
+ posix_spawn_file_actions_t fa;
+ if (int ret = posix_spawn_file_actions_init(&fa)) {
+ posix_spawnattr_destroy(&attr);
+ throwIOException(env, "posix_spawn_file_actions_init failed", ret);
+ return -1;
+ }
+
+ if (int ret = posix_spawn_file_actions_adddup2(&fa, tunFd, passedTunFd)) {
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+ throwIOException(env, "posix_spawn_file_actions_adddup2 for tun fd failed", ret);
+ return -1;
+ }
+ if (int ret = posix_spawn_file_actions_adddup2(&fa, readSock, passedSockRead)) {
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+ throwIOException(env, "posix_spawn_file_actions_adddup2 for read socket failed", ret);
+ return -1;
+ }
+ if (int ret = posix_spawn_file_actions_adddup2(&fa, writeSock, passedSockWrite)) {
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+ throwIOException(env, "posix_spawn_file_actions_adddup2 for write socket failed", ret);
+ return -1;
+ }
+
+ // 5. actually perform vfork/dup2/execve
+ pid_t pid;
+ if (int ret = posix_spawn(&pid, kClatdPath, &fa, &attr, (char* const*)args, nullptr)) {
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+ throwIOException(env, "posix_spawn failed", ret);
+ return -1;
+ }
+
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&fa);
+
+ // 5. Start BPF if any
+ if (!net::clat::initMaps()) {
+ net::clat::ClatdTracker tracker = {};
+ if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
+ &tracker)) {
+ net::clat::maybeStartBpf(tracker);
+ }
+ }
+
+ return pid;
+}
+
+// Stop clatd process. SIGTERM with timeout first, if fail, SIGKILL.
+// See stopProcess() in system/netd/server/NetdConstants.cpp.
+// TODO: have a function stopProcess(int pid, const char *name) in common location and call it.
+static constexpr int WAITPID_ATTEMPTS = 50;
+static constexpr int WAITPID_RETRY_INTERVAL_US = 100000;
+
+static void stopClatdProcess(int pid) {
+ int err = kill(pid, SIGTERM);
+ if (err) {
+ err = errno;
+ }
+ if (err == ESRCH) {
+ ALOGE("clatd child process %d unexpectedly disappeared", pid);
+ return;
+ }
+ if (err) {
+ ALOGE("Error killing clatd child process %d: %s", pid, strerror(err));
+ }
+ int status = 0;
+ int ret = 0;
+ for (int count = 0; ret == 0 && count < WAITPID_ATTEMPTS; count++) {
+ usleep(WAITPID_RETRY_INTERVAL_US);
+ ret = waitpid(pid, &status, WNOHANG);
+ }
+ if (ret == 0) {
+ ALOGE("Failed to SIGTERM clatd pid=%d, try SIGKILL", pid);
+ // TODO: fix that kill failed or waitpid doesn't return.
+ kill(pid, SIGKILL);
+ ret = waitpid(pid, &status, 0);
+ }
+ if (ret == -1) {
+ ALOGE("Error waiting for clatd child process %d: %s", pid, strerror(errno));
+ } else {
+ ALOGD("clatd process %d terminated status=%d", pid, status);
+ }
+}
+
+static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jobject clazz,
+ jstring iface, jstring pfx96,
+ jstring v4, jstring v6,
+ jint pid) {
+ ScopedUtfChars ifaceStr(env, iface);
+ ScopedUtfChars pfx96Str(env, pfx96);
+ ScopedUtfChars v4Str(env, v4);
+ ScopedUtfChars v6Str(env, v6);
+
+ if (pid <= 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid pid");
+ return;
+ }
+
+ if (!net::clat::initMaps()) {
+ net::clat::ClatdTracker tracker = {};
+ if (!initTracker(ifaceStr.c_str(), pfx96Str.c_str(), v4Str.c_str(), v6Str.c_str(),
+ &tracker)) {
+ net::clat::maybeStopBpf(tracker);
+ }
+ }
+
+ stopClatdProcess(pid);
+}
+
/*
* JNI registration.
*/
@@ -259,9 +510,16 @@
(void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt},
{"native_configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
(void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket},
+ {"native_startClatd",
+ "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/lang/"
+ "String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_startClatd},
+ {"native_stopClatd",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V",
+ (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
new file mode 100644
index 0000000..cb26bc3
--- /dev/null
+++ b/service/native/Android.bp
@@ -0,0 +1,74 @@
+/*
+ * 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+ name: "libtraffic_controller",
+ defaults: ["netd_defaults"],
+ srcs: [
+ "TrafficController.cpp",
+ ],
+ header_libs: [
+ "bpf_connectivity_headers",
+ ],
+ static_libs: [
+ // TrafficController would use the constants of INetd so that add
+ // netd_aidl_interface-lateststable-ndk.
+ "netd_aidl_interface-lateststable-ndk",
+ ],
+ shared_libs: [
+ // TODO: Find a good way to remove libbase.
+ "libbase",
+ "libcutils",
+ "libnetdutils",
+ "libutils",
+ "liblog",
+ ],
+ export_include_dirs: ["include"],
+ sanitize: {
+ cfi: true,
+ },
+ apex_available: [
+ "com.android.tethering",
+ ],
+ min_sdk_version: "30",
+}
+
+cc_test {
+ name: "traffic_controller_unit_test",
+ test_suites: ["general-tests"],
+ require_root: true,
+ local_include_dirs: ["include"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ ],
+ srcs: [
+ "TrafficControllerTest.cpp",
+ ],
+ static_libs: [
+ "libbase",
+ "libgmock",
+ "liblog",
+ "libnetdutils",
+ "libtraffic_controller",
+ "libutils",
+ "libnetd_updatable",
+ "netd_aidl_interface-lateststable-ndk",
+ ],
+}
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index c24a41b..5981906 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -30,6 +30,7 @@
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
+#include <map>
#include <mutex>
#include <unordered_set>
#include <vector>
@@ -38,12 +39,13 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <netdutils/StatusOr.h>
-
#include <netdutils/Syscalls.h>
+#include <netdutils/UidConstants.h>
#include <netdutils/Utils.h>
+#include <private/android_filesystem_config.h>
+
#include "TrafficController.h"
#include "bpf/BpfMap.h"
-
#include "netdutils/DumpWriter.h"
namespace android {
@@ -73,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");
@@ -95,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);
@@ -186,18 +190,12 @@
}
Status TrafficController::start() {
- /* When netd restarts from a crash without total system reboot, the program
- * is still attached to the cgroup, detach it so the program can be freed
- * and we can load and attach new program into the target cgroup.
- *
- * TODO: Scrape existing socket when run-time restart and clean up the map
- * if the socket no longer exist
- */
-
RETURN_IF_NOT_OK(initMaps());
// Fetch the list of currently-existing interfaces. At this point NetlinkHandler is
// already running, so it will call addInterface() when any new interface appears.
+ // TODO: Clean-up addInterface() after interface monitoring is in
+ // NetworkStatsService.
std::map<std::string, uint32_t> ifacePairs;
ASSIGN_OR_RETURN(ifacePairs, getIfaceList());
for (const auto& ifacePair:ifacePairs) {
@@ -406,18 +404,16 @@
return netdutils::status::ok;
}
-Status TrafficController::updateUidOwnerMap(const std::vector<uint32_t>& appUids,
+Status TrafficController::updateUidOwnerMap(const uint32_t uid,
UidOwnerMatchType matchType, IptOp op) {
std::lock_guard guard(mMutex);
- for (uint32_t uid : appUids) {
- if (op == IptOpDelete) {
- RETURN_IF_NOT_OK(removeRule(uid, matchType));
- } else if (op == IptOpInsert) {
- RETURN_IF_NOT_OK(addRule(uid, matchType));
- } else {
- // Cannot happen.
- return statusFromErrno(EINVAL, StringPrintf("invalid IptOp: %d, %d", op, matchType));
- }
+ if (op == IptOpDelete) {
+ RETURN_IF_NOT_OK(removeRule(uid, matchType));
+ } else if (op == IptOpInsert) {
+ RETURN_IF_NOT_OK(addRule(uid, matchType));
+ } else {
+ // Cannot happen.
+ return statusFromErrno(EINVAL, StringPrintf("invalid IptOp: %d, %d", op, matchType));
}
return netdutils::status::ok;
}
@@ -432,6 +428,8 @@
return ALLOWLIST;
case RESTRICTED:
return ALLOWLIST;
+ case LOW_POWER_STANDBY:
+ return ALLOWLIST;
case NONE:
default:
return DENYLIST;
@@ -454,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);
@@ -532,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;
@@ -568,6 +571,9 @@
case RESTRICTED:
match = RESTRICTED_MATCH;
break;
+ case LOW_POWER_STANDBY:
+ match = LOW_POWER_STANDBY_MATCH;
+ break;
default:
return -EINVAL;
}
@@ -689,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);
@@ -752,7 +756,7 @@
dw.println("mCookieTagMap print end with error: %s", res.error().message().c_str());
}
- // Print UidCounterSetMap Content
+ // Print UidCounterSetMap content.
dumpBpfMap("mUidCounterSetMap", dw, "");
const auto printUidInfo = [&dw](const uint32_t& key, const uint8_t& value,
const BpfMap<uint32_t, uint8_t>&) {
@@ -764,7 +768,7 @@
dw.println("mUidCounterSetMap print end with error: %s", res.error().message().c_str());
}
- // Print AppUidStatsMap content
+ // Print AppUidStatsMap content.
std::string appUidStatsHeader = StringPrintf("uid rxBytes rxPackets txBytes txPackets");
dumpBpfMap("mAppUidStatsMap:", dw, appUidStatsHeader);
auto printAppUidStatsInfo = [&dw](const uint32_t& key, const StatsValue& value,
@@ -778,7 +782,7 @@
dw.println("mAppUidStatsMap print end with error: %s", res.error().message().c_str());
}
- // Print uidStatsMap content
+ // Print uidStatsMap content.
std::string statsHeader = StringPrintf("ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes"
" rxPackets txBytes txPackets");
dumpBpfMap("mStatsMapA", dw, statsHeader);
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index f5d5911..d0eca34 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 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.
@@ -32,17 +32,20 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <binder/Status.h>
#include <netdutils/MockSyscalls.h>
#include "TrafficController.h"
#include "bpf/BpfUtils.h"
+#include "NetdUpdatablePublic.h"
using namespace android::bpf; // NOLINT(google-build-using-namespace): grandfathered
namespace android {
namespace net {
+using android::netdutils::Status;
using base::Result;
using netdutils::isOk;
@@ -273,6 +276,17 @@
EXPECT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
EXPECT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
}
+
+ Status updateUidOwnerMaps(const std::vector<uint32_t>& appUids,
+ UidOwnerMatchType matchType, TrafficController::IptOp op) {
+ Status ret(0);
+ for (auto uid : appUids) {
+ ret = mTc.updateUidOwnerMap(uid, matchType, op);
+ if(!isOk(ret)) break;
+ }
+ return ret;
+ }
+
};
TEST_F(TrafficControllerTest, TestSetCounterSet) {
@@ -456,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));
}
@@ -466,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));
}
@@ -478,66 +494,62 @@
TEST_F(TrafficControllerTest, TestDenylistUidMatch) {
std::vector<uint32_t> appUids = {1000, 1001, 10012};
- ASSERT_TRUE(isOk(
- mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
- ASSERT_TRUE(isOk(
- mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, TrafficController::IptOpDelete)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
expectMapEmpty(mFakeUidOwnerMap);
}
TEST_F(TrafficControllerTest, TestAllowlistUidMatch) {
std::vector<uint32_t> appUids = {1000, 1001, 10012};
- ASSERT_TRUE(
- isOk(mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert)));
expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
- ASSERT_TRUE(
- isOk(mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete)));
expectMapEmpty(mFakeUidOwnerMap);
}
TEST_F(TrafficControllerTest, TestReplaceMatchUid) {
std::vector<uint32_t> appUids = {1000, 1001, 10012};
// Add appUids to the denylist and expect that their values are all PENALTY_BOX_MATCH.
- ASSERT_TRUE(isOk(
- mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
// Add the same UIDs to the allowlist and expect that we get PENALTY_BOX_MATCH |
// HAPPY_BOX_MATCH.
- ASSERT_TRUE(
- isOk(mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert)));
expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH | PENALTY_BOX_MATCH, 0);
// Remove the same UIDs from the allowlist and check the PENALTY_BOX_MATCH is still there.
- ASSERT_TRUE(
- isOk(mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete)));
expectUidOwnerMapValues(appUids, PENALTY_BOX_MATCH, 0);
// Remove the same UIDs from the denylist and check the map is empty.
- ASSERT_TRUE(isOk(
- mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, TrafficController::IptOpDelete)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
ASSERT_FALSE(mFakeUidOwnerMap.getFirstKey().ok());
}
TEST_F(TrafficControllerTest, TestDeleteWrongMatchSilentlyFails) {
std::vector<uint32_t> appUids = {1000, 1001, 10012};
// If the uid does not exist in the map, trying to delete a rule about it will fail.
- ASSERT_FALSE(
- isOk(mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpDelete)));
+ ASSERT_FALSE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
expectMapEmpty(mFakeUidOwnerMap);
// Add denylist rules for appUids.
- ASSERT_TRUE(
- isOk(mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH, TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, HAPPY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
// Delete (non-existent) denylist rules for appUids, and check that this silently does
// nothing if the uid is in the map but does not have denylist match. This is required because
// NetworkManagementService will try to remove a uid from denylist after adding it to the
// allowlist and if the remove fails it will not update the uid status.
- ASSERT_TRUE(isOk(
- mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH, TrafficController::IptOpDelete)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps(appUids, PENALTY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
expectUidOwnerMapValues(appUids, HAPPY_BOX_MATCH, 0);
}
@@ -586,8 +598,8 @@
TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithExistingMatches) {
// Set up existing PENALTY_BOX_MATCH rules
- ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({1000, 1001, 10012}, PENALTY_BOX_MATCH,
- TrafficController::IptOpInsert)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1000, 1001, 10012}, PENALTY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
expectUidOwnerMapValues({1000, 1001, 10012}, PENALTY_BOX_MATCH, 0);
// Add some partially-overlapping uid owner rules and check result
@@ -598,8 +610,8 @@
expectUidOwnerMapValues({10013, 10014}, IIF_MATCH, iif1);
// Removing some PENALTY_BOX_MATCH rules should not change uid interface rule
- ASSERT_TRUE(isOk(mTc.updateUidOwnerMap({1001, 10012}, PENALTY_BOX_MATCH,
- TrafficController::IptOpDelete)));
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({1001, 10012}, PENALTY_BOX_MATCH,
+ TrafficController::IptOpDelete)));
expectUidOwnerMapValues({1000}, PENALTY_BOX_MATCH, 0);
expectUidOwnerMapValues({10012, 10013, 10014}, IIF_MATCH, iif1);
@@ -737,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
new file mode 100644
index 0000000..dc44845
--- /dev/null
+++ b/service/native/include/Common.h
@@ -0,0 +1,40 @@
+/*
+ * 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
+// TODO: deduplicate with the constants in NetdConstants.h.
+#include <aidl/android/net/INetd.h>
+
+using aidl::android::net::INetd;
+
+enum FirewallRule { ALLOW = INetd::FIREWALL_RULE_ALLOW, DENY = INetd::FIREWALL_RULE_DENY };
+
+// ALLOWLIST means the firewall denies all by default, uids must be explicitly ALLOWed
+// DENYLIST means the firewall allows all by default, uids must be explicitly DENYed
+
+enum FirewallType { ALLOWLIST = INetd::FIREWALL_ALLOWLIST, DENYLIST = INetd::FIREWALL_DENYLIST };
+
+// LINT.IfChange(firewall_chain)
+enum ChildChain {
+ 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 3e98b68..e741dd6 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,33 +14,32 @@
* limitations under the License.
*/
-#ifndef NETD_SERVER_TRAFFIC_CONTROLLER_H
-#define NETD_SERVER_TRAFFIC_CONTROLLER_H
+#pragma once
-#include <linux/bpf.h>
+#include <set>
+#include <Common.h>
-#include "NetlinkListener.h"
#include "android-base/thread_annotations.h"
#include "bpf/BpfMap.h"
#include "bpf_shared.h"
#include "netdutils/DumpWriter.h"
#include "netdutils/NetlinkListener.h"
#include "netdutils/StatusOr.h"
-#include "utils/String16.h"
namespace android {
namespace net {
+using netdutils::StatusOr;
+
class TrafficController {
public:
+ static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
+
/*
* Initialize the whole controller
*/
netdutils::Status start();
- /*
- * Similiar as above, no external lock required.
- */
int setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
/*
@@ -84,9 +83,8 @@
EXCLUDES(mMutex);
netdutils::Status removeUidInterfaceRules(const std::vector<int32_t>& uids) EXCLUDES(mMutex);
- netdutils::Status updateUidOwnerMap(const std::vector<uint32_t>& appStrUids,
+ 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);
@@ -101,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:
/*
@@ -162,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
@@ -187,21 +186,6 @@
netdutils::Status addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif = 0)
REQUIRES(mMutex);
- // mMutex guards all accesses to mConfigurationMap, mUidOwnerMap, mUidPermissionMap,
- // mStatsMapA, mStatsMapB and mPrivilegedUser. It is designed to solve the following
- // problems:
- // 1. Prevent concurrent access and modification to mConfigurationMap, mUidOwnerMap,
- // mUidPermissionMap, and mPrivilegedUser. These data members are controlled by netd but can
- // be modified from different threads. TrafficController provides several APIs directly
- // called by the binder RPC, and different binder threads can concurrently access these data
- // members mentioned above. Some of the data members such as mUidPermissionMap and
- // mPrivilegedUsers are also accessed from a different thread when tagging sockets or
- // setting the counterSet through FwmarkServer
- // 2. Coordinate the deletion of uid stats in mStatsMapA and mStatsMapB. The system server
- // always call into netd to ask for a live stats map change before it pull and clean up the
- // stats from the inactive map. The mMutex will block netd from accessing the stats map when
- // the mConfigurationMap is updating the current stats map so netd will not accidentally
- // read the map that system_server is cleaning up.
std::mutex mMutex;
netdutils::Status initMaps() EXCLUDES(mMutex);
@@ -218,5 +202,3 @@
} // namespace net
} // namespace android
-
-#endif // NETD_SERVER_TRAFFIC_CONTROLLER_H
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 8540787..17ee996 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -19,9 +19,20 @@
cc_library_static {
name: "libclat",
defaults: ["netd_defaults"],
- srcs: ["clatutils.cpp"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ "libbase_headers",
+ ],
+ srcs: [
+ "TcUtils.cpp", // TODO: move to frameworks/libs/net
+ "bpfhelper.cpp",
+ "clatutils.cpp",
+ ],
stl: "libc++_static",
- static_libs: ["libip_checksum"],
+ static_libs: [
+ "libip_checksum",
+ "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
+ ],
shared_libs: ["liblog"],
export_include_dirs: ["include"],
min_sdk_version: "30",
@@ -32,7 +43,11 @@
name: "libclat_test",
defaults: ["netd_defaults"],
test_suites: ["device-tests"],
+ header_libs: [
+ "bpf_connectivity_headers",
+ ],
srcs: [
+ "TcUtilsTest.cpp",
"clatutils_test.cpp",
],
static_libs: [
@@ -40,10 +55,12 @@
"libclat",
"libip_checksum",
"libnetd_test_tun_interface",
+ "libnetdutils", // for netdutils/UidConstants.h in bpf_shared.h
+ "libtcutils",
],
shared_libs: [
"liblog",
"libnetutils",
],
require_root: true,
-}
\ No newline at end of file
+}
diff --git a/service/native/libs/libclat/TcUtils.cpp b/service/native/libs/libclat/TcUtils.cpp
new file mode 100644
index 0000000..cdfb763
--- /dev/null
+++ b/service/native/libs/libclat/TcUtils.cpp
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TcUtils"
+
+#include "libclat/TcUtils.h"
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <linux/if_arp.h>
+#include <linux/netlink.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <log/log.h>
+
+#include "android-base/unique_fd.h"
+
+namespace android {
+namespace net {
+
+using std::max;
+
+// Sync from system/netd/server/NetlinkCommands.h
+const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+
+static int doSIOCGIF(const std::string& interface, int opt) {
+ base::unique_fd ufd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+
+ if (ufd < 0) {
+ const int err = errno;
+ ALOGE("socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0)");
+ return -err;
+ };
+
+ struct ifreq ifr = {};
+ // We use strncpy() instead of strlcpy() since kernel has to be able
+ // to handle non-zero terminated junk passed in by userspace anyway,
+ // and this way too long interface names (more than IFNAMSIZ-1 = 15
+ // characters plus terminating NULL) will not get truncated to 15
+ // characters and zero-terminated and thus potentially erroneously
+ // match a truncated interface if one were to exist.
+ strncpy(ifr.ifr_name, interface.c_str(), sizeof(ifr.ifr_name));
+
+ if (ioctl(ufd, opt, &ifr, sizeof(ifr))) return -errno;
+
+ if (opt == SIOCGIFHWADDR) return ifr.ifr_hwaddr.sa_family;
+ if (opt == SIOCGIFMTU) return ifr.ifr_mtu;
+ return -EINVAL;
+}
+
+int hardwareAddressType(const std::string& interface) {
+ return doSIOCGIF(interface, SIOCGIFHWADDR);
+}
+
+int deviceMTU(const std::string& interface) {
+ return doSIOCGIF(interface, SIOCGIFMTU);
+}
+
+base::Result<bool> isEthernet(const std::string& interface) {
+ int rv = hardwareAddressType(interface);
+ if (rv < 0) {
+ errno = -rv;
+ return ErrnoErrorf("Get hardware address type of interface {} failed", interface);
+ }
+
+ switch (rv) {
+ case ARPHRD_ETHER:
+ return true;
+ case ARPHRD_NONE:
+ case ARPHRD_RAWIP: // in Linux 4.14+ rmnet support was upstreamed and this is 519
+ case 530: // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
+ return false;
+ default:
+ errno = EAFNOSUPPORT; // Address family not supported
+ return ErrnoErrorf("Unknown hardware address type {} on interface {}", rv, interface);
+ }
+}
+
+// TODO: use //system/netd/server/NetlinkCommands.cpp:openNetlinkSocket(protocol)
+// and //system/netd/server/SockDiag.cpp:checkError(fd)
+static int sendAndProcessNetlinkResponse(const void* req, int len) {
+ base::unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
+ if (fd == -1) {
+ const int err = errno;
+ ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)");
+ return -err;
+ }
+
+ static constexpr int on = 1;
+ int rv = setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on));
+ if (rv) ALOGE("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
+
+ // this is needed to get sane strace netlink parsing, it allocates the pid
+ rv = bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
+ if (rv) {
+ const int err = errno;
+ ALOGE("bind(fd, {AF_NETLINK, 0, 0})");
+ return -err;
+ }
+
+ // we do not want to receive messages from anyone besides the kernel
+ rv = connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR));
+ if (rv) {
+ const int err = errno;
+ ALOGE("connect(fd, {AF_NETLINK, 0, 0})");
+ return -err;
+ }
+
+ rv = send(fd, req, len, 0);
+ if (rv == -1) return -errno;
+ if (rv != len) return -EMSGSIZE;
+
+ struct {
+ nlmsghdr h;
+ nlmsgerr e;
+ char buf[256];
+ } resp = {};
+
+ rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
+
+ if (rv == -1) {
+ const int err = errno;
+ ALOGE("recv() failed");
+ return -err;
+ }
+
+ if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
+ ALOGE("recv() returned short packet: %d", rv);
+ return -EMSGSIZE;
+ }
+
+ if (resp.h.nlmsg_len != (unsigned)rv) {
+ ALOGE("recv() returned invalid header length: %d != %d", resp.h.nlmsg_len, rv);
+ return -EBADMSG;
+ }
+
+ if (resp.h.nlmsg_type != NLMSG_ERROR) {
+ ALOGE("recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
+ return -EBADMSG;
+ }
+
+ return resp.e.error; // returns 0 on success
+}
+
+// ADD: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_EXCL|NLM_F_CREATE
+// REPLACE: nlMsgType=RTM_NEWQDISC nlMsgFlags=NLM_F_CREATE|NLM_F_REPLACE
+// DEL: nlMsgType=RTM_DELQDISC nlMsgFlags=0
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags) {
+ // This is the name of the qdisc we are attaching.
+ // Some hoop jumping to make this compile time constant with known size,
+ // so that the structure declaration is well defined at compile time.
+#define CLSACT "clsact"
+ // sizeof() includes the terminating NULL
+ static constexpr size_t ASCIIZ_LEN_CLSACT = sizeof(CLSACT);
+
+ const struct {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_LEN_CLSACT)];
+ } kind;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = nlMsgType,
+ .nlmsg_flags = static_cast<__u16>(NETLINK_REQUEST_FLAGS | nlMsgFlags),
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0),
+ .tcm_parent = TC_H_CLSACT,
+ },
+ .kind =
+ {
+ .attr =
+ {
+ .nla_len = NLA_HDRLEN + ASCIIZ_LEN_CLSACT,
+ .nla_type = TCA_KIND,
+ },
+ .str = CLSACT,
+ },
+ };
+#undef CLSACT
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet) {
+ // This is the name of the filter we're attaching (ie. this is the 'bpf'
+ // packet classifier enabled by kernel config option CONFIG_NET_CLS_BPF.
+ //
+ // We go through some hoops in order to make this compile time constants
+ // so that we can define the struct further down the function with the
+ // field for this sized correctly already during the build.
+#define BPF "bpf"
+ // sizeof() includes the terminating NULL
+ static constexpr size_t ASCIIZ_LEN_BPF = sizeof(BPF);
+
+ // This is to replicate program name suffix used by 'tc' Linux cli
+ // when it attaches programs.
+#define FSOBJ_SUFFIX ":[*fsobj]"
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_ingress6_clat_rawip:[*fsobj]
+ // and is the name of the pinned ingress ebpf program for ARPHRD_RAWIP interfaces.
+ // (also compatible with anything that has 0 size L2 header)
+ static constexpr char name_clat_rx_rawip[] = CLAT_INGRESS6_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_ingress6_clat_ether:[*fsobj]
+ // and is the name of the pinned ingress ebpf program for ARPHRD_ETHER interfaces.
+ // (also compatible with anything that has standard ethernet header)
+ static constexpr char name_clat_rx_ether[] = CLAT_INGRESS6_PROG_ETHER_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_egress4_clat_rawip:[*fsobj]
+ // and is the name of the pinned egress ebpf program for ARPHRD_RAWIP interfaces.
+ // (also compatible with anything that has 0 size L2 header)
+ static constexpr char name_clat_tx_rawip[] = CLAT_EGRESS4_PROG_RAWIP_NAME FSOBJ_SUFFIX;
+
+ // This macro expands (from header files) to:
+ // prog_clatd_schedcls_egress4_clat_ether:[*fsobj]
+ // and is the name of the pinned egress ebpf program for ARPHRD_ETHER interfaces.
+ // (also compatible with anything that has standard ethernet header)
+ static constexpr char name_clat_tx_ether[] = CLAT_EGRESS4_PROG_ETHER_NAME FSOBJ_SUFFIX;
+
+#undef FSOBJ_SUFFIX
+
+ // The actual name we'll use is determined at run time via 'ethernet' and 'ingress'
+ // booleans. We need to compile time allocate enough space in the struct
+ // hence this macro magic to make sure we have enough space for either
+ // possibility. In practice some of these are actually the same size.
+ static constexpr size_t ASCIIZ_MAXLEN_NAME = max({
+ sizeof(name_clat_rx_rawip),
+ sizeof(name_clat_rx_ether),
+ sizeof(name_clat_tx_rawip),
+ sizeof(name_clat_tx_ether),
+ });
+
+ // These are not compile time constants: 'name' is used in strncpy below
+ const char* const name_clat_rx = ethernet ? name_clat_rx_ether : name_clat_rx_rawip;
+ const char* const name_clat_tx = ethernet ? name_clat_tx_ether : name_clat_tx_rawip;
+ const char* const name = ingress ? name_clat_rx : name_clat_tx;
+
+ struct {
+ nlmsghdr n;
+ tcmsg t;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_LEN_BPF)];
+ } kind;
+ struct {
+ nlattr attr;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } fd;
+ struct {
+ nlattr attr;
+ char str[NLMSG_ALIGN(ASCIIZ_MAXLEN_NAME)];
+ } name;
+ struct {
+ nlattr attr;
+ __u32 u32;
+ } flags;
+ } options;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_NEWTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = static_cast<__u32>((PRIO_CLAT << 16) | htons(proto)),
+ },
+ .kind =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.kind),
+ .nla_type = TCA_KIND,
+ },
+ .str = BPF,
+ },
+ .options =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options),
+ .nla_type = NLA_F_NESTED | TCA_OPTIONS,
+ },
+ .fd =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.fd),
+ .nla_type = TCA_BPF_FD,
+ },
+ .u32 = static_cast<__u32>(bpfFd),
+ },
+ .name =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.name),
+ .nla_type = TCA_BPF_NAME,
+ },
+ // Visible via 'tc filter show', but
+ // is overwritten by strncpy below
+ .str = "placeholder",
+ },
+ .flags =
+ {
+ .attr =
+ {
+ .nla_len = sizeof(req.options.flags),
+ .nla_type = TCA_BPF_FLAGS,
+ },
+ .u32 = TCA_BPF_FLAG_ACT_DIRECT,
+ },
+ },
+ };
+#undef BPF
+
+ strncpy(req.options.name.str, name, sizeof(req.options.name.str));
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+// tc filter del dev .. in/egress prio 4 protocol ..
+int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
+ const struct {
+ nlmsghdr n;
+ tcmsg t;
+ } req = {
+ .n =
+ {
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = RTM_DELTFILTER,
+ .nlmsg_flags = NETLINK_REQUEST_FLAGS,
+ },
+ .t =
+ {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = ifIndex,
+ .tcm_handle = TC_H_UNSPEC,
+ .tcm_parent = TC_H_MAKE(TC_H_CLSACT,
+ ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
+ .tcm_info = (static_cast<uint32_t>(prio) << 16) |
+ static_cast<uint32_t>(htons(proto)),
+ },
+ };
+
+ return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/TcUtilsTest.cpp b/service/native/libs/libclat/TcUtilsTest.cpp
new file mode 100644
index 0000000..08f3042
--- /dev/null
+++ b/service/native/libs/libclat/TcUtilsTest.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * TcUtilsTest.cpp - unit tests for TcUtils.cpp
+ */
+
+#include <gtest/gtest.h>
+
+#include "libclat/TcUtils.h"
+
+#include <linux/if_arp.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "bpf/BpfUtils.h"
+#include "bpf_shared.h"
+
+namespace android {
+namespace net {
+
+class TcUtilsTest : public ::testing::Test {
+ public:
+ void SetUp() {}
+};
+
+TEST_F(TcUtilsTest, HardwareAddressTypeOfNonExistingIf) {
+ ASSERT_EQ(-ENODEV, hardwareAddressType("not_existing_if"));
+}
+
+TEST_F(TcUtilsTest, HardwareAddressTypeOfLoopback) {
+ ASSERT_EQ(ARPHRD_LOOPBACK, hardwareAddressType("lo"));
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+TEST_F(TcUtilsTest, HardwareAddressTypeOfWireless) {
+ int type = hardwareAddressType("wlan0");
+ if (type == -ENODEV) return;
+
+ ASSERT_EQ(ARPHRD_ETHER, type);
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+TEST_F(TcUtilsTest, HardwareAddressTypeOfCellular) {
+ int type = hardwareAddressType("rmnet_data0");
+ if (type == -ENODEV) return;
+
+ ASSERT_NE(ARPHRD_ETHER, type);
+
+ // ARPHRD_RAWIP is 530 on some pre-4.14 Qualcomm devices.
+ if (type == 530) return;
+
+ ASSERT_EQ(ARPHRD_RAWIP, type);
+}
+
+TEST_F(TcUtilsTest, IsEthernetOfNonExistingIf) {
+ auto res = isEthernet("not_existing_if");
+ ASSERT_FALSE(res.ok());
+ ASSERT_EQ(ENODEV, res.error().code());
+}
+
+TEST_F(TcUtilsTest, IsEthernetOfLoopback) {
+ auto res = isEthernet("lo");
+ ASSERT_FALSE(res.ok());
+ ASSERT_EQ(EAFNOSUPPORT, res.error().code());
+}
+
+// If wireless 'wlan0' interface exists it should be Ethernet.
+// See also HardwareAddressTypeOfWireless.
+TEST_F(TcUtilsTest, IsEthernetOfWireless) {
+ auto res = isEthernet("wlan0");
+ if (!res.ok() && res.error().code() == ENODEV) return;
+
+ ASSERT_RESULT_OK(res);
+ ASSERT_TRUE(res.value());
+}
+
+// If cellular 'rmnet_data0' interface exists it should
+// *probably* not be Ethernet and instead be RawIp.
+// See also HardwareAddressTypeOfCellular.
+TEST_F(TcUtilsTest, IsEthernetOfCellular) {
+ auto res = isEthernet("rmnet_data0");
+ if (!res.ok() && res.error().code() == ENODEV) return;
+
+ ASSERT_RESULT_OK(res);
+ ASSERT_FALSE(res.value());
+}
+
+TEST_F(TcUtilsTest, DeviceMTUOfNonExistingIf) {
+ ASSERT_EQ(-ENODEV, deviceMTU("not_existing_if"));
+}
+
+TEST_F(TcUtilsTest, DeviceMTUofLoopback) {
+ ASSERT_EQ(65536, deviceMTU("lo"));
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4MapFd) {
+ int fd = getClatEgress4MapFd();
+ ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4RawIpProgFd) {
+ int fd = getClatEgress4ProgFd(RAWIP);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatEgress4EtherProgFd) {
+ int fd = getClatEgress4ProgFd(ETHER);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6MapFd) {
+ int fd = getClatIngress6MapFd();
+ ASSERT_GE(fd, 3); // 0,1,2 - stdin/out/err, thus fd >= 3
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6RawIpProgFd) {
+ int fd = getClatIngress6ProgFd(RAWIP);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+TEST_F(TcUtilsTest, GetClatIngress6EtherProgFd) {
+ int fd = getClatIngress6ProgFd(ETHER);
+ ASSERT_GE(fd, 3);
+ EXPECT_EQ(FD_CLOEXEC, fcntl(fd, F_GETFD));
+ close(fd);
+}
+
+// See Linux kernel source in include/net/flow.h
+#define LOOPBACK_IFINDEX 1
+
+TEST_F(TcUtilsTest, AttachReplaceDetachClsactLo) {
+ // This attaches and detaches a configuration-less and thus no-op clsact
+ // qdisc to loopback interface (and it takes fractions of a second)
+ EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscReplaceDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+}
+
+static void checkAttachDetachBpfFilterClsactLo(const bool ingress, const bool ethernet) {
+ // Older kernels return EINVAL instead of ENOENT due to lacking proper error propagation...
+ const int errNOENT = android::bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+
+ int clatBpfFd = ingress ? getClatIngress6ProgFd(ethernet) : getClatEgress4ProgFd(ethernet);
+ ASSERT_GE(clatBpfFd, 3);
+
+ // This attaches and detaches a clsact plus ebpf program to loopback
+ // interface, but it should not affect traffic by virtue of us not
+ // actually populating the ebpf control map.
+ // Furthermore: it only takes fractions of a second.
+ EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscAddDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ if (ingress) {
+ EXPECT_EQ(0, tcFilterAddDevIngressClatIpv6(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
+ EXPECT_EQ(0, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ } else {
+ EXPECT_EQ(0, tcFilterAddDevEgressClatIpv4(LOOPBACK_IFINDEX, clatBpfFd, ethernet));
+ EXPECT_EQ(0, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ }
+ EXPECT_EQ(-errNOENT, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-errNOENT, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+ EXPECT_EQ(0, tcQdiscDelDevClsact(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevIngressClatIpv6(LOOPBACK_IFINDEX));
+ EXPECT_EQ(-EINVAL, tcFilterDelDevEgressClatIpv4(LOOPBACK_IFINDEX));
+
+ close(clatBpfFd);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactEgressLo) {
+ checkAttachDetachBpfFilterClsactLo(EGRESS, RAWIP);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactEgressLo) {
+ checkAttachDetachBpfFilterClsactLo(EGRESS, ETHER);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterRawIpClsactIngressLo) {
+ checkAttachDetachBpfFilterClsactLo(INGRESS, RAWIP);
+}
+
+TEST_F(TcUtilsTest, CheckAttachBpfFilterEthernetClsactIngressLo) {
+ checkAttachDetachBpfFilterClsactLo(INGRESS, ETHER);
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/bpfhelper.cpp b/service/native/libs/libclat/bpfhelper.cpp
new file mode 100644
index 0000000..6e230d0
--- /dev/null
+++ b/service/native/libs/libclat/bpfhelper.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * main.c - main function
+ */
+#define LOG_TAG "bpfhelper"
+
+#include "libclat/bpfhelper.h"
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+
+#include "bpf/BpfMap.h"
+#include "libclat/TcUtils.h"
+
+#define DEVICEPREFIX "v4-"
+
+using android::base::unique_fd;
+using android::net::RAWIP;
+using android::net::getClatEgress4MapFd;
+using android::net::getClatIngress6MapFd;
+using android::net::getClatEgress4ProgFd;
+using android::net::getClatIngress6ProgFd;
+using android::net::tcQdiscAddDevClsact;
+using android::net::tcFilterAddDevEgressClatIpv4;
+using android::net::tcFilterAddDevIngressClatIpv6;
+using android::net::tcFilterDelDevEgressClatIpv4;
+using android::net::tcFilterDelDevIngressClatIpv6;
+using android::bpf::BpfMap;
+
+BpfMap<ClatEgress4Key, ClatEgress4Value> mClatEgress4Map;
+BpfMap<ClatIngress6Key, ClatIngress6Value> mClatIngress6Map;
+
+namespace android {
+namespace net {
+namespace clat {
+
+// TODO: have a clearMap function to remove all stubs while system server crash.
+// For long term, move bpf access into java and map initialization should live
+// ClatCoordinator constructor.
+int initMaps(void) {
+ int rv = getClatEgress4MapFd();
+ if (rv < 0) {
+ ALOGE("getClatEgress4MapFd() failure: %s", strerror(-rv));
+ return -rv;
+ }
+ mClatEgress4Map.reset(rv);
+
+ rv = getClatIngress6MapFd();
+ if (rv < 0) {
+ ALOGE("getClatIngress6MapFd() failure: %s", strerror(-rv));
+ mClatEgress4Map.reset(-1);
+ return -rv;
+ }
+ mClatIngress6Map.reset(rv);
+
+ return 0;
+}
+
+void maybeStartBpf(const ClatdTracker& tracker) {
+ auto isEthernet = android::net::isEthernet(tracker.iface);
+ if (!isEthernet.ok()) {
+ ALOGE("isEthernet(%s[%d]) failure: %s", tracker.iface, tracker.ifIndex,
+ isEthernet.error().message().c_str());
+ return;
+ }
+
+ // This program will be attached to the v4-* interface which is a TUN and thus always rawip.
+ int rv = getClatEgress4ProgFd(RAWIP);
+ if (rv < 0) {
+ ALOGE("getClatEgress4ProgFd(RAWIP) failure: %s", strerror(-rv));
+ return;
+ }
+ unique_fd txRawIpProgFd(rv);
+
+ rv = getClatIngress6ProgFd(isEthernet.value());
+ if (rv < 0) {
+ ALOGE("getClatIngress6ProgFd(%d) failure: %s", isEthernet.value(), strerror(-rv));
+ return;
+ }
+ unique_fd rxProgFd(rv);
+
+ ClatEgress4Key txKey = {
+ .iif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+ ClatEgress4Value txValue = {
+ .oif = tracker.ifIndex,
+ .local6 = tracker.v6,
+ .pfx96 = tracker.pfx96,
+ .oifIsEthernet = isEthernet.value(),
+ };
+
+ auto ret = mClatEgress4Map.writeValue(txKey, txValue, BPF_ANY);
+ if (!ret.ok()) {
+ ALOGE("mClatEgress4Map.writeValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ ClatIngress6Key rxKey = {
+ .iif = tracker.ifIndex,
+ .pfx96 = tracker.pfx96,
+ .local6 = tracker.v6,
+ };
+ ClatIngress6Value rxValue = {
+ // TODO: move all the clat code to eBPF and remove the tun interface entirely.
+ .oif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+
+ ret = mClatIngress6Map.writeValue(rxKey, rxValue, BPF_ANY);
+ if (!ret.ok()) {
+ ALOGE("mClatIngress6Map.writeValue failure: %s", strerror(ret.error().code()));
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ // We do tc setup *after* populating the maps, so scanning through them
+ // can always be used to tell us what needs cleanup.
+
+ // Usually the clsact will be added in RouteController::addInterfaceToPhysicalNetwork.
+ // But clat is started before the v4- interface is added to the network. The clat startup have
+ // to add clsact of v4- tun interface first for adding bpf filter in maybeStartBpf.
+ // TODO: move "qdisc add clsact" of v4- tun interface out from ClatdController.
+ rv = tcQdiscAddDevClsact(tracker.v4ifIndex);
+ if (rv) {
+ ALOGE("tcQdiscAddDevClsact(%d[%s]) failure: %s", tracker.v4ifIndex, tracker.v4iface,
+ strerror(-rv));
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ rv = tcFilterAddDevEgressClatIpv4(tracker.v4ifIndex, txRawIpProgFd, RAWIP);
+ if (rv) {
+ ALOGE("tcFilterAddDevEgressClatIpv4(%d[%s], RAWIP) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+
+ // The v4- interface clsact is not deleted for unwinding error because once it is created
+ // with interface addition, the lifetime is till interface deletion. Moreover, the clsact
+ // has no clat filter now. It should not break anything.
+
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ rv = tcFilterAddDevIngressClatIpv6(tracker.ifIndex, rxProgFd, isEthernet.value());
+ if (rv) {
+ ALOGE("tcFilterAddDevIngressClatIpv6(%d[%s], %d) failure: %s", tracker.ifIndex,
+ tracker.iface, isEthernet.value(), strerror(-rv));
+ rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
+ if (rv) {
+ ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+ }
+
+ // The v4- interface clsact is not deleted. See the reason in the error unwinding code of
+ // the egress filter attaching of v4- tun interface.
+
+ ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok())
+ ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok())
+ ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+ return;
+ }
+
+ // success
+}
+
+void maybeStopBpf(const ClatdTracker& tracker) {
+ int rv = tcFilterDelDevIngressClatIpv6(tracker.ifIndex);
+ if (rv < 0) {
+ ALOGE("tcFilterDelDevIngressClatIpv6(%d[%s]) failure: %s", tracker.ifIndex, tracker.iface,
+ strerror(-rv));
+ }
+
+ rv = tcFilterDelDevEgressClatIpv4(tracker.v4ifIndex);
+ if (rv < 0) {
+ ALOGE("tcFilterDelDevEgressClatIpv4(%d[%s]) failure: %s", tracker.v4ifIndex,
+ tracker.v4iface, strerror(-rv));
+ }
+
+ // We cleanup the maps last, so scanning through them can be used to
+ // determine what still needs cleanup.
+
+ ClatEgress4Key txKey = {
+ .iif = tracker.v4ifIndex,
+ .local4 = tracker.v4,
+ };
+
+ auto ret = mClatEgress4Map.deleteValue(txKey);
+ if (!ret.ok()) ALOGE("mClatEgress4Map.deleteValue failure: %s", strerror(ret.error().code()));
+
+ ClatIngress6Key rxKey = {
+ .iif = tracker.ifIndex,
+ .pfx96 = tracker.pfx96,
+ .local6 = tracker.v6,
+ };
+
+ ret = mClatIngress6Map.deleteValue(rxKey);
+ if (!ret.ok()) ALOGE("mClatIngress6Map.deleteValue failure: %s", strerror(ret.error().code()));
+}
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/TcUtils.h b/service/native/libs/libclat/include/libclat/TcUtils.h
new file mode 100644
index 0000000..212838e
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/TcUtils.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <errno.h>
+#include <linux/if_ether.h>
+#include <linux/if_link.h>
+#include <linux/rtnetlink.h>
+
+#include <string>
+
+#include "bpf/BpfUtils.h"
+#include "bpf_shared.h"
+
+namespace android {
+namespace net {
+
+// For better code clarity - do not change values - used for booleans like
+// with_ethernet_header or isEthernet.
+constexpr bool RAWIP = false;
+constexpr bool ETHER = true;
+
+// For better code clarity when used for 'bool ingress' parameter.
+constexpr bool EGRESS = false;
+constexpr bool INGRESS = true;
+
+// The priority of clat hook - must be after tethering.
+constexpr uint16_t PRIO_CLAT = 4;
+
+// this returns an ARPHRD_* constant or a -errno
+int hardwareAddressType(const std::string& interface);
+
+// return MTU or -errno
+int deviceMTU(const std::string& interface);
+
+base::Result<bool> isEthernet(const std::string& interface);
+
+inline int getClatEgress4MapFd(void) {
+ const int fd = bpf::mapRetrieveRW(CLAT_EGRESS4_MAP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatEgress4ProgFd(bool with_ethernet_header) {
+ const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_EGRESS4_PROG_ETHER_PATH
+ : CLAT_EGRESS4_PROG_RAWIP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatIngress6MapFd(void) {
+ const int fd = bpf::mapRetrieveRW(CLAT_INGRESS6_MAP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+inline int getClatIngress6ProgFd(bool with_ethernet_header) {
+ const int fd = bpf::retrieveProgram(with_ethernet_header ? CLAT_INGRESS6_PROG_ETHER_PATH
+ : CLAT_INGRESS6_PROG_RAWIP_PATH);
+ return (fd == -1) ? -errno : fd;
+}
+
+int doTcQdiscClsact(int ifIndex, uint16_t nlMsgType, uint16_t nlMsgFlags);
+
+inline int tcQdiscAddDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_EXCL | NLM_F_CREATE);
+}
+
+inline int tcQdiscReplaceDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_REPLACE);
+}
+
+inline int tcQdiscDelDevClsact(int ifIndex) {
+ return doTcQdiscClsact(ifIndex, RTM_DELQDISC, 0);
+}
+
+// tc filter add dev .. in/egress prio 4 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
+// direct-action
+int tcFilterAddDevBpf(int ifIndex, bool ingress, uint16_t proto, int bpfFd, bool ethernet);
+
+// tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/... direct-action
+inline int tcFilterAddDevIngressClatIpv6(int ifIndex, int bpfFd, bool ethernet) {
+ return tcFilterAddDevBpf(ifIndex, INGRESS, ETH_P_IPV6, bpfFd, ethernet);
+}
+
+// tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/... direct-action
+inline int tcFilterAddDevEgressClatIpv4(int ifIndex, int bpfFd, bool ethernet) {
+ return tcFilterAddDevBpf(ifIndex, EGRESS, ETH_P_IP, bpfFd, ethernet);
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+int tcFilterDelDev(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
+
+// tc filter del dev .. ingress prio 4 protocol ipv6
+inline int tcFilterDelDevIngressClatIpv6(int ifIndex) {
+ return tcFilterDelDev(ifIndex, INGRESS, PRIO_CLAT, ETH_P_IPV6);
+}
+
+// tc filter del dev .. egress prio 4 protocol ip
+inline int tcFilterDelDevEgressClatIpv4(int ifIndex) {
+ return tcFilterDelDev(ifIndex, EGRESS, PRIO_CLAT, ETH_P_IP);
+}
+
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/bpfhelper.h b/service/native/libs/libclat/include/libclat/bpfhelper.h
new file mode 100644
index 0000000..c0328c0
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/bpfhelper.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+
+namespace android {
+namespace net {
+namespace clat {
+
+struct ClatdTracker {
+ unsigned ifIndex;
+ char iface[IFNAMSIZ];
+ unsigned v4ifIndex;
+ char v4iface[IFNAMSIZ];
+ in_addr v4;
+ in6_addr v6;
+ in6_addr pfx96;
+};
+
+int initMaps(void);
+void maybeStartBpf(const ClatdTracker& tracker);
+void maybeStopBpf(const ClatdTracker& tracker);
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index bc63eef..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,81 +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;
- /**
- * 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) {
+ /**
+ * 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;
+ }
+
+ 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");
}
/**
@@ -109,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));
@@ -126,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");
}
/**
@@ -155,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");
}
/**
@@ -175,70 +226,80 @@
* 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();
private native int native_addNaughtyApp(int uid);
private native int native_removeNaughtyApp(int uid);
private native int native_addNiceApp(int uid);
@@ -249,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 6c27c4a..d833bc2 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -73,6 +73,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -124,6 +126,7 @@
import android.net.ConnectivitySettingsManager;
import android.net.DataStallReportParcelable;
import android.net.DnsResolverServiceManager;
+import android.net.DscpPolicy;
import android.net.ICaptivePortal;
import android.net.IConnectivityDiagnosticsCallback;
import android.net.IConnectivityManager;
@@ -218,6 +221,7 @@
import android.os.UserManager;
import android.provider.Settings;
import android.sysprop.NetworkProperties;
+import android.system.ErrnoException;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -245,9 +249,11 @@
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;
+import com.android.server.connectivity.DscpPolicyTracker;
import com.android.server.connectivity.FullScore;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
@@ -387,9 +393,11 @@
protected IDnsResolver mDnsResolver;
@VisibleForTesting
protected INetd mNetd;
+ private DscpPolicyTracker mDscpPolicyTracker = null;
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
@@ -432,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
@@ -441,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}.
@@ -460,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
@@ -587,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.
@@ -754,6 +769,7 @@
private Set<String> mWolSupportedInterfaces;
private final TelephonyManager mTelephonyManager;
+ private final CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
private final AppOpsManager mAppOpsManager;
private final LocationPermissionChecker mLocationPermissionChecker;
@@ -1323,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) {
@@ -1397,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
@@ -1424,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.
@@ -1487,6 +1527,19 @@
new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null,
new NetworkAgentConfig(), this, null, null, 0, INVALID_UID,
mLingerDelayMs, mQosCallbackTracker, mDeps);
+
+ try {
+ // DscpPolicyTracker cannot run on S because on S the tethering module can only load
+ // BPF programs/maps into /sys/fs/tethering/bpf, which the system server cannot access.
+ // Even if it could, running on S would at least require mocking out the BPF map,
+ // otherwise the unit tests will fail on pre-T devices where the seccomp filter blocks
+ // the bpf syscall. http://aosp/1907693
+ if (SdkLevel.isAtLeastT()) {
+ mDscpPolicyTracker = new DscpPolicyTracker();
+ }
+ } catch (ErrnoException e) {
+ loge("Unable to create DscpPolicyTracker");
+ }
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
@@ -2065,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());
}
@@ -3314,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;
@@ -3404,6 +3448,25 @@
nai.setLingerDuration((int) arg.second);
break;
}
+ case NetworkAgent.EVENT_ADD_DSCP_POLICY: {
+ DscpPolicy policy = (DscpPolicy) arg.second;
+ if (mDscpPolicyTracker != null) {
+ mDscpPolicyTracker.addDscpPolicy(nai, policy);
+ }
+ break;
+ }
+ case NetworkAgent.EVENT_REMOVE_DSCP_POLICY: {
+ if (mDscpPolicyTracker != null) {
+ mDscpPolicyTracker.removeDscpPolicy(nai, (int) arg.second);
+ }
+ break;
+ }
+ case NetworkAgent.EVENT_REMOVE_ALL_DSCP_POLICIES: {
+ if (mDscpPolicyTracker != null) {
+ mDscpPolicyTracker.removeAllDscpPolicies(nai);
+ }
+ break;
+ }
}
}
@@ -4044,7 +4107,7 @@
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.VIRTUAL,
INetd.PERMISSION_NONE,
(nai.networkAgentConfig == null || !nai.networkAgentConfig.allowBypass),
- getVpnType(nai), /*excludeLocalRoutes=*/ false);
+ getVpnType(nai), nai.networkAgentConfig.excludeLocalRouteVpn);
} else {
config = new NativeNetworkConfig(nai.network.getNetId(), NativeNetworkType.PHYSICAL,
getNetworkPermission(nai.networkCapabilities), /*secure=*/ false,
@@ -4102,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.
@@ -4125,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);
@@ -4139,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();
@@ -4149,6 +4230,11 @@
}
}
+ if (requestToBeReleased != null) {
+ releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
+ return;
+ }
+
if (mFlags.noRematchAllRequestsOnRegister()) {
rematchNetworksAndRequests(nris);
} else {
@@ -4988,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));
@@ -6057,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,
@@ -6121,7 +6205,7 @@
nai.onSignalStrengthThresholdsUpdated(thresholdsArray);
}
- private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+ private static void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
if (nc == null) {
return;
}
@@ -6134,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.
@@ -6233,7 +6328,6 @@
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
- ensureValid(networkCapabilities);
final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), reqType);
@@ -6292,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();
@@ -6361,7 +6467,6 @@
ensureRequestableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
- ensureValidNetworkSpecifier(networkCapabilities);
restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
callingUid, callingPackageName);
@@ -6430,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);
@@ -6452,7 +6557,7 @@
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
- ensureValid(networkCapabilities);
+ ensureListenableCapabilities(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), callingUid, callingPackageName);
final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
@@ -6480,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.
@@ -6877,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;
@@ -6913,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);
@@ -6925,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 {
@@ -7372,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
@@ -7385,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|. */
@@ -7560,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)) {
@@ -7649,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++) {
@@ -7719,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<>();
@@ -7775,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.
}
}
@@ -8688,6 +8862,7 @@
}
networkAgent.created = true;
networkAgent.onNetworkCreated();
+ updateAccessUids(networkAgent, null, networkAgent.networkCapabilities);
}
if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
@@ -8741,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()) {
@@ -9745,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);
@@ -10147,11 +10322,19 @@
switch (preference.getPreference()) {
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
nc = null;
+ if (preference.getPreferenceEnterpriseId() != 0) {
+ throw new IllegalArgumentException(
+ "Invalid enterprise identifier in setProfileNetworkPreferences");
+ }
break;
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK:
allowFallback = false;
// continue to process the enterprise preference.
case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
+ if (!isEnterpriseIdentifierValid(preference.getPreferenceEnterpriseId())) {
+ throw new IllegalArgumentException(
+ "Invalid enterprise identifier in setProfileNetworkPreferences");
+ }
final Set<UidRange> uidRangeSet =
getUidListToBeAppliedForNetworkPreference(profile, preference);
if (!isRangeAlreadyInPreferenceList(preferenceList, uidRangeSet)) {
@@ -10161,6 +10344,8 @@
"Overlapping uid range in setProfileNetworkPreferences");
}
nc.addCapability(NET_CAPABILITY_ENTERPRISE);
+ nc.addEnterpriseId(
+ preference.getPreferenceEnterpriseId());
nc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
break;
default:
@@ -10203,6 +10388,15 @@
return uidRangeSet;
}
+ private boolean isEnterpriseIdentifierValid(
+ @NetworkCapabilities.EnterpriseId int identifier) {
+ if ((identifier >= NET_ENTERPRISE_ID_1)
+ && (identifier <= NET_ENTERPRISE_ID_5)) {
+ return true;
+ }
+ return false;
+ }
+
private void validateNetworkCapabilitiesOfProfileNetworkPreference(
@Nullable final NetworkCapabilities nc) {
if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
@@ -10652,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);
@@ -10667,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);
@@ -10681,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);
@@ -10693,7 +10887,7 @@
enforceNetworkStackOrSettingsPermission();
try {
- mNetd.firewallEnableChildChain(chain, enable);
+ mBpfNetMaps.setChildChain(chain, enable);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
@@ -10706,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: "
@@ -10730,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/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 4d243c4..c57983b 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -74,6 +74,12 @@
private final Dependencies mDeps;
@Nullable
private String mIface = null;
+ @Nullable
+ private String mNat64Prefix = null;
+ @Nullable
+ private String mXlatLocalAddress4 = null;
+ @Nullable
+ private String mXlatLocalAddress6 = null;
private int mPid = INVALID_PID;
@VisibleForTesting
@@ -162,6 +168,23 @@
throws IOException {
native_configurePacketSocket(sock, v6, ifindex);
}
+
+ /**
+ * Start clatd.
+ */
+ public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
+ @NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96,
+ @NonNull String v4, @NonNull String v6) throws IOException {
+ return native_startClatd(tunfd, readsock6, writesock6, iface, pfx96, v4, v6);
+ }
+
+ /**
+ * Stop clatd.
+ */
+ public void stopClatd(String iface, String pfx96, String v4, String v6, int pid)
+ throws IOException {
+ native_stopClatd(iface, pfx96, v4, v6, pid);
+ }
}
@VisibleForTesting
@@ -196,6 +219,9 @@
public String clatStart(final String iface, final int netId,
@NonNull final IpPrefix nat64Prefix)
throws IOException {
+ if (mIface != null || mPid != INVALID_PID) {
+ throw new IOException("Clatd is already running on " + mIface + " (pid " + mPid + ")");
+ }
if (nat64Prefix.getPrefixLength() != 96) {
throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
}
@@ -231,6 +257,7 @@
try {
mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
} catch (RemoteException | ServiceSpecificException e) {
+ tunFd.close();
Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
}
@@ -247,6 +274,7 @@
try {
mNetd.interfaceSetMtu(tunIface, mtu);
} catch (RemoteException | ServiceSpecificException e) {
+ tunFd.close();
throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
}
final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
@@ -258,6 +286,7 @@
try {
mNetd.interfaceSetCfg(ifConfig);
} catch (RemoteException | ServiceSpecificException e) {
+ tunFd.close();
throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
+ ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
}
@@ -267,11 +296,12 @@
final ParcelFileDescriptor readSock6;
try {
// Use a JNI call to get native file descriptor instead of Os.socket() because we would
- // like to use ParcelFileDescriptor to close file descriptor automatically. But ctor
+ // like to use ParcelFileDescriptor to manage file descriptor. But ctor
// ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file
// descriptor to initialize ParcelFileDescriptor object instead.
readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
} catch (IOException e) {
+ tunFd.close();
throw new IOException("Open packet socket failed: " + e);
}
@@ -282,11 +312,16 @@
// reason why we use jniOpenPacketSocket6().
writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
} catch (IOException e) {
+ tunFd.close();
+ readSock6.close();
throw new IOException("Open raw socket failed: " + e);
}
final int ifaceIndex = mDeps.getInterfaceIndex(iface);
if (ifaceIndex == INVALID_IFINDEX) {
+ tunFd.close();
+ readSock6.close();
+ writeSock6.close();
throw new IOException("Fail to get interface index for interface " + iface);
}
@@ -294,6 +329,9 @@
try {
mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex);
} catch (IOException e) {
+ tunFd.close();
+ readSock6.close();
+ writeSock6.close();
throw new IOException("add anycast sockopt failed: " + e);
}
@@ -301,11 +339,50 @@
try {
mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex);
} catch (IOException e) {
+ tunFd.close();
+ readSock6.close();
+ writeSock6.close();
throw new IOException("configure packet socket failed: " + e);
}
- // TODO: start clatd and returns local xlat464 v6 address.
- return null;
+ // [5] Start clatd.
+ try {
+ mPid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
+ writeSock6.getFileDescriptor(), iface, pfx96, v4, v6);
+ mIface = iface;
+ mNat64Prefix = pfx96;
+ mXlatLocalAddress4 = v4;
+ mXlatLocalAddress6 = v6;
+ } catch (IOException e) {
+ throw new IOException("Error start clatd on " + iface + ": " + e);
+ } finally {
+ tunFd.close();
+ readSock6.close();
+ writeSock6.close();
+ }
+
+ return v6;
+ }
+
+ /**
+ * Stop clatd
+ */
+ public void clatStop() throws IOException {
+ if (mPid == INVALID_PID) {
+ throw new IOException("Clatd has not started");
+ }
+ Log.i(TAG, "Stopping clatd pid=" + mPid + " on " + mIface);
+
+ mDeps.stopClatd(mIface, mNat64Prefix, mXlatLocalAddress4, mXlatLocalAddress6, mPid);
+ // TODO: remove setIptablesDropRule
+
+ Log.i(TAG, "clatd on " + mIface + " stopped");
+
+ mIface = null;
+ mNat64Prefix = null;
+ mXlatLocalAddress4 = null;
+ mXlatLocalAddress6 = null;
+ mPid = INVALID_PID;
}
private static native String native_selectIpv4Address(String v4addr, int prefixlen)
@@ -321,4 +398,9 @@
int ifindex) throws IOException;
private static native void native_configurePacketSocket(FileDescriptor sock, String v6,
int ifindex) throws IOException;
+ private static native int native_startClatd(FileDescriptor tunfd, FileDescriptor readsock6,
+ FileDescriptor writesock6, String iface, String pfx96, String v4, String v6)
+ throws IOException;
+ private static native void native_stopClatd(String iface, String pfx96, String v4, String v6,
+ int pid) throws IOException;
}
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
new file mode 100644
index 0000000..43cfc8f
--- /dev/null
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.net.DscpPolicy.STATUS_DELETED;
+import static android.net.DscpPolicy.STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
+import static android.net.DscpPolicy.STATUS_POLICY_NOT_FOUND;
+import static android.net.DscpPolicy.STATUS_SUCCESS;
+import static android.system.OsConstants.ETH_P_ALL;
+
+import android.annotation.NonNull;
+import android.net.DscpPolicy;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.TcUtils;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.NetworkInterface;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * DscpPolicyTracker has a single entry point from ConnectivityService handler.
+ * This guarantees that all code runs on the same thread and no locking is needed.
+ */
+public class DscpPolicyTracker {
+ // After tethering and clat priorities.
+ static final short PRIO_DSCP = 5;
+
+ private static final String TAG = DscpPolicyTracker.class.getSimpleName();
+ private static final String PROG_PATH =
+ "/sys/fs/bpf/prog_dscp_policy_schedcls_set_dscp";
+ // Name is "map + *.o + map_name + map". Can probably shorten this
+ private static final String IPV4_POLICY_MAP_PATH = makeMapPath(
+ "dscp_policy_ipv4_dscp_policies");
+ private static final String IPV6_POLICY_MAP_PATH = makeMapPath(
+ "dscp_policy_ipv6_dscp_policies");
+ private static final int MAX_POLICIES = 16;
+
+ private static String makeMapPath(String which) {
+ return "/sys/fs/bpf/map_" + which + "_map";
+ }
+
+ private Set<String> mAttachedIfaces;
+
+ private final BpfMap<Struct.U32, DscpPolicyValue> mBpfDscpIpv4Policies;
+ private final BpfMap<Struct.U32, DscpPolicyValue> mBpfDscpIpv6Policies;
+ private final SparseIntArray mPolicyIdToBpfMapIndex;
+
+ public DscpPolicyTracker() throws ErrnoException {
+ mAttachedIfaces = new HashSet<String>();
+
+ mPolicyIdToBpfMapIndex = new SparseIntArray(MAX_POLICIES);
+ mBpfDscpIpv4Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV4_POLICY_MAP_PATH,
+ BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class);
+ mBpfDscpIpv6Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV6_POLICY_MAP_PATH,
+ BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class);
+ }
+
+ private int getFirstFreeIndex() {
+ for (int i = 0; i < MAX_POLICIES; i++) {
+ if (mPolicyIdToBpfMapIndex.indexOfValue(i) < 0) return i;
+ }
+ return MAX_POLICIES;
+ }
+
+ private void sendStatus(NetworkAgentInfo nai, int policyId, int status) {
+ try {
+ nai.networkAgent.onDscpPolicyStatusUpdated(policyId, status);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed update policy status: ", e);
+ }
+ }
+
+ private boolean matchesIpv4(DscpPolicy policy) {
+ return ((policy.getDestinationAddress() == null
+ || policy.getDestinationAddress() instanceof Inet4Address)
+ && (policy.getSourceAddress() == null
+ || policy.getSourceAddress() instanceof Inet4Address));
+ }
+
+ private boolean matchesIpv6(DscpPolicy policy) {
+ return ((policy.getDestinationAddress() == null
+ || policy.getDestinationAddress() instanceof Inet6Address)
+ && (policy.getSourceAddress() == null
+ || policy.getSourceAddress() instanceof Inet6Address));
+ }
+
+ private int addDscpPolicyInternal(DscpPolicy policy) {
+ // If there is no existing policy with a matching ID, and we are already at
+ // the maximum number of policies then return INSUFFICIENT_PROCESSING_RESOURCES.
+ final int existingIndex = mPolicyIdToBpfMapIndex.get(policy.getPolicyId(), -1);
+ if (existingIndex == -1 && mPolicyIdToBpfMapIndex.size() >= MAX_POLICIES) {
+ return STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
+ }
+
+ // Currently all classifiers are supported, if any are removed return
+ // STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
+ // and for any other generic error STATUS_REQUEST_DECLINED
+
+ int addIndex = 0;
+ // If a policy with a matching ID exists, replace it, otherwise use the next free
+ // index for the policy.
+ if (existingIndex != -1) {
+ addIndex = mPolicyIdToBpfMapIndex.get(policy.getPolicyId());
+ } else {
+ addIndex = getFirstFreeIndex();
+ }
+
+ try {
+ mPolicyIdToBpfMapIndex.put(policy.getPolicyId(), addIndex);
+
+ // Add v4 policy to mBpfDscpIpv4Policies if source and destination address
+ // are both null or if they are both instances of Inet6Address.
+ if (matchesIpv4(policy)) {
+ mBpfDscpIpv4Policies.insertOrReplaceEntry(
+ new Struct.U32(addIndex),
+ new DscpPolicyValue(policy.getSourceAddress(),
+ policy.getDestinationAddress(),
+ policy.getSourcePort(), policy.getDestinationPortRange(),
+ (short) policy.getProtocol(), (short) policy.getDscpValue()));
+ }
+
+ // Add v6 policy to mBpfDscpIpv6Policies if source and destination address
+ // are both null or if they are both instances of Inet6Address.
+ if (matchesIpv6(policy)) {
+ mBpfDscpIpv6Policies.insertOrReplaceEntry(
+ new Struct.U32(addIndex),
+ new DscpPolicyValue(policy.getSourceAddress(),
+ policy.getDestinationAddress(),
+ policy.getSourcePort(), policy.getDestinationPortRange(),
+ (short) policy.getProtocol(), (short) policy.getDscpValue()));
+ }
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to insert policy into map: ", e);
+ return STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
+ }
+
+ return STATUS_SUCCESS;
+ }
+
+ /**
+ * Add the provided DSCP policy to the bpf map. Attach bpf program dscp_policy to iface
+ * if not already attached. Response will be sent back to nai with status.
+ *
+ * STATUS_SUCCESS - if policy was added successfully
+ * STATUS_INSUFFICIENT_PROCESSING_RESOURCES - if max policies were already set
+ */
+ public void addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy) {
+ if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
+ if (!attachProgram(nai.linkProperties.getInterfaceName())) {
+ Log.e(TAG, "Unable to attach program");
+ sendStatus(nai, policy.getPolicyId(), STATUS_INSUFFICIENT_PROCESSING_RESOURCES);
+ return;
+ }
+ }
+
+ int status = addDscpPolicyInternal(policy);
+ sendStatus(nai, policy.getPolicyId(), status);
+ }
+
+ private void removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index) {
+ int status = STATUS_POLICY_NOT_FOUND;
+ try {
+ mBpfDscpIpv4Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE);
+ mBpfDscpIpv6Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE);
+ status = STATUS_DELETED;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to delete policy from map: ", e);
+ }
+
+ sendStatus(nai, policyId, status);
+ }
+
+ /**
+ * Remove specified DSCP policy and detach program if no other policies are active.
+ */
+ public void removeDscpPolicy(NetworkAgentInfo nai, int policyId) {
+ if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
+ // Nothing to remove since program is not attached. Send update back for policy id.
+ sendStatus(nai, policyId, STATUS_POLICY_NOT_FOUND);
+ return;
+ }
+
+ if (mPolicyIdToBpfMapIndex.get(policyId, -1) != -1) {
+ removePolicyFromMap(nai, policyId, mPolicyIdToBpfMapIndex.get(policyId));
+ mPolicyIdToBpfMapIndex.delete(policyId);
+ }
+
+ // TODO: detach should only occur if no more policies are present on the nai's iface.
+ if (mPolicyIdToBpfMapIndex.size() == 0) {
+ detachProgram(nai.linkProperties.getInterfaceName());
+ }
+ }
+
+ /**
+ * Remove all DSCP policies and detach program.
+ */
+ // TODO: Remove all should only remove policies from corresponding nai iface.
+ public void removeAllDscpPolicies(NetworkAgentInfo nai) {
+ if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
+ // Nothing to remove since program is not attached. Send update for policy
+ // id 0. The status update must contain a policy ID, and 0 is an invalid id.
+ sendStatus(nai, 0, STATUS_SUCCESS);
+ return;
+ }
+
+ for (int i = 0; i < mPolicyIdToBpfMapIndex.size(); i++) {
+ removePolicyFromMap(nai, mPolicyIdToBpfMapIndex.keyAt(i),
+ mPolicyIdToBpfMapIndex.valueAt(i));
+ }
+ mPolicyIdToBpfMapIndex.clear();
+
+ // Can detach program since no policies are active.
+ detachProgram(nai.linkProperties.getInterfaceName());
+ }
+
+ /**
+ * Attach BPF program
+ */
+ private boolean attachProgram(@NonNull String iface) {
+ // TODO: attach needs to be per iface not program.
+
+ try {
+ NetworkInterface netIface = NetworkInterface.getByName(iface);
+ TcUtils.tcFilterAddDevBpf(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL,
+ PROG_PATH);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to attach to TC on " + iface + ": " + e);
+ return false;
+ }
+ mAttachedIfaces.add(iface);
+ return true;
+ }
+
+ /**
+ * Detach BPF program
+ */
+ public void detachProgram(@NonNull String iface) {
+ try {
+ NetworkInterface netIface = NetworkInterface.getByName(iface);
+ if (netIface != null) {
+ TcUtils.tcFilterDelDev(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to detach to TC on " + iface + ": " + e);
+ }
+ mAttachedIfaces.remove(iface);
+ }
+}
diff --git a/service/src/com/android/server/connectivity/DscpPolicyValue.java b/service/src/com/android/server/connectivity/DscpPolicyValue.java
new file mode 100644
index 0000000..cb40306
--- /dev/null
+++ b/service/src/com/android/server/connectivity/DscpPolicyValue.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.util.Log;
+import android.util.Range;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/** Value type for DSCP setting and rewriting to DSCP policy BPF maps. */
+public class DscpPolicyValue extends Struct {
+ private static final String TAG = DscpPolicyValue.class.getSimpleName();
+
+ // TODO: add the interface index.
+ @Field(order = 0, type = Type.ByteArray, arraysize = 16)
+ public final byte[] src46;
+
+ @Field(order = 1, type = Type.ByteArray, arraysize = 16)
+ public final byte[] dst46;
+
+ @Field(order = 2, type = Type.UBE16)
+ public final int srcPort;
+
+ @Field(order = 3, type = Type.UBE16)
+ public final int dstPortStart;
+
+ @Field(order = 4, type = Type.UBE16)
+ public final int dstPortEnd;
+
+ @Field(order = 5, type = Type.U8)
+ public final short proto;
+
+ @Field(order = 6, type = Type.U8)
+ public final short dscp;
+
+ @Field(order = 7, type = Type.U8, padding = 3)
+ public final short mask;
+
+ private static final int SRC_IP_MASK = 0x1;
+ private static final int DST_IP_MASK = 0x02;
+ private static final int SRC_PORT_MASK = 0x4;
+ private static final int DST_PORT_MASK = 0x8;
+ private static final int PROTO_MASK = 0x10;
+
+ private boolean ipEmpty(final byte[] ip) {
+ for (int i = 0; i < ip.length; i++) {
+ if (ip[i] != 0) return false;
+ }
+ return true;
+ }
+
+ private byte[] toIpv4MappedAddressBytes(InetAddress ia) {
+ final byte[] addr6 = new byte[16];
+ if (ia != null) {
+ final byte[] addr4 = ia.getAddress();
+ addr6[10] = (byte) 0xff;
+ addr6[11] = (byte) 0xff;
+ addr6[12] = addr4[0];
+ addr6[13] = addr4[1];
+ addr6[14] = addr4[2];
+ addr6[15] = addr4[3];
+ }
+ return addr6;
+ }
+
+ private byte[] toAddressField(InetAddress addr) {
+ if (addr == null) {
+ return EMPTY_ADDRESS_FIELD;
+ } else if (addr instanceof Inet4Address) {
+ return toIpv4MappedAddressBytes(addr);
+ } else {
+ return addr.getAddress();
+ }
+ }
+
+ private static final byte[] EMPTY_ADDRESS_FIELD =
+ InetAddress.parseNumericAddress("::").getAddress();
+
+ private short makeMask(final byte[] src46, final byte[] dst46, final int srcPort,
+ final int dstPortStart, final short proto, final short dscp) {
+ short mask = 0;
+ if (src46 != EMPTY_ADDRESS_FIELD) {
+ mask |= SRC_IP_MASK;
+ }
+ if (dst46 != EMPTY_ADDRESS_FIELD) {
+ mask |= DST_IP_MASK;
+ }
+ if (srcPort != -1) {
+ mask |= SRC_PORT_MASK;
+ }
+ if (dstPortStart != -1 && dstPortEnd != -1) {
+ mask |= DST_PORT_MASK;
+ }
+ if (proto != -1) {
+ mask |= PROTO_MASK;
+ }
+ return mask;
+ }
+
+ // This constructor is necessary for BpfMap#getValue since all values must be
+ // in the constructor.
+ public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final int srcPort,
+ final int dstPortStart, final int dstPortEnd, final short proto,
+ final short dscp) {
+ this.src46 = toAddressField(src46);
+ this.dst46 = toAddressField(dst46);
+
+ // These params need to be stored as 0 because uints are used in BpfMap.
+ // If they are -1 BpfMap write will throw errors.
+ this.srcPort = srcPort != -1 ? srcPort : 0;
+ this.dstPortStart = dstPortStart != -1 ? dstPortStart : 0;
+ this.dstPortEnd = dstPortEnd != -1 ? dstPortEnd : 0;
+ this.proto = proto != -1 ? proto : 0;
+
+ this.dscp = dscp;
+ // Use member variables for IP since byte[] is needed and api variables for everything else
+ // so -1 is passed into mask if parameter is not present.
+ this.mask = makeMask(this.src46, this.dst46, srcPort, dstPortStart, proto, dscp);
+ }
+
+ public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final int srcPort,
+ final Range<Integer> dstPort, final short proto,
+ final short dscp) {
+ this(src46, dst46, srcPort, dstPort != null ? dstPort.getLower() : -1,
+ dstPort != null ? dstPort.getUpper() : -1, proto, dscp);
+ }
+
+ public static final DscpPolicyValue NONE = new DscpPolicyValue(
+ null /* src46 */, null /* dst46 */, -1 /* srcPort */,
+ -1 /* dstPortStart */, -1 /* dstPortEnd */, (short) -1 /* proto */,
+ (short) 0 /* dscp */);
+
+ @Override
+ public String toString() {
+ String srcIpString = "empty";
+ String dstIpString = "empty";
+
+ // Separate try/catch for IP's so it's easier to debug.
+ try {
+ srcIpString = InetAddress.getByAddress(src46).getHostAddress();
+ } catch (UnknownHostException e) {
+ Log.e(TAG, "Invalid SRC IP address", e);
+ }
+
+ try {
+ dstIpString = InetAddress.getByAddress(src46).getHostAddress();
+ } catch (UnknownHostException e) {
+ Log.e(TAG, "Invalid DST IP address", e);
+ }
+
+ try {
+ return String.format(
+ "src46: %s, dst46: %s, srcPort: %d, dstPortStart: %d, dstPortEnd: %d,"
+ + " protocol: %d, dscp %s", srcIpString, dstIpString, srcPort, dstPortStart,
+ dstPortEnd, proto, dscp);
+ } catch (IllegalArgumentException e) {
+ return String.format("String format error: " + e);
+ }
+ }
+}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index b7f3ed9..e917b3f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,13 +17,16 @@
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;
import android.annotation.Nullable;
import android.content.Context;
import android.net.CaptivePortalData;
+import android.net.DscpPolicy;
import android.net.IDnsResolver;
import android.net.INetd;
import android.net.INetworkAgent;
@@ -51,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;
@@ -700,6 +705,24 @@
mHandler.obtainMessage(NetworkAgent.EVENT_LINGER_DURATION_CHANGED,
new Pair<>(NetworkAgentInfo.this, durationMs)).sendToTarget();
}
+
+ @Override
+ public void sendAddDscpPolicy(final DscpPolicy policy) {
+ mHandler.obtainMessage(NetworkAgent.EVENT_ADD_DSCP_POLICY,
+ new Pair<>(NetworkAgentInfo.this, policy)).sendToTarget();
+ }
+
+ @Override
+ public void sendRemoveDscpPolicy(final int policyId) {
+ mHandler.obtainMessage(NetworkAgent.EVENT_REMOVE_DSCP_POLICY,
+ new Pair<>(NetworkAgentInfo.this, policyId)).sendToTarget();
+ }
+
+ @Override
+ public void sendRemoveAllDscpPolicies() {
+ mHandler.obtainMessage(NetworkAgent.EVENT_REMOVE_ALL_DSCP_POLICIES,
+ new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
+ }
}
/**
@@ -1169,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 09d36e5..742044b 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -23,11 +23,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
@@ -39,9 +34,16 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_2;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_4;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -306,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<>();
@@ -316,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);
}
@@ -415,6 +463,31 @@
assertFalse(nr.satisfiedByNetworkCapabilities(new NetworkCapabilities()));
}
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+ public void testPrioritizeLatencyAndBandwidth() {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_LATENCY);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_LATENCY);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ netCap.addCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+ netCap.removeCapability(NET_CAPABILITY_NOT_METERED);
+ netCap.maybeMarkCapabilitiesRestricted();
+ assertTrue(netCap.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.R)
public void testOemPrivate() {
NetworkCapabilities nc = new NetworkCapabilities();
@@ -790,78 +863,79 @@
}
@Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
- public void testEnterpriseCapabilitySubLevel() {
+ public void testEnterpriseId() {
final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
.build();
- assertEquals(1, nc1.getEnterpriseCapabilitySubLevels().length);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1,
- nc1.getEnterpriseCapabilitySubLevels()[0]);
+ assertEquals(1, nc1.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc1.getEnterpriseIds()[0]);
final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
.build();
- assertEquals(2, nc2.getEnterpriseCapabilitySubLevels().length);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1,
- nc2.getEnterpriseCapabilitySubLevels()[0]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2,
- nc2.getEnterpriseCapabilitySubLevels()[1]);
+ assertEquals(2, nc2.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc2.getEnterpriseIds()[0]);
+ assertEquals(NET_ENTERPRISE_ID_2,
+ nc2.getEnterpriseIds()[1]);
final NetworkCapabilities nc3 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_3)
+ .addEnterpriseId(NET_ENTERPRISE_ID_4)
+ .addEnterpriseId(NET_ENTERPRISE_ID_5)
.build();
- assertEquals(5, nc3.getEnterpriseCapabilitySubLevels().length);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1,
- nc3.getEnterpriseCapabilitySubLevels()[0]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2,
- nc3.getEnterpriseCapabilitySubLevels()[1]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_3,
- nc3.getEnterpriseCapabilitySubLevels()[2]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_4,
- nc3.getEnterpriseCapabilitySubLevels()[3]);
- assertEquals(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_5,
- nc3.getEnterpriseCapabilitySubLevels()[4]);
+ assertEquals(5, nc3.getEnterpriseIds().length);
+ assertEquals(NET_ENTERPRISE_ID_1,
+ nc3.getEnterpriseIds()[0]);
+ assertEquals(NET_ENTERPRISE_ID_2,
+ nc3.getEnterpriseIds()[1]);
+ assertEquals(NET_ENTERPRISE_ID_3,
+ nc3.getEnterpriseIds()[2]);
+ assertEquals(NET_ENTERPRISE_ID_4,
+ nc3.getEnterpriseIds()[3]);
+ assertEquals(NET_ENTERPRISE_ID_5,
+ nc3.getEnterpriseIds()[4]);
final Class<IllegalArgumentException> illegalArgumentExceptionClass =
IllegalArgumentException.class;
assertThrows(illegalArgumentExceptionClass, () -> new NetworkCapabilities.Builder()
- .addEnterpriseCapabilitySubLevel(6)
+ .addEnterpriseId(6)
.build());
assertThrows(illegalArgumentExceptionClass, () -> new NetworkCapabilities.Builder()
- .removeEnterpriseCapabilitySubLevel(6)
+ .removeEnterpriseId(6)
.build());
final Class<IllegalStateException> illegalStateException =
IllegalStateException.class;
assertThrows(illegalStateException, () -> new NetworkCapabilities.Builder()
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
.build());
final NetworkCapabilities nc4 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
- .removeEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .removeEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_1)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_2)
.build();
- assertEquals(0, nc4.getEnterpriseCapabilitySubLevels().length);
+ assertEquals(1, nc4.getEnterpriseIds().length);
+ assertTrue(nc4.hasEnterpriseId(NET_ENTERPRISE_ID_1));
final NetworkCapabilities nc5 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_CBS)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .addEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
- .removeEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_1)
- .removeEnterpriseCapabilitySubLevel(NET_CAPABILITY_ENTERPRISE_SUB_LEVEL_2)
+ .addEnterpriseId(NET_ENTERPRISE_ID_1)
+ .addEnterpriseId(NET_ENTERPRISE_ID_2)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_1)
+ .removeEnterpriseId(NET_ENTERPRISE_ID_2)
.build();
assertTrue(nc4.satisfiedByNetworkCapabilities(nc1));
- assertFalse(nc1.satisfiedByNetworkCapabilities(nc4));
+ assertTrue(nc1.satisfiedByNetworkCapabilities(nc4));
assertFalse(nc3.satisfiedByNetworkCapabilities(nc2));
assertTrue(nc2.satisfiedByNetworkCapabilities(nc3));
diff --git a/tests/cts/net/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/native/Android.bp b/tests/cts/net/native/Android.bp
index 1d1c18e..153ff51 100644
--- a/tests/cts/net/native/Android.bp
+++ b/tests/cts/net/native/Android.bp
@@ -43,6 +43,7 @@
static_libs: [
"libbpf_android",
"libgtest",
+ "libmodules-utils-build",
],
// Tag this module as a cts test artifact
diff --git a/tests/cts/net/native/src/BpfCompatTest.cpp b/tests/cts/net/native/src/BpfCompatTest.cpp
index 874bad4..97ecb9e 100644
--- a/tests/cts/net/native/src/BpfCompatTest.cpp
+++ b/tests/cts/net/native/src/BpfCompatTest.cpp
@@ -21,6 +21,8 @@
#include <gtest/gtest.h>
+#include "android-modules-utils/sdk_level.h"
+
#include "libbpf_android.h"
using namespace android::bpf;
@@ -33,11 +35,17 @@
EXPECT_EQ(28, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
}
-TEST(BpfTest, bpfStructSizeTest) {
+TEST(BpfTest, bpfStructSizeTestPreT) {
+ if (android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << "T+ device.";
doBpfStructSizeTest("/system/etc/bpf/netd.o");
doBpfStructSizeTest("/system/etc/bpf/clatd.o");
}
+TEST(BpfTest, bpfStructSizeTest) {
+ doBpfStructSizeTest("/system/etc/bpf/gpu_mem.o");
+ doBpfStructSizeTest("/system/etc/bpf/time_in_state.o");
+}
+
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
diff --git a/tests/cts/net/src/android/net/cts/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/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 80c2db4..53e4ab7 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -168,6 +168,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
+import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
@@ -3021,6 +3022,13 @@
}
}
+ @Test
+ public void testDump() throws Exception {
+ final String dumpOutput = DumpTestUtils.dumpServiceWithShellPermission(
+ Context.CONNECTIVITY_SERVICE, "--short");
+ assertTrue(dumpOutput, dumpOutput.contains("Active default network"));
+ }
+
private void unregisterRegisteredCallbacks() {
for (NetworkCallback callback: mRegisteredCallbacks) {
mCm.unregisterNetworkCallback(callback);
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
new file mode 100644
index 0000000..886b078
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -0,0 +1,523 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts
+
+import android.net.cts.util.CtsNetUtils.TestNetworkCallback
+
+import android.app.Instrumentation
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.cts.util.CtsNetUtils
+import android.net.DscpPolicy
+import android.net.DscpPolicy.STATUS_DELETED
+import android.net.DscpPolicy.STATUS_SUCCESS
+import android.net.InetAddresses
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkAgent
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
+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.RouteInfo
+import android.net.util.SocketUtils
+import android.os.Build
+import android.os.HandlerThread
+import android.os.Looper
+import android.platform.test.annotations.AppModeFull
+import android.system.Os
+import android.system.OsConstants
+import android.system.OsConstants.AF_INET
+import android.system.OsConstants.IPPROTO_IP
+import android.system.OsConstants.IPPROTO_UDP
+import android.system.OsConstants.SOCK_DGRAM
+import android.system.OsConstants.SOCK_NONBLOCK
+import android.util.Log
+import android.util.Range
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel
+import com.android.testutils.CompatUtil
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.OffsetFilter
+import com.android.testutils.assertParcelingIsLossless
+import com.android.testutils.runAsShell
+import com.android.testutils.SC_V2
+import com.android.testutils.TapPacketReader
+import com.android.testutils.TestableNetworkAgent
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnDscpPolicyStatusUpdated
+import com.android.testutils.TestableNetworkCallback
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.net.Inet4Address
+import java.net.Inet6Address
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.net.ServerSocket
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.util.UUID
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.concurrent.thread
+import kotlin.test.fail
+
+private const val MAX_PACKET_LENGTH = 1500
+
+private val instrumentation: Instrumentation
+ get() = InstrumentationRegistry.getInstrumentation()
+
+private const val TAG = "DscpPolicyTest"
+private const val PACKET_TIMEOUT_MS = 2_000L
+
+@AppModeFull(reason = "Instant apps cannot create test networks")
+@RunWith(AndroidJUnit4::class)
+class DscpPolicyTest {
+ @JvmField
+ @Rule
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
+ private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
+ private val TEST_TARGET_IPV4_ADDR =
+ InetAddresses.parseNumericAddress("8.8.8.8") as Inet4Address
+
+ private val realContext = InstrumentationRegistry.getContext()
+ private val cm = realContext.getSystemService(ConnectivityManager::class.java)
+
+ private val agentsToCleanUp = mutableListOf<NetworkAgent>()
+ private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
+
+ private val handlerThread = HandlerThread(DscpPolicyTest::class.java.simpleName)
+
+ private lateinit var iface: TestNetworkInterface
+ private lateinit var tunNetworkCallback: TestNetworkCallback
+ private lateinit var reader: TapPacketReader
+
+ @Before
+ fun setUp() {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ val tnm = realContext.getSystemService(TestNetworkManager::class.java)
+
+ iface = tnm.createTunInterface( Array(1){ LinkAddress(LOCAL_IPV4_ADDRESS, 32) } )
+ assertNotNull(iface)
+ }
+
+ handlerThread.start()
+ reader = TapPacketReader(
+ handlerThread.threadHandler,
+ iface.fileDescriptor.fileDescriptor,
+ MAX_PACKET_LENGTH)
+ reader.startAsyncForTest()
+ }
+
+ @After
+ fun tearDown() {
+ agentsToCleanUp.forEach { it.unregister() }
+ callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
+
+ // reader.stop() cleans up tun fd
+ reader.handler.post { reader.stop() }
+ handlerThread.quitSafely()
+ }
+
+ private fun requestNetwork(request: NetworkRequest, callback: TestableNetworkCallback) {
+ cm.requestNetwork(request, callback)
+ callbacksToCleanUp.add(callback)
+ }
+
+ private fun makeTestNetworkRequest(specifier: String? = null): NetworkRequest {
+ return NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .addTransportType(TRANSPORT_TEST)
+ .also {
+ if (specifier != null) {
+ it.setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(specifier))
+ }
+ }
+ .build()
+ }
+
+ private fun createConnectedNetworkAgent(
+ context: Context = realContext,
+ specifier: String? = iface.getInterfaceName(),
+ ): Pair<TestableNetworkAgent, TestableNetworkCallback> {
+ val callback = TestableNetworkCallback()
+ // Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
+ requestNetwork(makeTestNetworkRequest(specifier = specifier), callback)
+
+ val nc = NetworkCapabilities().apply {
+ addTransportType(TRANSPORT_TEST)
+ removeCapability(NET_CAPABILITY_TRUSTED)
+ removeCapability(NET_CAPABILITY_INTERNET)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VPN)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ if (null != specifier) {
+ setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(specifier))
+ }
+ }
+ val lp = LinkProperties().apply {
+ addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+ addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
+ setInterfaceName(iface.getInterfaceName())
+ }
+ val config = NetworkAgentConfig.Builder().build()
+ val agent = TestableNetworkAgent(context, handlerThread.looper, nc, lp, config)
+ agentsToCleanUp.add(agent)
+
+ // Connect the agent and verify initial status callbacks.
+ runAsShell(MANAGE_TEST_NETWORKS) { agent.register() }
+ agent.markConnected()
+ agent.expectCallback<OnNetworkCreated>()
+ agent.expectSignalStrengths(intArrayOf())
+ agent.expectValidationBypassedStatus()
+ val network = agent.network ?: fail("Expected a non-null network")
+ return agent to callback
+ }
+
+ fun ByteArray.toHex(): String = joinToString(separator = "") {
+ eachByte -> "%02x".format(eachByte)
+ }
+
+ fun checkDscpValue(
+ agent : TestableNetworkAgent,
+ callback : TestableNetworkCallback,
+ dscpValue : Int = 0,
+ dstPort : Int = 0,
+ ) {
+ val testString = "test string"
+ val testPacket = ByteBuffer.wrap(testString.toByteArray(Charsets.UTF_8))
+ var packetFound = false
+
+ val socket = Os.socket(AF_INET, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_UDP)
+ agent.network.bindSocket(socket)
+
+ val originalPacket = testPacket.readAsArray()
+ Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size,
+ 0 /* flags */, TEST_TARGET_IPV4_ADDR, dstPort)
+
+ Os.close(socket)
+ generateSequence { reader.poll(PACKET_TIMEOUT_MS) }.forEach { packet ->
+ val buffer = ByteBuffer.wrap(packet, 0, packet.size).order(ByteOrder.BIG_ENDIAN)
+ val ip_ver = buffer.get()
+ val tos = buffer.get()
+ val length = buffer.getShort()
+ val id = buffer.getShort()
+ val offset = buffer.getShort()
+ val ttl = buffer.get()
+ val ipType = buffer.get()
+ val checksum = buffer.getShort()
+
+ val ipAddr = ByteArray(4)
+ buffer.get(ipAddr)
+ val srcIp = Inet4Address.getByAddress(ipAddr);
+ buffer.get(ipAddr)
+ val dstIp = Inet4Address.getByAddress(ipAddr);
+ val packetSrcPort = buffer.getShort().toInt()
+ val packetDstPort = buffer.getShort().toInt()
+
+ // TODO: Add source port comparison.
+ if (srcIp == LOCAL_IPV4_ADDRESS && dstIp == TEST_TARGET_IPV4_ADDR &&
+ packetDstPort == dstPort) {
+ assertEquals(dscpValue, (tos.toInt().shr(2)))
+ packetFound = true
+ }
+ }
+ assertTrue(packetFound)
+ }
+
+ fun doRemovePolicyTest(
+ agent : TestableNetworkAgent,
+ callback : TestableNetworkCallback,
+ policyId : Int
+ ) {
+ val portNumber = 1111 * policyId
+ agent.sendRemoveDscpPolicy(policyId)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(policyId, it.policyId)
+ assertEquals(STATUS_DELETED, it.status)
+ checkDscpValue(agent, callback, dstPort = portNumber)
+ }
+ }
+
+ @Test
+ fun testDscpPolicyAddPolicies(): Unit = createConnectedNetworkAgent().let { (agent, callback) ->
+ val policy = DscpPolicy.Builder(1, 1)
+ .setDestinationPortRange(Range(4444, 4444)).build()
+ agent.sendAddDscpPolicy(policy)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ }
+
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 4444)
+
+ agent.sendRemoveDscpPolicy(1)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_DELETED, it.status)
+ }
+
+ val policy2 = DscpPolicy.Builder(1, 4)
+ .setDestinationPortRange(Range(5555, 5555)).setSourceAddress(LOCAL_IPV4_ADDRESS)
+ .setDestinationAddress(TEST_TARGET_IPV4_ADDR).setProtocol(IPPROTO_UDP).build()
+ agent.sendAddDscpPolicy(policy2)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ }
+
+ checkDscpValue(agent, callback, dscpValue = 4, dstPort = 5555)
+
+ agent.sendRemoveDscpPolicy(1)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_DELETED, it.status)
+ }
+ }
+
+ @Test
+ // Remove policies in the same order as addition.
+ fun testRemoveDscpPolicy_RemoveSameOrderAsAdd(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
+ val policy = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(1111, 1111)).build()
+ agent.sendAddDscpPolicy(policy)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ }
+
+ val policy2 = DscpPolicy.Builder(2, 1).setDestinationPortRange(Range(2222, 2222)).build()
+ agent.sendAddDscpPolicy(policy2)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(2, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ }
+
+ val policy3 = DscpPolicy.Builder(3, 1).setDestinationPortRange(Range(3333, 3333)).build()
+ agent.sendAddDscpPolicy(policy3)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(3, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ }
+
+ /* Remove Policies and check CE is no longer set */
+ doRemovePolicyTest(agent, callback, 1)
+ doRemovePolicyTest(agent, callback, 2)
+ doRemovePolicyTest(agent, callback, 3)
+ }
+
+ @Test
+ fun testRemoveDscpPolicy_RemoveImmediatelyAfterAdd(): Unit =
+ createConnectedNetworkAgent().let{ (agent, callback) ->
+ val policy = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(1111, 1111)).build()
+ agent.sendAddDscpPolicy(policy)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ }
+ doRemovePolicyTest(agent, callback, 1)
+
+ val policy2 = DscpPolicy.Builder(2, 1).setDestinationPortRange(Range(2222, 2222)).build()
+ agent.sendAddDscpPolicy(policy2)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(2, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ }
+ doRemovePolicyTest(agent, callback, 2)
+
+ val policy3 = DscpPolicy.Builder(3, 1).setDestinationPortRange(Range(3333, 3333)).build()
+ agent.sendAddDscpPolicy(policy3)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(3, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ }
+ doRemovePolicyTest(agent, callback, 3)
+ }
+
+ @Test
+ // Remove policies in reverse order from addition.
+ fun testRemoveDscpPolicy_RemoveReverseOrder(): Unit =
+ createConnectedNetworkAgent().let { (agent, callback) ->
+ val policy = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(1111, 1111)).build()
+ agent.sendAddDscpPolicy(policy)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ }
+
+ val policy2 = DscpPolicy.Builder(2, 1).setDestinationPortRange(Range(2222, 2222)).build()
+ agent.sendAddDscpPolicy(policy2)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(2, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ }
+
+ val policy3 = DscpPolicy.Builder(3, 1).setDestinationPortRange(Range(3333, 3333)).build()
+ agent.sendAddDscpPolicy(policy3)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(3, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ }
+
+ /* Remove Policies and check CE is no longer set */
+ doRemovePolicyTest(agent, callback, 3)
+ doRemovePolicyTest(agent, callback, 2)
+ doRemovePolicyTest(agent, callback, 1)
+ }
+
+ @Test
+ fun testRemoveDscpPolicy_InvalidPolicy(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
+ agent.sendRemoveDscpPolicy(3)
+ // Is there something to add in TestableNetworkCallback to NOT expect a callback?
+ // Or should we send STATUS_DELETED in any case or a different STATUS?
+ }
+
+ @Test
+ fun testRemoveAllDscpPolicies(): Unit = createConnectedNetworkAgent().let { (agent, callback) ->
+ val policy = DscpPolicy.Builder(1, 1)
+ .setDestinationPortRange(Range(1111, 1111)).build()
+ agent.sendAddDscpPolicy(policy)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 1111)
+ }
+
+ val policy2 = DscpPolicy.Builder(2, 1)
+ .setDestinationPortRange(Range(2222, 2222)).build()
+ agent.sendAddDscpPolicy(policy2)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(2, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 2222)
+ }
+
+ val policy3 = DscpPolicy.Builder(3, 1)
+ .setDestinationPortRange(Range(3333, 3333)).build()
+ agent.sendAddDscpPolicy(policy3)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(3, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 3333)
+ }
+
+ agent.sendRemoveAllDscpPolicies()
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_DELETED, it.status)
+ checkDscpValue(agent, callback, dstPort = 1111)
+ }
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(2, it.policyId)
+ assertEquals(STATUS_DELETED, it.status)
+ checkDscpValue(agent, callback, dstPort = 2222)
+ }
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(3, it.policyId)
+ assertEquals(STATUS_DELETED, it.status)
+ checkDscpValue(agent, callback, dstPort = 3333)
+ }
+ }
+
+ @Test
+ fun testAddDuplicateDscpPolicy(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
+ val policy = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(4444, 4444)).build()
+ agent.sendAddDscpPolicy(policy)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 4444)
+ }
+
+ // TODO: send packet on socket and confirm that changing the DSCP policy
+ // updates the mark to the new value.
+
+ val policy2 = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(5555, 5555)).build()
+ agent.sendAddDscpPolicy(policy2)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_SUCCESS, it.status)
+
+ // Sending packet with old policy should fail
+ checkDscpValue(agent, callback, dstPort = 4444)
+ checkDscpValue(agent, callback, dscpValue = 1, dstPort = 5555)
+ }
+
+ agent.sendRemoveDscpPolicy(1)
+ agent.expectCallback<OnDscpPolicyStatusUpdated>().let {
+ assertEquals(1, it.policyId)
+ assertEquals(STATUS_DELETED, it.status)
+ }
+ }
+
+ @Test
+ fun testParcelingDscpPolicyIsLossless(): Unit = createConnectedNetworkAgent().let {
+ (agent, callback) ->
+ // Check that policy with partial parameters is lossless.
+ val policy = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(4444, 4444)).build()
+ assertParcelingIsLossless(policy);
+
+ // Check that policy with all parameters is lossless.
+ val policy2 = DscpPolicy.Builder(1, 1).setDestinationPortRange(Range(4444, 4444))
+ .setSourceAddress(LOCAL_IPV4_ADDRESS)
+ .setDestinationAddress(TEST_TARGET_IPV4_ADDR)
+ .setProtocol(IPPROTO_UDP).build()
+ assertParcelingIsLossless(policy2);
+ }
+}
+
+private fun ByteBuffer.readAsArray(): ByteArray {
+ val out = ByteArray(remaining())
+ get(out)
+ return out
+}
+
+private fun <T> Context.assertHasService(manager: Class<T>): T {
+ return getSystemService(manager) ?: fail("Service $manager not found")
+}
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 7b5b44f..530fa91 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -53,6 +53,8 @@
// android_library does not include JNI libs: include NetworkStack dependencies here
"libnativehelper_compat_libc++",
"libnetworkstackutilsjni",
+ "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/Android.bp b/tests/unit/Android.bp
index 562414f..fe0210c 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -23,6 +23,7 @@
name: "FrameworksNetTests-jni-defaults",
jni_libs: [
"ld-android",
+ "libandroid_net_frameworktests_util_jni",
"libbase",
"libbinder",
"libbpf_bcc",
@@ -38,8 +39,8 @@
"liblog",
"liblzma",
"libnativehelper",
- "libnetdbpf",
"libnetdutils",
+ "libnetworkstats",
"libnetworkstatsfactorytestjni",
"libpackagelistparser",
"libpcre2",
@@ -74,6 +75,7 @@
"java/android/net/TelephonyNetworkSpecifierTest.java",
"java/android/net/VpnManagerTest.java",
"java/android/net/ipmemorystore/*.java",
+ "java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
"java/android/net/nsd/*.java",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
"java/com/android/internal/net/VpnProfileTest.java",
@@ -88,7 +90,9 @@
"java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
"java/com/android/server/connectivity/VpnTest.java",
"java/com/android/server/net/ipmemorystore/*.java",
+ "java/com/android/server/net/BpfInterfaceMapUpdaterTest.java",
"java/com/android/server/net/NetworkStats*.java",
+ "java/com/android/server/net/TestableUsageCallback.kt",
]
}
@@ -141,6 +145,7 @@
"ServiceConnectivityResources",
],
visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
+ exclude_kotlinc_generated_files: false,
}
android_test {
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/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index 6ad8b06..4b2d874 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -17,11 +17,16 @@
package android.net
import android.content.Context
+import android.net.ConnectivityManager.MAX_NETWORK_TYPE
+import android.net.ConnectivityManager.TYPE_ETHERNET
import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_NONE
+import android.net.ConnectivityManager.TYPE_WIFI
import android.net.NetworkIdentity.OEM_NONE
import android.net.NetworkIdentity.OEM_PAID
import android.net.NetworkIdentity.OEM_PRIVATE
import android.net.NetworkIdentity.getOemBitfield
+import android.app.usage.NetworkStatsManager
import android.telephony.TelephonyManager
import android.os.Build
import com.android.testutils.DevSdkIgnoreRule
@@ -30,10 +35,12 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
private const val TEST_IMSI = "testimsi"
+private const val TEST_WIFI_KEY = "testwifikey"
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
@@ -126,6 +133,93 @@
assertEquals(identFromLegacyBuild, identFromSnapshot)
assertEquals(identFromConstructor, identFromSnapshot)
- // TODO: Add test cases for wifiNetworkKey and ratType.
+ // Assert non-wifi can't have wifi network key.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_ETHERNET)
+ .setWifiNetworkKey(TEST_WIFI_KEY)
+ .build()
+ }
+
+ // Assert non-mobile can't have ratType.
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_WIFI)
+ .setRatType(TelephonyManager.NETWORK_TYPE_LTE)
+ .build()
+ }
+ }
+
+ @Test
+ fun testBuilder_type() {
+ // Assert illegal type values cannot make an identity.
+ listOf(Integer.MIN_VALUE, TYPE_NONE - 1, MAX_NETWORK_TYPE + 1, Integer.MAX_VALUE)
+ .forEach { type ->
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder().setType(type).build()
+ }
+ }
+
+ // Verify legitimate type values can make an identity.
+ for (type in TYPE_NONE..MAX_NETWORK_TYPE) {
+ NetworkIdentity.Builder().setType(type).build().also {
+ assertEquals(it.type, type)
+ }
+ }
+ }
+
+ @Test
+ fun testBuilder_ratType() {
+ // Assert illegal ratTypes cannot make an identity.
+ listOf(Integer.MIN_VALUE, NetworkTemplate.NETWORK_TYPE_ALL,
+ NetworkStatsManager.NETWORK_TYPE_5G_NSA - 1, Integer.MAX_VALUE)
+ .forEach {
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setRatType(it)
+ .build()
+ }
+ }
+
+ // Verify legitimate ratTypes can make an identity.
+ TelephonyManager.getAllNetworkTypes().toMutableList().also {
+ it.add(TelephonyManager.NETWORK_TYPE_UNKNOWN)
+ it.add(NetworkStatsManager.NETWORK_TYPE_5G_NSA)
+ }.forEach { rat ->
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setRatType(rat)
+ .build().also {
+ assertEquals(it.ratType, rat)
+ }
+ }
+ }
+
+ @Test
+ fun testBuilder_oemManaged() {
+ // Assert illegal oemManage values cannot make an identity.
+ listOf(Integer.MIN_VALUE, NetworkTemplate.OEM_MANAGED_ALL, NetworkTemplate.OEM_MANAGED_YES,
+ Integer.MAX_VALUE)
+ .forEach { oemManaged ->
+ assertFailsWith<IllegalArgumentException> {
+ NetworkIdentity.Builder()
+ .setType(TYPE_MOBILE)
+ .setOemManaged(oemManaged)
+ .build()
+ }
+ }
+
+ // Verify legitimate oem managed values can make an identity.
+ listOf(NetworkTemplate.OEM_MANAGED_NO, NetworkTemplate.OEM_MANAGED_PAID,
+ NetworkTemplate.OEM_MANAGED_PRIVATE, NetworkTemplate.OEM_MANAGED_PAID or
+ NetworkTemplate.OEM_MANAGED_PRIVATE)
+ .forEach { oemManaged ->
+ NetworkIdentity.Builder()
+ .setOemManaged(oemManaged)
+ .build().also {
+ assertEquals(it.oemManaged, oemManaged)
+ }
+ }
}
}
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 2e82986..c27ee93 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -38,11 +38,13 @@
import static org.junit.Assert.fail;
import android.content.res.Resources;
+import android.net.NetworkStatsCollection.Key;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.RecurrenceRule;
import androidx.test.InstrumentationRegistry;
@@ -73,6 +75,7 @@
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Tests for {@link NetworkStatsCollection}.
@@ -530,6 +533,52 @@
assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
}
+ @Test
+ public void testBuilder() {
+ final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final NetworkIdentitySet ident = new NetworkIdentitySet();
+ final Key key1 = new Key(ident, 0, 0, 0);
+ final Key key2 = new Key(ident, 1, 0, 0);
+ final long bucketDuration = 10;
+
+ final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 10, 40,
+ 4, 50, 5, 60);
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 10, 3,
+ 41, 7, 1, 0);
+
+ NetworkStatsHistory history1 = new NetworkStatsHistory.Builder(10, 5)
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .build();
+
+ NetworkStatsHistory history2 = new NetworkStatsHistory(10, 5);
+
+ NetworkStatsCollection actualCollection = new NetworkStatsCollection.Builder(bucketDuration)
+ .addEntry(key1, history1)
+ .addEntry(key2, history2)
+ .build();
+
+ // The builder will omit any entry with empty history. Thus, history2
+ // is not expected in the result collection.
+ expectedEntries.put(key1, history1);
+
+ final Map<Key, NetworkStatsHistory> actualEntries = actualCollection.getEntries();
+
+ assertEquals(expectedEntries.size(), actualEntries.size());
+ for (Key expectedKey : expectedEntries.keySet()) {
+ final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
+
+ final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
+ assertNotNull(actualHistory);
+
+ assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
+
+ actualEntries.remove(expectedKey);
+ }
+ assertEquals(0, actualEntries.size());
+ }
+
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.
@@ -587,6 +636,14 @@
actual.txBytes, actual.txPackets, 0L));
}
+ private static void assertEntry(NetworkStatsHistory.Entry expected,
+ NetworkStatsHistory.Entry actual) {
+ assertEntry(new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
+ actual.txBytes, actual.txPackets, 0L),
+ new NetworkStats.Entry(actual.rxBytes, actual.rxPackets,
+ actual.txBytes, actual.txPackets, 0L));
+ }
+
private static void assertEntry(NetworkStats.Entry expected,
NetworkStats.Entry actual) {
assertEquals("unexpected rxBytes", expected.rxBytes, actual.rxBytes);
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index c5f8c00..c170605 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -56,6 +56,7 @@
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.util.List;
import java.util.Random;
@RunWith(DevSdkIgnoreRunner.class)
@@ -532,6 +533,40 @@
assertEquals(512L + 4096L, stats.getTotalBytes());
}
+ @Test
+ public void testBuilder() {
+ final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 30, 40,
+ 4, 50, 5, 60);
+ final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(30, 15, 3,
+ 41, 7, 1, 0);
+ final NetworkStatsHistory.Entry entry3 = new NetworkStatsHistory.Entry(7, 301, 11,
+ 14, 31, 2, 80);
+
+ final NetworkStatsHistory statsEmpty = new NetworkStatsHistory
+ .Builder(HOUR_IN_MILLIS, 10).build();
+ assertEquals(0, statsEmpty.getEntries().size());
+ assertEquals(HOUR_IN_MILLIS, statsEmpty.getBucketDuration());
+
+ NetworkStatsHistory statsSingle = new NetworkStatsHistory
+ .Builder(HOUR_IN_MILLIS, 8)
+ .addEntry(entry1)
+ .build();
+ assertEquals(1, statsSingle.getEntries().size());
+ assertEquals(HOUR_IN_MILLIS, statsSingle.getBucketDuration());
+ assertEquals(entry1, statsSingle.getEntries().get(0));
+
+ NetworkStatsHistory statsMultiple = new NetworkStatsHistory
+ .Builder(SECOND_IN_MILLIS, 0)
+ .addEntry(entry1).addEntry(entry2).addEntry(entry3)
+ .build();
+ final List<NetworkStatsHistory.Entry> entries = statsMultiple.getEntries();
+ assertEquals(3, entries.size());
+ assertEquals(SECOND_IN_MILLIS, statsMultiple.getBucketDuration());
+ assertEquals(entry1, entries.get(0));
+ assertEquals(entry2, entries.get(1));
+ assertEquals(entry3, entries.get(2));
+ }
+
private static void assertIndexBeforeAfter(
NetworkStatsHistory stats, int before, int after, long time) {
assertEquals("unexpected before", before, stats.getIndexBefore(time));
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 0c3bee3..453612f 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -16,6 +16,7 @@
package android.net
+import android.app.usage.NetworkStatsManager.NETWORK_TYPE_5G_NSA
import android.content.Context
import android.net.ConnectivityManager.TYPE_MOBILE
import android.net.ConnectivityManager.TYPE_WIFI
@@ -36,7 +37,6 @@
import android.net.NetworkTemplate.MATCH_PROXY
import android.net.NetworkTemplate.MATCH_WIFI
import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
-import android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA
import android.net.NetworkTemplate.NETWORK_TYPE_ALL
import android.net.NetworkTemplate.OEM_MANAGED_ALL
import android.net.NetworkTemplate.OEM_MANAGED_NO
@@ -95,7 +95,7 @@
oemManaged: Int = OEM_NONE,
metered: Boolean = true
): NetworkStateSnapshot {
- `when`(mockWifiInfo.getCurrentNetworkKey()).thenReturn(wifiKey)
+ `when`(mockWifiInfo.getNetworkKey()).thenReturn(wifiKey)
val lp = LinkProperties()
val caps = NetworkCapabilities().apply {
setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !metered)
diff --git a/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
new file mode 100644
index 0000000..e4943ea
--- /dev/null
+++ b/tests/unit/java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.netstats
+
+import android.net.NetworkStatsCollection
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.frameworks.tests.net.R
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+import java.io.DataInputStream
+import java.net.ProtocolException
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
+
+private const val BUCKET_DURATION_MS = 2 * 60 * 60 * 1000L
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+class NetworkStatsDataMigrationUtilsTest {
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testReadPlatformCollection() {
+ // Verify the method throws for wrong file version.
+ assertFailsWith<ProtocolException> {
+ NetworkStatsDataMigrationUtils.readPlatformCollection(
+ NetworkStatsCollection.Builder(BUCKET_DURATION_MS),
+ getInputStreamForResource(R.raw.netstats_uid_v4))
+ }
+
+ val builder = NetworkStatsCollection.Builder(BUCKET_DURATION_MS)
+ NetworkStatsDataMigrationUtils.readPlatformCollection(builder,
+ getInputStreamForResource(R.raw.netstats_uid_v16))
+ // The values are obtained by dumping from NetworkStatsCollection that
+ // read by the logic inside the service.
+ assertValues(builder.build(), 55, 1814302L, 21050L, 31001636L, 26152L)
+ }
+
+ @Test
+ fun testMaybeReadLegacyUid() {
+ val builder = NetworkStatsCollection.Builder(BUCKET_DURATION_MS)
+ NetworkStatsDataMigrationUtils.readLegacyUid(builder,
+ getInputStreamForResource(R.raw.netstats_uid_v4), false /* taggedData */)
+ assertValues(builder.build(), 223, 106245210L, 710722L, 1130647496L, 1103989L)
+ }
+
+ private fun assertValues(
+ collection: NetworkStatsCollection,
+ expectedSize: Int,
+ expectedTxBytes: Long,
+ expectedTxPackets: Long,
+ expectedRxBytes: Long,
+ expectedRxPackets: Long
+ ) {
+ var txBytes = 0L
+ var txPackets = 0L
+ var rxBytes = 0L
+ var rxPackets = 0L
+ val entries = collection.entries
+
+ for (history in entries.values) {
+ for (historyEntry in history.entries) {
+ txBytes += historyEntry.txBytes
+ txPackets += historyEntry.txPackets
+ rxBytes += historyEntry.rxBytes
+ rxPackets += historyEntry.rxPackets
+ }
+ }
+ if (expectedSize != entries.size ||
+ expectedTxBytes != txBytes ||
+ expectedTxPackets != txPackets ||
+ expectedRxBytes != rxBytes ||
+ expectedRxPackets != rxPackets) {
+ fail("expected size=$expectedSize" +
+ "txb=$expectedTxBytes txp=$expectedTxPackets " +
+ "rxb=$expectedRxBytes rxp=$expectedRxPackets bus was " +
+ "size=${entries.size} txb=$txBytes txp=$txPackets " +
+ "rxb=$rxBytes rxp=$rxPackets")
+ }
+ assertEquals(txBytes + rxBytes, collection.totalBytes)
+ }
+
+ private fun getInputStreamForResource(resourceId: Int): DataInputStream {
+ return DataInputStream(InstrumentationRegistry.getContext()
+ .getResources().openRawResource(resourceId))
+ }
+}
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 14ff981..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;
@@ -101,12 +102,14 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM;
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
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;
@@ -261,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;
@@ -331,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;
@@ -525,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.
@@ -966,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) {
@@ -1489,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() {
@@ -1851,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));
}
@@ -1943,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) {
@@ -3812,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()));
@@ -3827,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);
@@ -5726,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 }
@@ -10073,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));
@@ -10082,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"));
}
@@ -10099,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
@@ -10115,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
@@ -10132,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));
@@ -10166,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);
}
@@ -10195,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(
@@ -10207,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);
}
@@ -13780,6 +13846,14 @@
return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc);
}
+ private TestNetworkAgentWrapper makeEnterpriseNetworkAgent(int enterpriseId) throws Exception {
+ final NetworkCapabilities workNc = new NetworkCapabilities();
+ workNc.addCapability(NET_CAPABILITY_ENTERPRISE);
+ workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ workNc.addEnterpriseId(enterpriseId);
+ return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc);
+ }
+
private TestNetworkCallback mEnterpriseCallback;
private UserHandle setupEnterpriseNetwork() {
final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
@@ -13827,7 +13901,8 @@
inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
- final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
+ final TestNetworkAgentWrapper workAgent =
+ makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
if (connectWorkProfileAgentAhead) {
workAgent.connect(false);
}
@@ -13841,7 +13916,7 @@
== PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK) {
allowFallback = false;
}
- if (allowFallback) {
+ if (allowFallback && !connectWorkProfileAgentAhead) {
// Setting a network preference for this user will create a new set of routing rules for
// the UID range that corresponds to this user, inorder to define the default network
// for these apps separately. This is true because the multi-layer request relevant to
@@ -13858,7 +13933,7 @@
// The enterprise network is not ready yet.
assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
- if (allowFallback) {
+ if (allowFallback && !connectWorkProfileAgentAhead) {
assertNoCallbacks(profileDefaultNetworkCallback);
} else if (!connectWorkProfileAgentAhead) {
profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
@@ -13884,7 +13959,7 @@
uidRangeFor(testHandle, profileNetworkPreference),
PREFERENCE_ORDER_PROFILE));
- if (allowFallback) {
+ if (allowFallback && !connectWorkProfileAgentAhead) {
inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
mCellNetworkAgent.getNetwork().netId,
uidRangeFor(testHandle, profileNetworkPreference),
@@ -13897,7 +13972,10 @@
workAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)
- && nc.hasCapability(NET_CAPABILITY_ENTERPRISE));
+ && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
+ && nc.hasEnterpriseId(
+ profileNetworkPreference.getPreferenceEnterpriseId())
+ && nc.getEnterpriseIds().length == 1);
if (disAllowProfileDefaultNetworkCallback != null) {
assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
}
@@ -13988,7 +14066,8 @@
// If the control comes here, callbacks seem to behave correctly in the presence of
// a default network when the enterprise network goes up and down. Now, make sure they
// also behave correctly in the absence of a system-wide default network.
- final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent();
+ final TestNetworkAgentWrapper workAgent2 =
+ makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
workAgent2.connect(false);
profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2);
@@ -14006,7 +14085,10 @@
workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid());
profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
- && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasEnterpriseId(
+ profileNetworkPreference.getPreferenceEnterpriseId())
+ && nc.getEnterpriseIds().length == 1);
if (disAllowProfileDefaultNetworkCallback != null) {
assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
}
@@ -14037,10 +14119,11 @@
@Test
public void testPreferenceForUserNetworkUpDown() throws Exception {
final UserHandle testHandle = setupEnterpriseNetwork();
+ registerDefaultNetworkCallbacks();
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
- registerDefaultNetworkCallbacks();
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), false,
testHandle, mProfileDefaultNetworkCallback, null);
@@ -14058,6 +14141,7 @@
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(
PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
registerDefaultNetworkCallbacks();
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), false,
@@ -14078,6 +14162,7 @@
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(
PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
registerDefaultNetworkCallbacks();
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), true, testHandle,
@@ -14094,6 +14179,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder.setIncludedUids(
List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID)));
registerDefaultNetworkCallbacks();
@@ -14112,6 +14198,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder.setIncludedUids(
List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
registerDefaultNetworkCallbacks();
@@ -14130,6 +14217,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder.setExcludedUids(
List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
registerDefaultNetworkCallbacks();
@@ -14149,6 +14237,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder.setExcludedUids(
List.of(testHandle.getUid(0) - 1));
final TestOnCompleteListener listener = new TestOnCompleteListener();
@@ -14178,6 +14267,7 @@
ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
new ProfileNetworkPreference.Builder();
profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
profileNetworkPreferenceBuilder2.setIncludedUids(
List.of(testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)));
profileNetworkPreferenceBuilder.setIncludedUids(
@@ -14213,6 +14303,84 @@
}
/**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active. Make sure they behave as expected whether
+ * there is a general default network or not when configured to fallback to default network
+ * along with already connected enterprise work agent
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithFallbackWithAlreadyConnectedWorkAgent()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback,
+ null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithDefaultEnterpriseId()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback,
+ null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithId2()
+ throws Exception {
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(
+ NetworkCapabilities.NET_ENTERPRISE_ID_2);
+ registerDefaultNetworkCallbacks();
+ testPreferenceForUserNetworkUpDownForGivenPreference(
+ profileNetworkPreferenceBuilder.build(), true,
+ testHandle, mProfileDefaultNetworkCallback, null);
+ }
+
+ /**
+ * Make sure per-profile networking preference behaves as expected when the enterprise network
+ * goes up and down while the preference is active for a given enterprise identifier
+ */
+ @Test
+ public void testPreferenceForUserNetworkUpDownWithInvalidId()
+ throws Exception {
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(0);
+ registerDefaultNetworkCallbacks();
+ assertThrows("Should not be able to set invalid enterprise id",
+ IllegalStateException.class, () -> profileNetworkPreferenceBuilder.build());
+ }
+
+ /**
* Test that, in a given networking context, calling setPreferenceForUser to set per-profile
* defaults on then off works as expected.
*/
@@ -14362,10 +14530,15 @@
public void testProfileNetworkPrefWrongPreference() throws Exception {
final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
mServiceContext.setWorkProfile(testHandle, true);
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(
+ PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + 1);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
assertThrows("Should not be able to set an illegal preference",
IllegalArgumentException.class,
- () -> mCm.setProfileNetworkPreference(testHandle,
- PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + 1,
+ () -> mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
null, null));
}
@@ -14447,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/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
index ea29da0..0c58582 100644
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -32,6 +33,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.INetdUnsolicitedEventListener;
import android.net.LinkAddress;
@@ -71,6 +73,7 @@
public class NetworkManagementServiceTest {
private NetworkManagementService mNMService;
@Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
@Mock private IBatteryStats.Stub mBatteryStatsService;
@Mock private INetd.Stub mNetdService;
@@ -113,6 +116,9 @@
MockitoAnnotations.initMocks(this);
doNothing().when(mNetdService)
.registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ eq(ConnectivityManager.class));
+ doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
// Start the service and wait until it connects to our socket.
mNMService = NetworkManagementService.create(mContext, mDeps);
}
@@ -239,6 +245,7 @@
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
assertTrue("Should be true since mobile data usage is restricted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkDenyList(TEST_UID, true /* enabled */);
mNMService.setDataSaverModeEnabled(true);
verify(mNetdService).bandwidthEnableDataSaver(true);
@@ -246,13 +253,16 @@
mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
assertTrue("Should be true since data saver is on and the uid is not allowlisted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkDenyList(TEST_UID, true /* false */);
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
assertFalse("Should be false since data saver is on and the uid is allowlisted",
mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).updateMeteredNetworkAllowList(TEST_UID, true /* enabled */);
// remove uid from allowlist and turn datasaver off again
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
+ verify(mCm).updateMeteredNetworkAllowList(TEST_UID, false /* enabled */);
mNMService.setDataSaverModeEnabled(false);
verify(mNetdService).bandwidthEnableDataSaver(false);
assertFalse("Network should not be restricted when data saver is off",
@@ -306,12 +316,14 @@
for (int chain : chains) {
final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
mNMService.setFirewallChainEnabled(chain, true);
+ verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
for (int state : states) {
mNMService.setFirewallUidRule(chain, TEST_UID, state);
assertEquals(errorMsg.apply(chain, state),
expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
}
mNMService.setFirewallChainEnabled(chain, false);
+ verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
}
}
}
diff --git a/tests/unit/java/com/android/server/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/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
new file mode 100644
index 0000000..84e02ce
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -0,0 +1,385 @@
+/*
+ * 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.INetd.IF_STATE_UP;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_MTU;
+import static com.android.server.connectivity.ClatCoordinator.CLAT_MAX_MTU;
+import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_PREFIX_LEN;
+import static com.android.server.connectivity.ClatCoordinator.INIT_V4ADDR_STRING;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+
+import android.annotation.NonNull;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+
+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.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Objects;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class ClatCoordinatorTest {
+ private static final String BASE_IFACE = "test0";
+ private static final String STACKED_IFACE = "v4-test0";
+ private static final int BASE_IFINDEX = 1000;
+
+ private static final IpPrefix NAT64_IP_PREFIX = new IpPrefix("64:ff9b::/96");
+ private static final String NAT64_PREFIX_STRING = "64:ff9b::";
+ private static final int GOOGLE_DNS_4 = 0x08080808; // 8.8.8.8
+ private static final int NETID = 42;
+
+ // The test fwmark means: PERMISSION_SYSTEM (0x2), protectedFromVpn: true,
+ // explicitlySelected: true, netid: 42. For bit field structure definition, see union Fwmark in
+ // system/netd/include/Fwmark.h
+ private static final int MARK = 0xb002a;
+
+ private static final String XLAT_LOCAL_IPV4ADDR_STRING = "192.0.0.46";
+ private static final String XLAT_LOCAL_IPV6ADDR_STRING = "2001:db8:0:b11::464";
+ private static final int CLATD_PID = 10483;
+
+ private static final int TUN_FD = 534;
+ private static final int RAW_SOCK_FD = 535;
+ private static final int PACKET_SOCK_FD = 536;
+ private static final ParcelFileDescriptor TUN_PFD = new ParcelFileDescriptor(
+ new FileDescriptor());
+ private static final ParcelFileDescriptor RAW_SOCK_PFD = new ParcelFileDescriptor(
+ new FileDescriptor());
+ private static final ParcelFileDescriptor PACKET_SOCK_PFD = new ParcelFileDescriptor(
+ new FileDescriptor());
+
+ @Mock private INetd mNetd;
+ @Spy private TestDependencies mDeps = new TestDependencies();
+
+ /**
+ * The dependency injection class is used to mock the JNI functions and system functions
+ * for clatd coordinator control plane. Note that any testing used JNI functions need to
+ * be overridden to avoid calling native methods.
+ */
+ protected class TestDependencies extends ClatCoordinator.Dependencies {
+ /**
+ * Get netd.
+ */
+ @Override
+ public INetd getNetd() {
+ return mNetd;
+ }
+
+ /**
+ * @see ParcelFileDescriptor#adoptFd(int).
+ */
+ @Override
+ public ParcelFileDescriptor adoptFd(int fd) {
+ switch (fd) {
+ case TUN_FD:
+ return TUN_PFD;
+ case RAW_SOCK_FD:
+ return RAW_SOCK_PFD;
+ case PACKET_SOCK_FD:
+ return PACKET_SOCK_PFD;
+ default:
+ fail("unsupported arg: " + fd);
+ return null;
+ }
+ }
+
+ /**
+ * Get interface index for a given interface.
+ */
+ @Override
+ public int getInterfaceIndex(String ifName) {
+ if (BASE_IFACE.equals(ifName)) {
+ return BASE_IFINDEX;
+ }
+ fail("unsupported arg: " + ifName);
+ return -1;
+ }
+
+ /**
+ * Create tun interface for a given interface name.
+ */
+ @Override
+ public int createTunInterface(@NonNull String tuniface) throws IOException {
+ if (STACKED_IFACE.equals(tuniface)) {
+ return TUN_FD;
+ }
+ fail("unsupported arg: " + tuniface);
+ return -1;
+ }
+
+ /**
+ * Pick an IPv4 address for clat.
+ */
+ @Override
+ public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
+ throws IOException {
+ if (INIT_V4ADDR_STRING.equals(v4addr) && INIT_V4ADDR_PREFIX_LEN == prefixlen) {
+ return XLAT_LOCAL_IPV4ADDR_STRING;
+ }
+ fail("unsupported args: " + v4addr + ", " + prefixlen);
+ return null;
+ }
+
+ /**
+ * Generate a checksum-neutral IID.
+ */
+ @Override
+ public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
+ @NonNull String prefix64) throws IOException {
+ if (BASE_IFACE.equals(iface) && XLAT_LOCAL_IPV4ADDR_STRING.equals(v4)
+ && NAT64_PREFIX_STRING.equals(prefix64)) {
+ return XLAT_LOCAL_IPV6ADDR_STRING;
+ }
+ fail("unsupported args: " + iface + ", " + v4 + ", " + prefix64);
+ return null;
+ }
+
+ /**
+ * Detect MTU.
+ */
+ @Override
+ public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
+ throws IOException {
+ if (NAT64_PREFIX_STRING.equals(platSubnet) && GOOGLE_DNS_4 == platSuffix
+ && MARK == mark) {
+ return ETHER_MTU;
+ }
+ fail("unsupported args: " + platSubnet + ", " + platSuffix + ", " + mark);
+ return -1;
+ }
+
+ /**
+ * Open IPv6 raw socket and set SO_MARK.
+ */
+ @Override
+ public int openRawSocket6(int mark) throws IOException {
+ if (mark == MARK) {
+ return RAW_SOCK_FD;
+ }
+ fail("unsupported arg: " + mark);
+ return -1;
+ }
+
+ /**
+ * Open packet socket.
+ */
+ @Override
+ public int openPacketSocket() throws IOException {
+ // assume that open socket always successfully because there is no argument to check.
+ return PACKET_SOCK_FD;
+ }
+
+ /**
+ * Add anycast setsockopt.
+ */
+ @Override
+ public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex)
+ throws IOException {
+ if (Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), sock)
+ && XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)
+ && BASE_IFINDEX == ifindex) return;
+ fail("unsupported args: " + sock + ", " + v6 + ", " + ifindex);
+ }
+
+ /**
+ * Configure packet socket.
+ */
+ @Override
+ public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex)
+ throws IOException {
+ if (Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), sock)
+ && XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)
+ && BASE_IFINDEX == ifindex) return;
+ fail("unsupported args: " + sock + ", " + v6 + ", " + ifindex);
+ }
+
+ /**
+ * Start clatd.
+ */
+ @Override
+ public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
+ @NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96,
+ @NonNull String v4, @NonNull String v6) throws IOException {
+ if (Objects.equals(TUN_PFD.getFileDescriptor(), tunfd)
+ && Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), readsock6)
+ && Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), writesock6)
+ && BASE_IFACE.equals(iface)
+ && NAT64_PREFIX_STRING.equals(pfx96)
+ && XLAT_LOCAL_IPV4ADDR_STRING.equals(v4)
+ && XLAT_LOCAL_IPV6ADDR_STRING.equals(v6)) {
+ return CLATD_PID;
+ }
+ fail("unsupported args: " + tunfd + ", " + readsock6 + ", " + writesock6 + ", "
+ + ", " + iface + ", " + v4 + ", " + v6);
+ return -1;
+ }
+
+ /**
+ * Stop clatd.
+ */
+ public void stopClatd(@NonNull String iface, @NonNull String pfx96, @NonNull String v4,
+ @NonNull String v6, int pid) throws IOException {
+ if (pid == -1) {
+ fail("unsupported arg: " + pid);
+ }
+ }
+ };
+
+ @NonNull
+ private ClatCoordinator makeClatCoordinator() throws Exception {
+ final ClatCoordinator coordinator = new ClatCoordinator(mDeps);
+ return coordinator;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private boolean assertContainsFlag(String[] flags, String match) {
+ for (String flag : flags) {
+ if (flag.equals(match)) return true;
+ }
+ fail("Missing flag: " + match);
+ return false;
+ }
+
+ @Test
+ public void testStartStopClatd() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ final InOrder inOrder = inOrder(mNetd, mDeps);
+ clearInvocations(mNetd, mDeps);
+
+ // [1] Start clatd.
+ final String addr6For464xlat = coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
+ assertEquals(XLAT_LOCAL_IPV6ADDR_STRING, addr6For464xlat);
+
+ // Pick an IPv4 address.
+ inOrder.verify(mDeps).selectIpv4Address(eq(INIT_V4ADDR_STRING),
+ eq(INIT_V4ADDR_PREFIX_LEN));
+
+ // Generate a checksum-neutral IID.
+ inOrder.verify(mDeps).generateIpv6Address(eq(BASE_IFACE),
+ eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(NAT64_PREFIX_STRING));
+
+ // Open, configure and bring up the tun interface.
+ inOrder.verify(mDeps).createTunInterface(eq(STACKED_IFACE));
+ inOrder.verify(mDeps).adoptFd(eq(TUN_FD));
+ inOrder.verify(mNetd).interfaceSetEnableIPv6(eq(STACKED_IFACE), eq(false /* enable */));
+ inOrder.verify(mDeps).detectMtu(eq(NAT64_PREFIX_STRING), eq(GOOGLE_DNS_4), eq(MARK));
+ inOrder.verify(mNetd).interfaceSetMtu(eq(STACKED_IFACE),
+ eq(1472 /* ETHER_MTU(1500) - MTU_DELTA(28) */));
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+ STACKED_IFACE.equals(cfg.ifName)
+ && XLAT_LOCAL_IPV4ADDR_STRING.equals(cfg.ipv4Addr)
+ && (32 == cfg.prefixLength)
+ && "".equals(cfg.hwAddr)
+ && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+
+ // Open and configure 464xlat read/write sockets.
+ inOrder.verify(mDeps).openPacketSocket();
+ inOrder.verify(mDeps).adoptFd(eq(PACKET_SOCK_FD));
+ inOrder.verify(mDeps).openRawSocket6(eq(MARK));
+ inOrder.verify(mDeps).adoptFd(eq(RAW_SOCK_FD));
+ inOrder.verify(mDeps).getInterfaceIndex(eq(BASE_IFACE));
+ inOrder.verify(mDeps).addAnycastSetsockopt(
+ argThat(fd -> Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), fd)),
+ eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(BASE_IFINDEX));
+ inOrder.verify(mDeps).configurePacketSocket(
+ argThat(fd -> Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), fd)),
+ eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(BASE_IFINDEX));
+
+ // Start clatd.
+ inOrder.verify(mDeps).startClatd(
+ argThat(fd -> Objects.equals(TUN_PFD.getFileDescriptor(), fd)),
+ argThat(fd -> Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), fd)),
+ argThat(fd -> Objects.equals(RAW_SOCK_PFD.getFileDescriptor(), fd)),
+ eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
+ eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING));
+ inOrder.verifyNoMoreInteractions();
+
+ // [2] Start clatd again failed.
+ assertThrows("java.io.IOException: Clatd is already running on test0 (pid 10483)",
+ IOException.class,
+ () -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+
+ // [3] Expect clatd to stop successfully.
+ coordinator.clatStop();
+ inOrder.verify(mDeps).stopClatd(eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
+ eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(CLATD_PID));
+ inOrder.verifyNoMoreInteractions();
+
+ // [4] Expect an IO exception while stopping a clatd that doesn't exist.
+ assertThrows("java.io.IOException: Clatd has not started", IOException.class,
+ () -> coordinator.clatStop());
+ inOrder.verify(mDeps, never()).stopClatd(anyString(), anyString(), anyString(),
+ anyString(), anyInt());
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testGetFwmark() throws Exception {
+ assertEquals(0xb0064, ClatCoordinator.getFwmark(100));
+ assertEquals(0xb03e8, ClatCoordinator.getFwmark(1000));
+ assertEquals(0xb2710, ClatCoordinator.getFwmark(10000));
+ assertEquals(0xbffff, ClatCoordinator.getFwmark(65535));
+ }
+
+ @Test
+ public void testAdjustMtu() throws Exception {
+ // Expected mtu is that IPV6_MIN_MTU(1280) minus MTU_DELTA(28).
+ assertEquals(1252, ClatCoordinator.adjustMtu(-1 /* detect mtu failed */));
+ assertEquals(1252, ClatCoordinator.adjustMtu(500));
+ assertEquals(1252, ClatCoordinator.adjustMtu(1000));
+ assertEquals(1252, ClatCoordinator.adjustMtu(1280));
+
+ // Expected mtu is that the detected mtu minus MTU_DELTA(28).
+ assertEquals(1372, ClatCoordinator.adjustMtu(1400));
+ assertEquals(1472, ClatCoordinator.adjustMtu(ETHER_MTU));
+ assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU));
+
+ // Expected mtu is that CLAT_MAX_MTU(65536) minus MTU_DELTA(28).
+ assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU + 1 /* over maximum mtu */));
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index c86e699..ec51537 100644
--- a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -296,7 +296,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
}
@Test
@@ -315,7 +315,7 @@
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
}
@Test
@@ -334,7 +334,7 @@
// Daily budget should be 15MB (5% of daily quota), 7MB used today: callback set for 8MB
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
}
@Test
@@ -351,7 +351,7 @@
// Default global setting should be used: 12 - 7 = 5
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(5)), any(), any());
}
@Test
@@ -366,7 +366,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(8)), any(), any());
// Update setting
setDefaultQuotaGlobalSetting(DataUnit.MEGABYTES.toBytes(14));
@@ -376,7 +376,7 @@
// Callback must have been re-registered with new setting
verify(mStatsManager, times(1)).unregisterUsageCallback(any());
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
}
@Test
@@ -391,7 +391,7 @@
false /* roaming */);
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(12)), any(), any());
when(mResources.getInteger(R.integer.config_networkDefaultDailyMultipathQuotaBytes))
.thenReturn((int) DataUnit.MEGABYTES.toBytes(16));
@@ -402,6 +402,6 @@
// Uses the new setting (16 - 2 = 14MB)
verify(mStatsManager, times(1)).registerUsageCallback(
- any(), anyInt(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
+ any(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
}
}
diff --git a/tests/unit/java/com/android/server/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/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
new file mode 100644
index 0000000..987b7b7
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.MacAddress;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.Struct.U32;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class BpfInterfaceMapUpdaterTest {
+ private static final int TEST_INDEX = 1;
+ private static final int TEST_INDEX2 = 2;
+ private static final String TEST_INTERFACE_NAME = "test1";
+ private static final String TEST_INTERFACE_NAME2 = "test2";
+
+ private final TestLooper mLooper = new TestLooper();
+ private BaseNetdUnsolicitedEventListener mListener;
+ private BpfInterfaceMapUpdater mUpdater;
+ @Mock private IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ @Mock private INetd mNetd;
+ @Mock private Context mContext;
+
+ private class TestDependencies extends BpfInterfaceMapUpdater.Dependencies {
+ @Override
+ public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+ return mBpfMap;
+ }
+
+ @Override
+ public InterfaceParams getInterfaceParams(String ifaceName) {
+ if (ifaceName.equals(TEST_INTERFACE_NAME)) {
+ return new InterfaceParams(TEST_INTERFACE_NAME, TEST_INDEX,
+ MacAddress.ALL_ZEROS_ADDRESS);
+ } else if (ifaceName.equals(TEST_INTERFACE_NAME2)) {
+ return new InterfaceParams(TEST_INTERFACE_NAME2, TEST_INDEX2,
+ MacAddress.ALL_ZEROS_ADDRESS);
+ }
+
+ return null;
+ }
+
+ @Override
+ public INetd getINetd(Context ctx) {
+ return mNetd;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mNetd.interfaceGetList()).thenReturn(new String[] {TEST_INTERFACE_NAME});
+ mUpdater = new BpfInterfaceMapUpdater(mContext, new Handler(mLooper.getLooper()),
+ new TestDependencies());
+ }
+
+ private void verifyStartUpdater() throws Exception {
+ mUpdater.start();
+ mLooper.dispatchAll();
+ final ArgumentCaptor<BaseNetdUnsolicitedEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener.class);
+ verify(mNetd).registerUnsolicitedEventListener(listenerCaptor.capture());
+ mListener = listenerCaptor.getValue();
+ verify(mBpfMap).updateEntry(eq(new U32(TEST_INDEX)),
+ eq(new InterfaceMapValue(TEST_INTERFACE_NAME)));
+ }
+
+ @Test
+ public void testUpdateInterfaceMap() throws Exception {
+ verifyStartUpdater();
+
+ mListener.onInterfaceAdded(TEST_INTERFACE_NAME2);
+ mLooper.dispatchAll();
+ verify(mBpfMap).updateEntry(eq(new U32(TEST_INDEX2)),
+ eq(new InterfaceMapValue(TEST_INTERFACE_NAME2)));
+
+ // Check that when onInterfaceRemoved is called, nothing happens.
+ mListener.onInterfaceRemoved(TEST_INTERFACE_NAME);
+ mLooper.dispatchAll();
+ verifyNoMoreInteractions(mBpfMap);
+ }
+}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 8340a13..6872f80 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -28,13 +28,16 @@
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;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import android.content.Context;
import android.content.res.Resources;
-import android.net.INetd;
+import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
@@ -73,8 +76,8 @@
private File mTestProc;
private NetworkStatsFactory mFactory;
- @Mock
- private INetd mNetd;
+ @Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
@Before
public void setUp() throws Exception {
@@ -85,7 +88,10 @@
// applications. So in order to have a test support native library, the native code
// related to networkStatsFactory is compiled to a minimal native library and loaded here.
System.loadLibrary("networkstatsfactorytestjni");
- mFactory = new NetworkStatsFactory(mTestProc, false, mNetd);
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ eq(ConnectivityManager.class));
+ doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
+ mFactory = new NetworkStatsFactory(mContext, mTestProc, false);
mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index d993d1f..66dcf6d 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -36,19 +36,15 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
-import android.app.usage.NetworkStatsManager;
import android.net.DataUsageRequest;
import android.net.NetworkIdentity;
import android.net.NetworkIdentitySet;
import android.net.NetworkStats;
import android.net.NetworkStatsAccess;
import android.net.NetworkTemplate;
-import android.os.ConditionVariable;
-import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Messenger;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -56,7 +52,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
@@ -97,21 +92,15 @@
private static final long WAIT_TIMEOUT_MS = 500;
private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
- private static final int INVALID_TYPE = -1;
-
- private long mElapsedRealtime;
private HandlerThread mObserverHandlerThread;
- private Handler mObserverNoopHandler;
-
- private LatchedHandler mHandler;
private NetworkStatsObservers mStatsObservers;
- private Messenger mMessenger;
private ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
private ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
- @Mock private IBinder mockBinder;
+ @Mock private IBinder mUsageCallbackBinder;
+ private TestableUsageCallback mUsageCallback;
@Before
public void setUp() throws Exception {
@@ -127,24 +116,29 @@
}
};
- mHandler = new LatchedHandler(Looper.getMainLooper(), new ConditionVariable());
- mMessenger = new Messenger(mHandler);
-
mActiveIfaces = new ArrayMap<>();
mActiveUidIfaces = new ArrayMap<>();
+ mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
}
@Test
public void testRegister_thresholdTooLow_setsDefaultThreshold() throws Exception {
- long thresholdTooLowBytes = 1L;
- DataUsageRequest inputRequest = new DataUsageRequest(
+ final long thresholdTooLowBytes = 1L;
+ final DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
- assertTrue(request.requestId > 0);
- assertTrue(Objects.equals(sTemplateWifi, request.template));
- assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
+ final DataUsageRequest requestByApp = mStatsObservers.register(inputRequest, mUsageCallback,
+ UID_RED, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(requestByApp.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, requestByApp.template));
+ assertEquals(THRESHOLD_BYTES, requestByApp.thresholdInBytes);
+
+ // Verify the threshold requested by system uid won't be overridden.
+ final DataUsageRequest requestBySystem = mStatsObservers.register(inputRequest,
+ mUsageCallback, Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ assertTrue(requestBySystem.requestId > 0);
+ assertTrue(Objects.equals(sTemplateWifi, requestBySystem.template));
+ assertEquals(1, requestBySystem.thresholdInBytes);
}
@Test
@@ -153,7 +147,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
@@ -165,13 +159,13 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
- DataUsageRequest request1 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request1 = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request1.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request1.template));
assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
- DataUsageRequest request2 = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request2 = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request2.requestId > request1.requestId);
assertTrue(Objects.equals(sTemplateWifi, request2.template));
@@ -191,17 +185,19 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder).linkToDeath(any(IBinder.DeathRecipient.class),
+ anyInt());
mStatsObservers.unregister(request, Process.SYSTEM_UID);
waitForObserverToIdle();
- Mockito.verify(mockBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder).unlinkToDeath(any(IBinder.DeathRecipient.class),
+ anyInt());
}
@Test
@@ -209,17 +205,18 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
- Mockito.verify(mockBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ Mockito.verify(mUsageCallbackBinder)
+ .linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
mStatsObservers.unregister(request, UID_BLUE);
waitForObserverToIdle();
- Mockito.verifyZeroInteractions(mockBinder);
+ Mockito.verifyZeroInteractions(mUsageCallbackBinder);
}
private NetworkIdentitySet makeTestIdentSet() {
@@ -236,7 +233,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -260,7 +257,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -290,7 +287,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -313,7 +310,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -321,7 +318,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -346,7 +343,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -354,7 +351,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -386,7 +383,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_BLUE, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -411,7 +408,7 @@
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
waitForObserverToIdle();
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, mHandler.lastMessageType);
+ mUsageCallback.expectOnThresholdReached(request);
}
@Test
@@ -419,7 +416,7 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
- DataUsageRequest request = mStatsObservers.register(inputRequest, mMessenger, mockBinder,
+ DataUsageRequest request = mStatsObservers.register(inputRequest, mUsageCallback,
UID_RED, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
@@ -448,6 +445,5 @@
private void waitForObserverToIdle() {
HandlerUtils.waitForIdle(mObserverHandlerThread, WAIT_TIMEOUT_MS);
- HandlerUtils.waitForIdle(mHandler, WAIT_TIMEOUT_MS);
}
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 5e1699a..76c0c38 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -73,14 +73,15 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.AlarmManager;
-import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
@@ -101,13 +102,9 @@
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.wifi.WifiInfo;
import android.os.Build;
-import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
import android.os.PowerManager;
import android.os.SimpleClock;
import android.provider.Settings;
@@ -118,7 +115,10 @@
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
+import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.U8;
import com.android.server.net.NetworkStatsService.AlertObserver;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -188,13 +188,17 @@
private @Mock TetheringManager mTetheringManager;
private @Mock NetworkStatsFactory mStatsFactory;
private @Mock NetworkStatsSettings mSettings;
- private @Mock IBinder mBinder;
+ private @Mock IBinder mUsageCallbackBinder;
+ private TestableUsageCallback mUsageCallback;
private @Mock AlarmManager mAlarmManager;
@Mock
private NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor;
+ private @Mock BpfInterfaceMapUpdater mBpfInterfaceMapUpdater;
private HandlerThread mHandlerThread;
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
+ private @Mock IBpfMap<U32, U8> mUidCounterSetMap;
+ private @Mock NetworkStatsService.TagStatsDeleter mTagStatsDeleter;
private NetworkStatsService mService;
private INetworkStatsSession mSession;
@@ -266,7 +270,7 @@
mServiceContext = new MockContext(context);
when(mLocationPermissionChecker.checkCallersLocationPermission(
any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
- when(sWifiInfo.getCurrentNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
+ when(sWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
mStatsDir = TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
PowerManager powerManager = (PowerManager) mServiceContext.getSystemService(
@@ -313,6 +317,8 @@
verify(mTetheringManager).registerTetheringEventCallback(
any(), tetheringEventCbCaptor.capture());
mTetheringEventCallback = tetheringEventCbCaptor.getValue();
+
+ mUsageCallback = new TestableUsageCallback(mUsageCallbackBinder);
}
@NonNull
@@ -342,6 +348,22 @@
public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
return mLocationPermissionChecker;
}
+
+ @Override
+ public BpfInterfaceMapUpdater makeBpfInterfaceMapUpdater(
+ @NonNull Context ctx, @NonNull Handler handler) {
+ return mBpfInterfaceMapUpdater;
+ }
+
+ @Override
+ public IBpfMap<U32, U8> getUidCounterSetMap() {
+ return mUidCounterSetMap;
+ }
+
+ @Override
+ public NetworkStatsService.TagStatsDeleter getTagStatsDeleter() {
+ return mTagStatsDeleter;
+ }
};
}
@@ -467,8 +489,11 @@
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
mService.setUidForeground(UID_RED, false);
+ verify(mUidCounterSetMap, never()).deleteEntry(any());
mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
mService.setUidForeground(UID_RED, true);
+ verify(mUidCounterSetMap).updateEntry(
+ eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
forcePollAndWaitForIdle();
@@ -677,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.
@@ -1065,6 +1092,8 @@
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
mService.setUidForeground(UID_RED, true);
+ verify(mUidCounterSetMap).updateEntry(
+ eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
forcePollAndWaitForIdle();
@@ -1240,20 +1269,14 @@
DataUsageRequest inputRequest = new DataUsageRequest(
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdInBytes);
- // Create a messenger that waits for callback activity
- ConditionVariable cv = new ConditionVariable(false);
- LatchedHandler latchedHandler = new LatchedHandler(Looper.getMainLooper(), cv);
- Messenger messenger = new Messenger(latchedHandler);
-
// Force poll
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(buildEmptyStats());
// Register and verify request and that binder was called
- DataUsageRequest request =
- mService.registerUsageCallback(mServiceContext.getOpPackageName(), inputRequest,
- messenger, mBinder);
+ DataUsageRequest request = mService.registerUsageCallback(
+ mServiceContext.getOpPackageName(), inputRequest, mUsageCallback);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
long minThresholdInBytes = 2 * 1024 * 1024; // 2 MB
@@ -1262,7 +1285,7 @@
HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
// Make sure that the caller binder gets connected
- verify(mBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ verify(mUsageCallbackBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
// modify some number on wifi, and trigger poll event
// not enough traffic to call data usage callback
@@ -1277,7 +1300,7 @@
assertNetworkTotal(sTemplateWifi, 1024L, 1L, 2048L, 2L, 0);
// make sure callback has not being called
- assertEquals(INVALID_TYPE, latchedHandler.lastMessageType);
+ mUsageCallback.assertNoCallback();
// and bump forward again, with counters going higher. this is
// important, since it will trigger the data usage callback
@@ -1292,23 +1315,21 @@
assertNetworkTotal(sTemplateWifi, 4096000L, 4L, 8192000L, 8L, 0);
- // Wait for the caller to ack receipt of CALLBACK_LIMIT_REACHED
- assertTrue(cv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_LIMIT_REACHED, latchedHandler.lastMessageType);
- cv.close();
+ // Wait for the caller to invoke expectOnThresholdReached.
+ mUsageCallback.expectOnThresholdReached(request);
// Allow binder to disconnect
- when(mBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt())).thenReturn(true);
+ when(mUsageCallbackBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt()))
+ .thenReturn(true);
// Unregister request
mService.unregisterUsageRequest(request);
- // Wait for the caller to ack receipt of CALLBACK_RELEASED
- assertTrue(cv.block(WAIT_TIMEOUT));
- assertEquals(NetworkStatsManager.CALLBACK_RELEASED, latchedHandler.lastMessageType);
+ // Wait for the caller to invoke expectOnCallbackReleased.
+ mUsageCallback.expectOnCallbackReleased(request);
// Make sure that the caller binder gets disconnected
- verify(mBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+ verify(mUsageCallbackBinder).unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
}
@Test
@@ -1884,21 +1905,4 @@
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
}
-
- static class LatchedHandler extends Handler {
- private final ConditionVariable mCv;
- int lastMessageType = INVALID_TYPE;
-
- LatchedHandler(Looper looper, ConditionVariable cv) {
- super(looper);
- mCv = cv;
- }
-
- @Override
- public void handleMessage(Message msg) {
- lastMessageType = msg.what;
- mCv.open();
- super.handleMessage(msg);
- }
- }
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 43aeec6..0d34609 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -35,8 +35,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.usage.NetworkStatsManager;
import android.content.Context;
-import android.net.NetworkTemplate;
import android.os.Build;
import android.os.Looper;
import android.os.Parcel;
@@ -282,7 +282,7 @@
// NETWORK_TYPE_5G_NSA.
setRatTypeForSub(TEST_SUBID1, TelephonyManager.NETWORK_TYPE_LTE,
OVERRIDE_NETWORK_TYPE_NR_NSA);
- assertRatTypeChangedForSub(TEST_IMSI1, NetworkTemplate.NETWORK_TYPE_5G_NSA);
+ assertRatTypeChangedForSub(TEST_IMSI1, NetworkStatsManager.NETWORK_TYPE_5G_NSA);
reset(mDelegate);
// Set RAT type to LTE without NR connected, the RAT type should be downgraded to LTE.
diff --git a/tests/unit/java/com/android/server/net/TestableUsageCallback.kt b/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
new file mode 100644
index 0000000..1917ec3
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/TestableUsageCallback.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net
+
+import android.net.DataUsageRequest
+import android.net.netstats.IUsageCallback
+import android.os.IBinder
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import kotlin.test.fail
+
+private const val DEFAULT_TIMEOUT_MS = 200L
+
+// TODO: Move the class to static libs once all downstream have IUsageCallback definition.
+class TestableUsageCallback(private val binder: IBinder) : IUsageCallback.Stub() {
+ sealed class CallbackType(val request: DataUsageRequest) {
+ class OnThresholdReached(request: DataUsageRequest) : CallbackType(request)
+ class OnCallbackReleased(request: DataUsageRequest) : CallbackType(request)
+ }
+
+ // TODO: Change to use ArrayTrackRecord once moved into to the module.
+ private val history = LinkedBlockingQueue<CallbackType>()
+
+ override fun onThresholdReached(request: DataUsageRequest) {
+ history.add(CallbackType.OnThresholdReached(request))
+ }
+
+ override fun onCallbackReleased(request: DataUsageRequest) {
+ history.add(CallbackType.OnCallbackReleased(request))
+ }
+
+ fun expectOnThresholdReached(request: DataUsageRequest) {
+ expectCallback<CallbackType.OnThresholdReached>(request, DEFAULT_TIMEOUT_MS)
+ }
+
+ fun expectOnCallbackReleased(request: DataUsageRequest) {
+ expectCallback<CallbackType.OnCallbackReleased>(request, DEFAULT_TIMEOUT_MS)
+ }
+
+ @JvmOverloads
+ fun assertNoCallback(timeout: Long = DEFAULT_TIMEOUT_MS) {
+ val cb = history.poll(timeout, TimeUnit.MILLISECONDS)
+ cb?.let { fail("Expected no callback but got $cb") }
+ }
+
+ // Expects a callback of the specified request on the specified network within the timeout.
+ // If no callback arrives, or a different callback arrives, fail.
+ private inline fun <reified T : CallbackType> expectCallback(
+ expectedRequest: DataUsageRequest,
+ timeoutMs: Long
+ ) {
+ history.poll(timeoutMs, TimeUnit.MILLISECONDS).let {
+ if (it !is T || it.request != expectedRequest) {
+ fail("Unexpected callback : $it," +
+ " expected ${T::class} with Request[$expectedRequest]")
+ } else {
+ it
+ }
+ }
+ }
+
+ override fun asBinder(): IBinder {
+ return binder
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index fe971e7..04ba98f 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -24,7 +24,28 @@
"libbpf_android",
"liblog",
"libnativehelper",
- "libnetdbpf",
"libnetdutils",
+ "libnetworkstats",
+ ],
+}
+
+cc_library_shared {
+ name: "libandroid_net_frameworktests_util_jni",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ ],
+ srcs: [
+ "android_net_frameworktests_util/onload.cpp",
+ ],
+ static_libs: [
+ "libnet_utils_device_common_bpfjni",
+ "libtcutils",
+ ],
+ shared_libs: [
+ "liblog",
+ "libnativehelper",
],
}
diff --git a/tests/unit/jni/android_net_frameworktests_util/onload.cpp b/tests/unit/jni/android_net_frameworktests_util/onload.cpp
new file mode 100644
index 0000000..06a3986
--- /dev/null
+++ b/tests/unit/jni/android_net_frameworktests_util/onload.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+
+#define LOG_TAG "NetFrameworkTestsJni"
+#include <android/log.h>
+
+namespace android {
+
+int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
+int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_com_android_net_module_util_BpfMap(env,
+ "android/net/frameworktests/util/BpfMap") < 0) return JNI_ERR;
+
+ if (register_com_android_net_module_util_TcUtils(env,
+ "android/net/frameworktests/util/TcUtils") < 0) return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
diff --git a/tests/unit/res/raw/netstats_uid_v16 b/tests/unit/res/raw/netstats_uid_v16
new file mode 100644
index 0000000..a6ee430
--- /dev/null
+++ b/tests/unit/res/raw/netstats_uid_v16
Binary files differ