Merge "Fix TetheringServiceTest#testTetheringManagerLeak flaky"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 1e8babf..6e30fd1 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -205,6 +205,9 @@
},
{
"path": "packages/modules/CaptivePortalLogin"
+ },
+ {
+ "path": "vendor/xts/gts-tests/hostsidetests/networkstack"
}
]
}
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 776832f..3cad1c6 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -28,7 +28,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.bpf.Tether4Key;
@@ -66,31 +66,31 @@
// BPF map for downstream IPv4 forwarding.
@Nullable
- private final BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
+ private final IBpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
// BPF map for upstream IPv4 forwarding.
@Nullable
- private final BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
+ private final IBpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
// BPF map for downstream IPv6 forwarding.
@Nullable
- private final BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
+ private final IBpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
// BPF map for upstream IPv6 forwarding.
@Nullable
- private final BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
+ private final IBpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
// BPF map of tethering statistics of the upstream interface since tethering startup.
@Nullable
- private final BpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap;
+ private final IBpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap;
// BPF map of per-interface quota for tethering offload.
@Nullable
- private final BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
+ private final IBpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
// BPF map of interface index mapping for XDP.
@Nullable
- private final BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
+ private final IBpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
// Tracking IPv4 rule count while any rule is using the given upstream interfaces. Used for
// reducing the BPF map iteration query. The count is increased or decreased when the rule is
@@ -482,7 +482,7 @@
return true;
}
- private String mapStatus(BpfMap m, String name) {
+ private String mapStatus(IBpfMap m, String name) {
return name + "{" + (m != null ? "OK" : "ERROR") + "}";
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 05a2884..142a0b9 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -60,6 +60,7 @@
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.SharedLog;
@@ -320,7 +321,7 @@
}
/** Get downstream4 BPF map. */
- @Nullable public BpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
+ @Nullable public IBpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_DOWNSTREAM4_MAP_PATH,
@@ -332,7 +333,7 @@
}
/** Get upstream4 BPF map. */
- @Nullable public BpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
+ @Nullable public IBpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_UPSTREAM4_MAP_PATH,
@@ -344,7 +345,7 @@
}
/** Get downstream6 BPF map. */
- @Nullable public BpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
+ @Nullable public IBpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH,
@@ -356,7 +357,7 @@
}
/** Get upstream6 BPF map. */
- @Nullable public BpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
+ @Nullable public IBpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_UPSTREAM6_FS_PATH, BpfMap.BPF_F_RDWR,
@@ -368,7 +369,7 @@
}
/** Get stats BPF map. */
- @Nullable public BpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
+ @Nullable public IBpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_STATS_MAP_PATH,
@@ -380,7 +381,7 @@
}
/** Get limit BPF map. */
- @Nullable public BpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
+ @Nullable public IBpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_LIMIT_MAP_PATH,
@@ -392,7 +393,7 @@
}
/** Get dev BPF map. */
- @Nullable public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
+ @Nullable public IBpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
if (!isAtLeastS()) return null;
try {
return new BpfMap<>(TETHER_DEV_MAP_PATH,
@@ -1047,7 +1048,7 @@
}
}
private void dumpBpfStats(@NonNull IndentingPrintWriter pw) {
- try (BpfMap<TetherStatsKey, TetherStatsValue> map = mDeps.getBpfStatsMap()) {
+ try (IBpfMap<TetherStatsKey, TetherStatsValue> map = mDeps.getBpfStatsMap()) {
if (map == null) {
pw.println("No BPF stats map");
return;
@@ -1102,7 +1103,7 @@
}
private void dumpIpv6UpstreamRules(IndentingPrintWriter pw) {
- try (BpfMap<TetherUpstream6Key, Tether6Value> map = mDeps.getBpfUpstream6Map()) {
+ try (IBpfMap<TetherUpstream6Key, Tether6Value> map = mDeps.getBpfUpstream6Map()) {
if (map == null) {
pw.println("No IPv6 upstream");
return;
@@ -1130,7 +1131,7 @@
}
private void dumpIpv6DownstreamRules(IndentingPrintWriter pw) {
- try (BpfMap<TetherDownstream6Key, Tether6Value> map = mDeps.getBpfDownstream6Map()) {
+ try (IBpfMap<TetherDownstream6Key, Tether6Value> map = mDeps.getBpfDownstream6Map()) {
if (map == null) {
pw.println("No IPv6 downstream");
return;
@@ -1161,7 +1162,7 @@
pw.decreaseIndent();
}
- private <K extends Struct, V extends Struct> void dumpRawMap(BpfMap<K, V> map,
+ private <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
IndentingPrintWriter pw) throws ErrnoException {
if (map == null) {
pw.println("No BPF support");
@@ -1192,7 +1193,7 @@
// expected argument order.
// TODO: dump downstream4 map.
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
- try (BpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
+ try (IBpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
dumpRawMap(statsMap, pw);
} catch (ErrnoException | IOException e) {
pw.println("Error dumping stats map: " + e);
@@ -1200,7 +1201,7 @@
return;
}
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
- try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
+ try (IBpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
dumpRawMap(upstreamMap, pw);
} catch (ErrnoException | IOException e) {
pw.println("Error dumping IPv4 map: " + e);
@@ -1245,7 +1246,7 @@
}
private void dumpIpv4ForwardingRuleMap(long now, boolean downstream,
- BpfMap<Tether4Key, Tether4Value> map, IndentingPrintWriter pw) throws ErrnoException {
+ IBpfMap<Tether4Key, Tether4Value> map, IndentingPrintWriter pw) throws ErrnoException {
if (map == null) {
pw.println("No IPv4 support");
return;
@@ -1260,8 +1261,8 @@
private void dumpBpfForwardingRulesIpv4(IndentingPrintWriter pw) {
final long now = SystemClock.elapsedRealtimeNanos();
- try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map();
- BpfMap<Tether4Key, Tether4Value> downstreamMap = mDeps.getBpfDownstream4Map()) {
+ try (IBpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map();
+ IBpfMap<Tether4Key, Tether4Value> downstreamMap = mDeps.getBpfDownstream4Map()) {
pw.println("IPv4 Upstream: proto [inDstMac] iif(iface) src -> nat -> "
+ "dst [outDstMac] age");
pw.increaseIndent();
@@ -1283,7 +1284,7 @@
pw.println("No counter support");
return;
}
- try (BpfMap<S32, S32> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, BpfMap.BPF_F_RDONLY,
+ try (IBpfMap<S32, S32> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, BpfMap.BPF_F_RDONLY,
S32.class, S32.class)) {
map.forEach((k, v) -> {
@@ -1304,7 +1305,7 @@
}
private void dumpDevmap(@NonNull IndentingPrintWriter pw) {
- try (BpfMap<TetherDevKey, TetherDevValue> map = mDeps.getBpfDevMap()) {
+ try (IBpfMap<TetherDevKey, TetherDevValue> map = mDeps.getBpfDevMap()) {
if (map == null) {
pw.println("No devmap support");
return;
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 880a285..f486c1e 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -16,6 +16,7 @@
package android.net;
+import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
@@ -75,6 +76,7 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.PacketBuilder;
@@ -139,6 +141,9 @@
// Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
// See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
private static final int UDP_STREAM_TS_MS = 2000;
+ // Give slack time for waiting UDP stream mode because handling conntrack event in user space
+ // may not in precise time. Used to reduce the flaky rate.
+ private static final int UDP_STREAM_SLACK_MS = 500;
// Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
private static final int RX_UDP_PACKET_SIZE = 30;
private static final int RX_UDP_PACKET_COUNT = 456;
@@ -148,7 +153,7 @@
private static final long WAIT_RA_TIMEOUT_MS = 2000;
private static final MacAddress TEST_MAC = MacAddress.fromString("1:2:3:4:5:6");
- private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
+ private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/24");
private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
private static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888");
@@ -311,6 +316,13 @@
}
private boolean isInterfaceForTetheringAvailable() throws Exception {
+ // Before T, all ethernet interfaces could be used for server mode. Instead of
+ // waiting timeout, just checking whether the system currently has any
+ // ethernet interface is more reliable.
+ if (!SdkLevel.isAtLeastT()) {
+ return runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS, () -> mEm.isAvailable());
+ }
+
// If previous test case doesn't release tethering interface successfully, the other tests
// after that test may be skipped as unexcepted.
// TODO: figure out a better way to check default tethering interface existenion.
@@ -1171,6 +1183,9 @@
Thread.sleep(UDP_STREAM_TS_MS);
sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
+ // Give a slack time for handling conntrack event in user space.
+ Thread.sleep(UDP_STREAM_SLACK_MS);
+
// [1] Verify IPv4 upstream rule map.
final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index a84fdd2..ae36499 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -41,6 +41,8 @@
"ctstestrunner-axt",
"junit",
"junit-params",
+ "connectivity-net-module-utils-bpf",
+ "net-utils-device-common-bpf",
],
jni_libs: [
diff --git a/Tethering/tests/mts/src/android/tethering/mts/SkDestroyListenerTest.java b/Tethering/tests/mts/src/android/tethering/mts/SkDestroyListenerTest.java
new file mode 100644
index 0000000..9494aa4
--- /dev/null
+++ b/Tethering/tests/mts/src/android/tethering/mts/SkDestroyListenerTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.tethering.mts;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.TrafficStats;
+import android.os.Build;
+import android.os.Process;
+import android.system.Os;
+import android.util.Pair;
+
+import com.android.net.module.util.BpfDump;
+import com.android.net.module.util.bpf.CookieTagMapKey;
+import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public class SkDestroyListenerTest {
+ private static final int COOKIE_TAG = 0x1234abcd;
+ private static final int SOCKET_COUNT = 100;
+ private static final int SOCKET_CLOSE_WAIT_MS = 200;
+ private static final String LINE_DELIMITER = "\\n";
+ private static final String DUMP_COMMAND = "dumpsys netstats --bpfRawMap --cookieTagMap";
+
+ private Map<CookieTagMapKey, CookieTagMapValue> parseBpfRawMap(final String dump) {
+ final Map<CookieTagMapKey, CookieTagMapValue> map = new HashMap<>();
+ for (final String line: dump.split(LINE_DELIMITER)) {
+ final Pair<CookieTagMapKey, CookieTagMapValue> keyValue =
+ BpfDump.fromBase64EncodedString(CookieTagMapKey.class,
+ CookieTagMapValue.class, line.trim());
+ map.put(keyValue.first, keyValue.second);
+ }
+ return map;
+ }
+
+ private int countTaggedSocket() {
+ final String dump = runShellCommandOrThrow(DUMP_COMMAND);
+ final Map<CookieTagMapKey, CookieTagMapValue> cookieTagMap = parseBpfRawMap(dump);
+ int count = 0;
+ for (final CookieTagMapValue value: cookieTagMap.values()) {
+ if (value.tag == COOKIE_TAG && value.uid == Process.myUid()) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private boolean noTaggedSocket() {
+ return countTaggedSocket() == 0;
+ }
+
+ private void doTestSkDestroyListener(final int family, final int type) throws Exception {
+ assertTrue("There are tagged sockets before test", noTaggedSocket());
+
+ TrafficStats.setThreadStatsTag(COOKIE_TAG);
+ final List<FileDescriptor> fds = new ArrayList<>();
+ for (int i = 0; i < SOCKET_COUNT; i++) {
+ fds.add(Os.socket(family, type, 0 /* protocol */));
+ }
+ TrafficStats.clearThreadStatsTag();
+ assertEquals("Number of tagged socket does not match after creating sockets",
+ SOCKET_COUNT, countTaggedSocket());
+
+ for (final FileDescriptor fd: fds) {
+ Os.close(fd);
+ }
+ // Wait a bit for skDestroyListener to handle all the netlink messages.
+ Thread.sleep(SOCKET_CLOSE_WAIT_MS);
+ assertTrue("There are tagged sockets after closing sockets", noTaggedSocket());
+ }
+
+ @Test
+ public void testSkDestroyListener() throws Exception {
+ doTestSkDestroyListener(AF_INET, SOCK_STREAM);
+ doTestSkDestroyListener(AF_INET, SOCK_DGRAM);
+ doTestSkDestroyListener(AF_INET6, SOCK_STREAM);
+ doTestSkDestroyListener(AF_INET6, SOCK_DGRAM);
+ }
+}
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 b100f58..758b533 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -94,8 +94,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.net.module.util.BpfMap;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.SharedLog;
@@ -362,9 +362,9 @@
@Mock private IpServer mIpServer2;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
- @Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
- @Mock private BpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
- @Mock private BpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
+ @Mock private IBpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
+ @Mock private IBpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map;
+ @Mock private IBpfMap<TetherDevKey, TetherDevValue> mBpfDevMap;
// Late init since methods must be called by the thread that created this object.
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
@@ -379,13 +379,13 @@
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
private final TestLooper mTestLooper = new TestLooper();
- private final BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map =
+ private final IBpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map =
spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class));
- private final BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map =
+ private final IBpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map =
spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class));
- private final TestBpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap =
+ private final IBpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap =
spy(new TestBpfMap<>(TetherStatsKey.class, TetherStatsValue.class));
- private final TestBpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap =
+ private final IBpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap =
spy(new TestBpfMap<>(TetherLimitKey.class, TetherLimitValue.class));
private BpfCoordinator.Dependencies mDeps =
spy(new BpfCoordinator.Dependencies() {
@@ -424,37 +424,37 @@
}
@Nullable
- public BpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
+ public IBpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
return mBpfDownstream4Map;
}
@Nullable
- public BpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
+ public IBpfMap<Tether4Key, Tether4Value> getBpfUpstream4Map() {
return mBpfUpstream4Map;
}
@Nullable
- public BpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
+ public IBpfMap<TetherDownstream6Key, Tether6Value> getBpfDownstream6Map() {
return mBpfDownstream6Map;
}
@Nullable
- public BpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
+ public IBpfMap<TetherUpstream6Key, Tether6Value> getBpfUpstream6Map() {
return mBpfUpstream6Map;
}
@Nullable
- public BpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
+ public IBpfMap<TetherStatsKey, TetherStatsValue> getBpfStatsMap() {
return mBpfStatsMap;
}
@Nullable
- public BpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
+ public IBpfMap<TetherLimitKey, TetherLimitValue> getBpfLimitMap() {
return mBpfLimitMap;
}
@Nullable
- public BpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
+ public IBpfMap<TetherDevKey, TetherDevValue> getBpfDevMap() {
return mBpfDevMap;
}
});
diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java
index da5f88d..350ed86 100644
--- a/framework-t/src/android/net/NetworkIdentity.java
+++ b/framework-t/src/android/net/NetworkIdentity.java
@@ -85,6 +85,12 @@
private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE;
+ // Need to be synchronized with ConnectivityManager.
+ // TODO: Use {@code ConnectivityManager#*} when visible.
+ static final int TYPE_TEST = 18;
+ private static final int MAX_NETWORK_TYPE = TYPE_TEST;
+ private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
+
final int mType;
final int mRatType;
final int mSubId;
@@ -346,11 +352,6 @@
* Builder class for {@link NetworkIdentity}.
*/
public static final class Builder {
- // Need to be synchronized with ConnectivityManager.
- // TODO: Use {@link ConnectivityManager#MAX_NETWORK_TYPE} when this file is in the module.
- private static final int MAX_NETWORK_TYPE = 18; // TYPE_TEST
- private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
-
private int mType;
private int mRatType;
private String mSubscriberId;
@@ -413,6 +414,12 @@
final WifiInfo info = (WifiInfo) transportInfo;
setWifiNetworkKey(info.getNetworkKey());
}
+ } else if (mType == TYPE_TEST) {
+ final NetworkSpecifier ns = snapshot.getNetworkCapabilities().getNetworkSpecifier();
+ if (ns instanceof TestNetworkSpecifier) {
+ // Reuse the wifi network key field to identify individual test networks.
+ setWifiNetworkKey(((TestNetworkSpecifier) ns).getInterfaceName());
+ }
}
return this;
}
@@ -574,7 +581,7 @@
}
// Assert non-wifi network cannot have a wifi network key.
- if (mType != TYPE_WIFI && mWifiNetworkKey != null) {
+ if (mType != TYPE_WIFI && mType != TYPE_TEST && mWifiNetworkKey != null) {
throw new IllegalArgumentException("Invalid wifi network key for type " + mType);
}
}
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index b82a126..b6bd1a5 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -50,6 +50,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkIdentityUtils;
import com.android.net.module.util.NetworkStatsUtils;
@@ -114,6 +115,14 @@
* may offer non-cellular networks like WiFi, which will be matched by this rule.
*/
public static final int MATCH_CARRIER = 10;
+ /**
+ * Match rule to match networks with {@link ConnectivityManager#TYPE_TEST} as the legacy
+ * network type.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static final int MATCH_TEST = 11;
// TODO: Remove this and replace all callers with WIFI_NETWORK_KEY_ALL.
/** @hide */
@@ -176,6 +185,7 @@
case MATCH_BLUETOOTH:
case MATCH_PROXY:
case MATCH_CARRIER:
+ case MATCH_TEST:
return true;
default:
@@ -666,6 +676,8 @@
return matchesProxy(ident);
case MATCH_CARRIER:
return matchesCarrier(ident);
+ case MATCH_TEST:
+ return matchesTest(ident);
default:
// We have no idea what kind of network template we are, so we
// just claim not to match anything.
@@ -776,6 +788,17 @@
&& CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId);
}
+ /**
+ * Check if matches test network. If the wifiNetworkKeys in the template is specified, Then it
+ * will only match a network containing any of the specified the wifi network key. Otherwise,
+ * all test networks would be matched.
+ */
+ private boolean matchesTest(NetworkIdentity ident) {
+ return ident.mType == NetworkIdentity.TYPE_TEST
+ && ((CollectionUtils.isEmpty(mMatchWifiNetworkKeys)
+ || CollectionUtils.contains(mMatchWifiNetworkKeys, ident.mWifiNetworkKey)));
+ }
+
private boolean matchesMobileWildcard(NetworkIdentity ident) {
if (ident.mType == TYPE_WIMAX) {
return true;
@@ -829,6 +852,8 @@
return "PROXY";
case MATCH_CARRIER:
return "CARRIER";
+ case MATCH_TEST:
+ return "TEST";
default:
return "UNKNOWN(" + matchRule + ")";
}
@@ -1079,7 +1104,9 @@
}
private void validateWifiNetworkKeys() {
- if (mMatchRule != MATCH_WIFI && !mMatchWifiNetworkKeys.isEmpty()) {
+ // Also allow querying test networks which use wifi network key as identifier.
+ if (mMatchRule != MATCH_WIFI && mMatchRule != MATCH_TEST
+ && !mMatchWifiNetworkKeys.isEmpty()) {
throw new IllegalArgumentException("Trying to build non wifi match rule: "
+ mMatchRule + " with wifi network keys");
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 1fbbd25..547b4ba 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -1167,6 +1167,8 @@
return "PROXY";
case TYPE_VPN:
return "VPN";
+ case TYPE_TEST:
+ return "TEST";
default:
return Integer.toString(type);
}
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index b6f3314..da12a0a 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -188,7 +188,8 @@
* Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
* Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
*
- * This is not parceled, because it would not make sense.
+ * This is not parceled, because it would not make sense. It's also ignored by the
+ * equals() and hashcode() methods.
*
* @hide
*/
@@ -503,8 +504,10 @@
&& provisioningNotificationDisabled == that.provisioningNotificationDisabled
&& skip464xlat == that.skip464xlat
&& legacyType == that.legacyType
+ && legacySubType == that.legacySubType
&& Objects.equals(subscriberId, that.subscriberId)
&& Objects.equals(legacyTypeName, that.legacyTypeName)
+ && Objects.equals(legacySubTypeName, that.legacySubTypeName)
&& Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo)
&& excludeLocalRouteVpn == that.excludeLocalRouteVpn
&& mVpnRequiresValidation == that.mVpnRequiresValidation;
@@ -514,8 +517,8 @@
public int hashCode() {
return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
- skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo, excludeLocalRouteVpn,
- mVpnRequiresValidation);
+ skip464xlat, legacyType, legacySubType, legacyTypeName, legacySubTypeName,
+ mLegacyExtraInfo, excludeLocalRouteVpn, mVpnRequiresValidation);
}
@Override
@@ -529,8 +532,10 @@
+ ", subscriberId = '" + subscriberId + '\''
+ ", skip464xlat = " + skip464xlat
+ ", legacyType = " + legacyType
+ + ", legacySubType = " + legacySubType
+ ", hasShownBroken = " + hasShownBroken
+ ", legacyTypeName = '" + legacyTypeName + '\''
+ + ", legacySubTypeName = '" + legacySubTypeName + '\''
+ ", legacyExtraInfo = '" + mLegacyExtraInfo + '\''
+ ", excludeLocalRouteVpn = '" + excludeLocalRouteVpn + '\''
+ ", vpnRequiresValidation = '" + mVpnRequiresValidation + '\''
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 994db1d..3f7ed2a 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -110,12 +110,12 @@
}
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(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ // initialized last so mCookieTagMap.isValid() implies everything else is valid too
+ RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH));
ALOGI("%s successfully", __func__);
return netdutils::status::ok;
@@ -133,7 +133,6 @@
}
int BpfHandler::tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
- std::lock_guard guard(mMutex);
if (!mCookieTagMap.isValid()) return -EPERM;
if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) return -EPERM;
@@ -185,9 +184,9 @@
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.
+ // the request to prevent the map from overflow. Note though that it isn't really
+ // safe here to iterate over the map since it might be modified by the system server,
+ // which might toggle the live stats map and clean it.
const auto countUidStatsEntries = [chargeUid, &totalEntryCount, &perUidEntryCount](
const StatsKey& key,
const BpfMap<StatsKey, StatsValue>&) {
@@ -227,9 +226,9 @@
}
// 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
+ // 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.
+ // should be fine to concurrently 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()),
@@ -240,8 +239,6 @@
}
int BpfHandler::untagSocket(int sockFd) {
- std::lock_guard guard(mMutex);
-
uint64_t sock_cookie = getSocketCookie(sockFd);
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
index 5ee04d1..925a725 100644
--- a/netd/BpfHandler.h
+++ b/netd/BpfHandler.h
@@ -16,8 +16,6 @@
#pragma once
-#include <mutex>
-
#include <netdutils/Status.h>
#include "bpf/BpfMap.h"
#include "bpf_shared.h"
@@ -66,8 +64,6 @@
BpfMapRO<uint32_t, uint32_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;
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
index 99160da..f5c9a68 100644
--- a/netd/BpfHandlerTest.cpp
+++ b/netd/BpfHandlerTest.cpp
@@ -53,7 +53,6 @@
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
void SetUp() {
- std::lock_guard guard(mBh.mMutex);
ASSERT_EQ(0, setrlimitForTest());
mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 6605428..28de881 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -40,10 +40,6 @@
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);
@@ -171,30 +167,42 @@
int limitUid) {
static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
static BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
+ static BpfMap<StatsKey, StatsValue> statsMapA(STATS_MAP_A_PATH);
+ static BpfMap<StatsKey, StatsValue> statsMapB(STATS_MAP_B_PATH);
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();
}
- if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+ // The target map for stats reading should be the inactive map, which is opposite
+ // from the config value.
+ BpfMap<StatsKey, StatsValue> *inactiveStatsMap;
+ switch (configuration.value()) {
+ case SELECT_MAP_A:
+ inactiveStatsMap = &statsMapB;
+ break;
+ case SELECT_MAP_B:
+ inactiveStatsMap = &statsMapA;
+ break;
+ default:
ALOGE("%s unknown configuration value: %d", __func__, configuration.value());
return -EINVAL;
}
- const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
- // TODO: fix this to not constantly reopen the bpf map
- BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
// 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);
+ // TODO: the above comment feels like it may be obsolete / out of date,
+ // since we no longer swap the map via netd binder rpc - though we do
+ // still swap it.
+ int ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid,
+ *inactiveStatsMap, ifaceIndexNameMap);
if (ret) {
ALOGE("parse detail network stats failed: %s", strerror(errno));
return ret;
}
- Result<void> res = statsMap.clear();
+ Result<void> res = inactiveStatsMap->clear();
if (!res.ok()) {
ALOGE("Clean up current stats map failed: %s", strerror(res.error().code()));
return -res.error().code();
diff --git a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
index 17abbab..156b526 100644
--- a/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
+++ b/service-t/src/com/android/server/ethernet/EthernetConfigStore.java
@@ -116,6 +116,10 @@
}
public void write(String iface, IpConfiguration config) {
+ final File directory = new File(APEX_IP_CONFIG_FILE_PATH);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
write(iface, config, APEX_IP_CONFIG_FILE_PATH + CONFIG_FILE);
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index c9d1718..8161f50 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -296,6 +296,16 @@
return mTunAnd464xlatAdjustedStats.clone();
}
+ /**
+ * Remove stats from {@code mPersistSnapshot} and {@code mTunAnd464xlatAdjustedStats} for the
+ * given uids.
+ */
+ public void removeUidsLocked(int[] uids) {
+ synchronized (mPersistentDataLock) {
+ mPersistSnapshot.removeUids(uids);
+ mTunAnd464xlatAdjustedStats.removeUids(uids);
+ }
+ }
public void assertEquals(NetworkStats expected, NetworkStats actual) {
if (expected.size() != actual.size()) {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index c4ffdec..8d5c881 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -162,11 +162,13 @@
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.NetworkStatsUtils;
import com.android.net.module.util.PermissionUtils;
+import com.android.net.module.util.SharedLog;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.server.BpfNetMaps;
import java.io.File;
import java.io.FileDescriptor;
@@ -454,6 +456,9 @@
@NonNull
private final BpfInterfaceMapUpdater mInterfaceMapUpdater;
+ @Nullable
+ private final SkDestroyListener mSkDestroyListener;
+
private static @NonNull Clock getDefaultClock() {
return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
Clock.systemUTC());
@@ -587,6 +592,18 @@
mStatsMapA = mDeps.getStatsMapA();
mStatsMapB = mDeps.getStatsMapB();
mAppUidStatsMap = mDeps.getAppUidStatsMap();
+
+ // TODO: Remove bpfNetMaps creation and always start SkDestroyListener
+ // Following code is for the experiment to verify the SkDestroyListener refactoring. Based
+ // on the experiment flag, BpfNetMaps starts C SkDestroyListener (existing code) or
+ // NetworkStatsService starts Java SkDestroyListener (new code).
+ final BpfNetMaps bpfNetMaps = mDeps.makeBpfNetMaps(mContext);
+ if (bpfNetMaps.isSkDestroyListenerRunning()) {
+ mSkDestroyListener = null;
+ } else {
+ mSkDestroyListener = mDeps.makeSkDestroyListener(mCookieTagMap, mHandler);
+ mHandler.post(mSkDestroyListener::start);
+ }
}
/**
@@ -782,6 +799,17 @@
public boolean isDebuggable() {
return Build.isDebuggable();
}
+
+ /** Create a new BpfNetMaps. */
+ public BpfNetMaps makeBpfNetMaps(Context ctx) {
+ return new BpfNetMaps(ctx);
+ }
+
+ /** Create a new SkDestroyListener. */
+ public SkDestroyListener makeSkDestroyListener(
+ IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
+ return new SkDestroyListener(cookieTagMap, handler, new SharedLog(TAG));
+ }
}
/**
@@ -2469,13 +2497,13 @@
mUidRecorder.removeUidsLocked(uids);
mUidTagRecorder.removeUidsLocked(uids);
+ mStatsFactory.removeUidsLocked(uids);
// Clear kernel stats associated with UID
for (int uid : uids) {
deleteKernelTagData(uid);
}
-
- // TODO: Remove the UID's entries from mOpenSessionCallsPerUid and
- // mOpenSessionCallsPerCaller
+ // TODO: Remove the UID's entries from mOpenSessionCallsPerUid and
+ // mOpenSessionCallsPerCaller
}
/**
@@ -2819,24 +2847,10 @@
if (mCookieTagMap == null) {
return;
}
- pw.println("mCookieTagMap:");
- pw.increaseIndent();
- try {
- mCookieTagMap.forEach((key, value) -> {
- // value could be null if there is a concurrent entry deletion.
- // http://b/220084230.
- if (value != null) {
- pw.println("cookie=" + key.socketCookie
- + " tag=0x" + Long.toHexString(value.tag)
- + " uid=" + value.uid);
- } else {
- pw.println("Entry is deleted while dumping, iterating from first entry");
- }
- });
- } catch (ErrnoException e) {
- pw.println("mCookieTagMap dump end with error: " + Os.strerror(e.errno));
- }
- pw.decreaseIndent();
+ BpfDump.dumpMap(mCookieTagMap, pw, "mCookieTagMap",
+ (key, value) -> "cookie=" + key.socketCookie
+ + " tag=0x" + Long.toHexString(value.tag)
+ + " uid=" + value.uid);
}
@GuardedBy("mStatsLock")
@@ -2844,22 +2858,8 @@
if (mUidCounterSetMap == null) {
return;
}
- pw.println("mUidCounterSetMap:");
- pw.increaseIndent();
- try {
- mUidCounterSetMap.forEach((uid, set) -> {
- // set could be null if there is a concurrent entry deletion.
- // http://b/220084230.
- if (set != null) {
- pw.println("uid=" + uid.val + " set=" + set.val);
- } else {
- pw.println("Entry is deleted while dumping, iterating from first entry");
- }
- });
- } catch (ErrnoException e) {
- pw.println("mUidCounterSetMap dump end with error: " + Os.strerror(e.errno));
- }
- pw.decreaseIndent();
+ BpfDump.dumpMap(mUidCounterSetMap, pw, "mUidCounterSetMap",
+ (uid, set) -> "uid=" + uid.val + " set=" + set.val);
}
@GuardedBy("mStatsLock")
@@ -2867,27 +2867,13 @@
if (mAppUidStatsMap == null) {
return;
}
- pw.println("mAppUidStatsMap:");
- pw.increaseIndent();
- pw.println("uid rxBytes rxPackets txBytes txPackets");
- try {
- mAppUidStatsMap.forEach((key, value) -> {
- // value could be null if there is a concurrent entry deletion.
- // http://b/220084230.
- if (value != null) {
- pw.println(key.uid + " "
- + value.rxBytes + " "
- + value.rxPackets + " "
- + value.txBytes + " "
- + value.txPackets);
- } else {
- pw.println("Entry is deleted while dumping, iterating from first entry");
- }
- });
- } catch (ErrnoException e) {
- pw.println("mAppUidStatsMap dump end with error: " + Os.strerror(e.errno));
- }
- pw.decreaseIndent();
+ BpfDump.dumpMap(mAppUidStatsMap, pw, "mAppUidStatsMap",
+ "uid rxBytes rxPackets txBytes txPackets",
+ (key, value) -> key.uid + " "
+ + value.rxBytes + " "
+ + value.rxPackets + " "
+ + value.txBytes + " "
+ + value.txPackets);
}
private NetworkStats readNetworkStatsSummaryDev() {
diff --git a/service-t/src/com/android/server/net/SkDestroyListener.java b/service-t/src/com/android/server/net/SkDestroyListener.java
new file mode 100644
index 0000000..7b68f89
--- /dev/null
+++ b/service-t/src/com/android/server/net/SkDestroyListener.java
@@ -0,0 +1,75 @@
+/*
+ * 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 android.system.OsConstants.NETLINK_INET_DIAG;
+
+import android.os.Handler;
+import android.system.ErrnoException;
+
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.bpf.CookieTagMapKey;
+import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.net.module.util.ip.NetlinkMonitor;
+import com.android.net.module.util.netlink.InetDiagMessage;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.StructInetDiagSockId;
+
+/**
+ * Monitor socket destroy and delete entry from cookie tag bpf map.
+ */
+public class SkDestroyListener extends NetlinkMonitor {
+ private static final int SKNLGRP_INET_TCP_DESTROY = 1;
+ private static final int SKNLGRP_INET_UDP_DESTROY = 2;
+ private static final int SKNLGRP_INET6_TCP_DESTROY = 3;
+ private static final int SKNLGRP_INET6_UDP_DESTROY = 4;
+
+ // TODO: if too many sockets are closed too quickly, this can overflow the socket buffer, and
+ // some entries in mCookieTagMap will not be freed. In order to fix this it would be needed to
+ // periodically dump all sockets and remove the tag entries for sockets that have been closed.
+ // For now, set a large-enough buffer that hundreds of sockets can be closed without getting
+ // ENOBUFS and leaking mCookieTagMap entries.
+ private static final int SOCK_RCV_BUF_SIZE = 512 * 1024;
+
+ private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap;
+
+ SkDestroyListener(final IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap,
+ final Handler handler, final SharedLog log) {
+ super(handler, log, "SkDestroyListener", NETLINK_INET_DIAG,
+ 1 << (SKNLGRP_INET_TCP_DESTROY - 1)
+ | 1 << (SKNLGRP_INET_UDP_DESTROY - 1)
+ | 1 << (SKNLGRP_INET6_TCP_DESTROY - 1)
+ | 1 << (SKNLGRP_INET6_UDP_DESTROY - 1),
+ SOCK_RCV_BUF_SIZE);
+ mCookieTagMap = cookieTagMap;
+ }
+
+ @Override
+ public void processNetlinkMessage(final NetlinkMessage nlMsg, final long whenMs) {
+ if (!(nlMsg instanceof InetDiagMessage)) {
+ mLog.e("Received non InetDiagMessage");
+ return;
+ }
+ final StructInetDiagSockId sockId = ((InetDiagMessage) nlMsg).inetDiagMsg.id;
+ try {
+ mCookieTagMap.deleteEntry(new CookieTagMapKey(sockId.cookie));
+ } catch (ErrnoException e) {
+ mLog.e("Failed to delete CookieTagMap entry for " + sockId.cookie + ": " + e);
+ }
+ }
+}
diff --git a/service/Android.bp b/service/Android.bp
index b68d389..68c1722 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -143,6 +143,7 @@
"src/**/*.java",
":framework-connectivity-shared-srcs",
":services-connectivity-shared-srcs",
+ ":statslog-connectivity-java-gen",
],
libs: [
"framework-annotations-lib",
@@ -152,6 +153,7 @@
"framework-wifi.stubs.module_lib",
"unsupportedappusage",
"ServiceConnectivityResources",
+ "framework-statsd.stubs.module_lib",
],
static_libs: [
// Do not add libs here if they are already included
@@ -332,3 +334,10 @@
"--output $(out)",
visibility: ["//visibility:private"],
}
+
+genrule {
+ name: "statslog-connectivity-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module connectivity --javaPackage com.android.server --javaClass ConnectivityStatsLog",
+ out: ["com/android/server/ConnectivityStatsLog.java"],
+}
diff --git a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
index fdca468..b24dee0 100644
--- a/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-es-rUS/strings.xml
@@ -22,7 +22,7 @@
<string name="network_available_sign_in" msgid="2622520134876355561">"Acceder a la red"</string>
<!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
- <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g>no tiene acceso a Internet"</string>
+ <string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> no tiene acceso a Internet"</string>
<string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Presiona para ver opciones"</string>
<string name="mobile_no_internet" msgid="4087718456753201450">"La red móvil no tiene acceso a Internet"</string>
<string name="other_networks_no_internet" msgid="5693932964749676542">"La red no tiene acceso a Internet"</string>
diff --git a/service/ServiceConnectivityResources/res/values-nb/strings.xml b/service/ServiceConnectivityResources/res/values-nb/strings.xml
index 00a0728..4439048 100644
--- a/service/ServiceConnectivityResources/res/values-nb/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-nb/strings.xml
@@ -34,7 +34,7 @@
<string name="network_switch_metered_toast" msgid="70691146054130335">"Byttet fra <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> til <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
<item msgid="3004933964374161223">"mobildata"</item>
- <item msgid="5624324321165953608">"Wi-Fi"</item>
+ <item msgid="5624324321165953608">"Wifi"</item>
<item msgid="5667906231066981731">"Bluetooth"</item>
<item msgid="346574747471703768">"Ethernet"</item>
<item msgid="5734728378097476003">"VPN"</item>
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 71fa8e4..799ac5c 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -47,8 +47,8 @@
ALOGE("%s failed, error code = %d", __func__, status.code()); \
} while (0)
-static void native_init(JNIEnv* env, jclass clazz) {
- Status status = mTc.start();
+static void native_init(JNIEnv* env, jclass clazz, jboolean startSkDestroyListener) {
+ Status status = mTc.start(startSkDestroyListener);
CHECK_LOG(status);
if (!isOk(status)) {
uid_t uid = getuid();
@@ -201,7 +201,7 @@
// clang-format off
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- {"native_init", "()V",
+ {"native_init", "(Z)V",
(void*)native_init},
{"native_addNaughtyApp", "(I)I",
(void*)native_addNaughtyApp},
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index a26d1e6..7fb1b34 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -181,9 +181,13 @@
return netdutils::status::ok;
}
-Status TrafficController::start() {
+Status TrafficController::start(bool startSkDestroyListener) {
RETURN_IF_NOT_OK(initMaps());
+ if (!startSkDestroyListener) {
+ return netdutils::status::ok;
+ }
+
auto result = makeSkDestroyListener();
if (!isOk(result)) {
ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index 8512929..b44d795 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -38,7 +38,7 @@
/*
* Initialize the whole controller
*/
- netdutils::Status start();
+ netdutils::Status start(bool startSkDestroyListener);
/*
* Swap the stats map config from current active stats map to the idle one.
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 6bb8115..bfa0808 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -33,6 +33,9 @@
import static android.system.OsConstants.ENOENT;
import static android.system.OsConstants.EOPNOTSUPP;
+import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
+
+import android.app.StatsManager;
import android.content.Context;
import android.net.INetd;
import android.os.RemoteException;
@@ -42,16 +45,23 @@
import android.system.Os;
import android.util.ArraySet;
import android.util.Log;
+import android.util.StatsEvent;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.BackgroundThread;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.Struct.U8;
+import com.android.net.module.util.bpf.CookieTagMapKey;
+import com.android.net.module.util.bpf.CookieTagMapValue;
import java.io.FileDescriptor;
import java.io.IOException;
+import java.util.List;
import java.util.Set;
/**
@@ -93,16 +103,19 @@
"/sys/fs/bpf/netd_shared/map_netd_uid_owner_map";
private static final String UID_PERMISSION_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_uid_permission_map";
+ private static final String COOKIE_TAG_MAP_PATH =
+ "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map";
private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
private static final U32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new U32(1);
private static final long UID_RULES_DEFAULT_CONFIGURATION = 0;
private static final long STATS_SELECT_MAP_A = 0;
private static final long STATS_SELECT_MAP_B = 1;
- private static BpfMap<U32, U32> sConfigurationMap = null;
+ private static IBpfMap<U32, U32> sConfigurationMap = null;
// BpfMap for UID_OWNER_MAP_PATH. This map is not accessed by others.
- private static BpfMap<U32, UidOwnerValue> sUidOwnerMap = null;
- private static BpfMap<U32, U8> sUidPermissionMap = null;
+ private static IBpfMap<U32, UidOwnerValue> sUidOwnerMap = null;
+ private static IBpfMap<U32, U8> sUidPermissionMap = null;
+ private static IBpfMap<CookieTagMapKey, CookieTagMapValue> sCookieTagMap = null;
// LINT.IfChange(match_type)
@VisibleForTesting public static final long NO_MATCH = 0;
@@ -132,7 +145,7 @@
* Set configurationMap for test.
*/
@VisibleForTesting
- public static void setConfigurationMapForTest(BpfMap<U32, U32> configurationMap) {
+ public static void setConfigurationMapForTest(IBpfMap<U32, U32> configurationMap) {
sConfigurationMap = configurationMap;
}
@@ -140,7 +153,7 @@
* Set uidOwnerMap for test.
*/
@VisibleForTesting
- public static void setUidOwnerMapForTest(BpfMap<U32, UidOwnerValue> uidOwnerMap) {
+ public static void setUidOwnerMapForTest(IBpfMap<U32, UidOwnerValue> uidOwnerMap) {
sUidOwnerMap = uidOwnerMap;
}
@@ -148,11 +161,20 @@
* Set uidPermissionMap for test.
*/
@VisibleForTesting
- public static void setUidPermissionMapForTest(BpfMap<U32, U8> uidPermissionMap) {
+ public static void setUidPermissionMapForTest(IBpfMap<U32, U8> uidPermissionMap) {
sUidPermissionMap = uidPermissionMap;
}
- private static BpfMap<U32, U32> getConfigurationMap() {
+ /**
+ * Set cookieTagMap for test.
+ */
+ @VisibleForTesting
+ public static void setCookieTagMapForTest(
+ IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap) {
+ sCookieTagMap = cookieTagMap;
+ }
+
+ private static IBpfMap<U32, U32> getConfigurationMap() {
try {
return new BpfMap<>(
CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U32.class);
@@ -161,7 +183,7 @@
}
}
- private static BpfMap<U32, UidOwnerValue> getUidOwnerMap() {
+ private static IBpfMap<U32, UidOwnerValue> getUidOwnerMap() {
try {
return new BpfMap<>(
UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, UidOwnerValue.class);
@@ -170,7 +192,7 @@
}
}
- private static BpfMap<U32, U8> getUidPermissionMap() {
+ private static IBpfMap<U32, U8> getUidPermissionMap() {
try {
return new BpfMap<>(
UID_PERMISSION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U8.class);
@@ -179,6 +201,15 @@
}
}
+ private static IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
+ try {
+ return new BpfMap<>(COOKIE_TAG_MAP_PATH, BpfMap.BPF_F_RDWR,
+ CookieTagMapKey.class, CookieTagMapValue.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open cookie tag map", e);
+ }
+ }
+
private static void initBpfMaps() {
if (sConfigurationMap == null) {
sConfigurationMap = getConfigurationMap();
@@ -208,6 +239,10 @@
if (sUidPermissionMap == null) {
sUidPermissionMap = getUidPermissionMap();
}
+
+ if (sCookieTagMap == null) {
+ sCookieTagMap = getCookieTagMap();
+ }
}
/**
@@ -224,10 +259,14 @@
Log.d(TAG, "BpfNetMaps is initialized with sEnableJavaBpfMap=" + sEnableJavaBpfMap);
initBpfMaps();
- native_init();
+ native_init(!sEnableJavaBpfMap /* startSkDestroyListener */);
sInitialized = true;
}
+ public boolean isSkDestroyListenerRunning() {
+ return !sEnableJavaBpfMap;
+ }
+
/**
* Dependencies of BpfNetMaps, for injection in tests.
*/
@@ -246,6 +285,15 @@
public int synchronizeKernelRCU() {
return native_synchronizeKernelRCU();
}
+
+ /**
+ * Build Stats Event for NETWORK_BPF_MAP_INFO atom
+ */
+ public StatsEvent buildStatsEvent(final int cookieTagMapSize, final int uidOwnerMapSize,
+ final int uidPermissionMapSize) {
+ return ConnectivityStatsLog.buildStatsEvent(NETWORK_BPF_MAP_INFO, cookieTagMapSize,
+ uidOwnerMapSize, uidPermissionMapSize);
+ }
}
/** Constructor used after T that doesn't need to use netd anymore. */
@@ -821,6 +869,43 @@
}
}
+ /** Register callback for statsd to pull atom. */
+ public void setPullAtomCallback(final Context context) {
+ throwIfPreT("setPullAtomCallback is not available on pre-T devices");
+
+ final StatsManager statsManager = context.getSystemService(StatsManager.class);
+ statsManager.setPullAtomCallback(NETWORK_BPF_MAP_INFO, null /* metadata */,
+ BackgroundThread.getExecutor(), this::pullBpfMapInfoAtom);
+ }
+
+ private <K extends Struct, V extends Struct> int getMapSize(IBpfMap<K, V> map)
+ throws ErrnoException {
+ // forEach could restart iteration from the beginning if there is a concurrent entry
+ // deletion. netd and skDestroyListener could delete CookieTagMap entry concurrently.
+ // So using Set to count the number of entry in the map.
+ Set<K> keySet = new ArraySet<>();
+ map.forEach((k, v) -> keySet.add(k));
+ return keySet.size();
+ }
+
+ /** Callback for StatsManager#setPullAtomCallback */
+ @VisibleForTesting
+ public int pullBpfMapInfoAtom(final int atomTag, final List<StatsEvent> data) {
+ if (atomTag != NETWORK_BPF_MAP_INFO) {
+ Log.e(TAG, "Unexpected atom tag: " + atomTag);
+ return StatsManager.PULL_SKIP;
+ }
+
+ try {
+ data.add(mDeps.buildStatsEvent(getMapSize(sCookieTagMap), getMapSize(sUidOwnerMap),
+ getMapSize(sUidPermissionMap)));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to pull NETWORK_BPF_MAP_INFO atom: " + e);
+ return StatsManager.PULL_SKIP;
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
/**
* Dump BPF maps
*
@@ -838,7 +923,7 @@
native_dump(fd, verbose);
}
- private static native void native_init();
+ private static native void native_init(boolean startSkDestroyListener);
private native int native_addNaughtyApp(int uid);
private native int native_removeNaughtyApp(int uid);
private native int native_addNiceApp(int uid);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 359a3bd..7e63761 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1744,7 +1744,8 @@
synchronized (mNetworkForNetId) {
for (int i = 0; i < mNetworkForNetId.size(); i++) {
final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
- if (nai.isVPN() && nai.everConnected && nai.networkCapabilities.appliesToUid(uid)) {
+ if (nai.isVPN() && nai.everConnected()
+ && nai.networkCapabilities.appliesToUid(uid)) {
return nai;
}
}
@@ -2478,7 +2479,7 @@
final ArrayList<NetworkStateSnapshot> result = new ArrayList<>();
for (Network network : getAllNetworks()) {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- if (nai != null && nai.everConnected) {
+ if (nai != null && nai.everConnected()) {
// TODO (b/73321673) : NetworkStateSnapshot contains a copy of the
// NetworkCapabilities, which may contain UIDs of apps to which the
// network applies. Should the UIDs be cleared so as not to leak or
@@ -3039,6 +3040,11 @@
if (!ConnectivitySettingsManager.getMobileDataPreferredUids(mContext).isEmpty()) {
updateMobileDataPreferredUids();
}
+
+ // On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom
+ if (SdkLevel.isAtLeastT()) {
+ mBpfNetMaps.setPullAtomCallback(mContext);
+ }
}
/**
@@ -3531,7 +3537,7 @@
}
// If the network has been destroyed, the only thing that it can do is disconnect.
- if (nai.destroyed && !isDisconnectRequest(msg)) {
+ if (nai.isDestroyed() && !isDisconnectRequest(msg)) {
return;
}
@@ -3560,7 +3566,7 @@
break;
}
case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
- if (nai.everConnected) {
+ if (nai.everConnected()) {
loge("ERROR: cannot call explicitlySelected on already-connected network");
// Note that if the NAI had been connected, this would affect the
// score, and therefore would require re-mixing the score and performing
@@ -3690,7 +3696,7 @@
final int netId = msg.arg2;
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
// If a network has already been destroyed, all NetworkMonitor updates are ignored.
- if (nai != null && nai.destroyed) return true;
+ if (nai != null && nai.isDestroyed()) return true;
switch (msg.what) {
default:
return false;
@@ -3739,12 +3745,10 @@
case EVENT_PROVISIONING_NOTIFICATION: {
final boolean visible = toBool(msg.arg1);
// If captive portal status has changed, update capabilities or disconnect.
- if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
- nai.lastCaptivePortalDetected = visible;
- nai.everCaptivePortalDetected |= visible;
- if (nai.lastCaptivePortalDetected &&
- ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
- == getCaptivePortalMode()) {
+ if (nai != null && (visible != nai.captivePortalDetected())) {
+ nai.setCaptivePortalDetected(visible);
+ if (visible && ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
+ == getCaptivePortalMode()) {
if (DBG) log("Avoiding captive portal network: " + nai.toShortString());
nai.onPreventAutomaticReconnect();
teardownUnneededNetwork(nai);
@@ -3796,11 +3800,10 @@
return;
}
- final boolean wasValidated = nai.lastValidated;
- final boolean wasPartial = nai.partialConnectivity;
- nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
- final boolean partialConnectivityChanged =
- (wasPartial != nai.partialConnectivity);
+ final boolean wasValidated = nai.isValidated();
+ final boolean wasPartial = nai.partialConnectivity();
+ nai.setPartialConnectivity((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
+ final boolean partialConnectivityChanged = (wasPartial != nai.partialConnectivity());
if (DBG) {
final String logMsg = !TextUtils.isEmpty(redirectUrl)
@@ -3808,10 +3811,9 @@
: "";
log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg);
}
- if (valid != nai.lastValidated) {
+ if (valid != nai.isValidated()) {
final FullScore oldScore = nai.getScore();
- nai.lastValidated = valid;
- nai.everValidated |= valid;
+ nai.setValidated(valid);
updateCapabilities(oldScore, nai, nai.networkCapabilities);
if (valid) {
handleFreshlyValidatedNetwork(nai);
@@ -3844,13 +3846,13 @@
// EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
// immediately. Re-notify partial connectivity silently if no internet
// notification already there.
- if (!wasPartial && nai.partialConnectivity) {
+ if (!wasPartial && nai.partialConnectivity()) {
// Remove delayed message if there is a pending message.
mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
handlePromptUnvalidated(nai.network);
}
- if (wasValidated && !nai.lastValidated) {
+ if (wasValidated && !nai.isValidated()) {
handleNetworkUnvalidated(nai);
}
}
@@ -4197,7 +4199,7 @@
}
private static boolean shouldDestroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
- return nai.created && !nai.destroyed;
+ return nai.isCreated() && !nai.isDestroyed();
}
private boolean shouldIgnoreValidationFailureAfterRoam(NetworkAgentInfo nai) {
@@ -4207,8 +4209,8 @@
R.integer.config_validationFailureAfterRoamIgnoreTimeMillis));
if (blockTimeOut <= MAX_VALIDATION_FAILURE_BLOCKING_TIME_MS
&& blockTimeOut >= 0) {
- final long currentTimeMs = SystemClock.elapsedRealtime();
- long timeSinceLastRoam = currentTimeMs - nai.lastRoamTimestamp;
+ final long currentTimeMs = SystemClock.elapsedRealtime();
+ long timeSinceLastRoam = currentTimeMs - nai.lastRoamTime;
if (timeSinceLastRoam <= blockTimeOut) {
log ("blocked because only " + timeSinceLastRoam + "ms after roam");
return true;
@@ -4312,7 +4314,7 @@
}
// Delayed teardown.
- if (nai.created) {
+ if (nai.isCreated()) {
try {
mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM);
} catch (RemoteException e) {
@@ -4333,7 +4335,7 @@
// for an unnecessarily long time.
destroyNativeNetwork(nai);
}
- if (!nai.created && !SdkLevel.isAtLeastT()) {
+ if (!nai.isCreated() && !SdkLevel.isAtLeastT()) {
// Backwards compatibility: send onNetworkDestroyed even if network was never created.
// This can never run if the code above runs because shouldDestroyNativeNetwork is
// false if the network was never created.
@@ -4394,11 +4396,11 @@
mDnsManager.removeNetwork(nai.network);
// clean up tc police filters on interface.
- if (nai.everConnected && canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) {
+ if (nai.everConnected() && canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) {
mDeps.disableIngressRateLimit(nai.linkProperties.getInterfaceName());
}
- nai.destroyed = true;
+ nai.setDestroyed();
nai.onNetworkDestroyed();
}
@@ -4527,7 +4529,7 @@
private boolean unneeded(NetworkAgentInfo nai, UnneededFor reason) {
ensureRunningOnConnectivityServiceThread();
- if (!nai.everConnected || nai.isVPN() || nai.isInactive()
+ if (!nai.everConnected() || nai.isVPN() || nai.isInactive()
|| nai.getScore().getKeepConnectedReason() != NetworkScore.KEEP_CONNECTED_NONE) {
return false;
}
@@ -4582,7 +4584,7 @@
if (req.isListen() || req.isListenForBest()) {
continue;
}
- // If this Network is already the highest scoring Network for a request, or if
+ // If this Network is already the best Network for a request, or if
// there is hope for it to become one if it validated, then it is needed.
if (candidate.satisfies(req)) {
// As soon as a network is found that satisfies a request, return. Specifically for
@@ -4859,7 +4861,7 @@
return;
}
- if (nai.everValidated) {
+ if (nai.everValidated()) {
// The network validated while the dialog box was up. Take no action.
return;
}
@@ -4904,7 +4906,7 @@
return;
}
- if (nai.lastValidated) {
+ if (nai.isValidated()) {
// The network validated while the dialog box was up. Take no action.
return;
}
@@ -4936,12 +4938,12 @@
private void handleSetAvoidUnvalidated(Network network) {
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- if (nai == null || nai.lastValidated) {
+ if (nai == null || nai.isValidated()) {
// Nothing to do. The network either disconnected or revalidated.
return;
}
- if (!nai.avoidUnvalidated) {
- nai.avoidUnvalidated = true;
+ if (0L == nai.getAvoidUnvalidated()) {
+ nai.setAvoidUnvalidated();
nai.updateScoreForNetworkAgentUpdate();
rematchAllNetworksAndRequests();
}
@@ -5091,7 +5093,7 @@
pw.println("Network overrides:");
pw.increaseIndent();
for (NetworkAgentInfo nai : networksSortedById()) {
- if (nai.avoidUnvalidated) {
+ if (0L != nai.getAvoidUnvalidated()) {
pw.println(nai.toShortString());
}
}
@@ -5162,7 +5164,7 @@
private boolean shouldPromptUnvalidated(NetworkAgentInfo nai) {
// Don't prompt if the network is validated, and don't prompt on captive portals
// because we're already prompting the user to sign in.
- if (nai.everValidated || nai.everCaptivePortalDetected) {
+ if (nai.everValidated() || nai.everCaptivePortalDetected()) {
return false;
}
@@ -5170,8 +5172,8 @@
// partial connectivity and selected don't ask again. This ensures that if the device
// automatically connects to a network that has partial Internet access, the user will
// always be able to use it, either because they've already chosen "don't ask again" or
- // because we have prompt them.
- if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) {
+ // because we have prompted them.
+ if (nai.partialConnectivity() && !nai.networkAgentConfig.acceptPartialConnectivity) {
return true;
}
@@ -5203,7 +5205,7 @@
// TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when
// NetworkMonitor detects the network is partial connectivity. Need to change the design to
// popup the notification immediately when the network is partial connectivity.
- if (nai.partialConnectivity) {
+ if (nai.partialConnectivity()) {
showNetworkNotification(nai, NotificationType.PARTIAL_CONNECTIVITY);
} else {
showNetworkNotification(nai, NotificationType.NO_INTERNET);
@@ -5557,7 +5559,7 @@
return;
}
// Revalidate if the app report does not match our current validated state.
- if (hasConnectivity == nai.lastValidated) {
+ if (hasConnectivity == nai.isValidated()) {
mConnectivityDiagnosticsHandler.sendMessage(
mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
@@ -5571,7 +5573,7 @@
}
// Validating a network that has not yet connected could result in a call to
// rematchNetworkAndRequests() which is not meant to work on such networks.
- if (!nai.everConnected) {
+ if (!nai.everConnected()) {
return;
}
final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
@@ -7750,7 +7752,7 @@
@NonNull final NetworkCapabilities newNc) {
final int oldPermission = getNetworkPermission(nai.networkCapabilities);
final int newPermission = getNetworkPermission(newNc);
- if (oldPermission != newPermission && nai.created && !nai.isVPN()) {
+ if (oldPermission != newPermission && nai.isCreated() && !nai.isVPN()) {
try {
mNetd.networkSetPermissionForNetwork(nai.network.getNetId(), newPermission);
} catch (RemoteException | ServiceSpecificException e) {
@@ -7840,9 +7842,9 @@
// causing a connect/teardown loop.
// TODO: remove this altogether and make it the responsibility of the NetworkProviders to
// avoid connect/teardown loops.
- if (nai.everConnected &&
- !nai.isVPN() &&
- !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) {
+ if (nai.everConnected()
+ && !nai.isVPN()
+ && !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) {
// TODO: consider not complaining when a network agent degrades its capabilities if this
// does not cause any request (that is not a listen) currently matching that agent to
// stop being matched by the updated agent.
@@ -7854,12 +7856,12 @@
// Don't modify caller's NetworkCapabilities.
final NetworkCapabilities newNc = new NetworkCapabilities(nc);
- if (nai.lastValidated) {
+ if (nai.isValidated()) {
newNc.addCapability(NET_CAPABILITY_VALIDATED);
} else {
newNc.removeCapability(NET_CAPABILITY_VALIDATED);
}
- if (nai.lastCaptivePortalDetected) {
+ if (nai.captivePortalDetected()) {
newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
} else {
newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
@@ -7869,7 +7871,7 @@
} else {
newNc.addCapability(NET_CAPABILITY_FOREGROUND);
}
- if (nai.partialConnectivity) {
+ if (nai.partialConnectivity()) {
newNc.addCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
} else {
newNc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
@@ -8116,7 +8118,7 @@
// that happens to prevent false alarms.
final Set<UidRange> prevUids = prevNc == null ? null : prevNc.getUidRanges();
final Set<UidRange> newUids = newNc == null ? null : newNc.getUidRanges();
- if (nai.isVPN() && nai.everConnected && !UidRange.hasSameUids(prevUids, newUids)
+ if (nai.isVPN() && nai.everConnected() && !UidRange.hasSameUids(prevUids, newUids)
&& (nai.linkProperties.getHttpProxy() != null || isProxySetOnAnyDefaultNetwork())) {
mProxyTracker.sendProxyBroadcast();
}
@@ -8236,8 +8238,8 @@
}
if (VDBG || DDBG) {
log("Update of LinkProperties for " + nai.toShortString()
- + "; created=" + nai.created
- + "; everConnected=" + nai.everConnected);
+ + "; created=" + nai.getCreatedTime()
+ + "; firstConnected=" + nai.getConnectedTime());
}
// TODO: eliminate this defensive copy after confirming that updateLinkProperties does not
// modify its oldLp parameter.
@@ -8669,7 +8671,7 @@
}
previousSatisfier.removeRequest(previousRequest.requestId);
if (canSupportGracefulNetworkSwitch(previousSatisfier, newSatisfier)
- && !previousSatisfier.destroyed) {
+ && !previousSatisfier.isDestroyed()) {
// If this network switch can't be supported gracefully, the request is not
// lingered. This allows letting go of the network sooner to reclaim some
// performance on the new network, since the radio can't do both at the same
@@ -8937,7 +8939,7 @@
// The new default network can be newly null if and only if the old default
// network doesn't satisfy the default request any more because it lost a
// capability.
- mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
+ mDefaultInetConditionPublished = newDefaultNetwork.isValidated() ? 100 : 0;
mLegacyTypeTracker.add(
newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
}
@@ -8958,7 +8960,7 @@
// they may get old info. Reverse this after the old startUsing api is removed.
// This is on top of the multiple intent sequencing referenced in the todo above.
for (NetworkAgentInfo nai : nais) {
- if (nai.everConnected) {
+ if (nai.everConnected()) {
addNetworkToLegacyTypeTracker(nai);
}
}
@@ -9084,12 +9086,12 @@
private void updateInetCondition(NetworkAgentInfo nai) {
// Don't bother updating until we've graduated to validated at least once.
- if (!nai.everValidated) return;
+ if (!nai.everValidated()) return;
// For now only update icons for the default connection.
// TODO: Update WiFi and cellular icons separately. b/17237507
if (!isDefaultNetwork(nai)) return;
- int newInetCondition = nai.lastValidated ? 100 : 0;
+ int newInetCondition = nai.isValidated() ? 100 : 0;
// Don't repeat publish.
if (newInetCondition == mDefaultInetConditionPublished) return;
@@ -9116,7 +9118,7 @@
// SUSPENDED state is currently only overridden from CONNECTED state. In the case the
// network agent is created, then goes to suspended, then goes out of suspended without
// ever setting connected. Check if network agent is ever connected to update the state.
- newInfo.setDetailedState(nai.everConnected
+ newInfo.setDetailedState(nai.everConnected()
? NetworkInfo.DetailedState.CONNECTED
: NetworkInfo.DetailedState.CONNECTING,
info.getReason(),
@@ -9141,7 +9143,7 @@
+ oldInfo.getState() + " to " + state);
}
- if (!networkAgent.created
+ if (!networkAgent.isCreated()
&& (state == NetworkInfo.State.CONNECTED
|| (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) {
@@ -9155,13 +9157,13 @@
// anything happens to the network.
updateCapabilitiesForNetwork(networkAgent);
}
- networkAgent.created = true;
+ networkAgent.setCreated();
networkAgent.onNetworkCreated();
updateAllowedUids(networkAgent, null, networkAgent.networkCapabilities);
}
- if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
- networkAgent.everConnected = true;
+ if (!networkAgent.everConnected() && state == NetworkInfo.State.CONNECTED) {
+ networkAgent.setConnected();
// NetworkCapabilities need to be set before sending the private DNS config to
// NetworkMonitor, otherwise NetworkMonitor cannot determine if validation is required.
@@ -9245,8 +9247,8 @@
// TODO(b/122649188): send the broadcast only to VPN users.
mProxyTracker.sendProxyBroadcast();
}
- } else if (networkAgent.created && (oldInfo.getState() == NetworkInfo.State.SUSPENDED ||
- state == NetworkInfo.State.SUSPENDED)) {
+ } else if (networkAgent.isCreated() && (oldInfo.getState() == NetworkInfo.State.SUSPENDED
+ || state == NetworkInfo.State.SUSPENDED)) {
mLegacyTypeTracker.update(networkAgent);
}
}
@@ -9674,7 +9676,7 @@
return;
}
if (!TextUtils.equals(((WifiInfo)prevInfo).getBSSID(), ((WifiInfo)newInfo).getBSSID())) {
- nai.lastRoamTimestamp = SystemClock.elapsedRealtime();
+ nai.lastRoamTime = SystemClock.elapsedRealtime();
}
}
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index c4754eb..22a820b 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -23,7 +23,6 @@
import static android.net.NetworkScore.KEEP_CONNECTED_NONE;
import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
@@ -35,8 +34,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.MessageUtils;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.StringJoiner;
/**
@@ -49,53 +46,44 @@
public class FullScore {
private static final String TAG = FullScore.class.getSimpleName();
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"POLICY_"}, value = {
- POLICY_IS_VALIDATED,
- POLICY_IS_VPN,
- POLICY_EVER_USER_SELECTED,
- POLICY_ACCEPT_UNVALIDATED,
- POLICY_IS_UNMETERED
- })
- public @interface Policy {
- }
-
// Agent-managed policies are in NetworkScore. They start from 1.
// CS-managed policies, counting from 63 downward
// This network is validated. CS-managed because the source of truth is in NetworkCapabilities.
/** @hide */
public static final int POLICY_IS_VALIDATED = 63;
+ // This network has been validated at least once since it was connected.
+ /** @hide */
+ public static final int POLICY_EVER_VALIDATED = 62;
+
// This is a VPN and behaves as one for scoring purposes.
/** @hide */
- public static final int POLICY_IS_VPN = 62;
+ public static final int POLICY_IS_VPN = 61;
// This network has been selected by the user manually from settings or a 3rd party app
// at least once. @see NetworkAgentConfig#explicitlySelected.
/** @hide */
- public static final int POLICY_EVER_USER_SELECTED = 61;
+ public static final int POLICY_EVER_USER_SELECTED = 60;
// The user has indicated in UI that this network should be used even if it doesn't
// validate. @see NetworkAgentConfig#acceptUnvalidated.
/** @hide */
- public static final int POLICY_ACCEPT_UNVALIDATED = 60;
+ public static final int POLICY_ACCEPT_UNVALIDATED = 59;
+
+ // The user explicitly said in UI to avoid this network when unvalidated.
+ // TODO : remove setAvoidUnvalidated and instead disconnect the network when the user
+ // chooses to move away from this network, and remove this flag.
+ /** @hide */
+ public static final int POLICY_AVOIDED_WHEN_UNVALIDATED = 58;
// This network is unmetered. @see NetworkCapabilities.NET_CAPABILITY_NOT_METERED.
/** @hide */
- public static final int POLICY_IS_UNMETERED = 59;
+ public static final int POLICY_IS_UNMETERED = 57;
// This network is invincible. This is useful for offers until there is an API to listen
// to requests.
/** @hide */
- public static final int POLICY_IS_INVINCIBLE = 58;
-
- // This network has been validated at least once since it was connected, but not explicitly
- // avoided in UI.
- // TODO : remove setAvoidUnvalidated and instead disconnect the network when the user
- // chooses to move away from this network, and remove this flag.
- /** @hide */
- public static final int POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD = 57;
+ public static final int POLICY_IS_INVINCIBLE = 56;
// The network agent has communicated that this network no longer functions, and the underlying
// native network has been destroyed. The network will still be reported to clients as connected
@@ -103,7 +91,7 @@
// This network should lose to an identical network that has not been destroyed, but should
// otherwise be scored exactly the same.
/** @hide */
- public static final int POLICY_IS_DESTROYED = 56;
+ public static final int POLICY_IS_DESTROYED = 55;
// To help iterate when printing
@VisibleForTesting
@@ -154,6 +142,7 @@
* @param caps the NetworkCapabilities of the network
* @param config the NetworkAgentConfig of the network
* @param everValidated whether this network has ever validated
+ * @param avoidUnvalidated whether the user said in UI to avoid this network when unvalidated
* @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad
* @param destroyed whether this network has been destroyed pending a replacement connecting
* @return a FullScore that is appropriate to use for ranking.
@@ -163,18 +152,19 @@
// connectivity for backward compatibility.
public static FullScore fromNetworkScore(@NonNull final NetworkScore score,
@NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config,
- final boolean everValidated, final boolean yieldToBadWiFi, final boolean destroyed) {
+ final boolean everValidated, final boolean avoidUnvalidated,
+ final boolean yieldToBadWiFi, final boolean destroyed) {
return withPolicies(score.getPolicies(),
score.getKeepConnectedReason(),
caps.hasCapability(NET_CAPABILITY_VALIDATED),
- caps.hasTransport(TRANSPORT_VPN),
- caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- everValidated,
+ everValidated, caps.hasTransport(TRANSPORT_VPN),
config.explicitlySelected,
config.acceptUnvalidated,
+ avoidUnvalidated,
+ caps.hasCapability(NET_CAPABILITY_NOT_METERED),
yieldToBadWiFi,
- destroyed,
- false /* invincible */); // only prospective scores can be invincible
+ false /* invincible */, // only prospective scores can be invincible
+ destroyed);
}
/**
@@ -194,25 +184,29 @@
@NonNull final NetworkCapabilities caps, final boolean yieldToBadWiFi) {
// If the network offers Internet access, it may validate.
final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET);
- // VPN transports are known in advance.
- final boolean vpn = caps.hasTransport(TRANSPORT_VPN);
- // Prospective scores are always unmetered, because unmetered networks are stronger
- // than metered networks, and it's not known in advance whether the network is metered.
- final boolean unmetered = true;
// If the offer may validate, then it should be considered to have validated at some point
final boolean everValidated = mayValidate;
+ // VPN transports are known in advance.
+ final boolean vpn = caps.hasTransport(TRANSPORT_VPN);
// The network hasn't been chosen by the user (yet, at least).
final boolean everUserSelected = false;
// Don't assume the user will accept unvalidated connectivity.
final boolean acceptUnvalidated = false;
+ // A prospective network is never avoided when unvalidated, because the user has never
+ // had the opportunity to say so in UI.
+ final boolean avoidUnvalidated = false;
+ // Prospective scores are always unmetered, because unmetered networks are stronger
+ // than metered networks, and it's not known in advance whether the network is metered.
+ final boolean unmetered = true;
// A network can only be destroyed once it has connected.
final boolean destroyed = false;
// A prospective score is invincible if the legacy int in the filter is over the maximum
// score.
final boolean invincible = score.getLegacyInt() > NetworkRanker.LEGACY_INT_MAX;
return withPolicies(score.getPolicies(), KEEP_CONNECTED_NONE,
- mayValidate, vpn, unmetered, everValidated, everUserSelected, acceptUnvalidated,
- yieldToBadWiFi, destroyed, invincible);
+ mayValidate, everValidated, vpn, everUserSelected, acceptUnvalidated,
+ avoidUnvalidated, unmetered,
+ yieldToBadWiFi, invincible, destroyed);
}
/**
@@ -228,18 +222,19 @@
public FullScore mixInScore(@NonNull final NetworkCapabilities caps,
@NonNull final NetworkAgentConfig config,
final boolean everValidated,
+ final boolean avoidUnvalidated,
final boolean yieldToBadWifi,
final boolean destroyed) {
return withPolicies(mPolicies, mKeepConnectedReason,
caps.hasCapability(NET_CAPABILITY_VALIDATED),
- caps.hasTransport(TRANSPORT_VPN),
- caps.hasCapability(NET_CAPABILITY_NOT_METERED),
- everValidated,
+ everValidated, caps.hasTransport(TRANSPORT_VPN),
config.explicitlySelected,
config.acceptUnvalidated,
+ avoidUnvalidated,
+ caps.hasCapability(NET_CAPABILITY_NOT_METERED),
yieldToBadWifi,
- destroyed,
- false /* invincible */); // only prospective scores can be invincible
+ false /* invincible */, // only prospective scores can be invincible
+ destroyed);
}
// TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
@@ -248,24 +243,26 @@
private static FullScore withPolicies(final long externalPolicies,
@KeepConnectedReason final int keepConnectedReason,
final boolean isValidated,
- final boolean isVpn,
- final boolean isUnmetered,
final boolean everValidated,
+ final boolean isVpn,
final boolean everUserSelected,
final boolean acceptUnvalidated,
+ final boolean avoidUnvalidated,
+ final boolean isUnmetered,
final boolean yieldToBadWiFi,
- final boolean destroyed,
- final boolean invincible) {
+ final boolean invincible,
+ final boolean destroyed) {
return new FullScore((externalPolicies & EXTERNAL_POLICIES_MASK)
| (isValidated ? 1L << POLICY_IS_VALIDATED : 0)
+ | (everValidated ? 1L << POLICY_EVER_VALIDATED : 0)
| (isVpn ? 1L << POLICY_IS_VPN : 0)
- | (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0)
- | (everValidated ? 1L << POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD : 0)
| (everUserSelected ? 1L << POLICY_EVER_USER_SELECTED : 0)
| (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0)
+ | (avoidUnvalidated ? 1L << POLICY_AVOIDED_WHEN_UNVALIDATED : 0)
+ | (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0)
| (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0)
- | (destroyed ? 1L << POLICY_IS_DESTROYED : 0)
- | (invincible ? 1L << POLICY_IS_INVINCIBLE : 0),
+ | (invincible ? 1L << POLICY_IS_INVINCIBLE : 0)
+ | (destroyed ? 1L << POLICY_IS_DESTROYED : 0),
keepConnectedReason);
}
diff --git a/service/src/com/android/server/connectivity/LingerMonitor.java b/service/src/com/android/server/connectivity/LingerMonitor.java
index 032612c..df34ce7 100644
--- a/service/src/com/android/server/connectivity/LingerMonitor.java
+++ b/service/src/com/android/server/connectivity/LingerMonitor.java
@@ -229,8 +229,8 @@
@Nullable final NetworkAgentInfo toNai) {
if (VDBG) {
Log.d(TAG, "noteLingerDefaultNetwork from=" + fromNai.toShortString()
- + " everValidated=" + fromNai.everValidated
- + " lastValidated=" + fromNai.lastValidated
+ + " firstValidated=" + fromNai.getFirstValidationTime()
+ + " lastValidated=" + fromNai.getCurrentValidationTime()
+ " to=" + toNai.toShortString());
}
@@ -253,7 +253,7 @@
// 1. User connects to wireless printer.
// 2. User turns on cellular data.
// 3. We show a notification.
- if (!fromNai.everValidated) return;
+ if (!fromNai.everValidated()) return;
// If this network is a captive portal, don't notify. This cannot happen on initial connect
// to a captive portal, because the everValidated check above will fail. However, it can
@@ -286,7 +286,7 @@
// because its score changed.
// TODO: instead of just skipping notification, keep a note of it, and show it if it becomes
// unvalidated.
- if (fromNai.lastValidated) return;
+ if (fromNai.isValidated()) return;
if (!isNotificationEnabled(fromNai, toNai)) return;
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index e4ad391..a57e992 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -144,7 +144,7 @@
&& nai.netAgentConfig().skip464xlat;
return (supported || isTestNetwork) && connected && isIpv6OnlyNetwork && !skip464xlat
- && !nai.destroyed && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
+ && !nai.isDestroyed() && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
? isCellular464XlatEnabled() : true);
}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 88a5f9c..d7be421 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -189,42 +189,193 @@
// field instead.
private @Nullable NetworkCapabilities mDeclaredCapabilitiesUnsanitized;
- // Indicates if netd has been told to create this Network. From this point on the appropriate
- // routing rules are setup and routes are added so packets can begin flowing over the Network.
- // This is a sticky bit; once set it is never cleared.
- public boolean created;
- // Set to true after the first time this network is marked as CONNECTED. Once set, the network
- // shows up in API calls, is able to satisfy NetworkRequests and can become the default network.
- // This is a sticky bit; once set it is never cleared.
- public boolean everConnected;
- // Whether this network has been destroyed and is being kept temporarily until it is replaced.
- public boolean destroyed;
- // To check how long it has been since last roam.
- public long lastRoamTimestamp;
+ // Timestamp (SystemClock.elapsedRealtime()) when netd has been told to create this Network, or
+ // 0 if it hasn't been done yet.
+ // From this point on, the appropriate routing rules are setup and routes are added so packets
+ // can begin flowing over the Network.
+ // This is a sticky value; once set != 0 it is never changed.
+ private long mCreatedTime;
- // Set to true if this Network successfully passed validation or if it did not satisfy the
- // default NetworkRequest in which case validation will not be attempted.
- // This is a sticky bit; once set it is never cleared even if future validation attempts fail.
- public boolean everValidated;
+ /** Notify this NAI that netd was just told to create this network */
+ public void setCreated() {
+ if (0L != mCreatedTime) throw new IllegalStateException("Already created");
+ mCreatedTime = SystemClock.elapsedRealtime();
+ }
- // The result of the last validation attempt on this network (true if validated, false if not).
- public boolean lastValidated;
+ /** Returns whether netd was told to create this network */
+ public boolean isCreated() {
+ return mCreatedTime != 0L;
+ }
- // If true, becoming unvalidated will lower the network's score. This is only meaningful if the
- // system is configured not to do this for certain networks, e.g., if the
- // config_networkAvoidBadWifi option is set to 0 and the user has not overridden that via
- // Settings.Global.NETWORK_AVOID_BAD_WIFI.
- public boolean avoidUnvalidated;
+ // Get the time (SystemClock.elapsedRealTime) when this network was created (or 0 if never).
+ public long getCreatedTime() {
+ return mCreatedTime;
+ }
- // Whether a captive portal was ever detected on this network.
- // This is a sticky bit; once set it is never cleared.
- public boolean everCaptivePortalDetected;
+ // Timestamp of the first time (SystemClock.elapsedRealtime()) this network is marked as
+ // connected, or 0 if this network has never been marked connected. Once set to non-zero, the
+ // network shows up in API calls, is able to satisfy NetworkRequests and can become the default
+ // network.
+ // This is a sticky value; once set != 0 it is never changed.
+ private long mConnectedTime;
- // Whether a captive portal was found during the last network validation attempt.
- public boolean lastCaptivePortalDetected;
+ /** Notify this NAI that this network just connected */
+ public void setConnected() {
+ if (0L != mConnectedTime) throw new IllegalStateException("Already connected");
+ mConnectedTime = SystemClock.elapsedRealtime();
+ }
- // Set to true when partial connectivity was detected.
- public boolean partialConnectivity;
+ /** Return whether this network ever connected */
+ public boolean everConnected() {
+ return mConnectedTime != 0L;
+ }
+
+ // Get the time (SystemClock.elapsedRealTime()) when this network was first connected, or 0 if
+ // never.
+ public long getConnectedTime() {
+ return mConnectedTime;
+ }
+
+ // When this network has been destroyed and is being kept temporarily until it is replaced,
+ // this is set to that timestamp (SystemClock.elapsedRealtime()). Zero otherwise.
+ private long mDestroyedTime;
+
+ /** Notify this NAI that this network was destroyed */
+ public void setDestroyed() {
+ if (0L != mDestroyedTime) throw new IllegalStateException("Already destroyed");
+ mDestroyedTime = SystemClock.elapsedRealtime();
+ }
+
+ /** Return whether this network was destroyed */
+ public boolean isDestroyed() {
+ return 0L != mDestroyedTime;
+ }
+
+ // Timestamp of the last roaming (SystemClock.elapsedRealtime()) or 0 if never roamed.
+ public long lastRoamTime;
+
+ // Timestamp (SystemClock.elapsedRealtime()) of the first time this network successfully
+ // passed validation or was deemed exempt of validation (see
+ // {@link NetworkMonitorUtils#isValidationRequired}). Zero if the network requires
+ // validation but never passed it successfully.
+ // This is a sticky value; once set it is never changed even if further validation attempts are
+ // made (whether they succeed or fail).
+ private long mFirstValidationTime;
+
+ // Timestamp (SystemClock.elapsedRealtime()) at which the latest validation attempt succeeded,
+ // or 0 if the latest validation attempt failed.
+ private long mCurrentValidationTime;
+
+ /** Notify this NAI that this network just finished a validation check */
+ public void setValidated(final boolean validated) {
+ final long nowOrZero = validated ? SystemClock.elapsedRealtime() : 0L;
+ if (validated && 0L == mFirstValidationTime) {
+ mFirstValidationTime = nowOrZero;
+ }
+ mCurrentValidationTime = nowOrZero;
+ }
+
+ /**
+ * Returns whether this network is currently validated.
+ *
+ * This is the result of the latest validation check. {@see #getCurrentValidationTime} for
+ * when that check was performed.
+ */
+ public boolean isValidated() {
+ return 0L != mCurrentValidationTime;
+ }
+
+ /**
+ * Returns whether this network ever passed the validation checks successfully.
+ *
+ * Note that the network may no longer be validated at this time ever if this is true.
+ * @see #isValidated
+ */
+ public boolean everValidated() {
+ return 0L != mFirstValidationTime;
+ }
+
+ // Get the time (SystemClock.elapsedRealTime()) when this network was most recently validated,
+ // or 0 if this network was found not to validate on the last attempt.
+ public long getCurrentValidationTime() {
+ return mCurrentValidationTime;
+ }
+
+ // Get the time (SystemClock.elapsedRealTime()) when this network was validated for the first
+ // time (or 0 if never).
+ public long getFirstValidationTime() {
+ return mFirstValidationTime;
+ }
+
+ // Timestamp (SystemClock.elapsedRealtime()) at which the user requested this network be
+ // avoided when unvalidated. Zero if this never happened for this network.
+ // This is only meaningful if the system is configured to have some cell networks yield
+ // to bad wifi, e.g., if the config_networkAvoidBadWifi option is set to 0 and the user has
+ // not overridden that via Settings.Global.NETWORK_AVOID_BAD_WIFI.
+ //
+ // Normally the system always prefers a validated network to a non-validated one, even if
+ // the non-validated one is cheaper. However, some cell networks may be configured by the
+ // setting above to yield to WiFi even if that WiFi network goes bad. When this configuration
+ // is active, specific networks can be marked to override this configuration so that the
+ // system will revert to preferring such a cell to this network when this network goes bad. This
+ // is achieved by calling {@link ConnectivityManager#setAvoidUnvalidated()}, and this field
+ // is set to non-zero when this happened to this network.
+ private long mAvoidUnvalidated;
+
+ /** Set this network as being avoided when unvalidated. {@see mAvoidUnvalidated} */
+ public void setAvoidUnvalidated() {
+ if (0L != mAvoidUnvalidated) throw new IllegalStateException("Already avoided unvalidated");
+ mAvoidUnvalidated = SystemClock.elapsedRealtime();
+ }
+
+ // Get the time (SystemClock.elapsedRealTime()) when this network was set to being avoided
+ // when unvalidated, or 0 if this never happened.
+ public long getAvoidUnvalidated() {
+ return mAvoidUnvalidated;
+ }
+
+ // Timestamp (SystemClock.elapsedRealtime()) at which a captive portal was first detected
+ // on this network, or zero if this never happened.
+ // This is a sticky value; once set != 0 it is never changed.
+ private long mFirstCaptivePortalDetectedTime;
+
+ // Timestamp (SystemClock.elapsedRealtime()) at which the latest validation attempt found a
+ // captive portal, or zero if the latest attempt didn't find a captive portal.
+ private long mCurrentCaptivePortalDetectedTime;
+
+ /** Notify this NAI that a captive portal has just been detected on this network */
+ public void setCaptivePortalDetected(final boolean hasCaptivePortal) {
+ if (!hasCaptivePortal) {
+ mCurrentCaptivePortalDetectedTime = 0L;
+ return;
+ }
+ final long now = SystemClock.elapsedRealtime();
+ if (0L == mFirstCaptivePortalDetectedTime) mFirstCaptivePortalDetectedTime = now;
+ mCurrentCaptivePortalDetectedTime = now;
+ }
+
+ /** Return whether a captive portal has ever been detected on this network */
+ public boolean everCaptivePortalDetected() {
+ return 0L != mFirstCaptivePortalDetectedTime;
+ }
+
+ /** Return whether this network has been detected to be behind a captive portal at the moment */
+ public boolean captivePortalDetected() {
+ return 0L != mCurrentCaptivePortalDetectedTime;
+ }
+
+ // Timestamp (SystemClock.elapsedRealtime()) at which the latest validation attempt found
+ // partial connectivity, or zero if the latest attempt didn't find partial connectivity.
+ private long mPartialConnectivityTime;
+
+ public void setPartialConnectivity(final boolean value) {
+ mPartialConnectivityTime = value ? SystemClock.elapsedRealtime() : 0L;
+ }
+
+ /** Return whether this NAI has partial connectivity */
+ public boolean partialConnectivity() {
+ return 0L != mPartialConnectivityTime;
+ }
// Delay between when the network is disconnected and when the native network is destroyed.
public int teardownDelayMs;
@@ -241,6 +392,9 @@
// URL, Terms & Conditions URL, and network friendly name.
public CaptivePortalData networkAgentPortalData;
+ // Indicate whether this device has the automotive feature.
+ private final boolean mHasAutomotiveFeature;
+
/**
* Sets the capabilities sent by the agent for later retrieval.
*
@@ -282,9 +436,8 @@
+ networkCapabilities.getOwnerUid() + " to " + nc.getOwnerUid());
nc.setOwnerUid(networkCapabilities.getOwnerUid());
}
- restrictCapabilitiesFromNetworkAgent(nc, creatorUid,
- mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE),
- carrierPrivilegeAuthenticator);
+ restrictCapabilitiesFromNetworkAgent(
+ nc, creatorUid, mHasAutomotiveFeature, carrierPrivilegeAuthenticator);
return nc;
}
@@ -453,6 +606,8 @@
? nc.getUnderlyingNetworks().toArray(new Network[0])
: null;
mCreationTime = System.currentTimeMillis();
+ mHasAutomotiveFeature =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
private class AgentDeathMonitor implements IBinder.DeathRecipient {
@@ -819,8 +974,8 @@
@NonNull final NetworkCapabilities nc) {
final NetworkCapabilities oldNc = networkCapabilities;
networkCapabilities = nc;
- mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, everValidatedForYield(),
- yieldToBadWiFi(), destroyed);
+ mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, everValidated(),
+ 0L != getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed());
final NetworkMonitorManager nm = mNetworkMonitor;
if (nm != null) {
nm.notifyNetworkCapabilitiesChanged(nc);
@@ -983,13 +1138,14 @@
// Does this network satisfy request?
public boolean satisfies(NetworkRequest request) {
- return everConnected
+ return everConnected()
&& request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities);
}
public boolean satisfiesImmutableCapabilitiesOf(NetworkRequest request) {
- return everConnected && request.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
- networkCapabilities);
+ return everConnected()
+ && request.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
+ networkCapabilities);
}
/** Whether this network is a VPN. */
@@ -1022,7 +1178,7 @@
*/
public void setScore(final NetworkScore score) {
mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
- everValidatedForYield(), yieldToBadWiFi(), destroyed);
+ everValidated(), 0L == getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed());
}
/**
@@ -1032,11 +1188,7 @@
*/
public void updateScoreForNetworkAgentUpdate() {
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig,
- everValidatedForYield(), yieldToBadWiFi(), destroyed);
- }
-
- private boolean everValidatedForYield() {
- return everValidated && !avoidUnvalidated;
+ everValidated(), 0L != getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed());
}
/**
@@ -1323,14 +1475,17 @@
+ networkInfo.toShortString() + "} "
+ "created=" + Instant.ofEpochMilli(mCreationTime) + " "
+ mScore + " "
- + (created ? " created" : "")
- + (destroyed ? " destroyed" : "")
+ + (isCreated() ? " created " + getCreatedTime() : "")
+ + (isDestroyed() ? " destroyed " + mDestroyedTime : "")
+ (isNascent() ? " nascent" : (isLingering() ? " lingering" : ""))
- + (everValidated ? " everValidated" : "")
- + (lastValidated ? " lastValidated" : "")
- + (partialConnectivity ? " partialConnectivity" : "")
- + (everCaptivePortalDetected ? " everCaptivePortal" : "")
- + (lastCaptivePortalDetected ? " isCaptivePortal" : "")
+ + (everValidated() ? " firstValidated " + getFirstValidationTime() : "")
+ + (isValidated() ? " lastValidated " + getCurrentValidationTime() : "")
+ + (partialConnectivity()
+ ? " partialConnectivity " + mPartialConnectivityTime : "")
+ + (everCaptivePortalDetected()
+ ? " firstCaptivePortalDetected " + mFirstCaptivePortalDetectedTime : "")
+ + (captivePortalDetected()
+ ? " currentCaptivePortalDetected " + mCurrentCaptivePortalDetectedTime : "")
+ (networkAgentConfig.explicitlySelected ? " explicitlySelected" : "")
+ (networkAgentConfig.acceptUnvalidated ? " acceptUnvalidated" : "")
+ (networkAgentConfig.acceptPartialConnectivity ? " acceptPartialConnectivity" : "")
@@ -1348,7 +1503,7 @@
*
* This is often not enough for debugging purposes for anything complex, but the full form
* is very long and hard to read, so this is useful when there isn't a lot of ambiguity.
- * This represents the network with something like "[100 WIFI|VPN]" or "[108 MOBILE]".
+ * This represents the network with something like "[100 WIFI|VPN]" or "[108 CELLULAR]".
*/
public String toShortString() {
return "[" + network.getNetId() + " "
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index babc353..f2c6aa1 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -26,8 +26,9 @@
import static com.android.net.module.util.CollectionUtils.filter;
import static com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED;
+import static com.android.server.connectivity.FullScore.POLICY_AVOIDED_WHEN_UNVALIDATED;
import static com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED;
-import static com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD;
+import static com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED;
import static com.android.server.connectivity.FullScore.POLICY_IS_DESTROYED;
import static com.android.server.connectivity.FullScore.POLICY_IS_INVINCIBLE;
import static com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED;
@@ -104,7 +105,9 @@
}
private <T extends Scoreable> boolean isBadWiFi(@NonNull final T candidate) {
- return candidate.getScore().hasPolicy(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD)
+ final FullScore score = candidate.getScore();
+ return score.hasPolicy(POLICY_EVER_VALIDATED)
+ && !score.hasPolicy(POLICY_AVOIDED_WHEN_UNVALIDATED)
&& candidate.getCapsNoCopy().hasTransport(TRANSPORT_WIFI);
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 7842eec..deca6a2 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -117,6 +117,7 @@
return false;
}
if (mDataSaverSupported == null) {
+ setRestrictBackgroundInternal(false);
assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
try {
setRestrictBackgroundInternal(true);
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index a6179fc..23cb15c 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -61,7 +61,9 @@
// uncomment when b/13249961 is fixed
// sdk_version: "current",
platform_apis: true,
- required: ["ConnectivityChecker"],
+ data: [":ConnectivityChecker"],
+ per_testcase_directory: true,
+ host_required: ["net-tests-utils-host-common"],
test_config_template: "AndroidTestTemplate.xml",
}
diff --git a/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt b/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
index 9599d4e..3a36cee 100644
--- a/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
+++ b/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
@@ -27,6 +27,9 @@
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
private val TAG = DeviceConfigRule::class.simpleName
@@ -110,17 +113,60 @@
* Set a configuration key/value. After the test case ends, it will be restored to the value it
* had when this method was first called.
*/
- fun setConfig(namespace: String, key: String, value: String?) {
- runAsShell(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) {
- val keyPair = Pair(namespace, key)
- if (!originalConfig.containsKey(keyPair)) {
- originalConfig[keyPair] = DeviceConfig.getProperty(namespace, key)
+ fun setConfig(namespace: String, key: String, value: String?): String? {
+ Log.i(TAG, "Setting config \"$key\" to \"$value\"")
+ val readWritePermissions = arrayOf(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
+
+ val keyPair = Pair(namespace, key)
+ val existingValue = runAsShell(*readWritePermissions) {
+ DeviceConfig.getProperty(namespace, key)
+ }
+ if (!originalConfig.containsKey(keyPair)) {
+ originalConfig[keyPair] = existingValue
+ }
+ usedConfig[keyPair] = value
+ if (existingValue == value) {
+ // Already the correct value. There may be a race if a change is already in flight,
+ // but if multiple threads update the config there is no way to fix that anyway.
+ Log.i(TAG, "\"$key\" already had value \"$value\"")
+ return value
+ }
+
+ val future = CompletableFuture<String>()
+ val listener = DeviceConfig.OnPropertiesChangedListener {
+ // The listener receives updates for any change to any key, so don't react to
+ // changes that do not affect the relevant key
+ if (!it.keyset.contains(key)) return@OnPropertiesChangedListener
+ // "null" means absent in DeviceConfig : there is no such thing as a present but
+ // null value, so the following works even if |value| is null.
+ if (it.getString(key, null) == value) {
+ future.complete(value)
}
- usedConfig[keyPair] = value
- DeviceConfig.setProperty(namespace, key, value, false /* makeDefault */)
+ }
+
+ return tryTest {
+ runAsShell(*readWritePermissions) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_CONNECTIVITY,
+ inlineExecutor,
+ listener)
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONNECTIVITY,
+ key,
+ value,
+ false /* makeDefault */)
+ // Don't drop the permission until the config is applied, just in case
+ future.get(NetworkValidationTestUtil.TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }.also {
+ Log.i(TAG, "Config \"$key\" successfully set to \"$value\"")
+ }
+ } cleanup {
+ DeviceConfig.removeOnPropertiesChangedListener(listener)
}
}
+ private val inlineExecutor get() = Executor { r -> r.run() }
+
/**
* Add an action to be run after config cleanup when the current test case ends.
*/
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index a02be85..d598830 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -65,6 +65,7 @@
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
+import com.android.testutils.waitForIdle
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertFalse
@@ -121,6 +122,7 @@
cm.unregisterNetworkCallback(requestCb)
agent.unregister()
iface.fileDescriptor.close()
+ agent.waitForIdle(TIMEOUT_MS)
}
}
@@ -291,7 +293,7 @@
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)
+ cb.expectAvailableThenValidatedCallbacks(network, TIMEOUT_MS)
return TestTapNetwork(iface, cb, agent, network)
}
@@ -319,6 +321,7 @@
testNetwork2.close(cm)
}
}
+ handlerThread.waitForIdle(TIMEOUT_MS)
handlerThread.quitSafely()
}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index f035f72..9d1fa60 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -357,7 +357,7 @@
public Network connectToCell() throws InterruptedException {
if (cellConnectAttempted()) {
- throw new IllegalStateException("Already connected");
+ mCm.unregisterNetworkCallback(mCellNetworkCallback);
}
NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR)
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index 5cb014f..2f5b0ab 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -471,6 +471,23 @@
new Ikev2VpnProfile.Builder(tunnelParams2).build());
}
+ @Test
+ public void testBuildProfileWithNullProxy() throws Exception {
+ final Ikev2VpnProfile ikev2VpnProfile =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
+ .build();
+
+ // ProxyInfo should be null for the profile without setting ProxyInfo.
+ assertNull(ikev2VpnProfile.getProxyInfo());
+
+ // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+ assertNull(vpnProfile.proxy);
+
+ final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
+ assertNull(convertedIkev2VpnProfile.getProxyInfo());
+ }
private static class CertificateAndKey {
public final X509Certificate cert;
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 3e9662d..6c39169 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -19,7 +19,10 @@
import android.app.usage.NetworkStatsManager.NETWORK_TYPE_5G_NSA
import android.content.Context
import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_TEST
import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkIdentity.OEM_NONE
import android.net.NetworkIdentity.OEM_PAID
import android.net.NetworkIdentity.OEM_PRIVATE
@@ -31,6 +34,7 @@
import android.net.NetworkStats.ROAMING_ALL
import android.net.NetworkTemplate.MATCH_MOBILE
import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
+import android.net.NetworkTemplate.MATCH_TEST
import android.net.NetworkTemplate.MATCH_WIFI
import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
import android.net.NetworkTemplate.NETWORK_TYPE_ALL
@@ -97,6 +101,14 @@
(oemManaged and OEM_PAID) == OEM_PAID)
setCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE,
(oemManaged and OEM_PRIVATE) == OEM_PRIVATE)
+ if (type == TYPE_TEST) {
+ wifiKey?.let { TestNetworkSpecifier(it) }?.let {
+ // Must have a single non-test transport specified to use setNetworkSpecifier.
+ // Put an arbitrary transport type which is not used in this test.
+ addTransportType(TRANSPORT_TEST)
+ addTransportType(TRANSPORT_WIFI)
+ setNetworkSpecifier(it) }
+ }
setTransportInfo(mockWifiInfo)
}
return NetworkStateSnapshot(mock(Network::class.java), caps, lp, subscriberId, type)
@@ -233,6 +245,32 @@
}
@Test
+ fun testTestNetworkTemplateMatches() {
+ val templateTestKey1 = NetworkTemplate.Builder(MATCH_TEST)
+ .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
+ val templateTestKey2 = NetworkTemplate.Builder(MATCH_TEST)
+ .setWifiNetworkKeys(setOf(TEST_WIFI_KEY2)).build()
+ val templateTestAll = NetworkTemplate.Builder(MATCH_TEST).build()
+
+ val stateWifiKey1 = buildNetworkState(TYPE_WIFI, null /* subscriberId */, TEST_WIFI_KEY1,
+ OEM_NONE, true /* metered */)
+ val stateTestKey1 = buildNetworkState(TYPE_TEST, null /* subscriberId */, TEST_WIFI_KEY1,
+ OEM_NONE, true /* metered */)
+ val identWifi1 = buildNetworkIdentity(mockContext, stateWifiKey1,
+ false /* defaultNetwork */, NetworkTemplate.NETWORK_TYPE_ALL)
+ val identTest1 = buildNetworkIdentity(mockContext, stateTestKey1,
+ false /* defaultNetwork */, NETWORK_TYPE_ALL)
+
+ // Verify that the template matches corresponding type and the subscriberId.
+ templateTestKey1.assertDoesNotMatch(identWifi1)
+ templateTestKey1.assertMatches(identTest1)
+ templateTestKey2.assertDoesNotMatch(identWifi1)
+ templateTestKey2.assertDoesNotMatch(identTest1)
+ templateTestAll.assertDoesNotMatch(identWifi1)
+ templateTestAll.assertMatches(identTest1)
+ }
+
+ @Test
fun testCarrierMeteredMatches() {
val templateCarrierImsi1Metered = buildTemplateCarrierMetered(TEST_IMSI1)
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index be286ec..eb5d2ef 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -30,6 +30,7 @@
import static android.net.INetd.PERMISSION_NONE;
import static android.net.INetd.PERMISSION_UNINSTALLED;
import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS;
+import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.EPERM;
import static com.android.server.BpfNetMaps.DOZABLE_MATCH;
@@ -40,6 +41,7 @@
import static com.android.server.BpfNetMaps.PENALTY_BOX_MATCH;
import static com.android.server.BpfNetMaps.POWERSAVE_MATCH;
import static com.android.server.BpfNetMaps.RESTRICTED_MATCH;
+import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -47,20 +49,27 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.StatsManager;
import android.content.Context;
import android.net.INetd;
import android.os.Build;
import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
import androidx.test.filters.SmallTest;
import com.android.modules.utils.build.SdkLevel;
-import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.Struct.U8;
+import com.android.net.module.util.bpf.CookieTagMapKey;
+import com.android.net.module.util.bpf.CookieTagMapValue;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -74,6 +83,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
@RunWith(DevSdkIgnoreRunner.class)
@@ -113,10 +123,12 @@
@Mock INetd mNetd;
@Mock BpfNetMaps.Dependencies mDeps;
@Mock Context mContext;
- private final BpfMap<U32, U32> mConfigurationMap = new TestBpfMap<>(U32.class, U32.class);
- private final BpfMap<U32, UidOwnerValue> mUidOwnerMap =
+ private final IBpfMap<U32, U32> mConfigurationMap = new TestBpfMap<>(U32.class, U32.class);
+ private final IBpfMap<U32, UidOwnerValue> mUidOwnerMap =
new TestBpfMap<>(U32.class, UidOwnerValue.class);
- private final BpfMap<U32, U8> mUidPermissionMap = new TestBpfMap<>(U32.class, U8.class);
+ private final IBpfMap<U32, U8> mUidPermissionMap = new TestBpfMap<>(U32.class, U8.class);
+ private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap =
+ spy(new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class));
@Before
public void setUp() throws Exception {
@@ -127,6 +139,7 @@
BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
+ BpfNetMaps.setCookieTagMapForTest(mCookieTagMap);
mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps);
}
@@ -877,4 +890,40 @@
assertThrows(ServiceSpecificException.class, () -> mBpfNetMaps.swapActiveStatsMap());
}
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testPullBpfMapInfo() throws Exception {
+ // mCookieTagMap has 1 entry
+ mCookieTagMap.updateEntry(new CookieTagMapKey(0), new CookieTagMapValue(0, 0));
+
+ // mUidOwnerMap has 2 entries
+ mUidOwnerMap.updateEntry(new U32(0), new UidOwnerValue(0, 0));
+ mUidOwnerMap.updateEntry(new U32(1), new UidOwnerValue(0, 0));
+
+ // mUidPermissionMap has 3 entries
+ mUidPermissionMap.updateEntry(new U32(0), new U8((short) 0));
+ mUidPermissionMap.updateEntry(new U32(1), new U8((short) 0));
+ mUidPermissionMap.updateEntry(new U32(2), new U8((short) 0));
+
+ final int ret = mBpfNetMaps.pullBpfMapInfoAtom(NETWORK_BPF_MAP_INFO, new ArrayList<>());
+ assertEquals(StatsManager.PULL_SUCCESS, ret);
+ verify(mDeps).buildStatsEvent(
+ 1 /* cookieTagMapSize */, 2 /* uidOwnerMapSize */, 3 /* uidPermissionMapSize */);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testPullBpfMapInfoGetMapSizeFailure() throws Exception {
+ doThrow(new ErrnoException("", EINVAL)).when(mCookieTagMap).forEach(any());
+ final int ret = mBpfNetMaps.pullBpfMapInfoAtom(NETWORK_BPF_MAP_INFO, new ArrayList<>());
+ assertEquals(StatsManager.PULL_SKIP, ret);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testPullBpfMapInfoUnexpectedAtomTag() {
+ final int ret = mBpfNetMaps.pullBpfMapInfoAtom(-1 /* atomTag */, new ArrayList<>());
+ assertEquals(StatsManager.PULL_SKIP, ret);
+ }
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index e23e72d..85aec56 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -15594,11 +15594,19 @@
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
- // In this test the automotive feature will be enabled.
- mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
+ // Has automotive feature.
+ validateAutomotiveEthernetAllowedUids(true);
+
+ // No automotive feature.
+ validateAutomotiveEthernetAllowedUids(false);
+ }
+
+ private void validateAutomotiveEthernetAllowedUids(final boolean hasAutomotiveFeature)
+ throws Exception {
+ mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, hasAutomotiveFeature);
// Simulate a restricted ethernet network.
- final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
+ final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_ETHERNET)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
@@ -15606,8 +15614,34 @@
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET,
- new LinkProperties(), agentNetCaps.build());
- validateAllowedUids(mEthernetNetworkAgent, TRANSPORT_ETHERNET, agentNetCaps, true);
+ new LinkProperties(), ncb.build());
+
+ final ArraySet<Integer> serviceUidSet = new ArraySet<>();
+ serviceUidSet.add(TEST_PACKAGE_UID);
+
+ final TestNetworkCallback cb = new TestNetworkCallback();
+
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_ETHERNET)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build(), cb);
+ mEthernetNetworkAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+
+ // Cell gets to set the service UID as access UID
+ ncb.setAllowedUids(serviceUidSet);
+ mEthernetNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ if (SdkLevel.isAtLeastT() && hasAutomotiveFeature) {
+ cb.expectCapabilitiesThat(mEthernetNetworkAgent,
+ caps -> caps.getAllowedUids().equals(serviceUidSet));
+ } else {
+ // S and no automotive feature must ignore access UIDs.
+ cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+ }
+
+ mEthernetNetworkAgent.disconnect();
+ cb.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
+ mCm.unregisterNetworkCallback(cb);
}
@Test
@@ -15621,7 +15655,7 @@
// 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 agentNetCaps = new NetworkCapabilities.Builder()
+ final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
@@ -15630,13 +15664,8 @@
.setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
- new LinkProperties(), agentNetCaps.build());
- validateAllowedUids(mCellNetworkAgent, TRANSPORT_CELLULAR, agentNetCaps, false);
- }
+ new LinkProperties(), ncb.build());
- private void validateAllowedUids(final TestNetworkAgentWrapper testAgent,
- @NetworkCapabilities.Transport final int transportUnderTest,
- final NetworkCapabilities.Builder ncb, final boolean forAutomotive) throws Exception {
final ArraySet<Integer> serviceUidSet = new ArraySet<>();
serviceUidSet.add(TEST_PACKAGE_UID);
final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
@@ -15647,34 +15676,28 @@
final TestNetworkCallback cb = new TestNetworkCallback();
- /* Test setting UIDs */
// Cell gets to set the service UID as access UID
mCm.requestNetwork(new NetworkRequest.Builder()
- .addTransportType(transportUnderTest)
+ .addTransportType(TRANSPORT_CELLULAR)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build(), cb);
- testAgent.connect(true);
- cb.expectAvailableThenValidatedCallbacks(testAgent);
+ mCellNetworkAgent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
ncb.setAllowedUids(serviceUidSet);
- testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(testAgent,
+ cb.expectCapabilitiesThat(mCellNetworkAgent,
caps -> caps.getAllowedUids().equals(serviceUidSet));
} else {
// S must ignore access UIDs.
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
}
- /* Test setting UIDs is rejected when expected */
- if (forAutomotive) {
- mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
- }
-
// ...but not to some other UID. Rejection sets UIDs to the empty set
ncb.setAllowedUids(nonServiceUidSet);
- testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
if (SdkLevel.isAtLeastT()) {
- cb.expectCapabilitiesThat(testAgent,
+ cb.expectCapabilitiesThat(mCellNetworkAgent,
caps -> caps.getAllowedUids().isEmpty());
} else {
// S must ignore access UIDs.
@@ -15683,18 +15706,18 @@
// ...and also not to multiple UIDs even including the service UID
ncb.setAllowedUids(serviceUidSetPlus);
- testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+ mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
- testAgent.disconnect();
- cb.expectCallback(CallbackEntry.LOST, testAgent);
+ 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(transportUnderTest);
+ 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()
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index a194131..c8a62be 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -66,7 +66,8 @@
if (vpn) addTransportType(NetworkCapabilities.TRANSPORT_VPN)
if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}.build()
- return mixInScore(nc, nac, validated, false /* yieldToBadWifi */, destroyed)
+ return mixInScore(nc, nac, validated, false /* avoidUnvalidated */,
+ false /* yieldToBadWifi */, destroyed)
}
private val TAG = this::class.simpleName
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index ad8613f..719314a 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -47,6 +48,7 @@
import android.net.metrics.ValidationProbeEvent;
import android.os.Build;
import android.os.Parcelable;
+import android.os.SystemClock;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
@@ -138,7 +140,7 @@
private void logDefaultNetworkEvent(long timeMs, NetworkAgentInfo nai,
NetworkAgentInfo oldNai) {
final Network network = (nai != null) ? nai.network() : null;
- final boolean validated = (nai != null) ? nai.lastValidated : false;
+ final boolean validated = (nai != null) ? nai.isValidated() : false;
final LinkProperties lp = (nai != null) ? nai.linkProperties : null;
final NetworkCapabilities nc = (nai != null) ? nai.networkCapabilities : null;
@@ -614,7 +616,10 @@
when(nai.network()).thenReturn(new Network(netId));
nai.linkProperties = new LinkProperties();
nai.networkCapabilities = new NetworkCapabilities();
- nai.lastValidated = true;
+ nai.setValidated(true);
+ doReturn(true).when(nai).isValidated();
+ doReturn(SystemClock.elapsedRealtime()).when(nai).getFirstValidationTime();
+ doReturn(SystemClock.elapsedRealtime()).when(nai).getCurrentValidationTime();
for (int t : BitUtils.unpackBits(transports)) {
nai.networkCapabilities.addTransportType(t);
}
@@ -629,8 +634,6 @@
return nai;
}
-
-
static void verifySerialization(String want, String output) {
try {
byte[] got = Base64.decode(output, Base64.DEFAULT);
diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
index 58a7c89..0d371fa 100644
--- a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -30,6 +31,7 @@
import android.app.PendingIntent;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityResources;
@@ -85,12 +87,14 @@
@Mock NetworkNotificationManager mNotifier;
@Mock Resources mResources;
@Mock QosCallbackTracker mQosCallbackTracker;
+ @Mock PackageManager mPackageManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mCtx.getResources()).thenReturn(mResources);
when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity");
+ doReturn(mPackageManager).when(mCtx).getPackageManager();
ConnectivityResources.setResourcesContextForTest(mCtx);
mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
@@ -272,9 +276,8 @@
public void testIgnoreNeverValidatedNetworks() {
setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
setNotificationSwitch(transition(WIFI, CELLULAR));
- NetworkAgentInfo from = wifiNai(100);
+ NetworkAgentInfo from = wifiNai(100, false /* setEverValidated */);
NetworkAgentInfo to = cellNai(101);
- from.everValidated = false;
mMonitor.noteLingerDefaultNetwork(from, to);
verifyNoNotifications();
@@ -286,7 +289,7 @@
setNotificationSwitch(transition(WIFI, CELLULAR));
NetworkAgentInfo from = wifiNai(100);
NetworkAgentInfo to = cellNai(101);
- from.lastValidated = true;
+ from.setValidated(true);
mMonitor.noteLingerDefaultNetwork(from, to);
verifyNoNotifications();
@@ -363,7 +366,8 @@
eq(NotificationType.NETWORK_SWITCH), eq(from), eq(to), any(), eq(true));
}
- NetworkAgentInfo nai(int netId, int transport, int networkType, String networkTypeName) {
+ NetworkAgentInfo nai(int netId, int transport, int networkType, String networkTypeName,
+ boolean setEverValidated) {
NetworkInfo info = new NetworkInfo(networkType, 0, networkTypeName, "");
NetworkCapabilities caps = new NetworkCapabilities();
caps.addCapability(0);
@@ -373,18 +377,32 @@
mCtx, null, new NetworkAgentConfig.Builder().build(), mConnService, mNetd,
mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS,
mQosCallbackTracker, new ConnectivityService.Dependencies());
- nai.everValidated = true;
+ if (setEverValidated) {
+ // As tests in this class deal with testing lingering, most tests are interested
+ // in networks that can be lingered, and therefore must have validated in the past.
+ // Thus, pretend the network validated once, then became invalidated.
+ nai.setValidated(true);
+ nai.setValidated(false);
+ }
return nai;
}
NetworkAgentInfo wifiNai(int netId) {
+ return wifiNai(netId, true /* setEverValidated */);
+ }
+
+ NetworkAgentInfo wifiNai(int netId, boolean setEverValidated) {
return nai(netId, NetworkCapabilities.TRANSPORT_WIFI,
- ConnectivityManager.TYPE_WIFI, WIFI);
+ ConnectivityManager.TYPE_WIFI, WIFI, setEverValidated);
}
NetworkAgentInfo cellNai(int netId) {
+ return cellNai(netId, true /* setEverValidated */);
+ }
+
+ NetworkAgentInfo cellNai(int netId, boolean setEverValidated) {
return nai(netId, NetworkCapabilities.TRANSPORT_CELLULAR,
- ConnectivityManager.TYPE_MOBILE, CELLULAR);
+ ConnectivityManager.TYPE_MOBILE, CELLULAR, setEverValidated);
}
public static class TestableLingerMonitor extends LingerMonitor {
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 6f9f430..8b1c510 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -25,7 +25,8 @@
import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI
import android.os.Build
import androidx.test.filters.SmallTest
-import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD
+import com.android.server.connectivity.FullScore.POLICY_AVOIDED_WHEN_UNVALIDATED
+import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED
import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
@@ -61,8 +62,7 @@
@Test
fun testYieldToBadWiFiOneCellOneBadWiFi() {
// Bad wifi wins against yielding validated cell
- val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
- caps(TRANSPORT_WIFI))
+ val winner = TestScore(score(POLICY_EVER_VALIDATED), caps(TRANSPORT_WIFI))
val scores = listOf(
winner,
TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
@@ -72,14 +72,26 @@
}
@Test
+ fun testYieldToBadWifiAvoidUnvalidated() {
+ // Bad wifi avoided when unvalidated loses against yielding validated cell
+ val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_CELLULAR))
+ val scores = listOf(
+ winner,
+ TestScore(score(POLICY_EVER_VALIDATED, POLICY_AVOIDED_WHEN_UNVALIDATED),
+ caps(TRANSPORT_WIFI))
+ )
+ assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ }
+
+ @Test
fun testYieldToBadWiFiOneCellTwoBadWiFi() {
// Bad wifi wins against yielding validated cell. Prefer the one that's primary.
- val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
+ val winner = TestScore(score(POLICY_EVER_VALIDATED,
POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI))
val scores = listOf(
winner,
- TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
- caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_EVER_VALIDATED), caps(TRANSPORT_WIFI)),
TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
caps(TRANSPORT_CELLULAR))
)
@@ -90,8 +102,7 @@
fun testYieldToBadWiFiOneCellTwoBadWiFiOneNotAvoided() {
// Bad wifi ever validated wins against bad wifi that never was validated (or was
// avoided when bad).
- val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD),
- caps(TRANSPORT_WIFI))
+ val winner = TestScore(score(POLICY_EVER_VALIDATED), caps(TRANSPORT_WIFI))
val scores = listOf(
winner,
TestScore(score(), caps(TRANSPORT_WIFI)),
@@ -104,12 +115,12 @@
@Test
fun testYieldToBadWiFiOneCellOneBadWiFiOneGoodWiFi() {
// Good wifi wins
- val winner = TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
- POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI))
+ val winner = TestScore(score(POLICY_EVER_VALIDATED, POLICY_IS_VALIDATED),
+ caps(TRANSPORT_WIFI))
val scores = listOf(
winner,
- TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
- POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_EVER_VALIDATED, POLICY_TRANSPORT_PRIMARY),
+ caps(TRANSPORT_WIFI)),
TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
caps(TRANSPORT_CELLULAR))
)
@@ -122,8 +133,8 @@
val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR))
val scores = listOf(
winner,
- TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
- POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_EVER_VALIDATED, POLICY_TRANSPORT_PRIMARY),
+ caps(TRANSPORT_WIFI)),
TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
caps(TRANSPORT_CELLULAR))
)
@@ -136,8 +147,8 @@
val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI))
val scores = listOf(
winner,
- TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
- POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI)),
+ TestScore(score(POLICY_EVER_VALIDATED, POLICY_TRANSPORT_PRIMARY),
+ caps(TRANSPORT_WIFI)),
TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)),
TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
caps(TRANSPORT_CELLULAR))
@@ -164,8 +175,7 @@
caps(TRANSPORT_CELLULAR))
val scores = listOf(
winner,
- TestScore(score(POLICY_EVER_VALIDATED_NOT_AVOIDED_WHEN_BAD,
- POLICY_EXITING), caps(TRANSPORT_WIFI))
+ TestScore(score(POLICY_EVER_VALIDATED, POLICY_EXITING), caps(TRANSPORT_WIFI))
)
assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index 082a016..ea3d392 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -87,8 +87,8 @@
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
initMockResources();
- when(mFactory.updateInterfaceLinkState(anyString(), anyBoolean())).thenReturn(false);
- when(mNetd.interfaceGetList()).thenReturn(new String[0]);
+ doReturn(false).when(mFactory).updateInterfaceLinkState(anyString(), anyBoolean());
+ doReturn(new String[0]).when(mNetd).interfaceGetList();
mHandlerThread = new HandlerThread(THREAD_NAME);
mHandlerThread.start();
tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd,
@@ -101,8 +101,8 @@
}
private void initMockResources() {
- when(mDeps.getInterfaceRegexFromResource(eq(mContext))).thenReturn("");
- when(mDeps.getInterfaceConfigFromResource(eq(mContext))).thenReturn(new String[0]);
+ doReturn("").when(mDeps).getInterfaceRegexFromResource(eq(mContext));
+ doReturn(new String[0]).when(mDeps).getInterfaceConfigFromResource(eq(mContext));
}
private void waitForIdle() {
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 14455fa..04db6d3 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -25,6 +25,7 @@
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
@@ -89,6 +90,7 @@
// related to networkStatsFactory is compiled to a minimal native library and loaded here.
System.loadLibrary("networkstatsfactorytestjni");
doReturn(mBpfNetMaps).when(mDeps).createBpfNetMaps(any());
+
mFactory = new NetworkStatsFactory(mContext, mDeps);
mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
}
@@ -462,6 +464,46 @@
assertNoStatsEntry(stats, "wlan0", 1029, SET_DEFAULT, 0x0);
}
+ @Test
+ public void testRemoveUidsStats() throws Exception {
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1)
+ .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE,
+ 256L, 16L, 512L, 32L, 0L)
+ .insertEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 64L, 3L, 1024L, 8L, 0L);
+
+ doReturn(stats).when(mDeps).getNetworkStatsDetail(anyInt(), any(),
+ anyInt());
+
+ final String[] ifaces = new String[]{TEST_IFACE};
+ final NetworkStats res = mFactory.readNetworkStatsDetail(UID_ALL, ifaces, TAG_ALL);
+
+ // Verify that the result of the mocked stats are expected.
+ assertValues(res, TEST_IFACE, UID_RED, 16L, 1L, 16L, 1L);
+ assertValues(res, TEST_IFACE, UID_BLUE, 256L, 16L, 512L, 32L);
+ assertValues(res, TEST_IFACE, UID_GREEN, 64L, 3L, 1024L, 8L);
+
+ // Assume the apps were removed.
+ final int[] removedUids = new int[]{UID_RED, UID_BLUE};
+ mFactory.removeUidsLocked(removedUids);
+
+ // Return empty stats for reading the result of removing uids stats later.
+ doReturn(buildEmptyStats()).when(mDeps).getNetworkStatsDetail(anyInt(), any(),
+ anyInt());
+
+ final NetworkStats removedUidsStats =
+ mFactory.readNetworkStatsDetail(UID_ALL, ifaces, TAG_ALL);
+
+ // Verify that the stats of the removed uids were removed.
+ assertValues(removedUidsStats, TEST_IFACE, UID_RED, 0L, 0L, 0L, 0L);
+ assertValues(removedUidsStats, TEST_IFACE, UID_BLUE, 0L, 0L, 0L, 0L);
+ assertValues(removedUidsStats, TEST_IFACE, UID_GREEN, 64L, 3L, 1024L, 8L);
+ }
+
+ private NetworkStats buildEmptyStats() {
+ return new NetworkStats(SystemClock.elapsedRealtime(), 0);
+ }
+
private NetworkStats parseNetworkStatsFromGoldenSample(int resourceId, int initialSize,
boolean consumeHeader, boolean checkActive, boolean isUidData) throws IOException {
final NetworkStats stats =
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index be44946..e27e4e2 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -25,6 +25,7 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_TEST;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkIdentity.OEM_PAID;
import static android.net.NetworkIdentity.OEM_PRIVATE;
@@ -48,6 +49,7 @@
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStatsHistory.FIELD_ALL;
import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_TEST;
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.OEM_MANAGED_NO;
import static android.net.NetworkTemplate.OEM_MANAGED_YES;
@@ -84,8 +86,8 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.AlarmManager;
@@ -107,6 +109,7 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
+import android.net.TestNetworkSpecifier;
import android.net.TetherStatsParcel;
import android.net.TetheringManager;
import android.net.UnderlyingNetworkInfo;
@@ -121,6 +124,7 @@
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Pair;
@@ -139,6 +143,7 @@
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.server.BpfNetMaps;
import com.android.server.net.NetworkStatsService.AlertObserver;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -172,6 +177,7 @@
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -209,9 +215,11 @@
private static final Network WIFI_NETWORK = new Network(100);
private static final Network MOBILE_NETWORK = new Network(101);
private static final Network VPN_NETWORK = new Network(102);
+ private static final Network TEST_NETWORK = new Network(103);
private static final Network[] NETWORKS_WIFI = new Network[]{ WIFI_NETWORK };
private static final Network[] NETWORKS_MOBILE = new Network[]{ MOBILE_NETWORK };
+ private static final Network[] NETWORKS_TEST = new Network[]{ TEST_NETWORK };
private static final long WAIT_TIMEOUT = 2 * 1000; // 2 secs
private static final int INVALID_TYPE = -1;
@@ -242,6 +250,10 @@
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
private TestBpfMap<U32, U8> mUidCounterSetMap = spy(new TestBpfMap<>(U32.class, U8.class));
+ @Mock
+ private BpfNetMaps mBpfNetMaps;
+ @Mock
+ private SkDestroyListener mSkDestroyListener;
private TestBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap = new TestBpfMap<>(
CookieTagMapKey.class, CookieTagMapValue.class);
@@ -336,9 +348,9 @@
final Context context = InstrumentationRegistry.getContext();
mServiceContext = new MockContext(context);
- when(mLocationPermissionChecker.checkCallersLocationPermission(
- any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
- when(sWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY);
+ doReturn(true).when(mLocationPermissionChecker).checkCallersLocationPermission(
+ any(), any(), anyInt(), anyBoolean(), any());
+ doReturn(TEST_WIFI_NETWORK_KEY).when(sWifiInfo).getNetworkKey();
mStatsDir = TestIoUtils.createTemporaryDirectory(getClass().getSimpleName());
mLegacyStatsDir = TestIoUtils.createTemporaryDirectory(
getClass().getSimpleName() + "-legacy");
@@ -494,6 +506,17 @@
public boolean isDebuggable() {
return mIsDebuggable == Boolean.TRUE;
}
+
+ @Override
+ public BpfNetMaps makeBpfNetMaps(Context ctx) {
+ return mBpfNetMaps;
+ }
+
+ @Override
+ public SkDestroyListener makeSkDestroyListener(
+ IBpfMap<CookieTagMapKey, CookieTagMapValue> cookieTagMap, Handler handler) {
+ return mSkDestroyListener;
+ }
};
}
@@ -817,7 +840,6 @@
assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0);
assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 1L, 16L, 1L, 0);
-
// now pretend two UIDs are uninstalled, which should migrate stats to
// special "removed" bucket.
mockDefaultSettings();
@@ -940,8 +962,10 @@
// Pretend that 5g mobile network comes online
final NetworkStateSnapshot[] mobileStates =
- new NetworkStateSnapshot[] {buildMobileState(IMSI_1), buildMobileState(TEST_IFACE2,
- IMSI_1, true /* isTemporarilyNotMetered */, false /* isRoaming */)};
+ new NetworkStateSnapshot[] {buildMobileState(IMSI_1), buildStateOfTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR, TYPE_MOBILE,
+ TEST_IFACE2, IMSI_1, null /* wifiNetworkKey */,
+ true /* isTemporarilyNotMetered */, false /* isRoaming */)};
setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_NR);
mService.notifyNetworkStatus(NETWORKS_MOBILE, mobileStates,
getActiveIface(mobileStates), new UnderlyingNetworkInfo[0]);
@@ -1070,8 +1094,8 @@
// TODO: support per IMSI state
private void setMobileRatTypeAndWaitForIdle(int ratType) {
- when(mNetworkStatsSubscriptionsMonitor.getRatTypeForSubscriberId(anyString()))
- .thenReturn(ratType);
+ doReturn(ratType).when(mNetworkStatsSubscriptionsMonitor)
+ .getRatTypeForSubscriberId(anyString());
mService.handleOnCollapsedRatTypeChanged();
HandlerUtils.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
}
@@ -1173,6 +1197,41 @@
}
@Test
+ public void testQueryTestNetworkUsage() throws Exception {
+ final NetworkTemplate templateTestAll = new NetworkTemplate.Builder(MATCH_TEST).build();
+ final NetworkTemplate templateTestIface1 = new NetworkTemplate.Builder(MATCH_TEST)
+ .setWifiNetworkKeys(Set.of(TEST_IFACE)).build();
+ final NetworkTemplate templateTestIface2 = new NetworkTemplate.Builder(MATCH_TEST)
+ .setWifiNetworkKeys(Set.of(TEST_IFACE2)).build();
+ // Test networks might use interface as subscriberId to identify individual networks.
+ // Simulate both cases.
+ final NetworkStateSnapshot[] states =
+ new NetworkStateSnapshot[]{buildTestState(TEST_IFACE, TEST_IFACE),
+ buildTestState(TEST_IFACE2, null /* wifiNetworkKey */)};
+
+ // Test networks comes online.
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
+ mService.notifyNetworkStatus(NETWORKS_TEST, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+
+ // Create some traffic on both interfaces.
+ incrementCurrentTime(MINUTE_IN_MILLIS);
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12L, 18L, 14L, 1L, 0L))
+ .addEntry(new NetworkStats.Entry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7L, 3L, 5L, 1L, 1L)));
+ forcePollAndWaitForIdle();
+
+ // Verify test network templates gets stats. Stats of test networks without subscriberId
+ // can only be matched by templates without subscriberId requirement.
+ assertUidTotal(templateTestAll, UID_RED, 19L, 21L, 19L, 2L, 1);
+ assertUidTotal(templateTestIface1, UID_RED, 12L, 18L, 14L, 1L, 0);
+ assertUidTotal(templateTestIface2, UID_RED, 0L, 0L, 0L, 0L, 0);
+ }
+
+ @Test
public void testUidStatsForTransport() throws Exception {
// pretend that network comes online
mockDefaultSettings();
@@ -1319,8 +1378,10 @@
// pretend that network comes online
mockDefaultSettings();
NetworkStateSnapshot[] states =
- new NetworkStateSnapshot[] {buildMobileState(TEST_IFACE, IMSI_1,
- false /* isTemporarilyNotMetered */, true /* isRoaming */)};
+ new NetworkStateSnapshot[] {buildStateOfTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR, TYPE_MOBILE,
+ TEST_IFACE, IMSI_1, null /* wifiNetworkKey */,
+ false /* isTemporarilyNotMetered */, true /* isRoaming */)};
mockNetworkStatsSummary(buildEmptyStats());
mockNetworkStatsUidDetail(buildEmptyStats());
@@ -1478,8 +1539,8 @@
mUsageCallback.expectOnThresholdReached(request);
// Allow binder to disconnect
- when(mUsageCallbackBinder.unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt()))
- .thenReturn(true);
+ doReturn(true).when(mUsageCallbackBinder)
+ .unlinkToDeath(any(IBinder.DeathRecipient.class), anyInt());
// Unregister request
mService.unregisterUsageRequest(request);
@@ -1661,7 +1722,7 @@
}
private void setCombineSubtypeEnabled(boolean enable) {
- when(mSettings.getCombineSubtypeEnabled()).thenReturn(enable);
+ doReturn(enable).when(mSettings).getCombineSubtypeEnabled();
mHandler.post(() -> mContentObserver.onChange(false, Settings.Global
.getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED)));
waitForIdle();
@@ -1821,8 +1882,8 @@
*/
@Test
public void testEnforceTemplateLocationPermission() throws Exception {
- when(mLocationPermissionChecker.checkCallersLocationPermission(
- any(), any(), anyInt(), anyBoolean(), any())).thenReturn(false);
+ doReturn(false).when(mLocationPermissionChecker)
+ .checkCallersLocationPermission(any(), any(), anyInt(), anyBoolean(), any());
initWifiStats(buildWifiState(true, TEST_IFACE, IMSI_1));
assertThrows(SecurityException.class, () ->
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0));
@@ -1830,8 +1891,8 @@
assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
- when(mLocationPermissionChecker.checkCallersLocationPermission(
- any(), any(), anyInt(), anyBoolean(), any())).thenReturn(true);
+ doReturn(true).when(mLocationPermissionChecker)
+ .checkCallersLocationPermission(any(), any(), anyInt(), anyBoolean(), any());
assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
@@ -2027,6 +2088,59 @@
}
}
+ @Test
+ public void testStatsFactoryRemoveUids() throws Exception {
+ // pretend that network comes online
+ mockDefaultSettings();
+ NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
+
+ mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+
+ // Create some traffic
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ mockDefaultSettings();
+ final NetworkStats stats = new NetworkStats(getElapsedRealtime(), 1)
+ .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE,
+ 4096L, 258L, 512L, 32L, 0L)
+ .insertEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 64L, 3L, 1024L, 8L, 0L);
+ mockNetworkStatsUidDetail(stats);
+
+ forcePollAndWaitForIdle();
+
+ // Verify service recorded history
+ assertUidTotal(sTemplateWifi, UID_RED, 16L, 1L, 16L, 1L, 0);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 258L, 512L, 32L, 0);
+ assertUidTotal(sTemplateWifi, UID_GREEN, 64L, 3L, 1024L, 8L, 0);
+
+ // Simulate that the apps are removed.
+ final Intent intentBlue = new Intent(ACTION_UID_REMOVED);
+ intentBlue.putExtra(EXTRA_UID, UID_BLUE);
+ mServiceContext.sendBroadcast(intentBlue);
+
+ final Intent intentRed = new Intent(ACTION_UID_REMOVED);
+ intentRed.putExtra(EXTRA_UID, UID_RED);
+ mServiceContext.sendBroadcast(intentRed);
+
+ final int[] removedUids = {UID_BLUE, UID_RED};
+
+ final ArgumentCaptor<int[]> removedUidsCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mStatsFactory, times(2)).removeUidsLocked(removedUidsCaptor.capture());
+ final List<int[]> captureRemovedUids = removedUidsCaptor.getAllValues();
+ // Simulate that the stats are removed in NetworkStatsFactory.
+ if (captureRemovedUids.contains(removedUids)) {
+ stats.removeUids(removedUids);
+ }
+
+ // Verify the stats of the removed uid is removed.
+ assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L, 0L, 0L, 0);
+ assertUidTotal(sTemplateWifi, UID_GREEN, 64L, 3L, 1024L, 8L, 0);
+ }
+
private void assertShouldRunComparison(boolean expected, boolean isDebuggable) {
assertEquals("shouldRunComparison (debuggable=" + isDebuggable + "): ",
expected, mService.shouldRunComparison());
@@ -2106,11 +2220,11 @@
}
private void mockNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
- when(mStatsFactory.readNetworkStatsSummaryDev()).thenReturn(summary);
+ doReturn(summary).when(mStatsFactory).readNetworkStatsSummaryDev();
}
private void mockNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
- when(mStatsFactory.readNetworkStatsSummaryXt()).thenReturn(summary);
+ doReturn(summary).when(mStatsFactory).readNetworkStatsSummaryXt();
}
private void mockNetworkStatsUidDetail(NetworkStats detail) throws Exception {
@@ -2120,11 +2234,11 @@
private void mockNetworkStatsUidDetail(NetworkStats detail,
TetherStatsParcel[] tetherStatsParcels) throws Exception {
- when(mStatsFactory.readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL))
- .thenReturn(detail);
+ doReturn(detail).when(mStatsFactory)
+ .readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
// also include tethering details, since they are folded into UID
- when(mNetd.tetherGetStats()).thenReturn(tetherStatsParcels);
+ doReturn(tetherStatsParcels).when(mNetd).tetherGetStats();
}
private void mockDefaultSettings() throws Exception {
@@ -2132,22 +2246,22 @@
}
private void mockSettings(long bucketDuration, long deleteAge) throws Exception {
- when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
- when(mSettings.getPollDelay()).thenReturn(0L);
- when(mSettings.getSampleEnabled()).thenReturn(true);
- when(mSettings.getCombineSubtypeEnabled()).thenReturn(false);
+ doReturn(HOUR_IN_MILLIS).when(mSettings).getPollInterval();
+ doReturn(0L).when(mSettings).getPollDelay();
+ doReturn(true).when(mSettings).getSampleEnabled();
+ doReturn(false).when(mSettings).getCombineSubtypeEnabled();
final Config config = new Config(bucketDuration, deleteAge, deleteAge);
- when(mSettings.getDevConfig()).thenReturn(config);
- when(mSettings.getXtConfig()).thenReturn(config);
- when(mSettings.getUidConfig()).thenReturn(config);
- when(mSettings.getUidTagConfig()).thenReturn(config);
+ doReturn(config).when(mSettings).getDevConfig();
+ doReturn(config).when(mSettings).getXtConfig();
+ doReturn(config).when(mSettings).getUidConfig();
+ doReturn(config).when(mSettings).getUidTagConfig();
- when(mSettings.getGlobalAlertBytes(anyLong())).thenReturn(MB_IN_BYTES);
- when(mSettings.getDevPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
- when(mSettings.getXtPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
- when(mSettings.getUidPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
- when(mSettings.getUidTagPersistBytes(anyLong())).thenReturn(MB_IN_BYTES);
+ doReturn(MB_IN_BYTES).when(mSettings).getGlobalAlertBytes(anyLong());
+ doReturn(MB_IN_BYTES).when(mSettings).getDevPersistBytes(anyLong());
+ doReturn(MB_IN_BYTES).when(mSettings).getXtPersistBytes(anyLong());
+ doReturn(MB_IN_BYTES).when(mSettings).getUidPersistBytes(anyLong());
+ doReturn(MB_IN_BYTES).when(mSettings).getUidTagPersistBytes(anyLong());
}
private void assertStatsFilesExist(boolean exist) {
@@ -2189,24 +2303,34 @@
}
private static NetworkStateSnapshot buildMobileState(String subscriberId) {
- return buildMobileState(TEST_IFACE, subscriberId, false /* isTemporarilyNotMetered */,
- false /* isRoaming */);
+ return buildStateOfTransport(NetworkCapabilities.TRANSPORT_CELLULAR, TYPE_MOBILE,
+ TEST_IFACE, subscriberId, null /* wifiNetworkKey */,
+ false /* isTemporarilyNotMetered */, false /* isRoaming */);
}
- private static NetworkStateSnapshot buildMobileState(String iface, String subscriberId,
+ private static NetworkStateSnapshot buildTestState(@NonNull String iface,
+ @Nullable String wifiNetworkKey) {
+ return buildStateOfTransport(NetworkCapabilities.TRANSPORT_TEST, TYPE_TEST,
+ iface, null /* subscriberId */, wifiNetworkKey,
+ false /* isTemporarilyNotMetered */, false /* isRoaming */);
+ }
+
+ private static NetworkStateSnapshot buildStateOfTransport(int transport, int legacyType,
+ String iface, String subscriberId, String wifiNetworkKey,
boolean isTemporarilyNotMetered, boolean isRoaming) {
final LinkProperties prop = new LinkProperties();
prop.setInterfaceName(iface);
final NetworkCapabilities capabilities = new NetworkCapabilities();
- if (isTemporarilyNotMetered) {
- capabilities.addCapability(
- NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED);
- }
+ capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+ isTemporarilyNotMetered);
capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming);
- capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ capabilities.addTransportType(transport);
+ if (legacyType == TYPE_TEST && !TextUtils.isEmpty(wifiNetworkKey)) {
+ capabilities.setNetworkSpecifier(new TestNetworkSpecifier(wifiNetworkKey));
+ }
return new NetworkStateSnapshot(
- MOBILE_NETWORK, capabilities, prop, subscriberId, TYPE_MOBILE);
+ MOBILE_NETWORK, capabilities, prop, subscriberId, legacyType);
}
private NetworkStats buildEmptyStats() {