Merge "Replace requestNetwork with registerSystemDefaultNetworkCallback"
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index a6627fe..59d6575 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -1,2 +1,7 @@
lorenzo@google.com
-satk@google.com
+satk@google.com #{LAST_RESORT_SUGGESTION}
+
+# For cherry-picks of CLs that are already merged in aosp/master.
+jchalard@google.com #{LAST_RESORT_SUGGESTION}
+maze@google.com #{LAST_RESORT_SUGGESTION}
+reminv@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
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/Android.bp b/Tethering/Android.bp
index f3d6aee..3ab1ec2 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -26,6 +26,21 @@
}
java_defaults {
+ name: "TetheringExternalLibs",
+ // Libraries not including Tethering's own framework-tethering (different flavors of that one
+ // are needed depending on the build rule)
+ libs: [
+ "framework-connectivity.stubs.module_lib",
+ "framework-connectivity-t.stubs.module_lib",
+ "framework-statsd.stubs.module_lib",
+ "framework-wifi",
+ "framework-bluetooth",
+ "unsupportedappusage",
+ ],
+ defaults_visibility: ["//visibility:private"],
+}
+
+java_defaults {
name: "TetheringAndroidLibraryDefaults",
srcs: [
"apishim/**/*.java",
@@ -51,14 +66,9 @@
"netd-client",
"tetheringstatsprotos",
],
+ defaults: ["TetheringExternalLibs"],
libs: [
- "framework-connectivity",
- "framework-connectivity-t.stubs.module_lib",
- "framework-statsd.stubs.module_lib",
"framework-tethering.impl",
- "framework-wifi",
- "framework-bluetooth",
- "unsupportedappusage",
],
plugins: ["java_api_finder"],
manifest: "AndroidManifestBase.xml",
@@ -148,9 +158,17 @@
resource_dirs: [
"res",
],
+ // Libs are not actually needed to build here since build rules using these defaults are just
+ // packaging the TetheringApiXLibs in APKs, but they are necessary so that R8 has the right
+ // references to optimize the code. Without these, there will be missing class warnings and code
+ // may be wrongly optimized.
+ // R8 runs after jarjar, so the framework-X libraries need to be the post-jarjar artifacts
+ // (framework-tethering.impl), if they are not just stubs, so that the name of jarjared
+ // classes match.
+ // TODO(b/229727645): ensure R8 fails the build fully if libraries are missing
+ defaults: ["TetheringExternalLibs"],
libs: [
- "framework-tethering",
- "framework-wifi",
+ "framework-tethering.impl",
],
jarjar_rules: "jarjar-rules.txt",
optimize: {
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/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index b3f0cf2..cd914d3 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -1273,8 +1273,10 @@
@Override
public int hashCode() {
- return Objects.hash(mTetherableBluetoothRegexs, mTetherableUsbRegexs,
- mTetherableWifiRegexs);
+ return Objects.hash(
+ Arrays.hashCode(mTetherableBluetoothRegexs),
+ Arrays.hashCode(mTetherableUsbRegexs),
+ Arrays.hashCode(mTetherableWifiRegexs));
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 7e0a589..142a0b9 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -60,11 +60,12 @@
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;
import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
@@ -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,
@@ -575,7 +576,7 @@
if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream, rule.srcMac,
NULL_MAC_ADDRESS, NULL_MAC_ADDRESS, NetworkStackConstants.ETHER_MTU)) {
mLog.e("Failed to enable upstream IPv6 forwarding from "
- + mInterfaceNames.get(downstream) + " to " + mInterfaceNames.get(upstream));
+ + getIfName(downstream) + " to " + getIfName(upstream));
}
}
@@ -616,7 +617,7 @@
if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream,
rule.srcMac)) {
mLog.e("Failed to disable upstream IPv6 forwarding from "
- + mInterfaceNames.get(downstream) + " to " + mInterfaceNames.get(upstream));
+ + getIfName(downstream) + " to " + getIfName(upstream));
}
}
@@ -960,8 +961,12 @@
}
// TODO: make mInterfaceNames accessible to the shim and move this code to there.
- private String getIfName(long ifindex) {
- return mInterfaceNames.get((int) ifindex, Long.toString(ifindex));
+ // This function should only be used for logging/dump purposes.
+ private String getIfName(int ifindex) {
+ // TODO: return something more useful on lookup failure
+ // likely use the 'iface_index_name_map' bpf map and/or if_nametoindex
+ // perhaps should even check that all 3 match if available.
+ return mInterfaceNames.get(ifindex, Integer.toString(ifindex));
}
/**
@@ -998,9 +1003,9 @@
pw.println("Forwarding rules:");
pw.increaseIndent();
- dumpIpv6UpstreamRules(pw);
- dumpIpv6ForwardingRules(pw);
- dumpIpv4ForwardingRules(pw);
+ dumpIpv6ForwardingRulesByDownstream(pw);
+ dumpBpfForwardingRulesIpv6(pw);
+ dumpBpfForwardingRulesIpv4(pw);
pw.decreaseIndent();
pw.println();
@@ -1038,12 +1043,12 @@
for (int i = 0; i < mStats.size(); i++) {
final int upstreamIfindex = mStats.keyAt(i);
final ForwardedStats stats = mStats.get(upstreamIfindex);
- pw.println(String.format("%d(%s) - %s", upstreamIfindex, mInterfaceNames.get(
- upstreamIfindex), stats.toString()));
+ pw.println(String.format("%d(%s) - %s", upstreamIfindex, getIfName(upstreamIfindex),
+ stats.toString()));
}
}
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;
@@ -1059,9 +1064,12 @@
}
}
- private void dumpIpv6ForwardingRules(@NonNull IndentingPrintWriter pw) {
+ private void dumpIpv6ForwardingRulesByDownstream(@NonNull IndentingPrintWriter pw) {
+ pw.println("IPv6 Forwarding rules by downstream interface:");
+ pw.increaseIndent();
if (mIpv6ForwardingRules.size() == 0) {
pw.println("No IPv6 rules");
+ pw.decreaseIndent();
return;
}
@@ -1071,28 +1079,31 @@
// The rule downstream interface index is paired with the interface name from
// IpServer#interfaceName. See #startIPv6, #updateIpv6ForwardingRules in IpServer.
final String downstreamIface = ipServer.interfaceName();
- pw.println("[" + downstreamIface + "]: iif(iface) oif(iface) v6addr srcmac dstmac");
+ pw.println("[" + downstreamIface + "]: iif(iface) oif(iface) v6addr "
+ + "[srcmac] [dstmac]");
pw.increaseIndent();
LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = entry.getValue();
for (Ipv6ForwardingRule rule : rules.values()) {
final int upstreamIfindex = rule.upstreamIfindex;
- pw.println(String.format("%d(%s) %d(%s) %s %s %s", upstreamIfindex,
- mInterfaceNames.get(upstreamIfindex), rule.downstreamIfindex,
- downstreamIface, rule.address.getHostAddress(), rule.srcMac, rule.dstMac));
+ pw.println(String.format("%d(%s) %d(%s) %s [%s] [%s]", upstreamIfindex,
+ getIfName(upstreamIfindex), rule.downstreamIfindex,
+ getIfName(rule.downstreamIfindex), rule.address.getHostAddress(),
+ rule.srcMac, rule.dstMac));
}
pw.decreaseIndent();
}
+ pw.decreaseIndent();
}
- private String ipv6UpstreamRuletoString(TetherUpstream6Key key, Tether6Value value) {
- return String.format("%d(%s) %s -> %d(%s) %04x %s %s",
+ private String ipv6UpstreamRuleToString(TetherUpstream6Key key, Tether6Value value) {
+ return String.format("%d(%s) [%s] -> %d(%s) %04x [%s] [%s]",
key.iif, getIfName(key.iif), key.dstMac, value.oif, getIfName(value.oif),
value.ethProto, value.ethSrcMac, value.ethDstMac);
}
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;
@@ -1101,13 +1112,57 @@
pw.println("No IPv6 upstream rules");
return;
}
- map.forEach((k, v) -> pw.println(ipv6UpstreamRuletoString(k, v)));
+ map.forEach((k, v) -> pw.println(ipv6UpstreamRuleToString(k, v)));
} catch (ErrnoException | IOException e) {
pw.println("Error dumping IPv6 upstream map: " + e);
}
}
- private <K extends Struct, V extends Struct> void dumpRawMap(BpfMap<K, V> map,
+ private String ipv6DownstreamRuleToString(TetherDownstream6Key key, Tether6Value value) {
+ final String neigh6;
+ try {
+ neigh6 = InetAddress.getByAddress(key.neigh6).getHostAddress();
+ } catch (UnknownHostException impossible) {
+ throw new AssertionError("IP address array not valid IPv6 address!");
+ }
+ return String.format("%d(%s) [%s] %s -> %d(%s) %04x [%s] [%s]",
+ key.iif, getIfName(key.iif), key.dstMac, neigh6, value.oif, getIfName(value.oif),
+ value.ethProto, value.ethSrcMac, value.ethDstMac);
+ }
+
+ private void dumpIpv6DownstreamRules(IndentingPrintWriter pw) {
+ try (IBpfMap<TetherDownstream6Key, Tether6Value> map = mDeps.getBpfDownstream6Map()) {
+ if (map == null) {
+ pw.println("No IPv6 downstream");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No IPv6 downstream rules");
+ return;
+ }
+ map.forEach((k, v) -> pw.println(ipv6DownstreamRuleToString(k, v)));
+ } catch (ErrnoException | IOException e) {
+ pw.println("Error dumping IPv6 downstream map: " + e);
+ }
+ }
+
+ // TODO: use dump utils with headerline and lambda which prints key and value to reduce
+ // duplicate bpf map dump code.
+ private void dumpBpfForwardingRulesIpv6(IndentingPrintWriter pw) {
+ pw.println("IPv6 Upstream: iif(iface) [inDstMac] -> oif(iface) etherType [outSrcMac] "
+ + "[outDstMac]");
+ pw.increaseIndent();
+ dumpIpv6UpstreamRules(pw);
+ pw.decreaseIndent();
+
+ pw.println("IPv6 Downstream: iif(iface) [inDstMac] neigh6 -> oif(iface) etherType "
+ + "[outSrcMac] [outDstMac]");
+ pw.increaseIndent();
+ dumpIpv6DownstreamRules(pw);
+ pw.decreaseIndent();
+ }
+
+ 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");
@@ -1138,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);
@@ -1146,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);
@@ -1191,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;
@@ -1203,11 +1258,11 @@
map.forEach((k, v) -> pw.println(ipv4RuleToString(now, downstream, k, v)));
}
- private void dumpIpv4ForwardingRules(IndentingPrintWriter pw) {
+ 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();
@@ -1229,18 +1284,18 @@
pw.println("No counter support");
return;
}
- try (BpfMap<U32, U32> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, BpfMap.BPF_F_RDONLY,
- U32.class, U32.class)) {
+ try (IBpfMap<S32, S32> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, BpfMap.BPF_F_RDONLY,
+ S32.class, S32.class)) {
map.forEach((k, v) -> {
String counterName;
try {
- counterName = sBpfCounterNames[(int) k.val];
+ counterName = sBpfCounterNames[k.val];
} catch (IndexOutOfBoundsException e) {
// Should never happen because this code gets the counter name from the same
// include file as the BPF program that increments the counter.
Log.wtf(TAG, "Unknown tethering counter type " + k.val);
- counterName = Long.toString(k.val);
+ counterName = Integer.toString(k.val);
}
if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val));
});
@@ -1250,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;
@@ -1768,8 +1823,7 @@
// TODO: Perhaps stop the coordinator.
boolean success = updateDataLimit(upstreamIfindex);
if (!success) {
- final String iface = mInterfaceNames.get(upstreamIfindex);
- mLog.e("Setting data limit for " + iface + " failed.");
+ mLog.e("Setting data limit for " + getIfName(upstreamIfindex) + " failed.");
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java b/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java
index 4283c1b..997080c 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java
@@ -22,10 +22,10 @@
/** The key of BpfMap which is used for mapping interface index. */
public class TetherDevKey extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long ifIndex; // interface index
+ @Field(order = 0, type = Type.S32)
+ public final int ifIndex; // interface index
- public TetherDevKey(final long ifIndex) {
+ public TetherDevKey(final int ifIndex) {
this.ifIndex = ifIndex;
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java b/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java
index 1cd99b5..b6e0c73 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java
@@ -22,10 +22,10 @@
/** The key of BpfMap which is used for mapping interface index. */
public class TetherDevValue extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long ifIndex; // interface index
+ @Field(order = 0, type = Type.S32)
+ public final int ifIndex; // interface index
- public TetherDevValue(final long ifIndex) {
+ public TetherDevValue(final int ifIndex) {
this.ifIndex = ifIndex;
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java b/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
index a08ad4a..e34b3f1 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
@@ -32,8 +32,8 @@
/** The key of BpfMap which is used for bpf offload. */
public class TetherDownstream6Key extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long iif; // The input interface index.
+ @Field(order = 0, type = Type.S32)
+ public final int iif; // The input interface index.
@Field(order = 1, type = Type.EUI48, padding = 2)
public final MacAddress dstMac; // Destination ethernet mac address (zeroed iff rawip ingress).
@@ -41,7 +41,7 @@
@Field(order = 2, type = Type.ByteArray, arraysize = 16)
public final byte[] neigh6; // The destination IPv6 address.
- public TetherDownstream6Key(final long iif, @NonNull final MacAddress dstMac,
+ public TetherDownstream6Key(final int iif, @NonNull final MacAddress dstMac,
final byte[] neigh6) {
Objects.requireNonNull(dstMac);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java b/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
index bc9bb47..a7e8ccf 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
@@ -22,10 +22,10 @@
/** The key of BpfMap which is used for tethering per-interface limit. */
public class TetherLimitKey extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long ifindex; // upstream interface index
+ @Field(order = 0, type = Type.S32)
+ public final int ifindex; // upstream interface index
- public TetherLimitKey(final long ifindex) {
+ public TetherLimitKey(final int ifindex) {
this.ifindex = ifindex;
}
@@ -43,7 +43,7 @@
@Override
public int hashCode() {
- return Long.hashCode(ifindex);
+ return Integer.hashCode(ifindex);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 75f63c8..1f3fc11 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -97,6 +97,7 @@
import android.net.TetheringInterface;
import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
+import android.net.Uri;
import android.net.ip.IpServer;
import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
@@ -343,9 +344,8 @@
mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
});
- mSettingsObserver = new SettingsObserver(mHandler);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(TETHER_FORCE_USB_FUNCTIONS), false, mSettingsObserver);
+ mSettingsObserver = new SettingsObserver(mContext, mHandler);
+ mSettingsObserver.startObserve();
mStateReceiver = new StateReceiver();
@@ -397,18 +397,42 @@
}
private class SettingsObserver extends ContentObserver {
- SettingsObserver(Handler handler) {
+ private final Uri mForceUsbFunctions;
+ private final Uri mTetherSupported;
+ private final Context mContext;
+
+ SettingsObserver(Context ctx, Handler handler) {
super(handler);
+ mContext = ctx;
+ mForceUsbFunctions = Settings.Global.getUriFor(TETHER_FORCE_USB_FUNCTIONS);
+ mTetherSupported = Settings.Global.getUriFor(Settings.Global.TETHER_SUPPORTED);
+ }
+
+ public void startObserve() {
+ mContext.getContentResolver().registerContentObserver(mForceUsbFunctions, false, this);
+ mContext.getContentResolver().registerContentObserver(mTetherSupported, false, this);
}
@Override
public void onChange(boolean selfChange) {
- mLog.i("OBSERVED Settings change");
- final boolean isUsingNcm = mConfig.isUsingNcm();
- updateConfiguration();
- if (isUsingNcm != mConfig.isUsingNcm()) {
- stopTetheringInternal(TETHERING_USB);
- stopTetheringInternal(TETHERING_NCM);
+ Log.wtf(TAG, "Should never be reached.");
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mForceUsbFunctions.equals(uri)) {
+ mLog.i("OBSERVED TETHER_FORCE_USB_FUNCTIONS settings change");
+ final boolean isUsingNcm = mConfig.isUsingNcm();
+ updateConfiguration();
+ if (isUsingNcm != mConfig.isUsingNcm()) {
+ stopTetheringInternal(TETHERING_USB);
+ stopTetheringInternal(TETHERING_NCM);
+ }
+ } else if (mTetherSupported.equals(uri)) {
+ mLog.i("OBSERVED TETHER_SUPPORTED settings change");
+ updateSupportedDownstreams(mConfig);
+ } else {
+ mLog.e("Unexpected settings change: " + uri);
}
}
}
@@ -1322,7 +1346,9 @@
}
private void handleUserRestrictionAction() {
- mTetheringRestriction.onUserRestrictionsChanged();
+ if (mTetheringRestriction.onUserRestrictionsChanged()) {
+ updateSupportedDownstreams(mConfig);
+ }
}
private void handleDataSaverChanged() {
@@ -1350,6 +1376,8 @@
return getTetheredIfaces().length > 0;
}
+ // TODO: Refine TetheringTest then remove UserRestrictionActionListener class and handle
+ // onUserRestrictionsChanged inside Tethering#handleUserRestrictionAction directly.
@VisibleForTesting
protected static class UserRestrictionActionListener {
private final UserManager mUserMgr;
@@ -1365,7 +1393,8 @@
mDisallowTethering = false;
}
- public void onUserRestrictionsChanged() {
+ // return whether tethering disallowed is changed.
+ public boolean onUserRestrictionsChanged() {
// getUserRestrictions gets restriction for this process' user, which is the primary
// user. This is fine because DISALLOW_CONFIG_TETHERING can only be set on the primary
// user. See UserManager.DISALLOW_CONFIG_TETHERING.
@@ -1376,15 +1405,13 @@
mDisallowTethering = newlyDisallowed;
final boolean tetheringDisallowedChanged = (newlyDisallowed != prevDisallowed);
- if (!tetheringDisallowedChanged) {
- return;
- }
+ if (!tetheringDisallowedChanged) return false;
if (!newlyDisallowed) {
// Clear the restricted notification when user is allowed to have tethering
// function.
mNotificationUpdater.tetheringRestrictionLifted();
- return;
+ return true;
}
if (mTethering.isTetheringActive()) {
@@ -1395,6 +1422,8 @@
// Untether from all downstreams since tethering is disallowed.
mTethering.untetherAll();
}
+
+ return true;
// TODO(b/148139325): send tetheringSupported on restriction change
}
}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 9aa2cff..11e3dc0 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -28,6 +28,7 @@
static_libs: [
"NetworkStackApiStableLib",
"androidx.test.rules",
+ "cts-net-utils",
"mockito-target-extended-minus-junit4",
"net-tests-utils",
"net-utils-device-common-bpf",
diff --git a/Tethering/tests/integration/AndroidManifest.xml b/Tethering/tests/integration/AndroidManifest.xml
index c89c556..7527913 100644
--- a/Tethering/tests/integration/AndroidManifest.xml
+++ b/Tethering/tests/integration/AndroidManifest.xml
@@ -16,12 +16,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.networkstack.tethering.tests.integration">
- <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!-- The test need CHANGE_NETWORK_STATE permission to use requestNetwork API to setup test
network. Since R shell application don't have such permission, grant permission to the test
here. TODO: Remove CHANGE_NETWORK_STATE permission here and use adopt shell perssion to
obtain CHANGE_NETWORK_STATE for testing once R device is no longer supported. -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 06586e2..39812cd 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -16,23 +16,24 @@
package android.net;
-import static android.Manifest.permission.ACCESS_NETWORK_STATE;
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;
import static android.Manifest.permission.TETHER_PRIVILEGED;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringTester.TestDnsPacket;
import static android.net.TetheringTester.isExpectedIcmpv6Packet;
+import static android.net.TetheringTester.isExpectedUdpDnsPacket;
import static android.net.TetheringTester.isExpectedUdpPacket;
import static android.system.OsConstants.IPPROTO_IP;
import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_UDP;
-import static com.android.net.module.util.BpfDump.BASE64_DELIMITER;
import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
import static com.android.net.module.util.HexDump.dumpHexString;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
@@ -42,6 +43,7 @@
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -54,20 +56,20 @@
import android.app.UiAutomation;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.EthernetManager.TetheredInterfaceCallback;
import android.net.EthernetManager.TetheredInterfaceRequest;
import android.net.TetheringManager.StartTetheringCallback;
import android.net.TetheringManager.TetheringEventCallback;
import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringTester.TetheredDevice;
+import android.net.cts.util.CtsNetUtils;;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.VintfRuntimeInfo;
-import android.text.TextUtils;
-import android.util.Base64;
import android.util.Log;
import android.util.Pair;
@@ -77,6 +79,8 @@
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;
import com.android.net.module.util.Struct;
@@ -84,7 +88,9 @@
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
import com.android.net.module.util.bpf.TetherStatsValue;
+import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DeviceInfoUtils;
@@ -107,7 +113,6 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -129,12 +134,19 @@
private static final String TAG = EthernetTetheringTest.class.getSimpleName();
private static final int TIMEOUT_MS = 5000;
+ // Used to check if any tethering interface is available. Choose 200ms to be request timeout
+ // because the average interface requested time on cuttlefish@acloud is around 10ms.
+ // See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable.
+ private static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
private static final int DUMP_POLLING_MAX_RETRY = 100;
private static final int DUMP_POLLING_INTERVAL_MS = 50;
// 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;
@@ -144,7 +156,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");
@@ -156,6 +168,8 @@
private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
+ private static final short DNS_PORT = 53;
+
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
@@ -165,9 +179,71 @@
private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
private static final short HOP_LIMIT = 0x40;
+ // TODO: use class DnsPacket to build DNS query and reply message once DnsPacket supports
+ // building packet for given arguments.
+ private static final ByteBuffer DNS_QUERY = ByteBuffer.wrap(new byte[] {
+ // scapy.DNS(
+ // id=0xbeef,
+ // qr=0,
+ // qd=scapy.DNSQR(qname="hello.example.com"))
+ //
+ /* Header */
+ (byte) 0xbe, (byte) 0xef, /* Transaction ID: 0xbeef */
+ (byte) 0x01, (byte) 0x00, /* Flags: rd */
+ (byte) 0x00, (byte) 0x01, /* Questions: 1 */
+ (byte) 0x00, (byte) 0x00, /* Answer RRs: 0 */
+ (byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
+ (byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
+ /* Queries */
+ (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
+ (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
+ (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
+ (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
+ (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
+ (byte) 0x00, (byte) 0x01, /* Type: A */
+ (byte) 0x00, (byte) 0x01 /* Class: IN */
+ });
+
+ private static final byte[] DNS_REPLY = new byte[] {
+ // scapy.DNS(
+ // id=0,
+ // qr=1,
+ // qd=scapy.DNSQR(qname="hello.example.com"),
+ // an=scapy.DNSRR(rrname="hello.example.com", rdata='1.2.3.4'))
+ //
+ /* Header */
+ (byte) 0x00, (byte) 0x00, /* Transaction ID: 0x0, must be updated by dns query id */
+ (byte) 0x81, (byte) 0x00, /* Flags: qr rd */
+ (byte) 0x00, (byte) 0x01, /* Questions: 1 */
+ (byte) 0x00, (byte) 0x01, /* Answer RRs: 1 */
+ (byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
+ (byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
+ /* Queries */
+ (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
+ (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
+ (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
+ (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
+ (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
+ (byte) 0x00, (byte) 0x01, /* Type: A */
+ (byte) 0x00, (byte) 0x01, /* Class: IN */
+ /* Answers */
+ (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
+ (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
+ (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
+ (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
+ (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
+ (byte) 0x00, (byte) 0x01, /* Type: A */
+ (byte) 0x00, (byte) 0x01, /* Class: IN */
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* Time to live: 0 */
+ (byte) 0x00, (byte) 0x04, /* Data length: 4 */
+ (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
+ };
+
private final Context mContext = InstrumentationRegistry.getContext();
private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
+ private final PackageManager mPackageManager = mContext.getPackageManager();
+ private final CtsNetUtils mCtsNetUtils = new CtsNetUtils(mContext);
private TestNetworkInterface mDownstreamIface;
private HandlerThread mHandlerThread;
@@ -186,30 +262,25 @@
@Before
public void setUp() throws Exception {
- // Needed to create a TestNetworkInterface, to call requestTetheredInterface, and to receive
- // tethered client callbacks. The restricted networks permission is needed to ensure that
- // EthernetManager#isAvailable will correctly return true on devices where Ethernet is
- // marked restricted, like cuttlefish. The dump permission is needed to verify bpf related
- // functions via dumpsys output.
- mUiAutomation.adoptShellPermissionIdentity(
- MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE,
- CONNECTIVITY_USE_RESTRICTED_NETWORKS, DUMP);
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
- mRunTests = isEthernetTetheringSupported();
+ mRunTests = runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () ->
+ mTm.isTetheringSupported());
assumeTrue(mRunTests);
mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
}
private void cleanUp() throws Exception {
- mTm.setPreferTestNetworks(false);
+ setPreferTestNetworks(false);
if (mUpstreamTracker != null) {
- mUpstreamTracker.teardown();
- mUpstreamTracker = null;
+ runAsShell(MANAGE_TEST_NETWORKS, () -> {
+ mUpstreamTracker.teardown();
+ mUpstreamTracker = null;
+ });
}
if (mUpstreamReader != null) {
TapPacketReader reader = mUpstreamReader;
@@ -217,20 +288,26 @@
mUpstreamReader = null;
}
- mTm.stopTethering(TETHERING_ETHERNET);
- if (mTetheringEventCallback != null) {
- mTetheringEventCallback.awaitInterfaceUntethered();
- mTetheringEventCallback.unregister();
- mTetheringEventCallback = null;
- }
if (mDownstreamReader != null) {
TapPacketReader reader = mDownstreamReader;
mHandler.post(() -> reader.stop());
mDownstreamReader = null;
}
- mTetheredInterfaceRequester.release();
- mEm.setIncludeTestInterfaces(false);
+
+ // To avoid flaky which caused by the next test started but the previous interface is not
+ // untracked from EthernetTracker yet. Just delete the test interface without explicitly
+ // calling TetheringManager#stopTethering could let EthernetTracker untrack the test
+ // interface from server mode before tethering stopped. Thus, awaitInterfaceUntethered
+ // could not only make sure tethering is stopped but also guarantee the test interface is
+ // untracked from EthernetTracker.
maybeDeleteTestInterface();
+ if (mTetheringEventCallback != null) {
+ mTetheringEventCallback.awaitInterfaceUntethered();
+ mTetheringEventCallback.unregister();
+ mTetheringEventCallback = null;
+ }
+ runAsShell(NETWORK_SETTINGS, () -> mTetheredInterfaceRequester.release());
+ setIncludeTestInterfaces(false);
}
@After
@@ -243,10 +320,51 @@
}
}
+ 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.
+ final TetheredInterfaceRequester requester = new TetheredInterfaceRequester(mHandler, mEm);
+ try {
+ // Use short timeout (200ms) for requesting an existing interface, if any, because
+ // it should reurn faster than requesting a new tethering interface. Using default
+ // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
+ // test module timeout on internal testing.
+ // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
+ // this check into #setUpOnce.
+ return requester.getInterface(AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS) != null;
+ } catch (TimeoutException e) {
+ return false;
+ } finally {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ requester.release();
+ });
+ }
+ }
+
+ private void setIncludeTestInterfaces(boolean include) {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mEm.setIncludeTestInterfaces(include);
+ });
+ }
+
+ private void setPreferTestNetworks(boolean prefer) {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTm.setPreferTestNetworks(prefer);
+ });
+ }
+
@Test
public void testVirtualEthernetAlreadyExists() throws Exception {
// This test requires manipulating packets. Skip if there is a physical Ethernet connected.
- assumeFalse(mEm.isAvailable());
+ assumeFalse(isInterfaceForTetheringAvailable());
mDownstreamIface = createTestInterface();
// This must be done now because as soon as setIncludeTestInterfaces(true) is called, the
@@ -256,7 +374,7 @@
int mtu = getMTU(mDownstreamIface);
Log.d(TAG, "Including test interfaces");
- mEm.setIncludeTestInterfaces(true);
+ setIncludeTestInterfaces(true);
final String iface = mTetheredInterfaceRequester.getInterface();
assertEquals("TetheredInterfaceCallback for unexpected interface",
@@ -268,11 +386,11 @@
@Test
public void testVirtualEthernet() throws Exception {
// This test requires manipulating packets. Skip if there is a physical Ethernet connected.
- assumeFalse(mEm.isAvailable());
+ assumeFalse(isInterfaceForTetheringAvailable());
CompletableFuture<String> futureIface = mTetheredInterfaceRequester.requestInterface();
- mEm.setIncludeTestInterfaces(true);
+ setIncludeTestInterfaces(true);
mDownstreamIface = createTestInterface();
@@ -285,9 +403,9 @@
@Test
public void testStaticIpv4() throws Exception {
- assumeFalse(mEm.isAvailable());
+ assumeFalse(isInterfaceForTetheringAvailable());
- mEm.setIncludeTestInterfaces(true);
+ setIncludeTestInterfaces(true);
mDownstreamIface = createTestInterface();
@@ -363,9 +481,9 @@
@Test
public void testLocalOnlyTethering() throws Exception {
- assumeFalse(mEm.isAvailable());
+ assumeFalse(isInterfaceForTetheringAvailable());
- mEm.setIncludeTestInterfaces(true);
+ setIncludeTestInterfaces(true);
mDownstreamIface = createTestInterface();
@@ -397,7 +515,7 @@
@Test
public void testPhysicalEthernet() throws Exception {
- assumeTrue(mEm.isAvailable());
+ assumeTrue(isInterfaceForTetheringAvailable());
// Do not run this test if adb is over network and ethernet is connected.
// It is likely the adb run over ethernet, the adb would break when ethernet is switching
// from client mode to server mode. See b/160389275.
@@ -438,6 +556,7 @@
private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
private final CountDownLatch mUpstreamLatch = new CountDownLatch(1);
+ private final CountDownLatch mCallbackRegisteredLatch = new CountDownLatch(1);
private final TetheringInterface mIface;
private final Network mExpectedUpstream;
@@ -516,6 +635,22 @@
mLocalOnlyStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
+ // Used to check if the callback has registered. When the callback is registered,
+ // onSupportedTetheringTypes is celled in onCallbackStarted(). After
+ // onSupportedTetheringTypes called, drop the permission for registering callback.
+ // See MyTetheringEventCallback#register, TetheringManager#onCallbackStarted.
+ @Override
+ public void onSupportedTetheringTypes(Set<Integer> supportedTypes) {
+ // Used to check callback registered.
+ mCallbackRegisteredLatch.countDown();
+ }
+
+ public void awaitCallbackRegistered() throws Exception {
+ if (!mCallbackRegisteredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Did not receive callback registered signal after " + TIMEOUT_MS + "ms");
+ }
+ }
+
public void awaitInterfaceUntethered() throws Exception {
// Don't block teardown if the interface was never tethered.
// This is racy because the interface might become tethered right after this check, but
@@ -572,10 +707,17 @@
}
}
- public Network awaitUpstreamChanged() throws Exception {
+ public Network awaitUpstreamChanged(boolean throwTimeoutException) throws Exception {
if (!mUpstreamLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Did not receive upstream " + (mAcceptAnyUpstream ? "any" : mExpectedUpstream)
- + " callback after " + TIMEOUT_MS + "ms");
+ final String errorMessage = "Did not receive upstream "
+ + (mAcceptAnyUpstream ? "any" : mExpectedUpstream)
+ + " callback after " + TIMEOUT_MS + "ms";
+
+ if (throwTimeoutException) {
+ throw new TimeoutException(errorMessage);
+ } else {
+ fail(errorMessage);
+ }
}
return mUpstream;
}
@@ -591,16 +733,34 @@
} else {
callback = new MyTetheringEventCallback(mTm, iface);
}
- mTm.registerTetheringEventCallback(mHandler::post, callback);
-
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTm.registerTetheringEventCallback(mHandler::post, callback);
+ // Need to hold the shell permission until callback is registered. This helps to avoid
+ // the test become flaky.
+ callback.awaitCallbackRegistered();
+ });
+ final CountDownLatch tetheringStartedLatch = new CountDownLatch(1);
StartTetheringCallback startTetheringCallback = new StartTetheringCallback() {
@Override
+ public void onTetheringStarted() {
+ Log.d(TAG, "Ethernet tethering started");
+ tetheringStartedLatch.countDown();
+ }
+
+ @Override
public void onTetheringFailed(int resultCode) {
fail("Unexpectedly got onTetheringFailed");
}
};
Log.d(TAG, "Starting Ethernet tethering");
- mTm.startTethering(request, mHandler::post /* executor */, startTetheringCallback);
+ runAsShell(TETHER_PRIVILEGED, () -> {
+ mTm.startTethering(request, mHandler::post /* executor */, startTetheringCallback);
+ // Binder call is an async call. Need to hold the shell permission until tethering
+ // started. This helps to avoid the test become flaky.
+ if (!tetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Did not receive tethering started callback after " + TIMEOUT_MS + "ms");
+ }
+ });
final int connectivityType = request.getConnectivityScope();
switch (connectivityType) {
@@ -708,12 +868,17 @@
public CompletableFuture<String> requestInterface() {
assertNull("BUG: more than one tethered interface request", mRequest);
Log.d(TAG, "Requesting tethered interface");
- mRequest = mEm.requestTetheredInterface(mHandler::post, this);
+ mRequest = runAsShell(NETWORK_SETTINGS, () ->
+ mEm.requestTetheredInterface(mHandler::post, this));
return mFuture;
}
+ public String getInterface(int timeout) throws Exception {
+ return requestInterface().get(timeout, TimeUnit.MILLISECONDS);
+ }
+
public String getInterface() throws Exception {
- return requestInterface().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return getInterface(TIMEOUT_MS);
}
public void release() {
@@ -764,8 +929,10 @@
}
private TestNetworkInterface createTestInterface() throws Exception {
- TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
- TestNetworkInterface iface = tnm.createTapInterface();
+ TestNetworkManager tnm = runAsShell(MANAGE_TEST_NETWORKS, () ->
+ mContext.getSystemService(TestNetworkManager.class));
+ TestNetworkInterface iface = runAsShell(MANAGE_TEST_NETWORKS, () ->
+ tnm.createTapInterface());
Log.d(TAG, "Created test interface " + iface.getInterfaceName());
return iface;
}
@@ -780,14 +947,14 @@
private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses,
final List<InetAddress> dnses) throws Exception {
- mTm.setPreferTestNetworks(true);
+ setPreferTestNetworks(true);
final LinkProperties lp = new LinkProperties();
lp.setLinkAddresses(addresses);
lp.setDnsServers(dnses);
lp.setNat64Prefix(TEST_NAT64PREFIX);
- return initTestNetwork(mContext, lp, TIMEOUT_MS);
+ return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(mContext, lp, TIMEOUT_MS));
}
@Test
@@ -1028,6 +1195,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);
@@ -1089,9 +1259,55 @@
}
}
+ // TODO: remove triggering upstream reselection once test network can replace selected upstream
+ // network in Tethering module.
+ private void maybeRetryTestedUpstreamChanged(final Network expectedUpstream,
+ final TimeoutException fallbackException) throws Exception {
+ // Fall back original exception because no way to reselect if there is no WIFI feature.
+ assertTrue(fallbackException.toString(), mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ // Try to toggle wifi network, if any, to reselect upstream network via default network
+ // switching. Because test network has higher priority than internet network, this can
+ // help selecting test network to be upstream network for testing. This tries to avoid
+ // the flaky upstream selection under multinetwork environment. Internet and test network
+ // upstream changed event order is not guaranteed. Once tethering selects non-test
+ // upstream {wifi, ..}, test network won't be selected anymore. If too many test cases
+ // trigger the reselection, the total test time may over test suite 1 minmute timeout.
+ // Probably need to disable/restore all internet networks in a common place of test
+ // process. Currently, EthernetTetheringTest is part of CTS test which needs wifi network
+ // connection if device has wifi feature. CtsNetUtils#toggleWifi() checks wifi connection
+ // during the toggling process.
+ // See Tethering#chooseUpstreamType, CtsNetUtils#toggleWifi.
+ // TODO: toggle cellular network if the device has no WIFI feature.
+ Log.d(TAG, "Toggle WIFI to retry upstream selection");
+ mCtsNetUtils.toggleWifi();
+
+ // Wait for expected upstream.
+ final CompletableFuture<Network> future = new CompletableFuture<>();
+ final TetheringEventCallback callback = new TetheringEventCallback() {
+ @Override
+ public void onUpstreamChanged(Network network) {
+ Log.d(TAG, "Got upstream changed: " + network);
+ if (Objects.equals(expectedUpstream, network)) {
+ future.complete(network);
+ }
+ }
+ };
+ try {
+ mTm.registerTetheringEventCallback(mHandler::post, callback);
+ assertEquals("onUpstreamChanged for unexpected network", expectedUpstream,
+ future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (TimeoutException e) {
+ throw new AssertionError("Did not receive upstream " + expectedUpstream
+ + " callback after " + TIMEOUT_MS + "ms");
+ } finally {
+ mTm.unregisterTetheringEventCallback(callback);
+ }
+ }
+
private TetheringTester initTetheringTester(List<LinkAddress> upstreamAddresses,
List<InetAddress> upstreamDnses) throws Exception {
- assumeFalse(mEm.isAvailable());
+ assumeFalse(isInterfaceForTetheringAvailable());
// MyTetheringEventCallback currently only support await first available upstream. Tethering
// may select internet network as upstream if test network is not available and not be
@@ -1099,7 +1315,7 @@
mUpstreamTracker = createTestUpstream(upstreamAddresses, upstreamDnses);
mDownstreamIface = createTestInterface();
- mEm.setIncludeTestInterfaces(true);
+ setIncludeTestInterfaces(true);
// Make sure EtherentTracker use "mDownstreamIface" as server mode interface.
assertEquals("TetheredInterfaceCallback for unexpected interface",
@@ -1107,8 +1323,17 @@
mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
mUpstreamTracker.getNetwork());
- assertEquals("onUpstreamChanged for unexpected network", mUpstreamTracker.getNetwork(),
- mTetheringEventCallback.awaitUpstreamChanged());
+
+ try {
+ assertEquals("onUpstreamChanged for test network", mUpstreamTracker.getNetwork(),
+ mTetheringEventCallback.awaitUpstreamChanged(
+ true /* throwTimeoutException */));
+ } catch (TimeoutException e) {
+ // Due to race condition inside tethering module, test network may not be selected as
+ // tethering upstream. Force tethering retry upstream if possible. If it is not
+ // possible to retry, fail the test with the original timeout exception.
+ maybeRetryTestedUpstreamChanged(mUpstreamTracker.getNetwork(), e);
+ }
mDownstreamReader = makePacketReader(mDownstreamIface);
mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
@@ -1193,42 +1418,18 @@
runUdp4Test(true /* verifyBpf */);
}
- @Nullable
- private <K extends Struct, V extends Struct> Pair<K, V> parseMapKeyValue(
- Class<K> keyClass, Class<V> valueClass, @NonNull String dumpStr) {
- Log.w(TAG, "Parsing string: " + dumpStr);
-
- String[] keyValueStrs = dumpStr.split(BASE64_DELIMITER);
- if (keyValueStrs.length != 2 /* key + value */) {
- fail("The length is " + keyValueStrs.length + " but expect 2. "
- + "Split string(s): " + TextUtils.join(",", keyValueStrs));
- }
-
- final byte[] keyBytes = Base64.decode(keyValueStrs[0], Base64.DEFAULT);
- Log.d(TAG, "keyBytes: " + dumpHexString(keyBytes));
- final ByteBuffer keyByteBuffer = ByteBuffer.wrap(keyBytes);
- keyByteBuffer.order(ByteOrder.nativeOrder());
- final K k = Struct.parse(keyClass, keyByteBuffer);
-
- final byte[] valueBytes = Base64.decode(keyValueStrs[1], Base64.DEFAULT);
- Log.d(TAG, "valueBytes: " + dumpHexString(valueBytes));
- final ByteBuffer valueByteBuffer = ByteBuffer.wrap(valueBytes);
- valueByteBuffer.order(ByteOrder.nativeOrder());
- final V v = Struct.parse(valueClass, valueByteBuffer);
-
- return new Pair<>(k, v);
- }
-
@NonNull
private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
throws Exception {
final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
- final String rawMapStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args);
+ final String rawMapStr = runAsShell(DUMP, () ->
+ DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
final HashMap<K, V> map = new HashMap<>();
for (final String line : rawMapStr.split(LINE_DELIMITER)) {
- final Pair<K, V> rule = parseMapKeyValue(keyClass, valueClass, line.trim());
+ final Pair<K, V> rule =
+ BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
map.put(rule.first, rule.second);
}
return map;
@@ -1250,7 +1451,8 @@
}
private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
- final String dumpStr = DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short");
+ final String dumpStr = runAsShell(DUMP, () ->
+ DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
// BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
// packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
@@ -1317,6 +1519,94 @@
runClatUdpTest();
}
+ @NonNull
+ private ByteBuffer buildDnsReplyMessageById(short id) {
+ byte[] replyMessage = Arrays.copyOf(DNS_REPLY, DNS_REPLY.length);
+ // Assign transaction id of reply message pattern with a given DNS transaction id.
+ replyMessage[0] = (byte) ((id >> 8) & 0xff);
+ replyMessage[1] = (byte) (id & 0xff);
+ Log.d(TAG, "Built DNS reply: " + dumpHexString(replyMessage));
+
+ return ByteBuffer.wrap(replyMessage);
+ }
+
+ @NonNull
+ private void sendDownloadPacketDnsV4(@NonNull final Inet4Address srcIp,
+ @NonNull final Inet4Address dstIp, short srcPort, short dstPort, short dnsId,
+ @NonNull final TetheringTester tester) throws Exception {
+ // DNS response transaction id must be copied from DNS query. Used by the requester
+ // to match up replies to outstanding queries. See RFC 1035 section 4.1.1.
+ final ByteBuffer dnsReplyMessage = buildDnsReplyMessageById(dnsId);
+ final ByteBuffer testPacket = buildUdpPacket((InetAddress) srcIp,
+ (InetAddress) dstIp, srcPort, dstPort, dnsReplyMessage);
+
+ tester.verifyDownload(testPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpDnsPacket(p, true /* hasEther */, true /* isIpv4 */,
+ dnsReplyMessage);
+ });
+ }
+
+ // Send IPv4 UDP DNS packet and return the forwarded DNS packet on upstream.
+ @NonNull
+ private byte[] sendUploadPacketDnsV4(@NonNull final MacAddress srcMac,
+ @NonNull final MacAddress dstMac, @NonNull final Inet4Address srcIp,
+ @NonNull final Inet4Address dstIp, short srcPort, short dstPort,
+ @NonNull final TetheringTester tester) throws Exception {
+ final ByteBuffer testPacket = buildUdpPacket(srcMac, dstMac, srcIp, dstIp,
+ srcPort, dstPort, DNS_QUERY);
+
+ return tester.verifyUpload(testPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpDnsPacket(p, false /* hasEther */, true /* isIpv4 */,
+ DNS_QUERY);
+ });
+ }
+
+ @Test
+ public void testTetherUdpV4Dns() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+ toList(TEST_IP4_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // See the same reason in runUdp4Test().
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+ // [1] Send DNS query.
+ // tethered device --> downstream --> dnsmasq forwarding --> upstream --> DNS server
+ //
+ // Need to extract DNS transaction id and source port from dnsmasq forwarded DNS query
+ // packet. dnsmasq forwarding creats new query which means UDP source port and DNS
+ // transaction id are changed from original sent DNS query. See forward_query() in
+ // external/dnsmasq/src/forward.c. Note that #TetheringTester.isExpectedUdpDnsPacket
+ // guarantees that |forwardedQueryPacket| is a valid DNS packet. So we can parse it as DNS
+ // packet.
+ final MacAddress srcMac = tethered.macAddr;
+ final MacAddress dstMac = tethered.routerMacAddr;
+ final Inet4Address clientIp = tethered.ipv4Addr;
+ final Inet4Address gatewayIp = tethered.ipv4Gatway;
+ final byte[] forwardedQueryPacket = sendUploadPacketDnsV4(srcMac, dstMac, clientIp,
+ gatewayIp, LOCAL_PORT, DNS_PORT, tester);
+ final ByteBuffer buf = ByteBuffer.wrap(forwardedQueryPacket);
+ Struct.parse(Ipv4Header.class, buf);
+ final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
+ final TestDnsPacket dnsQuery = TestDnsPacket.getTestDnsPacket(buf);
+ assertNotNull(dnsQuery);
+ Log.d(TAG, "Forwarded UDP source port: " + udpHeader.srcPort + ", DNS query id: "
+ + dnsQuery.getHeader().id);
+
+ // [2] Send DNS reply.
+ // DNS server --> upstream --> dnsmasq forwarding --> downstream --> tethered device
+ //
+ // DNS reply transaction id must be copied from DNS query. Used by the requester to match
+ // up replies to outstanding queries. See RFC 1035 section 4.1.1.
+ final Inet4Address remoteIp = (Inet4Address) TEST_IP4_DNS;
+ final Inet4Address tetheringUpstreamIp = (Inet4Address) TEST_IP4_ADDR.getAddress();
+ sendDownloadPacketDnsV4(remoteIp, tetheringUpstreamIp, DNS_PORT,
+ (short) udpHeader.srcPort, (short) dnsQuery.getHeader().id, tester);
+ }
+
private <T> List<T> toList(T... array) {
return Arrays.asList(array);
}
diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java
index 4d90d39..9cc2e49 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/src/android/net/TetheringTester.java
@@ -20,6 +20,11 @@
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.net.module.util.DnsPacket.ANSECTION;
+import static com.android.net.module.util.DnsPacket.ARSECTION;
+import static com.android.net.module.util.DnsPacket.NSSECTION;
+import static com.android.net.module.util.DnsPacket.QDSECTION;
+import static com.android.net.module.util.HexDump.dumpHexString;
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
@@ -41,12 +46,14 @@
import android.net.dhcp.DhcpAckPacket;
import android.net.dhcp.DhcpOfferPacket;
import android.net.dhcp.DhcpPacket;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.net.module.util.DnsPacket;
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.EthernetHeader;
@@ -79,7 +86,7 @@
*/
public final class TetheringTester {
private static final String TAG = TetheringTester.class.getSimpleName();
- private static final int PACKET_READ_TIMEOUT_MS = 100;
+ private static final int PACKET_READ_TIMEOUT_MS = 500;
private static final int DHCP_DISCOVER_ATTEMPTS = 10;
private static final int READ_RA_ATTEMPTS = 10;
private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] {
@@ -124,12 +131,14 @@
public final MacAddress macAddr;
public final MacAddress routerMacAddr;
public final Inet4Address ipv4Addr;
+ public final Inet4Address ipv4Gatway;
public final Inet6Address ipv6Addr;
private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception {
macAddr = mac;
DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
+ ipv4Gatway = (Inet4Address) dhcpResults.gateway;
routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
dhcpResults.serverAddress);
ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null;
@@ -386,8 +395,8 @@
}
}
- public static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
- boolean isIpv4, @NonNull final ByteBuffer payload) {
+ private static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
+ boolean isIpv4, Predicate<ByteBuffer> payloadVerifier) {
final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
try {
if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
@@ -395,15 +404,178 @@
if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_UDP)) return false;
if (Struct.parse(UdpHeader.class, buf) == null) return false;
+
+ if (!payloadVerifier.test(buf)) return false;
} catch (Exception e) {
// Parsing packet fail means it is not udp packet.
return false;
}
+ return true;
+ }
- if (buf.remaining() != payload.limit()) return false;
+ // Returns remaining bytes in the ByteBuffer in a new byte array of the right size. The
+ // ByteBuffer will be empty upon return. Used to avoid lint warning.
+ // See https://errorprone.info/bugpattern/ByteBufferBackingArray
+ private static byte[] getRemaining(final ByteBuffer buf) {
+ final byte[] bytes = new byte[buf.remaining()];
+ buf.get(bytes);
+ Log.d(TAG, "Get remaining bytes: " + dumpHexString(bytes));
+ return bytes;
+ }
- return Arrays.equals(Arrays.copyOfRange(buf.array(), buf.position(), buf.limit()),
- payload.array());
+ // |expectedPayload| is copied as read-only because the caller may reuse it.
+ public static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
+ boolean isIpv4, @NonNull final ByteBuffer expectedPayload) {
+ return isExpectedUdpPacket(rawPacket, hasEth, isIpv4, p -> {
+ if (p.remaining() != expectedPayload.limit()) return false;
+
+ return Arrays.equals(getRemaining(p), getRemaining(
+ expectedPayload.asReadOnlyBuffer()));
+ });
+ }
+
+ // |expectedPayload| is copied as read-only because the caller may reuse it.
+ // See hasExpectedDnsMessage.
+ public static boolean isExpectedUdpDnsPacket(@NonNull final byte[] rawPacket, boolean hasEth,
+ boolean isIpv4, @NonNull final ByteBuffer expectedPayload) {
+ return isExpectedUdpPacket(rawPacket, hasEth, isIpv4, p -> {
+ return hasExpectedDnsMessage(p, expectedPayload);
+ });
+ }
+
+ public static class TestDnsPacket extends DnsPacket {
+ TestDnsPacket(byte[] data) throws DnsPacket.ParseException {
+ super(data);
+ }
+
+ @Nullable
+ public static TestDnsPacket getTestDnsPacket(final ByteBuffer buf) {
+ try {
+ // The ByteBuffer will be empty upon return.
+ return new TestDnsPacket(getRemaining(buf));
+ } catch (DnsPacket.ParseException e) {
+ return null;
+ }
+ }
+
+ public DnsHeader getHeader() {
+ return mHeader;
+ }
+
+ public List<DnsRecord> getRecordList(int secType) {
+ return mRecords[secType];
+ }
+
+ public int getANCount() {
+ return mHeader.getRecordCount(ANSECTION);
+ }
+
+ public int getQDCount() {
+ return mHeader.getRecordCount(QDSECTION);
+ }
+
+ public int getNSCount() {
+ return mHeader.getRecordCount(NSSECTION);
+ }
+
+ public int getARCount() {
+ return mHeader.getRecordCount(ARSECTION);
+ }
+
+ private boolean isRecordsEquals(int type, @NonNull final TestDnsPacket other) {
+ List<DnsRecord> records = getRecordList(type);
+ List<DnsRecord> otherRecords = other.getRecordList(type);
+
+ if (records.size() != otherRecords.size()) return false;
+
+ // Expect that two compared resource records are in the same order. For current tests
+ // in EthernetTetheringTest, it is okay because dnsmasq doesn't reorder the forwarded
+ // resource records.
+ // TODO: consider allowing that compare records out of order.
+ for (int i = 0; i < records.size(); i++) {
+ // TODO: use DnsRecord.equals once aosp/1387135 is merged.
+ if (!TextUtils.equals(records.get(i).dName, otherRecords.get(i).dName)
+ || records.get(i).nsType != otherRecords.get(i).nsType
+ || records.get(i).nsClass != otherRecords.get(i).nsClass
+ || records.get(i).ttl != otherRecords.get(i).ttl
+ || !Arrays.equals(records.get(i).getRR(), otherRecords.get(i).getRR())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean isQDRecordsEquals(@NonNull final TestDnsPacket other) {
+ return isRecordsEquals(QDSECTION, other);
+ }
+
+ public boolean isANRecordsEquals(@NonNull final TestDnsPacket other) {
+ return isRecordsEquals(ANSECTION, other);
+ }
+ }
+
+ // The ByteBuffer |actual| will be empty upon return. The ByteBuffer |excepted| will be copied
+ // as read-only because the caller may reuse it.
+ private static boolean hasExpectedDnsMessage(@NonNull final ByteBuffer actual,
+ @NonNull final ByteBuffer excepted) {
+ // Forwarded DNS message is extracted from remaining received packet buffer which has
+ // already parsed ethernet header, if any, IP header and UDP header.
+ final TestDnsPacket forwardedDns = TestDnsPacket.getTestDnsPacket(actual);
+ if (forwardedDns == null) return false;
+
+ // Original DNS message is the payload of the sending test UDP packet. It is used to check
+ // that the forwarded DNS query and reply have corresponding contents.
+ final TestDnsPacket originalDns = TestDnsPacket.getTestDnsPacket(
+ excepted.asReadOnlyBuffer());
+ assertNotNull(originalDns);
+
+ // Compare original DNS message which is sent to dnsmasq and forwarded DNS message which
+ // is forwarded by dnsmasq. The original message and forwarded message may be not identical
+ // because dnsmasq may change the header flags or even recreate the DNS query message and
+ // so on. We only simple check on forwarded packet and monitor if test will be broken by
+ // vendor dnsmasq customization. See forward_query() in external/dnsmasq/src/forward.c.
+ //
+ // DNS message format. See rfc1035 section 4.1.
+ // +---------------------+
+ // | Header |
+ // +---------------------+
+ // | Question | the question for the name server
+ // +---------------------+
+ // | Answer | RRs answering the question
+ // +---------------------+
+ // | Authority | RRs pointing toward an authority
+ // +---------------------+
+ // | Additional | RRs holding additional information
+ // +---------------------+
+
+ // [1] Header section. See rfc1035 section 4.1.1.
+ // Verify QR flag bit, QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT.
+ if (originalDns.getHeader().isResponse() != forwardedDns.getHeader().isResponse()) {
+ return false;
+ }
+ if (originalDns.getQDCount() != forwardedDns.getQDCount()) return false;
+ if (originalDns.getANCount() != forwardedDns.getANCount()) return false;
+ if (originalDns.getNSCount() != forwardedDns.getNSCount()) return false;
+ if (originalDns.getARCount() != forwardedDns.getARCount()) return false;
+
+ // [2] Question section. See rfc1035 section 4.1.2.
+ // Question section has at least one entry either DNS query or DNS reply.
+ if (forwardedDns.getRecordList(QDSECTION).isEmpty()) return false;
+ // Expect that original and forwarded message have the same question records (usually 1).
+ if (!originalDns.isQDRecordsEquals(forwardedDns)) return false;
+
+ // [3] Answer section. See rfc1035 section 4.1.3.
+ if (forwardedDns.getHeader().isResponse()) {
+ // DNS reply has at least have one answer in our tests.
+ // See EthernetTetheringTest#testTetherUdpV4Dns.
+ if (forwardedDns.getRecordList(ANSECTION).isEmpty()) return false;
+ // Expect that original and forwarded message have the same answer records.
+ if (!originalDns.isANRecordsEquals(forwardedDns)) return false;
+ }
+
+ // Ignore checking {Authority, Additional} sections because they are not tested
+ // in EthernetTetheringTest.
+ return true;
}
private void sendUploadPacket(ByteBuffer packet) throws Exception {
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/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index 536ab2d..0e8b044 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -98,7 +98,7 @@
assertTrue(mTestMap.isEmpty());
}
- private TetherDownstream6Key createTetherDownstream6Key(long iif, String mac,
+ private TetherDownstream6Key createTetherDownstream6Key(int iif, String mac,
String address) throws Exception {
final MacAddress dstMac = MacAddress.fromString(mac);
final InetAddress ipv6Address = InetAddress.getByName(address);
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 63bb731..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;
@@ -222,7 +222,7 @@
private static class TestUpstream4Key {
public static class Builder {
- private long mIif = DOWNSTREAM_IFINDEX;
+ private int mIif = DOWNSTREAM_IFINDEX;
private MacAddress mDstMac = DOWNSTREAM_MAC;
private short mL4proto = (short) IPPROTO_TCP;
private byte[] mSrc4 = PRIVATE_ADDR.getAddress();
@@ -246,7 +246,7 @@
private static class TestDownstream4Key {
public static class Builder {
- private long mIif = UPSTREAM_IFINDEX;
+ private int mIif = UPSTREAM_IFINDEX;
private MacAddress mDstMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
private short mL4proto = (short) IPPROTO_TCP;
private byte[] mSrc4 = REMOTE_ADDR.getAddress();
@@ -270,7 +270,7 @@
private static class TestUpstream4Value {
public static class Builder {
- private long mOif = UPSTREAM_IFINDEX;
+ private int mOif = UPSTREAM_IFINDEX;
private MacAddress mEthDstMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
private MacAddress mEthSrcMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
private int mEthProto = ETH_P_IP;
@@ -290,7 +290,7 @@
private static class TestDownstream4Value {
public static class Builder {
- private long mOif = DOWNSTREAM_IFINDEX;
+ private int mOif = DOWNSTREAM_IFINDEX;
private MacAddress mEthDstMac = MAC_A /* client mac */;
private MacAddress mEthSrcMac = DOWNSTREAM_MAC;
private int mEthProto = ETH_P_IP;
@@ -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;
}
});
@@ -941,11 +941,11 @@
@Test
public void testRuleMakeTetherDownstream6Key() throws Exception {
- final Integer mobileIfIndex = 100;
+ final int mobileIfIndex = 100;
final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
- assertEquals(key.iif, (long) mobileIfIndex);
+ assertEquals(key.iif, mobileIfIndex);
assertEquals(key.dstMac, MacAddress.ALL_ZEROS_ADDRESS); // rawip upstream
assertTrue(Arrays.equals(key.neigh6, NEIGH_A.getAddress()));
// iif (4) + dstMac(6) + padding(2) + neigh6 (16) = 28.
@@ -954,7 +954,7 @@
@Test
public void testRuleMakeTether6Value() throws Exception {
- final Integer mobileIfIndex = 100;
+ final int mobileIfIndex = 100;
final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
final Tether6Value value = rule.makeTether6Value();
@@ -974,7 +974,7 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
final String mobileIface = "rmnet_data0";
- final Integer mobileIfIndex = 100;
+ final int mobileIfIndex = 100;
coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
// [1] Default limit.
@@ -1018,7 +1018,7 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
final String mobileIface = "rmnet_data0";
- final Integer mobileIfIndex = 100;
+ final int mobileIfIndex = 100;
coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
// Applying a data limit to the current upstream does not take any immediate action.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index e114cb5..38f1e9c 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -664,17 +664,17 @@
assertEquals("Internal callback is not registered", 1, callbacks.size());
assertNotNull(weakTm.get());
+ // Calling System.gc() or System.runFinalization() doesn't guarantee GCs or finalizers
+ // are executed synchronously. The finalizer is called after GC on a separate thread.
final int attempts = 100;
final long waitIntervalMs = 50;
for (int i = 0; i < attempts; i++) {
forceGc();
- if (weakTm.get() == null) break;
+ if (weakTm.get() == null && callbacks.size() == 0) break;
Thread.sleep(waitIntervalMs);
}
- assertNull("TetheringManager weak reference still not null after " + attempts
- + " attempts", weakTm.get());
-
+ assertNull("TetheringManager weak reference is not null", weakTm.get());
assertEquals("Internal callback is not unregistered", 0, callbacks.size());
});
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 66ad167..a36d67f 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -2899,9 +2899,13 @@
}
private void forceUsbTetheringUse(final int function) {
- Settings.Global.putInt(mContentResolver, TETHER_FORCE_USB_FUNCTIONS, function);
+ setSetting(TETHER_FORCE_USB_FUNCTIONS, function);
+ }
+
+ private void setSetting(final String key, final int value) {
+ Settings.Global.putInt(mContentResolver, key, value);
final ContentObserver observer = mTethering.getSettingsObserverForTest();
- observer.onChange(false /* selfChange */);
+ observer.onChange(false /* selfChange */, Settings.Global.getUriFor(key));
mLooper.dispatchAll();
}
@@ -2957,74 +2961,86 @@
TETHERING_WIFI_P2P, TETHERING_BLUETOOTH, TETHERING_ETHERNET });
}
+ private void setUserRestricted(boolean restricted) {
+ final Bundle restrictions = new Bundle();
+ restrictions.putBoolean(UserManager.DISALLOW_CONFIG_TETHERING, restricted);
+ when(mUserManager.getUserRestrictions()).thenReturn(restrictions);
+ when(mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(restricted);
+
+ final Intent intent = new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+ mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ mLooper.dispatchAll();
+ }
+
@Test
public void testTetheringSupported() throws Exception {
final ArraySet<Integer> expectedTypes = getAllSupportedTetheringTypes();
// Check tethering is supported after initialization.
- setTetheringSupported(true /* supported */);
TestTetheringEventCallback callback = new TestTetheringEventCallback();
mTethering.registerTetheringEventCallback(callback);
mLooper.dispatchAll();
- updateConfigAndVerifySupported(callback, expectedTypes);
+ verifySupported(callback, expectedTypes);
- // Could disable tethering supported by settings.
- Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED, 0);
- updateConfigAndVerifySupported(callback, new ArraySet<>());
+ // Could change tethering supported by settings.
+ setSetting(Settings.Global.TETHER_SUPPORTED, 0);
+ verifySupported(callback, new ArraySet<>());
+ setSetting(Settings.Global.TETHER_SUPPORTED, 1);
+ verifySupported(callback, expectedTypes);
- // Could disable tethering supported by user restriction.
- setTetheringSupported(true /* supported */);
- updateConfigAndVerifySupported(callback, expectedTypes);
- when(mUserManager.hasUserRestriction(
- UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(true);
- updateConfigAndVerifySupported(callback, new ArraySet<>());
+ // Could change tethering supported by user restriction.
+ setUserRestricted(true /* restricted */);
+ verifySupported(callback, new ArraySet<>());
+ setUserRestricted(false /* restricted */);
+ verifySupported(callback, expectedTypes);
- // Tethering is supported if it has any supported downstream.
- setTetheringSupported(true /* supported */);
- updateConfigAndVerifySupported(callback, expectedTypes);
// Usb tethering is not supported:
expectedTypes.remove(TETHERING_USB);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(callback, expectedTypes);
+ sendConfigurationChanged();
+ verifySupported(callback, expectedTypes);
// Wifi tethering is not supported:
expectedTypes.remove(TETHERING_WIFI);
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(callback, expectedTypes);
+ sendConfigurationChanged();
+ verifySupported(callback, expectedTypes);
// Bluetooth tethering is not supported:
expectedTypes.remove(TETHERING_BLUETOOTH);
when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
.thenReturn(new String[0]);
if (isAtLeastT()) {
- updateConfigAndVerifySupported(callback, expectedTypes);
+ sendConfigurationChanged();
+ verifySupported(callback, expectedTypes);
// P2p tethering is not supported:
expectedTypes.remove(TETHERING_WIFI_P2P);
when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(callback, expectedTypes);
+ sendConfigurationChanged();
+ verifySupported(callback, expectedTypes);
// Ncm tethering is not supported:
expectedTypes.remove(TETHERING_NCM);
when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
.thenReturn(new String[0]);
- updateConfigAndVerifySupported(callback, expectedTypes);
+ sendConfigurationChanged();
+ verifySupported(callback, expectedTypes);
// Ethernet tethering (last supported type) is not supported:
expectedTypes.remove(TETHERING_ETHERNET);
mForceEthernetServiceUnavailable = true;
- updateConfigAndVerifySupported(callback, new ArraySet<>());
-
+ sendConfigurationChanged();
+ verifySupported(callback, new ArraySet<>());
} else {
// If wifi, usb and bluetooth are all not supported, all the types are not supported.
- expectedTypes.clear();
- updateConfigAndVerifySupported(callback, expectedTypes);
+ sendConfigurationChanged();
+ verifySupported(callback, new ArraySet<>());
}
}
- private void updateConfigAndVerifySupported(final TestTetheringEventCallback callback,
+ private void verifySupported(final TestTetheringEventCallback callback,
final ArraySet<Integer> expectedTypes) {
- sendConfigurationChanged();
-
assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported());
callback.expectSupportedTetheringTypes(expectedTypes);
}
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index 3e4456f..72f63c6 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -62,8 +62,8 @@
uint8_t protocol = 0; // TODO: Use are reserved value? Or int (-1) and cast to uint below?
struct in6_addr src_ip = {};
struct in6_addr dst_ip = {};
- uint8_t tos = 0; // Only used for IPv4
- uint32_t old_first_u32 = 0; // Only used for IPv6
+ uint8_t tos = 0; // Only used for IPv4
+ __be32 old_first_be32 = 0; // Only used for IPv6
if (ipv4) {
const struct iphdr* const iph = (void*)(eth + 1);
hdr_size = l2_header_size + sizeof(struct iphdr);
@@ -96,7 +96,7 @@
src_ip = ip6h->saddr;
dst_ip = ip6h->daddr;
protocol = ip6h->nexthdr;
- old_first_u32 = *(uint32_t*)ip6h;
+ old_first_be32 = *(__be32*)ip6h;
}
switch (protocol) {
@@ -135,9 +135,9 @@
sizeof(uint16_t));
bpf_skb_store_bytes(skb, IP4_OFFSET(tos, l2_header_size), &newTos, sizeof(newTos), 0);
} else {
- uint32_t new_first_u32 =
- htonl(ntohl(old_first_u32) & 0xF03FFFFF | (existing_rule->dscp_val << 22));
- bpf_skb_store_bytes(skb, l2_header_size, &new_first_u32, sizeof(uint32_t),
+ __be32 new_first_be32 =
+ htonl(ntohl(old_first_be32) & 0xF03FFFFF | (existing_rule->dscp_val << 22));
+ bpf_skb_store_bytes(skb, l2_header_size, &new_first_be32, sizeof(__be32),
BPF_F_RECOMPUTE_CSUM);
}
return;
@@ -214,8 +214,8 @@
bpf_l3_csum_replace(skb, IP4_OFFSET(check, l2_header_size), htons(tos), htons(new_tos), 2);
bpf_skb_store_bytes(skb, IP4_OFFSET(tos, l2_header_size), &new_tos, sizeof(new_tos), 0);
} else {
- uint32_t new_first_u32 = htonl(ntohl(old_first_u32) & 0xF03FFFFF | (new_dscp << 22));
- bpf_skb_store_bytes(skb, l2_header_size, &new_first_u32, sizeof(uint32_t),
+ __be32 new_first_be32 = htonl(ntohl(old_first_be32) & 0xF03FFFFF | (new_dscp << 22));
+ bpf_skb_store_bytes(skb, l2_header_size, &new_first_be32, sizeof(__be32),
BPF_F_RECOMPUTE_CSUM);
}
return;
diff --git a/common/src/com/android/net/module/util/bpf/Tether4Key.java b/common/src/com/android/net/module/util/bpf/Tether4Key.java
index 638576f..8273e6a 100644
--- a/common/src/com/android/net/module/util/bpf/Tether4Key.java
+++ b/common/src/com/android/net/module/util/bpf/Tether4Key.java
@@ -30,8 +30,8 @@
/** Key type for downstream & upstream IPv4 forwarding maps. */
public class Tether4Key extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long iif;
+ @Field(order = 0, type = Type.S32)
+ public final int iif;
@Field(order = 1, type = Type.EUI48)
public final MacAddress dstMac;
@@ -51,7 +51,7 @@
@Field(order = 6, type = Type.UBE16)
public final int dstPort;
- public Tether4Key(final long iif, @NonNull final MacAddress dstMac, final short l4proto,
+ public Tether4Key(final int iif, @NonNull final MacAddress dstMac, final short l4proto,
final byte[] src4, final byte[] dst4, final int srcPort,
final int dstPort) {
Objects.requireNonNull(dstMac);
diff --git a/common/src/com/android/net/module/util/bpf/Tether4Value.java b/common/src/com/android/net/module/util/bpf/Tether4Value.java
index de98766..74fdda2 100644
--- a/common/src/com/android/net/module/util/bpf/Tether4Value.java
+++ b/common/src/com/android/net/module/util/bpf/Tether4Value.java
@@ -30,8 +30,8 @@
/** Value type for downstream & upstream IPv4 forwarding maps. */
public class Tether4Value extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long oif;
+ @Field(order = 0, type = Type.S32)
+ public final int oif;
// The ethhdr struct which is defined in uapi/linux/if_ether.h
@Field(order = 1, type = Type.EUI48)
@@ -60,7 +60,7 @@
@Field(order = 9, type = Type.U63)
public final long lastUsed;
- public Tether4Value(final long oif, @NonNull final MacAddress ethDstMac,
+ public Tether4Value(final int oif, @NonNull final MacAddress ethDstMac,
@NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu,
final byte[] src46, final byte[] dst46, final int srcPort,
final int dstPort, final long lastUsed) {
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/Android.bp b/framework/Android.bp
index fcce7a5..485961c 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -228,12 +228,13 @@
],
out: ["framework_connectivity_jarjar_rules.txt"],
cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :framework-connectivity-pre-jarjar{.jar}) " +
+ "$(location :framework-connectivity-pre-jarjar{.jar}) " +
"$(location :framework-connectivity-t-pre-jarjar{.jar}) " +
"--prefix android.net.connectivity " +
"--apistubs $(location :framework-connectivity.stubs.module_lib{.jar}) " +
- "$(location :framework-connectivity-t.stubs.module_lib{.jar}) " +
- "--unsupportedapi $(locations :connectivity-hiddenapi-files) " +
+ "--apistubs $(location :framework-connectivity-t.stubs.module_lib{.jar}) " +
+ // Make a ":"-separated list. There will be an extra ":" but empty items are ignored.
+ "--unsupportedapi $$(printf ':%s' $(locations :connectivity-hiddenapi-files)) " +
"--excludes $(location jarjar-excludes.txt) " +
"--output $(out)",
visibility: [
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 2810d80..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,19 +133,16 @@
}
int BpfHandler::tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
- std::lock_guard guard(mMutex);
- if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) {
- return -EPERM;
- }
+ if (!mCookieTagMap.isValid()) return -EPERM;
+
+ if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) return -EPERM;
// Note that tagging the socket to AID_CLAT is only implemented in JNI ClatCoordinator.
// The process is not allowed to tag socket to AID_CLAT via tagSocket() which would cause
// process data usage accounting to be bypassed. Tagging AID_CLAT is used for avoiding counting
// CLAT traffic data usage twice. See packages/modules/Connectivity/service/jni/
// com_android_server_connectivity_ClatCoordinator.cpp
- if (chargeUid == AID_CLAT) {
- return -EPERM;
- }
+ if (chargeUid == AID_CLAT) return -EPERM;
// The socket destroy listener only monitors on the group {INET_TCP, INET_UDP, INET6_TCP,
// INET6_UDP}. Tagging listener unsupported socket causes that the tag can't be removed from
@@ -180,15 +177,16 @@
uint64_t sock_cookie = getSocketCookie(sockFd);
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+
UidTagValue newKey = {.uid = (uint32_t)chargeUid, .tag = tag};
uint32_t totalEntryCount = 0;
uint32_t perUidEntryCount = 0;
// Now we go through the stats map and count how many entries are associated
// with chargeUid. If the uid entry hit the limit for each chargeUid, we block
- // the request to prevent the map from overflow. It is safe here to iterate
- // over the map since when mMutex is hold, system server cannot toggle
- // the live stats map and clean it. So nobody can delete entries from the map.
+ // 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>&) {
@@ -228,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()),
@@ -241,10 +239,10 @@
}
int BpfHandler::untagSocket(int sockFd) {
- std::lock_guard guard(mMutex);
uint64_t sock_cookie = getSocketCookie(sockFd);
-
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+
+ if (!mCookieTagMap.isValid()) return -EPERM;
base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
if (!res.ok()) {
ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
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/netd/NetdUpdatable.cpp b/netd/NetdUpdatable.cpp
index f0997fc..41b1fdb 100644
--- a/netd/NetdUpdatable.cpp
+++ b/netd/NetdUpdatable.cpp
@@ -16,19 +16,20 @@
#define LOG_TAG "NetdUpdatable"
-#include "NetdUpdatable.h"
+#include "BpfHandler.h"
#include <android-base/logging.h>
#include <netdutils/Status.h>
#include "NetdUpdatablePublic.h"
+static android::net::BpfHandler sBpfHandler;
+
int libnetd_updatable_init(const char* cg2_path) {
android::base::InitLogging(/*argv=*/nullptr);
LOG(INFO) << __func__ << ": Initializing";
- android::net::gNetdUpdatable = android::net::NetdUpdatable::getInstance();
- android::netdutils::Status ret = android::net::gNetdUpdatable->mBpfHandler.init(cg2_path);
+ android::netdutils::Status ret = sBpfHandler.init(cg2_path);
if (!android::netdutils::isOk(ret)) {
LOG(ERROR) << __func__ << ": BPF handler init failed";
return -ret.code();
@@ -37,25 +38,9 @@
}
int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
- if (android::net::gNetdUpdatable == nullptr) return -EPERM;
- return android::net::gNetdUpdatable->mBpfHandler.tagSocket(sockFd, tag, chargeUid, realUid);
+ return sBpfHandler.tagSocket(sockFd, tag, chargeUid, realUid);
}
int libnetd_updatable_untagSocket(int sockFd) {
- if (android::net::gNetdUpdatable == nullptr) return -EPERM;
- return android::net::gNetdUpdatable->mBpfHandler.untagSocket(sockFd);
+ return sBpfHandler.untagSocket(sockFd);
}
-
-namespace android {
-namespace net {
-
-NetdUpdatable* gNetdUpdatable = nullptr;
-
-NetdUpdatable* NetdUpdatable::getInstance() {
- // Instantiated on first use.
- static NetdUpdatable instance;
- return &instance;
-}
-
-} // namespace net
-} // namespace android
diff --git a/netd/NetdUpdatable.h b/netd/NetdUpdatable.h
deleted file mode 100644
index 333037f..0000000
--- a/netd/NetdUpdatable.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2022, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "BpfHandler.h"
-
-namespace android {
-namespace net {
-
-class NetdUpdatable {
- public:
- NetdUpdatable() = default;
- NetdUpdatable(const NetdUpdatable&) = delete;
- NetdUpdatable& operator=(const NetdUpdatable&) = delete;
- static NetdUpdatable* getInstance();
-
- BpfHandler mBpfHandler;
-};
-
-extern NetdUpdatable* gNetdUpdatable;
-
-} // namespace net
-} // namespace android
\ No newline at end of file
diff --git a/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/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 8818460..1226eea 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -295,6 +295,13 @@
if (DBG) Log.d(TAG, "Discover services");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
+ // If the binder death notification for a INsdManagerCallback was received
+ // before any calls are received by NsdService, the clientInfo would be
+ // cleared and cause NPE. Add a null check here to prevent this corner case.
+ if (clientInfo == null) {
+ Log.e(TAG, "Unknown connector in discovery");
+ break;
+ }
if (requestLimitReached(clientInfo)) {
clientInfo.onDiscoverServicesFailed(
@@ -321,6 +328,13 @@
if (DBG) Log.d(TAG, "Stop service discovery");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
+ // If the binder death notification for a INsdManagerCallback was received
+ // before any calls are received by NsdService, the clientInfo would be
+ // cleared and cause NPE. Add a null check here to prevent this corner case.
+ if (clientInfo == null) {
+ Log.e(TAG, "Unknown connector in stop discovery");
+ break;
+ }
try {
id = clientInfo.mClientIds.get(clientId);
@@ -341,6 +355,14 @@
if (DBG) Log.d(TAG, "Register service");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
+ // If the binder death notification for a INsdManagerCallback was received
+ // before any calls are received by NsdService, the clientInfo would be
+ // cleared and cause NPE. Add a null check here to prevent this corner case.
+ if (clientInfo == null) {
+ Log.e(TAG, "Unknown connector in registration");
+ break;
+ }
+
if (requestLimitReached(clientInfo)) {
clientInfo.onRegisterServiceFailed(
clientId, NsdManager.FAILURE_MAX_LIMIT);
@@ -363,6 +385,9 @@
if (DBG) Log.d(TAG, "unregister service");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
+ // If the binder death notification for a INsdManagerCallback was received
+ // before any calls are received by NsdService, the clientInfo would be
+ // cleared and cause NPE. Add a null check here to prevent this corner case.
if (clientInfo == null) {
Log.e(TAG, "Unknown connector in unregistration");
break;
@@ -380,6 +405,13 @@
if (DBG) Log.d(TAG, "Resolve service");
args = (ListenerArgs) msg.obj;
clientInfo = mClients.get(args.connector);
+ // If the binder death notification for a INsdManagerCallback was received
+ // before any calls are received by NsdService, the clientInfo would be
+ // cleared and cause NPE. Add a null check here to prevent this corner case.
+ if (clientInfo == null) {
+ Log.e(TAG, "Unknown connector in resolution");
+ break;
+ }
if (clientInfo.mResolvedService != null) {
clientInfo.onResolveServiceFailed(
diff --git a/service-t/src/com/android/server/ethernet/EthernetCallback.java b/service-t/src/com/android/server/ethernet/EthernetCallback.java
new file mode 100644
index 0000000..5461156
--- /dev/null
+++ b/service-t/src/com/android/server/ethernet/EthernetCallback.java
@@ -0,0 +1,57 @@
+/*
+ * 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.ethernet;
+
+import android.net.EthernetNetworkManagementException;
+import android.net.INetworkInterfaceOutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** Convenience wrapper for INetworkInterfaceOutcomeReceiver */
+@VisibleForTesting
+public class EthernetCallback {
+ private static final String TAG = EthernetCallback.class.getSimpleName();
+ private final INetworkInterfaceOutcomeReceiver mReceiver;
+
+ public EthernetCallback(INetworkInterfaceOutcomeReceiver receiver) {
+ mReceiver = receiver;
+ }
+
+ /** Calls INetworkInterfaceOutcomeReceiver#onResult */
+ public void onResult(String ifname) {
+ try {
+ if (mReceiver != null) {
+ mReceiver.onResult(ifname);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to report error to OutcomeReceiver", e);
+ }
+ }
+
+ /** Calls INetworkInterfaceOutcomeReceiver#onError */
+ public void onError(String msg) {
+ try {
+ if (mReceiver != null) {
+ mReceiver.onError(new EthernetNetworkManagementException(msg));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to report error to OutcomeReceiver", e);
+ }
+ }
+}
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/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index e5bddf6..56c21eb 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -22,9 +22,7 @@
import android.net.ConnectivityManager;
import android.net.ConnectivityResources;
import android.net.EthernetManager;
-import android.net.EthernetNetworkManagementException;
import android.net.EthernetNetworkSpecifier;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
@@ -42,7 +40,6 @@
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
-import android.os.RemoteException;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
@@ -190,22 +187,19 @@
* {@code null} is passed, then the network's current
* {@link NetworkCapabilities} will be used in support of existing APIs as
* the public API does not allow this.
- * @param listener an optional {@link INetworkInterfaceOutcomeReceiver} to notify callers of
- * completion.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected void updateInterface(@NonNull final String ifaceName,
@Nullable final IpConfiguration ipConfig,
- @Nullable final NetworkCapabilities capabilities,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ @Nullable final NetworkCapabilities capabilities) {
if (!hasInterface(ifaceName)) {
- maybeSendNetworkManagementCallbackForUntracked(ifaceName, listener);
return;
}
final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
- iface.updateInterface(ipConfig, capabilities, listener);
+ iface.updateInterface(ipConfig, capabilities);
mTrackingInterfaces.put(ifaceName, iface);
+ return;
}
private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
@@ -238,10 +232,8 @@
/** Returns true if state has been modified */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- protected boolean updateInterfaceLinkState(@NonNull final String ifaceName, final boolean up,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ protected boolean updateInterfaceLinkState(@NonNull final String ifaceName, final boolean up) {
if (!hasInterface(ifaceName)) {
- maybeSendNetworkManagementCallbackForUntracked(ifaceName, listener);
return false;
}
@@ -250,14 +242,7 @@
}
NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
- return iface.updateLinkState(up, listener);
- }
-
- private void maybeSendNetworkManagementCallbackForUntracked(
- String ifaceName, INetworkInterfaceOutcomeReceiver listener) {
- maybeSendNetworkManagementCallback(listener, null,
- new EthernetNetworkManagementException(
- ifaceName + " can't be updated as it is not available."));
+ return iface.updateLinkState(up);
}
@VisibleForTesting
@@ -265,25 +250,6 @@
return mTrackingInterfaces.containsKey(ifaceName);
}
- private static void maybeSendNetworkManagementCallback(
- @Nullable final INetworkInterfaceOutcomeReceiver listener,
- @Nullable final String iface,
- @Nullable final EthernetNetworkManagementException e) {
- if (null == listener) {
- return;
- }
-
- try {
- if (iface != null) {
- listener.onResult(iface);
- } else {
- listener.onError(e);
- }
- } catch (RemoteException re) {
- Log.e(TAG, "Can't send onComplete for network management callback", re);
- }
- }
-
@VisibleForTesting
static class NetworkInterfaceState {
final String name;
@@ -332,11 +298,6 @@
private class EthernetIpClientCallback extends IpClientCallbacks {
private final ConditionVariable mIpClientStartCv = new ConditionVariable(false);
private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false);
- @Nullable INetworkInterfaceOutcomeReceiver mNetworkManagementListener;
-
- EthernetIpClientCallback(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
- mNetworkManagementListener = listener;
- }
@Override
public void onIpClientCreated(IIpClient ipClient) {
@@ -372,14 +333,14 @@
@Override
public void onProvisioningSuccess(LinkProperties newLp) {
- handleIpEvent(() -> onIpLayerStarted(newLp, mNetworkManagementListener));
+ handleIpEvent(() -> onIpLayerStarted(newLp));
}
@Override
public void onProvisioningFailure(LinkProperties newLp) {
// This cannot happen due to provisioning timeout, because our timeout is 0. It can
// happen due to errors while provisioning or on provisioning loss.
- handleIpEvent(() -> onIpLayerStopped(mNetworkManagementListener));
+ handleIpEvent(() -> onIpLayerStopped());
}
@Override
@@ -491,13 +452,11 @@
}
void updateInterface(@Nullable final IpConfiguration ipConfig,
- @Nullable final NetworkCapabilities capabilities,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ @Nullable final NetworkCapabilities capabilities) {
if (DBG) {
Log.d(TAG, "updateInterface, iface: " + name
+ ", ipConfig: " + ipConfig + ", old ipConfig: " + mIpConfig
+ ", capabilities: " + capabilities + ", old capabilities: " + mCapabilities
- + ", listener: " + listener
);
}
@@ -510,7 +469,7 @@
// TODO: Update this logic to only do a restart if required. Although a restart may
// be required due to the capabilities or ipConfiguration values, not all
// capabilities changes require a restart.
- restart(listener);
+ restart();
}
boolean isRestricted() {
@@ -518,10 +477,6 @@
}
private void start() {
- start(null);
- }
-
- private void start(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
if (mIpClient != null) {
if (DBG) Log.d(TAG, "IpClient already started");
return;
@@ -530,7 +485,7 @@
Log.d(TAG, String.format("Starting Ethernet IpClient(%s)", name));
}
- mIpClientCallback = new EthernetIpClientCallback(listener);
+ mIpClientCallback = new EthernetIpClientCallback();
mDeps.makeIpClient(mContext, name, mIpClientCallback);
mIpClientCallback.awaitIpClientStart();
@@ -540,8 +495,7 @@
provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
}
- void onIpLayerStarted(@NonNull final LinkProperties linkProperties,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ void onIpLayerStarted(@NonNull final LinkProperties linkProperties) {
if (mNetworkAgent != null) {
Log.e(TAG, "Already have a NetworkAgent - aborting new request");
stop();
@@ -573,40 +527,18 @@
});
mNetworkAgent.register();
mNetworkAgent.markConnected();
- realizeNetworkManagementCallback(name, null);
}
- void onIpLayerStopped(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ void onIpLayerStopped() {
// There is no point in continuing if the interface is gone as stop() will be triggered
// by removeInterface() when processed on the handler thread and start() won't
// work for a non-existent interface.
if (null == mDeps.getNetworkInterfaceByName(name)) {
if (DBG) Log.d(TAG, name + " is no longer available.");
// Send a callback in case a provisioning request was in progress.
- maybeSendNetworkManagementCallbackForAbort();
return;
}
- restart(listener);
- }
-
- private void maybeSendNetworkManagementCallbackForAbort() {
- realizeNetworkManagementCallback(null,
- new EthernetNetworkManagementException(
- "The IP provisioning request has been aborted."));
- }
-
- // Must be called on the handler thread
- private void realizeNetworkManagementCallback(@Nullable final String iface,
- @Nullable final EthernetNetworkManagementException e) {
- ensureRunningOnEthernetHandlerThread();
- if (null == mIpClientCallback) {
- return;
- }
-
- EthernetNetworkFactory.maybeSendNetworkManagementCallback(
- mIpClientCallback.mNetworkManagementListener, iface, e);
- // Only send a single callback per listener.
- mIpClientCallback.mNetworkManagementListener = null;
+ restart();
}
private void ensureRunningOnEthernetHandlerThread() {
@@ -636,12 +568,8 @@
}
/** Returns true if state has been modified */
- boolean updateLinkState(final boolean up,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ boolean updateLinkState(final boolean up) {
if (mLinkUp == up) {
- EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, null,
- new EthernetNetworkManagementException(
- "No changes with requested link state " + up + " for " + name));
return false;
}
mLinkUp = up;
@@ -654,7 +582,6 @@
registerNetworkOffer();
}
- EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, name, null);
return true;
}
@@ -665,8 +592,7 @@
mIpClientCallback.awaitIpClientShutdown();
mIpClient = null;
}
- // Send an abort callback if an updateInterface request was in progress.
- maybeSendNetworkManagementCallbackForAbort();
+
mIpClientCallback = null;
if (mNetworkAgent != null) {
@@ -723,13 +649,9 @@
}
void restart() {
- restart(null);
- }
-
- void restart(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
if (DBG) Log.d(TAG, "reconnecting Ethernet");
stop();
- start(listener);
+ start();
}
@Override
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index dae3d2a..edf04b2 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -260,7 +260,7 @@
@Override
public void updateConfiguration(@NonNull final String iface,
@NonNull final EthernetNetworkUpdateRequest request,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ @Nullable final INetworkInterfaceOutcomeReceiver cb) {
Objects.requireNonNull(iface);
Objects.requireNonNull(request);
throwIfEthernetNotStarted();
@@ -277,31 +277,31 @@
}
mTracker.updateConfiguration(
- iface, request.getIpConfiguration(), nc, listener);
+ iface, request.getIpConfiguration(), nc, new EthernetCallback(cb));
}
@Override
public void enableInterface(@NonNull final String iface,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
- Log.i(TAG, "enableInterface called with: iface=" + iface + ", listener=" + listener);
+ @Nullable final INetworkInterfaceOutcomeReceiver cb) {
+ Log.i(TAG, "enableInterface called with: iface=" + iface + ", cb=" + cb);
Objects.requireNonNull(iface);
throwIfEthernetNotStarted();
enforceAdminPermission(iface, false, "enableInterface()");
- mTracker.enableInterface(iface, listener);
+ mTracker.enableInterface(iface, new EthernetCallback(cb));
}
@Override
public void disableInterface(@NonNull final String iface,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
- Log.i(TAG, "disableInterface called with: iface=" + iface + ", listener=" + listener);
+ @Nullable final INetworkInterfaceOutcomeReceiver cb) {
+ Log.i(TAG, "disableInterface called with: iface=" + iface + ", cb=" + cb);
Objects.requireNonNull(iface);
throwIfEthernetNotStarted();
enforceAdminPermission(iface, false, "disableInterface()");
- mTracker.disableInterface(iface, listener);
+ mTracker.disableInterface(iface, new EthernetCallback(cb));
}
@Override
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index ba367cf..00dff5b 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -29,7 +29,6 @@
import android.net.EthernetManager;
import android.net.IEthernetServiceListener;
import android.net.INetd;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.ITetheredInterfaceCallback;
import android.net.InterfaceConfigurationParcel;
import android.net.IpConfiguration;
@@ -271,7 +270,7 @@
}
writeIpConfiguration(iface, ipConfiguration);
mHandler.post(() -> {
- mFactory.updateInterface(iface, ipConfiguration, null, null);
+ mFactory.updateInterface(iface, ipConfiguration, null);
broadcastInterfaceStateChange(iface);
});
}
@@ -335,7 +334,7 @@
protected void updateConfiguration(@NonNull final String iface,
@Nullable final IpConfiguration ipConfig,
@Nullable final NetworkCapabilities capabilities,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ @Nullable final EthernetCallback cb) {
if (DBG) {
Log.i(TAG, "updateConfiguration, iface: " + iface + ", capabilities: " + capabilities
+ ", ipConfig: " + ipConfig);
@@ -353,21 +352,29 @@
mNetworkCapabilities.put(iface, capabilities);
}
mHandler.post(() -> {
- mFactory.updateInterface(iface, localIpConfig, capabilities, listener);
- broadcastInterfaceStateChange(iface);
+ mFactory.updateInterface(iface, localIpConfig, capabilities);
+
+ // only broadcast state change when the ip configuration is updated.
+ if (ipConfig != null) {
+ broadcastInterfaceStateChange(iface);
+ }
+ // Always return success. Even if the interface does not currently exist, the
+ // IpConfiguration and NetworkCapabilities were saved and will be applied if an
+ // interface with the given name is ever added.
+ cb.onResult(iface);
});
}
@VisibleForTesting(visibility = PACKAGE)
protected void enableInterface(@NonNull final String iface,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
- mHandler.post(() -> updateInterfaceState(iface, true, listener));
+ @Nullable final EthernetCallback cb) {
+ mHandler.post(() -> updateInterfaceState(iface, true, cb));
}
@VisibleForTesting(visibility = PACKAGE)
protected void disableInterface(@NonNull final String iface,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
- mHandler.post(() -> updateInterfaceState(iface, false, listener));
+ @Nullable final EthernetCallback cb) {
+ mHandler.post(() -> updateInterfaceState(iface, false, cb));
}
IpConfiguration getIpConfiguration(String iface) {
@@ -571,8 +578,14 @@
// Bring up the interface so we get link status indications.
try {
PermissionUtils.enforceNetworkStackPermission(mContext);
- NetdUtils.setInterfaceUp(mNetd, iface);
+ // Read the flags before attempting to bring up the interface. If the interface is
+ // already running an UP event is created after adding the interface.
config = NetdUtils.getInterfaceConfigParcel(mNetd, iface);
+ if (NetdUtils.hasFlag(config, INetd.IF_STATE_DOWN)) {
+ // As a side-effect, NetdUtils#setInterfaceUp() also clears the interface's IPv4
+ // address and readds it which *could* lead to unexpected behavior in the future.
+ NetdUtils.setInterfaceUp(mNetd, iface);
+ }
} catch (IllegalStateException e) {
// Either the system is crashing or the interface has disappeared. Just ignore the
// error; we haven't modified any state because we only do that if our calls succeed.
@@ -608,23 +621,31 @@
// Note: if the interface already has link (e.g., if we crashed and got
// restarted while it was running), we need to fake a link up notification so we
// start configuring it.
- if (NetdUtils.hasFlag(config, "running")) {
+ if (NetdUtils.hasFlag(config, INetd.IF_FLAG_RUNNING)) {
updateInterfaceState(iface, true);
}
}
private void updateInterfaceState(String iface, boolean up) {
- updateInterfaceState(iface, up, null /* listener */);
+ // TODO: pull EthernetCallbacks out of EthernetNetworkFactory.
+ updateInterfaceState(iface, up, new EthernetCallback(null /* cb */));
}
private void updateInterfaceState(@NonNull final String iface, final boolean up,
- @Nullable final INetworkInterfaceOutcomeReceiver listener) {
+ @Nullable final EthernetCallback cb) {
final int mode = getInterfaceMode(iface);
final boolean factoryLinkStateUpdated = (mode == INTERFACE_MODE_CLIENT)
- && mFactory.updateInterfaceLinkState(iface, up, listener);
+ && mFactory.updateInterfaceLinkState(iface, up);
if (factoryLinkStateUpdated) {
broadcastInterfaceStateChange(iface);
+ cb.onResult(iface);
+ } else {
+ // The interface may already be in the correct state. While usually this should not be
+ // an error, since updateInterfaceState is used in setInterfaceEnabled() /
+ // setInterfaceDisabled() it has to be reported as such.
+ // It is also possible that the interface disappeared or is in server mode.
+ cb.onError("Failed to set link state " + (up ? "up" : "down") + " for " + iface);
}
}
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
index 3b44d81..57466a6 100644
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -22,10 +22,12 @@
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
@@ -136,4 +138,37 @@
mHandler.post(() -> addInterface(ifName));
}
}
+
+ /** get interface name by interface index from bpf map */
+ public String getIfNameByIndex(final long index) {
+ try {
+ final InterfaceMapValue value = mBpfMap.getValue(new U32(index));
+ if (value == null) {
+ Log.e(TAG, "No if name entry for index " + index);
+ return null;
+ }
+ return value.getInterfaceNameString();
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to get entry for index " + index + ": " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Dump BPF map
+ *
+ * @param pw print writer
+ */
+ public void dump(final IndentingPrintWriter pw) {
+ pw.println("BPF map status:");
+ pw.increaseIndent();
+ BpfDump.dumpMapStatus(mBpfMap, pw, "IfaceIndexNameMap", IFACE_INDEX_NAME_MAP_PATH);
+ pw.decreaseIndent();
+ pw.println("BPF map content:");
+ pw.increaseIndent();
+ BpfDump.dumpMap(mBpfMap, pw, "IfaceIndexNameMap",
+ (key, value) -> "ifaceIndex=" + key.val
+ + " ifaceName=" + value.getInterfaceNameString());
+ pw.decreaseIndent();
+ }
}
diff --git a/service-t/src/com/android/server/net/InterfaceMapValue.java b/service-t/src/com/android/server/net/InterfaceMapValue.java
index 42c0044..95da981 100644
--- a/service-t/src/com/android/server/net/InterfaceMapValue.java
+++ b/service-t/src/com/android/server/net/InterfaceMapValue.java
@@ -16,20 +16,45 @@
package com.android.server.net;
import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
+
+import java.util.Arrays;
/**
* The value of bpf interface index map which is used for NetworkStatsService.
*/
public class InterfaceMapValue extends Struct {
+ private static final int IF_NAME_SIZE = 16;
+
@Field(order = 0, type = Type.ByteArray, arraysize = 16)
public final byte[] interfaceName;
public InterfaceMapValue(String iface) {
- final byte[] ifaceArray = iface.getBytes();
- interfaceName = new byte[16];
// All array bytes after the interface name, if any, must be 0.
- System.arraycopy(ifaceArray, 0, interfaceName, 0, ifaceArray.length);
+ interfaceName = Arrays.copyOf(iface.getBytes(), IF_NAME_SIZE);
+ }
+
+ /**
+ * Constructor for Struct#parse. Build this struct from byte array of interface name.
+ *
+ * @param ifName Byte array of interface name, length is expected to be IF_NAME_SIZE(16).
+ * If longer or shorter, interface name will be truncated or padded with zeros.
+ * All array bytes after the interface name, if any, must be 0.
+ */
+ public InterfaceMapValue(final byte[] ifName) {
+ interfaceName = Arrays.copyOf(ifName, IF_NAME_SIZE);
+ }
+
+ /** Returns the length of the null-terminated string. */
+ private int strlen(byte[] str) {
+ for (int i = 0; i < str.length; ++i) {
+ if (str[i] == '\0') {
+ return i;
+ }
+ }
+ return str.length;
+ }
+
+ public String getInterfaceNameString() {
+ return new String(interfaceName, 0 /* offset */, strlen(interfaceName));
}
}
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 96c615b..807f5d7 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -154,6 +154,7 @@
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.BestClock;
import com.android.net.module.util.BinderUtils;
+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.DeviceConfigUtils;
@@ -161,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;
@@ -453,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());
@@ -586,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);
+ }
}
/**
@@ -781,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));
+ }
}
/**
@@ -1292,7 +1321,7 @@
mNetd.bandwidthSetGlobalAlert(mGlobalAlertBytes);
} catch (IllegalStateException e) {
Log.w(TAG, "problem registering for global alert: " + e);
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
// ignored; service lives in system_server
}
invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetAlert(mGlobalAlertBytes));
@@ -2468,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
}
/**
@@ -2532,6 +2561,7 @@
// usage: dumpsys netstats --full --uid --tag --poll --checkin
final boolean poll = argSet.contains("--poll") || argSet.contains("poll");
final boolean checkin = argSet.contains("--checkin");
+ final boolean bpfRawMap = argSet.contains("--bpfRawMap");
final boolean fullHistory = argSet.contains("--full") || argSet.contains("full");
final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail");
final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail");
@@ -2573,6 +2603,11 @@
return;
}
+ if (bpfRawMap) {
+ dumpRawMapLocked(pw, args);
+ return;
+ }
+
pw.println("Directory:");
pw.increaseIndent();
pw.println(mStatsDir);
@@ -2706,6 +2741,12 @@
}
pw.println();
+ pw.println("InterfaceMapUpdater:");
+ pw.increaseIndent();
+ mInterfaceMapUpdater.dump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
pw.println("BPF map status:");
pw.increaseIndent();
dumpMapStatus(pw);
@@ -2720,6 +2761,8 @@
dumpCookieTagMapLocked(pw);
dumpUidCounterSetMapLocked(pw);
dumpAppUidStatsMapLocked(pw);
+ dumpStatsMapLocked(mStatsMapA, pw, "mStatsMapA");
+ dumpStatsMapLocked(mStatsMapB, pw, "mStatsMapB");
pw.decreaseIndent();
}
}
@@ -2743,6 +2786,38 @@
proto.flush();
}
+ private <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
+ IndentingPrintWriter pw) throws ErrnoException {
+ if (map == null) {
+ pw.println("Map is null");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No entries");
+ return;
+ }
+ // If there is a concurrent entry deletion, value could be null. http://b/220084230.
+ // Also, map.forEach could restart iteration from the beginning and dump could contain
+ // duplicated entries. User of this dump needs to take care of the duplicated entries.
+ map.forEach((k, v) -> {
+ if (v != null) {
+ pw.println(BpfDump.toBase64EncodedString(k, v));
+ }
+ });
+ }
+
+ @GuardedBy("mStatsLock")
+ private void dumpRawMapLocked(final IndentingPrintWriter pw, final String[] args) {
+ if (CollectionUtils.contains(args, "--cookieTagMap")) {
+ try {
+ dumpRawMap(mCookieTagMap, pw);
+ } catch (ErrnoException e) {
+ pw.println("Error dumping cookieTag map: " + e);
+ }
+ return;
+ }
+ }
+
private static void dumpInterfaces(ProtoOutputStream proto, long tag,
ArrayMap<String, NetworkIdentitySet> ifaces) {
for (int i = 0; i < ifaces.size(); i++) {
@@ -2773,6 +2848,8 @@
pw.println("mUidCounterSetMap: "
+ getMapStatus(mUidCounterSetMap, UID_COUNTERSET_MAP_PATH));
pw.println("mAppUidStatsMap: " + getMapStatus(mAppUidStatsMap, APP_UID_STATS_MAP_PATH));
+ pw.println("mStatsMapA: " + getMapStatus(mStatsMapA, STATS_MAP_A_PATH));
+ pw.println("mStatsMapB: " + getMapStatus(mStatsMapB, STATS_MAP_B_PATH));
}
@GuardedBy("mStatsLock")
@@ -2780,24 +2857,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")
@@ -2805,22 +2868,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")
@@ -2828,27 +2877,37 @@
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 + " "
+ BpfDump.dumpMap(mAppUidStatsMap, pw, "mAppUidStatsMap",
+ "uid rxBytes rxPackets txBytes txPackets",
+ (key, value) -> key.uid + " "
+ + value.rxBytes + " "
+ + value.rxPackets + " "
+ + value.txBytes + " "
+ + value.txPackets);
+ }
+
+ @GuardedBy("mStatsLock")
+ private void dumpStatsMapLocked(final IBpfMap<StatsMapKey, StatsMapValue> statsMap,
+ final IndentingPrintWriter pw, final String mapName) {
+ if (statsMap == null) {
+ return;
+ }
+
+ BpfDump.dumpMap(statsMap, pw, mapName,
+ "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
+ (key, value) -> {
+ final long ifIndex = key.ifaceIndex;
+ final String ifName = mInterfaceMapUpdater.getIfNameByIndex(ifIndex);
+ return ifIndex + " "
+ + (ifName != null ? ifName : "unknown") + " "
+ + "0x" + Long.toHexString(key.tag) + " "
+ + key.uid + " "
+ + key.counterSet + " "
+ 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();
+ + 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..7d3b39a 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
@@ -307,7 +309,7 @@
],
out: ["service_connectivity_jarjar_rules.txt"],
cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :service-connectivity-pre-jarjar{.jar}) " +
+ "$(location :service-connectivity-pre-jarjar{.jar}) " +
"$(location :service-connectivity-tiramisu-pre-jarjar{.jar}) " +
"--prefix android.net.connectivity " +
"--excludes $(location jarjar-excludes.txt) " +
@@ -326,9 +328,16 @@
],
out: ["service_nearby_jarjar_rules.txt"],
cmd: "$(location jarjar-rules-generator) " +
- "--jars $(location :service-nearby-pre-jarjar{.jar}) " +
+ "$(location :service-nearby-pre-jarjar{.jar}) " +
"--prefix com.android.server.nearby " +
"--excludes $(location jarjar-excludes.txt) " +
"--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 11ba235..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();
@@ -191,13 +191,17 @@
mTc.dump(fd, verbose);
}
+static jint native_synchronizeKernelRCU(JNIEnv* env, jobject self) {
+ return -bpf::synchronizeKernelRCU();
+}
+
/*
* JNI registration.
*/
// 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},
@@ -225,6 +229,8 @@
(void*)native_setPermissionForUids},
{"native_dump", "(Ljava/io/FileDescriptor;Z)V",
(void*)native_dump},
+ {"native_synchronizeKernelRCU", "()I",
+ (void*)native_synchronizeKernelRCU},
};
// clang-format on
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index a1d0310..bd74d54 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -59,7 +59,8 @@
}
}
-static int createTunTapImpl(JNIEnv* env, bool isTun, bool hasCarrier, const char* iface) {
+static int createTunTapImpl(JNIEnv* env, bool isTun, bool hasCarrier, bool setIffMulticast,
+ const char* iface) {
base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK));
ifreq ifr{};
@@ -76,8 +77,8 @@
setTunTapCarrierEnabledImpl(env, iface, tun.get(), hasCarrier);
}
- // Mark TAP interfaces as supporting multicast
- if (!isTun) {
+ // Mark some TAP interfaces as supporting multicast
+ if (setIffMulticast && !isTun) {
base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
ifr.ifr_flags = IFF_MULTICAST;
@@ -122,14 +123,14 @@
}
static jint createTunTap(JNIEnv* env, jclass /* clazz */, jboolean isTun,
- jboolean hasCarrier, jstring jIface) {
+ jboolean hasCarrier, jboolean setIffMulticast, jstring jIface) {
ScopedUtfChars iface(env, jIface);
if (!iface.c_str()) {
jniThrowNullPointerException(env, "iface");
return -1;
}
- return createTunTapImpl(env, isTun, hasCarrier, iface.c_str());
+ return createTunTapImpl(env, isTun, hasCarrier, setIffMulticast, iface.c_str());
}
static void bringUpInterface(JNIEnv* env, jclass /* clazz */, jstring jIface) {
@@ -145,7 +146,7 @@
static const JNINativeMethod gMethods[] = {
{"nativeSetTunTapCarrierEnabled", "(Ljava/lang/String;IZ)V", (void*)setTunTapCarrierEnabled},
- {"nativeCreateTunTap", "(ZZLjava/lang/String;)I", (void*)createTunTap},
+ {"nativeCreateTunTap", "(ZZZLjava/lang/String;)I", (void*)createTunTap},
{"nativeBringUpInterface", "(Ljava/lang/String;)V", (void*)bringUpInterface},
};
diff --git a/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index 3db1b22..f366363 100644
--- a/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -38,7 +38,7 @@
* and the list of the subtypes in the query as a {@link Pair}. If a query is failed to build, or if
* it can not be enqueued, then call to {@link #call()} returns {@code null}.
*/
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<String>>> {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java b/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java
index ed28700..0b2066a 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java
@@ -27,7 +27,7 @@
import java.nio.charset.StandardCharsets;
/** mDNS-related constants. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
@VisibleForTesting
public final class MdnsConstants {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
index e35743c..bd47414 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
@@ -27,7 +27,7 @@
import java.util.Objects;
/** An mDNS "AAAA" or "A" record, which holds an IPv6 or IPv4 address. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
@VisibleForTesting
public class MdnsInetAddressRecord extends MdnsRecord {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java
index 2b36a3c..0166815 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java
@@ -22,7 +22,7 @@
import java.util.Arrays;
/** An mDNS "PTR" record, which holds a name (the "pointer"). */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
@VisibleForTesting
public class MdnsPointerRecord extends MdnsRecord {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java
index 4bfdb2c..24fb09e 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java
@@ -30,7 +30,7 @@
* Abstract base class for mDNS records. Stores the header fields and provides methods for reading
* the record from and writing it to a packet.
*/
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
public abstract class MdnsRecord {
public static final int TYPE_A = 0x0001;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java b/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
index 1305e07..9f3894f 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -25,7 +25,7 @@
import java.util.List;
/** An mDNS response. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
public class MdnsResponse {
private final List<MdnsRecord> records;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 72c3156..3e5fc42 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -30,7 +30,7 @@
import java.util.List;
/** A class that decodes mDNS responses from UDP packets. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
public class MdnsResponseDecoder {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java
index 51de3b2..7f54d96 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java
@@ -24,7 +24,7 @@
import java.util.Objects;
/** An mDNS "SRV" record, which contains service information. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
@VisibleForTesting
public class MdnsServiceRecord extends MdnsRecord {
@@ -143,7 +143,7 @@
return super.equals(other)
&& (servicePriority == otherRecord.servicePriority)
&& (serviceWeight == otherRecord.serviceWeight)
- && Objects.equals(serviceHost, otherRecord.serviceHost)
+ && Arrays.equals(serviceHost, otherRecord.serviceHost)
&& (servicePort == otherRecord.servicePort);
}
-}
\ No newline at end of file
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index c3a86e3..e335de9 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -39,7 +39,7 @@
* Instance of this class sends and receives mDNS packets of a given service type and invoke
* registered {@link MdnsServiceBrowserListener} instances.
*/
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
public class MdnsServiceTypeClient {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
index 241a52a..34db7f0 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
@@ -32,7 +32,7 @@
*
* @see MulticastSocket for javadoc of each public method.
*/
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
public class MdnsSocket {
private static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
index e689d6c..010f761 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -46,7 +46,7 @@
*
* <p>See https://tools.ietf.org/html/rfc6763 (namely sections 4 and 5).
*/
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
public class MdnsSocketClient {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java
index a5b5595..a364560 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java
@@ -25,7 +25,7 @@
import java.util.Objects;
/** An mDNS "TXT" record, which contains a list of text strings. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
@SuppressWarnings("nullness")
@VisibleForTesting
public class MdnsTextRecord extends MdnsRecord {
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index a26d1e6..49b23c9 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());
@@ -643,48 +647,6 @@
dw.println("mCookieTagMap print end with error: %s", res.error().message().c_str());
}
-
- // Print uidStatsMap content.
- std::string statsHeader = StringPrintf("ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes"
- " rxPackets txBytes txPackets");
- dumpBpfMap("mStatsMapA", dw, statsHeader);
- const auto printStatsInfo = [&dw, this](const StatsKey& key, const StatsValue& value,
- const BpfMap<StatsKey, StatsValue>&) {
- uint32_t ifIndex = key.ifaceIndex;
- auto ifname = mIfaceIndexNameMap.readValue(ifIndex);
- if (!ifname.ok()) {
- ifname = IfaceValue{"unknown"};
- }
- dw.println("%u %s 0x%x %u %u %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, ifIndex,
- ifname.value().name, key.tag, key.uid, key.counterSet, value.rxBytes,
- value.rxPackets, value.txBytes, value.txPackets);
- return base::Result<void>();
- };
- res = mStatsMapA.iterateWithValue(printStatsInfo);
- if (!res.ok()) {
- dw.println("mStatsMapA print end with error: %s", res.error().message().c_str());
- }
-
- // Print TagStatsMap content.
- dumpBpfMap("mStatsMapB", dw, statsHeader);
- res = mStatsMapB.iterateWithValue(printStatsInfo);
- if (!res.ok()) {
- dw.println("mStatsMapB print end with error: %s", res.error().message().c_str());
- }
-
- // Print ifaceIndexToNameMap content.
- dumpBpfMap("mIfaceIndexNameMap", dw, "");
- const auto printIfaceNameInfo = [&dw](const uint32_t& key, const IfaceValue& value,
- const BpfMap<uint32_t, IfaceValue>&) {
- const char* ifname = value.name;
- dw.println("ifaceIndex=%u ifaceName=%s", key, ifname);
- return base::Result<void>();
- };
- res = mIfaceIndexNameMap.iterateWithValue(printIfaceNameInfo);
- if (!res.ok()) {
- dw.println("mIfaceIndexNameMap print end with error: %s", res.error().message().c_str());
- }
-
// Print ifaceStatsMap content
std::string ifaceStatsHeader = StringPrintf("ifaceIndex ifaceName rxBytes rxPackets txBytes"
" txPackets");
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index d08ffee..8525738 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -60,7 +60,6 @@
constexpr uint32_t TEST_TAG = 42;
constexpr uint32_t TEST_COUNTERSET = 1;
constexpr int TEST_COOKIE = 1;
-constexpr char TEST_IFNAME[] = "test0";
constexpr int TEST_IFINDEX = 999;
constexpr int RXPACKETS = 1;
constexpr int RXBYTES = 100;
@@ -792,17 +791,7 @@
// 999 test0 0x2a 10086 1 100 1 0 0
std::vector<std::string> expectedLines = {
"mCookieTagMap:",
- fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID),
- "mStatsMapA",
- "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
- fmt::format("{} {} {:#x} {} {} {} {} {} {}",
- TEST_IFINDEX, TEST_IFNAME, TEST_TAG, TEST_UID, TEST_COUNTERSET, RXBYTES,
- RXPACKETS, TXBYTES, TXPACKETS)};
-
- populateFakeIfaceIndexName(TEST_IFNAME, TEST_IFINDEX);
- expectedLines.emplace_back("mIfaceIndexNameMap:");
- expectedLines.emplace_back(fmt::format("ifaceIndex={} ifaceName={}",
- TEST_IFINDEX, TEST_IFNAME));
+ fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID)};
ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, HAPPY_BOX_MATCH,
TrafficController::IptOpInsert)));
@@ -829,9 +818,6 @@
std::vector<std::string> expectedLines = {
fmt::format("mCookieTagMap {}", kErrIterate),
- fmt::format("mStatsMapA {}", kErrIterate),
- fmt::format("mStatsMapB {}", kErrIterate),
- fmt::format("mIfaceIndexNameMap {}", kErrIterate),
fmt::format("mIfaceStatsMap {}", kErrIterate),
fmt::format("mConfigurationMap {}", kErrReadRulesConfig),
fmt::format("mConfigurationMap {}", kErrReadStatsMapConfig),
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 594223c..bfa0808 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -26,11 +26,16 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+import static android.net.INetd.PERMISSION_INTERNET;
+import static android.net.INetd.PERMISSION_UNINSTALLED;
import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENODEV;
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;
@@ -38,20 +43,26 @@
import android.provider.DeviceConfig;
import android.system.ErrnoException;
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.Arrays;
-import java.util.HashSet;
+import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* BpfNetMaps is responsible for providing traffic controller relevant functionality.
@@ -81,19 +92,30 @@
// BpfNetMaps acquires this lock while sequence of read, modify, and write.
private static final Object sUidRulesConfigBpfMapLock = new Object();
+ // Lock for sConfigurationMap entry for CURRENT_STATS_MAP_CONFIGURATION_KEY.
+ // BpfNetMaps acquires this lock while sequence of read, modify, and write.
+ // BpfNetMaps is an only writer of this entry.
+ private static final Object sCurrentStatsMapConfigLock = new Object();
+
private static final String CONFIGURATION_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_configuration_map";
private static final String UID_OWNER_MAP_PATH =
"/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 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;
@@ -123,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;
}
@@ -131,11 +153,28 @@
* Set uidOwnerMap for test.
*/
@VisibleForTesting
- public static void setUidOwnerMapForTest(BpfMap<U32, UidOwnerValue> uidOwnerMap) {
+ public static void setUidOwnerMapForTest(IBpfMap<U32, UidOwnerValue> uidOwnerMap) {
sUidOwnerMap = uidOwnerMap;
}
- private static BpfMap<U32, U32> getConfigurationMap() {
+ /**
+ * Set uidPermissionMap for test.
+ */
+ @VisibleForTesting
+ public static void setUidPermissionMapForTest(IBpfMap<U32, U8> uidPermissionMap) {
+ sUidPermissionMap = uidPermissionMap;
+ }
+
+ /**
+ * 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);
@@ -144,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);
@@ -153,6 +192,24 @@
}
}
+ private static IBpfMap<U32, U8> getUidPermissionMap() {
+ try {
+ return new BpfMap<>(
+ UID_PERMISSION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U8.class);
+ } catch (ErrnoException e) {
+ throw new IllegalStateException("Cannot open uid permission map", e);
+ }
+ }
+
+ 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();
@@ -178,6 +235,14 @@
} catch (ErrnoException e) {
throw new IllegalStateException("Failed to initialize uid owner map", e);
}
+
+ if (sUidPermissionMap == null) {
+ sUidPermissionMap = getUidPermissionMap();
+ }
+
+ if (sCookieTagMap == null) {
+ sCookieTagMap = getCookieTagMap();
+ }
}
/**
@@ -194,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.
*/
@@ -209,6 +278,22 @@
public int getIfIndex(final String ifName) {
return Os.if_nametoindex(ifName);
}
+
+ /**
+ * Call synchronize_rcu()
+ */
+ 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. */
@@ -479,6 +564,14 @@
}
}
+ private Set<Integer> asSet(final int[] uids) {
+ final Set<Integer> uidSet = new ArraySet<>();
+ for (final int uid: uids) {
+ uidSet.add(uid);
+ }
+ return uidSet;
+ }
+
/**
* Replaces the contents of the specified UID-based firewall chain.
* Enables the chain for specified uids and disables the chain for non-specified uids.
@@ -500,15 +593,17 @@
// ConnectivityManager#replaceFirewallChain API
throw new IllegalArgumentException("Invalid firewall chain: " + chain);
}
- final Set<Integer> uidSet = Arrays.stream(uids).boxed().collect(Collectors.toSet());
- final Set<Integer> uidSetToRemoveRule = new HashSet<>();
+ final Set<Integer> uidSet = asSet(uids);
+ final Set<Integer> uidSetToRemoveRule = new ArraySet<>();
try {
synchronized (sUidOwnerMap) {
sUidOwnerMap.forEach((uid, config) -> {
// config could be null if there is a concurrent entry deletion.
- // http://b/220084230.
- if (config != null
- && !uidSet.contains((int) uid.val) && (config.rule & match) != 0) {
+ // http://b/220084230. But sUidOwnerMap update must be done while holding a
+ // lock, so this should not happen.
+ if (config == null) {
+ Log.wtf(TAG, "sUidOwnerMap entry was deleted while holding a lock");
+ } else if (!uidSet.contains((int) uid.val) && (config.rule & match) != 0) {
uidSetToRemoveRule.add((int) uid.val);
}
});
@@ -696,12 +791,40 @@
/**
* Request netd to change the current active network stats map.
*
+ * @throws UnsupportedOperationException if called on pre-T devices.
* @throws ServiceSpecificException in case of failure, with an error code indicating the
* cause of the failure.
*/
public void swapActiveStatsMap() {
- final int err = native_swapActiveStatsMap();
- maybeThrow(err, "Unable to swap active stats map");
+ throwIfPreT("swapActiveStatsMap is not available on pre-T devices");
+
+ if (sEnableJavaBpfMap) {
+ try {
+ synchronized (sCurrentStatsMapConfigLock) {
+ final long config = sConfigurationMap.getValue(
+ CURRENT_STATS_MAP_CONFIGURATION_KEY).val;
+ final long newConfig = (config == STATS_SELECT_MAP_A)
+ ? STATS_SELECT_MAP_B : STATS_SELECT_MAP_A;
+ sConfigurationMap.updateEntry(CURRENT_STATS_MAP_CONFIGURATION_KEY,
+ new U32(newConfig));
+ }
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno, "Failed to swap active stats map");
+ }
+
+ // After changing the config, it's needed to make sure all the current running eBPF
+ // programs are finished and all the CPUs are aware of this config change before the old
+ // map is modified. So special hack is needed here to wait for the kernel to do a
+ // synchronize_rcu(). Once the kernel called synchronize_rcu(), the updated config will
+ // be available to all cores and the next eBPF programs triggered inside the kernel will
+ // use the new map configuration. So once this function returns it is safe to modify the
+ // old stats map without concerning about race between the kernel and userspace.
+ final int err = mDeps.synchronizeKernelRCU();
+ maybeThrow(err, "synchronizeKernelRCU failed");
+ } else {
+ final int err = native_swapActiveStatsMap();
+ maybeThrow(err, "Unable to swap active stats map");
+ }
}
/**
@@ -719,7 +842,68 @@
mNetd.trafficSetNetPermForUids(permissions, uids);
return;
}
- native_setPermissionForUids(permissions, uids);
+
+ if (sEnableJavaBpfMap) {
+ // Remove the entry if package is uninstalled or uid has only INTERNET permission.
+ if (permissions == PERMISSION_UNINSTALLED || permissions == PERMISSION_INTERNET) {
+ for (final int uid : uids) {
+ try {
+ sUidPermissionMap.deleteEntry(new U32(uid));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to remove uid " + uid + " from permission map: " + e);
+ }
+ }
+ return;
+ }
+
+ for (final int uid : uids) {
+ try {
+ sUidPermissionMap.updateEntry(new U32(uid), new U8((short) permissions));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to set permission "
+ + permissions + " to uid " + uid + ": " + e);
+ }
+ }
+ } else {
+ native_setPermissionForUids(permissions, uids);
+ }
+ }
+
+ /** 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;
}
/**
@@ -739,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);
@@ -753,4 +937,5 @@
private native int native_swapActiveStatsMap();
private native void native_setPermissionForUids(int permissions, int[] uids);
private native void native_dump(FileDescriptor fd, boolean verbose);
+ private static native int native_synchronizeKernelRCU();
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
old mode 100644
new mode 100755
index 1ac95a1..8ec979a
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -257,6 +257,7 @@
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.NetworkCapabilitiesUtils;
+import com.android.net.module.util.PerUidCounter;
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.TcUtils;
import com.android.net.module.util.netlink.InetDiagMessage;
@@ -386,9 +387,9 @@
protected final PermissionMonitor mPermissionMonitor;
@VisibleForTesting
- final PerUidCounter mNetworkRequestCounter;
+ final RequestInfoPerUidCounter mNetworkRequestCounter;
@VisibleForTesting
- final PerUidCounter mSystemNetworkRequestCounter;
+ final RequestInfoPerUidCounter mSystemNetworkRequestCounter;
private volatile boolean mLockdownEnabled;
@@ -1186,71 +1187,6 @@
}
/**
- * Keeps track of the number of requests made under different uids.
- */
- // TODO: Remove the hack and use com.android.net.module.util.PerUidCounter instead.
- public static class PerUidCounter {
- private final int mMaxCountPerUid;
-
- // Map from UID to number of NetworkRequests that UID has filed.
- @VisibleForTesting
- @GuardedBy("mUidToNetworkRequestCount")
- final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
-
- /**
- * Constructor
- *
- * @param maxCountPerUid the maximum count per uid allowed
- */
- public PerUidCounter(final int maxCountPerUid) {
- mMaxCountPerUid = maxCountPerUid;
- }
-
- /**
- * Increments the request count of the given uid. Throws an exception if the number
- * of open requests for the uid exceeds the value of maxCounterPerUid which is the value
- * passed into the constructor. see: {@link #PerUidCounter(int)}.
- *
- * @throws ServiceSpecificException with
- * {@link ConnectivityManager.Errors.TOO_MANY_REQUESTS} if the number of requests for
- * the uid exceed the allowed number.
- *
- * @param uid the uid that the request was made under
- */
- public void incrementCountOrThrow(final int uid) {
- synchronized (mUidToNetworkRequestCount) {
- final int newRequestCount = mUidToNetworkRequestCount.get(uid, 0) + 1;
- if (newRequestCount >= mMaxCountPerUid) {
- throw new ServiceSpecificException(
- ConnectivityManager.Errors.TOO_MANY_REQUESTS,
- "Uid " + uid + " exceeded its allotted requests limit");
- }
- mUidToNetworkRequestCount.put(uid, newRequestCount);
- }
- }
-
- /**
- * Decrements the request count of the given uid.
- *
- * @param uid the uid that the request was made under
- */
- public void decrementCount(final int uid) {
- synchronized (mUidToNetworkRequestCount) {
- /* numToDecrement */
- final int newRequestCount = mUidToNetworkRequestCount.get(uid, 0) - 1;
- if (newRequestCount < 0) {
- logwtf("BUG: too small request count " + newRequestCount + " for UID " + uid);
- } else if (newRequestCount == 0) {
- mUidToNetworkRequestCount.delete(uid);
- } else {
- mUidToNetworkRequestCount.put(uid, newRequestCount);
- }
- }
- }
-
- }
-
- /**
* Dependencies of ConnectivityService, for injection in tests.
*/
@VisibleForTesting
@@ -1464,8 +1400,13 @@
mNetIdManager = mDeps.makeNetIdManager();
mContext = Objects.requireNonNull(context, "missing Context");
mResources = deps.getResources(mContext);
- mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID);
- mSystemNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID);
+ // The legacy PerUidCounter is buggy and throwing exception at count == limit.
+ // Pass limit - 1 to maintain backward compatibility.
+ // TODO: Remove the workaround.
+ mNetworkRequestCounter =
+ new RequestInfoPerUidCounter(MAX_NETWORK_REQUESTS_PER_UID - 1);
+ mSystemNetworkRequestCounter =
+ new RequestInfoPerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1);
mMetricsLog = logger;
mNetworkRanker = new NetworkRanker();
@@ -1803,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;
}
}
@@ -2537,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
@@ -3098,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);
+ }
}
/**
@@ -3590,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;
}
@@ -3619,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
@@ -3749,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;
@@ -3798,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);
@@ -3855,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)
@@ -3867,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);
@@ -3903,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);
}
}
@@ -4256,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) {
@@ -4266,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;
@@ -4371,7 +4314,7 @@
}
// Delayed teardown.
- if (nai.created) {
+ if (nai.isCreated()) {
try {
mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM);
} catch (RemoteException e) {
@@ -4392,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.
@@ -4453,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();
}
@@ -4586,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;
}
@@ -4641,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
@@ -4763,7 +4706,7 @@
}
}
}
- nri.decrementRequestCount();
+ nri.mPerUidCounter.decrementCount(nri.mUid);
mNetworkRequestInfoLogs.log("RELEASE " + nri);
checkNrisConsistency(nri);
@@ -4866,7 +4809,7 @@
}
}
- private PerUidCounter getRequestCounter(NetworkRequestInfo nri) {
+ private RequestInfoPerUidCounter getRequestCounter(NetworkRequestInfo nri) {
return checkAnyPermissionOf(
nri.mPid, nri.mUid, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
? mSystemNetworkRequestCounter : mNetworkRequestCounter;
@@ -4918,7 +4861,7 @@
return;
}
- if (nai.everValidated) {
+ if (nai.everValidated()) {
// The network validated while the dialog box was up. Take no action.
return;
}
@@ -4963,7 +4906,7 @@
return;
}
- if (nai.lastValidated) {
+ if (nai.isValidated()) {
// The network validated while the dialog box was up. Take no action.
return;
}
@@ -4995,22 +4938,27 @@
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();
}
}
- private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
- if (VDBG) log("scheduleUnvalidatedPrompt " + nai.network);
+ private void scheduleUnvalidatedPrompt(@NonNull final Network network) {
+ scheduleUnvalidatedPrompt(network, PROMPT_UNVALIDATED_DELAY_MS);
+ }
+
+ /** Schedule unvalidated prompt for testing */
+ @VisibleForTesting
+ public void scheduleUnvalidatedPrompt(@NonNull final Network network, final long delayMs) {
+ if (VDBG) log("scheduleUnvalidatedPrompt " + network);
mHandler.sendMessageDelayed(
- mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
- PROMPT_UNVALIDATED_DELAY_MS);
+ mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, network), delayMs);
}
@Override
@@ -5150,7 +5098,7 @@
pw.println("Network overrides:");
pw.increaseIndent();
for (NetworkAgentInfo nai : networksSortedById()) {
- if (nai.avoidUnvalidated) {
+ if (0L != nai.getAvoidUnvalidated()) {
pw.println(nai.toShortString());
}
}
@@ -5221,7 +5169,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;
}
@@ -5229,8 +5177,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;
}
@@ -5262,7 +5210,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);
@@ -5616,7 +5564,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,
@@ -5630,7 +5578,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);
@@ -6236,7 +6184,7 @@
final String mCallingAttributionTag;
// Counter keeping track of this NRI.
- final PerUidCounter mPerUidCounter;
+ final RequestInfoPerUidCounter mPerUidCounter;
// Effective UID of this request. This is different from mUid when a privileged process
// files a request on behalf of another UID. This UID is used to determine blocked status,
@@ -6402,10 +6350,6 @@
return Collections.unmodifiableList(tempRequests);
}
- void decrementRequestCount() {
- mPerUidCounter.decrementCount(mUid);
- }
-
void linkDeathRecipient() {
if (null != mBinder) {
try {
@@ -6467,6 +6411,38 @@
}
}
+ // Keep backward compatibility since the ServiceSpecificException is used by
+ // the API surface, see {@link ConnectivityManager#convertServiceException}.
+ public static class RequestInfoPerUidCounter extends PerUidCounter {
+ RequestInfoPerUidCounter(int maxCountPerUid) {
+ super(maxCountPerUid);
+ }
+
+ @Override
+ public synchronized void incrementCountOrThrow(int uid) {
+ try {
+ super.incrementCountOrThrow(uid);
+ } catch (IllegalStateException e) {
+ throw new ServiceSpecificException(
+ ConnectivityManager.Errors.TOO_MANY_REQUESTS,
+ "Uid " + uid + " exceeded its allotted requests limit");
+ }
+ }
+
+ @Override
+ public synchronized void decrementCountOrThrow(int uid) {
+ throw new UnsupportedOperationException("Use decrementCount instead.");
+ }
+
+ public synchronized void decrementCount(int uid) {
+ try {
+ super.decrementCountOrThrow(uid);
+ } catch (IllegalStateException e) {
+ logwtf("Exception when decrement per uid request count: ", e);
+ }
+ }
+ }
+
// This checks that the passed capabilities either do not request a
// specific SSID/SignalStrength, or the calling app has permission to do so.
private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
@@ -6954,6 +6930,7 @@
@Override
public void unofferNetwork(@NonNull final INetworkOfferCallback callback) {
+ Objects.requireNonNull(callback);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_OFFER, callback));
}
@@ -7501,9 +7478,7 @@
notifyIfacesChangedForNetworkStats();
networkAgent.networkMonitor().notifyLinkPropertiesChanged(
new LinkProperties(newLp, true /* parcelSensitiveFields */));
- if (networkAgent.everConnected) {
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
- }
+ notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
}
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
@@ -7782,7 +7757,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) {
@@ -7872,9 +7847,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.
@@ -7886,12 +7861,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);
@@ -7901,7 +7876,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);
@@ -8148,7 +8123,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();
}
@@ -8268,8 +8243,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.
@@ -8701,7 +8676,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
@@ -8763,9 +8738,6 @@
// Gather the list of all relevant agents.
final ArrayList<NetworkAgentInfo> nais = new ArrayList<>();
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
- if (!nai.everConnected) {
- continue;
- }
nais.add(nai);
}
@@ -8889,7 +8861,6 @@
}
for (final NetworkAgentInfo nai : nais) {
- if (!nai.everConnected) continue;
final boolean oldBackground = oldBgNetworks.contains(nai);
// Process listen requests and update capabilities if the background state has
// changed for this network. For consistency with previous behavior, send onLost
@@ -8973,7 +8944,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);
}
@@ -8994,7 +8965,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);
}
}
@@ -9120,12 +9091,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;
@@ -9152,7 +9123,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(),
@@ -9177,7 +9148,7 @@
+ oldInfo.getState() + " to " + state);
}
- if (!networkAgent.created
+ if (!networkAgent.isCreated()
&& (state == NetworkInfo.State.CONNECTED
|| (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) {
@@ -9191,13 +9162,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.
@@ -9242,7 +9213,7 @@
networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
params.networkCapabilities);
}
- scheduleUnvalidatedPrompt(networkAgent);
+ scheduleUnvalidatedPrompt(networkAgent.network);
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
@@ -9281,8 +9252,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);
}
}
@@ -9482,7 +9453,7 @@
}
}
for (NetworkAgentInfo nai : mNetworkAgentInfos) {
- if (nai.everConnected && (activeNetIds.contains(nai.network().netId) || nai.isVPN())) {
+ if (activeNetIds.contains(nai.network().netId) || nai.isVPN()) {
defaultNetworks.add(nai.network);
}
}
@@ -9710,7 +9681,7 @@
return;
}
if (!TextUtils.equals(((WifiInfo)prevInfo).getBSSID(), ((WifiInfo)newInfo).getBSSID())) {
- nai.lastRoamTimestamp = SystemClock.elapsedRealtime();
+ nai.lastRoamTime = SystemClock.elapsedRealtime();
}
}
@@ -9963,7 +9934,7 @@
// Decrement the reference count for this NetworkRequestInfo. The reference count is
// incremented when the NetworkRequestInfo is created as part of
// enforceRequestCountLimit().
- nri.decrementRequestCount();
+ nri.mPerUidCounter.decrementCount(nri.mUid);
return;
}
@@ -10029,7 +10000,7 @@
// Decrement the reference count for this NetworkRequestInfo. The reference count is
// incremented when the NetworkRequestInfo is created as part of
// enforceRequestCountLimit().
- nri.decrementRequestCount();
+ nri.mPerUidCounter.decrementCount(nri.mUid);
iCb.unlinkToDeath(cbInfo, 0);
}
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 15d9f13..5549fbe 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -77,7 +77,7 @@
// Native method stubs
private static native int nativeCreateTunTap(boolean isTun, boolean hasCarrier,
- @NonNull String iface);
+ boolean setIffMulticast, @NonNull String iface);
private static native void nativeSetTunTapCarrierEnabled(@NonNull String iface, int tunFd,
boolean enabled);
@@ -136,8 +136,14 @@
final long token = Binder.clearCallingIdentity();
try {
+ // Note: if the interface is brought up by ethernet, setting IFF_MULTICAST
+ // races NetUtils#setInterfaceUp(). This flag is not necessary for ethernet
+ // tests, so let's not set it when bringUp is false. See also b/242343156.
+ // In the future, we could use RTM_SETLINK with ifi_change set to set the
+ // flags atomically.
+ final boolean setIffMulticast = bringUp;
ParcelFileDescriptor tunIntf = ParcelFileDescriptor.adoptFd(
- nativeCreateTunTap(isTun, hasCarrier, interfaceName));
+ nativeCreateTunTap(isTun, hasCarrier, setIffMulticast, interfaceName));
// Disable DAD and remove router_solicitation_delay before assigning link addresses.
if (disableIpv6ProvisioningDelay) {
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 6c4a021..e1c7b64 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.INetd.IF_STATE_UP;
+import static android.net.INetd.PERMISSION_NETWORK;
import static android.net.INetd.PERMISSION_SYSTEM;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
@@ -386,9 +387,9 @@
static int getFwmark(int netId) {
// See union Fwmark in system/netd/include/Fwmark.h
return (netId & 0xffff)
- | 0x1 << 16 // protectedFromVpn: true
- | 0x1 << 17 // explicitlySelected: true
- | (PERMISSION_SYSTEM & 0x3) << 18;
+ | 0x1 << 16 // explicitlySelected: true
+ | 0x1 << 17 // protectedFromVpn: true
+ | ((PERMISSION_NETWORK | PERMISSION_SYSTEM) & 0x3) << 18; // 2 permission bits = 3
}
@VisibleForTesting
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index c4754eb..aec4a71 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,54 @@
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;
+ public static final int POLICY_IS_INVINCIBLE = 56;
- // 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;
+ // This network has undergone initial validation.
+ //
+ // The stack considers that any result finding some working connectivity (valid, partial,
+ // captive portal) is an initial validation. Negative result (not valid), however, is not
+ // considered initial validation until {@link ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS}
+ // have elapsed. This is because some networks may spuriously fail for a short time immediately
+ // after associating. If no positive result is found after the timeout has elapsed, then
+ // the network has been evaluated once.
+ public static final int POLICY_EVER_EVALUATED = 55;
// 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 +101,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 = 54;
// To help iterate when printing
@VisibleForTesting
@@ -154,7 +152,9 @@
* @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 everEvaluated whether this network ever evaluated at least once
* @param destroyed whether this network has been destroyed pending a replacement connecting
* @return a FullScore that is appropriate to use for ranking.
*/
@@ -163,18 +163,20 @@
// 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 everEvaluated, 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
+ everEvaluated,
+ destroyed);
}
/**
@@ -194,25 +196,31 @@
@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 network can only be destroyed once it has connected.
- final boolean destroyed = 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 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;
+ // A prospective network will eventually be evaluated.
+ final boolean everEvaluated = true;
+ // A network can only be destroyed once it has connected.
+ final boolean destroyed = false;
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, everEvaluated, destroyed);
}
/**
@@ -228,18 +236,21 @@
public FullScore mixInScore(@NonNull final NetworkCapabilities caps,
@NonNull final NetworkAgentConfig config,
final boolean everValidated,
+ final boolean avoidUnvalidated,
final boolean yieldToBadWifi,
+ final boolean everEvaluated,
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
+ everEvaluated,
+ destroyed);
}
// TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
@@ -248,24 +259,28 @@
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 everEvaluated,
+ 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)
+ | (everEvaluated ? 1L << POLICY_EVER_EVALUATED : 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 04f378f..a4c70c8 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -189,42 +189,215 @@
// 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;
+ }
+
+ // Timestamp (SystemClock.elapsedRealTime()) at which the first validation attempt concluded,
+ // or timed out after {@link ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS}. 0 if not yet.
+ private long mFirstEvaluationConcludedTime;
+
+ /**
+ * Notify this NAI that this network has been evaluated.
+ *
+ * The stack considers that any result finding some working connectivity (valid, partial,
+ * captive portal) is an initial validation. Negative result (not valid), however, is not
+ * considered initial validation until {@link ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS}
+ * have elapsed. This is because some networks may spuriously fail for a short time immediately
+ * after associating. If no positive result is found after the timeout has elapsed, then
+ * the network has been evaluated once.
+ *
+ * @return true the first time this is called on this object, then always returns false.
+ */
+ public boolean setEvaluated() {
+ if (0L != mFirstEvaluationConcludedTime) return false;
+ mFirstEvaluationConcludedTime = SystemClock.elapsedRealtime();
+ return true;
+ }
// Delay between when the network is disconnected and when the native network is destroyed.
public int teardownDelayMs;
@@ -241,6 +414,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 +458,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 +628,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 +996,9 @@
@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(),
+ 0L != mFirstEvaluationConcludedTime, isDestroyed());
final NetworkMonitorManager nm = mNetworkMonitor;
if (nm != null) {
nm.notifyNetworkCapabilitiesChanged(nc);
@@ -983,13 +1161,13 @@
// Does this network satisfy request?
public boolean satisfies(NetworkRequest request) {
- return created &&
- request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities);
+ return everConnected()
+ && request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities);
}
public boolean satisfiesImmutableCapabilitiesOf(NetworkRequest request) {
- return created &&
- request.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
+ return everConnected()
+ && request.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
networkCapabilities);
}
@@ -1023,7 +1201,8 @@
*/
public void setScore(final NetworkScore score) {
mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
- everValidatedForYield(), yieldToBadWiFi(), destroyed);
+ everValidated(), 0L == getAvoidUnvalidated(), yieldToBadWiFi(),
+ 0L != mFirstEvaluationConcludedTime, isDestroyed());
}
/**
@@ -1033,11 +1212,8 @@
*/
public void updateScoreForNetworkAgentUpdate() {
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig,
- everValidatedForYield(), yieldToBadWiFi(), destroyed);
- }
-
- private boolean everValidatedForYield() {
- return everValidated && !avoidUnvalidated;
+ everValidated(), 0L != getAvoidUnvalidated(), yieldToBadWiFi(),
+ 0L != mFirstEvaluationConcludedTime, isDestroyed());
}
/**
@@ -1324,14 +1500,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" : "")
@@ -1349,7 +1528,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/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 155f6c4..45da0ea 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -394,8 +394,9 @@
Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
}
- @VisibleForTesting
- static String tagFor(int id) {
+ /** Get the logging tag for a notification ID */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static String tagFor(int id) {
return String.format("ConnectivityNotification:%d", id);
}
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/service/src/com/android/server/connectivity/QosCallbackTracker.java b/service/src/com/android/server/connectivity/QosCallbackTracker.java
index b6ab47b..336a399 100644
--- a/service/src/com/android/server/connectivity/QosCallbackTracker.java
+++ b/service/src/com/android/server/connectivity/QosCallbackTracker.java
@@ -52,7 +52,7 @@
private final Handler mConnectivityServiceHandler;
@NonNull
- private final ConnectivityService.PerUidCounter mNetworkRequestCounter;
+ private final ConnectivityService.RequestInfoPerUidCounter mNetworkRequestCounter;
/**
* Each agent gets a unique callback id that is used to proxy messages back to the original
@@ -78,7 +78,7 @@
* uid
*/
public QosCallbackTracker(@NonNull final Handler connectivityServiceHandler,
- final ConnectivityService.PerUidCounter networkRequestCounter) {
+ final ConnectivityService.RequestInfoPerUidCounter networkRequestCounter) {
mConnectivityServiceHandler = connectivityServiceHandler;
mNetworkRequestCounter = networkRequestCounter;
}
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/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index edfaf9f..db92f5c 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -21,7 +21,7 @@
android_test_helper_app {
name: "CtsHostsideNetworkTestsApp2",
defaults: ["cts_support_defaults"],
- sdk_version: "test_current",
+ platform_apis: true,
static_libs: [
"androidx.annotation_annotation",
"CtsHostsideNetworkTestsAidl",
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
index 771b404..825f2c9 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
@@ -48,6 +48,7 @@
import android.widget.Toast;
import java.net.HttpURLConnection;
+import java.net.InetAddress;
import java.net.URL;
/**
@@ -182,6 +183,11 @@
checkStatus = false;
checkDetails = "Exception getting " + address + ": " + e;
}
+ // If the app tries to make a network connection in the foreground immediately after
+ // trying to do the same when it's network access was blocked, it could receive a
+ // UnknownHostException due to the cached DNS entry. So, clear the dns cache after
+ // every network access for now until we have a fix on the platform side.
+ InetAddress.clearDnsCache();
Log.d(TAG, checkDetails);
final String state, detailedState;
if (networkInfo != null) {
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/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 6ff2458..8dbcc00 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -26,6 +26,7 @@
import static android.Manifest.permission.NETWORK_SETUP_WIZARD;
import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
import static android.content.pm.PackageManager.FEATURE_ETHERNET;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
@@ -164,7 +165,6 @@
import android.os.Process;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.VintfRuntimeInfo;
import android.platform.test.annotations.AppModeFull;
@@ -352,7 +352,8 @@
// Get com.android.internal.R.array.networkAttributes
int resId = mContext.getResources().getIdentifier("networkAttributes", "array", "android");
String[] naStrings = mContext.getResources().getStringArray(resId);
- boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
+ boolean wifiOnly = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+ && !mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
for (String naString : naStrings) {
try {
final String[] splitConfig = naString.split(",");
@@ -2488,15 +2489,24 @@
ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext);
final int curPrivateDnsMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
- TestTetheringEventCallback tetherEventCallback = null;
final CtsTetheringUtils tetherUtils = new CtsTetheringUtils(mContext);
+ final TestTetheringEventCallback tetherEventCallback =
+ tetherUtils.registerTetheringEventCallback();
try {
- tetherEventCallback = tetherUtils.registerTetheringEventCallback();
- // start tethering
tetherEventCallback.assumeWifiTetheringSupported(mContext);
- tetherUtils.startWifiTethering(tetherEventCallback);
+
+ final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
+ mCtsNetUtils.ensureWifiConnected();
+ registerCallbackAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
// Update setting to verify the behavior.
setAirplaneMode(true);
+ // Verify wifi lost to make sure airplane mode takes effect. This could
+ // prevent the race condition between airplane mode enabled and the followed
+ // up wifi tethering enabled.
+ waitForLost(wifiCb);
+ // start wifi tethering
+ tetherUtils.startWifiTethering(tetherEventCallback);
+
ConnectivitySettingsManager.setPrivateDnsMode(mContext,
ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF);
ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext,
@@ -2504,20 +2514,19 @@
assertEquals(AIRPLANE_MODE_ON, Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON));
// Verify factoryReset
- runAsShell(NETWORK_SETTINGS, () -> mCm.factoryReset());
+ runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () -> {
+ mCm.factoryReset();
+ tetherEventCallback.expectNoTetheringActive();
+ });
verifySettings(AIRPLANE_MODE_OFF,
ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC,
ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT);
-
- tetherEventCallback.expectNoTetheringActive();
} finally {
// Restore settings.
setAirplaneMode(false);
ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext, curAvoidBadWifi);
ConnectivitySettingsManager.setPrivateDnsMode(mContext, curPrivateDnsMode);
- if (tetherEventCallback != null) {
- tetherUtils.unregisterTetheringEventCallback(tetherEventCallback);
- }
+ tetherUtils.unregisterTetheringEventCallback(tetherEventCallback);
tetherUtils.stopAllTethering();
}
}
diff --git a/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt b/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
index 3a739f2..3a36cee 100644
--- a/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
+++ b/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
@@ -21,12 +21,15 @@
import android.provider.DeviceConfig
import android.util.Log
import com.android.modules.utils.build.SdkLevel
-import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
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/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 1f76773..8940075 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -163,8 +163,7 @@
// Only statically configure the IPv4 address; for IPv6, use the SLAAC generated
// address.
- iface = tnm.createTapInterface(true /* disableIpv6ProvisioningDelay */,
- arrayOf(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN)))
+ iface = tnm.createTapInterface(arrayOf(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN)))
assertNotNull(iface)
}
@@ -224,7 +223,7 @@
val onLinkPrefix = raResponder.prefix
val startTime = SystemClock.elapsedRealtime()
while (SystemClock.elapsedRealtime() - startTime < PACKET_TIMEOUT_MS) {
- SystemClock.sleep(1 /* ms */)
+ SystemClock.sleep(50 /* ms */)
val sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
try {
network.bindSocket(sock)
@@ -273,7 +272,6 @@
val lp = LinkProperties().apply {
addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN))
addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
- addRoute(RouteInfo(IpPrefix("::/0"), TEST_ROUTER_IPV6_ADDR))
setInterfaceName(specifier)
}
val config = NetworkAgentConfig.Builder().build()
@@ -318,6 +316,7 @@
fun parseV4PacketDscp(buffer: ByteBuffer): Int {
// Validate checksum before parsing packet.
val calCheck = IpUtils.ipChecksum(buffer, Struct.getSize(EthernetHeader::class.java))
+ assertEquals(0, calCheck, "Invalid IPv4 header checksum")
val ip_ver = buffer.get()
val tos = buffer.get()
@@ -328,7 +327,11 @@
val ipType = buffer.get()
val checksum = buffer.getShort()
- assertEquals(0, calCheck, "Invalid IPv4 header checksum")
+ if (ipType.toInt() == 2 /* IPPROTO_IGMP */ && ip_ver.toInt() == 0x46) {
+ // Need to ignore 'igmp v3 report' with 'router alert' option
+ } else {
+ assertEquals(0x45, ip_ver.toInt(), "Invalid IPv4 version or IPv4 options present")
+ }
return tos.toInt().shr(2)
}
@@ -339,6 +342,9 @@
val length = buffer.getShort()
val proto = buffer.get()
val hop = buffer.get()
+
+ assertEquals(6, ip_ver.toInt().shr(4), "Invalid IPv6 version")
+
// DSCP is bottom 4 bits of ip_ver and top 2 of tc.
val ip_ver_bottom = ip_ver.toInt().and(0xf)
val tc_dscp = tc.toInt().shr(6)
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index ce8584f..b21c5b4 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -77,6 +77,7 @@
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import java.net.Inet6Address
@@ -95,12 +96,11 @@
import kotlin.test.fail
private const val TAG = "EthernetManagerTest"
-// TODO: try to lower this timeout in the future. Currently, ethernet tests are still flaky because
-// the interface is not ready fast enough (mostly due to the up / up / down / up issue).
-private const val TIMEOUT_MS = 2000L
+private const val TIMEOUT_MS = 1000L
// Timeout used to confirm no callbacks matching given criteria are received. Must be long enough to
// process all callbacks including ip provisioning when using the updateConfiguration API.
-private const val NO_CALLBACK_TIMEOUT_MS = 500L
+// Note that increasing this timeout increases the test duration.
+private const val NO_CALLBACK_TIMEOUT_MS = 200L
private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
IpConfiguration.ProxySettings.NONE, null, null)
@@ -151,7 +151,9 @@
context.getSystemService(TestNetworkManager::class.java)
}
tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
- tnm.createTapInterface(hasCarrier, false /* bringUp */)
+ // setting RS delay to 0 and disabling DAD speeds up tests.
+ tnm.createTapInterface(hasCarrier, false /* bringUp */,
+ true /* disableIpv6ProvisioningDelay */)
}
val mtu = tapInterface.mtu
packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
@@ -193,9 +195,35 @@
val state: Int,
val role: Int,
val configuration: IpConfiguration?
- ) : CallbackEntry()
+ ) : CallbackEntry() {
+ override fun toString(): String {
+ val stateString = when (state) {
+ STATE_ABSENT -> "STATE_ABSENT"
+ STATE_LINK_UP -> "STATE_LINK_UP"
+ STATE_LINK_DOWN -> "STATE_LINK_DOWN"
+ else -> state.toString()
+ }
+ val roleString = when (role) {
+ ROLE_NONE -> "ROLE_NONE"
+ ROLE_CLIENT -> "ROLE_CLIENT"
+ ROLE_SERVER -> "ROLE_SERVER"
+ else -> role.toString()
+ }
+ return ("InterfaceStateChanged(iface=$iface, state=$stateString, " +
+ "role=$roleString, ipConfig=$configuration)")
+ }
+ }
- data class EthernetStateChanged(val state: Int) : CallbackEntry()
+ data class EthernetStateChanged(val state: Int) : CallbackEntry() {
+ override fun toString(): String {
+ val stateString = when (state) {
+ ETHERNET_STATE_ENABLED -> "ETHERNET_STATE_ENABLED"
+ ETHERNET_STATE_DISABLED -> "ETHERNET_STATE_DISABLED"
+ else -> state.toString()
+ }
+ return "EthernetStateChanged(state=$stateString)"
+ }
+ }
}
override fun onInterfaceStateChanged(
@@ -236,11 +264,13 @@
fun eventuallyExpect(expected: CallbackEntry) = events.poll(TIMEOUT_MS) { it == expected }
fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
- assertNotNull(eventuallyExpect(createChangeEvent(iface.name, state, role)))
+ val event = createChangeEvent(iface.name, state, role)
+ assertNotNull(eventuallyExpect(event), "Never received expected $event")
}
fun eventuallyExpect(state: Int) {
- assertNotNull(eventuallyExpect(EthernetStateChanged(state)))
+ val event = EthernetStateChanged(state)
+ assertNotNull(eventuallyExpect(event), "Never received expected $event")
}
fun assertNoCallback() {
@@ -306,14 +336,24 @@
}
}
+ private fun isEthernetSupported() = em != null
+
@Before
fun setUp() {
+ assumeTrue(isEthernetSupported())
setIncludeTestInterfaces(true)
addInterfaceStateListener(ifaceListener)
+ // Handler.post() events may get processed after native fd events, so it is possible that
+ // RTM_NEWLINK (from a subsequent createInterface() call) arrives before the interface state
+ // listener is registered. This affects the callbacks and breaks the tests.
+ // setEthernetEnabled() will always wait on a callback, so it is used as a barrier to ensure
+ // proper listener registration before proceeding.
+ setEthernetEnabled(true)
}
@After
fun tearDown() {
+ if (!isEthernetSupported()) return
// Reenable ethernet, so ABSENT callbacks are received.
setEthernetEnabled(true)
@@ -496,10 +536,7 @@
validateListenerOnRegistration(listener1)
// If an interface appears, existing callbacks see it.
- // TODO: fix the up/up/down/up callbacks and only send down/up.
val iface2 = createInterface()
- listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
- listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
listener1.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener1.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
@@ -778,7 +815,7 @@
.Builder(ETH_REQUEST.networkCapabilities)
.addCapability(testCapability)
.build()
- updateConfiguration(iface, STATIC_IP_CONFIGURATION, nc)
+ updateConfiguration(iface, STATIC_IP_CONFIGURATION, nc).expectResult(iface.name)
// UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
// will be expected first before available, as part of the restart.
@@ -796,7 +833,7 @@
val network = cb.expectAvailable()
cb.assertNeverLost()
- updateConfiguration(iface, STATIC_IP_CONFIGURATION)
+ updateConfiguration(iface, STATIC_IP_CONFIGURATION).expectResult(iface.name)
// UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
// will be expected first before available, as part of the restart.
@@ -807,6 +844,9 @@
STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
}
+ // TODO(b/240323229): This test is currently flaky due to a race between IpClient restarting and
+ // NetworkAgent tearing down the routes. This problem is exacerbated by disabling RS delay.
+ @Ignore
@Test
fun testUpdateConfiguration_forOnlyCapabilities() {
val iface: EthernetTestInterface = createInterface()
@@ -819,7 +859,7 @@
.Builder(ETH_REQUEST.networkCapabilities)
.addCapability(testCapability)
.build()
- updateConfiguration(iface, capabilities = nc)
+ updateConfiguration(iface, capabilities = nc).expectResult(iface.name)
// UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
// will be expected first before available, as part of the restart.
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/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
index d8e39b4..0377160 100644
--- a/tests/cts/net/src/android/net/cts/TunUtils.java
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -27,12 +27,13 @@
import android.os.ParcelFileDescriptor;
+import com.android.net.module.util.CollectionUtils;
+
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
@@ -170,7 +171,7 @@
*/
private static boolean isEspFailIfSpecifiedPlaintextFound(
byte[] pkt, int spi, boolean encap, byte[] plaintext) {
- if (Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext)) != -1) {
+ if (CollectionUtils.indexOfSubArray(pkt, plaintext) != -1) {
fail("Banned plaintext packet found");
}
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/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml
index 2e13689..50f02d3 100644
--- a/tests/integration/AndroidManifest.xml
+++ b/tests/integration/AndroidManifest.xml
@@ -60,7 +60,7 @@
<action android:name=".INetworkStackInstrumentation"/>
</intent-filter>
</service>
- <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
+ <service android:name="com.android.networkstack.ipmemorystore.RegularMaintenanceJobService"
android:process="com.android.server.net.integrationtests.testnetworkstack"
android:permission="android.permission.BIND_JOB_SERVICE"/>
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 97688d5..28edcb2 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -269,6 +269,7 @@
mNetworkAgent.sendNetworkScore(score);
}
+ // TODO : remove adjustScore and replace with the appropriate exiting flags.
public void adjustScore(int change) {
final int newLegacyScore = mScore.getLegacyInt() + change;
final NetworkScore.Builder builder = new NetworkScore.Builder()
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/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index e3dbb14..8a4932b 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -38,7 +38,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.ExceptionUtils;
+import com.android.testutils.FunctionalUtils.ThrowingConsumer;
import org.junit.Before;
import org.junit.Rule;
@@ -396,7 +396,7 @@
}
}
- int getRequestKey(ExceptionUtils.ThrowingConsumer<ArgumentCaptor<Integer>> verifier)
+ int getRequestKey(ThrowingConsumer<ArgumentCaptor<Integer>> verifier)
throws Exception {
final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
verifier.accept(captor);
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 2d09bf2..eb5d2ef 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -27,6 +27,11 @@
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.INetd.PERMISSION_INTERNET;
+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;
import static com.android.server.BpfNetMaps.HAPPY_BOX_MATCH;
@@ -36,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;
@@ -43,19 +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;
@@ -69,6 +83,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
@RunWith(DevSdkIgnoreRunner.class)
@@ -88,6 +103,7 @@
private static final int NULL_IIF = 0;
private static final String CHAINNAME = "fw_dozable";
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 List<Integer> FIREWALL_CHAINS = List.of(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
@@ -99,22 +115,31 @@
FIREWALL_CHAIN_OEM_DENY_3
);
+ private static final long STATS_SELECT_MAP_A = 0;
+ private static final long STATS_SELECT_MAP_B = 1;
+
private BpfNetMaps mBpfNetMaps;
@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 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 {
MockitoAnnotations.initMocks(this);
doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+ doReturn(0).when(mDeps).synchronizeKernelRCU();
BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */);
BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
+ BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
+ BpfNetMaps.setCookieTagMapForTest(mCookieTagMap);
mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps);
}
@@ -728,4 +753,177 @@
() -> mBpfNetMaps.replaceUidChain(FIREWALL_CHAIN_DOZABLE, TEST_UIDS));
}
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetNetPermForUidsGrantInternetPermission() throws Exception {
+ mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+
+ assertTrue(mUidPermissionMap.isEmpty());
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetNetPermForUidsGrantUpdateStatsPermission() throws Exception {
+ mBpfNetMaps.setNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS, TEST_UIDS);
+
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+ assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new U32(uid1)).val);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetNetPermForUidsGrantMultiplePermissions() throws Exception {
+ final int permission = PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS;
+ mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+ assertEquals(permission, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetNetPermForUidsRevokeInternetPermission() throws Exception {
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+ mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+ mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, new int[]{uid0});
+
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertNull(mUidPermissionMap.getValue(new U32(uid1)));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetNetPermForUidsRevokeUpdateDeviceStatsPermission() throws Exception {
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+ mBpfNetMaps.setNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS, TEST_UIDS);
+ mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, new int[]{uid0});
+
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new U32(uid1)).val);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetNetPermForUidsRevokeMultiplePermissions() throws Exception {
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+ final int permission = PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS;
+ mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+ mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, new int[]{uid0});
+
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetNetPermForUidsPermissionUninstalled() throws Exception {
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+ final int permission = PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS;
+ mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+ mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED, new int[]{uid0});
+
+ assertNull(mUidPermissionMap.getValue(new U32(uid0)));
+ assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSetNetPermForUidsDuplicatedRequestSilentlyIgnored() throws Exception {
+ final int uid0 = TEST_UIDS[0];
+ final int uid1 = TEST_UIDS[1];
+ final int permission = PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS;
+
+ mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+ assertEquals(permission, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+
+ mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+ assertEquals(permission, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+
+ mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, TEST_UIDS);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid1)).val);
+
+ mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, TEST_UIDS);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid1)).val);
+
+ mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED, TEST_UIDS);
+ assertNull(mUidPermissionMap.getValue(new U32(uid0)));
+ assertNull(mUidPermissionMap.getValue(new U32(uid1)));
+
+ mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED, TEST_UIDS);
+ assertNull(mUidPermissionMap.getValue(new U32(uid0)));
+ assertNull(mUidPermissionMap.getValue(new U32(uid1)));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSwapActiveStatsMap() throws Exception {
+ mConfigurationMap.updateEntry(
+ CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
+
+ mBpfNetMaps.swapActiveStatsMap();
+ assertEquals(STATS_SELECT_MAP_B,
+ mConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val);
+
+ mBpfNetMaps.swapActiveStatsMap();
+ assertEquals(STATS_SELECT_MAP_A,
+ mConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testSwapActiveStatsMapSynchronizeKernelRCUFail() throws Exception {
+ doReturn(EPERM).when(mDeps).synchronizeKernelRCU();
+ mConfigurationMap.updateEntry(
+ CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
+
+ 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
old mode 100644
new mode 100755
index 4b832dd..5aade99
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -35,7 +35,6 @@
import static android.content.Intent.ACTION_PACKAGE_REPLACED;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
-import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.FEATURE_ETHERNET;
import static android.content.pm.PackageManager.FEATURE_WIFI;
@@ -159,7 +158,7 @@
import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-import static com.android.testutils.ExceptionUtils.ignoreExceptions;
+import static com.android.testutils.FunctionalUtils.ignoreExceptions;
import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
import static com.android.testutils.MiscAsserts.assertContainsAll;
import static com.android.testutils.MiscAsserts.assertContainsExactly;
@@ -366,19 +365,22 @@
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
-import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
+import com.android.server.connectivity.NetworkNotificationManager;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.Vpn;
import com.android.server.connectivity.VpnProfileStore;
+import com.android.server.net.LockdownVpnTracker;
import com.android.server.net.NetworkPinner;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.ExceptionUtils;
+import com.android.testutils.FunctionalUtils.Function3;
+import com.android.testutils.FunctionalUtils.ThrowingConsumer;
+import com.android.testutils.FunctionalUtils.ThrowingRunnable;
import com.android.testutils.HandlerUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestableNetworkCallback;
@@ -520,7 +522,6 @@
private MockContext mServiceContext;
private HandlerThread mCsHandlerThread;
- private HandlerThread mVMSHandlerThread;
private ConnectivityServiceDependencies mDeps;
private ConnectivityService mService;
private WrappedConnectivityManager mCm;
@@ -536,7 +537,6 @@
private TestNetIdManager mNetIdManager;
private QosCallbackMockHelper mQosCallbackMockHelper;
private QosCallbackTracker mQosCallbackTracker;
- private VpnManagerService mVpnManagerService;
private TestNetworkCallback mDefaultNetworkCallback;
private TestNetworkCallback mSystemDefaultNetworkCallback;
private TestNetworkCallback mProfileDefaultNetworkCallback;
@@ -744,7 +744,7 @@
}
private int checkMockedPermission(String permission, int pid, int uid,
- Supplier<Integer> ifAbsent) {
+ Function3<String, Integer, Integer, Integer> ifAbsent /* perm, uid, pid -> int */) {
final Integer granted = mMockedPermissions.get(permission + "," + pid + "," + uid);
if (null != granted) {
return granted;
@@ -753,27 +753,27 @@
if (null != allGranted) {
return allGranted;
}
- return ifAbsent.get();
+ return ifAbsent.apply(permission, pid, uid);
}
@Override
public int checkPermission(String permission, int pid, int uid) {
return checkMockedPermission(permission, pid, uid,
- () -> super.checkPermission(permission, pid, uid));
+ (perm, p, u) -> super.checkPermission(perm, p, u));
}
@Override
public int checkCallingOrSelfPermission(String permission) {
return checkMockedPermission(permission, Process.myPid(), Process.myUid(),
- () -> super.checkCallingOrSelfPermission(permission));
+ (perm, p, u) -> super.checkCallingOrSelfPermission(perm));
}
@Override
public void enforceCallingOrSelfPermission(String permission, String message) {
final Integer granted = checkMockedPermission(permission,
Process.myPid(), Process.myUid(),
- () -> {
- super.enforceCallingOrSelfPermission(permission, message);
+ (perm, p, u) -> {
+ super.enforceCallingOrSelfPermission(perm, message);
// enforce will crash if the permission is not granted
return PERMISSION_GRANTED;
});
@@ -786,7 +786,7 @@
/**
* Mock checks for the specified permission, and have them behave as per {@code granted}.
*
- * This will apply across the board no matter what the checked UID and PID are.
+ * This will apply to all calls no matter what the checked UID and PID are.
*
* <p>Passing null reverts to default behavior, which does a real permission check on the
* test package.
@@ -1584,32 +1584,6 @@
return ranges.stream().map(r -> new UidRangeParcel(r, r)).toArray(UidRangeParcel[]::new);
}
- private VpnManagerService makeVpnManagerService() {
- final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
- public int getCallingUid() {
- return mDeps.getCallingUid();
- }
-
- public HandlerThread makeHandlerThread() {
- return mVMSHandlerThread;
- }
-
- @Override
- public VpnProfileStore getVpnProfileStore() {
- return mVpnProfileStore;
- }
-
- public INetd getNetd() {
- return mMockNetd;
- }
-
- public INetworkManagementService getINetworkManagementService() {
- return mNetworkManagementService;
- }
- };
- return new VpnManagerService(mServiceContext, deps);
- }
-
private void assertVpnTransportInfo(NetworkCapabilities nc, int type) {
assertNotNull(nc);
final TransportInfo ti = nc.getTransportInfo();
@@ -1621,17 +1595,12 @@
private void processBroadcast(Intent intent) {
mServiceContext.sendBroadcast(intent);
- HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS);
waitForIdle();
}
private void mockVpn(int uid) {
- synchronized (mVpnManagerService.mVpns) {
- int userId = UserHandle.getUserId(uid);
- mMockVpn = new MockVpn(userId);
- // Every running user always has a Vpn in the mVpns array, even if no VPN is running.
- mVpnManagerService.mVpns.put(userId, mMockVpn);
- }
+ int userId = UserHandle.getUserId(uid);
+ mMockVpn = new MockVpn(userId);
}
private void mockUidNetworkingBlocked() {
@@ -1718,11 +1687,7 @@
});
}
- private interface ExceptionalRunnable {
- void run() throws Exception;
- }
-
- private void withPermission(String permission, ExceptionalRunnable r) throws Exception {
+ private void withPermission(String permission, ThrowingRunnable r) throws Exception {
try {
mServiceContext.setPermission(permission, PERMISSION_GRANTED);
r.run();
@@ -1731,7 +1696,7 @@
}
}
- private void withPermission(String permission, int pid, int uid, ExceptionalRunnable r)
+ private void withPermission(String permission, int pid, int uid, ThrowingRunnable r)
throws Exception {
try {
mServiceContext.setPermission(permission, pid, uid, PERMISSION_GRANTED);
@@ -1812,7 +1777,6 @@
initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler());
mCsHandlerThread = new HandlerThread("TestConnectivityService");
- mVMSHandlerThread = new HandlerThread("TestVpnManagerService");
mProxyTracker = new ProxyTracker(mServiceContext, mock(Handler.class),
16 /* EVENT_PROXY_HAS_CHANGED */);
@@ -1841,8 +1805,7 @@
mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
mService.systemReadyInternal();
verify(mMockDnsResolver).registerUnsolicitedEventListener(any());
- mVpnManagerService = makeVpnManagerService();
- mVpnManagerService.systemReady();
+
mockVpn(Process.myUid());
mCm.bindProcessToNetwork(null);
mQosCallbackTracker = mock(QosCallbackTracker.class);
@@ -1882,21 +1845,12 @@
class ConnectivityServiceDependencies extends ConnectivityService.Dependencies {
final ConnectivityResources mConnRes;
- @Mock final MockableSystemProperties mSystemProperties;
ConnectivityServiceDependencies(final Context mockResContext) {
- mSystemProperties = mock(MockableSystemProperties.class);
- doReturn(false).when(mSystemProperties).getBoolean("ro.radio.noril", false);
-
mConnRes = new ConnectivityResources(mockResContext);
}
@Override
- public MockableSystemProperties getSystemProperties() {
- return mSystemProperties;
- }
-
- @Override
public HandlerThread makeHandlerThread() {
return mCsHandlerThread;
}
@@ -3058,6 +3012,43 @@
}
@Test
+ public void testNetworkDoesntMatchRequestsUntilConnected() throws Exception {
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ mCm.requestNetwork(wifiRequest, cb);
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ // Updating the score triggers a rematch.
+ mWiFiNetworkAgent.setScore(new NetworkScore.Builder().build());
+ cb.assertNoCallback();
+ mWiFiNetworkAgent.connect(false);
+ cb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ cb.assertNoCallback();
+ mCm.unregisterNetworkCallback(cb);
+ }
+
+ @Test
+ public void testNetworkNotVisibleUntilConnected() throws Exception {
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ mCm.registerNetworkCallback(wifiRequest, cb);
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ final NetworkCapabilities nc = mWiFiNetworkAgent.getNetworkCapabilities();
+ nc.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+ mWiFiNetworkAgent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ cb.assertNoCallback();
+ mWiFiNetworkAgent.connect(false);
+ cb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ final CallbackEntry found = CollectionUtils.findLast(cb.getHistory(),
+ it -> it instanceof CallbackEntry.CapabilitiesChanged);
+ assertTrue(((CallbackEntry.CapabilitiesChanged) found).getCaps()
+ .hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+ cb.assertNoCallback();
+ mCm.unregisterNetworkCallback(cb);
+ }
+
+ @Test
public void testStateChangeNetworkCallbacks() throws Exception {
final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
@@ -3533,6 +3524,54 @@
mCm.unregisterNetworkCallback(callback);
}
+ /** Expects the specified notification and returns the notification ID. */
+ private int expectNotification(TestNetworkAgentWrapper agent, NotificationType type) {
+ verify(mNotificationManager).notify(
+ eq(NetworkNotificationManager.tagFor(agent.getNetwork().netId)),
+ eq(type.eventId), any());
+ return type.eventId;
+ }
+
+ /**
+ * Expects the specified notification happens when the unvalidated prompt message arrives
+ *
+ * @return the notification ID.
+ **/
+ private int expectUnvalidationCheckWillNotify(TestNetworkAgentWrapper agent,
+ NotificationType type) {
+ mService.scheduleUnvalidatedPrompt(agent.getNetwork(), 0 /* delayMs */);
+ waitForIdle();
+ return expectNotification(agent, type);
+ }
+
+ /**
+ * Expects that the notification for the specified network is cleared.
+ *
+ * This generally happens when the network disconnects or when the newtwork validates. During
+ * normal usage the notification is also cleared by the system when the notification is tapped.
+ */
+ private void expectClearNotification(TestNetworkAgentWrapper agent, int expectedId) {
+ verify(mNotificationManager).cancel(
+ eq(NetworkNotificationManager.tagFor(agent.getNetwork().netId)), eq(expectedId));
+ }
+
+ /**
+ * Expects that no notification happens when the unvalidated prompt message arrives
+ *
+ * @return the notification ID.
+ **/
+ private void expectUnvalidationCheckWillNotNotify(TestNetworkAgentWrapper agent) {
+ mService.scheduleUnvalidatedPrompt(agent.getNetwork(), 0 /*delayMs */);
+ waitForIdle();
+ verify(mNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(), any());
+ }
+
+ private void expectDisconnectAndClearNotifications(TestNetworkCallback callback,
+ TestNetworkAgentWrapper agent, int id) {
+ callback.expectCallback(CallbackEntry.LOST, agent);
+ expectClearNotification(agent, id);
+ }
+
private NativeNetworkConfig nativeNetworkConfigPhysical(int netId, int permission) {
return new NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
/*secure=*/ false, VpnManager.TYPE_VPN_NONE, /*excludeLocalRoutes=*/ false);
@@ -3653,10 +3692,13 @@
mWiFiNetworkAgent.connect(false);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
- // Cell Remains the default.
+ // Cell remains the default.
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
- // Lower wifi's score to below than cell, and check that it doesn't disconnect because
+ // Expect a high-priority NO_INTERNET notification.
+ expectUnvalidationCheckWillNotify(mWiFiNetworkAgent, NotificationType.NO_INTERNET);
+
+ // Lower WiFi's score to lower than cell, and check that it doesn't disconnect because
// it's explicitly selected.
mWiFiNetworkAgent.adjustScore(-40);
mWiFiNetworkAgent.adjustScore(40);
@@ -3670,18 +3712,26 @@
// Disconnect wifi, and then reconnect, again with explicitlySelected=true.
mWiFiNetworkAgent.disconnect();
- callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ expectDisconnectAndClearNotifications(callback, mWiFiNetworkAgent,
+ NotificationType.NO_INTERNET.eventId);
+
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.explicitlySelected(true, false);
mWiFiNetworkAgent.connect(false);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ // Expect a high-priority NO_INTERNET notification.
+ expectUnvalidationCheckWillNotify(mWiFiNetworkAgent, NotificationType.NO_INTERNET);
+
// If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
// network to disconnect.
mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
- callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ expectDisconnectAndClearNotifications(callback, mWiFiNetworkAgent,
+ NotificationType.NO_INTERNET.eventId);
+ reset(mNotificationManager);
// Reconnect, again with explicitlySelected=true, but this time validate.
+ // Expect no notifications.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.explicitlySelected(true, false);
mWiFiNetworkAgent.connect(true);
@@ -3689,6 +3739,7 @@
callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
mEthernetNetworkAgent.connect(true);
@@ -3711,16 +3762,19 @@
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mEthernetNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent);
+ expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
// Disconnect and reconnect with explicitlySelected=false and acceptUnvalidated=true.
// Check that the network is not scored specially and that the device prefers cell data.
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.explicitlySelected(false, true);
mWiFiNetworkAgent.connect(false);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
// Clean up.
mWiFiNetworkAgent.disconnect();
@@ -4088,6 +4142,12 @@
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
callback.assertNoCallback();
+ // Expect a PARTIAL_CONNECTIVITY notification. The notification appears as soon as partial
+ // connectivity is detected, and is low priority because the network was not explicitly
+ // selected by the user. This happens if we reconnect to a network where the user previously
+ // accepted partial connectivity without checking "always".
+ expectNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY);
+
// With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
// probe.
mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
@@ -4100,7 +4160,7 @@
waitForIdle();
verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
- // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
+ // Need a trigger point to let NetworkMonitor tell ConnectivityService that the network is
// validated.
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
@@ -4109,9 +4169,13 @@
assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ // Once the network validates, the notification disappears.
+ expectClearNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY.eventId);
+
// Disconnect and reconnect wifi with partial connectivity again.
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.connectWithPartialConnectivity();
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -4119,20 +4183,28 @@
// Mobile data should be the default network.
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ waitForIdle();
+
+ // Expect a low-priority PARTIAL_CONNECTIVITY notification as soon as partial connectivity
+ // is detected.
+ expectNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY);
// If the user chooses no, disconnect wifi immediately.
- mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */,
+ mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false /* accept */,
false /* always */);
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ expectClearNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY.eventId);
+ reset(mNotificationManager);
- // If user accepted partial connectivity before, and device reconnects to that network
- // again, but now the network has full connectivity. The network shouldn't contain
+ // If the user accepted partial connectivity before, and the device connects to that network
+ // again, but now the network has full connectivity, then the network shouldn't contain
// NET_CAPABILITY_PARTIAL_CONNECTIVITY.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
// acceptUnvalidated is also used as setting for accepting partial networks.
mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */,
true /* acceptUnvalidated */);
mWiFiNetworkAgent.connect(true);
+ expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
// If user accepted partial connectivity network before,
// NetworkMonitor#setAcceptPartialConnectivity() will be called in
@@ -4163,9 +4235,11 @@
callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
+ expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
+
mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
- // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
+ // Need a trigger point to let NetworkMonitor tell ConnectivityService that the network is
// validated.
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
@@ -4187,8 +4261,10 @@
callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(
NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ verifyNoMoreInteractions(mNotificationManager);
}
@Test
@@ -6200,7 +6276,7 @@
}
// Helper method to prepare the executor and run test
- private void runTestWithSerialExecutors(ExceptionUtils.ThrowingConsumer<Executor> functor)
+ private void runTestWithSerialExecutors(ThrowingConsumer<Executor> functor)
throws Exception {
final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor();
final Executor executorInline = (Runnable r) -> r.run();
@@ -8465,12 +8541,8 @@
doReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)).when(mPackageManager)
.getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER);
- final Intent addedIntent = new Intent(ACTION_USER_ADDED);
- addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
-
- // Send a USER_ADDED broadcast for it.
- processBroadcast(addedIntent);
+ // New user added
+ mMockVpn.onUserAdded(RESTRICTED_USER);
// Expect that the VPN UID ranges contain both |uid| and the UID range for the newly-added
// restricted user.
@@ -8494,11 +8566,8 @@
&& caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_WIFI));
- // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user.
- final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
- removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
- removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
- processBroadcast(removedIntent);
+ // User removed and expect to lose the UID range for the restricted user.
+ mMockVpn.onUserRemoved(RESTRICTED_USER);
// Expect that the VPN gains the UID range for the restricted user, and that the capability
// change made just before that (i.e., loss of TRANSPORT_WIFI) is preserved.
@@ -8551,6 +8620,7 @@
doReturn(asList(PRIMARY_USER_INFO, RESTRICTED_USER_INFO)).when(mUserManager)
.getAliveUsers();
// TODO: check that VPN app within restricted profile still has access, etc.
+ mMockVpn.onUserAdded(RESTRICTED_USER);
final Intent addedIntent = new Intent(ACTION_USER_ADDED);
addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
@@ -8562,6 +8632,7 @@
doReturn(asList(PRIMARY_USER_INFO)).when(mUserManager).getAliveUsers();
// Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user.
+ mMockVpn.onUserRemoved(RESTRICTED_USER);
final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER));
removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
@@ -9257,7 +9328,7 @@
doAsUid(Process.SYSTEM_UID, () -> mCm.unregisterNetworkCallback(perUidCb));
}
- private void setupLegacyLockdownVpn() {
+ private VpnProfile setupLegacyLockdownVpn() {
final String profileName = "testVpnProfile";
final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8);
doReturn(profileTag).when(mVpnProfileStore).get(Credentials.LOCKDOWN_VPN);
@@ -9269,6 +9340,8 @@
profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK;
final byte[] encodedProfile = profile.encode();
doReturn(encodedProfile).when(mVpnProfileStore).get(Credentials.VPN + profileName);
+
+ return profile;
}
private void establishLegacyLockdownVpn(Network underlying) throws Exception {
@@ -9301,21 +9374,28 @@
new Handler(ConnectivityThread.getInstanceLooper()));
// Pretend lockdown VPN was configured.
- setupLegacyLockdownVpn();
+ final VpnProfile profile = setupLegacyLockdownVpn();
// LockdownVpnTracker disables the Vpn teardown code and enables lockdown.
// Check the VPN's state before it does so.
assertTrue(mMockVpn.getEnableTeardown());
assertFalse(mMockVpn.getLockdown());
- // Send a USER_UNLOCKED broadcast so CS starts LockdownVpnTracker.
- final int userId = UserHandle.getUserId(Process.myUid());
- final Intent addedIntent = new Intent(ACTION_USER_UNLOCKED);
- addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- processBroadcast(addedIntent);
+ // VMSHandlerThread was used inside VpnManagerService and taken into LockDownVpnTracker.
+ // VpnManagerService was decoupled from this test but this handlerThread is still required
+ // in LockDownVpnTracker. Keep it until LockDownVpnTracker related verification is moved to
+ // its own test.
+ final HandlerThread VMSHandlerThread = new HandlerThread("TestVpnManagerService");
+ VMSHandlerThread.start();
+ // LockdownVpnTracker is created from VpnManagerService but VpnManagerService is decoupled
+ // from ConnectivityServiceTest. Create it directly to simulate LockdownVpnTracker is
+ // created.
+ // TODO: move LockdownVpnTracker related tests to its own test.
// Lockdown VPN disables teardown and enables lockdown.
+ final LockdownVpnTracker lockdownVpnTracker = new LockdownVpnTracker(mServiceContext,
+ VMSHandlerThread.getThreadHandler(), mMockVpn, profile);
+ lockdownVpnTracker.init();
assertFalse(mMockVpn.getEnableTeardown());
assertTrue(mMockVpn.getLockdown());
@@ -9485,6 +9565,8 @@
mMockVpn.expectStopVpnRunnerPrivileged();
callback.expectCallback(CallbackEntry.LOST, mMockVpn);
b2.expectBroadcast();
+
+ VMSHandlerThread.quitSafely();
}
@Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -15588,11 +15670,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)
@@ -15600,8 +15690,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
@@ -15615,7 +15731,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)
@@ -15624,13 +15740,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<>();
@@ -15641,34 +15752,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.
@@ -15677,18 +15782,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()
@@ -15752,7 +15857,7 @@
final UserHandle testHandle = setupEnterpriseNetwork();
final TestOnCompleteListener listener = new TestOnCompleteListener();
// Leave one request available so the profile preference can be set.
- testRequestCountLimits(1 /* countToLeaveAvailable */, () -> {
+ withRequestCountersAcquired(1 /* countToLeaveAvailable */, () -> {
withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
Process.myPid(), Process.myUid(), () -> {
// Set initially to test the limit prior to having existing requests.
@@ -15766,7 +15871,7 @@
final int otherAppUid = UserHandle.getUid(TEST_WORK_PROFILE_USER_ID,
UserHandle.getAppId(Process.myUid() + 1));
final int remainingCount = ConnectivityService.MAX_NETWORK_REQUESTS_PER_UID
- - mService.mNetworkRequestCounter.mUidToNetworkRequestCount.get(otherAppUid)
+ - mService.mNetworkRequestCounter.get(otherAppUid)
- 1;
final NetworkCallback[] callbacks = new NetworkCallback[remainingCount];
doAsUid(otherAppUid, () -> {
@@ -15801,7 +15906,7 @@
@OemNetworkPreferences.OemNetworkPreference final int networkPref =
OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
// Leave one request available so the OEM preference can be set.
- testRequestCountLimits(1 /* countToLeaveAvailable */, () ->
+ withRequestCountersAcquired(1 /* countToLeaveAvailable */, () ->
withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
// Set initially to test the limit prior to having existing requests.
final TestOemListenerCallback listener = new TestOemListenerCallback();
@@ -15816,12 +15921,11 @@
}));
}
- private void testRequestCountLimits(final int countToLeaveAvailable,
- @NonNull final ExceptionalRunnable r) throws Exception {
+ private void withRequestCountersAcquired(final int countToLeaveAvailable,
+ @NonNull final ThrowingRunnable r) throws Exception {
final ArraySet<TestNetworkCallback> callbacks = new ArraySet<>();
try {
- final int requestCount = mService.mSystemNetworkRequestCounter
- .mUidToNetworkRequestCount.get(Process.myUid());
+ final int requestCount = mService.mSystemNetworkRequestCounter.get(Process.myUid());
// The limit is hit when total requests = limit - 1, and exceeded with a crash when
// total requests >= limit.
final int countToFile =
@@ -15834,8 +15938,7 @@
callbacks.add(cb);
}
assertEquals(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1 - countToLeaveAvailable,
- mService.mSystemNetworkRequestCounter
- .mUidToNetworkRequestCount.get(Process.myUid()));
+ mService.mSystemNetworkRequestCounter.get(Process.myUid()));
});
// Code to run to check if it triggers a max request count limit error.
r.run();
@@ -16084,7 +16187,7 @@
ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext,
Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)));
// Leave one request available so MDO preference set up above can be set.
- testRequestCountLimits(1 /* countToLeaveAvailable */, () ->
+ withRequestCountersAcquired(1 /* countToLeaveAvailable */, () ->
withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
Process.myPid(), Process.myUid(), () -> {
// Set initially to test the limit prior to having existing requests.
@@ -16448,6 +16551,26 @@
}
@Test
+ public void testOfferNetwork_ChecksArgumentsOutsideOfHandler() throws Exception {
+ final TestableNetworkOfferCallback callback = new TestableNetworkOfferCallback(
+ TIMEOUT_MS /* timeout */, TEST_CALLBACK_TIMEOUT_MS /* noCallbackTimeout */);
+ final NetworkProvider testProvider = new NetworkProvider(mServiceContext,
+ mCsHandlerThread.getLooper(), "Test provider");
+ final NetworkCapabilities caps = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build();
+
+ final NetworkScore score = new NetworkScore.Builder().build();
+ testProvider.registerNetworkOffer(score, caps, r -> r.run(), callback);
+ testProvider.unregisterNetworkOffer(callback);
+
+ assertThrows(NullPointerException.class,
+ () -> mService.offerNetwork(100, score, caps, null));
+ assertThrows(NullPointerException.class, () -> mService.unofferNetwork(null));
+ }
+
+ @Test
public void testIgnoreValidationAfterRoamDisabled() throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
// testIgnoreValidationAfterRoam off
diff --git a/tests/unit/java/com/android/server/NetIdManagerTest.kt b/tests/unit/java/com/android/server/NetIdManagerTest.kt
index 811134e..f2b14a1 100644
--- a/tests/unit/java/com/android/server/NetIdManagerTest.kt
+++ b/tests/unit/java/com/android/server/NetIdManagerTest.kt
@@ -21,7 +21,7 @@
import com.android.server.NetIdManager.MIN_NET_ID
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
import com.android.testutils.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 1813393..5808beb 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -536,6 +536,25 @@
.onResolveFailed(any(), eq(FAILURE_INTERNAL_ERROR));
}
+ @Test
+ public void testNoCrashWhenProcessResolutionAfterBinderDied() throws Exception {
+ final NsdManager client = connectClient(mService);
+ final INsdManagerCallback cb = getCallback();
+ final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb);
+ deathRecipient.binderDied();
+
+ final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+ final ResolveListener resolveListener = mock(ResolveListener.class);
+ client.resolveService(request, resolveListener);
+ waitForIdle();
+
+ verify(mMockMDnsM, never()).registerEventListener(any());
+ verify(mMockMDnsM, never()).startDaemon();
+ verify(mMockMDnsM, never()).resolve(anyInt() /* id */, anyString() /* serviceName */,
+ anyString() /* registrationType */, anyString() /* domain */,
+ anyInt()/* interfaceIdx */);
+ }
+
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
}
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index bbb61cd..7646c19 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -94,10 +94,10 @@
private static final int GOOGLE_DNS_4 = 0x08080808; // 8.8.8.8
private static final int NETID = 42;
- // The test fwmark means: PERMISSION_SYSTEM (0x2), protectedFromVpn: true,
+ // The test fwmark means: PERMISSION_NETWORK | PERMISSION_SYSTEM (0x3), protectedFromVpn: true,
// explicitlySelected: true, netid: 42. For bit field structure definition, see union Fwmark in
// system/netd/include/Fwmark.h
- private static final int MARK = 0xb002a;
+ private static final int MARK = 0xf002a;
private static final String XLAT_LOCAL_IPV4ADDR_STRING = "192.0.0.46";
private static final String XLAT_LOCAL_IPV6ADDR_STRING = "2001:db8:0:b11::464";
@@ -493,10 +493,10 @@
@Test
public void testGetFwmark() throws Exception {
- assertEquals(0xb0064, ClatCoordinator.getFwmark(100));
- assertEquals(0xb03e8, ClatCoordinator.getFwmark(1000));
- assertEquals(0xb2710, ClatCoordinator.getFwmark(10000));
- assertEquals(0xbffff, ClatCoordinator.getFwmark(65535));
+ assertEquals(0xf0064, ClatCoordinator.getFwmark(100));
+ assertEquals(0xf03e8, ClatCoordinator.getFwmark(1000));
+ assertEquals(0xf2710, ClatCoordinator.getFwmark(10000));
+ assertEquals(0xfffff, ClatCoordinator.getFwmark(65535));
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index a194131..b39e960 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -29,6 +29,7 @@
import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY
import com.android.server.connectivity.FullScore.MIN_CS_MANAGED_POLICY
import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED
+import com.android.server.connectivity.FullScore.POLICY_EVER_EVALUATED
import com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED
import com.android.server.connectivity.FullScore.POLICY_IS_DESTROYED
import com.android.server.connectivity.FullScore.POLICY_IS_UNMETERED
@@ -56,6 +57,7 @@
vpn: Boolean = false,
onceChosen: Boolean = false,
acceptUnvalidated: Boolean = false,
+ everEvaluated: Boolean = true,
destroyed: Boolean = false
): FullScore {
val nac = NetworkAgentConfig.Builder().apply {
@@ -66,7 +68,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 */, everEvaluated, destroyed)
}
private val TAG = this::class.simpleName
@@ -122,6 +125,7 @@
assertTrue(ns.withPolicies(onceChosen = true).hasPolicy(POLICY_EVER_USER_SELECTED))
assertTrue(ns.withPolicies(acceptUnvalidated = true).hasPolicy(POLICY_ACCEPT_UNVALIDATED))
assertTrue(ns.withPolicies(destroyed = true).hasPolicy(POLICY_IS_DESTROYED))
+ assertTrue(ns.withPolicies(everEvaluated = true).hasPolicy(POLICY_EVER_EVALUATED))
}
@Test
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/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index caef54a..795eb47 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -92,6 +92,7 @@
import android.net.LinkProperties;
import android.net.LocalSocket;
import android.net.Network;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo.DetailedState;
import android.net.RouteInfo;
@@ -264,6 +265,7 @@
final Ikev2VpnProfile.Builder builder =
new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
builder.setAuthPsk(TEST_VPN_PSK);
+ builder.setBypassable(true /* isBypassable */);
mVpnProfile = builder.build().toVpnProfile();
}
@@ -968,31 +970,6 @@
AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, AppOpsManager.OPSTR_ACTIVATE_VPN);
}
- private void setAppOpsPermission() {
- doAnswer(invocation -> {
- when(mAppOps.noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN,
- Process.myUid(), TEST_VPN_PKG,
- null /* attributionTag */, null /* message */))
- .thenReturn(AppOpsManager.MODE_ALLOWED);
- return null;
- }).when(mAppOps).setMode(
- eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(AppOpsManager.MODE_ALLOWED));
- }
-
- @Test
- public void testProvisionVpnProfileNotPreconsented_withControlVpnPermission() throws Exception {
- setAppOpsPermission();
- doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
- final Vpn vpn = createVpnAndSetupUidChecks();
-
- // ACTIVATE_PLATFORM_VPN will be granted if VPN app has CONTROL_VPN permission.
- checkProvisionVpnProfile(vpn, true /* expectedResult */,
- AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
- }
-
@Test
public void testProvisionVpnProfileVpnServicePreconsented() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
@@ -1787,9 +1764,11 @@
ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
+ ArgumentCaptor<NetworkAgentConfig> nacCaptor =
+ ArgumentCaptor.forClass(NetworkAgentConfig.class);
verify(mTestDeps).newNetworkAgent(
any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
- any(), any(), any());
+ any(), nacCaptor.capture(), any());
// Check LinkProperties
final LinkProperties lp = lpCaptor.getValue();
@@ -1811,6 +1790,9 @@
// Check NetworkCapabilities
assertEquals(Arrays.asList(TEST_NETWORK), ncCaptor.getValue().getUnderlyingNetworks());
+ // Check if allowBypass is set or not.
+ assertTrue(nacCaptor.getValue().isBypassableVpn());
+
return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb);
}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index 503d920..aad80d5 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -38,9 +37,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.net.ConnectivityManager;
-import android.net.EthernetNetworkManagementException;
import android.net.EthernetNetworkSpecifier;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IpConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -55,7 +52,6 @@
import android.net.ip.IpClientManager;
import android.os.Build;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.test.TestLooper;
@@ -74,9 +70,6 @@
import org.mockito.MockitoAnnotations;
import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
@@ -84,7 +77,6 @@
public class EthernetNetworkFactoryTest {
private static final int TIMEOUT_MS = 2_000;
private static final String TEST_IFACE = "test123";
- private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
private static final String IP_ADDR = "192.0.2.2/25";
private static final LinkAddress LINK_ADDR = new LinkAddress(IP_ADDR);
private static final String HW_ADDR = "01:02:03:04:05:06";
@@ -241,7 +233,7 @@
final IpConfiguration ipConfig = createDefaultIpConfig();
mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
createInterfaceCapsBuilder(transportType).build());
- assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
+ assertTrue(mNetFactory.updateInterfaceLinkState(iface, true));
ArgumentCaptor<NetworkOfferCallback> captor = ArgumentCaptor.forClass(
NetworkOfferCallback.class);
@@ -295,7 +287,7 @@
// then calling onNetworkUnwanted.
mNetFactory.addInterface(iface, HW_ADDR, createDefaultIpConfig(),
createInterfaceCapsBuilder(NetworkCapabilities.TRANSPORT_ETHERNET).build());
- assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
+ assertTrue(mNetFactory.updateInterfaceLinkState(iface, true));
clearInvocations(mIpClient);
clearInvocations(mNetworkAgent);
@@ -305,81 +297,63 @@
public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception {
initEthernetNetworkFactory();
createInterfaceUndergoingProvisioning(TEST_IFACE);
- final TestNetworkManagementListener listener = new TestNetworkManagementListener();
// verify that the IpClient gets shut down when interface state changes to down.
- final boolean ret =
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
+ final boolean ret = mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */);
assertTrue(ret);
verify(mIpClient).shutdown();
- assertEquals(TEST_IFACE, listener.expectOnResult());
}
@Test
public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception {
initEthernetNetworkFactory();
createAndVerifyProvisionedInterface(TEST_IFACE);
- final TestNetworkManagementListener listenerDown = new TestNetworkManagementListener();
- final TestNetworkManagementListener listenerUp = new TestNetworkManagementListener();
- final boolean retDown =
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listenerDown);
+ final boolean retDown = mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */);
assertTrue(retDown);
verifyStop();
- assertEquals(TEST_IFACE, listenerDown.expectOnResult());
- final boolean retUp =
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listenerUp);
+ final boolean retUp = mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */);
assertTrue(retUp);
- assertEquals(TEST_IFACE, listenerUp.expectOnResult());
}
@Test
public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception {
initEthernetNetworkFactory();
createUnprovisionedInterface(TEST_IFACE);
- final TestNetworkManagementListener listener = new TestNetworkManagementListener();
- final boolean ret =
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
+ final boolean ret = mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */);
assertTrue(ret);
// There should not be an active IPClient or NetworkAgent.
verify(mDeps, never()).makeIpClient(any(), any(), any());
verify(mDeps, never())
.makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
- assertEquals(TEST_IFACE, listener.expectOnResult());
}
@Test
public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception {
initEthernetNetworkFactory();
- final TestNetworkManagementListener listener = new TestNetworkManagementListener();
// if interface was never added, link state cannot be updated.
- final boolean ret =
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
+ final boolean ret = mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */);
assertFalse(ret);
verifyNoStopOrStart();
- listener.expectOnError();
}
@Test
public void testUpdateInterfaceLinkStateWithNoChanges() throws Exception {
initEthernetNetworkFactory();
createAndVerifyProvisionedInterface(TEST_IFACE);
- final TestNetworkManagementListener listener = new TestNetworkManagementListener();
- final boolean ret =
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
+ final boolean ret = mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */);
assertFalse(ret);
verifyNoStopOrStart();
- listener.expectOnError();
}
@Test
@@ -571,127 +545,16 @@
verify(mNetworkAgent).markConnected();
}
- private static final class TestNetworkManagementListener
- implements INetworkInterfaceOutcomeReceiver {
- private final CompletableFuture<String> mResult = new CompletableFuture<>();
-
- @Override
- public void onResult(@NonNull String iface) {
- mResult.complete(iface);
- }
-
- @Override
- public void onError(@NonNull EthernetNetworkManagementException exception) {
- mResult.completeExceptionally(exception);
- }
-
- String expectOnResult() throws Exception {
- return mResult.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- }
-
- void expectOnError() throws Exception {
- assertThrows(EthernetNetworkManagementException.class, () -> {
- try {
- mResult.get();
- } catch (ExecutionException e) {
- throw e.getCause();
- }
- });
- }
-
- @Override
- public IBinder asBinder() {
- return null;
- }
- }
-
- @Test
- public void testUpdateInterfaceCallsListenerCorrectlyOnSuccess() throws Exception {
- initEthernetNetworkFactory();
- createAndVerifyProvisionedInterface(TEST_IFACE);
- final NetworkCapabilities capabilities = createDefaultFilterCaps();
- final IpConfiguration ipConfiguration = createStaticIpConfig();
- final TestNetworkManagementListener listener = new TestNetworkManagementListener();
-
- mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
- triggerOnProvisioningSuccess();
-
- assertEquals(TEST_IFACE, listener.expectOnResult());
- }
-
- @Test
- public void testUpdateInterfaceAbortsOnConcurrentRemoveInterface() throws Exception {
- initEthernetNetworkFactory();
- verifyNetworkManagementCallIsAbortedWhenInterrupted(
- TEST_IFACE,
- () -> mNetFactory.removeInterface(TEST_IFACE));
- }
-
- @Test
- public void testUpdateInterfaceAbortsOnConcurrentUpdateInterfaceLinkState() throws Exception {
- initEthernetNetworkFactory();
- verifyNetworkManagementCallIsAbortedWhenInterrupted(
- TEST_IFACE,
- () -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER));
- }
-
- @Test
- public void testUpdateInterfaceAbortsOnNetworkUneededRemovesAllRequests() throws Exception {
- initEthernetNetworkFactory();
- verifyNetworkManagementCallIsAbortedWhenInterrupted(
- TEST_IFACE,
- () -> mNetworkOfferCallback.onNetworkUnneeded(mRequestToKeepNetworkUp));
- }
-
- @Test
- public void testUpdateInterfaceCallsListenerCorrectlyOnConcurrentRequests() throws Exception {
- initEthernetNetworkFactory();
- final NetworkCapabilities capabilities = createDefaultFilterCaps();
- final IpConfiguration ipConfiguration = createStaticIpConfig();
- final TestNetworkManagementListener successfulListener =
- new TestNetworkManagementListener();
-
- // If two calls come in before the first one completes, the first listener will be aborted
- // and the second one will be successful.
- verifyNetworkManagementCallIsAbortedWhenInterrupted(
- TEST_IFACE,
- () -> {
- mNetFactory.updateInterface(
- TEST_IFACE, ipConfiguration, capabilities, successfulListener);
- triggerOnProvisioningSuccess();
- });
-
- assertEquals(successfulListener.expectOnResult(), TEST_IFACE);
- assertEquals(TEST_IFACE, successfulListener.expectOnResult());
- }
-
- private void verifyNetworkManagementCallIsAbortedWhenInterrupted(
- @NonNull final String iface,
- @NonNull final Runnable interruptingRunnable) throws Exception {
- createAndVerifyProvisionedInterface(iface);
- final NetworkCapabilities capabilities = createDefaultFilterCaps();
- final IpConfiguration ipConfiguration = createStaticIpConfig();
- final TestNetworkManagementListener failedListener = new TestNetworkManagementListener();
-
- // An active update request will be aborted on interrupt prior to provisioning completion.
- mNetFactory.updateInterface(iface, ipConfiguration, capabilities, failedListener);
- interruptingRunnable.run();
-
- failedListener.expectOnError();
- }
-
@Test
public void testUpdateInterfaceRestartsAgentCorrectly() throws Exception {
initEthernetNetworkFactory();
createAndVerifyProvisionedInterface(TEST_IFACE);
final NetworkCapabilities capabilities = createDefaultFilterCaps();
final IpConfiguration ipConfiguration = createStaticIpConfig();
- final TestNetworkManagementListener listener = new TestNetworkManagementListener();
- mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
+ mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities);
triggerOnProvisioningSuccess();
- assertEquals(TEST_IFACE, listener.expectOnResult());
verify(mDeps).makeEthernetNetworkAgent(any(), any(),
eq(capabilities), any(), any(), any(), any());
verifyRestart(ipConfiguration);
@@ -703,12 +566,10 @@
// No interface exists due to not calling createAndVerifyProvisionedInterface(...).
final NetworkCapabilities capabilities = createDefaultFilterCaps();
final IpConfiguration ipConfiguration = createStaticIpConfig();
- final TestNetworkManagementListener listener = new TestNetworkManagementListener();
- mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
+ mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities);
verifyNoStopOrStart();
- listener.expectOnError();
}
@Test
@@ -717,8 +578,8 @@
createAndVerifyProvisionedInterface(TEST_IFACE);
final IpConfiguration initialIpConfig = createStaticIpConfig();
- mNetFactory.updateInterface(TEST_IFACE, initialIpConfig, null /*capabilities*/,
- null /*listener*/);
+ mNetFactory.updateInterface(TEST_IFACE, initialIpConfig, null /*capabilities*/);
+
triggerOnProvisioningSuccess();
verifyRestart(initialIpConfig);
@@ -729,8 +590,7 @@
// verify that sending a null ipConfig does not update the current ipConfig.
- mNetFactory.updateInterface(TEST_IFACE, null /*ipConfig*/, null /*capabilities*/,
- null /*listener*/);
+ mNetFactory.updateInterface(TEST_IFACE, null /*ipConfig*/, null /*capabilities*/);
triggerOnProvisioningSuccess();
verifyRestart(initialIpConfig);
}
@@ -739,7 +599,7 @@
public void testOnNetworkNeededOnStaleNetworkOffer() throws Exception {
initEthernetNetworkFactory();
createAndVerifyProvisionedInterface(TEST_IFACE);
- mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, null);
+ mNetFactory.updateInterfaceLinkState(TEST_IFACE, false);
verify(mNetworkProvider).unregisterNetworkOffer(mNetworkOfferCallback);
// It is possible that even after a network offer is unregistered, CS still sends it
// onNetworkNeeded() callbacks.
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
index a1d93a0..9bf893a 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@@ -209,7 +210,8 @@
NULL_LISTENER);
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getIpConfiguration()),
- eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()), isNull());
+ eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()),
+ any(EthernetCallback.class));
}
private void denyManageEthPermission() {
@@ -285,7 +287,8 @@
verify(mEthernetTracker).updateConfiguration(
eq(TEST_IFACE),
eq(UPDATE_REQUEST.getIpConfiguration()),
- eq(UPDATE_REQUEST.getNetworkCapabilities()), eq(NULL_LISTENER));
+ eq(UPDATE_REQUEST.getNetworkCapabilities()),
+ any(EthernetCallback.class));
}
@Test
@@ -303,19 +306,20 @@
verify(mEthernetTracker).updateConfiguration(
eq(TEST_IFACE),
isNull(),
- eq(ncWithSpecifier), eq(NULL_LISTENER));
+ eq(ncWithSpecifier), any(EthernetCallback.class));
}
@Test
public void testEnableInterface() {
mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE),
+ any(EthernetCallback.class));
}
@Test
public void testDisableInterface() {
mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), any(EthernetCallback.class));
}
@Test
@@ -328,7 +332,7 @@
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER);
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
eq(request.getIpConfiguration()),
- eq(request.getNetworkCapabilities()), isNull());
+ eq(request.getNetworkCapabilities()), any(EthernetCallback.class));
}
@Test
@@ -337,7 +341,8 @@
NULL_LISTENER);
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getIpConfiguration()),
- eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getNetworkCapabilities()), isNull());
+ eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getNetworkCapabilities()),
+ any(EthernetCallback.class));
}
@Test
@@ -369,7 +374,7 @@
verify(mEthernetTracker).updateConfiguration(
eq(TEST_IFACE),
eq(request.getIpConfiguration()),
- eq(request.getNetworkCapabilities()), eq(NULL_LISTENER));
+ eq(request.getNetworkCapabilities()), any(EthernetCallback.class));
}
@Test
@@ -379,7 +384,8 @@
denyManageEthPermission();
mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE),
+ any(EthernetCallback.class));
}
@Test
@@ -389,7 +395,8 @@
denyManageEthPermission();
mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE),
+ any(EthernetCallback.class));
}
private void denyPermissions(String... permissions) {
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index 0376a2a..ea3d392 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -39,7 +39,6 @@
import android.net.EthernetManager;
import android.net.IEthernetServiceListener;
import android.net.INetd;
-import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
import android.net.IpConfiguration;
@@ -76,7 +75,7 @@
private static final String TEST_IFACE = "test123";
private static final int TIMEOUT_MS = 1_000;
private static final String THREAD_NAME = "EthernetServiceThread";
- private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
+ private static final EthernetCallback NULL_CB = new EthernetCallback(null);
private EthernetTracker tracker;
private HandlerThread mHandlerThread;
@Mock private Context mContext;
@@ -88,8 +87,8 @@
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
initMockResources();
- when(mFactory.updateInterfaceLinkState(anyString(), anyBoolean(), any())).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,
@@ -102,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() {
@@ -344,31 +343,29 @@
new StaticIpConfiguration.Builder().setIpAddress(linkAddr).build();
final IpConfiguration ipConfig =
new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
- final INetworkInterfaceOutcomeReceiver listener = null;
+ final EthernetCallback listener = new EthernetCallback(null);
tracker.updateConfiguration(TEST_IFACE, ipConfig, capabilities, listener);
waitForIdle();
verify(mFactory).updateInterface(
- eq(TEST_IFACE), eq(ipConfig), eq(capabilities), eq(listener));
+ eq(TEST_IFACE), eq(ipConfig), eq(capabilities));
}
@Test
public void testEnableInterfaceCorrectlyCallsFactory() {
- tracker.enableInterface(TEST_IFACE, NULL_LISTENER);
+ tracker.enableInterface(TEST_IFACE, NULL_CB);
waitForIdle();
- verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */),
- eq(NULL_LISTENER));
+ verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */));
}
@Test
public void testDisableInterfaceCorrectlyCallsFactory() {
- tracker.disableInterface(TEST_IFACE, NULL_LISTENER);
+ tracker.disableInterface(TEST_IFACE, NULL_CB);
waitForIdle();
- verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */),
- eq(NULL_LISTENER));
+ verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */));
}
@Test
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
index c6852d1..83e6b5f 100644
--- a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
@@ -16,7 +16,14 @@
package com.android.server.net;
+import static android.system.OsConstants.EPERM;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -27,6 +34,8 @@
import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
+import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
import androidx.test.filters.SmallTest;
@@ -36,6 +45,7 @@
import com.android.net.module.util.Struct.U32;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestBpfMap;
import org.junit.Before;
import org.junit.Test;
@@ -44,6 +54,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -56,7 +69,8 @@
private final TestLooper mLooper = new TestLooper();
private BaseNetdUnsolicitedEventListener mListener;
private BpfInterfaceMapUpdater mUpdater;
- @Mock private IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ private IBpfMap<U32, InterfaceMapValue> mBpfMap =
+ spy(new TestBpfMap<>(U32.class, InterfaceMapValue.class));
@Mock private INetd mNetd;
@Mock private Context mContext;
@@ -118,4 +132,43 @@
mLooper.dispatchAll();
verifyNoMoreInteractions(mBpfMap);
}
+
+ @Test
+ public void testGetIfNameByIndex() throws Exception {
+ mBpfMap.updateEntry(new U32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+ assertEquals(TEST_INTERFACE_NAME, mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ @Test
+ public void testGetIfNameByIndexNoEntry() {
+ assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ @Test
+ public void testGetIfNameByIndexException() throws Exception {
+ doThrow(new ErrnoException("", EPERM)).when(mBpfMap).getValue(new U32(TEST_INDEX));
+ assertNull(mUpdater.getIfNameByIndex(TEST_INDEX));
+ }
+
+ private void assertDumpContains(final String dump, final String message) {
+ assertTrue(String.format("dump(%s) does not contain '%s'", dump, message),
+ dump.contains(message));
+ }
+
+ private String getDump() {
+ final StringWriter sw = new StringWriter();
+ mUpdater.dump(new IndentingPrintWriter(new PrintWriter(sw), " "));
+ return sw.toString();
+ }
+
+ @Test
+ public void testDump() throws ErrnoException {
+ mBpfMap.updateEntry(new U32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+ mBpfMap.updateEntry(new U32(TEST_INDEX2), new InterfaceMapValue(TEST_INTERFACE_NAME2));
+
+ final String dump = getDump();
+ assertDumpContains(dump, "IfaceIndexNameMap: OK");
+ assertDumpContains(dump, "ifaceIndex=1 ifaceName=test1");
+ assertDumpContains(dump, "ifaceIndex=2 ifaceName=test2");
+ }
}
diff --git a/tests/unit/java/com/android/server/net/InterfaceMapValueTest.java b/tests/unit/java/com/android/server/net/InterfaceMapValueTest.java
new file mode 100644
index 0000000..ee13d5f
--- /dev/null
+++ b/tests/unit/java/com/android/server/net/InterfaceMapValueTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class InterfaceMapValueTest {
+ private static final String IF_NAME = "wlan0";
+ private static final byte[] IF_NAME_BYTE = new byte[]{'w', 'l', 'a', 'n', '0'};
+ private static final byte[] IF_NAME_BYTE_WITH_PADDING =
+ new byte[]{'w', 'l', 'a', 'n', '0', 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0}; // IF_NAME_BYTE_WITH_PADDING.length = 16
+ private static final byte[] IF_NAME_BYTE_LONG =
+ new byte[]{'w', 'l', 'a', 'n', '0', 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0}; // IF_NAME_BYTE_LONG.length = 24
+
+ @Test
+ public void testInterfaceMapValueFromString() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME);
+ assertArrayEquals(IF_NAME_BYTE_WITH_PADDING, value.interfaceName);
+ }
+
+ @Test
+ public void testInterfaceMapValueFromByte() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME_BYTE_WITH_PADDING);
+ assertArrayEquals(IF_NAME_BYTE_WITH_PADDING, value.interfaceName);
+ }
+
+ @Test
+ public void testInterfaceMapValueFromByteShort() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME_BYTE);
+ assertArrayEquals(IF_NAME_BYTE_WITH_PADDING, value.interfaceName);
+ }
+
+ @Test
+ public void testInterfaceMapValueFromByteLong() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME_BYTE_LONG);
+ assertArrayEquals(IF_NAME_BYTE_WITH_PADDING, value.interfaceName);
+ }
+
+ @Test
+ public void testGetInterfaceNameString() {
+ final InterfaceMapValue value = new InterfaceMapValue(IF_NAME_BYTE_WITH_PADDING);
+ assertEquals(IF_NAME, value.getInterfaceNameString());
+ }
+}
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 f64e35b..fdbccba 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,7 +124,9 @@
import android.provider.Settings;
import android.system.ErrnoException;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
@@ -130,12 +135,15 @@
import com.android.connectivity.resources.R;
import com.android.internal.util.FileRotator;
import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
+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 com.android.server.net.NetworkStatsService.AlertObserver;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -168,6 +176,8 @@
import java.time.ZoneOffset;
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;
@@ -205,13 +215,20 @@
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;
+ private static final String DUMPSYS_BPF_RAW_MAP = "--bpfRawMap";
+ private static final String DUMPSYS_COOKIE_TAG_MAP = "--cookieTagMap";
+ private static final String LINE_DELIMITER = "\\n";
+
+
private long mElapsedRealtime;
private File mStatsDir;
@@ -233,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);
@@ -327,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");
@@ -346,9 +367,9 @@
mElapsedRealtime = 0L;
- expectDefaultSettings();
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectSystemReady();
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
+ prepareForSystemReady();
mService.systemReady();
// Verify that system ready fetches realtime stats
verify(mStatsFactory).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
@@ -485,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;
+ }
};
}
@@ -505,10 +537,10 @@
private void initWifiStats(NetworkStateSnapshot snapshot) throws Exception {
// pretend that wifi network comes online; service should ask about full
// network state, and poll any existing interfaces before updating.
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {snapshot};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
@@ -517,10 +549,10 @@
private void incrementWifiStats(long durationMillis, String iface,
long rxb, long rxp, long txb, long txp) throws Exception {
incrementCurrentTime(durationMillis);
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(iface, rxb, rxp, txb, txp));
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
forcePollAndWaitForIdle();
}
@@ -586,10 +618,10 @@
// pretend that wifi network comes online; service should ask about full
// network state, and poll any existing interfaces before updating.
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
@@ -600,10 +632,10 @@
// modify some number on wifi, and trigger poll event
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
@@ -631,14 +663,14 @@
// graceful shutdown system, which should trigger persist of stats, and
// clear any values in memory.
- expectDefaultSettings();
+ mockDefaultSettings();
mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
assertStatsFilesExist(true);
// boot through serviceReady() again
- expectDefaultSettings();
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectSystemReady();
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
+ prepareForSystemReady();
mService.systemReady();
@@ -663,20 +695,20 @@
// pretend that wifi network comes online; service should ask about full
// network state, and poll any existing interfaces before updating.
- expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ mockSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// modify some number on wifi, and trigger poll event
incrementCurrentTime(2 * HOUR_IN_MILLIS);
- expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 512L, 4L, 512L, 4L));
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
forcePollAndWaitForIdle();
// verify service recorded history
@@ -688,9 +720,9 @@
// now change bucket duration setting and trigger another poll with
// exact same values, which should resize existing buckets.
- expectSettings(0L, 30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockSettings(30 * MINUTE_IN_MILLIS, WEEK_IN_MILLIS);
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
forcePollAndWaitForIdle();
// verify identical stats, but spread across 4 buckets now
@@ -704,20 +736,20 @@
@Test
public void testUidStatsAcrossNetworks() throws Exception {
// pretend first mobile network comes online
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildMobileState(IMSI_1)};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// create some traffic on first network
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 2048L, 16L, 512L, 4L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
@@ -735,11 +767,11 @@
// now switch networks; this also tests that we're okay with interfaces
// disappearing, to verify we don't count backwards.
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
+ mockDefaultSettings();
states = new NetworkStateSnapshot[] {buildMobileState(IMSI_2)};
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 2048L, 16L, 512L, 4L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
@@ -751,10 +783,10 @@
// create traffic on second network
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 2176L, 17L, 1536L, 12L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
@@ -779,20 +811,20 @@
@Test
public void testUidRemovedIsMoved() throws Exception {
// pretend that network comes online
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// create some traffic
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 4128L, 258L, 544L, 34L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE,
@@ -808,13 +840,12 @@
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.
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 4128L, 258L, 544L, 34L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE,
@@ -851,8 +882,8 @@
new NetworkStateSnapshot[]{buildMobileState(IMSI_1)};
// 3G network comes online.
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS);
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
@@ -860,7 +891,7 @@
// Create some traffic.
incrementCurrentTime(MINUTE_IN_MILLIS);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ 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)));
forcePollAndWaitForIdle();
@@ -874,7 +905,7 @@
// 4G network comes online.
incrementCurrentTime(MINUTE_IN_MILLIS);
setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_LTE);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
// Append more traffic on existing 3g stats entry.
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16L, 22L, 17L, 2L, 0L))
@@ -894,7 +925,7 @@
// 5g network comes online.
incrementCurrentTime(MINUTE_IN_MILLIS);
setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_NR);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
// Existing stats remains.
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16L, 22L, 17L, 2L, 0L))
@@ -925,14 +956,16 @@
.setRatType(TelephonyManager.NETWORK_TYPE_NR)
.setMeteredness(METERED_NO).build();
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
// 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]);
@@ -942,7 +975,7 @@
// and DEFAULT_NETWORK_YES, because these three properties aren't tracked at that layer.
// They are layered on top by inspecting the iface properties.
incrementCurrentTime(HOUR_IN_MILLIS);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L)
.insertEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
@@ -975,14 +1008,14 @@
NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{
buildOemManagedMobileState(IMSI_1, false,
new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PAID})};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// Create some traffic.
incrementCurrentTime(MINUTE_IN_MILLIS);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 36L, 41L, 24L, 96L, 0L)));
forcePollAndWaitForIdle();
@@ -990,14 +1023,14 @@
// OEM_PRIVATE network comes online.
states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false,
new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE})};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// Create some traffic.
incrementCurrentTime(MINUTE_IN_MILLIS);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 49L, 71L, 72L, 48L, 0L)));
forcePollAndWaitForIdle();
@@ -1006,28 +1039,28 @@
states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false,
new int[]{NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE,
NetworkCapabilities.NET_CAPABILITY_OEM_PAID})};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// Create some traffic.
incrementCurrentTime(MINUTE_IN_MILLIS);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 57L, 86L, 83L, 93L, 0L)));
forcePollAndWaitForIdle();
// OEM_NONE network comes online.
states = new NetworkStateSnapshot[]{buildOemManagedMobileState(IMSI_1, false, new int[]{})};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// Create some traffic.
incrementCurrentTime(MINUTE_IN_MILLIS);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 29L, 73L, 34L, 31L, 0L)));
forcePollAndWaitForIdle();
@@ -1061,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);
}
@@ -1070,19 +1103,19 @@
@Test
public void testSummaryForAllUid() throws Exception {
// pretend that network comes online
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// create some traffic for two apps
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
@@ -1097,9 +1130,9 @@
// now create more traffic in next hour, but only for one app
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE,
@@ -1129,10 +1162,10 @@
@Test
public void testGetLatestSummary() throws Exception {
// Pretend that network comes online.
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{buildWifiState()};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
@@ -1142,8 +1175,8 @@
NetworkStats.Entry entry = new NetworkStats.Entry(
TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 50L, 5L, 51L, 1L, 3L);
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1).insertEntry(entry));
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1).insertEntry(entry));
+ mockNetworkStatsUidDetail(buildEmptyStats());
forcePollAndWaitForIdle();
// Verify the mocked stats is returned by querying with the range of the latest bucket.
@@ -1164,12 +1197,47 @@
}
@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
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
@@ -1185,9 +1253,9 @@
DEFAULT_NETWORK_NO, 1024L, 8L, 512L, 4L, 0L);
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
.insertEntry(entry1)
.insertEntry(entry2)
.insertEntry(entry3));
@@ -1209,19 +1277,19 @@
@Test
public void testForegroundBackground() throws Exception {
// pretend that network comes online
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// create some initial traffic
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
mService.incrementOperationCount(UID_RED, 0xF00D, 1);
@@ -1234,9 +1302,9 @@
// now switch to foreground
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
@@ -1268,23 +1336,23 @@
@Test
public void testMetered() throws Exception {
// pretend that network comes online
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states =
new NetworkStateSnapshot[] {buildWifiState(true /* isMetered */, TEST_IFACE)};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// create some initial traffic
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
// Note that all traffic from NetworkManagementService is tagged as METERED_NO, ROAMING_NO
// and DEFAULT_NETWORK_YES, because these three properties aren't tracked at that layer.
// We layer them on top by inspecting the iface properties.
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
@@ -1308,24 +1376,26 @@
@Test
public void testRoaming() throws Exception {
// pretend that network comes online
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states =
- new NetworkStateSnapshot[] {buildMobileState(TEST_IFACE, IMSI_1,
- false /* isTemporarilyNotMetered */, true /* isRoaming */)};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ new NetworkStateSnapshot[] {buildStateOfTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR, TYPE_MOBILE,
+ TEST_IFACE, IMSI_1, null /* wifiNetworkKey */,
+ false /* isTemporarilyNotMetered */, true /* isRoaming */)};
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// Create some traffic
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
// Note that all traffic from NetworkManagementService is tagged as METERED_NO and
// ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
// on top by inspecting the iface properties.
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO,
@@ -1348,18 +1418,18 @@
@Test
public void testTethering() throws Exception {
// pretend first mobile network comes online
- expectDefaultSettings();
+ mockDefaultSettings();
final NetworkStateSnapshot[] states =
new NetworkStateSnapshot[]{buildMobileState(IMSI_1)};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// create some tethering traffic
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
+ mockDefaultSettings();
// Register custom provider and retrieve callback.
final TestableNetworkStatsProviderBinder provider =
@@ -1391,8 +1461,8 @@
final TetherStatsParcel[] tetherStatsParcels =
{buildTetherStatsParcel(TEST_IFACE, 1408L, 10L, 256L, 1L, 0)};
- expectNetworkStatsSummary(swIfaceStats);
- expectNetworkStatsUidDetail(localUidStats, tetherStatsParcels);
+ mockNetworkStatsSummary(swIfaceStats);
+ mockNetworkStatsUidDetail(localUidStats, tetherStatsParcels);
forcePollAndWaitForIdle();
// verify service recorded history
@@ -1405,10 +1475,10 @@
public void testRegisterUsageCallback() throws Exception {
// pretend that wifi network comes online; service should ask about full
// network state, and poll any existing interfaces before updating.
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
@@ -1420,9 +1490,9 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdInBytes);
// Force poll
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockDefaultSettings();
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
// Register and verify request and that binder was called
DataUsageRequest request = mService.registerUsageCallback(
@@ -1440,10 +1510,10 @@
// modify some number on wifi, and trigger poll event
// not enough traffic to call data usage callback
incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 1024L, 1L, 2048L, 2L));
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
forcePollAndWaitForIdle();
// verify service recorded history
@@ -1455,10 +1525,10 @@
// and bump forward again, with counters going higher. this is
// important, since it will trigger the data usage callback
incrementCurrentTime(DAY_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockDefaultSettings();
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 4096000L, 4L, 8192000L, 8L));
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
forcePollAndWaitForIdle();
// verify service recorded history
@@ -1469,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);
@@ -1495,11 +1565,11 @@
@Test
public void testStatsProviderUpdateStats() throws Exception {
// Pretend that network comes online.
- expectDefaultSettings();
+ mockDefaultSettings();
final NetworkStateSnapshot[] states =
new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
// Register custom provider and retrieve callback.
final TestableNetworkStatsProviderBinder provider =
@@ -1528,7 +1598,7 @@
// Make another empty mutable stats object. This is necessary since the new NetworkStats
// object will be used to compare with the old one in NetworkStatsRecoder, two of them
// cannot be the same object.
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
forcePollAndWaitForIdle();
@@ -1557,14 +1627,14 @@
@Test
public void testDualVilteProviderStats() throws Exception {
// Pretend that network comes online.
- expectDefaultSettings();
+ mockDefaultSettings();
final int subId1 = 1;
final int subId2 = 2;
final NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{
buildImsState(IMSI_1, subId1, TEST_IFACE),
buildImsState(IMSI_2, subId2, TEST_IFACE2)};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
// Register custom provider and retrieve callback.
final TestableNetworkStatsProviderBinder provider =
@@ -1595,7 +1665,7 @@
// Make another empty mutable stats object. This is necessary since the new NetworkStats
// object will be used to compare with the old one in NetworkStatsRecoder, two of them
// cannot be the same object.
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
forcePollAndWaitForIdle();
@@ -1628,7 +1698,7 @@
@Test
public void testStatsProviderSetAlert() throws Exception {
// Pretend that network comes online.
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states =
new NetworkStateSnapshot[]{buildWifiState(true /* isMetered */, TEST_IFACE)};
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
@@ -1652,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();
@@ -1678,8 +1748,8 @@
final NetworkStateSnapshot[] states =
new NetworkStateSnapshot[]{buildMobileState(IMSI_1)};
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsSummary(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
// 3G network comes online.
setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS);
@@ -1688,7 +1758,7 @@
// Create some traffic.
incrementCurrentTime(MINUTE_IN_MILLIS);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ 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)));
forcePollAndWaitForIdle();
@@ -1712,7 +1782,7 @@
// Create some traffic.
incrementCurrentTime(MINUTE_IN_MILLIS);
// Append more traffic on existing snapshot.
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ 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 + 4L, 18L + 4L, 14L + 3L,
1L + 1L, 0L))
@@ -1735,7 +1805,7 @@
// Create some traffic.
incrementCurrentTime(MINUTE_IN_MILLIS);
// Append more traffic on existing snapshot.
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 22L, 26L, 19L, 5L, 0L))
.addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE,
@@ -1752,16 +1822,16 @@
@Test
public void testOperationCount_nonDefault_traffic() throws Exception {
// Pretend mobile network comes online, but wifi is the default network.
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{
buildWifiState(true /*isMetered*/, TEST_IFACE2), buildMobileState(IMSI_1)};
- expectNetworkStatsUidDetail(buildEmptyStats());
+ mockNetworkStatsUidDetail(buildEmptyStats());
mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
new UnderlyingNetworkInfo[0]);
// Create some traffic on mobile network.
incrementCurrentTime(HOUR_IN_MILLIS);
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 2L, 1L, 3L, 4L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
@@ -1812,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));
@@ -1821,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);
@@ -1834,7 +1904,7 @@
@Test
public void testDataMigration() throws Exception {
assertStatsFilesExist(false);
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
@@ -1843,10 +1913,9 @@
// modify some number on wifi, and trigger poll event
incrementCurrentTime(HOUR_IN_MILLIS);
- // expectDefaultSettings();
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
@@ -1883,9 +1952,9 @@
getLegacyCollection(PREFIX_UID_TAG, true /* includeTags */));
// Mock zero usage and boot through serviceReady(), verify there is no imported data.
- expectDefaultSettings();
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectSystemReady();
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
+ prepareForSystemReady();
mService.systemReady();
assertStatsFilesExist(false);
@@ -1896,9 +1965,9 @@
assertStatsFilesExist(false);
// Boot through systemReady() again.
- expectDefaultSettings();
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectSystemReady();
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
+ prepareForSystemReady();
mService.systemReady();
// After systemReady(), the service should have historical stats loaded again.
@@ -1919,7 +1988,7 @@
@Test
public void testDataMigration_differentFromFallback() throws Exception {
assertStatsFilesExist(false);
- expectDefaultSettings();
+ mockDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{buildWifiState()};
@@ -1928,9 +1997,9 @@
// modify some number on wifi, and trigger poll event
incrementCurrentTime(HOUR_IN_MILLIS);
- expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, 1024L, 8L, 2048L, 16L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+ mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
.insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
forcePollAndWaitForIdle();
// Simulate shutdown to force persisting data
@@ -1971,9 +2040,9 @@
getLegacyCollection(PREFIX_UID_TAG, true /* includeTags */));
// Mock zero usage and boot through serviceReady(), verify there is no imported data.
- expectDefaultSettings();
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectSystemReady();
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
+ prepareForSystemReady();
mService.systemReady();
assertStatsFilesExist(false);
@@ -1984,9 +2053,9 @@
assertStatsFilesExist(false);
// Boot through systemReady() again.
- expectDefaultSettings();
- expectNetworkStatsUidDetail(buildEmptyStats());
- expectSystemReady();
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
+ prepareForSystemReady();
mService.systemReady();
// Verify the result read from public API matches the result returned from the importer.
@@ -2019,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());
@@ -2081,8 +2203,8 @@
rxBytes, rxPackets, txBytes, txPackets, operations);
}
- private void expectSystemReady() throws Exception {
- expectNetworkStatsSummary(buildEmptyStats());
+ private void prepareForSystemReady() throws Exception {
+ mockNetworkStatsSummary(buildEmptyStats());
}
private String getActiveIface(NetworkStateSnapshot... states) throws Exception {
@@ -2092,57 +2214,54 @@
return states[0].getLinkProperties().getInterfaceName();
}
- // TODO: These expect* methods are used to have NetworkStatsService returns the given stats
- // instead of expecting anything. Therefore, these methods should be renamed properly.
- private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
- expectNetworkStatsSummaryDev(summary.clone());
- expectNetworkStatsSummaryXt(summary.clone());
+ private void mockNetworkStatsSummary(NetworkStats summary) throws Exception {
+ mockNetworkStatsSummaryDev(summary.clone());
+ mockNetworkStatsSummaryXt(summary.clone());
}
- private void expectNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
- when(mStatsFactory.readNetworkStatsSummaryDev()).thenReturn(summary);
+ private void mockNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
+ doReturn(summary).when(mStatsFactory).readNetworkStatsSummaryDev();
}
- private void expectNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
- when(mStatsFactory.readNetworkStatsSummaryXt()).thenReturn(summary);
+ private void mockNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
+ doReturn(summary).when(mStatsFactory).readNetworkStatsSummaryXt();
}
- private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
+ private void mockNetworkStatsUidDetail(NetworkStats detail) throws Exception {
final TetherStatsParcel[] tetherStatsParcels = {};
- expectNetworkStatsUidDetail(detail, tetherStatsParcels);
+ mockNetworkStatsUidDetail(detail, tetherStatsParcels);
}
- private void expectNetworkStatsUidDetail(NetworkStats detail,
+ 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 expectDefaultSettings() throws Exception {
- expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
+ private void mockDefaultSettings() throws Exception {
+ mockSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
}
- private void expectSettings(long persistBytes, 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);
+ private void mockSettings(long bucketDuration, long deleteAge) throws Exception {
+ 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) {
@@ -2184,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() {
@@ -2333,12 +2462,27 @@
dump.contains(message));
}
- private String getDump() {
+ private String getDump(final String[] args) {
final StringWriter sw = new StringWriter();
- mService.dump(new FileDescriptor(), new PrintWriter(sw), new String[]{});
+ mService.dump(new FileDescriptor(), new PrintWriter(sw), args);
return sw.toString();
}
+ private String getDump() {
+ return getDump(new String[]{});
+ }
+
+ private <K extends Struct, V extends Struct> Map<K, V> parseBpfRawMap(
+ Class<K> keyClass, Class<V> valueClass, String dumpStr) {
+ final HashMap<K, V> map = new HashMap<>();
+ for (final String line : dumpStr.split(LINE_DELIMITER)) {
+ final Pair<K, V> keyValue =
+ BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
+ map.put(keyValue.first, keyValue.second);
+ }
+ return map;
+ }
+
@Test
public void testDumpCookieTagMap() throws ErrnoException {
initBpfMapsWithTagData(UID_BLUE);
@@ -2350,6 +2494,23 @@
}
@Test
+ public void testDumpCookieTagMapBpfRawMap() throws ErrnoException {
+ initBpfMapsWithTagData(UID_BLUE);
+
+ final String dump = getDump(new String[]{DUMPSYS_BPF_RAW_MAP, DUMPSYS_COOKIE_TAG_MAP});
+ Map<CookieTagMapKey, CookieTagMapValue> cookieTagMap = parseBpfRawMap(
+ CookieTagMapKey.class, CookieTagMapValue.class, dump);
+
+ final CookieTagMapValue val1 = cookieTagMap.get(new CookieTagMapKey(2002));
+ assertEquals(1, val1.tag);
+ assertEquals(1002, val1.uid);
+
+ final CookieTagMapValue val2 = cookieTagMap.get(new CookieTagMapKey(3002));
+ assertEquals(2, val2.tag);
+ assertEquals(1002, val2.uid);
+ }
+
+ @Test
public void testDumpUidCounterSetMap() throws ErrnoException {
initBpfMapsWithTagData(UID_BLUE);
@@ -2367,4 +2528,28 @@
assertDumpContains(dump, "uid rxBytes rxPackets txBytes txPackets");
assertDumpContains(dump, "1002 10000 10 6000 6");
}
+
+ private void doTestDumpStatsMap(final String expectedIfaceName) throws ErrnoException {
+ initBpfMapsWithTagData(UID_BLUE);
+
+ final String dump = getDump();
+ assertDumpContains(dump, "mStatsMapA: OK");
+ assertDumpContains(dump, "mStatsMapB: OK");
+ assertDumpContains(dump,
+ "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets");
+ assertDumpContains(dump, "10 " + expectedIfaceName + " 0x2 1002 0 5000 5 3000 3");
+ assertDumpContains(dump, "10 " + expectedIfaceName + " 0x1 1002 0 5000 5 3000 3");
+ }
+
+ @Test
+ public void testDumpStatsMap() throws ErrnoException {
+ doReturn("wlan0").when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+ doTestDumpStatsMap("wlan0");
+ }
+
+ @Test
+ public void testDumpStatsMapUnknownInterface() throws ErrnoException {
+ doReturn(null).when(mBpfInterfaceMapUpdater).getIfNameByIndex(10 /* index */);
+ doTestDumpStatsMap("unknown");
+ }
}
diff --git a/tools/Android.bp b/tools/Android.bp
index 1fa93bb..7d6b248 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -48,6 +48,7 @@
java_library {
name: "jarjar-rules-generator-testjavalib",
srcs: ["testdata/java/**/*.java"],
+ libs: ["unsupportedappusage"],
visibility: ["//visibility:private"],
}
@@ -67,6 +68,17 @@
compile_dex: false,
}
+java_library {
+ name: "framework-connectivity-t.stubs.module_lib-for-test",
+ visibility: ["//visibility:private"],
+ static_libs: [
+ "framework-connectivity-t.stubs.module_lib",
+ ],
+ // Not strictly necessary but specified as this MUST not have generate
+ // a dex jar as that will break the tests.
+ compile_dex: false,
+}
+
python_test_host {
name: "jarjar-rules-generator-test",
srcs: [
@@ -75,8 +87,11 @@
],
data: [
"testdata/test-jarjar-excludes.txt",
+ // two unsupportedappusage lists with different classes to test using multiple lists
"testdata/test-unsupportedappusage.txt",
+ "testdata/test-other-unsupportedappusage.txt",
":framework-connectivity.stubs.module_lib-for-test",
+ ":framework-connectivity-t.stubs.module_lib-for-test",
":jarjar-rules-generator-testjavalib",
],
main: "gen_jarjar_test.py",
diff --git a/tools/gen_jarjar.py b/tools/gen_jarjar.py
index 4c2cf54..eb686ce 100755
--- a/tools/gen_jarjar.py
+++ b/tools/gen_jarjar.py
@@ -28,8 +28,8 @@
def parse_arguments(argv):
parser = argparse.ArgumentParser()
parser.add_argument(
- '--jars', nargs='+',
- help='Path to pre-jarjar JAR. Can be followed by multiple space-separated paths.')
+ 'jars', nargs='+',
+ help='Path to pre-jarjar JAR. Multiple jars can be specified.')
parser.add_argument(
'--prefix', required=True,
help='Package prefix to use for jarjared classes, '
@@ -37,18 +37,17 @@
parser.add_argument(
'--output', required=True, help='Path to output jarjar rules file.')
parser.add_argument(
- '--apistubs', nargs='*', default=[],
- help='Path to API stubs jar. Classes that are API will not be jarjared. Can be followed by '
- 'multiple space-separated paths.')
+ '--apistubs', action='append', default=[],
+ help='Path to API stubs jar. Classes that are API will not be jarjared. Can be repeated to '
+ 'specify multiple jars.')
parser.add_argument(
- '--unsupportedapi', nargs='*', default=[],
- help='Path to UnsupportedAppUsage hidden API .txt lists. '
- 'Classes that have UnsupportedAppUsage API will not be jarjared. Can be followed by '
- 'multiple space-separated paths.')
+ '--unsupportedapi',
+ help='Column(:)-separated paths to UnsupportedAppUsage hidden API .txt lists. '
+ 'Classes that have UnsupportedAppUsage API will not be jarjared.')
parser.add_argument(
- '--excludes', nargs='*', default=[],
- help='Path to files listing classes that should not be jarjared. Can be followed by '
- 'multiple space-separated paths. '
+ '--excludes', action='append', default=[],
+ help='Path to files listing classes that should not be jarjared. Can be repeated to '
+ 'specify multiple files.'
'Each file should contain one full-match regex per line. Empty lines or lines '
'starting with "#" are ignored.')
return parser.parse_args(argv)
@@ -103,8 +102,10 @@
for apistubs_file in args.apistubs:
excluded_classes.update(_list_toplevel_jar_classes(apistubs_file))
- for unsupportedapi_file in args.unsupportedapi:
- excluded_classes.update(_list_hiddenapi_classes(unsupportedapi_file))
+ unsupportedapi_files = (args.unsupportedapi and args.unsupportedapi.split(':')) or []
+ for unsupportedapi_file in unsupportedapi_files:
+ if unsupportedapi_file:
+ excluded_classes.update(_list_hiddenapi_classes(unsupportedapi_file))
exclude_regexes = []
for exclude_file in args.excludes:
@@ -115,7 +116,8 @@
jar_classes = _list_jar_classes(jar)
jar_classes.sort()
for clazz in jar_classes:
- if (_get_toplevel_class(clazz) not in excluded_classes and
+ if (not clazz.startswith(args.prefix + '.') and
+ _get_toplevel_class(clazz) not in excluded_classes and
not any(r.fullmatch(clazz) for r in exclude_regexes)):
outfile.write(f'rule {clazz} {args.prefix}.@0\n')
# Also include jarjar rules for unit tests of the class, so the package matches
diff --git a/tools/gen_jarjar_test.py b/tools/gen_jarjar_test.py
index 8d8e82b..f5bf499 100644
--- a/tools/gen_jarjar_test.py
+++ b/tools/gen_jarjar_test.py
@@ -31,11 +31,11 @@
class TestGenJarjar(unittest.TestCase):
def test_gen_rules(self):
args = gen_jarjar.parse_arguments([
- "--jars", "jarjar-rules-generator-testjavalib.jar",
+ "jarjar-rules-generator-testjavalib.jar",
"--prefix", "jarjar.prefix",
"--output", "test-output-rules.txt",
"--apistubs", "framework-connectivity.stubs.module_lib.jar",
- "--unsupportedapi", "testdata/test-unsupportedappusage.txt",
+ "--unsupportedapi", ":testdata/test-unsupportedappusage.txt",
"--excludes", "testdata/test-jarjar-excludes.txt",
])
gen_jarjar.make_jarjar_rules(args)
@@ -43,6 +43,39 @@
with open(args.output) as out:
lines = out.readlines()
+ self.maxDiff = None
+ self.assertListEqual([
+ 'rule android.net.IpSecTransform jarjar.prefix.@0\n',
+ 'rule android.net.IpSecTransformTest jarjar.prefix.@0\n',
+ 'rule android.net.IpSecTransformTest$* jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClass jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClassTest jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClassTest$* jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClassTest jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClassTest$* jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClass jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClassTest jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClassTest$* jarjar.prefix.@0\n'
+ ], lines)
+
+ def test_gen_rules_repeated_args(self):
+ args = gen_jarjar.parse_arguments([
+ "jarjar-rules-generator-testjavalib.jar",
+ "--prefix", "jarjar.prefix",
+ "--output", "test-output-rules.txt",
+ "--apistubs", "framework-connectivity.stubs.module_lib.jar",
+ "--apistubs", "framework-connectivity-t.stubs.module_lib.jar",
+ "--unsupportedapi",
+ "testdata/test-unsupportedappusage.txt:testdata/test-other-unsupportedappusage.txt",
+ "--excludes", "testdata/test-jarjar-excludes.txt",
+ ])
+ gen_jarjar.make_jarjar_rules(args)
+
+ with open(args.output) as out:
+ lines = out.readlines()
+
+ self.maxDiff = None
self.assertListEqual([
'rule test.utils.TestUtilClass jarjar.prefix.@0\n',
'rule test.utils.TestUtilClassTest jarjar.prefix.@0\n',
diff --git a/tools/testdata/java/android/net/IpSecTransform.java b/tools/testdata/java/android/net/IpSecTransform.java
new file mode 100644
index 0000000..0140bc5
--- /dev/null
+++ b/tools/testdata/java/android/net/IpSecTransform.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Test class with a name matching a public API in a secondary (framework-connectivity-t) stubs jar.
+ */
+public class IpSecTransform {
+}
diff --git a/tools/testdata/java/jarjar/prefix/AlreadyInTargetPackageClass.java b/tools/testdata/java/jarjar/prefix/AlreadyInTargetPackageClass.java
new file mode 100644
index 0000000..6859020
--- /dev/null
+++ b/tools/testdata/java/jarjar/prefix/AlreadyInTargetPackageClass.java
@@ -0,0 +1,25 @@
+/*
+ * 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 jarjar.prefix;
+
+/**
+ * Sample class to test jarjar rules, already in the "jarjar.prefix" package.
+ */
+public class AlreadyInTargetPackageClass {
+ /** Test inner class that should not be jarjared either */
+ public static class TestInnerClass {}
+}
diff --git a/tools/testdata/java/test/unsupportedappusage/OtherUnsupportedUsageClass.java b/tools/testdata/java/test/unsupportedappusage/OtherUnsupportedUsageClass.java
new file mode 100644
index 0000000..9d3ae2e0
--- /dev/null
+++ b/tools/testdata/java/test/unsupportedappusage/OtherUnsupportedUsageClass.java
@@ -0,0 +1,25 @@
+/*
+ * 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 test.unsupportedappusage;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
+public class OtherUnsupportedUsageClass {
+ // The annotation is just for completeness, what matters is the unsupportedappusage.txt file
+ @UnsupportedAppUsage
+ public void testSecondMethod() {}
+}
diff --git a/tools/testdata/java/test/unsupportedappusage/TestUnsupportedAppUsageClass.java b/tools/testdata/java/test/unsupportedappusage/TestUnsupportedAppUsageClass.java
index 9d32296..460c91b 100644
--- a/tools/testdata/java/test/unsupportedappusage/TestUnsupportedAppUsageClass.java
+++ b/tools/testdata/java/test/unsupportedappusage/TestUnsupportedAppUsageClass.java
@@ -16,6 +16,11 @@
package test.unsupportedappusage;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
public class TestUnsupportedAppUsageClass {
+ // The annotation is just for completeness, what matters is the unsupportedappusage.txt file
+ @UnsupportedAppUsage
public void testMethod() {}
}
diff --git a/tools/testdata/test-other-unsupportedappusage.txt b/tools/testdata/test-other-unsupportedappusage.txt
new file mode 100644
index 0000000..b7d74a4
--- /dev/null
+++ b/tools/testdata/test-other-unsupportedappusage.txt
@@ -0,0 +1 @@
+Ltest/unsupportedappusage/OtherUnsupportedUsageClass;->testSecondMethod()V
\ No newline at end of file