Merge "BpfNetMaps.java - array index U32 -> S32"
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 39812cd..43aff7e 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -27,20 +27,33 @@
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringTester.TestDnsPacket;
-import static android.net.TetheringTester.isExpectedIcmpv6Packet;
+import static android.net.TetheringTester.isExpectedIcmpPacket;
+import static android.net.TetheringTester.isExpectedTcpPacket;
import static android.net.TetheringTester.isExpectedUdpDnsPacket;
import static android.net.TetheringTester.isExpectedUdpPacket;
+import static android.system.OsConstants.ICMP_ECHO;
+import static android.system.OsConstants.ICMP_ECHOREPLY;
+import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_IP;
import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
import static com.android.net.module.util.HexDump.dumpHexString;
+import static com.android.net.module.util.IpUtils.icmpChecksum;
+import static com.android.net.module.util.IpUtils.ipChecksum;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -88,6 +101,8 @@
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
import com.android.net.module.util.bpf.TetherStatsValue;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv4Header;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.UdpHeader;
@@ -167,8 +182,11 @@
(Inet6Address) parseNumericAddress("2002:db8:1::515:ca");
private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
+ private static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.wrap(new byte[0]);
private static final short DNS_PORT = 53;
+ private static final short WINDOW = (short) 0x2000;
+ private static final short URGENT_POINTER = 0;
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
@@ -179,6 +197,10 @@
private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
private static final short HOP_LIMIT = 0x40;
+ private static final short ICMPECHO_CODE = 0x0;
+ private static final short ICMPECHO_ID = 0x0;
+ private static final short ICMPECHO_SEQ = 0x0;
+
// TODO: use class DnsPacket to build DNS query and reply message once DnsPacket supports
// building packet for given arguments.
private static final ByteBuffer DNS_QUERY = ByteBuffer.wrap(new byte[] {
@@ -450,7 +472,10 @@
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
do {
byte[] pkt = reader.popPacket(timeoutMs);
- if (isExpectedIcmpv6Packet(pkt, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return;
+ if (isExpectedIcmpPacket(pkt, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ROUTER_ADVERTISEMENT)) {
+ return;
+ }
timeoutMs = deadline - SystemClock.uptimeMillis();
} while (timeoutMs > 0);
@@ -971,14 +996,16 @@
tester.verifyUpload(request, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedIcmpv6Packet(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE);
+ return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REQUEST_TYPE);
});
ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
tester.verifyDownload(reply, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE);
+ return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REPLY_TYPE);
});
}
@@ -1009,26 +1036,21 @@
private static final ByteBuffer TX_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x56, (byte) 0x78 });
+ private short getEthType(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
+ return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6;
+ }
+
+ private int getIpProto(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
+ return isAddressIpv4(srcIp, dstIp) ? IPPROTO_IP : IPPROTO_IPV6;
+ }
+
@NonNull
private ByteBuffer buildUdpPacket(
@Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
short srcPort, short dstPort, @Nullable final ByteBuffer payload)
throws Exception {
- int ipProto;
- short ethType;
- if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) {
- ipProto = IPPROTO_IP;
- ethType = (short) ETHER_TYPE_IPV4;
- } else if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) {
- ipProto = IPPROTO_IPV6;
- ethType = (short) ETHER_TYPE_IPV6;
- } else {
- fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp);
- // Make compiler happy to the uninitialized ipProto and ethType.
- return null; // unreachable, the annotation @NonNull of function return value is true.
- }
-
+ final int ipProto = getIpProto(srcIp, dstIp);
final boolean hasEther = (srcMac != null && dstMac != null);
final int payloadLen = (payload == null) ? 0 : payload.limit();
final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP,
@@ -1036,7 +1058,9 @@
final PacketBuilder packetBuilder = new PacketBuilder(buffer);
// [1] Ethernet header
- if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ethType);
+ if (hasEther) {
+ packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp));
+ }
// [2] IP header
if (ipProto == IPPROTO_IP) {
@@ -1513,12 +1537,156 @@
// TODO: test CLAT bpf maps.
}
+ // TODO: support R device. See b/234727688.
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
public void testTetherClatUdp() throws Exception {
runClatUdpTest();
}
+ // PacketBuilder doesn't support IPv4 ICMP packet. It may need to refactor PacketBuilder first
+ // because ICMP is a specific layer 3 protocol for PacketBuilder which expects packets always
+ // have layer 3 (IP) and layer 4 (TCP, UDP) for now. Since we don't use IPv4 ICMP packet too
+ // much in this test, we just write a ICMP packet builder here.
+ // TODO: move ICMPv4 packet build function to common utilis.
+ @NonNull
+ private ByteBuffer buildIcmpEchoPacketV4(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
+ int type, short id, short seq) throws Exception {
+ if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) {
+ fail("Unsupported ICMP type: " + type);
+ }
+
+ // Build ICMP echo id and seq fields as payload. Ignore the data field.
+ final ByteBuffer payload = ByteBuffer.allocate(4);
+ payload.putShort(id);
+ payload.putShort(seq);
+ payload.rewind();
+
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0;
+ final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class);
+ final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class);
+ final int payloadLen = payload.limit();
+ final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen
+ + Icmpv4HeaderLen + payloadLen);
+
+ // [1] Ethernet header
+ if (hasEther) {
+ final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4);
+ ethHeader.writeToByteBuffer(packet);
+ }
+
+ // [2] IP header
+ final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE,
+ (short) 0 /* totalLength, calculate later */, ID,
+ FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP,
+ (short) 0 /* checksum, calculate later */, srcIp, dstIp);
+ ipv4Header.writeToByteBuffer(packet);
+
+ // [3] ICMP header
+ final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE,
+ (short) 0 /* checksum, calculate later */);
+ icmpv4Header.writeToByteBuffer(packet);
+
+ // [4] Payload
+ packet.put(payload);
+ packet.flip();
+
+ // [5] Finalize packet
+ // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset
+ // in buffer equals ethernet header length because IPv4 header is located next to ethernet
+ // header. Otherwise, IPv4 header offset is 0.
+ final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0;
+
+ // Populate the IPv4 totalLength field.
+ packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET,
+ (short) (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen));
+
+ // Populate the IPv4 header checksum field.
+ packet.putShort(ipv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
+ ipChecksum(packet, ipv4HeaderOffset /* headerOffset */));
+
+ // Populate the ICMP checksum field.
+ packet.putShort(ipv4HeaderOffset + IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET,
+ icmpChecksum(packet, ipv4HeaderOffset + IPV4_HEADER_MIN_LEN,
+ Icmpv4HeaderLen + payloadLen));
+ return packet;
+ }
+
+ @NonNull
+ private ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp,
+ @NonNull final Inet4Address dstIp, int type, short id, short seq)
+ throws Exception {
+ return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp,
+ type, id, seq);
+ }
+
+ @Test
+ public void testIcmpv4Echo() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+ toList(TEST_IP4_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // See the same reason in runUdp4Test().
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+ final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
+ tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
+ tester.verifyUpload(request, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, false /* hasEth */, true /* isIpv4 */, ICMP_ECHO);
+ });
+
+ final ByteBuffer reply = buildIcmpEchoPacketV4(REMOTE_IP4_ADDR /* srcIp*/,
+ (Inet4Address) TEST_IP4_ADDR.getAddress() /* dstIp */, ICMP_ECHOREPLY, ICMPECHO_ID,
+ ICMPECHO_SEQ);
+ tester.verifyDownload(reply, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
+ });
+ }
+
+ // TODO: support R device. See b/234727688.
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherClatIcmp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
+
+ // Send an IPv4 ICMP packet in original direction.
+ // IPv4 packet -- CLAT translation --> IPv6 packet
+ final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
+ tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
+ (Inet4Address) REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
+ tester.verifyUpload(request, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REQUEST_TYPE);
+ });
+
+ // Send an IPv6 ICMP packet in reply direction.
+ // IPv6 packet -- CLAT translation --> IPv4 packet
+ final ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(
+ (Inet6Address) REMOTE_NAT64_ADDR /* srcIp */, clatIp6 /* dstIp */);
+ tester.verifyDownload(reply, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
+ });
+ }
+
@NonNull
private ByteBuffer buildDnsReplyMessageById(short id) {
byte[] replyMessage = Arrays.copyOf(DNS_REPLY, DNS_REPLY.length);
@@ -1607,6 +1775,190 @@
(short) udpHeader.srcPort, (short) dnsQuery.getHeader().id, tester);
}
+ @NonNull
+ private ByteBuffer buildTcpPacket(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
+ short srcPort, short dstPort, final short seq, final short ack,
+ final byte tcpFlags, @NonNull final ByteBuffer payload) throws Exception {
+ final int ipProto = getIpProto(srcIp, dstIp);
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_TCP,
+ payload.limit());
+ final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+
+ // [1] Ethernet header
+ if (hasEther) {
+ packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp));
+ }
+
+ // [2] IP header
+ if (ipProto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_TCP, (Inet4Address) srcIp, (Inet4Address) dstIp);
+ } else {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_TCP,
+ HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
+ }
+
+ // [3] TCP header
+ packetBuilder.writeTcpHeader(srcPort, dstPort, seq, ack, tcpFlags, WINDOW, URGENT_POINTER);
+
+ // [4] Payload
+ buffer.put(payload);
+ // in case data might be reused by caller, restore the position and
+ // limit of bytebuffer.
+ payload.clear();
+
+ return packetBuilder.finalizePacket();
+ }
+
+ private void sendDownloadPacketTcp(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
+ @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
+ boolean is6To4) throws Exception {
+ if (is6To4) {
+ assertFalse("CLAT download test must sends IPv6 packet", isAddressIpv4(srcIp, dstIp));
+ }
+
+ // Expected received TCP packet IP protocol. While testing CLAT (is6To4 = true), the packet
+ // on downstream must be IPv4. Otherwise, the IP protocol of test packet is the same on
+ // both downstream and upstream.
+ final boolean isIpv4 = is6To4 ? true : isAddressIpv4(srcIp, dstIp);
+
+ final ByteBuffer testPacket = buildTcpPacket(null /* srcMac */, null /* dstMac */,
+ srcIp, dstIp, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */, seq, ack,
+ tcpFlags, payload);
+ tester.verifyDownload(testPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedTcpPacket(p, true /* hasEther */, isIpv4, seq, payload);
+ });
+ }
+
+ private void sendUploadPacketTcp(@NonNull final MacAddress srcMac,
+ @NonNull final MacAddress dstMac, @NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
+ @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
+ boolean is4To6) throws Exception {
+ if (is4To6) {
+ assertTrue("CLAT upload test must sends IPv4 packet", isAddressIpv4(srcIp, dstIp));
+ }
+
+ // Expected received TCP packet IP protocol. While testing CLAT (is4To6 = true), the packet
+ // on upstream must be IPv6. Otherwise, the IP protocol of test packet is the same on
+ // both downstream and upstream.
+ final boolean isIpv4 = is4To6 ? false : isAddressIpv4(srcIp, dstIp);
+
+ final ByteBuffer testPacket = buildTcpPacket(srcMac, dstMac, srcIp, dstIp,
+ LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, seq, ack, tcpFlags,
+ payload);
+ tester.verifyUpload(testPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedTcpPacket(p, false /* hasEther */, isIpv4, seq, payload);
+ });
+ }
+
+ void runTcpTest(
+ @NonNull final MacAddress uploadSrcMac, @NonNull final MacAddress uploadDstMac,
+ @NonNull final InetAddress uploadSrcIp, @NonNull final InetAddress uploadDstIp,
+ @NonNull final InetAddress downloadSrcIp, @NonNull final InetAddress downloadDstIp,
+ @NonNull final TetheringTester tester, boolean isClat) throws Exception {
+ // Three way handshake and data transfer.
+ //
+ // Server (base seq = 2000) Client (base seq = 1000)
+ // | |
+ // | [1] [SYN] SEQ = 1000 |
+ // |<---------------------------------------------------------| -
+ // | | ^
+ // | [2] [SYN + ACK] SEQ = 2000, ACK = 1000+1 | |
+ // |--------------------------------------------------------->| three way handshake
+ // | | |
+ // | [3] [ACK] SEQ = 1001, ACK = 2000+1 | v
+ // |<---------------------------------------------------------| -
+ // | | ^
+ // | [4] [ACK] SEQ = 1001, ACK = 2001, 2 byte payload | |
+ // |<---------------------------------------------------------| data transfer
+ // | | |
+ // | [5] [ACK] SEQ = 2001, ACK = 1001+2, 2 byte payload | v
+ // |--------------------------------------------------------->| -
+ // | |
+ //
+
+ // This test can only verify the packets are transferred end to end but TCP state.
+ // TODO: verify TCP state change via /proc/net/nf_conntrack or netlink conntrack event.
+ // [1] [UPLOAD] [SYN]: SEQ = 1000
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1000 /* seq */, (short) 0 /* ack */, TCPHDR_SYN, EMPTY_PAYLOAD,
+ tester, isClat /* is4To6 */);
+
+ // [2] [DONWLOAD] [SYN + ACK]: SEQ = 2000, ACK = 1001
+ sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2000 /* seq */,
+ (short) 1001 /* ack */, (byte) ((TCPHDR_SYN | TCPHDR_ACK) & 0xff), EMPTY_PAYLOAD,
+ tester, isClat /* is6To4 */);
+
+ // [3] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, EMPTY_PAYLOAD, tester,
+ isClat /* is4To6 */);
+
+ // [4] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001, 2 byte payload
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, TX_PAYLOAD,
+ tester, isClat /* is4To6 */);
+
+ // [5] [DONWLOAD] [ACK]: SEQ = 2001, ACK = 1003, 2 byte payload
+ sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2001 /* seq */,
+ (short) 1003 /* ack */, TCPHDR_ACK, RX_PAYLOAD, tester, isClat /* is6To4 */);
+
+ // TODO: test BPF offload maps.
+ }
+
+ @Test
+ public void testTetherTcpV4() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+ toList(TEST_IP4_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // See the same reason in runUdp4Test().
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
+ REMOTE_IP4_ADDR /* downloadSrcIp */, TEST_IP4_ADDR.getAddress() /* downloadDstIp */,
+ tester, false /* isClat */);
+ }
+
+ @Test
+ public void testTetherTcpV6() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv6Addr /* uploadSrcIp */, REMOTE_IP6_ADDR /* uploadDstIp */,
+ REMOTE_IP6_ADDR /* downloadSrcIp */, tethered.ipv6Addr /* downloadDstIp */,
+ tester, false /* isClat */);
+ }
+
+ @Test
+ public void testTetherClatTcp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
+ REMOTE_NAT64_ADDR /* downloadSrcIp */, clatIp6 /* downloadDstIp */,
+ tester, true /* isClat */);
+ }
+
private <T> List<T> toList(T... array) {
return Arrays.asList(array);
}
diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java
index 9cc2e49..ae39b24 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/src/android/net/TetheringTester.java
@@ -17,7 +17,9 @@
package android.net;
import static android.net.InetAddresses.parseNumericAddress;
+import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.DnsPacket.ANSECTION;
@@ -39,6 +41,7 @@
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -57,6 +60,7 @@
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv4Header;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
@@ -64,6 +68,7 @@
import com.android.net.module.util.structs.NsHeader;
import com.android.net.module.util.structs.PrefixInformationOption;
import com.android.net.module.util.structs.RaHeader;
+import com.android.net.module.util.structs.TcpHeader;
import com.android.net.module.util.structs.UdpHeader;
import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
@@ -268,7 +273,8 @@
private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) {
ByteBuffer buf = ByteBuffer.wrap(packet);
- if (!isExpectedIcmpv6Packet(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) {
+ if (!isExpectedIcmpPacket(buf, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ROUTER_ADVERTISEMENT)) {
fail("Parsing RA packet fail");
}
@@ -298,7 +304,8 @@
sendRsPacket(srcMac, dstMac);
final byte[] raPacket = verifyPacketNotNull("Receive RA fail", getDownloadPacket(p -> {
- return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
+ return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ROUTER_ADVERTISEMENT);
}));
final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
@@ -360,20 +367,27 @@
}
}
- public static boolean isExpectedIcmpv6Packet(byte[] packet, boolean hasEth, int type) {
+ public static boolean isExpectedIcmpPacket(byte[] packet, boolean hasEth, boolean isIpv4,
+ int type) {
final ByteBuffer buf = ByteBuffer.wrap(packet);
- return isExpectedIcmpv6Packet(buf, hasEth, type);
+ return isExpectedIcmpPacket(buf, hasEth, isIpv4, type);
}
- private static boolean isExpectedIcmpv6Packet(ByteBuffer buf, boolean hasEth, int type) {
+ private static boolean isExpectedIcmpPacket(ByteBuffer buf, boolean hasEth, boolean isIpv4,
+ int type) {
try {
- if (hasEth && !hasExpectedEtherHeader(buf, false /* isIpv4 */)) return false;
+ if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
- if (!hasExpectedIpHeader(buf, false /* isIpv4 */, IPPROTO_ICMPV6)) return false;
+ final int ipProto = isIpv4 ? IPPROTO_ICMP : IPPROTO_ICMPV6;
+ if (!hasExpectedIpHeader(buf, isIpv4, ipProto)) return false;
- return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
+ if (isIpv4) {
+ return Struct.parse(Icmpv4Header.class, buf).type == (short) type;
+ } else {
+ return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
+ }
} catch (Exception e) {
- // Parsing packet fail means it is not icmpv6 packet.
+ // Parsing packet fail means it is not icmp packet.
}
return false;
@@ -578,6 +592,42 @@
return true;
}
+
+ private static boolean isTcpSynPacket(@NonNull final TcpHeader tcpHeader) {
+ return (tcpHeader.dataOffsetAndControlBits & TCPHDR_SYN) != 0;
+ }
+
+ public static boolean isExpectedTcpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
+ boolean isIpv4, int seq, @NonNull final ByteBuffer payload) {
+ final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
+ try {
+ if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
+
+ if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_TCP)) return false;
+
+ final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, buf);
+ if (tcpHeader.seq != seq) return false;
+
+ // Don't try to parse the payload if it is a TCP SYN segment because additional TCP
+ // option MSS may be added in the SYN segment. Currently, TetherController uses
+ // iptables to limit downstream MSS for IPv4. The additional TCP options will be
+ // misunderstood as payload because parsing TCP options are not supported by class
+ // TcpHeader for now. See TetherController::setupIptablesHooks.
+ // TODO: remove once TcpHeader supports parsing TCP options.
+ if (isTcpSynPacket(tcpHeader)) {
+ Log.d(TAG, "Found SYN segment. Ignore parsing the remaining part of packet.");
+ return true;
+ }
+
+ if (payload.limit() != buf.remaining()) return false;
+ return Arrays.equals(getRemaining(buf), getRemaining(payload.asReadOnlyBuffer()));
+ } catch (Exception e) {
+ // Parsing packet fail means it is not tcp packet.
+ }
+
+ return false;
+ }
+
private void sendUploadPacket(ByteBuffer packet) throws Exception {
mDownstreamReader.sendResponse(packet);
}
diff --git a/common/src/com/android/net/module/util/bpf/CookieTagMapValue.java b/common/src/com/android/net/module/util/bpf/CookieTagMapValue.java
index e1a221f..3fbd6fc 100644
--- a/common/src/com/android/net/module/util/bpf/CookieTagMapValue.java
+++ b/common/src/com/android/net/module/util/bpf/CookieTagMapValue.java
@@ -24,13 +24,13 @@
* Value for cookie tag map.
*/
public class CookieTagMapValue extends Struct {
- @Field(order = 0, type = Type.U32)
- public final long uid;
+ @Field(order = 0, type = Type.S32)
+ public final int uid;
@Field(order = 1, type = Type.U32)
public final long tag;
- public CookieTagMapValue(final long uid, final long tag) {
+ public CookieTagMapValue(final int uid, final long tag) {
this.uid = uid;
this.tag = tag;
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 39a705f..0da7b6f 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -164,7 +164,7 @@
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
@@ -408,7 +408,7 @@
* mActiveUidCounterSet to avoid accessing kernel too frequently.
*/
private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
- private final IBpfMap<U32, U8> mUidCounterSetMap;
+ private final IBpfMap<S32, U8> mUidCounterSetMap;
private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap;
private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapA;
private final IBpfMap<StatsMapKey, StatsMapValue> mStatsMapB;
@@ -741,10 +741,10 @@
}
/** Get counter sets map for each UID. */
- public IBpfMap<U32, U8> getUidCounterSetMap() {
+ public IBpfMap<S32, U8> getUidCounterSetMap() {
try {
- return new BpfMap<U32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR,
- U32.class, U8.class);
+ return new BpfMap<S32, U8>(UID_COUNTERSET_MAP_PATH, BpfMap.BPF_F_RDWR,
+ S32.class, U8.class);
} catch (ErrnoException e) {
Log.wtf(TAG, "Cannot open uid counter set map: " + e);
return null;
@@ -1747,7 +1747,7 @@
if (set == SET_DEFAULT) {
try {
- mUidCounterSetMap.deleteEntry(new U32(uid));
+ mUidCounterSetMap.deleteEntry(new S32(uid));
} catch (ErrnoException e) {
Log.w(TAG, "UidCounterSetMap.deleteEntry(" + uid + ") failed with errno: " + e);
}
@@ -1755,7 +1755,7 @@
}
try {
- mUidCounterSetMap.updateEntry(new U32(uid), new U8((short) set));
+ mUidCounterSetMap.updateEntry(new S32(uid), new U8((short) set));
} catch (ErrnoException e) {
Log.w(TAG, "UidCounterSetMap.updateEntry(" + uid + ", " + set
+ ") failed with errno: " + e);
@@ -2472,7 +2472,7 @@
deleteStatsMapTagData(mStatsMapB, uid);
try {
- mUidCounterSetMap.deleteEntry(new U32(uid));
+ mUidCounterSetMap.deleteEntry(new S32(uid));
} catch (ErrnoException e) {
logErrorIfNotErrNoent(e, "Failed to delete tag data from uid counter set map");
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 8ec979a..9322172 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -353,6 +353,12 @@
// connect anyway?" dialog after the user selects a network that doesn't validate.
private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+ // How long to wait before considering that a network is bad in the absence of any form
+ // of connectivity (valid, partial, captive portal). If none has been detected after this
+ // delay, the stack considers this network bad, which may affect how it's handled in ranking
+ // according to config_networkAvoidBadWifi.
+ private static final int INITIAL_EVALUATION_TIMEOUT_MS = 20 * 1000;
+
// Default to 30s linger time-out, and 5s for nascent network. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
@@ -581,12 +587,6 @@
private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
/**
- * used to ask the user to confirm a connection to an unvalidated network.
- * obj = network
- */
- private static final int EVENT_PROMPT_UNVALIDATED = 29;
-
- /**
* used internally to (re)configure always-on networks.
*/
private static final int EVENT_CONFIGURE_ALWAYS_ON_NETWORKS = 30;
@@ -725,6 +725,14 @@
private static final int EVENT_INGRESS_RATE_LIMIT_CHANGED = 56;
/**
+ * The initial evaluation period is over for this network.
+ *
+ * If no form of connectivity has been found on this network (valid, partial, captive portal)
+ * then the stack will now consider it to have been determined bad.
+ */
+ private static final int EVENT_INITIAL_EVALUATION_TIMEOUT = 57;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -3342,17 +3350,6 @@
pw.increaseIndent();
mNetworkActivityTracker.dump(pw);
pw.decreaseIndent();
-
- // pre-T is logged by netd.
- if (SdkLevel.isAtLeastT()) {
- pw.println();
- pw.println("BPF programs & maps:");
- pw.increaseIndent();
- // Flush is required. Otherwise, the traces in fd can interleave with traces in pw.
- pw.flush();
- dumpTrafficController(pw, fd, /*verbose=*/ true);
- pw.decreaseIndent();
- }
}
private void dumpNetworks(IndentingPrintWriter pw) {
@@ -3791,7 +3788,17 @@
private void handleNetworkTested(
@NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) {
- final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0);
+ final boolean valid = (testResult & NETWORK_VALIDATION_RESULT_VALID) != 0;
+ final boolean partial = (testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0;
+ final boolean captive = !TextUtils.isEmpty(redirectUrl);
+
+ // If there is any kind of working networking, then the NAI has been evaluated
+ // once. {@see NetworkAgentInfo#setEvaluated}, which returns whether this is
+ // the first time this ever happened.
+ final boolean someConnectivity = (valid || partial || captive);
+ final boolean becameEvaluated = someConnectivity && nai.setEvaluated();
+ if (becameEvaluated) nai.updateScoreForNetworkAgentUpdate();
+
if (!valid && shouldIgnoreValidationFailureAfterRoam(nai)) {
// Assume the validation failure is due to a temporary failure after roaming
// and ignore it. NetworkMonitor will continue to retry validation. If it
@@ -3834,8 +3841,13 @@
}
} else if (partialConnectivityChanged) {
updateCapabilitiesForNetwork(nai);
+ } else if (becameEvaluated) {
+ // If valid or partial connectivity changed, updateCapabilities* has
+ // done the rematch.
+ rematchAllNetworksAndRequests();
}
updateInetCondition(nai);
+
// Let the NetworkAgent know the state of its network
// TODO: Evaluate to update partial connectivity to status to NetworkAgent.
nai.onValidationStatusChanged(
@@ -3843,13 +3855,13 @@
redirectUrl);
// If NetworkMonitor detects partial connectivity before
- // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
+ // EVENT_INITIAL_EVALUATION_TIMEOUT arrives, show the partial connectivity notification
// immediately. Re-notify partial connectivity silently if no internet
// notification already there.
if (!wasPartial && nai.partialConnectivity()) {
// Remove delayed message if there is a pending message.
- mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
- handlePromptUnvalidated(nai.network);
+ mHandler.removeMessages(EVENT_INITIAL_EVALUATION_TIMEOUT, nai.network);
+ handleInitialEvaluationTimeout(nai.network);
}
if (wasValidated && !nai.isValidated()) {
@@ -4949,16 +4961,11 @@
}
}
- private void scheduleUnvalidatedPrompt(@NonNull final Network network) {
- scheduleUnvalidatedPrompt(network, PROMPT_UNVALIDATED_DELAY_MS);
- }
-
- /** Schedule unvalidated prompt for testing */
+ /** Schedule evaluation timeout */
@VisibleForTesting
- public void scheduleUnvalidatedPrompt(@NonNull final Network network, final long delayMs) {
- if (VDBG) log("scheduleUnvalidatedPrompt " + network);
+ public void scheduleEvaluationTimeout(@NonNull final Network network, final long delayMs) {
mHandler.sendMessageDelayed(
- mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, network), delayMs);
+ mHandler.obtainMessage(EVENT_INITIAL_EVALUATION_TIMEOUT, network), delayMs);
}
@Override
@@ -5193,23 +5200,28 @@
return false;
}
- private void handlePromptUnvalidated(Network network) {
- if (VDBG || DDBG) log("handlePromptUnvalidated " + network);
- NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ private void handleInitialEvaluationTimeout(@NonNull final Network network) {
+ if (VDBG || DDBG) log("handleInitialEvaluationTimeout " + network);
- if (nai == null || !shouldPromptUnvalidated(nai)) {
- return;
+ NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (null == nai) return;
+
+ if (nai.setEvaluated()) {
+ // If setEvaluated() returned true, the network never had any form of connectivity.
+ // This may have an impact on request matching if bad WiFi avoidance is off and the
+ // network was found not to have Internet access.
+ nai.updateScoreForNetworkAgentUpdate();
+ rematchAllNetworksAndRequests();
}
+ if (!shouldPromptUnvalidated(nai)) return;
+
// Stop automatically reconnecting to this network in the future. Automatically connecting
// to a network that provides no or limited connectivity is not useful, because the user
// cannot use that network except through the notification shown by this method, and the
// notification is only shown if the network is explicitly selected by the user.
nai.onPreventAutomaticReconnect();
- // TODO: Evaluate if it's needed to wait 8 seconds for triggering notification when
- // NetworkMonitor detects the network is partial connectivity. Need to change the design to
- // popup the notification immediately when the network is partial connectivity.
if (nai.partialConnectivity()) {
showNetworkNotification(nai, NotificationType.PARTIAL_CONNECTIVITY);
} else {
@@ -5348,8 +5360,8 @@
handleSetAvoidUnvalidated((Network) msg.obj);
break;
}
- case EVENT_PROMPT_UNVALIDATED: {
- handlePromptUnvalidated((Network) msg.obj);
+ case EVENT_INITIAL_EVALUATION_TIMEOUT: {
+ handleInitialEvaluationTimeout((Network) msg.obj);
break;
}
case EVENT_CONFIGURE_ALWAYS_ON_NETWORKS: {
@@ -9213,7 +9225,7 @@
networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
params.networkCapabilities);
}
- scheduleUnvalidatedPrompt(networkAgent.network);
+ scheduleEvaluationTimeout(networkAgent.network, INITIAL_EVALUATION_TIMEOUT_MS);
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index a4c70c8..c732170 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -61,6 +61,7 @@
import android.util.Pair;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.WakeupMessage;
import com.android.modules.utils.build.SdkLevel;
@@ -399,6 +400,12 @@
return true;
}
+ /** When this network ever concluded its first evaluation, or 0 if this never happened. */
+ @VisibleForTesting
+ public long getFirstEvaluationConcludedTime() {
+ return mFirstEvaluationConcludedTime;
+ }
+
// Delay between when the network is disconnected and when the native network is destroyed.
public int teardownDelayMs;
diff --git a/tests/common/java/android/net/metrics/IpConnectivityLogTest.java b/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
index ab97f2d..93cf748 100644
--- a/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
+++ b/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
@@ -23,7 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.net.ConnectivityMetricsEvent;
@@ -51,6 +51,7 @@
private static final int FAKE_NET_ID = 100;
private static final int[] FAKE_TRANSPORT_TYPES = unpackBits(TRANSPORT_WIFI);
private static final long FAKE_TIME_STAMP = System.currentTimeMillis();
+ private static final long THREAD_TIMEOUT_MS = 10_000L;
private static final String FAKE_INTERFACE_NAME = "test";
private static final IpReachabilityEvent FAKE_EV =
new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
@@ -93,22 +94,26 @@
final int nCallers = 10;
final int nEvents = 10;
+ final Thread[] threads = new Thread[nCallers];
for (int n = 0; n < nCallers; n++) {
final int i = n;
- new Thread() {
- public void run() {
- for (int j = 0; j < nEvents; j++) {
- assertTrue(logger.log(makeExpectedEvent(
- FAKE_TIME_STAMP + i * 100 + j,
- FAKE_NET_ID + i * 100 + j,
- ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR,
- FAKE_INTERFACE_NAME)));
- }
+ threads[i] = new Thread(() -> {
+ for (int j = 0; j < nEvents; j++) {
+ assertTrue(logger.log(makeExpectedEvent(
+ FAKE_TIME_STAMP + i * 100 + j,
+ FAKE_NET_ID + i * 100 + j,
+ ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR,
+ FAKE_INTERFACE_NAME)));
}
- }.start();
+ });
+ threads[i].start();
+ }
+ // To ensure the events have been sent out on each thread. Wait for the thread to die.
+ for (Thread thread : threads) {
+ thread.join(THREAD_TIMEOUT_MS);
}
- List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
+ final List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents);
Collections.sort(got, EVENT_COMPARATOR);
Iterator<ConnectivityMetricsEvent> iter = got.iterator();
for (int i = 0; i < nCallers; i++) {
@@ -123,17 +128,13 @@
}
}
- private List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
+ private List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
ArgumentCaptor<ConnectivityMetricsEvent> captor =
ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
- verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
+ verify(mMockService, times(n)).logEvent(captor.capture());
return captor.getAllValues();
}
- private List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
- return verifyEvents(n, 10);
- }
-
private ConnectivityMetricsEvent makeExpectedEvent(long timestamp, int netId, long transports,
String ifname) {
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 687f157..d2a7135 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -3539,7 +3539,7 @@
**/
private int expectUnvalidationCheckWillNotify(TestNetworkAgentWrapper agent,
NotificationType type) {
- mService.scheduleUnvalidatedPrompt(agent.getNetwork(), 0 /* delayMs */);
+ mService.scheduleEvaluationTimeout(agent.getNetwork(), 0 /* delayMs */);
waitForIdle();
return expectNotification(agent, type);
}
@@ -3561,7 +3561,7 @@
* @return the notification ID.
**/
private void expectUnvalidationCheckWillNotNotify(TestNetworkAgentWrapper agent) {
- mService.scheduleUnvalidatedPrompt(agent.getNetwork(), 0 /*delayMs */);
+ mService.scheduleEvaluationTimeout(agent.getNetwork(), 0 /*delayMs */);
waitForIdle();
verify(mNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(), any());
}
@@ -3784,6 +3784,63 @@
callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
}
+ private void doTestFirstEvaluation(
+ @NonNull final Consumer<TestNetworkAgentWrapper> doConnect,
+ final boolean waitForSecondCaps,
+ final boolean evaluatedByValidation)
+ throws Exception {
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ doConnect.accept(mWiFiNetworkAgent);
+ // Expect the available callbacks, but don't require specific values for their arguments
+ // since this method doesn't know how the network was connected.
+ callback.expectCallback(CallbackEntry.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiNetworkAgent);
+ callback.expectCallback(CallbackEntry.BLOCKED_STATUS, mWiFiNetworkAgent);
+ if (waitForSecondCaps) {
+ // This is necessary because of b/245893397, the same bug that happens where we use
+ // expectAvailableDoubleValidatedCallbacks.
+ callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
+ }
+ final NetworkAgentInfo nai =
+ mService.getNetworkAgentInfoForNetwork(mWiFiNetworkAgent.getNetwork());
+ final long firstEvaluation = nai.getFirstEvaluationConcludedTime();
+ if (evaluatedByValidation) {
+ assertNotEquals(0L, firstEvaluation);
+ } else {
+ assertEquals(0L, firstEvaluation);
+ }
+ mService.scheduleEvaluationTimeout(mWiFiNetworkAgent.getNetwork(), 0L /* timeout */);
+ waitForIdle();
+ if (evaluatedByValidation) {
+ assertEquals(firstEvaluation, nai.getFirstEvaluationConcludedTime());
+ } else {
+ assertNotEquals(0L, nai.getFirstEvaluationConcludedTime());
+ }
+ mWiFiNetworkAgent.disconnect();
+ callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+
+ mCm.unregisterNetworkCallback(callback);
+ }
+
+ @Test
+ public void testEverEvaluated() throws Exception {
+ doTestFirstEvaluation(naw -> naw.connect(true /* validated */),
+ true /* waitForSecondCaps */, true /* immediatelyEvaluated */);
+ doTestFirstEvaluation(naw -> naw.connectWithPartialConnectivity(),
+ true /* waitForSecondCaps */, true /* immediatelyEvaluated */);
+ doTestFirstEvaluation(naw -> naw.connectWithCaptivePortal(TEST_REDIRECT_URL, false),
+ true /* waitForSecondCaps */, true /* immediatelyEvaluated */);
+ doTestFirstEvaluation(naw -> naw.connect(false /* validated */),
+ false /* waitForSecondCaps */, false /* immediatelyEvaluated */);
+ }
+
private void tryNetworkFactoryRequests(int capability) throws Exception {
// Verify NOT_RESTRICTED is set appropriately
final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability)
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index fdbccba..6448819 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -139,7 +139,7 @@
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U8;
import com.android.net.module.util.bpf.CookieTagMapKey;
import com.android.net.module.util.bpf.CookieTagMapValue;
@@ -249,7 +249,7 @@
private HandlerThread mHandlerThread;
@Mock
private LocationPermissionChecker mLocationPermissionChecker;
- private TestBpfMap<U32, U8> mUidCounterSetMap = spy(new TestBpfMap<>(U32.class, U8.class));
+ private TestBpfMap<S32, U8> mUidCounterSetMap = spy(new TestBpfMap<>(S32.class, U8.class));
@Mock
private BpfNetMaps mBpfNetMaps;
@Mock
@@ -478,7 +478,7 @@
}
@Override
- public IBpfMap<U32, U8> getUidCounterSetMap() {
+ public IBpfMap<S32, U8> getUidCounterSetMap() {
return mUidCounterSetMap;
}
@@ -646,7 +646,7 @@
mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
mService.noteUidForeground(UID_RED, true);
verify(mUidCounterSetMap).updateEntry(
- eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ eq(new S32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
forcePollAndWaitForIdle();
@@ -1311,7 +1311,7 @@
.insertEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
mService.noteUidForeground(UID_RED, true);
verify(mUidCounterSetMap).updateEntry(
- eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ eq(new S32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
forcePollAndWaitForIdle();
@@ -1927,7 +1927,7 @@
mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
mService.noteUidForeground(UID_RED, true);
verify(mUidCounterSetMap).updateEntry(
- eq(new U32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
+ eq(new S32(UID_RED)), eq(new U8((short) SET_FOREGROUND)));
mService.incrementOperationCount(UID_RED, 0xFAAD, 6);
forcePollAndWaitForIdle();
@@ -2424,13 +2424,13 @@
mAppUidStatsMap.insertEntry(new UidStatsMapKey(uid), new StatsMapValue(10, 10000, 6, 6000));
- mUidCounterSetMap.insertEntry(new U32(uid), new U8((short) 1));
+ mUidCounterSetMap.insertEntry(new S32(uid), new U8((short) 1));
assertTrue(cookieTagMapContainsUid(uid));
assertTrue(statsMapContainsUid(mStatsMapA, uid));
assertTrue(statsMapContainsUid(mStatsMapB, uid));
assertTrue(mAppUidStatsMap.containsKey(new UidStatsMapKey(uid)));
- assertTrue(mUidCounterSetMap.containsKey(new U32(uid)));
+ assertTrue(mUidCounterSetMap.containsKey(new S32(uid)));
}
@Test
@@ -2447,14 +2447,14 @@
assertFalse(statsMapContainsUid(mStatsMapA, UID_BLUE));
assertFalse(statsMapContainsUid(mStatsMapB, UID_BLUE));
assertFalse(mAppUidStatsMap.containsKey(new UidStatsMapKey(UID_BLUE)));
- assertFalse(mUidCounterSetMap.containsKey(new U32(UID_BLUE)));
+ assertFalse(mUidCounterSetMap.containsKey(new S32(UID_BLUE)));
// assert that UID_RED related tag data is still in the maps.
assertTrue(cookieTagMapContainsUid(UID_RED));
assertTrue(statsMapContainsUid(mStatsMapA, UID_RED));
assertTrue(statsMapContainsUid(mStatsMapB, UID_RED));
assertTrue(mAppUidStatsMap.containsKey(new UidStatsMapKey(UID_RED)));
- assertTrue(mUidCounterSetMap.containsKey(new U32(UID_RED)));
+ assertTrue(mUidCounterSetMap.containsKey(new S32(UID_RED)));
}
private void assertDumpContains(final String dump, final String message) {