Merge "Verify converting to Vpnprofile with IkeTunnelConnectionParams set"
diff --git a/OWNERS_core_networking b/OWNERS_core_networking
index bc1d002..3a08422 100644
--- a/OWNERS_core_networking
+++ b/OWNERS_core_networking
@@ -10,6 +10,7 @@
markchien@google.com
martinwu@google.com
maze@google.com
+motomuman@google.com
nuccachen@google.com
paulhu@google.com
prohr@google.com
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/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 3b5d6bf..8cf46ef 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -128,7 +128,10 @@
filegroup {
name: "connectivity-hiddenapi-files",
- srcs: ["hiddenapi/*.txt"],
+ srcs: [
+ ":connectivity-t-hiddenapi-files",
+ "hiddenapi/*.txt",
+ ],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
@@ -159,16 +162,11 @@
// Additional hidden API flag files to override the defaults. This must only be
// modified by the Soong or platform compat team.
hidden_api: {
- max_target_r_low_priority: [
- "hiddenapi/hiddenapi-max-target-r-loprio.txt",
- ],
max_target_o_low_priority: [
"hiddenapi/hiddenapi-max-target-o-low-priority.txt",
- "hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt",
],
unsupported: [
"hiddenapi/hiddenapi-unsupported.txt",
- "hiddenapi/hiddenapi-unsupported-tiramisu.txt",
],
// The following packages contain classes from other modules on the
diff --git a/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp b/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
index 291bf54..6699c0d 100644
--- a/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
+++ b/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
@@ -49,8 +49,10 @@
BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, type, 0, 1),
- // Accept or reject.
+ // Accept.
BPF_STMT(BPF_RET | BPF_K, 0xffff),
+
+ // Reject.
BPF_STMT(BPF_RET | BPF_K, 0)
};
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java b/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
index a7e8ccf..68d694a 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
@@ -28,26 +28,4 @@
public TetherLimitKey(final int ifindex) {
this.ifindex = ifindex;
}
-
- // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherLimitKey)) return false;
-
- final TetherLimitKey that = (TetherLimitKey) obj;
-
- return ifindex == that.ifindex;
- }
-
- @Override
- public int hashCode() {
- return Integer.hashCode(ifindex);
- }
-
- @Override
- public String toString() {
- return String.format("ifindex: %d", ifindex);
- }
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherLimitValue.java b/Tethering/src/com/android/networkstack/tethering/TetherLimitValue.java
index ed7e7d4..00dfcc6 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherLimitValue.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherLimitValue.java
@@ -32,26 +32,4 @@
public TetherLimitValue(final long limit) {
this.limit = limit;
}
-
- // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherLimitValue)) return false;
-
- final TetherLimitValue that = (TetherLimitValue) obj;
-
- return limit == that.limit;
- }
-
- @Override
- public int hashCode() {
- return Long.hashCode(limit);
- }
-
- @Override
- public String toString() {
- return String.format("limit: %d", limit);
- }
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0d1b22e..1f3fc11 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2004,10 +2004,6 @@
return;
}
- if (arg1 == UpstreamNetworkMonitor.NOTIFY_TEST_NETWORK_AVAILABLE) {
- chooseUpstreamType(false);
- }
-
if (ns == null || !pertainsToCurrentUpstream(ns)) {
// TODO: In future, this is where upstream evaluation and selection
// could be handled for notifications which include sufficient data.
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index 15df0c6..16c031b 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -85,12 +85,11 @@
private static final boolean DBG = false;
private static final boolean VDBG = false;
- public static final int EVENT_ON_CAPABILITIES = 1;
- public static final int EVENT_ON_LINKPROPERTIES = 2;
- public static final int EVENT_ON_LOST = 3;
- public static final int EVENT_DEFAULT_SWITCHED = 4;
- public static final int NOTIFY_LOCAL_PREFIXES = 10;
- public static final int NOTIFY_TEST_NETWORK_AVAILABLE = 11;
+ public static final int EVENT_ON_CAPABILITIES = 1;
+ public static final int EVENT_ON_LINKPROPERTIES = 2;
+ public static final int EVENT_ON_LOST = 3;
+ public static final int EVENT_DEFAULT_SWITCHED = 4;
+ public static final int NOTIFY_LOCAL_PREFIXES = 10;
// This value is used by deprecated preferredUpstreamIfaceTypes selection which is default
// disabled.
@VisibleForTesting
@@ -468,17 +467,6 @@
notifyTarget(EVENT_DEFAULT_SWITCHED, ns);
}
- private void maybeHandleTestNetwork(@NonNull Network network) {
- if (!mPreferTestNetworks) return;
-
- final UpstreamNetworkState ns = mNetworkMap.get(network);
- if (network.equals(mTetheringUpstreamNetwork) || !isTestNetwork(ns)) return;
-
- // Test network is available. Notify tethering.
- Log.d(TAG, "Handle test network: " + network);
- notifyTarget(NOTIFY_TEST_NETWORK_AVAILABLE, ns);
- }
-
private void recomputeLocalPrefixes() {
final HashSet<IpPrefix> localPrefixes = allLocalPrefixes(mNetworkMap.values());
if (!mLocalPrefixes.equals(localPrefixes)) {
@@ -561,12 +549,6 @@
// So it's not useful to do this work for non-LISTEN_ALL callbacks.
if (mCallbackType == CALLBACK_LISTEN_ALL) {
recomputeLocalPrefixes();
-
- // When the LISTEN_ALL network callback calls onLinkPropertiesChanged, it means that
- // all the network information for the network is known (because
- // onLinkPropertiesChanged is called after onAvailable and onCapabilitiesChanged).
- // Inform tethering that the test network might have changed.
- maybeHandleTestNetwork(network);
}
}
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 9303d0a..7527913 100644
--- a/Tethering/tests/integration/AndroidManifest.xml
+++ b/Tethering/tests/integration/AndroidManifest.xml
@@ -17,6 +17,7 @@
package="com.android.networkstack.tethering.tests.integration">
<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
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index f486c1e..43aff7e 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -21,25 +21,39 @@
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.isExpectedIcmpPacket;
+import static android.net.TetheringTester.isExpectedTcpPacket;
import static android.net.TetheringTester.isExpectedUdpDnsPacket;
import static android.net.TetheringTester.isExpectedUdpPacket;
+import static android.system.OsConstants.ICMP_ECHO;
+import static android.system.OsConstants.ICMP_ECHOREPLY;
+import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_IP;
import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
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.IpUtils.icmpChecksum;
+import static com.android.net.module.util.IpUtils.ipChecksum;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -55,12 +69,14 @@
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;
@@ -85,6 +101,8 @@
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.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv4Header;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.UdpHeader;
@@ -164,8 +182,11 @@
(Inet6Address) parseNumericAddress("2002:db8:1::515:ca");
private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
+ private static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.wrap(new byte[0]);
private static final short DNS_PORT = 53;
+ private static final short WINDOW = (short) 0x2000;
+ private static final short URGENT_POINTER = 0;
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
@@ -176,6 +197,10 @@
private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
private static final short HOP_LIMIT = 0x40;
+ private static final short ICMPECHO_CODE = 0x0;
+ private static final short ICMPECHO_ID = 0x0;
+ private static final short ICMPECHO_SEQ = 0x0;
+
// 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[] {
@@ -239,6 +264,8 @@
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;
@@ -445,7 +472,10 @@
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
do {
byte[] pkt = reader.popPacket(timeoutMs);
- if (isExpectedIcmpv6Packet(pkt, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return;
+ if (isExpectedIcmpPacket(pkt, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ROUTER_ADVERTISEMENT)) {
+ return;
+ }
timeoutMs = deadline - SystemClock.uptimeMillis();
} while (timeoutMs > 0);
@@ -702,10 +732,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;
}
@@ -959,14 +996,16 @@
tester.verifyUpload(request, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedIcmpv6Packet(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE);
+ return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REQUEST_TYPE);
});
ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
tester.verifyDownload(reply, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE);
+ return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REPLY_TYPE);
});
}
@@ -997,26 +1036,21 @@
private static final ByteBuffer TX_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x56, (byte) 0x78 });
+ private short getEthType(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
+ return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6;
+ }
+
+ private int getIpProto(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
+ return isAddressIpv4(srcIp, dstIp) ? IPPROTO_IP : IPPROTO_IPV6;
+ }
+
@NonNull
private ByteBuffer buildUdpPacket(
@Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
short srcPort, short dstPort, @Nullable final ByteBuffer payload)
throws Exception {
- int ipProto;
- short ethType;
- if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) {
- ipProto = IPPROTO_IP;
- ethType = (short) ETHER_TYPE_IPV4;
- } else if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) {
- ipProto = IPPROTO_IPV6;
- ethType = (short) ETHER_TYPE_IPV6;
- } else {
- fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp);
- // Make compiler happy to the uninitialized ipProto and ethType.
- return null; // unreachable, the annotation @NonNull of function return value is true.
- }
-
+ final int ipProto = getIpProto(srcIp, dstIp);
final boolean hasEther = (srcMac != null && dstMac != null);
final int payloadLen = (payload == null) ? 0 : payload.limit();
final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP,
@@ -1024,7 +1058,9 @@
final PacketBuilder packetBuilder = new PacketBuilder(buffer);
// [1] Ethernet header
- if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ethType);
+ if (hasEther) {
+ packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp));
+ }
// [2] IP header
if (ipProto == IPPROTO_IP) {
@@ -1247,6 +1283,52 @@
}
}
+ // 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(isInterfaceForTetheringAvailable());
@@ -1265,8 +1347,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());
@@ -1446,12 +1537,156 @@
// TODO: test CLAT bpf maps.
}
+ // TODO: support R device. See b/234727688.
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
public void testTetherClatUdp() throws Exception {
runClatUdpTest();
}
+ // PacketBuilder doesn't support IPv4 ICMP packet. It may need to refactor PacketBuilder first
+ // because ICMP is a specific layer 3 protocol for PacketBuilder which expects packets always
+ // have layer 3 (IP) and layer 4 (TCP, UDP) for now. Since we don't use IPv4 ICMP packet too
+ // much in this test, we just write a ICMP packet builder here.
+ // TODO: move ICMPv4 packet build function to common utilis.
+ @NonNull
+ private ByteBuffer buildIcmpEchoPacketV4(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
+ int type, short id, short seq) throws Exception {
+ if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) {
+ fail("Unsupported ICMP type: " + type);
+ }
+
+ // Build ICMP echo id and seq fields as payload. Ignore the data field.
+ final ByteBuffer payload = ByteBuffer.allocate(4);
+ payload.putShort(id);
+ payload.putShort(seq);
+ payload.rewind();
+
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0;
+ final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class);
+ final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class);
+ final int payloadLen = payload.limit();
+ final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen
+ + Icmpv4HeaderLen + payloadLen);
+
+ // [1] Ethernet header
+ if (hasEther) {
+ final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4);
+ ethHeader.writeToByteBuffer(packet);
+ }
+
+ // [2] IP header
+ final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE,
+ (short) 0 /* totalLength, calculate later */, ID,
+ FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP,
+ (short) 0 /* checksum, calculate later */, srcIp, dstIp);
+ ipv4Header.writeToByteBuffer(packet);
+
+ // [3] ICMP header
+ final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE,
+ (short) 0 /* checksum, calculate later */);
+ icmpv4Header.writeToByteBuffer(packet);
+
+ // [4] Payload
+ packet.put(payload);
+ packet.flip();
+
+ // [5] Finalize packet
+ // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset
+ // in buffer equals ethernet header length because IPv4 header is located next to ethernet
+ // header. Otherwise, IPv4 header offset is 0.
+ final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0;
+
+ // Populate the IPv4 totalLength field.
+ packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET,
+ (short) (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen));
+
+ // Populate the IPv4 header checksum field.
+ packet.putShort(ipv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
+ ipChecksum(packet, ipv4HeaderOffset /* headerOffset */));
+
+ // Populate the ICMP checksum field.
+ packet.putShort(ipv4HeaderOffset + IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET,
+ icmpChecksum(packet, ipv4HeaderOffset + IPV4_HEADER_MIN_LEN,
+ Icmpv4HeaderLen + payloadLen));
+ return packet;
+ }
+
+ @NonNull
+ private ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp,
+ @NonNull final Inet4Address dstIp, int type, short id, short seq)
+ throws Exception {
+ return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp,
+ type, id, seq);
+ }
+
+ @Test
+ public void testIcmpv4Echo() 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 */);
+
+ final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
+ tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
+ tester.verifyUpload(request, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, false /* hasEth */, true /* isIpv4 */, ICMP_ECHO);
+ });
+
+ final ByteBuffer reply = buildIcmpEchoPacketV4(REMOTE_IP4_ADDR /* srcIp*/,
+ (Inet4Address) TEST_IP4_ADDR.getAddress() /* dstIp */, ICMP_ECHOREPLY, ICMPECHO_ID,
+ ICMPECHO_SEQ);
+ tester.verifyDownload(reply, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
+ });
+ }
+
+ // TODO: support R device. See b/234727688.
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherClatIcmp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
+
+ // Send an IPv4 ICMP packet in original direction.
+ // IPv4 packet -- CLAT translation --> IPv6 packet
+ final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
+ tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
+ (Inet4Address) REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
+ tester.verifyUpload(request, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REQUEST_TYPE);
+ });
+
+ // Send an IPv6 ICMP packet in reply direction.
+ // IPv6 packet -- CLAT translation --> IPv4 packet
+ final ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(
+ (Inet6Address) REMOTE_NAT64_ADDR /* srcIp */, clatIp6 /* dstIp */);
+ tester.verifyDownload(reply, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
+ });
+ }
+
@NonNull
private ByteBuffer buildDnsReplyMessageById(short id) {
byte[] replyMessage = Arrays.copyOf(DNS_REPLY, DNS_REPLY.length);
@@ -1540,6 +1775,190 @@
(short) udpHeader.srcPort, (short) dnsQuery.getHeader().id, tester);
}
+ @NonNull
+ private ByteBuffer buildTcpPacket(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
+ short srcPort, short dstPort, final short seq, final short ack,
+ final byte tcpFlags, @NonNull final ByteBuffer payload) throws Exception {
+ final int ipProto = getIpProto(srcIp, dstIp);
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_TCP,
+ payload.limit());
+ final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+
+ // [1] Ethernet header
+ if (hasEther) {
+ packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp));
+ }
+
+ // [2] IP header
+ if (ipProto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_TCP, (Inet4Address) srcIp, (Inet4Address) dstIp);
+ } else {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_TCP,
+ HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
+ }
+
+ // [3] TCP header
+ packetBuilder.writeTcpHeader(srcPort, dstPort, seq, ack, tcpFlags, WINDOW, URGENT_POINTER);
+
+ // [4] Payload
+ buffer.put(payload);
+ // in case data might be reused by caller, restore the position and
+ // limit of bytebuffer.
+ payload.clear();
+
+ return packetBuilder.finalizePacket();
+ }
+
+ private void sendDownloadPacketTcp(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
+ @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
+ boolean is6To4) throws Exception {
+ if (is6To4) {
+ assertFalse("CLAT download test must sends IPv6 packet", isAddressIpv4(srcIp, dstIp));
+ }
+
+ // Expected received TCP packet IP protocol. While testing CLAT (is6To4 = true), the packet
+ // on downstream must be IPv4. Otherwise, the IP protocol of test packet is the same on
+ // both downstream and upstream.
+ final boolean isIpv4 = is6To4 ? true : isAddressIpv4(srcIp, dstIp);
+
+ final ByteBuffer testPacket = buildTcpPacket(null /* srcMac */, null /* dstMac */,
+ srcIp, dstIp, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */, seq, ack,
+ tcpFlags, payload);
+ tester.verifyDownload(testPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedTcpPacket(p, true /* hasEther */, isIpv4, seq, payload);
+ });
+ }
+
+ private void sendUploadPacketTcp(@NonNull final MacAddress srcMac,
+ @NonNull final MacAddress dstMac, @NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
+ @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
+ boolean is4To6) throws Exception {
+ if (is4To6) {
+ assertTrue("CLAT upload test must sends IPv4 packet", isAddressIpv4(srcIp, dstIp));
+ }
+
+ // Expected received TCP packet IP protocol. While testing CLAT (is4To6 = true), the packet
+ // on upstream must be IPv6. Otherwise, the IP protocol of test packet is the same on
+ // both downstream and upstream.
+ final boolean isIpv4 = is4To6 ? false : isAddressIpv4(srcIp, dstIp);
+
+ final ByteBuffer testPacket = buildTcpPacket(srcMac, dstMac, srcIp, dstIp,
+ LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, seq, ack, tcpFlags,
+ payload);
+ tester.verifyUpload(testPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedTcpPacket(p, false /* hasEther */, isIpv4, seq, payload);
+ });
+ }
+
+ void runTcpTest(
+ @NonNull final MacAddress uploadSrcMac, @NonNull final MacAddress uploadDstMac,
+ @NonNull final InetAddress uploadSrcIp, @NonNull final InetAddress uploadDstIp,
+ @NonNull final InetAddress downloadSrcIp, @NonNull final InetAddress downloadDstIp,
+ @NonNull final TetheringTester tester, boolean isClat) throws Exception {
+ // Three way handshake and data transfer.
+ //
+ // Server (base seq = 2000) Client (base seq = 1000)
+ // | |
+ // | [1] [SYN] SEQ = 1000 |
+ // |<---------------------------------------------------------| -
+ // | | ^
+ // | [2] [SYN + ACK] SEQ = 2000, ACK = 1000+1 | |
+ // |--------------------------------------------------------->| three way handshake
+ // | | |
+ // | [3] [ACK] SEQ = 1001, ACK = 2000+1 | v
+ // |<---------------------------------------------------------| -
+ // | | ^
+ // | [4] [ACK] SEQ = 1001, ACK = 2001, 2 byte payload | |
+ // |<---------------------------------------------------------| data transfer
+ // | | |
+ // | [5] [ACK] SEQ = 2001, ACK = 1001+2, 2 byte payload | v
+ // |--------------------------------------------------------->| -
+ // | |
+ //
+
+ // This test can only verify the packets are transferred end to end but TCP state.
+ // TODO: verify TCP state change via /proc/net/nf_conntrack or netlink conntrack event.
+ // [1] [UPLOAD] [SYN]: SEQ = 1000
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1000 /* seq */, (short) 0 /* ack */, TCPHDR_SYN, EMPTY_PAYLOAD,
+ tester, isClat /* is4To6 */);
+
+ // [2] [DONWLOAD] [SYN + ACK]: SEQ = 2000, ACK = 1001
+ sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2000 /* seq */,
+ (short) 1001 /* ack */, (byte) ((TCPHDR_SYN | TCPHDR_ACK) & 0xff), EMPTY_PAYLOAD,
+ tester, isClat /* is6To4 */);
+
+ // [3] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, EMPTY_PAYLOAD, tester,
+ isClat /* is4To6 */);
+
+ // [4] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001, 2 byte payload
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, TX_PAYLOAD,
+ tester, isClat /* is4To6 */);
+
+ // [5] [DONWLOAD] [ACK]: SEQ = 2001, ACK = 1003, 2 byte payload
+ sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2001 /* seq */,
+ (short) 1003 /* ack */, TCPHDR_ACK, RX_PAYLOAD, tester, isClat /* is6To4 */);
+
+ // TODO: test BPF offload maps.
+ }
+
+ @Test
+ public void testTetherTcpV4() 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 */);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
+ REMOTE_IP4_ADDR /* downloadSrcIp */, TEST_IP4_ADDR.getAddress() /* downloadDstIp */,
+ tester, false /* isClat */);
+ }
+
+ @Test
+ public void testTetherTcpV6() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv6Addr /* uploadSrcIp */, REMOTE_IP6_ADDR /* uploadDstIp */,
+ REMOTE_IP6_ADDR /* downloadSrcIp */, tethered.ipv6Addr /* downloadDstIp */,
+ tester, false /* isClat */);
+ }
+
+ @Test
+ public void testTetherClatTcp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
+ REMOTE_NAT64_ADDR /* downloadSrcIp */, clatIp6 /* downloadDstIp */,
+ tester, true /* isClat */);
+ }
+
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 9cc2e49..ae39b24 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/src/android/net/TetheringTester.java
@@ -17,7 +17,9 @@
package android.net;
import static android.net.InetAddresses.parseNumericAddress;
+import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.DnsPacket.ANSECTION;
@@ -39,6 +41,7 @@
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -57,6 +60,7 @@
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv4Header;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
@@ -64,6 +68,7 @@
import com.android.net.module.util.structs.NsHeader;
import com.android.net.module.util.structs.PrefixInformationOption;
import com.android.net.module.util.structs.RaHeader;
+import com.android.net.module.util.structs.TcpHeader;
import com.android.net.module.util.structs.UdpHeader;
import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
@@ -268,7 +273,8 @@
private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) {
ByteBuffer buf = ByteBuffer.wrap(packet);
- if (!isExpectedIcmpv6Packet(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) {
+ if (!isExpectedIcmpPacket(buf, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ROUTER_ADVERTISEMENT)) {
fail("Parsing RA packet fail");
}
@@ -298,7 +304,8 @@
sendRsPacket(srcMac, dstMac);
final byte[] raPacket = verifyPacketNotNull("Receive RA fail", getDownloadPacket(p -> {
- return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
+ return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ROUTER_ADVERTISEMENT);
}));
final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
@@ -360,20 +367,27 @@
}
}
- public static boolean isExpectedIcmpv6Packet(byte[] packet, boolean hasEth, int type) {
+ public static boolean isExpectedIcmpPacket(byte[] packet, boolean hasEth, boolean isIpv4,
+ int type) {
final ByteBuffer buf = ByteBuffer.wrap(packet);
- return isExpectedIcmpv6Packet(buf, hasEth, type);
+ return isExpectedIcmpPacket(buf, hasEth, isIpv4, type);
}
- private static boolean isExpectedIcmpv6Packet(ByteBuffer buf, boolean hasEth, int type) {
+ private static boolean isExpectedIcmpPacket(ByteBuffer buf, boolean hasEth, boolean isIpv4,
+ int type) {
try {
- if (hasEth && !hasExpectedEtherHeader(buf, false /* isIpv4 */)) return false;
+ if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
- if (!hasExpectedIpHeader(buf, false /* isIpv4 */, IPPROTO_ICMPV6)) return false;
+ final int ipProto = isIpv4 ? IPPROTO_ICMP : IPPROTO_ICMPV6;
+ if (!hasExpectedIpHeader(buf, isIpv4, ipProto)) return false;
- return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
+ if (isIpv4) {
+ return Struct.parse(Icmpv4Header.class, buf).type == (short) type;
+ } else {
+ return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
+ }
} catch (Exception e) {
- // Parsing packet fail means it is not icmpv6 packet.
+ // Parsing packet fail means it is not icmp packet.
}
return false;
@@ -578,6 +592,42 @@
return true;
}
+
+ private static boolean isTcpSynPacket(@NonNull final TcpHeader tcpHeader) {
+ return (tcpHeader.dataOffsetAndControlBits & TCPHDR_SYN) != 0;
+ }
+
+ public static boolean isExpectedTcpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
+ boolean isIpv4, int seq, @NonNull final ByteBuffer payload) {
+ final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
+ try {
+ if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
+
+ if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_TCP)) return false;
+
+ final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, buf);
+ if (tcpHeader.seq != seq) return false;
+
+ // Don't try to parse the payload if it is a TCP SYN segment because additional TCP
+ // option MSS may be added in the SYN segment. Currently, TetherController uses
+ // iptables to limit downstream MSS for IPv4. The additional TCP options will be
+ // misunderstood as payload because parsing TCP options are not supported by class
+ // TcpHeader for now. See TetherController::setupIptablesHooks.
+ // TODO: remove once TcpHeader supports parsing TCP options.
+ if (isTcpSynPacket(tcpHeader)) {
+ Log.d(TAG, "Found SYN segment. Ignore parsing the remaining part of packet.");
+ return true;
+ }
+
+ if (payload.limit() != buf.remaining()) return false;
+ return Arrays.equals(getRemaining(buf), getRemaining(payload.asReadOnlyBuffer()));
+ } catch (Exception e) {
+ // Parsing packet fail means it is not tcp packet.
+ }
+
+ return false;
+ }
+
private void sendUploadPacket(ByteBuffer packet) throws Exception {
mDownstreamReader.sendResponse(packet);
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
index e0d77ee..b2cbf75 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
@@ -19,7 +19,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static com.android.networkstack.apishim.common.ShimUtils.isAtLeastS;
@@ -330,28 +329,6 @@
this.legacyType = toLegacyType(networkCapabilities);
}
- // Used for test network only because ConnectivityManager.networkCapabilitiesForType
- // doesn't support "TRANSPORT_TEST -> TYPE_TEST" in #matchesLegacyType. Beware of
- // satisfiedByNetworkCapabilities doesn't check on new |networkCapabilities| as
- // #matchesLegacyType.
- // TODO: refactor when tethering no longer uses CONNECTIVITY_ACTION.
- private TestNetworkAgent(TestConnectivityManager cm) {
- this.cm = cm;
- networkId = new Network(cm.getNetworkId());
- networkCapabilities = new NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_TEST)
- .addCapability(NET_CAPABILITY_INTERNET)
- .build();
- linkProperties = new LinkProperties();
- legacyType = TYPE_TEST;
- }
-
- // TODO: refactor when tethering no longer uses CONNECTIVITY_ACTION.
- public static TestNetworkAgent buildTestNetworkAgentForTestNetwork(
- TestConnectivityManager cm) {
- return new TestNetworkAgent(cm);
- }
-
private static int toLegacyType(NetworkCapabilities nc) {
for (int type = 0; type < ConnectivityManager.TYPE_TEST; type++) {
if (matchesLegacyType(nc, type)) return type;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index b0cb7f2..9b9507b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -449,36 +449,6 @@
}
@Test
- public void testGetCurrentPreferredUpstream_TestNetworkPreferred() throws Exception {
- mUNM.startTrackDefaultNetwork(mEntitleMgr);
- mUNM.startObserveAllNetworks();
- mUNM.setUpstreamConfig(true /* autoUpstream */, false /* dunRequired */);
- mUNM.setTryCell(true);
- mUNM.setPreferTestNetworks(true);
-
- // [1] Mobile connects, DUN not required -> mobile selected.
- final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
- cellAgent.fakeConnect();
- mCM.makeDefaultNetwork(cellAgent);
- mLooper.dispatchAll();
- assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
- assertEquals(0, mCM.mRequested.size());
-
- // [2] Test network connects -> test network selected.
- final TestNetworkAgent testAgent =
- TestNetworkAgent.buildTestNetworkAgentForTestNetwork(mCM);
- testAgent.fakeConnect();
- mLooper.dispatchAll();
- assertEquals(testAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
- assertEquals(0, mCM.mRequested.size());
-
- // [3] Disable test networks preferred -> mobile selected.
- mUNM.setPreferTestNetworks(false);
- assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
- assertEquals(0, mCM.mRequested.size());
- }
-
- @Test
public void testLocalPrefixes() throws Exception {
mUNM.startTrackDefaultNetwork(mEntitleMgr);
mUNM.startObserveAllNetworks();
diff --git a/common/src/com/android/net/module/util/bpf/ClatEgress4Key.java b/common/src/com/android/net/module/util/bpf/ClatEgress4Key.java
index 12200ec..f0af3dd 100644
--- a/common/src/com/android/net/module/util/bpf/ClatEgress4Key.java
+++ b/common/src/com/android/net/module/util/bpf/ClatEgress4Key.java
@@ -24,13 +24,13 @@
/** Key type for clat egress IPv4 maps. */
public class ClatEgress4Key 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.Ipv4Address)
public final Inet4Address local4; // The source IPv4 address
- public ClatEgress4Key(final long iif, final Inet4Address local4) {
+ public ClatEgress4Key(final int iif, final Inet4Address local4) {
this.iif = iif;
this.local4 = local4;
}
diff --git a/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java b/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
index c10cb4d..69fab09 100644
--- a/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
+++ b/common/src/com/android/net/module/util/bpf/ClatEgress4Value.java
@@ -24,8 +24,8 @@
/** Value type for clat egress IPv4 maps. */
public class ClatEgress4Value extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long oif; // The output interface to redirect to
+ @Field(order = 0, type = Type.S32)
+ public final int oif; // The output interface to redirect to
@Field(order = 1, type = Type.Ipv6Address)
public final Inet6Address local6; // The full 128-bits of the source IPv6 address
@@ -36,7 +36,7 @@
@Field(order = 3, type = Type.U8, padding = 3)
public final short oifIsEthernet; // Whether the output interface requires ethernet header
- public ClatEgress4Value(final long oif, final Inet6Address local6, final Inet6Address pfx96,
+ public ClatEgress4Value(final int oif, final Inet6Address local6, final Inet6Address pfx96,
final short oifIsEthernet) {
this.oif = oif;
this.local6 = local6;
diff --git a/common/src/com/android/net/module/util/bpf/ClatIngress6Key.java b/common/src/com/android/net/module/util/bpf/ClatIngress6Key.java
index 1e2f4e0..561113c 100644
--- a/common/src/com/android/net/module/util/bpf/ClatIngress6Key.java
+++ b/common/src/com/android/net/module/util/bpf/ClatIngress6Key.java
@@ -24,8 +24,8 @@
/** Key type for clat ingress IPv6 maps. */
public class ClatIngress6Key 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.Ipv6Address)
public final Inet6Address pfx96; // The source /96 nat64 prefix, bottom 32 bits must be 0
@@ -33,7 +33,7 @@
@Field(order = 2, type = Type.Ipv6Address)
public final Inet6Address local6; // The full 128-bits of the destination IPv6 address
- public ClatIngress6Key(final long iif, final Inet6Address pfx96, final Inet6Address local6) {
+ public ClatIngress6Key(final int iif, final Inet6Address pfx96, final Inet6Address local6) {
this.iif = iif;
this.pfx96 = pfx96;
this.local6 = local6;
diff --git a/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java b/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
index bfec44f..fb81caa 100644
--- a/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
+++ b/common/src/com/android/net/module/util/bpf/ClatIngress6Value.java
@@ -24,13 +24,13 @@
/** Value type for clat ingress IPv6 maps. */
public class ClatIngress6Value extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long oif; // The output interface to redirect to (0 means don't redirect)
+ @Field(order = 0, type = Type.S32)
+ public final int oif; // The output interface to redirect to (0 means don't redirect)
@Field(order = 1, type = Type.Ipv4Address)
public final Inet4Address local4; // The destination IPv4 address
- public ClatIngress6Value(final long oif, final Inet4Address local4) {
+ public ClatIngress6Value(final int oif, final Inet4Address local4) {
this.oif = oif;
this.local4 = local4;
}
diff --git a/common/src/com/android/net/module/util/bpf/CookieTagMapValue.java b/common/src/com/android/net/module/util/bpf/CookieTagMapValue.java
index e1a221f..3fbd6fc 100644
--- a/common/src/com/android/net/module/util/bpf/CookieTagMapValue.java
+++ b/common/src/com/android/net/module/util/bpf/CookieTagMapValue.java
@@ -24,13 +24,13 @@
* Value for cookie tag map.
*/
public class CookieTagMapValue extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long uid;
+ @Field(order = 0, type = Type.S32)
+ public final int uid;
@Field(order = 1, type = Type.U32)
public final long tag;
- public CookieTagMapValue(final long uid, final long tag) {
+ public CookieTagMapValue(final int uid, final long tag) {
this.uid = uid;
this.tag = tag;
}
diff --git a/common/src/com/android/net/module/util/bpf/TetherStatsKey.java b/common/src/com/android/net/module/util/bpf/TetherStatsKey.java
index c6d595b..68111b6 100644
--- a/common/src/com/android/net/module/util/bpf/TetherStatsKey.java
+++ b/common/src/com/android/net/module/util/bpf/TetherStatsKey.java
@@ -22,32 +22,10 @@
/** The key of BpfMap which is used for tethering stats. */
public class TetherStatsKey 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 TetherStatsKey(final long ifindex) {
+ public TetherStatsKey(final int ifindex) {
this.ifindex = ifindex;
}
-
- // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherStatsKey)) return false;
-
- final TetherStatsKey that = (TetherStatsKey) obj;
-
- return ifindex == that.ifindex;
- }
-
- @Override
- public int hashCode() {
- return Long.hashCode(ifindex);
- }
-
- @Override
- public String toString() {
- return String.format("ifindex: %d", ifindex);
- }
}
diff --git a/common/src/com/android/net/module/util/bpf/TetherStatsValue.java b/common/src/com/android/net/module/util/bpf/TetherStatsValue.java
index 028d217..f05d1b7 100644
--- a/common/src/com/android/net/module/util/bpf/TetherStatsValue.java
+++ b/common/src/com/android/net/module/util/bpf/TetherStatsValue.java
@@ -47,34 +47,4 @@
this.txBytes = txBytes;
this.txErrors = txErrors;
}
-
- // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
-
- if (!(obj instanceof TetherStatsValue)) return false;
-
- final TetherStatsValue that = (TetherStatsValue) obj;
-
- return rxPackets == that.rxPackets
- && rxBytes == that.rxBytes
- && rxErrors == that.rxErrors
- && txPackets == that.txPackets
- && txBytes == that.txBytes
- && txErrors == that.txErrors;
- }
-
- @Override
- public int hashCode() {
- return Long.hashCode(rxPackets) ^ Long.hashCode(rxBytes) ^ Long.hashCode(rxErrors)
- ^ Long.hashCode(txPackets) ^ Long.hashCode(txBytes) ^ Long.hashCode(txErrors);
- }
-
- @Override
- public String toString() {
- return String.format("rxPackets: %s, rxBytes: %s, rxErrors: %s, txPackets: %s, "
- + "txBytes: %s, txErrors: %s", rxPackets, rxBytes, rxErrors, txPackets,
- txBytes, txErrors);
- }
}
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 2e49307..d40fad9 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -75,6 +75,12 @@
],
}
+filegroup {
+ name: "connectivity-t-hiddenapi-files",
+ srcs: ["hiddenapi/*.txt"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
java_library {
name: "framework-connectivity-t-pre-jarjar",
defaults: ["framework-connectivity-t-defaults"],
@@ -114,6 +120,19 @@
"com.android.connectivity",
"com.android.nearby",
],
+
+ hidden_api: {
+ max_target_o_low_priority: [
+ "hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt",
+ ],
+ max_target_r_low_priority: [
+ "hiddenapi/hiddenapi-max-target-r-loprio.txt",
+ ],
+ unsupported: [
+ "hiddenapi/hiddenapi-unsupported-tiramisu.txt",
+ ],
+ },
+
impl_library_visibility: [
"//packages/modules/Connectivity/Tethering/apex",
// In preparation for future move
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt b/framework-t/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
similarity index 100%
rename from Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
rename to framework-t/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt b/framework-t/hiddenapi/hiddenapi-max-target-r-loprio.txt
similarity index 100%
rename from Tethering/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt
rename to framework-t/hiddenapi/hiddenapi-max-target-r-loprio.txt
diff --git a/Tethering/apex/hiddenapi/hiddenapi-unsupported-tiramisu.txt b/framework-t/hiddenapi/hiddenapi-unsupported-tiramisu.txt
similarity index 100%
rename from Tethering/apex/hiddenapi/hiddenapi-unsupported-tiramisu.txt
rename to framework-t/hiddenapi/hiddenapi-unsupported-tiramisu.txt
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/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp
index 328751a..b406776 100644
--- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp
+++ b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp
@@ -23,8 +23,8 @@
static_libs: [
// TODO(b/228406038): Remove "framework-nearby-static" once Fast Pair system APIs add back.
"framework-nearby-static",
+ "gson",
"guava",
- "gson-prebuilt-jar",
],
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
index adae97d..fdda6f7 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
@@ -81,30 +81,20 @@
Context mContext = ApplicationProvider.getApplicationContext();
when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
-
- FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
- fastPairCacheManager.saveDiscoveryItem(mDiscoveryItem);
- assertThat(fastPairCacheManager.getStoredDiscoveryItem(MODEL_ID).getAppName())
- .isEqualTo(APP_NAME);
- }
-
- @Test
- @SdkSuppress(minSdkVersion = 32, codeName = "T")
- public void getAllInfo() {
- Context mContext = ApplicationProvider.getApplicationContext();
- when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
- when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
when(mDiscoveryItem2.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem2);
when(mDiscoveryItem2.getTriggerId()).thenReturn(MODEL_ID2);
FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
fastPairCacheManager.saveDiscoveryItem(mDiscoveryItem);
-
- assertThat(fastPairCacheManager.getAllSavedStoreDiscoveryItem()).hasSize(2);
+ assertThat(fastPairCacheManager.getStoredDiscoveryItem(MODEL_ID).getAppName())
+ .isEqualTo(APP_NAME);
+ assertThat(fastPairCacheManager.getAllSavedStoreDiscoveryItem()).hasSize(1);
fastPairCacheManager.saveDiscoveryItem(mDiscoveryItem2);
-
- assertThat(fastPairCacheManager.getAllSavedStoreDiscoveryItem()).hasSize(3);
+ assertThat(fastPairCacheManager.getStoredDiscoveryItem(MODEL_ID2).getAppName())
+ .isEqualTo(APP_NAME);
+ assertThat(fastPairCacheManager.getAllSavedStoreDiscoveryItem()).hasSize(2);
+ fastPairCacheManager.cleanUp();
}
@Test
diff --git a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
index 3b44d81..ceae9ba 100644
--- a/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
+++ b/service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
@@ -22,14 +22,16 @@
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;
-import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.S32;
/**
* Monitor interface added (without removed) and right interface name and its index to bpf map.
@@ -39,7 +41,7 @@
// This is current path but may be changed soon.
private static final String IFACE_INDEX_NAME_MAP_PATH =
"/sys/fs/bpf/netd_shared/map_netd_iface_index_name_map";
- private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ private final IBpfMap<S32, InterfaceMapValue> mBpfMap;
private final INetd mNetd;
private final Handler mHandler;
private final Dependencies mDeps;
@@ -62,10 +64,10 @@
@VisibleForTesting
public static class Dependencies {
/** Create BpfMap for updating interface and index mapping. */
- public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+ public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
try {
return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH, BpfMap.BPF_F_RDWR,
- U32.class, InterfaceMapValue.class);
+ S32.class, InterfaceMapValue.class);
} catch (ErrnoException e) {
Log.e(TAG, "Cannot create interface map: " + e);
return null;
@@ -124,7 +126,7 @@
}
try {
- mBpfMap.updateEntry(new U32(iface.index), new InterfaceMapValue(ifaceName));
+ mBpfMap.updateEntry(new S32(iface.index), new InterfaceMapValue(ifaceName));
} catch (ErrnoException e) {
Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e);
}
@@ -136,4 +138,37 @@
mHandler.post(() -> addInterface(ifName));
}
}
+
+ /** get interface name by interface index from bpf map */
+ public String getIfNameByIndex(final int index) {
+ try {
+ final InterfaceMapValue value = mBpfMap.getValue(new S32(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/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 8d5c881..0da7b6f 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -164,7 +164,7 @@
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.S32;
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
@@ -408,7 +408,7 @@
* mActiveUidCounterSet to avoid accessing kernel too frequently.
*/
private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
- private final IBpfMap<U32, U8> mUidCounterSetMap;
+ private final IBpfMap<S32, U8> mUidCounterSetMap;
private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap;
private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapA;
private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapB;
@@ -741,10 +741,10 @@
}
/** Get counter sets map for each UID. */
- public IBpfMap<U32, U8> getUidCounterSetMap() {
+ public IBpfMap<S32, U8> getUidCounterSetMap() {
try {
- return new BpfMap<U32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR,
- U32.class, U8.class);
+ return new BpfMap<S32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR,
+ S32.class, U8.class);
} catch (ErrnoException e) {
Log.wtf(TAG, "Cannot open uid counter set map: " + e);
return null;
@@ -1321,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));
@@ -1747,7 +1747,7 @@
if (set == SET_DEFAULT) {
try {
- mUidCounterSetMap.deleteEntry(new U32(uid));
+ mUidCounterSetMap.deleteEntry(new S32(uid));
} catch (ErrnoException e) {
Log.w(TAG, "UidCounterSetMap.deleteEntry(" + uid + ") failed with errno: " + e);
}
@@ -1755,7 +1755,7 @@
}
try {
- mUidCounterSetMap.updateEntry(new U32(uid), new U8((short) set));
+ mUidCounterSetMap.updateEntry(new S32(uid), new U8((short) set));
} catch (ErrnoException e) {
Log.w(TAG, "UidCounterSetMap.updateEntry(" + uid + ", " + set
+ ") failed with errno: " + e);
@@ -2472,7 +2472,7 @@
deleteStatsMapTagData(mStatsMapB, uid);
try {
- mUidCounterSetMap.deleteEntry(new U32(uid));
+ mUidCounterSetMap.deleteEntry(new S32(uid));
} catch (ErrnoException e) {
logErrorIfNotErrNoent(e, "Failed to delete tag data from uid counter set map");
}
@@ -2741,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);
@@ -2755,6 +2761,8 @@
dumpCookieTagMapLocked(pw);
dumpUidCounterSetMapLocked(pw);
dumpAppUidStatsMapLocked(pw);
+ dumpStatsMapLocked(mStatsMapA, pw, "mStatsMapA");
+ dumpStatsMapLocked(mStatsMapB, pw, "mStatsMapB");
pw.decreaseIndent();
}
}
@@ -2840,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")
@@ -2876,6 +2886,29 @@
+ 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 String ifName = mInterfaceMapUpdater.getIfNameByIndex(key.ifaceIndex);
+ return key.ifaceIndex + " "
+ + (ifName != null ? ifName : "unknown") + " "
+ + "0x" + Long.toHexString(key.tag) + " "
+ + key.uid + " "
+ + key.counterSet + " "
+ + value.rxBytes + " "
+ + value.rxPackets + " "
+ + value.txBytes + " "
+ + value.txPackets;
+ });
+ }
+
private NetworkStats readNetworkStatsSummaryDev() {
try {
return mStatsFactory.readNetworkStatsSummaryDev();
diff --git a/service-t/src/com/android/server/net/StatsMapKey.java b/service-t/src/com/android/server/net/StatsMapKey.java
index ea8d836..44269b3 100644
--- a/service-t/src/com/android/server/net/StatsMapKey.java
+++ b/service-t/src/com/android/server/net/StatsMapKey.java
@@ -24,8 +24,8 @@
* Key for both stats maps.
*/
public class StatsMapKey extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long uid;
+ @Field(order = 0, type = Type.S32)
+ public final int uid;
@Field(order = 1, type = Type.U32)
public final long tag;
@@ -33,11 +33,11 @@
@Field(order = 2, type = Type.U32)
public final long counterSet;
- @Field(order = 3, type = Type.U32)
- public final long ifaceIndex;
+ @Field(order = 3, type = Type.S32)
+ public final int ifaceIndex;
- public StatsMapKey(final long uid, final long tag, final long counterSet,
- final long ifaceIndex) {
+ public StatsMapKey(final int uid, final long tag, final long counterSet,
+ final int ifaceIndex) {
this.uid = uid;
this.tag = tag;
this.counterSet = counterSet;
diff --git a/service-t/src/com/android/server/net/UidStatsMapKey.java b/service-t/src/com/android/server/net/UidStatsMapKey.java
index 2849f94..59025fd 100644
--- a/service-t/src/com/android/server/net/UidStatsMapKey.java
+++ b/service-t/src/com/android/server/net/UidStatsMapKey.java
@@ -24,10 +24,10 @@
* Key for uid stats map.
*/
public class UidStatsMapKey extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long uid;
+ @Field(order = 0, type = Type.S32)
+ public final int uid;
- public UidStatsMapKey(final long uid) {
+ public UidStatsMapKey(final int uid) {
this.uid = uid;
}
}
diff --git a/service/Android.bp b/service/Android.bp
index 68c1722..7d3b39a 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -309,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) " +
@@ -328,7 +328,7 @@
],
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)",
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 7fb1b34..49b23c9 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -647,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/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index bfa0808..dbfc383 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -54,6 +54,7 @@
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.S32;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
@@ -105,16 +106,16 @@
"/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 S32 UID_RULES_CONFIGURATION_KEY = new S32(0);
+ private static final S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new S32(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 IBpfMap<U32, U32> sConfigurationMap = null;
+ private static IBpfMap<S32, U32> sConfigurationMap = null;
// BpfMap for UID_OWNER_MAP_PATH. This map is not accessed by others.
- private static IBpfMap<U32, UidOwnerValue> sUidOwnerMap = null;
- private static IBpfMap<U32, U8> sUidPermissionMap = null;
+ private static IBpfMap<S32, UidOwnerValue> sUidOwnerMap = null;
+ private static IBpfMap<S32, U8> sUidPermissionMap = null;
private static IBpfMap<CookieTagMapKey, CookieTagMapValue> sCookieTagMap = null;
// LINT.IfChange(match_type)
@@ -145,7 +146,7 @@
* Set configurationMap for test.
*/
@VisibleForTesting
- public static void setConfigurationMapForTest(IBpfMap<U32, U32> configurationMap) {
+ public static void setConfigurationMapForTest(IBpfMap<S32, U32> configurationMap) {
sConfigurationMap = configurationMap;
}
@@ -153,7 +154,7 @@
* Set uidOwnerMap for test.
*/
@VisibleForTesting
- public static void setUidOwnerMapForTest(IBpfMap<U32, UidOwnerValue> uidOwnerMap) {
+ public static void setUidOwnerMapForTest(IBpfMap<S32, UidOwnerValue> uidOwnerMap) {
sUidOwnerMap = uidOwnerMap;
}
@@ -161,7 +162,7 @@
* Set uidPermissionMap for test.
*/
@VisibleForTesting
- public static void setUidPermissionMapForTest(IBpfMap<U32, U8> uidPermissionMap) {
+ public static void setUidPermissionMapForTest(IBpfMap<S32, U8> uidPermissionMap) {
sUidPermissionMap = uidPermissionMap;
}
@@ -174,28 +175,28 @@
sCookieTagMap = cookieTagMap;
}
- private static IBpfMap<U32, U32> getConfigurationMap() {
+ private static IBpfMap<S32, U32> getConfigurationMap() {
try {
return new BpfMap<>(
- CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U32.class);
+ CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, U32.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open netd configuration map", e);
}
}
- private static IBpfMap<U32, UidOwnerValue> getUidOwnerMap() {
+ private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
try {
return new BpfMap<>(
- UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, UidOwnerValue.class);
+ UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, UidOwnerValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid owner map", e);
}
}
- private static IBpfMap<U32, U8> getUidPermissionMap() {
+ private static IBpfMap<S32, U8> getUidPermissionMap() {
try {
return new BpfMap<>(
- UID_PERMISSION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U8.class);
+ UID_PERMISSION_MAP_PATH, BpfMap.BPF_F_RDWR, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid permission map", e);
}
@@ -294,6 +295,13 @@
return ConnectivityStatsLog.buildStatsEvent(NETWORK_BPF_MAP_INFO, cookieTagMapSize,
uidOwnerMapSize, uidPermissionMapSize);
}
+
+ /**
+ * Call native_dump
+ */
+ public void nativeDump(final FileDescriptor fd, final boolean verbose) {
+ native_dump(fd, verbose);
+ }
}
/** Constructor used after T that doesn't need to use netd anymore. */
@@ -382,7 +390,7 @@
private void removeRule(final int uid, final long match, final String caller) {
try {
synchronized (sUidOwnerMap) {
- final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new U32(uid));
+ final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new S32(uid));
if (oldMatch == null) {
throw new ServiceSpecificException(ENOENT,
@@ -395,9 +403,9 @@
);
if (newMatch.rule == 0) {
- sUidOwnerMap.deleteEntry(new U32(uid));
+ sUidOwnerMap.deleteEntry(new S32(uid));
} else {
- sUidOwnerMap.updateEntry(new U32(uid), newMatch);
+ sUidOwnerMap.updateEntry(new S32(uid), newMatch);
}
}
} catch (ErrnoException e) {
@@ -406,7 +414,7 @@
}
}
- private void addRule(final int uid, final long match, final long iif, final String caller) {
+ private void addRule(final int uid, final long match, final int iif, final String caller) {
if (match != IIF_MATCH && iif != 0) {
throw new ServiceSpecificException(EINVAL,
"Non-interface match must have zero interface index");
@@ -414,7 +422,7 @@
try {
synchronized (sUidOwnerMap) {
- final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new U32(uid));
+ final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new S32(uid));
final UidOwnerValue newMatch;
if (oldMatch != null) {
@@ -428,7 +436,7 @@
match
);
}
- sUidOwnerMap.updateEntry(new U32(uid), newMatch);
+ sUidOwnerMap.updateEntry(new S32(uid), newMatch);
}
} catch (ErrnoException e) {
throw new ServiceSpecificException(e.errno,
@@ -848,7 +856,7 @@
if (permissions == PERMISSION_UNINSTALLED || permissions == PERMISSION_INTERNET) {
for (final int uid : uids) {
try {
- sUidPermissionMap.deleteEntry(new U32(uid));
+ sUidPermissionMap.deleteEntry(new S32(uid));
} catch (ErrnoException e) {
Log.e(TAG, "Failed to remove uid " + uid + " from permission map: " + e);
}
@@ -858,7 +866,7 @@
for (final int uid : uids) {
try {
- sUidPermissionMap.updateEntry(new U32(uid), new U8((short) permissions));
+ sUidPermissionMap.updateEntry(new S32(uid), new U8((short) permissions));
} catch (ErrnoException e) {
Log.e(TAG, "Failed to set permission "
+ permissions + " to uid " + uid + ": " + e);
@@ -920,7 +928,7 @@
EOPNOTSUPP, "dumpsys connectivity trafficcontroller dump not available on pre-T"
+ " devices, use dumpsys netd trafficcontroller instead.");
}
- native_dump(fd, verbose);
+ mDeps.nativeDump(fd, verbose);
}
private static native void native_init(boolean startSkDestroyListener);
@@ -936,6 +944,6 @@
private native int native_updateUidLockdownRule(int uid, boolean add);
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 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
index 7e63761..9322172 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -353,6 +353,12 @@
// connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+ // How long to wait before considering that a network is bad in the absence of any form
+ // of connectivity (valid, partial, captive portal). If none has been detected after this
+ // delay, the stack considers this network bad, which may affect how it's handled in ranking
+ // according to config_networkAvoidBadWifi.
+ private static final int INITIAL_EVALUATION_TIMEOUT_MS = 20 * 1000;
+
// Default to 30s linger time-out, and 5s for nascent network. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -581,12 +587,6 @@
private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
/**
- * used to ask the user to confirm a connection to an unvalidated network.
- * obj = network
- */
- private static final int EVENT_PROMPT_UNVALIDATED = 29;
-
- /**
* used internally to (re)configure always-on networks.
*/
private static final int EVENT_CONFIGURE_ALWAYS_ON_NETWORKS = 30;
@@ -725,6 +725,14 @@
private static final int EVENT_INGRESS_RATE_LIMIT_CHANGED = 56;
/**
+ * The initial evaluation period is over for this network.
+ *
+ * If no form of connectivity has been found on this network (valid, partial, captive portal)
+ * then the stack will now consider it to have been determined bad.
+ */
+ private static final int EVENT_INITIAL_EVALUATION_TIMEOUT = 57;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -3342,17 +3350,6 @@
pw.increaseIndent();
mNetworkActivityTracker.dump(pw);
pw.decreaseIndent();
-
- // pre-T is logged by netd.
- if (SdkLevel.isAtLeastT()) {
- pw.println();
- pw.println("BPF programs & maps:");
- pw.increaseIndent();
- // Flush is required. Otherwise, the traces in fd can interleave with traces in pw.
- pw.flush();
- dumpTrafficController(pw, fd, /*verbose=*/ true);
- pw.decreaseIndent();
- }
}
private void dumpNetworks(IndentingPrintWriter pw) {
@@ -3791,7 +3788,17 @@
private void handleNetworkTested(
@NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) {
- final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0);
+ final boolean valid = (testResult & NETWORK_VALIDATION_RESULT_VALID) != 0;
+ final boolean partial = (testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0;
+ final boolean captive = !TextUtils.isEmpty(redirectUrl);
+
+ // If there is any kind of working networking, then the NAI has been evaluated
+ // once. {@see NetworkAgentInfo#setEvaluated}, which returns whether this is
+ // the first time this ever happened.
+ final boolean someConnectivity = (valid || partial || captive);
+ final boolean becameEvaluated = someConnectivity && nai.setEvaluated();
+ if (becameEvaluated) nai.updateScoreForNetworkAgentUpdate();
+
if (!valid && shouldIgnoreValidationFailureAfterRoam(nai)) {
// Assume the validation failure is due to a temporary failure after roaming
// and ignore it. NetworkMonitor will continue to retry validation. If it
@@ -3834,8 +3841,13 @@
}
} else if (partialConnectivityChanged) {
updateCapabilitiesForNetwork(nai);
+ } else if (becameEvaluated) {
+ // If valid or partial connectivity changed, updateCapabilities* has
+ // done the rematch.
+ rematchAllNetworksAndRequests();
}
updateInetCondition(nai);
+
// Let the NetworkAgent know the state of its network
// TODO: Evaluate to update partial connectivity to status to NetworkAgent.
nai.onValidationStatusChanged(
@@ -3843,13 +3855,13 @@
redirectUrl);
// If NetworkMonitor detects partial connectivity before
- // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
+ // EVENT_INITIAL_EVALUATION_TIMEOUT arrives, show the partial connectivity notification
// immediately. Re-notify partial connectivity silently if no internet
// notification already there.
if (!wasPartial && nai.partialConnectivity()) {
// Remove delayed message if there is a pending message.
- mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
- handlePromptUnvalidated(nai.network);
+ mHandler.removeMessages(EVENT_INITIAL_EVALUATION_TIMEOUT, nai.network);
+ handleInitialEvaluationTimeout(nai.network);
}
if (wasValidated && !nai.isValidated()) {
@@ -4949,11 +4961,11 @@
}
}
- private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
- if (VDBG) log("scheduleUnvalidatedPrompt " + nai.network);
+ /** Schedule evaluation timeout */
+ @VisibleForTesting
+ public void scheduleEvaluationTimeout(@NonNull final Network network, final long delayMs) {
mHandler.sendMessageDelayed(
- mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
- PROMPT_UNVALIDATED_DELAY_MS);
+ mHandler.obtainMessage(EVENT_INITIAL_EVALUATION_TIMEOUT, network), delayMs);
}
@Override
@@ -5188,23 +5200,28 @@
return false;
}
- private void handlePromptUnvalidated(Network network) {
- if (VDBG || DDBG) log("handlePromptUnvalidated " + network);
- NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ private void handleInitialEvaluationTimeout(@NonNull final Network network) {
+ if (VDBG || DDBG) log("handleInitialEvaluationTimeout " + network);
- if (nai == null || !shouldPromptUnvalidated(nai)) {
- return;
+ NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (null == nai) return;
+
+ if (nai.setEvaluated()) {
+ // If setEvaluated() returned true, the network never had any form of connectivity.
+ // This may have an impact on request matching if bad WiFi avoidance is off and the
+ // network was found not to have Internet access.
+ nai.updateScoreForNetworkAgentUpdate();
+ rematchAllNetworksAndRequests();
}
+ if (!shouldPromptUnvalidated(nai)) return;
+
// Stop automatically reconnecting to this network in the future. Automatically connecting
// to a network that provides no or limited connectivity is not useful, because the user
// cannot use that network except through the notification shown by this method, and the
// notification is only shown if the network is explicitly selected by the user.
nai.onPreventAutomaticReconnect();
- // 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()) {
showNetworkNotification(nai, NotificationType.PARTIAL_CONNECTIVITY);
} else {
@@ -5343,8 +5360,8 @@
handleSetAvoidUnvalidated((Network) msg.obj);
break;
}
- case EVENT_PROMPT_UNVALIDATED: {
- handlePromptUnvalidated((Network) msg.obj);
+ case EVENT_INITIAL_EVALUATION_TIMEOUT: {
+ handleInitialEvaluationTimeout((Network) msg.obj);
break;
}
case EVENT_CONFIGURE_ALWAYS_ON_NETWORKS: {
@@ -9208,7 +9225,7 @@
networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
params.networkCapabilities);
}
- scheduleUnvalidatedPrompt(networkAgent);
+ scheduleEvaluationTimeout(networkAgent.network, INITIAL_EVALUATION_TIMEOUT_MS);
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
diff --git a/service/src/com/android/server/UidOwnerValue.java b/service/src/com/android/server/UidOwnerValue.java
index f89e354..d6c0e0d 100644
--- a/service/src/com/android/server/UidOwnerValue.java
+++ b/service/src/com/android/server/UidOwnerValue.java
@@ -21,14 +21,14 @@
/** Value type for per uid traffic control configuration map */
public class UidOwnerValue extends Struct {
// Allowed interface index. Only applicable if IIF_MATCH is set in the rule bitmask below.
- @Field(order = 0, type = Type.U32)
- public final long iif;
+ @Field(order = 0, type = Type.S32)
+ public final int iif;
// A bitmask of match type.
@Field(order = 1, type = Type.U32)
public final long rule;
- public UidOwnerValue(final long iif, final long rule) {
+ public UidOwnerValue(final int iif, final long rule) {
this.iif = iif;
this.rule = rule;
}
diff --git a/service/src/com/android/server/connectivity/DscpPolicyValue.java b/service/src/com/android/server/connectivity/DscpPolicyValue.java
index fed96b4..7b11eda 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyValue.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyValue.java
@@ -37,8 +37,8 @@
@Field(order = 1, type = Type.ByteArray, arraysize = 16)
public final byte[] dst46;
- @Field(order = 2, type = Type.U32)
- public final long ifIndex;
+ @Field(order = 2, type = Type.S32)
+ public final int ifIndex;
@Field(order = 3, type = Type.UBE16)
public final int srcPort;
@@ -116,7 +116,7 @@
return mask;
}
- private DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final long ifIndex,
+ private DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final int ifIndex,
final int srcPort, final int dstPortStart, final int dstPortEnd, final short proto,
final byte dscp) {
this.src46 = toAddressField(src46);
@@ -136,7 +136,7 @@
this.mask = makeMask(this.src46, this.dst46, srcPort, dstPortStart, proto, dscp);
}
- public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final long ifIndex,
+ public DscpPolicyValue(final InetAddress src46, final InetAddress dst46, final int ifIndex,
final int srcPort, final Range<Integer> dstPort, final short proto,
final byte dscp) {
this(src46, dst46, ifIndex, srcPort, dstPort != null ? dstPort.getLower() : -1,
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index 22a820b..aec4a71 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -85,13 +85,23 @@
/** @hide */
public static final int POLICY_IS_INVINCIBLE = 56;
+ // 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
// until a timeout expires, the agent disconnects, or the network no longer satisfies requests.
// 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 = 55;
+ public static final int POLICY_IS_DESTROYED = 54;
// To help iterate when printing
@VisibleForTesting
@@ -144,6 +154,7 @@
* @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.
*/
@@ -153,7 +164,7 @@
public static FullScore fromNetworkScore(@NonNull final NetworkScore score,
@NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config,
final boolean everValidated, final boolean avoidUnvalidated,
- final boolean yieldToBadWiFi, final boolean destroyed) {
+ final boolean yieldToBadWiFi, final boolean everEvaluated, final boolean destroyed) {
return withPolicies(score.getPolicies(),
score.getKeepConnectedReason(),
caps.hasCapability(NET_CAPABILITY_VALIDATED),
@@ -164,6 +175,7 @@
caps.hasCapability(NET_CAPABILITY_NOT_METERED),
yieldToBadWiFi,
false /* invincible */, // only prospective scores can be invincible
+ everEvaluated,
destroyed);
}
@@ -198,15 +210,17 @@
// Prospective scores are always unmetered, because unmetered networks are stronger
// than metered networks, and it's not known in advance whether the network is metered.
final boolean unmetered = true;
- // A network can only be destroyed once it has connected.
- final boolean destroyed = false;
// A prospective score is invincible if the legacy int in the filter is over the maximum
// score.
final boolean invincible = score.getLegacyInt() > NetworkRanker.LEGACY_INT_MAX;
+ // 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, everValidated, vpn, everUserSelected, acceptUnvalidated,
- avoidUnvalidated, unmetered,
- yieldToBadWiFi, invincible, destroyed);
+ mayValidate, everValidated, vpn, everUserSelected,
+ acceptUnvalidated, avoidUnvalidated, unmetered, yieldToBadWiFi,
+ invincible, everEvaluated, destroyed);
}
/**
@@ -224,6 +238,7 @@
final boolean everValidated,
final boolean avoidUnvalidated,
final boolean yieldToBadWifi,
+ final boolean everEvaluated,
final boolean destroyed) {
return withPolicies(mPolicies, mKeepConnectedReason,
caps.hasCapability(NET_CAPABILITY_VALIDATED),
@@ -234,6 +249,7 @@
caps.hasCapability(NET_CAPABILITY_NOT_METERED),
yieldToBadWifi,
false /* invincible */, // only prospective scores can be invincible
+ everEvaluated,
destroyed);
}
@@ -251,6 +267,7 @@
final boolean isUnmetered,
final boolean yieldToBadWiFi,
final boolean invincible,
+ final boolean everEvaluated,
final boolean destroyed) {
return new FullScore((externalPolicies & EXTERNAL_POLICIES_MASK)
| (isValidated ? 1L << POLICY_IS_VALIDATED : 0)
@@ -262,6 +279,7 @@
| (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0)
| (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 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/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index d7be421..c732170 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -61,6 +61,7 @@
import android.util.Pair;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.WakeupMessage;
import com.android.modules.utils.build.SdkLevel;
@@ -377,6 +378,34 @@
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;
+ }
+
+ /** When this network ever concluded its first evaluation, or 0 if this never happened. */
+ @VisibleForTesting
+ public long getFirstEvaluationConcludedTime() {
+ return mFirstEvaluationConcludedTime;
+ }
+
// Delay between when the network is disconnected and when the native network is destroyed.
public int teardownDelayMs;
@@ -975,7 +1004,8 @@
final NetworkCapabilities oldNc = networkCapabilities;
networkCapabilities = nc;
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, everValidated(),
- 0L != getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed());
+ 0L != getAvoidUnvalidated(), yieldToBadWiFi(),
+ 0L != mFirstEvaluationConcludedTime, isDestroyed());
final NetworkMonitorManager nm = mNetworkMonitor;
if (nm != null) {
nm.notifyNetworkCapabilitiesChanged(nc);
@@ -1178,7 +1208,8 @@
*/
public void setScore(final NetworkScore score) {
mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
- everValidated(), 0L == getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed());
+ everValidated(), 0L == getAvoidUnvalidated(), yieldToBadWiFi(),
+ 0L != mFirstEvaluationConcludedTime, isDestroyed());
}
/**
@@ -1188,7 +1219,8 @@
*/
public void updateScoreForNetworkAgentUpdate() {
mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig,
- everValidated(), 0L != getAvoidUnvalidated(), yieldToBadWiFi(), isDestroyed());
+ everValidated(), 0L != getAvoidUnvalidated(), yieldToBadWiFi(),
+ 0L != mFirstEvaluationConcludedTime, isDestroyed());
}
/**
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/tests/common/java/android/net/metrics/IpConnectivityLogTest.java b/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
index ab97f2d..93cf748 100644
--- a/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
+++ b/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
@@ -23,7 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.net.ConnectivityMetricsEvent;
@@ -51,6 +51,7 @@
private static final int FAKE_NET_ID = 100;
private static final int[] FAKE_TRANSPORT_TYPES = unpackBits(TRANSPORT_WIFI);
private static final long FAKE_TIME_STAMP = System.currentTimeMillis();
+ private static final long THREAD_TIMEOUT_MS = 10_000L;
private static final String FAKE_INTERFACE_NAME = "test";
private static final IpReachabilityEvent FAKE_EV =
new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
@@ -93,22 +94,26 @@
final int nCallers = 10;
final int nEvents = 10;
+ final Thread[] threads = new Thread[nCallers];
for (int n = 0; n < nCallers; n++) {
final int i = n;
- new Thread() {
- public void run() {
- for (int j = 0; j < nEvents; j++) {
- assertTrue(logger.log(makeExpectedEvent(
- FAKE_TIME_STAMP + i * 100 + j,
- FAKE_NET_ID + i * 100 + j,
- ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR,
- FAKE_INTERFACE_NAME)));
- }
+ threads[i] = new Thread(() -> {
+ for (int j = 0; j < nEvents; j++) {
+ assertTrue(logger.log(makeExpectedEvent(
+ FAKE_TIME_STAMP + i * 100 + j,
+ FAKE_NET_ID + i * 100 + j,
+ ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR,
+ FAKE_INTERFACE_NAME)));
}
- }.start();
+ });
+ threads[i].start();
+ }
+ // To ensure the events have been sent out on each thread. Wait for the thread to die.
+ for (Thread thread : threads) {
+ thread.join(THREAD_TIMEOUT_MS);
}
- List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
+ final List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents);
Collections.sort(got, EVENT_COMPARATOR);
Iterator<ConnectivityMetricsEvent> iter = got.iterator();
for (int i = 0; i < nCallers; i++) {
@@ -123,17 +128,13 @@
}
}
- private List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
+ private List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
ArgumentCaptor<ConnectivityMetricsEvent> captor =
ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
- verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
+ verify(mMockService, times(n)).logEvent(captor.capture());
return captor.getAllValues();
}
- private List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
- return verifyEvents(n, 10);
- }
-
private ConnectivityMetricsEvent makeExpectedEvent(long timestamp, int netId, long transports,
String ifname) {
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index 6b5bb93..25490da 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -35,6 +35,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<!-- TODO (b/186093901): remove after fixing resource querying -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.cpp b/tests/cts/net/jni/NativeMultinetworkJni.cpp
index 60e31bc..6610d10 100644
--- a/tests/cts/net/jni/NativeMultinetworkJni.cpp
+++ b/tests/cts/net/jni/NativeMultinetworkJni.cpp
@@ -40,8 +40,12 @@
#define LOGD(fmt, ...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
-#define EXPECT_GE(env, actual, expected, msg) \
+// Since the tests in this file commonly pass expression statements as parameters to these macros,
+// get the returned value of the statements to avoid statement double-called.
+#define EXPECT_GE(env, actual_stmt, expected_stmt, msg) \
do { \
+ const auto expected = (expected_stmt); \
+ const auto actual = (actual_stmt); \
if (actual < expected) { \
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
"%s:%d: %s EXPECT_GE: expected %d, got %d", \
@@ -49,8 +53,10 @@
} \
} while (0)
-#define EXPECT_GT(env, actual, expected, msg) \
+#define EXPECT_GT(env, actual_stmt, expected_stmt, msg) \
do { \
+ const auto expected = (expected_stmt); \
+ const auto actual = (actual_stmt); \
if (actual <= expected) { \
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
"%s:%d: %s EXPECT_GT: expected %d, got %d", \
@@ -58,8 +64,10 @@
} \
} while (0)
-#define EXPECT_EQ(env, expected, actual, msg) \
+#define EXPECT_EQ(env, expected_stmt, actual_stmt, msg) \
do { \
+ const auto expected = (expected_stmt); \
+ const auto actual = (actual_stmt); \
if (actual != expected) { \
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
"%s:%d: %s EXPECT_EQ: expected %d, got %d", \
@@ -110,6 +118,18 @@
// If there is no valid answer, test will fail.
continue;
}
+
+ const int rtype = ns_rr_type(rr);
+ if (family == AF_INET) {
+ // If there is no expected address type, test will fail.
+ if (rtype != ns_t_a) continue;
+ } else if (family == AF_INET6) {
+ // If there is no expected address type, test will fail.
+ if (rtype != ns_t_aaaa) continue;
+ } else {
+ return -EAFNOSUPPORT;
+ }
+
const uint8_t* rdata = ns_rr_rdata(rr);
char buffer[INET6_ADDRSTRLEN];
if (inet_ntop(family, (const char*) rdata, buffer, sizeof(buffer)) == NULL) {
@@ -161,7 +181,7 @@
// V6
fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_aaaa, 0);
EXPECT_GE(env, fd, 0, "v6 res_nquery");
- EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET6, ns_r_noerror),
"v6 res_nquery check answers");
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index cece4df..8dbcc00 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -165,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;
@@ -353,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(",");
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/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index eb5d2ef..4966aed 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -66,6 +66,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U32;
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
@@ -102,8 +103,8 @@
private static final int NO_IIF = 0;
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 S32 UID_RULES_CONFIGURATION_KEY = new S32(0);
+ private static final S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new S32(1);
private static final List<Integer> FIREWALL_CHAINS = List.of(
FIREWALL_CHAIN_DOZABLE,
FIREWALL_CHAIN_STANDBY,
@@ -123,10 +124,10 @@
@Mock INetd mNetd;
@Mock BpfNetMaps.Dependencies mDeps;
@Mock Context mContext;
- 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<S32, U32> mConfigurationMap = new TestBpfMap<>(S32.class, U32.class);
+ private final IBpfMap<S32, UidOwnerValue> mUidOwnerMap =
+ new TestBpfMap<>(S32.class, UidOwnerValue.class);
+ private final IBpfMap<S32, U8> mUidPermissionMap = new TestBpfMap<>(S32.class, U8.class);
private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap =
spy(new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class));
@@ -292,9 +293,9 @@
() -> mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true /* enable */));
}
- private void checkUidOwnerValue(final long uid, final long expectedIif,
+ private void checkUidOwnerValue(final int uid, final int expectedIif,
final long expectedMatch) throws Exception {
- final UidOwnerValue config = mUidOwnerMap.getValue(new U32(uid));
+ final UidOwnerValue config = mUidOwnerMap.getValue(new S32(uid));
if (expectedMatch == 0) {
assertNull(config);
} else {
@@ -303,8 +304,8 @@
}
}
- private void doTestRemoveNaughtyApp(final long iif, final long match) throws Exception {
- mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+ private void doTestRemoveNaughtyApp(final int iif, final long match) throws Exception {
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
mBpfNetMaps.removeNaughtyApp(TEST_UID);
@@ -341,9 +342,9 @@
() -> mBpfNetMaps.removeNaughtyApp(TEST_UID));
}
- private void doTestAddNaughtyApp(final long iif, final long match) throws Exception {
+ private void doTestAddNaughtyApp(final int iif, final long match) throws Exception {
if (match != NO_MATCH) {
- mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
}
mBpfNetMaps.addNaughtyApp(TEST_UID);
@@ -373,8 +374,8 @@
() -> mBpfNetMaps.addNaughtyApp(TEST_UID));
}
- private void doTestRemoveNiceApp(final long iif, final long match) throws Exception {
- mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+ private void doTestRemoveNiceApp(final int iif, final long match) throws Exception {
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
mBpfNetMaps.removeNiceApp(TEST_UID);
@@ -411,9 +412,9 @@
() -> mBpfNetMaps.removeNiceApp(TEST_UID));
}
- private void doTestAddNiceApp(final long iif, final long match) throws Exception {
+ private void doTestAddNiceApp(final int iif, final long match) throws Exception {
if (match != NO_MATCH) {
- mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
}
mBpfNetMaps.addNiceApp(TEST_UID);
@@ -443,10 +444,10 @@
() -> mBpfNetMaps.addNiceApp(TEST_UID));
}
- private void doTestUpdateUidLockdownRule(final long iif, final long match, final boolean add)
+ private void doTestUpdateUidLockdownRule(final int iif, final long match, final boolean add)
throws Exception {
if (match != NO_MATCH) {
- mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
}
mBpfNetMaps.updateUidLockdownRule(TEST_UID, add);
@@ -516,8 +517,8 @@
final int uid1 = TEST_UIDS[1];
final long match0 = DOZABLE_MATCH;
final long match1 = DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH;
- mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(NO_IIF, match0));
- mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NO_IIF, match1));
+ mUidOwnerMap.updateEntry(new S32(uid0), new UidOwnerValue(NO_IIF, match0));
+ mUidOwnerMap.updateEntry(new S32(uid1), new UidOwnerValue(NO_IIF, match1));
mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
@@ -532,8 +533,8 @@
final int uid1 = TEST_UIDS[1];
final long match0 = IIF_MATCH;
final long match1 = IIF_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH;
- mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(TEST_IF_INDEX + 1, match0));
- mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NULL_IIF, match1));
+ mUidOwnerMap.updateEntry(new S32(uid0), new UidOwnerValue(TEST_IF_INDEX + 1, match0));
+ mUidOwnerMap.updateEntry(new S32(uid1), new UidOwnerValue(NULL_IIF, match1));
mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
@@ -556,8 +557,8 @@
final int uid1 = TEST_UIDS[1];
final long match0 = IIF_MATCH;
final long match1 = IIF_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH;
- mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(TEST_IF_INDEX, match0));
- mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NULL_IIF, match1));
+ mUidOwnerMap.updateEntry(new S32(uid0), new UidOwnerValue(TEST_IF_INDEX, match0));
+ mUidOwnerMap.updateEntry(new S32(uid1), new UidOwnerValue(NULL_IIF, match1));
mBpfNetMaps.addUidInterfaceRules(null /* ifName */, TEST_UIDS);
@@ -565,12 +566,12 @@
checkUidOwnerValue(uid1, NULL_IIF, match1);
}
- private void doTestRemoveUidInterfaceRules(final long iif0, final long match0,
- final long iif1, final long match1) throws Exception {
+ private void doTestRemoveUidInterfaceRules(final int iif0, final long match0,
+ final int iif1, final long match1) throws Exception {
final int uid0 = TEST_UIDS[0];
final int uid1 = TEST_UIDS[1];
- mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(iif0, match0));
- mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(iif1, match1));
+ mUidOwnerMap.updateEntry(new S32(uid0), new UidOwnerValue(iif0, match0));
+ mUidOwnerMap.updateEntry(new S32(uid1), new UidOwnerValue(iif1, match1));
mBpfNetMaps.removeUidInterfaceRules(TEST_UIDS);
@@ -593,7 +594,7 @@
}
private void doTestSetUidRule(final List<Integer> testChains) throws Exception {
- mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(TEST_IF_INDEX, IIF_MATCH));
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(TEST_IF_INDEX, IIF_MATCH));
for (final int chain: testChains) {
final int ruleToAddMatch = mBpfNetMaps.isFirewallAllowList(chain)
@@ -697,8 +698,8 @@
final int uid1 = TEST_UIDS[1];
final long match0 = POWERSAVE_MATCH;
final long match1 = POWERSAVE_MATCH | RESTRICTED_MATCH;
- mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(NO_IIF, match0));
- mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NO_IIF, match1));
+ mUidOwnerMap.updateEntry(new S32(uid0), new UidOwnerValue(NO_IIF, match0));
+ mUidOwnerMap.updateEntry(new S32(uid1), new UidOwnerValue(NO_IIF, match1));
mBpfNetMaps.replaceUidChain(FIREWALL_CHAIN_DOZABLE, new int[]{uid1});
@@ -713,8 +714,8 @@
final int uid1 = TEST_UIDS[1];
final long match0 = IIF_MATCH;
final long match1 = IIF_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH;
- mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(TEST_IF_INDEX, match0));
- mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NULL_IIF, match1));
+ mUidOwnerMap.updateEntry(new S32(uid0), new UidOwnerValue(TEST_IF_INDEX, match0));
+ mUidOwnerMap.updateEntry(new S32(uid1), new UidOwnerValue(NULL_IIF, match1));
mBpfNetMaps.replaceUidChain(FIREWALL_CHAIN_DOZABLE, TEST_UIDS);
@@ -729,8 +730,8 @@
final int uid1 = TEST_UIDS[1];
final long match0 = IIF_MATCH | DOZABLE_MATCH;
final long match1 = IIF_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH;
- mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(TEST_IF_INDEX, match0));
- mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NULL_IIF, match1));
+ mUidOwnerMap.updateEntry(new S32(uid0), new UidOwnerValue(TEST_IF_INDEX, match0));
+ mUidOwnerMap.updateEntry(new S32(uid1), new UidOwnerValue(NULL_IIF, match1));
mBpfNetMaps.replaceUidChain(FIREWALL_CHAIN_DOZABLE, new int[]{uid1});
@@ -768,8 +769,8 @@
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);
+ assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new S32(uid1)).val);
}
@Test
@@ -780,8 +781,8 @@
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);
+ assertEquals(permission, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new S32(uid1)).val);
}
@Test
@@ -792,8 +793,8 @@
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)));
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertNull(mUidPermissionMap.getValue(new S32(uid1)));
}
@Test
@@ -804,8 +805,8 @@
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);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new S32(uid1)).val);
}
@Test
@@ -817,8 +818,8 @@
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);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new S32(uid1)).val);
}
@Test
@@ -830,8 +831,8 @@
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);
+ assertNull(mUidPermissionMap.getValue(new S32(uid0)));
+ assertEquals(permission, mUidPermissionMap.getValue(new S32(uid1)).val);
}
@Test
@@ -842,28 +843,28 @@
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);
+ assertEquals(permission, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new S32(uid1)).val);
mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
- assertEquals(permission, mUidPermissionMap.getValue(new U32(uid0)).val);
- assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertEquals(permission, mUidPermissionMap.getValue(new S32(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);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new S32(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);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new S32(uid0)).val);
+ assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new S32(uid1)).val);
mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED, TEST_UIDS);
- assertNull(mUidPermissionMap.getValue(new U32(uid0)));
- assertNull(mUidPermissionMap.getValue(new U32(uid1)));
+ assertNull(mUidPermissionMap.getValue(new S32(uid0)));
+ assertNull(mUidPermissionMap.getValue(new S32(uid1)));
mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED, TEST_UIDS);
- assertNull(mUidPermissionMap.getValue(new U32(uid0)));
- assertNull(mUidPermissionMap.getValue(new U32(uid1)));
+ assertNull(mUidPermissionMap.getValue(new S32(uid0)));
+ assertNull(mUidPermissionMap.getValue(new S32(uid1)));
}
@Test
@@ -898,13 +899,13 @@
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));
+ mUidOwnerMap.updateEntry(new S32(0), new UidOwnerValue(0, 0));
+ mUidOwnerMap.updateEntry(new S32(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));
+ mUidPermissionMap.updateEntry(new S32(0), new U8((short) 0));
+ mUidPermissionMap.updateEntry(new S32(1), new U8((short) 0));
+ mUidPermissionMap.updateEntry(new S32(2), new U8((short) 0));
final int ret = mBpfNetMaps.pullBpfMapInfoAtom(NETWORK_BPF_MAP_INFO, new ArrayList<>());
assertEquals(StatsManager.PULL_SUCCESS, ret);
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 85aec56..d2a7135 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -365,9 +365,9 @@
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;
@@ -1845,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;
}
@@ -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.scheduleEvaluationTimeout(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.scheduleEvaluationTimeout(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();
@@ -3730,6 +3784,63 @@
callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
}
+ private void doTestFirstEvaluation(
+ @NonNull final Consumer<TestNetworkAgentWrapper> doConnect,
+ final boolean waitForSecondCaps,
+ final boolean evaluatedByValidation)
+ throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ doConnect.accept(mWiFiNetworkAgent);
+ // Expect the available callbacks, but don't require specific values for their arguments
+ // since this method doesn't know how the network was connected.
+ callback.expectCallback(CallbackEntry.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackEntry.BLOCKED_STATUS, mWiFiNetworkAgent);
+ if (waitForSecondCaps) {
+ // This is necessary because of b/245893397, the same bug that happens where we use
+ // expectAvailableDoubleValidatedCallbacks.
+ callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
+ }
+ final NetworkAgentInfo nai =
+ mService.getNetworkAgentInfoForNetwork(mWiFiNetworkAgent.getNetwork());
+ final long firstEvaluation = nai.getFirstEvaluationConcludedTime();
+ if (evaluatedByValidation) {
+ assertNotEquals(0L, firstEvaluation);
+ } else {
+ assertEquals(0L, firstEvaluation);
+ }
+ mService.scheduleEvaluationTimeout(mWiFiNetworkAgent.getNetwork(), 0L /* timeout */);
+ waitForIdle();
+ if (evaluatedByValidation) {
+ assertEquals(firstEvaluation, nai.getFirstEvaluationConcludedTime());
+ } else {
+ assertNotEquals(0L, nai.getFirstEvaluationConcludedTime());
+ }
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+
+ mCm.unregisterNetworkCallback(callback);
+ }
+
+ @Test
+ public void testEverEvaluated() throws Exception {
+ doTestFirstEvaluation(naw -> naw.connect(true /* validated */),
+ true /* waitForSecondCaps */, true /* immediatelyEvaluated */);
+ doTestFirstEvaluation(naw -> naw.connectWithPartialConnectivity(),
+ true /* waitForSecondCaps */, true /* immediatelyEvaluated */);
+ doTestFirstEvaluation(naw -> naw.connectWithCaptivePortal(TEST_REDIRECT_URL, false),
+ true /* waitForSecondCaps */, true /* immediatelyEvaluated */);
+ doTestFirstEvaluation(naw -> naw.connect(false /* validated */),
+ false /* waitForSecondCaps */, false /* immediatelyEvaluated */);
+ }
+
private void tryNetworkFactoryRequests(int capability) throws Exception {
// Verify NOT_RESTRICTED is set appropriately
final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability)
@@ -4088,6 +4199,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 +4217,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 +4226,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 +4240,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 +4292,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 +4318,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
@@ -16494,19 +16627,21 @@
assertThrows(NullPointerException.class, () -> mService.unofferNetwork(null));
}
- @Test
- public void testIgnoreValidationAfterRoamDisabled() throws Exception {
+ public void doTestIgnoreValidationAfterRoam(final boolean enabled) throws Exception {
assumeFalse(SdkLevel.isAtLeastT());
- // testIgnoreValidationAfterRoam off
- doReturn(-1).when(mResources)
+ doReturn(enabled ? 5000 : -1).when(mResources)
.getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
NetworkCapabilities wifiNc1 = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.addTransportType(TRANSPORT_WIFI)
.setTransportInfo(new WifiInfo.Builder().setBssid("AA:AA:AA:AA:AA:AA").build());
NetworkCapabilities wifiNc2 = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.addTransportType(TRANSPORT_WIFI)
.setTransportInfo(new WifiInfo.Builder().setBssid("BB:BB:BB:BB:BB:BB").build());
final LinkProperties wifiLp = new LinkProperties();
@@ -16518,51 +16653,74 @@
final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
final NetworkRequest wifiRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI).build();
- mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+ mCm.requestNetwork(wifiRequest, wifiNetworkCallback);
wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
registerDefaultNetworkCallbacks();
mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
- // Wi-Fi roaming from wifiNc1 to wifiNc2.
+ // There is a bug in the current code where ignoring validation after roam will not
+ // correctly change the default network if the result if the validation is partial or
+ // captive portal. TODO : fix the bug and reinstate this code.
+ if (false) {
+ // Wi-Fi roaming from wifiNc1 to wifiNc2 but the network is now behind a captive portal.
+ mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true /* sendToConnectivityService */);
+ // The only thing changed in this CAPS is the BSSID, which can't be tested for in this
+ // test because it's redacted.
+ wifiNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED,
+ mWiFiNetworkAgent);
+ mDefaultNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED,
+ mWiFiNetworkAgent);
+ mWiFiNetworkAgent.setNetworkPortal(TEST_REDIRECT_URL, false /* isStrictMode */);
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+ // Wi-Fi is now detected to have a portal : cell should become the default network.
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED,
+ mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_CAPTIVE_PORTAL,
+ mWiFiNetworkAgent);
+
+ // Wi-Fi becomes valid again. The default network goes back to Wi-Fi.
+ mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_CAPTIVE_PORTAL,
+ mWiFiNetworkAgent);
+
+ // Wi-Fi roaming from wifiNc2 to wifiNc1, and the network now has partial connectivity.
+ mWiFiNetworkAgent.setNetworkCapabilities(wifiNc1, true);
+ wifiNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED,
+ mWiFiNetworkAgent);
+ mDefaultNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED,
+ mWiFiNetworkAgent);
+ mWiFiNetworkAgent.setNetworkPartial();
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+ // Wi-Fi now only offers partial connectivity, so in the absence of accepting partial
+ // connectivity explicitly for this network, it loses default status to cell.
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ mWiFiNetworkAgent);
+
+ // Wi-Fi becomes valid again. The default network goes back to Wi-Fi.
+ mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+ wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ mWiFiNetworkAgent);
+ }
+
+ // Wi-Fi roams from wifiNc1 to wifiNc2, and now becomes really invalid. If validation
+ // failures after roam are not ignored, this will cause cell to become the default network.
+ // If they are ignored, this will not cause a switch until later.
mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true);
- mWiFiNetworkAgent.setNetworkInvalid(false);
+ mDefaultNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED,
+ mWiFiNetworkAgent);
+ mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
- mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
- }
- @Test
- public void testIgnoreValidationAfterRoamEnabled() throws Exception {
- assumeFalse(SdkLevel.isAtLeastT());
- // testIgnoreValidationAfterRoam on
- doReturn(5000).when(mResources)
- .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);
-
- mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
- mCellNetworkAgent.connect(true);
- NetworkCapabilities wifiNc1 = new NetworkCapabilities()
- .addTransportType(TRANSPORT_WIFI)
- .setTransportInfo(new WifiInfo.Builder().setBssid("AA:AA:AA:AA:AA:AA").build());
- NetworkCapabilities wifiNc2 = new NetworkCapabilities()
- .addTransportType(TRANSPORT_WIFI)
- .setTransportInfo(new WifiInfo.Builder().setBssid("BB:BB:BB:BB:BB:BB").build());
- final LinkProperties wifiLp = new LinkProperties();
- wifiLp.setInterfaceName(WIFI_IFNAME);
- mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1);
- mWiFiNetworkAgent.connect(true);
-
- // The default network will be switching to Wi-Fi Network.
- final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
- final NetworkRequest wifiRequest = new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_WIFI).build();
- mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
- wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
- registerDefaultNetworkCallbacks();
- mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-
- // Wi-Fi roaming from wifiNc1 to wifiNc2.
- mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true);
- mWiFiNetworkAgent.setNetworkInvalid(false);
- mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+ if (!enabled) {
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ return;
+ }
// Network validation failed, but the result will be ignored.
assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
@@ -16579,6 +16737,17 @@
waitForValidationBlock.block(150);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+ mCm.unregisterNetworkCallback(wifiNetworkCallback);
+ }
+
+ @Test
+ public void testIgnoreValidationAfterRoamDisabled() throws Exception {
+ doTestIgnoreValidationAfterRoam(false /* enabled */);
+ }
+ @Test
+ public void testIgnoreValidationAfterRoamEnabled() throws Exception {
+ doTestIgnoreValidationAfterRoam(true /* enabled */);
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index c8a62be..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 {
@@ -67,7 +69,7 @@
if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}.build()
return mixInScore(nc, nac, validated, false /* avoidUnvalidated */,
- false /* yieldToBadWifi */, destroyed)
+ false /* yieldToBadWifi */, everEvaluated, destroyed)
}
private val TAG = this::class.simpleName
@@ -123,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/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 1fda04e..795eb47 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -1434,7 +1434,7 @@
final ArgumentCaptor<NetworkCallback> networkCallbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
- .requestNetwork(any(), networkCallbackCaptor.capture());
+ .registerSystemDefaultNetworkCallback(networkCallbackCaptor.capture(), any());
// onAvailable() will trigger onDefaultNetworkChanged(), so NetdUtils#setInterfaceUp will be
// invoked. Set the return value of INetd#interfaceGetCfg to prevent NullPointerException.
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
index c6852d1..c730856 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,15 +34,18 @@
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;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
-import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.S32;
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,13 +69,14 @@
private final TestLooper mLooper = new TestLooper();
private BaseNetdUnsolicitedEventListener mListener;
private BpfInterfaceMapUpdater mUpdater;
- @Mock private IBpfMap<U32, InterfaceMapValue> mBpfMap;
+ private IBpfMap<S32, InterfaceMapValue> mBpfMap =
+ spy(new TestBpfMap<>(S32.class, InterfaceMapValue.class));
@Mock private INetd mNetd;
@Mock private Context mContext;
private class TestDependencies extends BpfInterfaceMapUpdater.Dependencies {
@Override
- public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
+ public IBpfMap<S32, InterfaceMapValue> getInterfaceMap() {
return mBpfMap;
}
@@ -100,7 +114,7 @@
ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener.class);
verify(mNetd).registerUnsolicitedEventListener(listenerCaptor.capture());
mListener = listenerCaptor.getValue();
- verify(mBpfMap).updateEntry(eq(new U32(TEST_INDEX)),
+ verify(mBpfMap).updateEntry(eq(new S32(TEST_INDEX)),
eq(new InterfaceMapValue(TEST_INTERFACE_NAME)));
}
@@ -110,7 +124,7 @@
mListener.onInterfaceAdded(TEST_INTERFACE_NAME2);
mLooper.dispatchAll();
- verify(mBpfMap).updateEntry(eq(new U32(TEST_INDEX2)),
+ verify(mBpfMap).updateEntry(eq(new S32(TEST_INDEX2)),
eq(new InterfaceMapValue(TEST_INTERFACE_NAME2)));
// Check that when onInterfaceRemoved is called, nothing happens.
@@ -118,4 +132,43 @@
mLooper.dispatchAll();
verifyNoMoreInteractions(mBpfMap);
}
+
+ @Test
+ public void testGetIfNameByIndex() throws Exception {
+ mBpfMap.updateEntry(new S32(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 S32(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 S32(TEST_INDEX), new InterfaceMapValue(TEST_INTERFACE_NAME));
+ mBpfMap.updateEntry(new S32(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/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index e27e4e2..6448819 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -139,7 +139,7 @@
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.S32;
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
@@ -249,7 +249,7 @@
private HandlerThread mHandlerThread;
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
- private TestBpfMap<U32, U8> mUidCounterSetMap = spy(new TestBpfMap<>(U32.class, U8.class));
+ private TestBpfMap<S32, U8> mUidCounterSetMap = spy(new TestBpfMap<>(S32.class, U8.class));
@Mock
private BpfNetMaps mBpfNetMaps;
@Mock
@@ -478,7 +478,7 @@
}
@Override
- public IBpfMap<U32, U8> getUidCounterSetMap() {
+ public IBpfMap<S32, U8> getUidCounterSetMap() {
return mUidCounterSetMap;
}
@@ -646,7 +646,7 @@
mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
mService.noteUidForeground(UID_RED, true);
verify(mUidCounterSetMap).updateEntry(
- eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ eq(new S32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
forcePollAndWaitForIdle();
@@ -1311,7 +1311,7 @@
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
mService.noteUidForeground(UID_RED, true);
verify(mUidCounterSetMap).updateEntry(
- eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ eq(new S32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
forcePollAndWaitForIdle();
@@ -1927,7 +1927,7 @@
mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
mService.noteUidForeground(UID_RED, true);
verify(mUidCounterSetMap).updateEntry(
- eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ eq(new S32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
forcePollAndWaitForIdle();
@@ -2424,13 +2424,13 @@
mAppUidStatsMap.insertEntry(new UidStatsMapKey(uid), new StatsMapValue(10, 10000, 6, 6000));
- mUidCounterSetMap.insertEntry(new U32(uid), new U8((short) 1));
+ mUidCounterSetMap.insertEntry(new S32(uid), new U8((short) 1));
assertTrue(cookieTagMapContainsUid(uid));
assertTrue(statsMapContainsUid(mStatsMapA, uid));
assertTrue(statsMapContainsUid(mStatsMapB, uid));
assertTrue(mAppUidStatsMap.containsKey(new UidStatsMapKey(uid)));
- assertTrue(mUidCounterSetMap.containsKey(new U32(uid)));
+ assertTrue(mUidCounterSetMap.containsKey(new S32(uid)));
}
@Test
@@ -2447,14 +2447,14 @@
assertFalse(statsMapContainsUid(mStatsMapA, UID_BLUE));
assertFalse(statsMapContainsUid(mStatsMapB, UID_BLUE));
assertFalse(mAppUidStatsMap.containsKey(new UidStatsMapKey(UID_BLUE)));
- assertFalse(mUidCounterSetMap.containsKey(new U32(UID_BLUE)));
+ assertFalse(mUidCounterSetMap.containsKey(new S32(UID_BLUE)));
// assert that UID_RED related tag data is still in the maps.
assertTrue(cookieTagMapContainsUid(UID_RED));
assertTrue(statsMapContainsUid(mStatsMapA, UID_RED));
assertTrue(statsMapContainsUid(mStatsMapB, UID_RED));
assertTrue(mAppUidStatsMap.containsKey(new UidStatsMapKey(UID_RED)));
- assertTrue(mUidCounterSetMap.containsKey(new U32(UID_RED)));
+ assertTrue(mUidCounterSetMap.containsKey(new S32(UID_RED)));
}
private void assertDumpContains(final String dump, final String message) {
@@ -2528,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 2ff53fa..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:
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/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