Snap for 13270875 from 83ea07ed94890b52265e07d6760c7ed8f4cbd344 to sdk-release Change-Id: I4b3a4eeb3eaee7e9ffa574466c73d28d0b10cfbc
diff --git a/res/values/config.xml b/res/values/config.xml index 8d6b64e..d9ee36d 100644 --- a/res/values/config.xml +++ b/res/values/config.xml
@@ -51,6 +51,8 @@ </string-array> <string-array name="config_captive_portal_https_urls" translatable="false"> </string-array> + <bool name="config_probe_multi_http_https_url_serial">false</bool> + <integer name="config_serial_url_probe_gap_time">1000</integer> <!-- Customized default DNS Servers address. --> <string-array name="config_default_dns_servers" translatable="false">
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml index 0aeaaec..8e8fe08 100644 --- a/res/values/overlayable.xml +++ b/res/values/overlayable.xml
@@ -42,6 +42,8 @@ <item type="array" name="config_captive_portal_http_urls"/> <item type="array" name="config_captive_portal_https_urls"/> <item type="array" name="config_captive_portal_fallback_urls"/> + <item type="bool" name="config_probe_multi_http_https_url_serial"/> + <item type="integer" name="config_serial_url_probe_gap_time"/> <item type="bool" name="config_no_sim_card_uses_neighbor_mcc"/> <!-- Configuration value for DhcpResults --> <item type="array" name="config_default_dns_servers"/>
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index dfab0df..249a1e5 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -1812,8 +1812,8 @@ } // Drop if not ARP IPv4. - gen.addLoadImmediate(R0, ARP_HEADER_OFFSET); - gen.addCountAndDropIfBytesAtR0NotEqual(ARP_IPV4_HEADER, DROPPED_ARP_NON_IPV4); + gen.addCountAndDropIfBytesAtOffsetNotEqual(ARP_HEADER_OFFSET, ARP_IPV4_HEADER, + DROPPED_ARP_NON_IPV4); final short checkArpRequest = gen.getUniqueLabel(); @@ -1830,8 +1830,8 @@ // Pass if non-broadcast reply. // This also accepts multicast arp, but we assume those don't exist. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_ARP_UNICAST_REPLY); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_ARP_UNICAST_REPLY); // It is a broadcast reply. if (mIPv4Address == null) { @@ -1897,8 +1897,8 @@ // the future, such packets will likely be dropped by multicast filters. // Since the device may have packet forwarding enabled, APF needs to pass any received // unicast IPv4 ping not destined for the device's IP address to the kernel. - gen.addLoadImmediate(R0, ETHER_DST_ADDR_OFFSET) - .addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipIpv4PingFilter) + gen.addJumpIfBytesAtOffsetNotEqual( + ETH_DEST_ADDR_OFFSET, mHardwareAddress, skipIpv4PingFilter) .addLoadImmediate(R0, IPV4_DEST_ADDR_OFFSET) .addJumpIfBytesAtR0NotEqual(mIPv4Address, skipIpv4PingFilter); @@ -1979,11 +1979,11 @@ // address for IPv4 mDNS packet) or the device's MAC address, skip filtering. // We need to check both the mDNS multicast MAC address and the device's MAC address // because multicast to unicast conversion might have occurred. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0EqualsNoneOf( - List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS), - skipMdnsFilter - ); + gen.addJumpIfBytesAtOffsetEqualsNoneOf( + ETH_DEST_ADDR_OFFSET, + List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS), + skipMdnsFilter + ); // Ignore packets with IPv4 options (header size not equal to 20) as they are rare. gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE) @@ -2153,7 +2153,10 @@ // If IPv4 destination address is in multicast range, drop. gen.addLoad8intoR0(IPV4_DEST_ADDR_OFFSET); - gen.addAnd(0xf0); + // we just loaded a byte, so top 24 bits are zero, thus and'ing + // with either one of 0xF0 and 0xFFFFFFF0 accomplishes the same thing, + // we thus choose the one which encodes shorter + gen.addAnd((gen instanceof ApfV4Generator) ? 0xF0 : 0xFFFFFFF0); gen.addCountAndDropIfR0Equals(0xe0, DROPPED_IPV4_MULTICAST); // If IPv4 broadcast packet, drop regardless of L2 (b/30231088). @@ -2182,8 +2185,8 @@ // Otherwise, this is an IPv4 unicast, pass // If L2 broadcast packet, drop. // TODO: can we invert this condition to fall through to the common pass case below? - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_IPV4_UNICAST); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_IPV4_UNICAST); gen.addCountAndDrop(DROPPED_IPV4_L2_BROADCAST); } @@ -2326,20 +2329,21 @@ // used by processes other than clatd. This is because APF cannot reliably detect signal // on when IPV6_{JOIN,LEAVE}_ANYCAST is triggered. final List<byte[]> allMACs = getKnownMacAddresses(); - v6Gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addCountAndDropIfBytesAtR0EqualsNoneOf(allMACs, DROPPED_IPV6_NS_OTHER_HOST); + v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf(ETH_DEST_ADDR_OFFSET, allMACs, + DROPPED_IPV6_NS_OTHER_HOST); // Dst IPv6 address check: final List<byte[]> allSuffixes = getSolicitedNodeMcastAddressSuffix(allIPv6Addrs); final short notIpV6SolicitedNodeMcast = v6Gen.getUniqueLabel(); final short endOfIpV6DstCheck = v6Gen.getUniqueLabel(); - v6Gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0NotEqual(IPV6_SOLICITED_NODES_PREFIX, notIpV6SolicitedNodeMcast) - .addAdd(13) + v6Gen.addJumpIfBytesAtOffsetNotEqual( + IPV6_DEST_ADDR_OFFSET, IPV6_SOLICITED_NODES_PREFIX, notIpV6SolicitedNodeMcast) + .addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET + 13) .addCountAndDropIfBytesAtR0EqualsNoneOf(allSuffixes, DROPPED_IPV6_NS_OTHER_HOST) .addJump(endOfIpV6DstCheck) .defineLabel(notIpV6SolicitedNodeMcast) - .addCountAndDropIfBytesAtR0EqualsNoneOf(allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST) + .addCountAndDropIfBytesAtOffsetEqualsNoneOf( + IPV6_DEST_ADDR_OFFSET, allIPv6Addrs, DROPPED_IPV6_NS_OTHER_HOST) .defineLabel(endOfIpV6DstCheck); // Hop limit not 255, NS requires hop limit to be 255 -> drop @@ -2362,10 +2366,10 @@ true, /* includeTentative */ false /* includeAnycast */ ); - v6Gen.addLoadImmediate(R0, ICMP6_NS_TARGET_IP_OFFSET); + if (!tentativeIPv6Addrs.isEmpty()) { - v6Gen.addCountAndPassIfBytesAtR0EqualsAnyOf( - tentativeIPv6Addrs, PASSED_IPV6_ICMP); + v6Gen.addCountAndPassIfBytesAtOffsetEqualsAnyOf( + ICMP6_NS_TARGET_IP_OFFSET, tentativeIPv6Addrs, PASSED_IPV6_ICMP); } final List<byte[]> nonTentativeIpv6Addrs = getIpv6Addresses( @@ -2377,12 +2381,12 @@ v6Gen.addCountAndDrop(DROPPED_IPV6_NS_OTHER_HOST); return; } - v6Gen.addCountAndDropIfBytesAtR0EqualsNoneOf( - nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST); + v6Gen.addCountAndDropIfBytesAtOffsetEqualsNoneOf( + ICMP6_NS_TARGET_IP_OFFSET, nonTentativeIpv6Addrs, DROPPED_IPV6_NS_OTHER_HOST); // if source ip is unspecified (::), it's DAD request -> pass - v6Gen.addLoadImmediate(R0, IPV6_SRC_ADDR_OFFSET) - .addCountAndPassIfBytesAtR0Equal(IPV6_UNSPECIFIED_ADDRESS, PASSED_IPV6_ICMP); + v6Gen.addCountAndPassIfBytesAtOffsetEqual( + IPV6_SRC_ADDR_OFFSET, IPV6_UNSPECIFIED_ADDRESS, PASSED_IPV6_ICMP); // Only offload NUD/Address resolution packets that have SLLA as the their first option. // For option-less NUD packets or NUD/Address resolution packets where @@ -2440,11 +2444,11 @@ // address for IPv6 mDNS packet) or the device's MAC address, skip filtering. // We need to check both the mDNS multicast MAC address and the device's MAC address // because multicast to unicast conversion might have occurred. - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0EqualsNoneOf( - List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS), - skipMdnsFilter - ); + gen.addJumpIfBytesAtOffsetEqualsNoneOf( + ETH_DEST_ADDR_OFFSET, + List.of(mHardwareAddress, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS), + skipMdnsFilter + ); // Skip filtering if the packet is not an IPv6 UDP packet. gen.addLoad8intoR0(IPV6_NEXT_HEADER_OFFSET) @@ -2455,8 +2459,7 @@ // Some devices can use unicast queries for mDNS to improve performance and reliability. // These packets are not currently offloaded and will be passed by APF and handled // by NsdService. - gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0NotEqual(MDNS_IPV6_ADDR, skipMdnsFilter); + gen.addJumpIfBytesAtOffsetNotEqual(IPV6_DEST_ADDR_OFFSET, MDNS_IPV6_ADDR, skipMdnsFilter); // We now know that the packet is an mDNS packet, // i.e., an IPv6 UDP packet destined for port 5353 with the expected destination MAC and IP @@ -2505,10 +2508,10 @@ true /* includeNonTentative */, false /* includeTentative */, false /* includeAnycast */); - gen.addLoadImmediate(R0, ETHER_DST_ADDR_OFFSET) - .addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipPing6Offload) - .addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET) - .addJumpIfBytesAtR0EqualsNoneOf(nonTentativeIPv6Addrs, skipPing6Offload); + gen.addJumpIfBytesAtOffsetNotEqual( + ETHER_DST_ADDR_OFFSET, mHardwareAddress, skipPing6Offload) + .addJumpIfBytesAtOffsetEqualsNoneOf( + IPV6_DEST_ADDR_OFFSET, nonTentativeIPv6Addrs, skipPing6Offload); // We need to check if the packet is sufficiently large to be a valid ICMPv6 echo packet. gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE) @@ -2731,8 +2734,8 @@ // This is a way to cover ff02::1 and ff02::2 with a single JNEBS. // TODO: Drop only if they don't contain the address of on-link neighbours. final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15); - gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET); - gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel); + gen.addJumpIfBytesAtOffsetNotEqual( + IPV6_DEST_ADDR_OFFSET, unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel); gen.addCountAndDrop(DROPPED_IPV6_MULTICAST_NA); gen.defineLabel(skipUnsolicitedMulticastNALabel); @@ -3268,15 +3271,14 @@ // If the multicast address is not "::", it is an MLD2 multicast-address-specific query, // then pass. - gen.addLoadImmediate(R0, IPV6_MLD_MULTICAST_ADDR_OFFSET) - .addCountAndPassIfBytesAtR0NotEqual(IPV6_ADDR_ANY.getAddress(), PASSED_IPV6_ICMP); + gen.addCountAndPassIfBytesAtOffsetNotEqual( + IPV6_MLD_MULTICAST_ADDR_OFFSET, IPV6_ADDR_ANY.getAddress(), PASSED_IPV6_ICMP); // If we reach here, we know it is an MLDv1/MLDv2 general query. // The general query IPv6 destination address must be ff02::1. - gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET) - .addCountAndDropIfBytesAtR0NotEqual(IPV6_ALL_NODES_ADDRESS, - DROPPED_IPV6_MLD_INVALID); + gen.addCountAndDropIfBytesAtOffsetNotEqual( + IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS, DROPPED_IPV6_MLD_INVALID); // If the MLD payload length is 24, it is an MLDv1 packet, otherwise, it is an MLDv2 packet. gen.addLoadFromMemory(R0, MemorySlot.SLOT_0) @@ -3588,11 +3590,13 @@ // pass // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets - gen.addLoadImmediate(R0, ETHER_SRC_ADDR_OFFSET); if (NetworkStackUtils.isAtLeast25Q2()) { - gen.addCountAndDropIfBytesAtR0Equal(mHardwareAddress, DROPPED_ETHER_OUR_SRC_MAC); + gen.addCountAndDropIfBytesAtOffsetEqual(ETHER_SRC_ADDR_OFFSET, mHardwareAddress, + DROPPED_ETHER_OUR_SRC_MAC); } else { - gen.addCountAndPassIfBytesAtR0Equal(mHardwareAddress, PASSED_ETHER_OUR_SRC_MAC); + // TODO: we don't have test coverage for this line + gen.addCountAndPassIfBytesAtOffsetEqual(ETHER_SRC_ADDR_OFFSET, mHardwareAddress, + PASSED_ETHER_OUR_SRC_MAC); } gen.addLoad16intoR0(ETH_ETHERTYPE_OFFSET); @@ -3600,8 +3604,8 @@ // Pass unicast TDLS packet but drop non-unicast TDLS packet. short skipTDLScheck = gen.getUniqueLabel(); gen.addJumpIfR0NotEquals(0x890DL, skipTDLScheck) - .addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET) - .addCountAndDropIfBytesAtR0NotEqual(mHardwareAddress, DROPPED_NON_UNICAST_TDLS) + .addCountAndDropIfBytesAtOffsetNotEqual( + ETH_DEST_ADDR_OFFSET, mHardwareAddress, DROPPED_NON_UNICAST_TDLS) .addCountAndPass(PASSED_NON_IP_UNICAST) .defineLabel(skipTDLScheck); @@ -3646,8 +3650,8 @@ gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel); // Drop non-IP non-ARP broadcasts, pass the rest - gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET); - gen.addCountAndPassIfBytesAtR0NotEqual(ETHER_BROADCAST, PASSED_NON_IP_UNICAST); + gen.addCountAndPassIfBytesAtOffsetNotEqual(ETH_DEST_ADDR_OFFSET, ETHER_BROADCAST, + PASSED_NON_IP_UNICAST); gen.addCountAndDrop(DROPPED_ETH_BROADCAST); // Add IPv6 filters: @@ -3728,10 +3732,80 @@ private int calcMdnsOffloadProgramSizeOverEstimate(int numOfMdnsRuleToOffload) throws IllegalInstructionException { ApfV6GeneratorBase<?> gen = (ApfV6GeneratorBase<?>) createApfGenerator(); + // We need to preload data for size estimation because the preloaded data contains mDNS + // data chunks. If we don't preload, generateMdnsQueryOffload() will add data to the data + // region, resulting in an incorrect estimated size. + if (gen instanceof ApfV61GeneratorBase<?>) { + preloadData((ApfV61GeneratorBase<?>) gen); + } + final int programLengthOverEstimateBefore = gen.programLengthOverEstimate(); short tmpLabelCheckMdnsQueryPayload = gen.getUniqueLabel(); generateMdnsQueryOffload(gen, tmpLabelCheckMdnsQueryPayload, numOfMdnsRuleToOffload); - return gen.programLengthOverEstimate() - gen.getBaseProgramSize(); + return gen.programLengthOverEstimate() - programLengthOverEstimateBefore; + } + + void preloadData(ApfV61GeneratorBase<?> gen) throws IllegalInstructionException { + final List<byte[]> preloadedMacAddress = getKnownMacAddresses(); + final List<byte[]> preloadedIPv6Address = getIpv6Addresses(true /* includeNonTentative */, + true /* includeTentative */, true /* includeAnycast */); + preloadedIPv6Address.add(IPV6_ADDR_ALL_NODES_MULTICAST.getAddress()); + preloadedIPv6Address.add(IPV6_ADDR_ANY.getAddress()); + byte[] mdns6NextHdrToUdpDport = new byte[0]; + byte[] mdns6EthDstToFlowLabel = new byte[0]; + byte[] mdns4EthDstToTos = new byte[0]; + if (enableMdns6Offload()) { + mdns6NextHdrToUdpDport = createMdns6PktFromIPv6NextHdrToUdpDport(true); + preloadedIPv6Address.removeIf( + addr -> Arrays.equals(addr, mIPv6LinkLocalAddress.getAddress())); + mdns6EthDstToFlowLabel = createMdns6PktFromEthDstToIPv6FlowLabel(true); + preloadedMacAddress.removeIf( + addr -> Arrays.equals(addr, mHardwareAddress) || Arrays.equals(addr, + ETH_MULTICAST_MDNS_V6_MAC_ADDRESS)); + } + + if (enableMdns4Offload()) { + mdns4EthDstToTos = createMdns4PktFromEthDstToIPv4Tos(true); + preloadedMacAddress.removeIf( + addr -> Arrays.equals(addr, mHardwareAddress) || Arrays.equals(addr, + ETH_MULTICAST_MDNS_V4_MAC_ADDRESS)); + } + + int preloadDataSize = mdns6NextHdrToUdpDport.length + mdns6EthDstToFlowLabel.length + + mdns4EthDstToTos.length + preloadedIPv6Address.size() * 16 + + preloadedMacAddress.size() * 6; + + if (enableArpOffload()) { + preloadDataSize += FIXED_ARP_REPLY_HEADER.length; + } + + final byte[] preloadData = new byte[preloadDataSize]; + int offset = 0; + System.arraycopy(mdns6NextHdrToUdpDport, 0, preloadData, offset, + mdns6NextHdrToUdpDport.length); + offset += mdns6NextHdrToUdpDport.length; + System.arraycopy(mdns6EthDstToFlowLabel, 0, preloadData, offset, + mdns6EthDstToFlowLabel.length); + offset += mdns6EthDstToFlowLabel.length; + System.arraycopy(mdns4EthDstToTos, 0, preloadData, offset, mdns4EthDstToTos.length); + offset += mdns4EthDstToTos.length; + for (byte[] addr : preloadedMacAddress) { + System.arraycopy(addr, 0, preloadData, offset, 6); + offset += 6; + } + for (byte[] addr : preloadedIPv6Address) { + System.arraycopy(addr, 0, preloadData, offset, 16); + offset += 16; + } + if (enableArpOffload()) { + System.arraycopy(FIXED_ARP_REPLY_HEADER, 0, preloadData, offset, + FIXED_ARP_REPLY_HEADER.length); + offset += FIXED_ARP_REPLY_HEADER.length; + } + + if (preloadDataSize > 0) { + gen.addPreloadData(preloadData); + } } /** @@ -3755,6 +3829,9 @@ try { // Step 1: Determine how many RA filters/mDNS offloads we can fit in the program. ApfV4GeneratorBase<?> gen = createApfGenerator(); + if (gen instanceof ApfV61GeneratorBase<?>) { + preloadData((ApfV61GeneratorBase<?>) gen); + } short labelCheckMdnsQueryPayload = gen.getUniqueLabel(); emitPrologue(gen, labelCheckMdnsQueryPayload);
diff --git a/src/android/net/apf/ApfV4GeneratorBase.java b/src/android/net/apf/ApfV4GeneratorBase.java index f142e31..d750229 100644 --- a/src/android/net/apf/ApfV4GeneratorBase.java +++ b/src/android/net/apf/ApfV4GeneratorBase.java
@@ -482,6 +482,17 @@ } /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0NotEqual(bytes, tgt); + } + + /** * Add instructions to the end of the program to increase counter and drop packet if the * bytes of the packet at an offset specified by register0 don't match {@code bytes}. * WARNING: may modify R1 @@ -499,6 +510,50 @@ /** * Add instructions to the end of the program to increase counter and drop packet if the + * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0NotEqual(bytes, cnt); + } + + /** + * Add instructions to the end of the program to increase counter and pass packet if the + * bytes of the packet at an offset specified by {@code offset} don't match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0NotEqual(bytes, cnt); + } + + /** + * Add instructions to the end of the program to increase counter and drop packet if the + * bytes of the packet at an offset specified by {@code offset} does match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addCountAndDropIfBytesAtOffsetEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0Equal(bytes, cnt); + } + + /** + * Add instructions to the end of the program to increase counter and pass packet if the + * bytes of the packet at an offset specified by {@code offset} does match {@code bytes}. + * This method needs to be non-final because APFv4 and APFv6 share the same implementation, + * but in APFv6.1, this method will be overridden to use the JBSPTRMATCH instruction. + */ + public Type addCountAndPassIfBytesAtOffsetEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0Equal(bytes, cnt); + } + + /** + * Add instructions to the end of the program to increase counter and drop packet if the * bytes of the packet at an offset specified by register0 match {@code bytes}. * WARNING: may modify R1 */
diff --git a/src/android/net/apf/ApfV61GeneratorBase.java b/src/android/net/apf/ApfV61GeneratorBase.java index e5859e7..c60de72 100644 --- a/src/android/net/apf/ApfV61GeneratorBase.java +++ b/src/android/net/apf/ApfV61GeneratorBase.java
@@ -15,11 +15,13 @@ */ package android.net.apf; +import static android.net.apf.BaseApfGenerator.Rbit.Rbit0; import static android.net.apf.BaseApfGenerator.Rbit.Rbit1; import static android.net.apf.BaseApfGenerator.Register.R0; import androidx.annotation.NonNull; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -199,6 +201,160 @@ return self(); } + @Override + public Type addAllocate(int size) { + final int imm = (size > 266) ? (size - 266 + 7) / 8 : 0; + return append(new Instruction(Opcodes.ALLOC_XMIT, Rbit1).addUnsigned(imm)); + } + + @Override + public Type addTransmitWithoutChecksum() { + return append(new Instruction(Opcodes.ALLOC_XMIT, Rbit0)); + } + + @Override + protected boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, + int partialCsum, boolean isUdp) { + if (ipOfs != 14) return false; + int v = -1; + if ( isUdp && csumStart == 26 && csumOfs == 40) v = 0; // ether/ipv4/udp + if (!isUdp && csumStart == 26 && csumOfs == 44) v = 1; // ether/ipv4/tcp + if (!isUdp && csumStart == 34 && csumOfs == 36) v = 2; // ether/ipv4/icmp + if (!isUdp && csumStart == 38 && csumOfs == 40) v = 3; // ether/ipv4/routeralert/icmp + if ( isUdp && csumStart == 22 && csumOfs == 60) v = 4; // ether/ipv6/udp + if (!isUdp && csumStart == 22 && csumOfs == 64) v = 5; // ether/ipv6/tcp + if (!isUdp && csumStart == 22 && csumOfs == 56) v = 6; // ether/ipv6/icmp + if (!isUdp && csumStart == 22 && csumOfs == 64) v = 7; // ether/ipv6/routeralert/icmp + if (v < 0) return false; + v |= partialCsum << 3; + append(new Instruction(Opcodes.ALLOC_XMIT, Rbit0).addUnsigned(v)); + return true; + } + + private List<byte[]> addJumpIfBytesAtOffsetEqualsHelper(int offset, + @NonNull List<byte[]> bytesList, short tgt, boolean jumpOnMatch) + throws IllegalInstructionException { + final List<byte[]> deduplicatedList = + bytesList.size() == 1 ? bytesList : validateDeduplicateBytesList(bytesList); + if (offset < 0 || offset > 255) { + return deduplicatedList; + } + final int count = deduplicatedList.size(); + final int compareLength = deduplicatedList.get(0).length; + if (compareLength > 16) { + return deduplicatedList; + } + final List<byte[]> failbackList = new ArrayList<>(); + final List<Integer> ptrs = new ArrayList<>(); + for (int i = 0; i < count; ++i) { + final byte[] bytes = deduplicatedList.get(i); + int relativeOffset = mInstructions.get(0).findMatchInDataBytes(bytes, 0, bytes.length); + if (relativeOffset < 0 || relativeOffset % 2 == 1 || relativeOffset > 510) { + failbackList.add(bytes); + continue; + } + ptrs.add(relativeOffset / 2); + } + final Rbit rbit = jumpOnMatch ? Rbit1 : Rbit0; + int totalPtrs = ptrs.size(); + for (int i = 0; i < totalPtrs; i += 16) { + final int currentCount = Math.min(totalPtrs - i, 16); + final Instruction instruction = new Instruction(Opcodes.JBSPTRMATCH, rbit) + .addU8(offset) + .addU8((currentCount - 1) * 16 + (compareLength - 1)) + .setTargetLabel(tgt); + for (int j = 0; j < currentCount; j++) { + instruction.addU8(ptrs.get(i + j)); + } + append(instruction); + } + return failbackList; + } + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesList}. + */ + public Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset, @NonNull List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + final List<byte[]> failbackList = addJumpIfBytesAtOffsetEqualsHelper(offset, bytesList, tgt, + true /* jumpOnMatch */); + if (failbackList.isEmpty()) { + return self(); + } + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(failbackList, tgt); + } + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match none of the elements in + * {@code bytesList}. + */ + public Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset, @NonNull List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + final List<byte[]> failbackList = addJumpIfBytesAtOffsetEqualsHelper(offset, bytesList, tgt, + false /* jumpOnMatch */); + if (failbackList.isEmpty()) { + return self(); + } + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(failbackList, tgt); + } + + @Override + public Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsAnyOf(offset, bytesList, cnt.getJumpDropLabel()); + } + + @Override + public Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsAnyOf(offset, bytesList, cnt.getJumpPassLabel()); + } + + @Override + public Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpDropLabel()); + } + + @Override + public Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, bytesList, cnt.getJumpPassLabel()); + } + + @Override + public Type addCountAndPassIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpPassLabel()); + } + + @Override + public Type addCountAndDropIfBytesAtOffsetNotEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), cnt.getJumpDropLabel()); + } + + @Override + public Type addCountAndPassIfBytesAtOffsetEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsAnyOf(offset, List.of(bytes), cnt.getJumpPassLabel()); + } + + @Override + public Type addCountAndDropIfBytesAtOffsetEqual(int offset, byte[] bytes, + ApfCounterTracker.Counter cnt) throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsAnyOf(offset, List.of(bytes), cnt.getJumpDropLabel()); + } + + @Override + public Type addJumpIfBytesAtOffsetNotEqual(int offset, @NonNull byte[] bytes, short tgt) + throws IllegalInstructionException { + return addJumpIfBytesAtOffsetEqualsNoneOf(offset, List.of(bytes), tgt); + } + /** * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype @@ -211,4 +367,12 @@ return append(new Instruction(ExtendedOpcodes.JDNSQMATCH2, Rbit1).setTargetLabel(tgt) .addU8(qtype1).addU8(qtype2).setBytesImm(qnames)); } + + /** + * Preload the content of the data region. + */ + public Type addPreloadData(@NonNull byte[] data) throws IllegalInstructionException { + mInstructions.get(0).maybeUpdateBytesImm(data, 0, data.length); + return self(); + } }
diff --git a/src/android/net/apf/ApfV6Generator.java b/src/android/net/apf/ApfV6Generator.java index ccd5490..07bd191 100644 --- a/src/android/net/apf/ApfV6Generator.java +++ b/src/android/net/apf/ApfV6Generator.java
@@ -15,6 +15,7 @@ */ package android.net.apf; +import static android.net.apf.BaseApfGenerator.Rbit.Rbit1; import static android.net.apf.BaseApfGenerator.Register.R0; import android.annotation.NonNull; @@ -233,6 +234,46 @@ } @Override + public ApfV6Generator addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, + List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0EqualsAnyOf(bytesList, cnt); + } + + @Override + public ApfV6Generator addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, + List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0EqualsAnyOf(bytesList, cnt); + } + + @Override + public ApfV6Generator addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, + List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndDropIfBytesAtR0EqualsNoneOf(bytesList, cnt); + } + + @Override + public ApfV6Generator addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, + List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addCountAndPassIfBytesAtR0EqualsNoneOf(bytesList, cnt); + } + + @Override + public ApfV6Generator addJumpIfBytesAtOffsetEqualsAnyOf(int offset, List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsAnyOf(bytesList, tgt); + } + + @Override + public ApfV6Generator addJumpIfBytesAtOffsetEqualsNoneOf(int offset, List<byte[]> bytesList, + short tgt) throws IllegalInstructionException { + return addLoadImmediate(R0, offset).addJumpIfBytesAtR0EqualsNoneOf(bytesList, tgt); + } + + @Override public ApfV6Generator addCountAndDropIfR0IsNoneOf(@NonNull Set<Long> values, ApfCounterTracker.Counter cnt) throws IllegalInstructionException { if (values.isEmpty()) { @@ -252,4 +293,21 @@ } return self(); } + + @Override + public ApfV6Generator addAllocate(int size) { + // Rbit1 means the extra be16 immediate is present + return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size)); + } + + @Override + public ApfV6Generator addTransmitWithoutChecksum() { + return addTransmit(-1 /* ipOfs */); + } + + @Override + protected boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, + int partialCsum, boolean isUdp) { + return false; + } }
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java index 658e4d1..90f0a28 100644 --- a/src/android/net/apf/ApfV6GeneratorBase.java +++ b/src/android/net/apf/ApfV6GeneratorBase.java
@@ -114,10 +114,7 @@ * * @param size the buffer length to be allocated. */ - public final Type addAllocate(int size) { - // Rbit1 means the extra be16 immediate is present - return append(new Instruction(ExtendedOpcodes.ALLOCATE, Rbit1).addU16(size)); - } + public abstract Type addAllocate(int size); /** * Add an instruction to the beginning of the program to reserve the empty data region. @@ -153,9 +150,7 @@ * Add an instruction to the end of the program to transmit the allocated buffer without * checksum. */ - public final Type addTransmitWithoutChecksum() { - return addTransmit(-1 /* ipOfs */); - } + public abstract Type addTransmitWithoutChecksum(); /** * Add an instruction to the end of the program to transmit the allocated buffer. @@ -168,6 +163,8 @@ return append(new Instruction(ExtendedOpcodes.TRANSMIT, Rbit0).addU8(ipOfs).addU8(255)); } + protected abstract boolean handleOptimizedTransmit(int ipOfs, int csumOfs, int csumStart, int partialCsum, boolean isUdp); + /** * Add an instruction to the end of the program to transmit the allocated buffer. */ @@ -181,6 +178,8 @@ throw new IllegalArgumentException("L4 checksum requires csum offset of " + csumOfs + " < 255"); } + if (handleOptimizedTransmit(ipOfs, csumOfs, csumStart, partialCsum, isUdp)) + return self(); return append(new Instruction(ExtendedOpcodes.TRANSMIT, isUdp ? Rbit1 : Rbit0) .addU8(ipOfs).addU8(csumOfs).addU8(csumStart).addU16(partialCsum)); } @@ -397,6 +396,45 @@ short tgt); /** + * Add an instruction to the end of the program to count and drop if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesList}. + * This method will use JBSPTRMATCH in APFv6.1 when possible. + */ + public abstract Type addCountAndDropIfBytesAtOffsetEqualsAnyOf(int offset, + @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to count and pass if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesList}. + * This method will use JBSPTRMATCH in APFv6.1 when possible. + */ + public abstract Type addCountAndPassIfBytesAtOffsetEqualsAnyOf(int offset, + @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to count and drop if the bytes of the + * packet at an offset specified by {@code offset} match none the elements in {@code bytesList}. + * This method will use JBSPTRMATCH in APFv6.1 when possible. + */ + public abstract Type addCountAndDropIfBytesAtOffsetEqualsNoneOf(int offset, + @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to count and pass if the bytes of the + * packet at an offset specified by {@code offset} match none of the elements in + * {@code bytesList}. + * This method will use JBSPTRMATCH in APFv6.1 when possible. + */ + public abstract Type addCountAndPassIfBytesAtOffsetEqualsNoneOf(int offset, + @NonNull List<byte[]> bytesList, ApfCounterTracker.Counter cnt) + throws IllegalInstructionException; + + /** * Appends a conditional jump instruction to the program: Jumps to {@code tgt} if the UDP * payload's DNS questions contain the QNAMEs specified in {@code qnames} and qtype * equals {@code qtype}. Examines the payload starting at the offset in R0. @@ -514,6 +552,21 @@ return addJumpIfBytesAtR0EqualsHelper(bytesList, tgt, false /* jumpOnMatch */); } + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match any of the elements in + * {@code bytesSet}. + */ + public abstract Type addJumpIfBytesAtOffsetEqualsAnyOf(int offset, + @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException; + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code offset} match none of the elements in + * {@code bytesSet}. + */ + public abstract Type addJumpIfBytesAtOffsetEqualsNoneOf(int offset, + @NonNull List<byte[]> bytesList, short tgt) throws IllegalInstructionException; /** * Check if the byte is valid dns character: A-Z,0-9,-,_,%,@
diff --git a/src/android/net/apf/BaseApfGenerator.java b/src/android/net/apf/BaseApfGenerator.java index c505f5d..21d8be3 100644 --- a/src/android/net/apf/BaseApfGenerator.java +++ b/src/android/net/apf/BaseApfGenerator.java
@@ -106,7 +106,33 @@ // R=1 means copy from APF program/data region. // The copy length is stored in (u8)imm2. // e.g. "pktcopy 5, 5" "datacopy 5, 5" - PKTDATACOPY(25); + PKTDATACOPY(25), + // JSET with reverse condition (jump if no bits set) + JNSET(26), + // APFv6.1: Compare byte sequence [R=0 not] equal, e.g. "jbsptrne 22,16,label,<dataptr>" + // imm1 is jmp target + // imm2(u8) is offset [0..255] into packet + // imm3(u8) is (count - 1) * 16 + (compare_len - 1), thus both count & compare_len are in + // [1..16] which is followed by compare_len u8 'even offset' ptrs into max 526 byte data + // section to compare against - ie. they are multipied by 2 and have 3 added to them + // (to skip over 'datajmp u16') + // Warning: do not specify the same byte sequence multiple times. + JBSPTRMATCH(27), + // APFv6.1: Bytecode optimized allocate | transmit instruction. + // R=1 -> allocate(266 + imm * 8) + // R=0 -> transmit + // immlen=0 -> no checksum offload (transmit ip_ofs=255) + // immlen>0 -> with checksum offload (transmit(udp) ip_ofs=14 ...) + // imm & 7 | type of offload | ip_ofs | udp | csum_start | csum_ofs | partial_csum | + // 0 | ip4/udp | 14 | X | 14+20-8 =26 | 14+20 +6=40 | imm >> 3 | + // 1 | ip4/tcp | 14 | | 14+20-8 =26 | 14+20 +10=44 | --"-- | + // 2 | ip4/icmp | 14 | | 14+20 =34 | 14+20 +2=36 | --"-- | + // 3 | ip4/routeralert/icmp | 14 | | 14+20+4 =38 | 14+20+4 +2=40 | --"-- | + // 4 | ip6/udp | 14 | X | 14+40-32=22 | 14+40 +6=60 | --"-- | + // 5 | ip6/tcp | 14 | | 14+40-32=22 | 14+40 +10=64 | --"-- | + // 6 | ip6/icmp | 14 | | 14+40-32=22 | 14+40 +2=56 | --"-- | + // 7 | ip6/routeralert/icmp | 14 | | 14+40-32=22 | 14+40+8 +2=64 | --"-- | + ALLOC_XMIT(28); final int value; @@ -496,7 +522,23 @@ return this; } - private int findMatchInDataBytes(@NonNull byte[] content, int fromIndex, int toIndex) { + int findMatchInDataBytes(@NonNull byte[] content, int fromIndex, int toIndex) + throws IllegalInstructionException { + if (fromIndex >= toIndex || fromIndex < 0 || toIndex > content.length) { + throw new IllegalArgumentException( + String.format("fromIndex: %d, toIndex: %d, content length: %d", fromIndex, + toIndex, content.length)); + } + if (mOpcode != Opcodes.JMP || mBytesImm == null) { + throw new IllegalInstructionException(String.format( + "this method is only valid for jump data instruction, mOpcode " + + ":%s, mBytesImm: %s", Opcodes.JMP, + mBytesImm == null ? "(empty)" : HexDump.toHexString(mBytesImm))); + } + if (mImmSizeOverride != 2) { + throw new IllegalInstructionException( + "mImmSizeOverride must be 2, mImmSizeOverride: " + mImmSizeOverride); + } final int subArrayLength = toIndex - fromIndex; for (int i = 0; i < mBytesImm.length - subArrayLength + 1; i++) { boolean found = true; @@ -532,21 +574,6 @@ */ int maybeUpdateBytesImm(byte[] content, int fromIndex, int toIndex) throws IllegalInstructionException { - if (fromIndex >= toIndex || fromIndex < 0 || toIndex > content.length) { - throw new IllegalArgumentException( - String.format("fromIndex: %d, toIndex: %d, content length: %d", fromIndex, - toIndex, content.length)); - } - if (mOpcode != Opcodes.JMP || mBytesImm == null) { - throw new IllegalInstructionException(String.format( - "maybeUpdateBytesImm() is only valid for jump data instruction, mOpcode " - + ":%s, mBytesImm: %s", Opcodes.JMP, - mBytesImm == null ? "(empty)" : HexDump.toHexString(mBytesImm))); - } - if (mImmSizeOverride != 2) { - throw new IllegalInstructionException( - "mImmSizeOverride must be 2, mImmSizeOverride: " + mImmSizeOverride); - } int offsetInDataBytes = findMatchInDataBytes(content, fromIndex, toIndex); if (offsetInDataBytes == -1) { offsetInDataBytes = mBytesImm.length;
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index eaed8e5..3ae8557 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -517,6 +517,8 @@ private final boolean mIsCaptivePortalCheckEnabled; private boolean mUseHttps; + private final boolean mUseSerialProbe; + private final int mSerialProbeGapTime; /** * The total number of completed validation attempts (network validated or a captive portal was * detected) for this NetworkMonitor instance. @@ -679,6 +681,8 @@ && deps.isFeatureSupported(mContext, FEATURE_DDR_IN_CONNECTIVITY) && deps.isFeatureSupported(mContext, FEATURE_DDR_IN_DNSRESOLVER); mUseHttps = getUseHttpsValidation(); + mUseSerialProbe = getUseSerialProbeValidation(); + mSerialProbeGapTime = getSerialProbeGapTime(); mCaptivePortalUserAgent = getCaptivePortalUserAgent(); mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs(getCustomizedContextOrDefault()); @@ -2392,6 +2396,16 @@ R.bool.config_force_dns_probe_private_ip_no_internet); } + private boolean getUseSerialProbeValidation() { + return mContext.getResources().getBoolean( + R.bool.config_probe_multi_http_https_url_serial); + } + + private int getSerialProbeGapTime() { + return mContext.getResources().getInteger( + R.integer.config_serial_url_probe_gap_time); + } + private boolean getUseHttpsValidation() { return mDependencies.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, CAPTIVE_PORTAL_USE_HTTPS, 1) == 1; @@ -3426,14 +3440,26 @@ // Probe capport API with the first HTTP probe. // TODO: Have the capport probe as a different probe for cleanliness. final URL urlMaybeWithCapport = httpUrls[0]; + int delayCount=0; for (final URL url : httpUrls) { - futures.add(ecs.submit(() -> new HttpProbe(properties, proxy, url, - url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe())); + final int cnt = delayCount++; + futures.add(ecs.submit(() -> { + if (mUseSerialProbe && cnt > 0) { + mDependencies.sleep(mSerialProbeGapTime * cnt); + } + return new HttpProbe(properties, proxy, url, + url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe(); + })); } - + delayCount=0; for (final URL url : httpsUrls) { - futures.add(ecs.submit(() -> new HttpsProbe(properties, proxy, url, capportApiUrl) - .sendProbe())); + final int cnt = delayCount++; + futures.add(ecs.submit(() -> { + if (mUseSerialProbe && cnt > 0) { + mDependencies.sleep(mSerialProbeGapTime * cnt); + } + return new HttpsProbe(properties, proxy, url, capportApiUrl).sendProbe(); + })); } final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>(); @@ -3778,6 +3804,13 @@ public void onExecutorServiceCreated(@NonNull ExecutorService ecs) { } + /** + * Wait for another round of serial probe + */ + public void sleep(int time) throws InterruptedException { + Thread.sleep((long)time); + } + public static final Dependencies DEFAULT = new Dependencies(); }
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt index fc612a2..5cfeaad 100644 --- a/tests/unit/src/android/net/apf/ApfFilterTest.kt +++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -22,6 +22,8 @@ import android.net.MacAddress import android.net.NattKeepalivePacketDataParcelable import android.net.TcpKeepalivePacketDataParcelable +import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V4_MAC_ADDRESS +import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V6_MAC_ADDRESS import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4 import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST @@ -240,6 +242,8 @@ intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(), // 33:33:ff:bb:cc:dd intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(), + ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, + ETH_MULTICAST_MDNS_V6_MAC_ADDRESS ) // Using scapy to generate payload: @@ -5566,6 +5570,18 @@ val apfFilter = getApfFilter(apfConfig) apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) + val srcAddr = byteArrayOf(10, 0, 0, 5) + val dstAddr = byteArrayOf(10, 0, 0, 6) + val srcPort = 1024 + val dstPort = 4500 + val parcel = NattKeepalivePacketDataParcelable() + parcel.srcAddress = InetAddress.getByAddress(srcAddr).address + parcel.srcPort = srcPort + parcel.dstAddress = InetAddress.getByAddress(dstAddr).address + parcel.dstPort = dstPort + apfFilter.addNattKeepalivePacketFilter(1, parcel) + apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) + val captor = ArgumentCaptor.forClass(OffloadEngine::class.java) verify(localNsdManager).registerOffloadEngine( eq(ifParams.name), @@ -5785,7 +5801,8 @@ ) assertThat(program.size).isLessThan(apfRamSize + 1) assertThat(program).isNotEqualTo(ByteArray(apfRamSize) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64) apfRamSize += step } } @@ -5805,7 +5822,8 @@ val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize() assertThat(program.size).isLessThan(availableRam + 1) assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64) apfRamSize += step } } @@ -5824,81 +5842,102 @@ val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize() assertThat(program.size).isLessThan(availableRam + 1) assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 }) - val step = Random.nextInt(1, 16) + // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' + val step = Random.nextInt(1, 64) apfRamSize += step } } + private fun getProgramForRaSizeEstimation( + apfRamSize: Int, + ): Pair<Int, ByteArray> { + val localRaWriterSocket = FileDescriptor() + val localRaReaderSocket = FileDescriptor() + Os.socketpair(AF_UNIX, SOCK_STREAM, 0, localRaWriterSocket, localRaReaderSocket) + doReturn(localRaReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt()) + var overEstimatedProgramSize = 0 + var program = byteArrayOf(0) + tryTest { + val apfConfig = getDefaultConfig() + apfConfig.apfRamSize = apfRamSize + val apfFilter = getApfFilter(apfConfig) + apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) + + val lp = LinkProperties() + val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) + lp.addLinkAddress(ipv4LinkAddress) + val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64) + lp.addLinkAddress(ipv6LinkAddress) + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + apfFilter.setLinkProperties(lp) + program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) + + val ra1 = """ + 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0 + 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800 + 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001 + a018000000000000101f434f06452fe + """.replace("\\s+".toRegex(), "").trim() + val ra1Bytes = HexDump.hexStringToByteArray(ra1) + Os.write(localRaWriterSocket, ra1Bytes, 0, ra1Bytes.size) + apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) + + // Using scapy to generate packet: + // eth = Ether(src="E8:9F:80:66:60:BC", dst="f2:9c:70:2c:39:5a") + // ip6 = IPv6(src="fe80::2", dst="ff02::1") + // icmpra = ICMPv6ND_RA(routerlifetime=360, retranstimer=360) + // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2002:db8::") + // rio = ICMPv6NDOptRouteInfo(prefix="2002:db8:cafe::") + // ra = eth/ip6/icmpra/pio1/rio + val ra2 = """ + f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0 + 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff + ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000 + 000000000000000 + """.replace("\\s+".toRegex(), "").trim() + val ra2Bytes = HexDump.hexStringToByteArray(ra2) + Os.write(localRaWriterSocket, ra2Bytes, 0, ra2Bytes.size) + program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) + overEstimatedProgramSize = apfFilter.overEstimatedProgramSize + } cleanup { + IoUtils.closeQuietly(localRaWriterSocket) + } + return Pair(overEstimatedProgramSize, program) + } + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Test fun testRaFilterSizeEstimation() { - val apfConfig = getDefaultConfig() - apfConfig.apfRamSize = 1500 - val apfFilter = getApfFilter(apfConfig) - apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) + val (overEstimatedProgramSize, _) = getProgramForRaSizeEstimation(apfRamSize = 8096) + val apfRam = overEstimatedProgramSize - 1 + val (_, program) = getProgramForRaSizeEstimation(apfRamSize = apfRam) - val lp = LinkProperties() - val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) - lp.addLinkAddress(ipv4LinkAddress) - val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64) - lp.addLinkAddress(ipv6LinkAddress) - for (addr in hostIpv6Addresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) - } - apfFilter.setLinkProperties(lp) - var program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) - - // Using scapy to generate packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="f2:9c:70:2c:39:5a") - // ip6 = IPv6(src="fe80::1", dst="ff02::1") - // icmpra = ICMPv6ND_RA(routerlifetime=3600, retranstimer=3600) - // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2001:db8::") - // rio = ICMPv6NDOptRouteInfo(prefix="2001:db8:cafe::") - // ra = eth/ip6/icmpra/pio1/rio val ra1 = """ - f29c702c395ae89f806660bb86dd6000000000483afffe800000000000000000000000000001ff0 - 200000000000000000000000000018600dd9600080e100000000000000e10030440c0ffffffffff - ffffff0000000020010db800000000000000000000000018030000ffffffff20010db8cafe00000 - 000000000000000 + 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0 + 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800 + 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001 + a018000000000000101f434f06452fe """.replace("\\s+".toRegex(), "").trim() - val ra1Bytes = HexDump.hexStringToByteArray(ra1) - Os.write(raWriterSocket, ra1Bytes, 0, ra1Bytes.size) - program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) - apfTestHelpers.verifyProgramRun( - apfFilter.mApfVersionSupported, - program, - ra1Bytes, - DROPPED_RA - ) - - // Using scapy to generate packet: - // eth = Ether(src="E8:9F:80:66:60:BC", dst="f2:9c:70:2c:39:5a") - // ip6 = IPv6(src="fe80::2", dst="ff02::1") - // icmpra = ICMPv6ND_RA(routerlifetime=360, retranstimer=360) - // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2002:db8::") - // rio = ICMPv6NDOptRouteInfo(prefix="2002:db8:cafe::") - // ra = eth/ip6/icmpra/pio1/rio val ra2 = """ f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000 000000000000000 """.replace("\\s+".toRegex(), "").trim() - val ra2Bytes = HexDump.hexStringToByteArray(ra2) - Os.write(raWriterSocket, ra2Bytes, 0, ra2Bytes.size) - program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) apfTestHelpers.verifyProgramRun( - apfFilter.mApfVersionSupported, + apfInterpreterVersion, program, - ra2Bytes, + HexDump.hexStringToByteArray(ra2), DROPPED_RA ) apfTestHelpers.verifyProgramRun( - apfFilter.mApfVersionSupported, + apfInterpreterVersion, program, - ra1Bytes, + HexDump.hexStringToByteArray(ra1), PASSED_IPV6_ICMP ) }
diff --git a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt index 03d360f..1c383bc 100644 --- a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt +++ b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
@@ -987,6 +987,95 @@ } @Test + fun testJBSPTRMATCHOpcodeEncoding() { + assumeTrue(apfInterpreterVersion != ApfJniUtils.APF_INTERPRETER_VERSION_V6) + val dataBytes = HexDump.hexStringToByteArray( + "01020304050607080910111213141516171819202122232425262728293031323334353637383940" + ) + val bytes1 = HexDump.hexStringToByteArray("0102") + val bytes2 = HexDump.hexStringToByteArray("0304") + val bytes3 = HexDump.hexStringToByteArray("0506") + val bytes4 = HexDump.hexStringToByteArray("0708") + val bytes5 = HexDump.hexStringToByteArray("0910") + val bytes6 = HexDump.hexStringToByteArray("1112") + val bytes7 = HexDump.hexStringToByteArray("1314") + val bytes8 = HexDump.hexStringToByteArray("1516") + val bytes9 = HexDump.hexStringToByteArray("1718") + val bytes10 = HexDump.hexStringToByteArray("1920") + val bytes11 = HexDump.hexStringToByteArray("2122") + val bytes12 = HexDump.hexStringToByteArray("2324") + val bytes13 = HexDump.hexStringToByteArray("2526") + val bytes14 = HexDump.hexStringToByteArray("2728") + val bytes15 = HexDump.hexStringToByteArray("2930") + val bytes16 = HexDump.hexStringToByteArray("3132") + val bytes17 = HexDump.hexStringToByteArray("3334") + val bytesAtOddIndex = HexDump.hexStringToByteArray("0203") + val notExistBytes = HexDump.hexStringToByteArray("ffff") + val total17BytesList = listOf( + bytes1, + bytes2, + bytes3, + bytes4, + bytes5, + bytes6, + bytes7, + bytes8, + bytes9, + bytes10, + bytes11, + bytes12, + bytes13, + bytes14, + bytes15, + bytes16, + bytes17, + ) + val joinedBytes: ByteArray = total17BytesList.flatMap { it.toList() }.toByteArray() + var program = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize) + .addPreloadData(dataBytes) + .addJumpIfBytesAtOffsetEqualsNoneOf(0, listOf(bytes1, notExistBytes), PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsAnyOf(1, listOf(bytes1, bytes2), PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsNoneOf(2, listOf(notExistBytes), PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsAnyOf(3, total17BytesList, PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsNoneOf(4, listOf(bytesAtOddIndex), PASS_LABEL) + .addJumpIfBytesAtOffsetEqualsNoneOf(6, listOf(joinedBytes), PASS_LABEL) + .generate() + var debugBufferSize = ramSize - program.size - Counter.totalSize() + assertContentEquals(listOf( + "0: data 40, ${HexDump.toHexString(dataBytes)}", + "43: debugbuf size=$debugBufferSize", + "47: jbsptrne pktofs=0, (2), PASS, @0[0102]", + "52: li r0, 0", + "53: jbsne r0, (2), PASS, ffff", + "58: jbsptreq pktofs=1, (2), PASS, { @0[0102], @2[0304] }[2]", + "64: li r0, 2", + "66: jbsne r0, (2), PASS, ffff", + "71: jbsptreq pktofs=3, (2), PASS, { @0[0102], @2[0304], @4[0506], @6[0708], " + + "@8[0910], @10[1112], @12[1314], @14[1516], @16[1718], @18[1920], @20[2122], " + + "@22[2324], @24[2526], @26[2728], @28[2930], @30[3132] }[16]", + "91: jbsptreq pktofs=3, (2), PASS, @32[3334]", + "96: li r0, 4", + "98: jbsne r0, (2), PASS, 0203", + "103: li r0, 6", + "105: jbsne r0, (34), PASS, ${HexDump.toHexString(joinedBytes)}", + ), apfTestHelpers.disassembleApf(program).map{ it.trim() }) + + val largePrefix = ByteArray(510) { 0 } + program = ApfV61Generator(apfInterpreterVersion, ramSize, clampSize) + .addPreloadData(largePrefix + dataBytes) + .addJumpIfBytesAtOffsetEqualsAnyOf(1, listOf(bytes1, bytes2), PASS_LABEL) + .generate() + debugBufferSize = ramSize - program.size - Counter.totalSize() + assertContentEquals(listOf( + "0: data 550, ${HexDump.toHexString(largePrefix + dataBytes)}", + "553: debugbuf size=$debugBufferSize", + "557: jbsptreq pktofs=1, (2), PASS, @510[0102]", + "562: li r0, 1", + "564: jbseq r0, (2), PASS, 0304", + ), apfTestHelpers.disassembleApf(program).map{ it.trim() }) + } + + @Test fun testPassDrop() { var program = ApfV6Generator(apfInterpreterVersion, ramSize, clampSize) .addDrop()
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java index 37b157b..e9bd616 100644 --- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -226,6 +226,7 @@ import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; @@ -320,6 +321,7 @@ private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5; private static final int HANDLER_TIMEOUT_MS = 1000; + private static final int SERIAL_PROBE_GAP_TIME_MS = 500; private static final int TEST_MIN_STALL_EVALUATE_INTERVAL_MS = 500; private static final int TEST_MIN_VALID_STALL_DNS_TIME_THRESHOLD_MS = 5000; private static final int STALL_EXPECTED_LAST_PROBE_TIME_MS = @@ -373,6 +375,7 @@ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); private FakeDns mFakeDns; + private Semaphore mSerialProbeLock; @GuardedBy("mThreadsToBeCleared") private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>(); @@ -474,6 +477,7 @@ initHttpConnection(mFallbackConnection); initHttpConnection(mOtherFallbackConnection); + mSerialProbeLock = new Semaphore(0); mFakeDns = new FakeDns(mNetwork, mDnsResolver); mFakeDns.startMocking(); // Set private dns suffix answer. sendPrivateDnsProbe() in NetworkMonitor send probe with @@ -3417,6 +3421,36 @@ } @Test + public void testSerialProbesOnFirstValidNetwork() throws Exception { + setupResourceForSerialProbes(); + setStatus(mOtherHttpsConnection1, 204); + runSerialProbesNetworkTest(NETWORK_VALIDATION_RESULT_VALID, + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); + verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(2)).sleep(anyInt()); + verify(mCleartextDnsNetwork, timeout(HANDLER_TIMEOUT_MS).times(2)).openConnection(any()); + } + + @Test + public void testSerialProbesOnSecondValidNetwork() throws Exception { + setupResourceForSerialProbes(); + setStatus(mOtherHttpsConnection2, 204); + mSerialProbeLock.release(2); + runSerialProbesNetworkTest(NETWORK_VALIDATION_RESULT_VALID, + NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS); + verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(2)).sleep(anyInt()); + verify(mCleartextDnsNetwork, timeout(HANDLER_TIMEOUT_MS).times(4)).openConnection(any()); + } + + @Test + public void testSerialProbesOnInValidNetwork() throws Exception { + setupResourceForSerialProbes(); + mSerialProbeLock.release(2); + runSerialProbesNetworkTest(VALIDATION_RESULT_INVALID, 0); + verify(mDependencies, timeout(HANDLER_TIMEOUT_MS).times(2)).sleep(anyInt()); + verify(mCleartextDnsNetwork, timeout(HANDLER_TIMEOUT_MS).times(4)).openConnection(any()); + } + + @Test public void testIsCaptivePortal_FromExternalSource() throws Exception { assumeTrue(CaptivePortalDataShimImpl.isSupported()); assumeTrue(ShimUtils.isAtLeastS()); @@ -3523,6 +3557,18 @@ verify(mCallbacks, never()).showProvisioningNotification(any(), any()); } + private void waitForSerialProbes(int time) throws InterruptedException { + mSerialProbeLock.tryAcquire(time, TimeUnit.MILLISECONDS); + } + + private void setupResourceForSerialProbes() { + doReturn(true).when(mResources) + .getBoolean(R.bool.config_probe_multi_http_https_url_serial); + doReturn(SERIAL_PROBE_GAP_TIME_MS).when(mResources) + .getInteger(R.integer.config_serial_url_probe_gap_time); + setupResourceForMultipleProbes(); + } + private void setupResourceForMultipleProbes() { // Configure the resource to send multiple probe. doReturn(TEST_HTTPS_URLS).when(mResources) @@ -3620,6 +3666,17 @@ return nm; } + private void runSerialProbesNetworkTest(int testResult, int probesSucceeded) throws Exception { + final WrappedNetworkMonitor monitor = makeMonitor(CELL_METERED_CAPABILITIES); + notifyNetworkConnected(monitor, TEST_AGENT_CONFIG, + TEST_LINK_PROPERTIES, CELL_METERED_CAPABILITIES); + doAnswer(invocation -> { + waitForSerialProbes(invocation.getArgument(0)); + return null; + }).when(mDependencies).sleep(anyInt()); + verifyNetworkTested(testResult, probesSucceeded, 1); + } + private NetworkMonitor runPartialConnectivityNetworkTest(int probesSucceeded) throws Exception { final NetworkMonitor nm = runNetworkTest(NETWORK_VALIDATION_RESULT_PARTIAL,