Merge changes Ic6ff7a3d,Iff9b9792
* changes:
Refactor code and improve tests for VPN filtering
Remove LOCKDOWN from FirewallChain IntDef
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index adcc236..19d0d5f 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -179,9 +179,9 @@
certificate: "networkstack",
manifest: "AndroidManifest.xml",
use_embedded_native_libs: true,
- // The permission configuration *must* be included to ensure security of the device
+ // The network stack *must* be included to ensure security of the device
required: [
- "NetworkPermissionConfig",
+ "NetworkStack",
"privapp_allowlist_com.android.tethering",
],
apex_available: ["com.android.tethering"],
@@ -199,9 +199,9 @@
certificate: "networkstack",
manifest: "AndroidManifest.xml",
use_embedded_native_libs: true,
- // The permission configuration *must* be included to ensure security of the device
+ // The network stack *must* be included to ensure security of the device
required: [
- "NetworkPermissionConfig",
+ "NetworkStackNext",
"privapp_allowlist_com.android.tethering",
],
apex_available: ["com.android.tethering"],
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 92be84d..819936d 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -26,9 +26,8 @@
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.RemoteResponder;
-import static android.net.TetheringTester.isIcmpv6Type;
-import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.net.TetheringTester.isExpectedIcmpv6Packet;
+import static android.net.TetheringTester.isExpectedUdpPacket;
import static android.system.OsConstants.IPPROTO_IP;
import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -84,11 +83,7 @@
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.Icmpv6Header;
-import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
-import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -146,6 +141,7 @@
// Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
private static final int TX_UDP_PACKET_SIZE = 44;
private static final int TX_UDP_PACKET_COUNT = 123;
+ private static final long WAIT_RA_TIMEOUT_MS = 2000;
private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
@@ -329,27 +325,13 @@
}
- private static boolean isRouterAdvertisement(byte[] pkt) {
- if (pkt == null) return false;
-
- ByteBuffer buf = ByteBuffer.wrap(pkt);
-
- final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
- if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
-
- final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
- if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
-
- final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
- return icmpv6Hdr.type == (short) ICMPV6_ROUTER_ADVERTISEMENT;
- }
-
- private static void expectRouterAdvertisement(TapPacketReader reader, String iface,
+ private static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
long timeoutMs) {
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
do {
byte[] pkt = reader.popPacket(timeoutMs);
- if (isRouterAdvertisement(pkt)) return;
+ if (isExpectedIcmpv6Packet(pkt, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return;
+
timeoutMs = deadline - SystemClock.uptimeMillis();
} while (timeoutMs > 0);
fail("Did not receive router advertisement on " + iface + " after "
@@ -401,7 +383,7 @@
// before the reader is started.
mDownstreamReader = makePacketReader(mDownstreamIface);
- expectRouterAdvertisement(mDownstreamReader, iface, 2000 /* timeoutMs */);
+ waitForRouterAdvertisement(mDownstreamReader, iface, WAIT_RA_TIMEOUT_MS);
expectLocalOnlyAddresses(iface);
}
@@ -808,61 +790,28 @@
@Test
public void testIcmpv6Echo() throws Exception {
- assumeFalse(mEm.isAvailable());
-
- // MyTetheringEventCallback currently only support await first available upstream. Tethering
- // may select internet network as upstream if test network is not available and not be
- // preferred yet. Create test upstream network before enable tethering.
- mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
- toList(TEST_IP4_DNS, TEST_IP6_DNS));
-
- mDownstreamIface = createTestInterface();
- mEm.setIncludeTestInterfaces(true);
-
- final String iface = mTetheredInterfaceRequester.getInterface();
- assertEquals("TetheredInterfaceCallback for unexpected interface",
- mDownstreamIface.getInterfaceName(), iface);
-
- mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
- mUpstreamTracker.getNetwork());
- assertEquals("onUpstreamChanged for unexpected network", mUpstreamTracker.getNetwork(),
- mTetheringEventCallback.awaitUpstreamChanged());
-
- mDownstreamReader = makePacketReader(mDownstreamIface);
- mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
-
- runPing6Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader));
+ runPing6Test(initTetheringTester(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
+ toList(TEST_IP4_DNS, TEST_IP6_DNS)));
}
- private void runPing6Test(TetheringTester tester, RemoteResponder remote) throws Exception {
- // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let
- // TetheringTester test ipv6 tethering connectivity before testing ipv6.
- // TODO: move to a common place to avoid that every IPv6 test needs to call this function.
- tester.waitForIpv6TetherConnectivityVerified();
-
+ private void runPing6Test(TetheringTester tester) throws Exception {
TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString("1:2:3:4:5:6"),
true /* hasIpv6 */);
Inet6Address remoteIp6Addr = (Inet6Address) parseNumericAddress("2400:222:222::222");
ByteBuffer request = Ipv6Utils.buildEchoRequestPacket(tethered.macAddr,
tethered.routerMacAddr, tethered.ipv6Addr, remoteIp6Addr);
- tester.sendPacket(request);
-
- final byte[] echoRequest = remote.getNextMatchedPacket((p) -> {
+ tester.verifyUpload(request, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isIcmpv6Type(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE);
+ return isExpectedIcmpv6Packet(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE);
});
- assertNotNull("No icmpv6 echo request in upstream", echoRequest);
ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
- remote.sendPacket(reply);
-
- final byte[] echoReply = tester.getNextMatchedPacket((p) -> {
+ tester.verifyDownload(reply, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE);
+ return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE);
});
- assertNotNull("No icmpv6 echo reply in downstream", echoReply);
}
// Test network topology:
@@ -894,28 +843,6 @@
private static final ByteBuffer PAYLOAD3 =
ByteBuffer.wrap(new byte[] { (byte) 0x9a, (byte) 0xbc });
- private boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEther,
- boolean isIpv4, @NonNull final ByteBuffer payload) {
- final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
-
- if (hasEther) {
- if (Struct.parse(EthernetHeader.class, buf) == null) return false;
- }
-
- if (isIpv4) {
- if (Struct.parse(Ipv4Header.class, buf) == null) return false;
- } else {
- if (Struct.parse(Ipv6Header.class, buf) == null) return false;
- }
-
- if (Struct.parse(UdpHeader.class, buf) == null) return false;
-
- if (buf.remaining() != payload.limit()) return false;
-
- return Arrays.equals(Arrays.copyOfRange(buf.array(), buf.position(), buf.limit()),
- payload.array());
- }
-
@NonNull
private ByteBuffer buildUdpPacket(
@Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
@@ -976,10 +903,15 @@
dstPort, payload);
}
- // TODO: remove this verification once upstream connected notification race is fixed.
- // See #runUdp4Test.
- private boolean isIpv4TetherConnectivityVerified(TetheringTester tester,
- RemoteResponder remote, TetheredDevice tethered) throws Exception {
+ // TODO: remove ipv4 verification (is4To6 = false) once upstream connected notification race is
+ // fixed. See #runUdp4Test.
+ //
+ // This function sends a probe packet to downstream interface and exam the result from upstream
+ // interface to make sure ipv4 tethering is ready. Return the entire packet which received from
+ // upstream interface.
+ @NonNull
+ private byte[] probeV4TetheringConnectivity(TetheringTester tester, TetheredDevice tethered,
+ boolean is4To6) throws Exception {
final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
@@ -987,19 +919,22 @@
// Send a UDP packet from client and check the packet can be found on upstream interface.
for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) {
- tester.sendPacket(probePacket);
- byte[] expectedPacket = remote.getNextMatchedPacket(p -> {
+ byte[] expectedPacket = tester.testUpload(probePacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */,
+ // If is4To6 is true, the ipv4 probe packet would be translated to ipv6 by Clat and
+ // would see this translated ipv6 packet in upstream interface.
+ return isExpectedUdpPacket(p, false /* hasEther */, !is4To6 /* isIpv4 */,
TEST_REACHABILITY_PAYLOAD);
});
- if (expectedPacket != null) return true;
+ if (expectedPacket != null) return expectedPacket;
}
- return false;
+
+ fail("Can't verify " + (is4To6 ? "ipv4 to ipv6" : "ipv4") + " tethering connectivity after "
+ + TETHER_REACHABILITY_ATTEMPTS + " attempts");
+ return null;
}
- private void runUdp4Test(TetheringTester tester, RemoteResponder remote, boolean usingBpf)
- throws Exception {
+ private void runUdp4Test(TetheringTester tester, boolean usingBpf) throws Exception {
final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
"1:2:3:4:5:6"), false /* hasIpv6 */);
@@ -1009,14 +944,14 @@
// For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
// from upstream. That can guarantee that the routing is ready. Long term plan is that
// refactors upstream connected notification from async to sync.
- assertTrue(isIpv4TetherConnectivityVerified(tester, remote, tethered));
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
// Send a UDP packet in original direction.
final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr,
tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
PAYLOAD /* payload */);
- tester.verifyUpload(remote, originalPacket, p -> {
+ tester.verifyUpload(originalPacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD);
});
@@ -1026,7 +961,7 @@
final ByteBuffer replyPacket = buildUdpPacket(REMOTE_IP4_ADDR /* srcIp */,
publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */,
PAYLOAD2 /* payload */);
- remote.verifyDownload(tester, replyPacket, p -> {
+ tester.verifyDownload(replyPacket, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
});
@@ -1045,7 +980,7 @@
tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */,
REMOTE_PORT /* dstPort */, PAYLOAD3 /* payload */);
- tester.verifyUpload(remote, originalPacket2, p -> {
+ tester.verifyUpload(originalPacket2, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD3);
});
@@ -1081,7 +1016,7 @@
// Send packets on original direction.
for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
- tester.verifyUpload(remote, originalPacket, p -> {
+ tester.verifyUpload(originalPacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD);
});
@@ -1089,7 +1024,7 @@
// Send packets on reply direction.
for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
- remote.verifyDownload(tester, replyPacket, p -> {
+ tester.verifyDownload(replyPacket, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
});
@@ -1116,8 +1051,8 @@
}
}
- void initializeTethering(List<LinkAddress> upstreamAddresses, List<InetAddress> upstreamDnses)
- throws Exception {
+ private TetheringTester initTetheringTester(List<LinkAddress> upstreamAddresses,
+ List<InetAddress> upstreamDnses) throws Exception {
assumeFalse(mEm.isAvailable());
// MyTetheringEventCallback currently only support await first available upstream. Tethering
@@ -1128,9 +1063,9 @@
mDownstreamIface = createTestInterface();
mEm.setIncludeTestInterfaces(true);
- final String iface = mTetheredInterfaceRequester.getInterface();
+ // Make sure EtherentTracker use "mDownstreamIface" as server mode interface.
assertEquals("TetheredInterfaceCallback for unexpected interface",
- mDownstreamIface.getInterfaceName(), iface);
+ mDownstreamIface.getInterfaceName(), mTetheredInterfaceRequester.getInterface());
mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
mUpstreamTracker.getNetwork());
@@ -1139,13 +1074,22 @@
mDownstreamReader = makePacketReader(mDownstreamIface);
mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
+
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, make
+ // sure tethering already have ipv6 connectivity before testing.
+ if (cm.getLinkProperties(mUpstreamTracker.getNetwork()).hasGlobalIpv6Address()) {
+ waitForRouterAdvertisement(mDownstreamReader, mDownstreamIface.getInterfaceName(),
+ WAIT_RA_TIMEOUT_MS);
+ }
+
+ return new TetheringTester(mDownstreamReader, mUpstreamReader);
}
@Test
@IgnoreAfter(Build.VERSION_CODES.R)
public void testTetherUdpV4UpToR() throws Exception {
- initializeTethering(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS));
- runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
+ runUdp4Test(initTetheringTester(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS)),
false /* usingBpf */);
}
@@ -1180,15 +1124,13 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
public void testTetherUdpV4AfterR() throws Exception {
- initializeTethering(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS));
final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
boolean usingBpf = isUdpOffloadSupportedByKernel(kernelVersion);
if (!usingBpf) {
Log.i(TAG, "testTetherUdpV4AfterR will skip BPF offload test for kernel "
+ kernelVersion);
}
- runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
- usingBpf);
+ runUdp4Test(initTetheringTester(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS)), usingBpf);
}
@Nullable
@@ -1247,33 +1189,16 @@
return null;
}
- @Nullable
- private Inet6Address getClatIpv6Address(TetheringTester tester,
- RemoteResponder remote, TetheredDevice tethered) throws Exception {
- final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
- tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
- TEST_REACHABILITY_PAYLOAD);
-
+ @NonNull
+ private Inet6Address getClatIpv6Address(TetheringTester tester, TetheredDevice tethered)
+ throws Exception {
// Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
// be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
// packet.
- byte[] expectedPacket = null;
- for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) {
- tester.sendPacket(probePacket);
- expectedPacket = remote.getNextMatchedPacket(p -> {
- Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */,
- TEST_REACHABILITY_PAYLOAD);
- });
- if (expectedPacket != null) break;
- }
- if (expectedPacket == null) return null;
+ byte[] expectedPacket = probeV4TetheringConnectivity(tester, tethered, true /* is4To6 */);
// Above has guaranteed that the found packet is an IPv6 packet without ether header.
- final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class,
- ByteBuffer.wrap(expectedPacket));
- return ipv6Header.srcIp;
+ return Struct.parse(Ipv6Header.class, ByteBuffer.wrap(expectedPacket)).srcIp;
}
// Test network topology:
@@ -1290,19 +1215,12 @@
// sending out an IPv4 packet and extracting the source address from CLAT translated IPv6
// packet.
//
- private void runClatUdpTest(TetheringTester tester, RemoteResponder remote)
- throws Exception {
- // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let
- // TetheringTester test ipv6 tethering connectivity before testing ipv6.
- // TODO: move to a common place to avoid that every IPv6 test needs to call this function.
- tester.waitForIpv6TetherConnectivityVerified();
-
+ private void runClatUdpTest(TetheringTester tester) throws Exception {
final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
"1:2:3:4:5:6"), true /* hasIpv6 */);
// Get CLAT IPv6 address.
- final Inet6Address clatAddr6 = getClatIpv6Address(tester, remote, tethered);
- assertNotNull(clatAddr6);
+ final Inet6Address clatAddr6 = getClatIpv6Address(tester, tethered);
// Send an IPv4 UDP packet in original direction.
// IPv4 packet -- CLAT translation --> IPv6 packet
@@ -1310,7 +1228,7 @@
tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
PAYLOAD /* payload */);
- tester.verifyUpload(remote, originalPacket, p -> {
+ tester.verifyUpload(originalPacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */, PAYLOAD);
});
@@ -1320,7 +1238,7 @@
final ByteBuffer replyPacket = buildUdpPacket(REMOTE_NAT64_ADDR /* srcIp */,
clatAddr6 /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */,
PAYLOAD2 /* payload */);
- remote.verifyDownload(tester, replyPacket, p -> {
+ tester.verifyDownload(replyPacket, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
});
@@ -1332,9 +1250,7 @@
@IgnoreUpTo(Build.VERSION_CODES.R)
public void testTetherClatUdp() throws Exception {
// CLAT only starts on IPv6 only network.
- initializeTethering(toList(TEST_IP6_ADDR), toList(TEST_IP6_DNS));
- runClatUdpTest(new TetheringTester(mDownstreamReader),
- new RemoteResponder(mUpstreamReader));
+ runClatUdpTest(initTetheringTester(toList(TEST_IP6_ADDR), toList(TEST_IP6_DNS)));
}
private <T> List<T> toList(T... array) {
diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java
index 458680a..4d90d39 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/src/android/net/TetheringTester.java
@@ -18,11 +18,13 @@
import static android.net.InetAddresses.parseNumericAddress;
import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+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_ND_OPTION_PIO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
@@ -42,17 +44,20 @@
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.LlaOption;
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.UdpHeader;
import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
@@ -61,6 +66,7 @@
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeoutException;
@@ -88,11 +94,17 @@
private final ArrayMap<MacAddress, TetheredDevice> mTetheredDevices;
private final TapPacketReader mDownstreamReader;
+ private final TapPacketReader mUpstreamReader;
public TetheringTester(TapPacketReader downstream) {
+ this(downstream, null);
+ }
+
+ public TetheringTester(TapPacketReader downstream, TapPacketReader upstream) {
if (downstream == null) fail("Downstream reader could not be NULL");
mDownstreamReader = downstream;
+ mUpstreamReader = upstream;
mTetheredDevices = new ArrayMap<>();
}
@@ -170,7 +182,7 @@
}
private DhcpPacket getNextDhcpPacket() throws Exception {
- final byte[] packet = getNextMatchedPacket((p) -> {
+ final byte[] packet = getDownloadPacket((p) -> {
// Test whether this is DHCP packet.
try {
DhcpPacket.decodeFullPacket(p, p.length, DhcpPacket.ENCAP_L2);
@@ -213,7 +225,7 @@
tethered.ipv4Addr.getAddress() /* sender IP */,
(short) ARP_REPLY);
try {
- sendPacket(arpReply);
+ sendUploadPacket(arpReply);
} catch (Exception e) {
fail("Failed to reply ARP for " + tethered.ipv4Addr);
}
@@ -227,9 +239,9 @@
tetherMac.toByteArray() /* srcMac */, routerIp.getAddress() /* target IP */,
new byte[ETHER_ADDR_LEN] /* target HW address */,
tetherIp.getAddress() /* sender IP */, (short) ARP_REQUEST);
- sendPacket(arpProbe);
+ sendUploadPacket(arpProbe);
- final byte[] packet = getNextMatchedPacket((p) -> {
+ final byte[] packet = getDownloadPacket((p) -> {
final ArpPacket arpPacket = parseArpPacket(p);
if (arpPacket == null || arpPacket.opCode != ARP_REPLY) return false;
return arpPacket.targetIp.equals(tetherIp);
@@ -245,25 +257,11 @@
return null;
}
- public void waitForIpv6TetherConnectivityVerified() throws Exception {
- Log.d(TAG, "Waiting RA multicast");
-
- // Wait for RA multicast message from router to confirm that the IPv6 tethering
- // connectivity is ready. We don't extract the router mac address from RA because
- // we get the router mac address from IPv4 ARP packet. See #getRouterMacAddressFromArp.
- for (int i = 0; i < READ_RA_ATTEMPTS; i++) {
- final byte[] raPacket = getNextMatchedPacket((p) -> {
- return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
- });
- if (raPacket != null) return;
- }
-
- fail("Could not get RA multicast packet after " + READ_RA_ATTEMPTS + " attempts");
- }
-
private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) {
ByteBuffer buf = ByteBuffer.wrap(packet);
- if (!isIcmpv6Type(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return null;
+ if (!isExpectedIcmpv6Packet(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) {
+ fail("Parsing RA packet fail");
+ }
Struct.parse(RaHeader.class, buf);
final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
@@ -290,13 +288,10 @@
private Inet6Address runSlaac(MacAddress srcMac, MacAddress dstMac) throws Exception {
sendRsPacket(srcMac, dstMac);
- final byte[] raPacket = getNextMatchedPacket((p) -> {
- return isIcmpv6Type(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
- });
+ final byte[] raPacket = verifyPacketNotNull("Receive RA fail", getDownloadPacket(p -> {
+ return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
+ }));
- if (raPacket == null) {
- fail("Could not get ra for prefix options");
- }
final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
for (PrefixInformationOption pio : options) {
@@ -312,7 +307,7 @@
}
}
- fail("Could not get ipv6 address");
+ fail("No available ipv6 prefix");
return null;
}
@@ -322,7 +317,7 @@
ByteBuffer rs = Ipv6Utils.buildRsPacket(srcMac, dstMac, (Inet6Address) LINK_LOCAL,
IPV6_ADDR_ALL_NODES_MULTICAST, slla);
- sendPacket(rs);
+ sendUploadPacket(rs);
}
private void maybeReplyNa(byte[] packet) {
@@ -347,7 +342,7 @@
ByteBuffer ns = Ipv6Utils.buildNaPacket(tethered.macAddr, tethered.routerMacAddr,
nsHdr.target, ipv6Hdr.srcIp, flags, nsHdr.target, tlla);
try {
- sendPacket(ns);
+ sendUploadPacket(ns);
} catch (Exception e) {
fail("Failed to reply NA for " + tethered.ipv6Addr);
}
@@ -356,23 +351,18 @@
}
}
- public static boolean isIcmpv6Type(byte[] packet, boolean hasEth, int type) {
+ public static boolean isExpectedIcmpv6Packet(byte[] packet, boolean hasEth, int type) {
final ByteBuffer buf = ByteBuffer.wrap(packet);
- return isIcmpv6Type(buf, hasEth, type);
+ return isExpectedIcmpv6Packet(buf, hasEth, type);
}
- private static boolean isIcmpv6Type(ByteBuffer buf, boolean hasEth, int type) {
+ private static boolean isExpectedIcmpv6Packet(ByteBuffer buf, boolean hasEth, int type) {
try {
- if (hasEth) {
- final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
- if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
- }
+ if (hasEth && !hasExpectedEtherHeader(buf, false /* isIpv4 */)) return false;
- final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
- if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
+ if (!hasExpectedIpHeader(buf, false /* isIpv4 */, IPPROTO_ICMPV6)) return false;
- final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
- return icmpv6Hdr.type == (short) type;
+ return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
} catch (Exception e) {
// Parsing packet fail means it is not icmpv6 packet.
}
@@ -380,11 +370,53 @@
return false;
}
- public void sendPacket(ByteBuffer packet) throws Exception {
+ private static boolean hasExpectedEtherHeader(@NonNull final ByteBuffer buf, boolean isIpv4)
+ throws Exception {
+ final int expected = isIpv4 ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
+
+ return Struct.parse(EthernetHeader.class, buf).etherType == expected;
+ }
+
+ private static boolean hasExpectedIpHeader(@NonNull final ByteBuffer buf, boolean isIpv4,
+ int ipProto) throws Exception {
+ if (isIpv4) {
+ return Struct.parse(Ipv4Header.class, buf).protocol == (byte) ipProto;
+ } else {
+ return Struct.parse(Ipv6Header.class, buf).nextHeader == (byte) ipProto;
+ }
+ }
+
+ public static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
+ boolean isIpv4, @NonNull final ByteBuffer payload) {
+ final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
+ try {
+ if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
+
+ if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_UDP)) return false;
+
+ if (Struct.parse(UdpHeader.class, buf) == null) return false;
+ } catch (Exception e) {
+ // Parsing packet fail means it is not udp packet.
+ return false;
+ }
+
+ if (buf.remaining() != payload.limit()) return false;
+
+ return Arrays.equals(Arrays.copyOfRange(buf.array(), buf.position(), buf.limit()),
+ payload.array());
+ }
+
+ private void sendUploadPacket(ByteBuffer packet) throws Exception {
mDownstreamReader.sendResponse(packet);
}
- public byte[] getNextMatchedPacket(Predicate<byte[]> filter) {
+ private void sendDownloadPacket(ByteBuffer packet) throws Exception {
+ assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader);
+
+ mUpstreamReader.sendResponse(packet);
+ }
+
+ private byte[] getDownloadPacket(Predicate<byte[]> filter) {
byte[] packet;
while ((packet = mDownstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) {
if (filter.test(packet)) return packet;
@@ -396,30 +428,34 @@
return null;
}
- public void verifyUpload(final RemoteResponder dst, final ByteBuffer packet,
- final Predicate<byte[]> filter) throws Exception {
- sendPacket(packet);
- assertNotNull("Upload fail", dst.getNextMatchedPacket(filter));
+ private byte[] getUploadPacket(Predicate<byte[]> filter) {
+ assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader);
+
+ return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter);
}
- public static class RemoteResponder {
- final TapPacketReader mUpstreamReader;
- public RemoteResponder(TapPacketReader reader) {
- mUpstreamReader = reader;
- }
+ private @NonNull byte[] verifyPacketNotNull(String message, @Nullable byte[] packet) {
+ assertNotNull(message, packet);
- public void sendPacket(ByteBuffer packet) throws Exception {
- mUpstreamReader.sendResponse(packet);
- }
+ return packet;
+ }
- public byte[] getNextMatchedPacket(Predicate<byte[]> filter) throws Exception {
- return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter);
- }
+ public byte[] testUpload(final ByteBuffer packet, final Predicate<byte[]> filter)
+ throws Exception {
+ sendUploadPacket(packet);
- public void verifyDownload(final TetheringTester dst, final ByteBuffer packet,
- final Predicate<byte[]> filter) throws Exception {
- sendPacket(packet);
- assertNotNull("Download fail", dst.getNextMatchedPacket(filter));
- }
+ return getUploadPacket(filter);
+ }
+
+ public byte[] verifyUpload(final ByteBuffer packet, final Predicate<byte[]> filter)
+ throws Exception {
+ return verifyPacketNotNull("Upload fail", testUpload(packet, filter));
+ }
+
+ public byte[] verifyDownload(final ByteBuffer packet, final Predicate<byte[]> filter)
+ throws Exception {
+ sendDownloadPacket(packet);
+
+ return verifyPacketNotNull("Download fail", getDownloadPacket(filter));
}
}
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 23af3e3..b1144b4 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -25,8 +25,14 @@
name: "bpf_connectivity_headers",
vendor_available: false,
host_supported: false,
- header_libs: ["bpf_headers"],
- export_header_lib_headers: ["bpf_headers"],
+ header_libs: [
+ "bpf_headers",
+ "libnetdbinder_utils_headers", // for XtBpfProgLocations.h
+ ],
+ export_header_lib_headers: [
+ "bpf_headers",
+ "libnetdbinder_utils_headers", // for XtBpfProgLocations.h
+ ],
export_include_dirs: ["."],
cflags: [
"-Wall",
@@ -37,11 +43,8 @@
apex_available: [
"//apex_available:platform",
"com.android.tethering",
- ],
+ ],
visibility: [
- // TODO: remove it when NetworkStatsService is moved into the mainline module and no more
- // calls to JNI in libservices.core.
- "//frameworks/base/services/core/jni",
"//packages/modules/Connectivity/netd",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service/native/libs/libclat",
@@ -50,7 +53,6 @@
"//packages/modules/Connectivity/tests/native",
"//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
"//packages/modules/Connectivity/tests/unit/jni",
- "//system/netd/server",
"//system/netd/tests",
],
}
@@ -106,13 +108,11 @@
"-Wall",
"-Werror",
],
- include_dirs: [
- "frameworks/libs/net/common/netd/libnetdutils/include",
- ],
sub_dir: "net_shared",
}
bpf {
+ // WARNING: Android T's non-updatable netd depends on 'netd' string for xt_bpf programs it loads
name: "netd.o",
srcs: ["netd.c"],
btf: true,
@@ -120,8 +120,6 @@
"-Wall",
"-Werror",
],
- include_dirs: [
- "frameworks/libs/net/common/netd/libnetdutils/include",
- ],
+ // WARNING: Android T's non-updatable netd depends on 'netd_shared' string for xt_bpf programs
sub_dir: "netd_shared",
}
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 706dd1d..4b3ba2f 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -21,6 +21,11 @@
#include <linux/in.h>
#include <linux/in6.h>
+#ifdef __cplusplus
+#include <string_view>
+#include "XtBpfProgLocations.h"
+#endif
+
// This header file is shared by eBPF kernel programs (C) and netd (C++) and
// some of the maps are also accessed directly from Java mainline module code.
//
@@ -98,14 +103,33 @@
static const int CONFIGURATION_MAP_SIZE = 2;
static const int UID_OWNER_MAP_SIZE = 2000;
+#ifdef __cplusplus
+
#define BPF_NETD_PATH "/sys/fs/bpf/netd_shared/"
#define BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_egress_stats"
#define BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupskb_ingress_stats"
-#define XT_BPF_INGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_ingress_xtbpf"
-#define XT_BPF_EGRESS_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_egress_xtbpf"
-#define XT_BPF_ALLOWLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf"
-#define XT_BPF_DENYLIST_PROG_PATH BPF_NETD_PATH "prog_netd_skfilter_denylist_xtbpf"
+
+#define ASSERT_STRING_EQUAL(s1, s2) \
+ static_assert(std::string_view(s1) == std::string_view(s2), "mismatch vs Android T netd")
+
+/* -=-=-=-=- WARNING -=-=-=-=-
+ *
+ * These 4 xt_bpf program paths are actually defined by:
+ * //system/netd/include/binder_utils/XtBpfProgLocations.h
+ * which is intentionally a non-automerged location.
+ *
+ * They are *UNCHANGEABLE* due to being hard coded in Android T's netd binary
+ * as such we have compile time asserts that things match.
+ * (which will be validated during build on mainline-prod branch against old system/netd)
+ *
+ * If you break this, netd on T will fail to start with your tethering mainline module.
+ */
+ASSERT_STRING_EQUAL(XT_BPF_INGRESS_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_ingress_xtbpf");
+ASSERT_STRING_EQUAL(XT_BPF_EGRESS_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_egress_xtbpf");
+ASSERT_STRING_EQUAL(XT_BPF_ALLOWLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_allowlist_xtbpf");
+ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilter_denylist_xtbpf");
+
#define CGROUP_SOCKET_PROG_PATH BPF_NETD_PATH "prog_netd_cgroupsock_inet_create"
#define TC_BPF_INGRESS_ACCOUNT_PROG_NAME "prog_netd_schedact_ingress_account"
@@ -122,6 +146,8 @@
#define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
#define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
+#endif // __cplusplus
+
enum UidOwnerMatchType {
NO_MATCH = 0,
HAPPY_BOX_MATCH = (1 << 0),
@@ -168,16 +194,6 @@
// Entry in the configuration map that stores which stats map is currently in use.
#define CURRENT_STATS_MAP_CONFIGURATION_KEY 2
-#define BPF_CLATD_PATH "/sys/fs/bpf/net_shared/"
-
-#define CLAT_INGRESS6_PROG_RAWIP_NAME "prog_clatd_schedcls_ingress6_clat_rawip"
-#define CLAT_INGRESS6_PROG_ETHER_NAME "prog_clatd_schedcls_ingress6_clat_ether"
-
-#define CLAT_INGRESS6_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_RAWIP_NAME
-#define CLAT_INGRESS6_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_INGRESS6_PROG_ETHER_NAME
-
-#define CLAT_INGRESS6_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_ingress6_map"
-
typedef struct {
uint32_t iif; // The input interface index
struct in6_addr pfx96; // The source /96 nat64 prefix, bottom 32 bits must be 0
@@ -191,14 +207,6 @@
} ClatIngress6Value;
STRUCT_SIZE(ClatIngress6Value, 4 + 4); // 8
-#define CLAT_EGRESS4_PROG_RAWIP_NAME "prog_clatd_schedcls_egress4_clat_rawip"
-#define CLAT_EGRESS4_PROG_ETHER_NAME "prog_clatd_schedcls_egress4_clat_ether"
-
-#define CLAT_EGRESS4_PROG_RAWIP_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_RAWIP_NAME
-#define CLAT_EGRESS4_PROG_ETHER_PATH BPF_CLATD_PATH CLAT_EGRESS4_PROG_ETHER_NAME
-
-#define CLAT_EGRESS4_MAP_PATH BPF_CLATD_PATH "map_clatd_clat_egress4_map"
-
typedef struct {
uint32_t iif; // The input interface index
struct in_addr local4; // The source IPv4 address
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 94d5ed8..8f72f7c 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -28,7 +28,6 @@
#include <linux/ipv6.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
-#include <netdutils/UidConstants.h>
#include <stdbool.h>
#include <stdint.h>
#include "bpf_net_helpers.h"
@@ -52,28 +51,35 @@
#define TCP_FLAG_OFF 13
#define RST_OFFSET 2
-DEFINE_BPF_MAP_GRW(cookie_tag_map, HASH, uint64_t, UidTagValue, COOKIE_UID_MAP_SIZE,
- AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(uid_counterset_map, HASH, uint32_t, uint8_t, UID_COUNTERSET_MAP_SIZE,
- AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(app_uid_stats_map, HASH, uint32_t, StatsValue, APP_STATS_MAP_SIZE,
- AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(stats_map_A, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE,
- AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint32_t, CONFIGURATION_MAP_SIZE,
- AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE,
- AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE, AID_NET_BW_ACCT)
+// For maps netd does not need to access
+#define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
+ DEFINE_BPF_MAP_UGM(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, AID_ROOT, AID_NET_BW_ACCT, 0060)
+
+// For maps netd only needs read only access to
+#define DEFINE_BPF_MAP_RO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
+ DEFINE_BPF_MAP_UGM(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, AID_ROOT, AID_NET_BW_ACCT, 0460)
+
+// For maps netd needs to be able to read and write
+#define DEFINE_BPF_MAP_RW_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
+ DEFINE_BPF_MAP_UGM(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, AID_ROOT, AID_NET_BW_ACCT, 0660)
+
+DEFINE_BPF_MAP_RW_NETD(cookie_tag_map, HASH, uint64_t, UidTagValue, COOKIE_UID_MAP_SIZE)
+DEFINE_BPF_MAP_NO_NETD(uid_counterset_map, HASH, uint32_t, uint8_t, UID_COUNTERSET_MAP_SIZE)
+DEFINE_BPF_MAP_NO_NETD(app_uid_stats_map, HASH, uint32_t, StatsValue, APP_STATS_MAP_SIZE)
+DEFINE_BPF_MAP_RW_NETD(stats_map_A, HASH, StatsKey, StatsValue, STATS_MAP_SIZE)
+DEFINE_BPF_MAP_RO_NETD(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE)
+DEFINE_BPF_MAP_NO_NETD(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE)
+DEFINE_BPF_MAP_RW_NETD(configuration_map, HASH, uint32_t, uint32_t, CONFIGURATION_MAP_SIZE)
+DEFINE_BPF_MAP_NO_NETD(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE)
+DEFINE_BPF_MAP_RW_NETD(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE)
/* never actually used from ebpf */
-DEFINE_BPF_MAP_GRW(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE,
- AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
static __always_inline int is_system_uid(uint32_t uid) {
- return (uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID);
+ // MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
+ // MAX_SYSTEM_UID is AID_NOBODY == 9999, while AID_APP_START == 10000
+ return (uid < AID_APP_START);
}
/*
@@ -189,6 +195,11 @@
return *config;
}
+// DROP_IF_SET is set of rules that BPF_DROP if rule is globally enabled, and per-uid bit is set
+#define DROP_IF_SET (STANDBY_MATCH | OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH)
+// DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set
+#define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH | LOW_POWER_STANDBY_MATCH)
+
static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direction) {
if (skip_owner_match(skb)) return BPF_PASS;
@@ -200,32 +211,13 @@
uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
- if (enabledRules) {
- if ((enabledRules & DOZABLE_MATCH) && !(uidRules & DOZABLE_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & STANDBY_MATCH) && (uidRules & STANDBY_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & POWERSAVE_MATCH) && !(uidRules & POWERSAVE_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & RESTRICTED_MATCH) && !(uidRules & RESTRICTED_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & OEM_DENY_1_MATCH) && (uidRules & OEM_DENY_1_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & OEM_DENY_2_MATCH) && (uidRules & OEM_DENY_2_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & OEM_DENY_3_MATCH) && (uidRules & OEM_DENY_3_MATCH)) {
- return BPF_DROP;
- }
- }
+ // Warning: funky bit-wise arithmetic: in parallel, for all DROP_IF_SET/UNSET rules
+ // check whether the rules are globally enabled, and if so whether the rules are
+ // set/unset for the specific uid. BPF_DROP if that is the case for ANY of the rules.
+ // We achieve this by masking out only the bits/rules we're interested in checking,
+ // and negating (via bit-wise xor) the bits/rules that should drop if unset.
+ if (enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET)) return BPF_DROP;
+
if (direction == BPF_INGRESS && skb->ifindex != 1) {
if (uidRules & IIF_MATCH) {
if (allowed_iif && skb->ifindex != allowed_iif) {
@@ -326,6 +318,7 @@
return bpf_traffic_account(skb, BPF_EGRESS);
}
+// WARNING: Android T's non-updatable netd depends on the name of this program.
DEFINE_BPF_PROG("skfilter/egress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_egress_prog)
(struct __sk_buff* skb) {
// Clat daemon does not generate new traffic, all its traffic is accounted for already
@@ -345,6 +338,7 @@
return BPF_MATCH;
}
+// WARNING: Android T's non-updatable netd depends on the name of this program.
DEFINE_BPF_PROG("skfilter/ingress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_ingress_prog)
(struct __sk_buff* skb) {
// Clat daemon traffic is not accounted by virtue of iptables raw prerouting drop rule
@@ -367,6 +361,7 @@
return TC_ACT_UNSPEC;
}
+// WARNING: Android T's non-updatable netd depends on the name of this program.
DEFINE_BPF_PROG("skfilter/allowlist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_allowlist_prog)
(struct __sk_buff* skb) {
uint32_t sock_uid = bpf_get_socket_uid(skb);
@@ -384,6 +379,7 @@
return BPF_NOMATCH;
}
+// WARNING: Android T's non-updatable netd depends on the name of this program.
DEFINE_BPF_PROG("skfilter/denylist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_denylist_prog)
(struct __sk_buff* skb) {
uint32_t sock_uid = bpf_get_socket_uid(skb);
@@ -401,7 +397,7 @@
* user at install time so we only check the appId part of a request uid at
* run time. See UserHandle#isSameApp for detail.
*/
- uint32_t appId = (gid_uid & 0xffffffff) % PER_USER_RANGE;
+ uint32_t appId = (gid_uid & 0xffffffff) % AID_USER_OFFSET; // == PER_USER_RANGE == 100000
uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
if (!permissions) {
// UID not in map. Default to just INTERNET permission.
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 51ff5ec..0bb98f8 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -1300,6 +1300,17 @@
}
/**
+ * Removes the interface name from all entries.
+ * This mutates the original structure in place.
+ * @hide
+ */
+ public void clearInterfaces() {
+ for (int i = 0; i < size; i++) {
+ iface[i] = null;
+ }
+ }
+
+ /**
* Only keep entries that match all specified filters.
*
* <p>This mutates the original structure in place. After this method is called,
diff --git a/framework/Android.bp b/framework/Android.bp
index d7de439..24d8cca 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -64,7 +64,6 @@
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
- ":framework-connectivity-javastream-protos",
],
aidl: {
generate_get_transaction_name: true,
@@ -90,6 +89,7 @@
"modules-utils-backgroundthread",
"modules-utils-build",
"modules-utils-preconditions",
+ "framework-connectivity-javastream-protos",
],
libs: [
"app-compat-annotations",
@@ -197,28 +197,16 @@
visibility: ["//frameworks/base"],
}
-gensrcs {
+java_library {
name: "framework-connectivity-javastream-protos",
- depfile: true,
-
- tools: [
- "aprotoc",
- "protoc-gen-javastream",
- "soong_zip",
+ proto: {
+ type: "stream",
+ },
+ srcs: [":framework-connectivity-protos"],
+ installable: false,
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
],
-
- cmd: "mkdir -p $(genDir)/$(in) " +
- "&& $(location aprotoc) " +
- " --plugin=$(location protoc-gen-javastream) " +
- " --dependency_out=$(depfile) " +
- " --javastream_out=$(genDir)/$(in) " +
- " -Iexternal/protobuf/src " +
- " -I . " +
- " $(in) " +
- "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
-
- srcs: [
- ":framework-connectivity-protos",
- ],
- output_extension: "srcjar",
}
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index ddac19d..a2a1ac0 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -51,6 +51,9 @@
field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5
+ field public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7; // 0x7
+ field public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8; // 0x8
+ field public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9; // 0x9
field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3
field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
@@ -197,6 +200,8 @@
method public int describeContents();
method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor();
method @NonNull public String getInterfaceName();
+ method @Nullable public android.net.MacAddress getMacAddress();
+ method public int getMtu();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR;
}
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index db1d7e9..f1298ce 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -249,10 +249,10 @@
method public void onValidationStatus(int, @Nullable android.net.Uri);
method @NonNull public android.net.Network register();
method public void sendAddDscpPolicy(@NonNull android.net.DscpPolicy);
- method public final void sendLinkProperties(@NonNull android.net.LinkProperties);
- method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
- method public final void sendNetworkScore(@NonNull android.net.NetworkScore);
- method public final void sendNetworkScore(@IntRange(from=0, to=99) int);
+ method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+ method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+ method public void sendNetworkScore(@NonNull android.net.NetworkScore);
+ method public void sendNetworkScore(@IntRange(from=0, to=99) int);
method public final void sendQosCallbackError(int, int);
method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
method public final void sendQosSessionLost(int, int, int);
@@ -262,7 +262,7 @@
method @Deprecated public void setLegacySubtype(int, @NonNull String);
method public void setLingerDuration(@NonNull java.time.Duration);
method public void setTeardownDelayMillis(@IntRange(from=0, to=0x1388) int);
- method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
+ method public void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
method public void unregister();
method public void unregisterAfterReplacement(@IntRange(from=0, to=0x1388) int);
field public static final int DSCP_POLICY_STATUS_DELETED = 4; // 0x4
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index d1c202c..39cd7f3 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -987,6 +987,7 @@
* Denylist of apps that will not have network access due to OEM-specific restrictions.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7;
/**
@@ -994,6 +995,7 @@
* Denylist of apps that will not have network access due to OEM-specific restrictions.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8;
/**
@@ -1001,6 +1003,7 @@
* Denylist of apps that will not have network access due to OEM-specific restrictions.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9;
/** @hide */
diff --git a/framework/src/android/net/ITestNetworkManager.aidl b/framework/src/android/net/ITestNetworkManager.aidl
index 27d13c1..d18b931 100644
--- a/framework/src/android/net/ITestNetworkManager.aidl
+++ b/framework/src/android/net/ITestNetworkManager.aidl
@@ -29,8 +29,10 @@
*/
interface ITestNetworkManager
{
- TestNetworkInterface createInterface(boolean isTun, boolean bringUp, in LinkAddress[] addrs,
- in @nullable String iface);
+ TestNetworkInterface createInterface(boolean isTun, boolean hasCarrier, boolean bringUp,
+ in LinkAddress[] addrs, in @nullable String iface);
+
+ void setCarrierEnabled(in TestNetworkInterface iface, boolean enabled);
void setupTestNetwork(in String iface, in LinkProperties lp, in boolean isMetered,
in int[] administratorUids, in IBinder binder);
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 2c50c73..5659a35 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -913,7 +913,7 @@
* Must be called by the agent when the network's {@link LinkProperties} change.
* @param linkProperties the new LinkProperties.
*/
- public final void sendLinkProperties(@NonNull LinkProperties linkProperties) {
+ public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
Objects.requireNonNull(linkProperties);
final LinkProperties lp = new LinkProperties(linkProperties);
queueOrSendMessage(reg -> reg.sendLinkProperties(lp));
@@ -938,7 +938,7 @@
* @param underlyingNetworks the new list of underlying networks.
* @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])}
*/
- public final void setUnderlyingNetworks(
+ public void setUnderlyingNetworks(
@SuppressLint("NullableCollection") @Nullable List<Network> underlyingNetworks) {
final ArrayList<Network> underlyingArray = (underlyingNetworks != null)
? new ArrayList<>(underlyingNetworks) : null;
@@ -1088,7 +1088,7 @@
* Must be called by the agent when the network's {@link NetworkCapabilities} change.
* @param networkCapabilities the new NetworkCapabilities.
*/
- public final void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+ public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
Objects.requireNonNull(networkCapabilities);
mBandwidthUpdatePending.set(false);
mLastBwRefreshTime = System.currentTimeMillis();
@@ -1102,7 +1102,7 @@
*
* @param score the new score.
*/
- public final void sendNetworkScore(@NonNull NetworkScore score) {
+ public void sendNetworkScore(@NonNull NetworkScore score) {
Objects.requireNonNull(score);
queueOrSendMessage(reg -> reg.sendScore(score));
}
@@ -1113,7 +1113,7 @@
* @param score the new score, between 0 and 99.
* deprecated use sendNetworkScore(NetworkScore) TODO : remove in S.
*/
- public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) {
+ public void sendNetworkScore(@IntRange(from = 0, to = 99) int score) {
sendNetworkScore(new NetworkScore.Builder().setLegacyInt(score).build());
}
diff --git a/framework/src/android/net/TestNetworkInterface.java b/framework/src/android/net/TestNetworkInterface.java
index 4449ff8..26200e1 100644
--- a/framework/src/android/net/TestNetworkInterface.java
+++ b/framework/src/android/net/TestNetworkInterface.java
@@ -16,22 +16,32 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.util.Log;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
/**
- * This class is used to return the interface name and fd of the test interface
+ * This class is used to return the interface name, fd, MAC, and MTU of the test interface
*
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class TestNetworkInterface implements Parcelable {
+ private static final String TAG = "TestNetworkInterface";
+
@NonNull
private final ParcelFileDescriptor mFileDescriptor;
@NonNull
private final String mInterfaceName;
+ @Nullable
+ private final MacAddress mMacAddress;
+ private final int mMtu;
@Override
public int describeContents() {
@@ -40,18 +50,41 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeParcelable(mFileDescriptor, PARCELABLE_WRITE_RETURN_VALUE);
+ out.writeParcelable(mFileDescriptor, flags);
out.writeString(mInterfaceName);
+ out.writeParcelable(mMacAddress, flags);
+ out.writeInt(mMtu);
}
public TestNetworkInterface(@NonNull ParcelFileDescriptor pfd, @NonNull String intf) {
mFileDescriptor = pfd;
mInterfaceName = intf;
+
+ MacAddress macAddress = null;
+ int mtu = 1500;
+ try {
+ // This constructor is called by TestNetworkManager which runs inside the system server,
+ // which has permission to read the MacAddress.
+ NetworkInterface nif = NetworkInterface.getByName(mInterfaceName);
+
+ // getHardwareAddress() returns null for tun interfaces.
+ byte[] hardwareAddress = nif.getHardwareAddress();
+ if (hardwareAddress != null) {
+ macAddress = MacAddress.fromBytes(nif.getHardwareAddress());
+ }
+ mtu = nif.getMTU();
+ } catch (SocketException e) {
+ Log.e(TAG, "Failed to fetch MacAddress or MTU size from NetworkInterface", e);
+ }
+ mMacAddress = macAddress;
+ mMtu = mtu;
}
private TestNetworkInterface(@NonNull Parcel in) {
mFileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
mInterfaceName = in.readString();
+ mMacAddress = in.readParcelable(MacAddress.class.getClassLoader());
+ mMtu = in.readInt();
}
@NonNull
@@ -64,6 +97,15 @@
return mInterfaceName;
}
+ @Nullable
+ public MacAddress getMacAddress() {
+ return mMacAddress;
+ }
+
+ public int getMtu() {
+ return mMtu;
+ }
+
@NonNull
public static final Parcelable.Creator<TestNetworkInterface> CREATOR =
new Parcelable.Creator<TestNetworkInterface>() {
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index 4e78823..7b18765 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -58,6 +58,7 @@
private static final boolean TAP = false;
private static final boolean TUN = true;
private static final boolean BRING_UP = true;
+ private static final boolean CARRIER_UP = true;
private static final LinkAddress[] NO_ADDRS = new LinkAddress[0];
/** @hide */
@@ -166,7 +167,7 @@
public TestNetworkInterface createTunInterface(@NonNull Collection<LinkAddress> linkAddrs) {
try {
final LinkAddress[] arr = new LinkAddress[linkAddrs.size()];
- return mService.createInterface(TUN, BRING_UP, linkAddrs.toArray(arr),
+ return mService.createInterface(TUN, CARRIER_UP, BRING_UP, linkAddrs.toArray(arr),
null /* iface */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -185,7 +186,7 @@
@NonNull
public TestNetworkInterface createTapInterface() {
try {
- return mService.createInterface(TAP, BRING_UP, NO_ADDRS, null /* iface */);
+ return mService.createInterface(TAP, CARRIER_UP, BRING_UP, NO_ADDRS, null /* iface */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -204,7 +205,7 @@
@NonNull
public TestNetworkInterface createTapInterface(boolean bringUp) {
try {
- return mService.createInterface(TAP, bringUp, NO_ADDRS, null /* iface */);
+ return mService.createInterface(TAP, CARRIER_UP, bringUp, NO_ADDRS, null /* iface */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -226,7 +227,43 @@
@NonNull
public TestNetworkInterface createTapInterface(boolean bringUp, @NonNull String iface) {
try {
- return mService.createInterface(TAP, bringUp, NO_ADDRS, iface);
+ return mService.createInterface(TAP, CARRIER_UP, bringUp, NO_ADDRS, iface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Create a tap interface with or without carrier for testing purposes.
+ *
+ * @param carrierUp whether the created interface has a carrier or not.
+ * @param bringUp whether to bring up the interface before returning it.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
+ @NonNull
+ public TestNetworkInterface createTapInterface(boolean carrierUp, boolean bringUp) {
+ try {
+ return mService.createInterface(TAP, carrierUp, bringUp, NO_ADDRS, null /* iface */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enable / disable carrier on TestNetworkInterface
+ *
+ * Note: TUNSETCARRIER is not supported until kernel version 5.0.
+ * TODO: add RequiresApi annotation.
+ *
+ * @param iface the interface to configure.
+ * @param enabled true to turn carrier on, false to turn carrier off.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
+ public void setCarrierEnabled(@NonNull TestNetworkInterface iface, boolean enabled) {
+ try {
+ mService.setCarrierEnabled(iface, enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 42d0de5..5ae8ab6 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -199,6 +199,7 @@
BpfMap<StatsKey, StatsValue>& currentMap =
(configuration.value() == SELECT_MAP_A) ? mStatsMapA : mStatsMapB;
+ // HACK: mStatsMapB becomes RW BpfMap here, but countUidStatsEntries doesn't modify so it works
base::Result<void> res = currentMap.iterate(countUidStatsEntries);
if (!res.ok()) {
ALOGE("Failed to count the stats entry in map %d: %s", currentMap.getMap().get(),
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
index 05b9ebc..7e3b94d 100644
--- a/netd/BpfHandler.h
+++ b/netd/BpfHandler.h
@@ -23,6 +23,7 @@
#include "bpf_shared.h"
using android::bpf::BpfMap;
+using android::bpf::BpfMapRO;
namespace android {
namespace net {
@@ -61,7 +62,7 @@
BpfMap<uint64_t, UidTagValue> mCookieTagMap;
BpfMap<StatsKey, StatsValue> mStatsMapA;
- BpfMap<StatsKey, StatsValue> mStatsMapB;
+ BpfMapRO<StatsKey, StatsValue> mStatsMapB;
BpfMap<uint32_t, uint32_t> mConfigurationMap;
BpfMap<uint32_t, uint8_t> mUidPermissionMap;
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
index 1bd222d..c0f7e45 100644
--- a/netd/BpfHandlerTest.cpp
+++ b/netd/BpfHandlerTest.cpp
@@ -21,7 +21,7 @@
#include <gtest/gtest.h>
-#define TEST_BPF_MAP
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
#include "BpfHandler.h"
using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted
@@ -65,7 +65,7 @@
mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_HASH, 1);
ASSERT_VALID(mFakeConfigurationMap);
- mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeUidPermissionMap);
mBh.mCookieTagMap = mFakeCookieTagMap;
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
index 4974b96..6f9c8c2 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -33,6 +33,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
#include "bpf/BpfMap.h"
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
@@ -80,19 +81,19 @@
ASSERT_EQ(0, setrlimitForTest());
mFakeCookieTagMap = BpfMap<uint64_t, UidTagValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeCookieTagMap.getMap());
+ ASSERT_TRUE(mFakeCookieTagMap.isValid());
mFakeAppUidStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeAppUidStatsMap.getMap());
+ ASSERT_TRUE(mFakeAppUidStatsMap.isValid());
mFakeStatsMap = BpfMap<StatsKey, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeStatsMap.getMap());
+ ASSERT_TRUE(mFakeStatsMap.isValid());
mFakeIfaceIndexNameMap = BpfMap<uint32_t, IfaceValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeIfaceIndexNameMap.getMap());
+ ASSERT_TRUE(mFakeIfaceIndexNameMap.isValid());
mFakeIfaceStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeIfaceStatsMap.getMap());
+ ASSERT_TRUE(mFakeIfaceStatsMap.isValid());
}
void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index cd29185..ff6e45d 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
import static android.net.NetworkStats.IFACE_ALL;
@@ -172,6 +173,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
@@ -341,11 +343,16 @@
@GuardedBy("mStatsLock")
private String mActiveIface;
- /** Set of any ifaces associated with mobile networks since boot. */
+ /** Set of all ifaces currently associated with mobile networks. */
private volatile String[] mMobileIfaces = new String[0];
- /** Set of any ifaces associated with wifi networks since boot. */
- private volatile String[] mWifiIfaces = new String[0];
+ /* A set of all interfaces that have ever been associated with mobile networks since boot. */
+ @GuardedBy("mStatsLock")
+ private final Set<String> mAllMobileIfacesSinceBoot = new ArraySet<>();
+
+ /* A set of all interfaces that have ever been associated with wifi networks since boot. */
+ @GuardedBy("mStatsLock")
+ private final Set<String> mAllWifiIfacesSinceBoot = new ArraySet<>();
/** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
@GuardedBy("mStatsLock")
@@ -1468,9 +1475,9 @@
// We've been using pure XT stats long enough that we no longer need to
// splice DEV and XT together.
final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL,
- accessLevel, callingUid, start, end);
+ accessLevel, callingUid, Long.MIN_VALUE, Long.MAX_VALUE);
- final long now = System.currentTimeMillis();
+ final long now = mClock.millis();
final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
final NetworkStats stats = new NetworkStats(end - start, 1);
@@ -1551,17 +1558,37 @@
return dataLayer;
}
+ private String[] getAllIfacesSinceBoot(int transport) {
+ synchronized (mStatsLock) {
+ final Set<String> ifaceSet;
+ if (transport == TRANSPORT_WIFI) {
+ ifaceSet = mAllWifiIfacesSinceBoot;
+ } else if (transport == TRANSPORT_CELLULAR) {
+ ifaceSet = mAllMobileIfacesSinceBoot;
+ } else {
+ throw new IllegalArgumentException("Invalid transport " + transport);
+ }
+
+ return ifaceSet.toArray(new String[0]);
+ }
+ }
+
@Override
public NetworkStats getUidStatsForTransport(int transport) {
PermissionUtils.enforceNetworkStackPermission(mContext);
try {
- final String[] relevantIfaces =
- transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces;
+ final String[] ifaceArray = getAllIfacesSinceBoot(transport);
// TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
// interfaces, so this is not useful, remove it.
final String[] ifacesToQuery =
- mStatsFactory.augmentWithStackedInterfaces(relevantIfaces);
- return getNetworkStatsUidDetail(ifacesToQuery);
+ mStatsFactory.augmentWithStackedInterfaces(ifaceArray);
+ final NetworkStats stats = getNetworkStatsUidDetail(ifacesToQuery);
+ // Clear the interfaces of the stats before returning, so callers won't get this
+ // information. This is because no caller needs this information for now, and it
+ // makes it easier to change the implementation later by using the histories in the
+ // recorder.
+ stats.clearInterfaces();
+ return stats;
} catch (RemoteException e) {
Log.wtf(TAG, "Error compiling UID stats", e);
return new NetworkStats(0L, 0);
@@ -1570,11 +1597,6 @@
@Override
public String[] getMobileIfaces() {
- // TODO (b/192758557): Remove debug log.
- if (CollectionUtils.contains(mMobileIfaces, null)) {
- throw new NullPointerException(
- "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
- }
return mMobileIfaces.clone();
}
@@ -1942,7 +1964,6 @@
final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled();
final ArraySet<String> mobileIfaces = new ArraySet<>();
- final ArraySet<String> wifiIfaces = new ArraySet<>();
for (NetworkStateSnapshot snapshot : snapshots) {
final int displayTransport =
getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
@@ -1987,9 +2008,12 @@
if (isMobile) {
mobileIfaces.add(baseIface);
+ // If the interface name was present in the wifi set, the interface won't
+ // be removed from it to prevent stats from getting rollback.
+ mAllMobileIfacesSinceBoot.add(baseIface);
}
if (isWifi) {
- wifiIfaces.add(baseIface);
+ mAllWifiIfacesSinceBoot.add(baseIface);
}
}
@@ -2031,9 +2055,10 @@
findOrCreateNetworkIdentitySet(mActiveUidIfaces, iface).add(ident);
if (isMobile) {
mobileIfaces.add(iface);
+ mAllMobileIfacesSinceBoot.add(iface);
}
if (isWifi) {
- wifiIfaces.add(iface);
+ mAllWifiIfacesSinceBoot.add(iface);
}
mStatsFactory.noteStackedIface(iface, baseIface);
@@ -2042,16 +2067,6 @@
}
mMobileIfaces = mobileIfaces.toArray(new String[0]);
- mWifiIfaces = wifiIfaces.toArray(new String[0]);
- // TODO (b/192758557): Remove debug log.
- if (CollectionUtils.contains(mMobileIfaces, null)) {
- throw new NullPointerException(
- "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
- }
- if (CollectionUtils.contains(mWifiIfaces, null)) {
- throw new NullPointerException(
- "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces));
- }
}
private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
@@ -2516,6 +2531,22 @@
}
pw.decreaseIndent();
+ pw.println("All wifi interfaces:");
+ pw.increaseIndent();
+ for (String iface : mAllWifiIfacesSinceBoot) {
+ pw.print(iface + " ");
+ }
+ pw.println();
+ pw.decreaseIndent();
+
+ pw.println("All mobile interfaces:");
+ pw.increaseIndent();
+ for (String iface : mAllMobileIfacesSinceBoot) {
+ pw.print(iface + " ");
+ }
+ pw.println();
+ pw.decreaseIndent();
+
// Get the top openSession callers
final HashMap calls;
synchronized (mOpenSessionCallsLock) {
diff --git a/service/Android.bp b/service/Android.bp
index 91b9d1c..45e43bc 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -198,11 +198,10 @@
lint: { strict_updatability_linting: true },
}
-java_library {
- name: "service-connectivity",
+java_defaults {
+ name: "service-connectivity-defaults",
sdk_version: "system_server_current",
min_sdk_version: "30",
- installable: true,
// This library combines system server jars that have access to different bootclasspath jars.
// Lower SDK service jars must not depend on higher SDK jars as that would let them
// transitively depend on the wrong bootclasspath jars. Sources also cannot be added here as
@@ -224,6 +223,24 @@
lint: { strict_updatability_linting: true },
}
+// A special library created strictly for use by the tests as they need the
+// implementation library but that is not available when building from prebuilts.
+// Using a library with a different name to what is used by the prebuilts ensures
+// that this will never depend on the prebuilt.
+// Switching service-connectivity to a java_sdk_library would also have worked as
+// that has built in support for managing this but that is too big a change at this
+// point.
+java_library {
+ name: "service-connectivity-for-tests",
+ defaults: ["service-connectivity-defaults"],
+}
+
+java_library {
+ name: "service-connectivity",
+ defaults: ["service-connectivity-defaults"],
+ installable: true,
+}
+
filegroup {
name: "connectivity-jarjar-rules",
srcs: ["jarjar-rules.txt"],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index c7223fc..4013d2e 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -118,6 +118,7 @@
rule androidx.core.** com.android.server.nearby.@0
rule androidx.versionedparcelable.** com.android.server.nearby.@0
rule com.google.common.** com.android.server.nearby.@0
+rule android.support.v4.** com.android.server.nearby.@0
# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index 4efd0e1..9c7a761 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -51,7 +51,15 @@
jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
}
-static int createTunTapInterface(JNIEnv* env, bool isTun, const char* iface) {
+// enable or disable carrier on tun / tap interface.
+static void setTunTapCarrierEnabledImpl(JNIEnv* env, const char* iface, int tunFd, bool enabled) {
+ uint32_t carrierOn = enabled;
+ if (ioctl(tunFd, TUNSETCARRIER, &carrierOn)) {
+ throwException(env, errno, "set carrier", iface);
+ }
+}
+
+static int createTunTapImpl(JNIEnv* env, bool isTun, bool hasCarrier, const char* iface) {
base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK));
ifreq ifr{};
@@ -63,6 +71,11 @@
return -1;
}
+ if (!hasCarrier) {
+ // disable carrier before setting IFF_UP
+ setTunTapCarrierEnabledImpl(env, iface, tun.get(), hasCarrier);
+ }
+
// Activate interface using an unconnected datagram socket.
base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
ifr.ifr_flags = IFF_UP;
@@ -79,23 +92,31 @@
//------------------------------------------------------------------------------
-static jint create(JNIEnv* env, jobject /* thiz */, jboolean isTun, jstring jIface) {
+static void setTunTapCarrierEnabled(JNIEnv* env, jclass /* clazz */, jstring
+ jIface, jint tunFd, jboolean enabled) {
+ ScopedUtfChars iface(env, jIface);
+ if (!iface.c_str()) {
+ jniThrowNullPointerException(env, "iface");
+ }
+ setTunTapCarrierEnabledImpl(env, iface.c_str(), tunFd, enabled);
+}
+
+static jint createTunTap(JNIEnv* env, jclass /* clazz */, jboolean isTun,
+ jboolean hasCarrier, jstring jIface) {
ScopedUtfChars iface(env, jIface);
if (!iface.c_str()) {
jniThrowNullPointerException(env, "iface");
return -1;
}
- int tun = createTunTapInterface(env, isTun, iface.c_str());
-
- // Any exceptions will be thrown from the createTunTapInterface call
- return tun;
+ return createTunTapImpl(env, isTun, hasCarrier, iface.c_str());
}
//------------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
- {"jniCreateTunTap", "(ZLjava/lang/String;)I", (void*)create},
+ {"nativeSetTunTapCarrierEnabled", "(Ljava/lang/String;IZ)V", (void*)setTunTapCarrierEnabled},
+ {"nativeCreateTunTap", "(ZZLjava/lang/String;)I", (void*)createTunTap},
};
int register_com_android_server_TestNetworkService(JNIEnv* env) {
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 8ad42c7..adc1925 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -495,8 +495,6 @@
oldConfigure.error().message().c_str());
return -oldConfigure.error().code();
}
- Status res;
- BpfConfig newConfiguration;
uint32_t match;
switch (chain) {
case DOZABLE:
@@ -526,9 +524,9 @@
default:
return -EINVAL;
}
- newConfiguration =
- enable ? (oldConfigure.value() | match) : (oldConfigure.value() & (~match));
- res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
+ BpfConfig newConfiguration =
+ enable ? (oldConfigure.value() | match) : (oldConfigure.value() & ~match);
+ Status res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
if (!isOk(res)) {
ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
}
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 0b4550e..b77c465 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -38,7 +38,7 @@
#include <netdutils/MockSyscalls.h>
-#define TEST_BPF_MAP
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
#include "TrafficController.h"
#include "bpf/BpfUtils.h"
#include "NetdUpdatablePublic.h"
@@ -68,6 +68,7 @@
constexpr int TXBYTES = 0;
#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+#define ASSERT_INVALID(x) ASSERT_FALSE((x).isValid())
class TrafficControllerTest : public ::testing::Test {
protected:
@@ -76,6 +77,8 @@
BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+ BpfMap<StatsKey, StatsValue> mFakeStatsMapB; // makeTrafficControllerMapsInvalid only
+ BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap; ; // makeTrafficControllerMapsInvalid only
BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
@@ -259,37 +262,6 @@
EXPECT_TRUE(mTc.mPrivilegedUser.empty());
}
- void addPrivilegedUid(uid_t uid) {
- std::vector privilegedUid = {uid};
- mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, privilegedUid);
- }
-
- void removePrivilegedUid(uid_t uid) {
- std::vector privilegedUid = {uid};
- mTc.setPermissionForUids(INetd::PERMISSION_NONE, privilegedUid);
- }
-
- void expectFakeStatsUnchanged(uint64_t cookie, uint32_t tag, uint32_t uid,
- StatsKey tagStatsMapKey) {
- Result<UidTagValue> cookieMapResult = mFakeCookieTagMap.readValue(cookie);
- EXPECT_RESULT_OK(cookieMapResult);
- EXPECT_EQ(uid, cookieMapResult.value().uid);
- EXPECT_EQ(tag, cookieMapResult.value().tag);
- Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
- EXPECT_RESULT_OK(statsMapResult);
- EXPECT_EQ((uint64_t)RXPACKETS, statsMapResult.value().rxPackets);
- EXPECT_EQ((uint64_t)RXBYTES, statsMapResult.value().rxBytes);
- tagStatsMapKey.tag = 0;
- statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
- EXPECT_RESULT_OK(statsMapResult);
- EXPECT_EQ((uint64_t)RXPACKETS, statsMapResult.value().rxPackets);
- EXPECT_EQ((uint64_t)RXBYTES, statsMapResult.value().rxBytes);
- auto appStatsResult = mFakeAppUidStatsMap.readValue(uid);
- EXPECT_RESULT_OK(appStatsResult);
- EXPECT_EQ((uint64_t)RXPACKETS, appStatsResult.value().rxPackets);
- EXPECT_EQ((uint64_t)RXBYTES, appStatsResult.value().rxBytes);
- }
-
Status updateUidOwnerMaps(const std::vector<uint32_t>& appUids,
UidOwnerMatchType matchType, TrafficController::IptOp op) {
Status ret(0);
@@ -356,6 +328,52 @@
}
return true;
}
+
+ // Once called, the maps of TrafficController can't recover to valid maps which initialized
+ // in SetUp().
+ void makeTrafficControllerMapsInvalid() {
+ constexpr char INVALID_PATH[] = "invalid";
+
+ mFakeCookieTagMap.init(INVALID_PATH);
+ mTc.mCookieTagMap = mFakeCookieTagMap;
+ ASSERT_INVALID(mTc.mCookieTagMap);
+
+ mFakeAppUidStatsMap.init(INVALID_PATH);
+ mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
+ ASSERT_INVALID(mTc.mAppUidStatsMap);
+
+ mFakeStatsMapA.init(INVALID_PATH);
+ mTc.mStatsMapA = mFakeStatsMapA;
+ ASSERT_INVALID(mTc.mStatsMapA);
+
+ mFakeStatsMapB.init(INVALID_PATH);
+ mTc.mStatsMapB = mFakeStatsMapB;
+ ASSERT_INVALID(mTc.mStatsMapB);
+
+ mFakeIfaceStatsMap.init(INVALID_PATH);
+ mTc.mIfaceStatsMap = mFakeIfaceStatsMap;
+ ASSERT_INVALID(mTc.mIfaceStatsMap);
+
+ mFakeConfigurationMap.init(INVALID_PATH);
+ mTc.mConfigurationMap = mFakeConfigurationMap;
+ ASSERT_INVALID(mTc.mConfigurationMap);
+
+ mFakeUidOwnerMap.init(INVALID_PATH);
+ mTc.mUidOwnerMap = mFakeUidOwnerMap;
+ ASSERT_INVALID(mTc.mUidOwnerMap);
+
+ mFakeUidPermissionMap.init(INVALID_PATH);
+ mTc.mUidPermissionMap = mFakeUidPermissionMap;
+ ASSERT_INVALID(mTc.mUidPermissionMap);
+
+ mFakeUidCounterSetMap.init(INVALID_PATH);
+ mTc.mUidCounterSetMap = mFakeUidCounterSetMap;
+ ASSERT_INVALID(mTc.mUidCounterSetMap);
+
+ mFakeIfaceIndexNameMap.init(INVALID_PATH);
+ mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
+ ASSERT_INVALID(mTc.mIfaceIndexNameMap);
+ }
};
TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
@@ -804,6 +822,71 @@
EXPECT_TRUE(expectDumpsysContains(expectedLines));
}
+TEST_F(TrafficControllerTest, dumpsysInvalidMaps) {
+ makeTrafficControllerMapsInvalid();
+
+ const std::string kErrIterate = "print end with error: Get firstKey map -1 failed: "
+ "Bad file descriptor";
+ const std::string kErrReadRulesConfig = "read ownerMatch configure failed with error: "
+ "Read value of map -1 failed: Bad file descriptor";
+ const std::string kErrReadStatsMapConfig = "read stats map configure failed with error: "
+ "Read value of map -1 failed: Bad file descriptor";
+
+ std::vector<std::string> expectedLines = {
+ fmt::format("mCookieTagMap {}", kErrIterate),
+ fmt::format("mUidCounterSetMap {}", kErrIterate),
+ fmt::format("mAppUidStatsMap {}", 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),
+ fmt::format("mUidOwnerMap {}", kErrIterate),
+ fmt::format("mUidPermissionMap {}", kErrIterate)};
+ EXPECT_TRUE(expectDumpsysContains(expectedLines));
+}
+
+TEST_F(TrafficControllerTest, uidMatchTypeToString) {
+ // NO_MATCH(0) can't be verified because match type flag is added by OR operator.
+ // See TrafficController::addRule()
+ static const struct TestConfig {
+ UidOwnerMatchType uidOwnerMatchType;
+ std::string expected;
+ } testConfigs[] = {
+ // clang-format off
+ {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
+ {DOZABLE_MATCH, "DOZABLE_MATCH"},
+ {STANDBY_MATCH, "STANDBY_MATCH"},
+ {POWERSAVE_MATCH, "POWERSAVE_MATCH"},
+ {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
+ {RESTRICTED_MATCH, "RESTRICTED_MATCH"},
+ {LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"},
+ {IIF_MATCH, "IIF_MATCH"},
+ {LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"},
+ {OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"},
+ {OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"},
+ {OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH"},
+ // clang-format on
+ };
+
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.uidOwnerMatchType,
+ config.expected));
+
+ // Test private function uidMatchTypeToString() via dumpsys.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
+ TrafficController::IptOpInsert)));
+ std::vector<std::string> expectedLines;
+ expectedLines.emplace_back(fmt::format("{} {}", TEST_UID, config.expected));
+ EXPECT_TRUE(expectDumpsysContains(expectedLines));
+
+ // Clean up the stubs.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
+ TrafficController::IptOpDelete)));
+ }
+}
+
TEST_F(TrafficControllerTest, getFirewallType) {
static const struct TestConfig {
ChildChain childChain;
@@ -818,6 +901,7 @@
{LOW_POWER_STANDBY, ALLOWLIST},
{OEM_DENY_1, DENYLIST},
{OEM_DENY_2, DENYLIST},
+ {OEM_DENY_3, DENYLIST},
{INVALID_CHAIN, DENYLIST},
// clang-format on
};
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index e12190c..1209579 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -50,6 +50,7 @@
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.NetworkStackConstants;
+import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -76,7 +77,11 @@
@NonNull private final NetworkProvider mNetworkProvider;
// Native method stubs
- private static native int jniCreateTunTap(boolean isTun, @NonNull String iface);
+ private static native int nativeCreateTunTap(boolean isTun, boolean hasCarrier,
+ @NonNull String iface);
+
+ private static native void nativeSetTunTapCarrierEnabled(@NonNull String iface, int tunFd,
+ boolean enabled);
@VisibleForTesting
protected TestNetworkService(@NonNull Context context) {
@@ -114,7 +119,7 @@
* interface.
*/
@Override
- public TestNetworkInterface createInterface(boolean isTun, boolean bringUp,
+ public TestNetworkInterface createInterface(boolean isTun, boolean hasCarrier, boolean bringUp,
LinkAddress[] linkAddrs, @Nullable String iface) {
enforceTestNetworkPermissions(mContext);
@@ -130,8 +135,8 @@
final long token = Binder.clearCallingIdentity();
try {
- ParcelFileDescriptor tunIntf =
- ParcelFileDescriptor.adoptFd(jniCreateTunTap(isTun, interfaceName));
+ ParcelFileDescriptor tunIntf = ParcelFileDescriptor.adoptFd(
+ nativeCreateTunTap(isTun, hasCarrier, interfaceName));
for (LinkAddress addr : linkAddrs) {
mNetd.interfaceAddAddress(
interfaceName,
@@ -375,4 +380,20 @@
public static void enforceTestNetworkPermissions(@NonNull Context context) {
context.enforceCallingOrSelfPermission(PERMISSION_NAME, "TestNetworkService");
}
+
+ /** Enable / disable TestNetworkInterface carrier */
+ @Override
+ public void setCarrierEnabled(@NonNull TestNetworkInterface iface, boolean enabled) {
+ enforceTestNetworkPermissions(mContext);
+ nativeSetTunTapCarrierEnabled(iface.getInterfaceName(), iface.getFileDescriptor().getFd(),
+ enabled);
+ // Explicitly close fd after use to prevent StrictMode from complaining.
+ // Also, explicitly referencing iface guarantees that the object is not garbage collected
+ // before nativeSetTunTapCarrierEnabled() executes.
+ try {
+ iface.getFileDescriptor().close();
+ } catch (IOException e) {
+ // if the close fails, there is not much that can be done -- move on.
+ }
+ }
}
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 8cefd47..498cf63 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -498,6 +498,31 @@
}
}
+ private void maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6,
+ ParcelFileDescriptor writeSock6) {
+ if (tunFd != null) {
+ try {
+ tunFd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close tun file descriptor " + e);
+ }
+ }
+ if (readSock6 != null) {
+ try {
+ readSock6.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close read socket " + e);
+ }
+ }
+ if (writeSock6 != null) {
+ try {
+ writeSock6.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close write socket " + e);
+ }
+ }
+ }
+
/**
* Start clatd for a given interface and NAT64 prefix.
*/
@@ -546,8 +571,15 @@
// [3] Open, configure and bring up the tun interface.
// Create the v4-... tun interface.
+
+ // Initialize all required file descriptors with null pointer. This makes the following
+ // error handling easier. Simply always call #maybeCleanUp for closing file descriptors,
+ // if any valid ones, in error handling.
+ ParcelFileDescriptor tunFd = null;
+ ParcelFileDescriptor readSock6 = null;
+ ParcelFileDescriptor writeSock6 = null;
+
final String tunIface = CLAT_PREFIX + iface;
- final ParcelFileDescriptor tunFd;
try {
tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
} catch (IOException e) {
@@ -556,7 +588,7 @@
final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
if (tunIfIndex == INVALID_IFINDEX) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Fail to get interface index for interface " + tunIface);
}
@@ -564,7 +596,6 @@
try {
mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
} catch (RemoteException | ServiceSpecificException e) {
- tunFd.close();
Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
}
@@ -575,19 +606,17 @@
detectedMtu = mDeps.detectMtu(pfx96Str,
ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
} catch (IOException e) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
}
final int mtu = adjustMtu(detectedMtu);
Log.i(TAG, "ipv4 mtu is " + mtu);
- // TODO: add setIptablesDropRule
-
// Config tun interface mtu, address and bring up.
try {
mNetd.interfaceSetMtu(tunIface, mtu);
} catch (RemoteException | ServiceSpecificException e) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
}
final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
@@ -599,14 +628,13 @@
try {
mNetd.interfaceSetCfg(ifConfig);
} catch (RemoteException | ServiceSpecificException e) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
+ ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
}
// [4] Open and configure local 464xlat read/write sockets.
// Opens a packet socket to receive IPv6 packets in clatd.
- final ParcelFileDescriptor readSock6;
try {
// Use a JNI call to get native file descriptor instead of Os.socket() because we would
// like to use ParcelFileDescriptor to manage file descriptor. But ctor
@@ -614,27 +642,23 @@
// descriptor to initialize ParcelFileDescriptor object instead.
readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
} catch (IOException e) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Open packet socket failed: " + e);
}
// Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
- final ParcelFileDescriptor writeSock6;
try {
// Use a JNI call to get native file descriptor instead of Os.socket(). See above
// reason why we use jniOpenPacketSocket6().
writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
} catch (IOException e) {
- tunFd.close();
- readSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Open raw socket failed: " + e);
}
final int ifIndex = mDeps.getInterfaceIndex(iface);
if (ifIndex == INVALID_IFINDEX) {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Fail to get interface index for interface " + iface);
}
@@ -642,9 +666,7 @@
try {
mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex);
} catch (IOException e) {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("add anycast sockopt failed: " + e);
}
@@ -653,9 +675,7 @@
try {
cookie = mDeps.tagSocketAsClat(writeSock6.getFileDescriptor());
} catch (IOException e) {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("tag raw socket failed: " + e);
}
@@ -663,9 +683,7 @@
try {
mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex);
} catch (IOException e) {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("configure packet socket failed: " + e);
}
@@ -679,9 +697,9 @@
mDeps.untagSocket(cookie);
throw new IOException("Error start clatd on " + iface + ": " + e);
} finally {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ // The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
+ // Close these file descriptor stubs which are unused anymore.
+ maybeCleanUp(tunFd, readSock6, writeSock6);
}
// [6] Initialize and store clatd tracker object.
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 581ee22..9ed2bb3 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -20,7 +20,6 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.RouteInfo.RTN_UNREACHABLE;
-import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
@@ -53,6 +52,7 @@
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.net.Inet4Address;
@@ -68,11 +68,13 @@
@SmallTest
@ConnectivityModuleTest
public class LinkPropertiesTest {
+ // Use a RuleChain to explicitly specify the order of rules. DevSdkIgnoreRule must run before
+ // PlatformCompatChange rule, because otherwise tests with that should be skipped when targeting
+ // target SDK 33 will still attempt to override compat changes (which on user builds will crash)
+ // before being skipped.
@Rule
- public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
-
- @Rule
- public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+ public final RuleChain chain = RuleChain.outerRule(
+ new DevSdkIgnoreRule()).around(new PlatformCompatChangeRule());
private static final InetAddress ADDRV4 = address("75.208.6.1");
private static final InetAddress ADDRV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
@@ -1262,7 +1264,8 @@
assertFalse(lp.hasIpv4UnreachableDefaultRoute());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
@EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testHasExcludeRoute() {
LinkProperties lp = new LinkProperties();
@@ -1274,7 +1277,8 @@
assertTrue(lp.hasExcludeRoute());
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
@EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testRouteAddWithSameKey() throws Exception {
LinkProperties lp = new LinkProperties();
@@ -1291,7 +1295,8 @@
assertEquals(2, lp.getRoutes().size());
}
- @Test @IgnoreUpTo(SC_V2)
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
@EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testExcludedRoutesEnabled() {
final LinkProperties lp = new LinkProperties();
@@ -1307,8 +1312,8 @@
assertEquals(3, lp.getRoutes().size());
}
- @Test @IgnoreUpTo(SC_V2)
- @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden on T or above")
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
@DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testExcludedRoutesDisabled() {
final LinkProperties lp = new LinkProperties();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
index 098f295..10775d0 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
@@ -75,6 +75,8 @@
@RequiredProperties({DOZE_MODE})
public void testStartActivity_doze() throws Exception {
setDozeMode(true);
+ // TODO (235284115): We need to turn on Doze every time before starting
+ // the activity.
assertLaunchedActivityHasNetworkAccess("testStartActivity_doze");
}
@@ -83,6 +85,8 @@
public void testStartActivity_appStandby() throws Exception {
turnBatteryOn();
setAppIdle(true);
+ // TODO (235284115): We need to put the app into app standby mode every
+ // time before starting the activity.
assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby");
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index dc67c70..dd8b523 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -378,10 +378,6 @@
if (mNetwork == null) {
fail("VPN did not become available after " + TIMEOUT_MS + "ms");
}
-
- // Unfortunately, when the available callback fires, the VPN UID ranges are not yet
- // configured. Give the system some time to do so. http://b/18436087 .
- try { Thread.sleep(3000); } catch(InterruptedException e) {}
}
private void stopVpn() {
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
index 3387fd7..cfd3130 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
@@ -16,6 +16,8 @@
package com.android.cts.net;
+import android.platform.test.annotations.FlakyTest;
+
public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
@Override
@@ -41,6 +43,7 @@
runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
}
+ @FlakyTest(bugId = 231440256)
public void testStartActivity_doze() throws Exception {
runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
index b65fb6b..9a1fa42 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
@@ -22,6 +22,9 @@
/**
* Tests for the {@link android.net.LinkProperties#EXCLUDED_ROUTES} compatibility change.
+ *
+ * TODO: see if we can delete this cumbersome host test by moving the coverage to CtsNetTestCases
+ * and CtsNetTestCasesMaxTargetSdk31.
*/
public class HostsideLinkPropertiesGatingTests extends CompatChangeGatingTestCase {
private static final String TEST_APK = "CtsHostsideNetworkTestsApp3.apk";
@@ -45,8 +48,19 @@
runDeviceCompatTest("testExcludedRoutesChangeDisabled");
}
- public void testExcludedRoutesChangeDisabledByOverride() throws Exception {
+ public void testExcludedRoutesChangeDisabledByOverrideOnDebugBuild() throws Exception {
+ // Must install APK even when skipping test, because tearDown expects uninstall to succeed.
installPackage(TEST_APK, true);
+
+ // This test uses an app with a target SDK where the compat change is on by default.
+ // Because user builds do not allow overriding compat changes, only run this test on debug
+ // builds. This seems better than deleting this test and not running it anywhere because we
+ // could in the future run this test on userdebug builds in presubmit.
+ //
+ // We cannot use assumeXyz here because CompatChangeGatingTestCase ultimately inherits from
+ // junit.framework.TestCase, which does not understand assumption failures.
+ if ("user".equals(getDevice().getProperty("ro.build.type"))) return;
+
runDeviceCompatTestWithChangeDisabled("testExcludedRoutesChangeDisabled");
}
diff --git a/tests/cts/net/src/android/net/cts/RateLimitTest.java b/tests/cts/net/src/android/net/cts/RateLimitTest.java
index 423f213..28cec1a 100644
--- a/tests/cts/net/src/android/net/cts/RateLimitTest.java
+++ b/tests/cts/net/src/android/net/cts/RateLimitTest.java
@@ -304,7 +304,7 @@
// If this value is too low, this test might become flaky because of the burst value that
// allows to send at a higher data rate for a short period of time. The faster the data rate
// and the longer the test, the less this test will be affected.
- final long dataLimitInBytesPerSecond = 1_000_000; // 1MB/s
+ final long dataLimitInBytesPerSecond = 2_000_000; // 2MB/s
long resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(1));
assertGreaterThan("Failed initial test with rate limit disabled", resultInBytesPerSecond,
dataLimitInBytesPerSecond);
@@ -315,9 +315,9 @@
waitForTcPoliceFilterInstalled(Duration.ofSeconds(1));
resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(10));
- // Add 1% tolerance to reduce test flakiness. Burst size is constant at 128KiB.
+ // Add 10% tolerance to reduce test flakiness. Burst size is constant at 128KiB.
assertLessThan("Failed test with rate limit enabled", resultInBytesPerSecond,
- (long) (dataLimitInBytesPerSecond * 1.01));
+ (long) (dataLimitInBytesPerSecond * 1.1));
ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext, -1);
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 6dfadc7..bd1b74a 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -26,13 +26,8 @@
import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
-import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
import static android.net.cts.util.CtsTetheringUtils.isAnyIfaceMatch;
-import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
-import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -73,19 +68,14 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.TestCarrierConfigReceiver;
-
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -94,9 +84,6 @@
@RunWith(AndroidJUnit4.class)
public class TetheringManagerTest {
- @Rule
- public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
-
private Context mContext;
private ConnectivityManager mCm;
@@ -405,7 +392,7 @@
// Override carrier config to ignore entitlement check.
final PersistableBundle bundle = new PersistableBundle();
bundle.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, false);
- overrideCarrierConfig(bundle, CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+ overrideCarrierConfig(bundle);
// Verify that requestLatestTetheringEntitlementResult() can get entitlement
// result TETHER_ERROR_NO_ERROR due to provisioning bypassed.
@@ -413,112 +400,14 @@
TETHERING_WIFI, false, c -> c.run(), listener), TETHER_ERROR_NO_ERROR);
// Reset carrier config.
- overrideCarrierConfig(null, "");
+ overrideCarrierConfig(null);
}
- @Test
- @IgnoreUpTo(SC_V2)
- public void testEnableTethering_carrierUnsupported_noTetheringActive() throws Exception {
- assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
-
- final TestTetheringEventCallback tetherEventCallback =
- mCtsTetheringUtils.registerTetheringEventCallback();
- boolean previousWifiEnabledState = false;
- try {
- tetherEventCallback.assumeWifiTetheringSupported(mContext);
- // Avoid device connected to Wifi network.
- previousWifiEnabledState = ensureCurrentNetworkIsCellular();
- final PersistableBundle bundle = new PersistableBundle();
- bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
- // Override carrier config to make carrier not support.
- overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
-
- mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
-
- mCtsTetheringUtils.expectSoftApDisabled();
- tetherEventCallback.expectNoTetheringActive();
- } finally {
- overrideCarrierConfig(null, "");
- mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
- if (previousWifiEnabledState) {
- mCtsNetUtils.connectToWifi();
- }
- }
- }
-
- @Test
- @IgnoreUpTo(SC_V2)
- public void testEnableTethering_carrierUnsupportByConfigChange_noTetheringActive()
- throws Exception {
- assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
-
- final TestTetheringEventCallback tetherEventCallback =
- mCtsTetheringUtils.registerTetheringEventCallback();
- boolean previousWifiEnabledState = false;
- try {
- tetherEventCallback.assumeWifiTetheringSupported(mContext);
- // Avoid device connected to Wifi network.
- previousWifiEnabledState = ensureCurrentNetworkIsCellular();
- mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
-
- final PersistableBundle bundle = new PersistableBundle();
- bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
- // Override carrier config to make carrier not support.
- overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
-
- mCtsTetheringUtils.expectSoftApDisabled();
- tetherEventCallback.expectNoTetheringActive();
- } finally {
- overrideCarrierConfig(null, "");
- mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
- if (previousWifiEnabledState) {
- mCtsNetUtils.connectToWifi();
- }
- }
- }
-
- @Test
- @IgnoreUpTo(SC_V2)
- public void testRequestLatestEntitlementResult_carrierUnsupported() throws Exception {
- assumeTrue(mTM.isTetheringSupported());
- assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
-
- final PersistableBundle bundle = new PersistableBundle();
- bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
- try {
- // Override carrier config to make carrier not support.
- overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
-
- // Verify that requestLatestTetheringEntitlementResult() can get entitlement
- // result TETHER_ERROR_PROVISIONING_FAILED due to carrier unsupported
- assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
- TETHERING_WIFI,
- false,
- c -> c.run(),
- listener),
- TETHER_ERROR_PROVISIONING_FAILED);
- } finally {
- // Reset carrier config.
- overrideCarrierConfig(null, "");
- }
- }
-
- private void overrideCarrierConfig(PersistableBundle bundle, String configName)
- throws Exception {
- final int timeoutMs = 5_000;
- final int currentSubId = SubscriptionManager.getDefaultSubscriptionId();
- TestCarrierConfigReceiver configListener =
- new TestCarrierConfigReceiver(mContext, currentSubId, timeoutMs, bundle,
- (configs) -> {
- if (bundle == null) {
- // This is to restore carrier config and means no need to do match.
- return true;
- }
- boolean requestConfigValue = bundle.getBoolean(configName);
- boolean receiveConfigValue = configs.getBoolean(configName);
- return Objects.equals(receiveConfigValue, requestConfigValue);
- });
- configListener.overrideCarrierConfigForTest();
+ private void overrideCarrierConfig(PersistableBundle bundle) {
+ final CarrierConfigManager configManager = (CarrierConfigManager) mContext
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ final int subId = SubscriptionManager.getDefaultSubscriptionId();
+ configManager.overrideConfig(subId, bundle);
}
@Test
@@ -532,8 +421,30 @@
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
tetherEventCallback.expectNoTetheringActive();
- // Avoid device connected to Wifi network.
- previousWifiEnabledState = ensureCurrentNetworkIsCellular();
+
+ previousWifiEnabledState = mWm.isWifiEnabled();
+ if (previousWifiEnabledState) {
+ mCtsNetUtils.ensureWifiDisconnected(null);
+ }
+
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ Network activeNetwork = null;
+ try {
+ mCm.registerDefaultNetworkCallback(networkCallback);
+ activeNetwork = networkCallback.waitForAvailable();
+ } finally {
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
+ assertNotNull("No active network. Please ensure the device has working mobile data.",
+ activeNetwork);
+ final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
+
+ // If active nework is ETHERNET, tethering may not use cell network as upstream.
+ assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET));
+
+ assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
+
mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
@@ -553,36 +464,4 @@
}
}
}
-
- /**
- * Make sure current network is cellular data.
- * @return true Previous Wifi state is enabled, false is disabled.
- */
- private boolean ensureCurrentNetworkIsCellular() throws Exception {
- final boolean previousWifiEnabledState = mWm.isWifiEnabled();
- if (previousWifiEnabledState) {
- mCtsNetUtils.ensureWifiDisconnected(null);
- }
-
- final TestNetworkCallback networkCallback = new TestNetworkCallback();
- Network activeNetwork = null;
- try {
- mCm.registerDefaultNetworkCallback(networkCallback);
- activeNetwork = networkCallback.waitForAvailable();
- } finally {
- mCm.unregisterNetworkCallback(networkCallback);
- }
-
- assertNotNull("No active network. Please ensure the device has working mobile data.",
- activeNetwork);
-
- final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
-
- // If active nework is ETHERNET, tethering may not use cell network as upstream.
- assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET));
-
- assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
-
- return previousWifiEnabledState;
- }
}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 97c1265..b3684ac 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -71,7 +71,7 @@
"net-tests-utils",
],
libs: [
- "service-connectivity",
+ "service-connectivity-for-tests",
"services.core",
"services.net",
],
diff --git a/tests/smoketest/Android.bp b/tests/smoketest/Android.bp
index df8ab74..4ab24fc 100644
--- a/tests/smoketest/Android.bp
+++ b/tests/smoketest/Android.bp
@@ -22,6 +22,6 @@
static_libs: [
"androidx.test.rules",
"mockito-target-minus-junit4",
- "service-connectivity",
+ "service-connectivity-for-tests",
],
}
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 3047a16..8dfe924 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -556,14 +557,28 @@
() -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
}
- private void checkNotStartClat(final TestDependencies deps, final boolean verifyTunFd,
- final boolean verifyPacketSockFd, final boolean verifyRawSockFd) throws Exception {
+ private void checkNotStartClat(final TestDependencies deps, final boolean needToCloseTunFd,
+ final boolean needToClosePacketSockFd, final boolean needToCloseRawSockFd)
+ throws Exception {
// [1] Expect that modified TestDependencies can't start clatd.
+ // Use precise check to make sure that there is no unexpected file descriptor closing.
clearInvocations(TUN_PFD, RAW_SOCK_PFD, PACKET_SOCK_PFD);
assertNotStartClat(deps);
- if (verifyTunFd) verify(TUN_PFD).close();
- if (verifyPacketSockFd) verify(PACKET_SOCK_PFD).close();
- if (verifyRawSockFd) verify(RAW_SOCK_PFD).close();
+ if (needToCloseTunFd) {
+ verify(TUN_PFD).close();
+ } else {
+ verify(TUN_PFD, never()).close();
+ }
+ if (needToClosePacketSockFd) {
+ verify(PACKET_SOCK_PFD).close();
+ } else {
+ verify(PACKET_SOCK_PFD, never()).close();
+ }
+ if (needToCloseRawSockFd) {
+ verify(RAW_SOCK_PFD).close();
+ } else {
+ verify(RAW_SOCK_PFD, never()).close();
+ }
// [2] Expect that unmodified TestDependencies can start clatd.
// Used to make sure that the above modified TestDependencies has really broken the
@@ -582,8 +597,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
- false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
}
@Test
@@ -595,8 +610,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
- false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
}
@Test
@@ -607,8 +622,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
- false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
}
@Test
@@ -620,8 +635,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
- false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
}
@Test
@@ -632,8 +647,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
- false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
}
@Test
@@ -644,8 +659,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
- true /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
}
@Test
@@ -657,8 +672,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
- true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
@Test
@@ -669,8 +684,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
- true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
@Test
@@ -682,8 +697,8 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
- true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
@Test
@@ -697,7 +712,7 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
- true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index ec51537..d1bf40e 100644
--- a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -27,6 +27,7 @@
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
@@ -184,7 +185,7 @@
(int) setting);
}
- private void testGetMultipathPreference(
+ private void prepareGetMultipathPreferenceTest(
long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit,
long defaultGlobalSetting, long defaultResSetting, boolean roaming) {
@@ -286,7 +287,7 @@
@Test
public void testGetMultipathPreference_SubscriptionQuota() {
- testGetMultipathPreference(
+ prepareGetMultipathPreferenceTest(
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
@@ -301,7 +302,7 @@
@Test
public void testGetMultipathPreference_UserWarningQuota() {
- testGetMultipathPreference(
+ prepareGetMultipathPreferenceTest(
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
// Remaining days are 29 days from Apr. 2nd to May 1st.
@@ -320,7 +321,7 @@
@Test
public void testGetMultipathPreference_SnoozedWarningQuota() {
- testGetMultipathPreference(
+ prepareGetMultipathPreferenceTest(
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
POLICY_SNOOZED /* policyWarning */,
@@ -339,7 +340,7 @@
@Test
public void testGetMultipathPreference_SnoozedBothQuota() {
- testGetMultipathPreference(
+ prepareGetMultipathPreferenceTest(
DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
// 29 days from Apr. 2nd to May 1st
@@ -356,7 +357,7 @@
@Test
public void testGetMultipathPreference_SettingChanged() {
- testGetMultipathPreference(
+ prepareGetMultipathPreferenceTest(
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
WARNING_DISABLED,
@@ -381,7 +382,7 @@
@Test
public void testGetMultipathPreference_ResourceChanged() {
- testGetMultipathPreference(
+ prepareGetMultipathPreferenceTest(
DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
OPPORTUNISTIC_QUOTA_UNKNOWN,
WARNING_DISABLED,
@@ -404,4 +405,45 @@
verify(mStatsManager, times(1)).registerUsageCallback(
any(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
}
+
+ @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @Test
+ public void testOnThresholdReached() {
+ prepareGetMultipathPreferenceTest(
+ DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
+ DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
+ DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
+ LIMIT_DISABLED,
+ DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
+ 2_500_000 /* defaultResSetting */,
+ false /* roaming */);
+
+ final ArgumentCaptor<NetworkStatsManager.UsageCallback> usageCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkStatsManager.UsageCallback.class);
+ final ArgumentCaptor<NetworkTemplate> networkTemplateCaptor =
+ ArgumentCaptor.forClass(NetworkTemplate.class);
+ // Verify the callback is registered with quota - used = 14 - 2 = 12MB.
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ networkTemplateCaptor.capture(), eq(DataUnit.MEGABYTES.toBytes(12)), any(),
+ usageCallbackCaptor.capture());
+
+ // Capture arguments for later use.
+ final NetworkStatsManager.UsageCallback usageCallback = usageCallbackCaptor.getValue();
+ final NetworkTemplate template = networkTemplateCaptor.getValue();
+ assertNotNull(usageCallback);
+ assertNotNull(template);
+
+ // Decrease quota from 14 to 11, and trigger the event.
+ // TODO: Mock daily and monthly used bytes instead of changing subscription to simulate
+ // remaining quota changed.
+ when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH))
+ .thenReturn(DataUnit.MEGABYTES.toBytes(11));
+ usageCallback.onThresholdReached(template);
+
+ // Callback must have been re-registered with new remaining quota = 11 - 2 = 9MB.
+ verify(mStatsManager, times(1))
+ .unregisterUsageCallback(eq(usageCallback));
+ verify(mStatsManager, times(1)).registerUsageCallback(
+ eq(template), eq(DataUnit.MEGABYTES.toBytes(9)), any(), eq(usageCallback));
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index eb35469..cdfa190 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -1315,7 +1315,7 @@
config -> Arrays.asList(config.flags).contains(flag)));
}
- private void setupPlatformVpnWithSpecificExceptionAndItsErrorCode(IkeException exception,
+ private void doTestPlatformVpnWithException(IkeException exception,
String category, int errorType, int errorCode) throws Exception {
final ArgumentCaptor<IkeSessionCallback> captor =
ArgumentCaptor.forClass(IkeSessionCallback.class);
@@ -1333,6 +1333,7 @@
// state
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+ reset(mIkev2SessionCreator);
final IkeSessionCallback ikeCb = captor.getValue();
ikeCb.onClosedWithException(exception);
@@ -1342,6 +1343,23 @@
if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
.unregisterNetworkCallback(eq(cb));
+ } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE) {
+ // To prevent spending much time to test the retry function, only retry 2 times here.
+ int retryIndex = 0;
+ verify(mIkev2SessionCreator,
+ timeout(((TestDeps) vpn.mDeps).getNextRetryDelaySeconds(retryIndex++) * 1000
+ + TEST_TIMEOUT_MS))
+ .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+
+ // Capture a new IkeSessionCallback to get the latest token.
+ reset(mIkev2SessionCreator);
+ final IkeSessionCallback ikeCb2 = captor.getValue();
+ ikeCb2.onClosedWithException(exception);
+ verify(mIkev2SessionCreator,
+ timeout(((TestDeps) vpn.mDeps).getNextRetryDelaySeconds(retryIndex++) * 1000
+ + TEST_TIMEOUT_MS))
+ .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+ reset(mIkev2SessionCreator);
}
}
@@ -1350,7 +1368,7 @@
final IkeProtocolException exception = mock(IkeProtocolException.class);
final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
when(exception.getErrorType()).thenReturn(errorCode);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
errorCode);
}
@@ -1360,7 +1378,7 @@
final IkeProtocolException exception = mock(IkeProtocolException.class);
final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
when(exception.getErrorType()).thenReturn(errorCode);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
}
@@ -1370,7 +1388,7 @@
final UnknownHostException unknownHostException = new UnknownHostException();
final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
when(exception.getCause()).thenReturn(unknownHostException);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@@ -1382,7 +1400,7 @@
new IkeTimeoutException("IkeTimeoutException");
final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
when(exception.getCause()).thenReturn(ikeTimeoutException);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@@ -1391,7 +1409,7 @@
public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
final IkeNetworkLostException exception = new IkeNetworkLostException(
new Network(100));
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_LOST);
}
@@ -1402,7 +1420,7 @@
final IOException ioException = new IOException();
final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
when(exception.getCause()).thenReturn(ioException);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@@ -1694,6 +1712,11 @@
public DeviceIdleInternal getDeviceIdleInternal() {
return mDeviceIdleInternal;
}
+
+ public long getNextRetryDelaySeconds(int retryCount) {
+ // Simply return retryCount as the delay seconds for retrying.
+ return retryCount;
+ }
}
/**
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index f1820b3..4fbbc75 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -140,16 +140,6 @@
import com.android.testutils.TestBpfMap;
import com.android.testutils.TestableNetworkStatsProviderBinder;
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Clock;
-import java.time.ZoneOffset;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
import libcore.testing.io.TestIoUtils;
import org.junit.After;
@@ -161,6 +151,19 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Clock;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/**
* Tests for {@link NetworkStatsService}.
*
@@ -1105,6 +1108,40 @@
}
@Test
+ public void testGetLatestSummary() throws Exception {
+ // Pretend that network comes online.
+ expectDefaultSettings();
+ NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{buildWifiState()};
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+
+ // Increase arbitrary time which does not align to the bucket edge, create some traffic.
+ incrementCurrentTime(1751000L);
+ NetworkStats.Entry entry = new NetworkStats.Entry(
+ TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 50L, 5L, 51L, 1L, 3L);
+ expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1).insertEntry(entry));
+ expectNetworkStatsUidDetail(buildEmptyStats());
+ forcePollAndWaitForIdle();
+
+ // Verify the mocked stats is returned by querying with the range of the latest bucket.
+ final ZonedDateTime end =
+ ZonedDateTime.ofInstant(mClock.instant(), ZoneId.systemDefault());
+ final ZonedDateTime start = end.truncatedTo(ChronoUnit.HOURS);
+ NetworkStats stats = mSession.getSummaryForNetwork(buildTemplateWifi(TEST_WIFI_NETWORK_KEY),
+ start.toInstant().toEpochMilli(), end.toInstant().toEpochMilli());
+ assertEquals(1, stats.size());
+ assertValues(stats, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+ DEFAULT_NETWORK_ALL, 50L, 5L, 51L, 1L, 3L);
+
+ // For getHistoryIntervalForNetwork, only includes buckets that atomically occur in
+ // the inclusive time range, instead of including the latest bucket. This behavior is
+ // already documented publicly, refer to {@link NetworkStatsManager#queryDetails}.
+ }
+
+ @Test
public void testUidStatsForTransport() throws Exception {
// pretend that network comes online
expectDefaultSettings();
@@ -1135,9 +1172,12 @@
assertEquals(3, stats.size());
entry1.operations = 1;
+ entry1.iface = null;
assertEquals(entry1, stats.getValues(0, null));
entry2.operations = 1;
+ entry2.iface = null;
assertEquals(entry2, stats.getValues(1, null));
+ entry3.iface = null;
assertEquals(entry3, stats.getValues(2, null));
}